import assert from 'node:assert/strict'; import { mock } from 'node:test'; import { applyAction, createGame, joinGame, newPlayer, viewGame, type Game, } from '../game/engine'; import { LEADER_SKILL_CARDS } from '../game/leader-skill-cards'; import { botActions } from '../game/bots'; export const mentatReload = (game: Game): Game => JSON.parse(JSON.stringify(game)) as Game; export const mentatPlayer = (game: Game, id: string) => game.players.find((player) => player.id === id)!; export function takeMentatCard(game: Game, player: string, name: string) { const pool = name === 'Stone Burner' || name === 'Mirror Weapon' ? game.richeseCache! : game.deck; const index = pool.findIndex((card) => card.name === name); assert.ok(index >= 0, `Missing fixture card ${name}`); const [card] = pool.splice(index, 1); mentatPlayer(game, player).hand.push(card); return card; } /** Genuine public skill assignment and setup, followed by a conserved battle position. */ export function mentatQuestionGame({ advanced = false, choam = false, independent = false, cards = ['Crysknife', 'Snooper'], begin = true, captured = false, }: { advanced?: boolean; choam?: boolean; independent?: boolean; cards?: string[]; begin?: boolean; captured?: boolean; } = {}): Game { const native = choam ? 'richese' : 'emperor'; let game = createGame( 'MENTATQA', newPlayer('a', 'Mentat owner', native), advanced, choam ? ['choam'] : [], ); joinGame( game, newPlayer('d', 'Questioned opponent', captured ? 'harkonnen' : 'atreides'), ); joinGame(game, newPlayer('o', 'Observer', 'guild')); game = applyAction(game, game.host, { type: 'moduleOption', module: 'leaderSkills', enabled: true }); if (independent) game = applyAction(game, game.host, { type: 'moduleOption', module: 'ecazTreachery', enabled: true }); for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); const index = LEADER_SKILL_CARDS.findIndex((card) => card.id === 'mentat'); let cursor = LEADER_SKILL_CARDS.length - 1; mock.method(globalThis.crypto, 'getRandomValues', (array: Uint32Array) => { array[0] = cursor-- === index ? 0 : 0xffffffff; return array; }); try { game = applyAction(game, game.host, advanced ? { type: 'start', advancedPreview: true } : { type: 'start' }); } finally { mock.restoreAll(); } assert.ok(game.leaderSkills!.offers.a.cards.includes('mentat')); for (let step = 0; game.status === 'setup' && step < 80; step++) { let progress = false; for (const player of game.players) { const offer = game.leaderSkills!.offers[player.id]; const actions = game.setupStage === 'leaderSkills' && offer ? [ { type: 'leaderSkill', event: offer.event, skill: player.id === 'a' ? 'mentat' : offer.cards[0], leader: `${player.faction}-0`, }, ] : game.setupStage === 'traitors' && player.traitorChoices.length ? [{ type: 'traitor', leader: player.traitorChoices[0] }] : botActions(viewGame(game, player.id)); for (const action of actions) try { game = applyAction(game, player.id, action); progress = true; break; } catch {} if (progress) break; } assert.ok(progress, `Setup stalled at ${game.setupStage}`); } assert.equal(game.status, 'playing'); for (const player of game.players) { game.deck.push(...player.hand); Object.assign(player, { hand: [], forces: player.id === 'o' ? {} : { 'arrakeen:10': 5 }, reserves: player.id === 'o' ? 20 : 15, tanks: 0, spice: 20, }); } takeMentatCard(game, 'a', 'Baliset'); takeMentatCard(game, 'o', 'Karama'); for (const name of cards) takeMentatCard(game, 'd', name); if (captured) mentatPlayer(game, 'a').leaders[0].capturedBy = 'd'; Object.assign(game, { phase: 6, turn: 2, storm: 18, active: 'a', order: ['a', 'd', 'o'], ready: [], decision: null, response: null, phaseOpening: null, }); return begin ? applyAction(game, 'a', { type: 'chooseBattle', territory: 'arrakeen', target: 'd', }) : game; } export function finishMentatPosture(game: Game, hide = true): Game { while (game.decision?.kind === 'leaderSkillVisibility') { const decision = game.decision; game = applyAction(game, decision.player, { type: 'leaderSkillVisibility', event: decision.event, hide: decision.player === 'a' ? hide : false, }); } return game; }