import { query, type Entity, type World } from "../../src/index"; import { ActionCard, GameState, Player, Table, Tool, WoodenFishMarker, getPlayersInSeatOrder, getSelectedAction, getToolOf, type ActionKind, type ToolKind, } from "./components"; import type { RoundProposals } from "./rules"; export const ACTION_LABELS: Record = { fetchWater: "挑水", exchange: "交换", drink: "喝水", chant: "念经", rest: "休息", }; export const TOOL_LABELS: Record = { woodenFish: "木鱼", bucket: "水桶", bottle: "净瓶", woodenBucket: "木桶", bigBowl: "大碗", bigBucket: "大桶", mouse: "老鼠", shoulderPole: "扁担", waterJar: "水缸", ladle: "水瓢", }; export interface PlayerSnapshot { entity: Entity; name: string; seat: number; water: number; action: ActionKind | null; tool: ToolKind; toolWater: number; hasMarker: boolean; } export interface GameSnapshot { round: number; centralWater: number; phase: string; winner: Entity | 0; players: PlayerSnapshot[]; } export class GameLog { private readonly lines: string[] = []; add(line = ""): void { this.lines.push(line); } section(title: string): void { if (this.lines.length > 0) this.lines.push(""); this.lines.push(title); } entries(): readonly string[] { return this.lines; } toString(): string { return this.lines.join("\n"); } } export function snapshotGame(world: World): GameSnapshot { const table = world.getSingleton(Table); const marker = world.getSingleton(WoodenFishMarker).holder; const state = world.getSingleton(GameState); return { round: table.round, centralWater: table.centralWater, phase: state.phase, winner: state.winner, players: getPlayersInSeatOrder(world).map((player) => { const playerData = world.get(player, Player); const toolData = world.get(getToolOf(world, player), Tool); return { entity: player, name: playerData.name, seat: playerData.seat, water: playerData.water, action: getSelectedAction(world, player), tool: toolData.kind, toolWater: toolData.water, hasMarker: player === marker, }; }), }; } export function formatSnapshot(snapshot: GameSnapshot): string[] { return [ `第 ${snapshot.round} 轮 | 中央水=${snapshot.centralWater} | 阶段=${formatPhase(snapshot.phase)}`, ...snapshot.players.map(formatPlayerSnapshot), ]; } export function formatPlayerSnapshot(player: PlayerSnapshot): string { const marker = player.hasMarker ? " 🪵" : ""; const action = player.action ? ` | 行动=${ACTION_LABELS[player.action]}` : ""; return `${player.name}${marker}: 已喝=${player.water}, 道具=${TOOL_LABELS[player.tool]}(${player.toolWater})${action}`; } export function formatProposals(proposals: RoundProposals): string { const phases: string[] = []; if (proposals.chant) phases.push("念经"); if (proposals.fetchWater) phases.push("挑水"); if (proposals.exchange) phases.push("交换"); if (proposals.drink) phases.push("喝水"); return phases.length > 0 ? phases.join(" → ") : "无行动阶段"; } export function formatAvailableActions(world: World, player: Entity): string { const actions: ActionKind[] = []; for (const card of world.query(query(ActionCard))) { const data = world.get(card, ActionCard); if (data.owner === player && data.zone === "hand") actions.push(data.kind); } return actions.map((action) => ACTION_LABELS[action]).join("、"); } export function logSnapshot(log: GameLog, world: World): void { for (const line of formatSnapshot(snapshotGame(world))) { log.add(line); } } export function logPhaseDelta( log: GameLog, label: string, before: GameSnapshot, after: GameSnapshot, ): void { const changes: string[] = []; if (before.centralWater !== after.centralWater) { changes.push(`中央水 ${before.centralWater}→${after.centralWater}`); } for (const beforePlayer of before.players) { const afterPlayer = after.players.find( (p) => p.entity === beforePlayer.entity, )!; const playerChanges: string[] = []; if (beforePlayer.water !== afterPlayer.water) { playerChanges.push(`已喝 ${beforePlayer.water}→${afterPlayer.water}`); } if ( beforePlayer.tool !== afterPlayer.tool || beforePlayer.toolWater !== afterPlayer.toolWater ) { playerChanges.push( `道具 ${TOOL_LABELS[beforePlayer.tool]}(${beforePlayer.toolWater})→${TOOL_LABELS[afterPlayer.tool]}(${afterPlayer.toolWater})`, ); } if (beforePlayer.hasMarker !== afterPlayer.hasMarker) { playerChanges.push( afterPlayer.hasMarker ? "获得木鱼标记" : "失去木鱼标记", ); } if (playerChanges.length > 0) { changes.push(`${afterPlayer.name}: ${playerChanges.join(",")}`); } } if (changes.length === 0) { log.add(` ${label}: 无变化`); } else { log.add(` ${label}: ${changes.join(";")}`); } } export function formatPhase(phase: string): string { switch (phase) { case "setup": return "设置"; case "selecting": return "出牌"; case "resolving": return "结算"; case "gameOver": return "游戏结束"; default: return phase; } } export function winnerName(world: World): string | null { const winner = world.getSingleton(GameState).winner; if (winner === 0) return null; return world.get(winner, Player).name; }