import assert from 'node:assert/strict'; import test from 'node:test'; import { viewGame, type Action } from '../game/engine'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { advanceTruthRevival, nextTruthRevival, truthRevivalClean, truthRevivalPlayer } from './fixture-truth-revival'; import { actFutureBattleCharity as act, askFutureBattleCharity as ask, createFutureBattleCharityFixture, futureBattleCharityRecords as records, reachFutureBattleCharityBattle as reachBattle, reachFutureBattleCharityWindow as reachMovement, } from './fixture-truth-future-battle-charity'; function fixture() { const source = createFutureBattleCharityFixture(); const game = advanceTruthRevival(structuredClone(source.game), state => state.turn === source.game.turn && state.phase === 3 && !!state.auction && truthRevivalClean(state)); assert.equal(game.phase, 3); assert.ok(viewGame(game, source.asker).truthFutureBattleLeaderTargets.includes(source.target)); return { ...source, game }; } for (const answer of ['yes', 'no'] as const) { void test(`Basic Bidding future-battle ${answer} preserves the lot and binds the later battle`, () => { const initial = fixture(); const gameWithBid = act(initial.game, initial.game.auction!.active, {type: 'bid', amount: 1}); const f = { ...initial, game: gameWithBid }; const lot = structuredClone(f.game.auction); let game = act(ask(f), f.target, {type: 'truthAnswer', answer}); assert.deepEqual(game.auction, lot); assert.equal(records(game)[0].status, 'waiting'); game = reachBattle(reachMovement(game, f), f); assert.equal(records(game)[0].status, 'bound'); const plan = (leader: string): Action => ({type: 'battlePlan', leader, dial: 0, support: 0, weapon: null, defense: null}); const before = structuredClone(game); assert.throws(() => act(game, f.target, plan(answer === 'yes' ? f.alternative : f.leader))); assert.deepEqual(game, before); game = act(game, f.target, plan(answer === 'yes' ? f.leader : f.alternative)); const opponent = game.battle!.attacker === f.target ? game.battle!.defender : game.battle!.attacker; const other = truthRevivalPlayer(game, opponent).leaders.find(leader => !leader.dead && !leader.capturedBy && !leader.gholaBy && !leader.usedAt); assert.ok(other); game = act(game, opponent, plan(other.id)); assert.ok(game.battle?.revealed); assert.equal(records(game)[0].status, 'settled'); assert.equal(game.discard.filter(card => card.id === f.truth).length, 1); }); } void test('Basic Bidding conditional answer expires without a respondent battle', () => { const f = fixture(); let game = act(ask(f), f.target, {type: 'truthAnswer', answer: 'yes'}); game = reachMovement(game, f); assert.ok(Object.values(truthRevivalPlayer(game, f.target).forces).every(amount => amount === 0)); for (let count = 0; count < 1200 && game.turn === f.game.turn && game.phase <= 6 && game.status === 'playing'; count++) { if (game.phase === 5 && game.active && truthRevivalClean(game)) { game = act(game, game.active, {type: 'endMovement'}); continue; } const next = nextTruthRevival(game); assert.ok(next); game = act(game, next.actor, next.action); } assert.equal(records(game)[0].status, 'expired'); assert.equal(game.discard.filter(card => card.id === f.truth).length, 1); }); void test('each AI difficulty can legally answer the Bidding question', () => { for (const difficulty of DIFFICULTIES) { const f = fixture(); const asked = ask(f); truthRevivalPlayer(asked, f.target).bot = difficulty; const answer = botActions(viewGame(asked, f.target)).find(action => action.type === 'truthAnswer'); assert.ok(answer); const game = act(asked, f.target, answer); assert.equal(records(game)[0].status, 'waiting'); } });