// Shared test rig: a deterministic two-wizard game plus the moves every // suite makes — force a card into a hand, burn the no-combat first round, // stand two wizards face to face, cast-and-let-resolve. import { applyCommand, activePlayer, boardView, createGame, type Command, type GameState, type PlayerId, } from "../src/game"; import { cellKey, SIDES, stepTarget, type Cell, type Side } from "../src/board"; import type { CardInstance } from "../src/cards"; export function newGame(seed = 42, players: PlayerId[] = ["alice", "bob"]) { return createGame({ playerIds: players, seed, sets: ["basic"] }); } export function newExpansionGame(seed = 42, players: PlayerId[] = ["alice", "bob"]) { return createGame({ playerIds: players, seed, sets: ["basic", "expansion1"] }); } export function must(state: GameState, player: PlayerId, command: Command): GameState { const result = applyCommand(state, player, command); if (!result.ok) throw new Error(`command failed: ${result.error}`); return result.state; } export function giveCard(state: GameState, playerId: PlayerId, cardId: string, tag = "T", slot = 0): CardInstance { const p = state.players.find((p) => p.id === playerId)!; const instance = { instanceId: `${cardId}#${tag}`, cardId }; p.hand[slot] = instance; return instance; } /** Burn the no-combat first round: both players pass their opening turn. */ export function toRound2(state: GameState): GameState { state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 }); state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 }); return state; } /** Stand the other wizard on the active one's square. */ export function faceOff(state: GameState): { attacker: PlayerId; defender: PlayerId } { const attacker = activePlayer(state); const defender = state.players.find((p) => p.id !== attacker.id)!; defender.position = { ...attacker.position }; return { attacker: attacker.id, defender: defender.id }; } /** Cast at a player and let it resolve uncountered. */ export function castAt( state: GameState, attacker: PlayerId, defender: PlayerId, card: CardInstance, extra: Partial> = {}, ): GameState { state = must(state, attacker, { type: "cast", instanceId: card.instanceId, target: { kind: "player", playerId: defender }, ...extra, }); return must(state, defender, { type: "pass" }); } /** An adjacent, walkable square holding no home, treasure, or wizard. */ export function emptyNeighborCell(state: GameState, of: Cell): { cell: Cell; side: Side } { const view = boardView(state); for (const side of SIDES) { const t = stepTarget(view, of, side); if (t.kind !== "step") continue; const key = cellKey(t.to); if (view.homes.some((h) => cellKey(h) === key)) continue; if (state.treasures.some((tr) => tr.position && cellKey(tr.position) === key)) continue; if (state.players.some((p) => cellKey(p.position) === key)) continue; return { cell: t.to, side }; } throw new Error("no empty neighbor"); }