import { World, query, type Entity } from "../../src/index"; import { ActionCard, GameState, Player, Table, setupGame, type ActionKind, } from "./components"; import { beginSelectionPhase, collectProposals, prepareNextRound, selectAction, chantPhase, fetchWaterPhase, exchangePhase, drinkPhase, endOfRoundPhase, } from "./rules"; import { ACTION_LABELS, GameLog, formatAvailableActions, formatProposals, logPhaseDelta, logSnapshot, snapshotGame, winnerName, } from "./logging"; export interface RandomPlaythroughOptions { seed?: number; playerNames?: readonly string[]; maxRounds?: number; } export interface RandomPlaythroughResult { world: World; log: string; roundsPlayed: number; } export function generateRandomPlayLog( options: RandomPlaythroughOptions = {}, ): RandomPlaythroughResult { const seed = options.seed ?? 20260701; const random = mulberry32(seed); const playerNames = options.playerNames ?? ["慧空", "明心", "了尘", "净远"]; const maxRounds = options.maxRounds ?? 200; const world = new World(); const players = setupGame(world, playerNames, random); const log = new GameLog(); log.section(`三个和尚随机对局(种子=${seed})`); log.add(`玩家:${playerNames.join("、")}`); log.add("初始状态:"); logSnapshot(log, world); let roundsPlayed = 0; while (world.getSingleton(GameState).phase !== "gameOver") { if (roundsPlayed >= maxRounds) { throw new Error(`随机对局未能在 ${maxRounds} 轮内结束`); } const round = world.getSingleton(Table).round; log.section(`第 ${round} 轮`); beginSelectionPhase(world); for (const player of players) { const action = pickRandom(availableActions(world, player), random); const playerName = world.get(player, Player).name; log.add( ` ${playerName} 选择 ${ACTION_LABELS[action]}(可选:${formatAvailableActions(world, player)})`, ); selectAction(world, player, action); } const proposals = collectProposals(world); log.add(` 本轮行动阶段:${formatProposals(proposals)}`); applyLoggedPhase(log, world, "念经", () => chantPhase(world)); applyLoggedPhase(log, world, "挑水", () => fetchWaterPhase(world)); applyLoggedPhase(log, world, "交换", () => exchangePhase(world)); applyLoggedPhase(log, world, "喝水", () => drinkPhase(world)); if (world.getSingleton(GameState).phase === "gameOver") break; applyLoggedPhase(log, world, "轮末道具", () => endOfRoundPhase(world)); if (world.getSingleton(GameState).phase === "gameOver") break; prepareNextRound(world); log.add(" 轮末状态:"); logSnapshot(log, world); roundsPlayed++; } roundsPlayed++; log.section("游戏结束"); log.add(`胜者:${winnerName(world)}`); logSnapshot(log, world); return { world, log: log.toString(), roundsPlayed }; } function applyLoggedPhase( log: GameLog, world: World, label: string, phase: () => void, ): void { const before = snapshotGame(world); phase(); const after = snapshotGame(world); logPhaseDelta(log, label, before, after); } function availableActions(world: World, player: Entity): ActionKind[] { 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); } } if (actions.length === 0) { throw new Error(`${world.get(player, Player).name} 没有可选行动牌`); } return actions; } function pickRandom(items: readonly T[], random: () => number): T { return items[Math.floor(random() * items.length)]; } function mulberry32(seed: number): () => number { let state = seed >>> 0; return () => { state += 0x6d2b79f5; let t = state; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } if (process.argv[1]?.endsWith("random-playthrough.ts")) { const result = generateRandomPlayLog(); console.log(result.log); }