import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, viewGame, type Game } from '../game/engine'; import { territory } from '../game/board'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { createTechTokens } from '../game/tech-tokens'; import { bindTruthBribe, createTruthBribeFixture, truthBribePlayer } from './fixture-truth-bribe'; /** Original Advanced Harkonnen/Atreides/Emperor setup and native actions reach * Collection. Only the conserved force locations are staged to expose a rare * victory board; the agreement case also stages a complete physical Tech set, * and the final-turn case stages turn ten. This is not a naturally earned * late-game board, a save/recovery test, or full module acceptance. */ function position(options: { holds?: number; simultaneous?: boolean; finalTurn?: boolean } = {}) { const source = createTruthBribeFixture({ advanced: true, phase: 7 }); const game = structuredClone(source.game); for (const player of game.players) { player.reserves += Object.values(player.forces).reduce((sum, count) => sum + count, 0); player.forces = {}; if (player.elites) { player.elites.reserves += Object.values(player.elites.forces).reduce((sum, count) => sum + count, 0); player.elites.forces = {}; } } const assign = (id: string, names: string[]) => { const player = truthBribePlayer(game, id); for (const name of names) player.forces[`${name}:${territory(name).sectors[0]}`] = 1; player.reserves -= names.length; }; const holds = options.holds ?? 3; assign('atreides', ['arrakeen', 'carthag', 'sietch_tabr'].slice(0, holds)); if (options.simultaneous) { assign('emperor', ['tueks_sietch', 'habbanya_ridge_sietch']); game.techTokens = createTechTokens(); for (const token of Object.values(game.techTokens)) token.owner = 'emperor'; } if (options.finalTurn) game.turn = 10; return { source, game }; } function enterMentat(initial: Game) { let game = initial; for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); assert.equal(game.phase, 8); assert.equal(game.status, 'playing'); assert.deepEqual(game.winner, []); assert.equal(game.victoryContinuation, undefined); return game; } function finishMentat(initial: Game) { let game = initial; for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); return game; } void test('Advanced winner waits through a real Truthtrance and its required Mentat payment', () => { const f = position(); let game = enterMentat(f.game); const bound = bindTruthBribe(f.source, 'yes', 3, f.source.recipient, game); game = bound.game; assert.throws(() => applyAction(game, f.source.payer, { type: 'ready' }), /Truthtrance|bribe|payment/i); const guide = viewGame(game, f.source.payer).bribePromiseGuide; assert.ok(guide); const payerSpice = truthBribePlayer(game, f.source.payer).spice; game = applyAction(game, f.source.payer, guide.action); assert.equal(truthBribePlayer(game, f.source.payer).spice, payerSpice - 3); assert.equal(truthBribePlayer(game, f.source.recipient).bribes, 3); game = finishMentat(game); assert.equal(game.status, 'finished'); assert.deepEqual(game.winner, ['atreides']); }); void test('Advanced Mentat with no winner advances once only after every player is ready', () => { let game = enterMentat(position({ holds: 2 }).game); const turn = game.turn; for (const player of game.players.slice(0, -1)) { game = applyAction(game, player.id, { type: 'ready' }); assert.equal(game.phase, 8); assert.equal(game.turn, turn); } game = applyAction(game, game.players.at(-1)!.id, { type: 'ready' }); assert.equal(game.status, 'playing'); assert.equal(game.turn, turn + 1); assert.equal(game.phase, 0); assert.deepEqual(game.winner, []); }); void test('Advanced final-turn fallback is checked after Mentat opportunities', () => { const game = enterMentat(position({ holds: 2, finalTurn: true }).game); const settled = finishMentat(game); assert.equal(settled.turn, 10); assert.equal(settled.status, 'finished'); assert.deepEqual(settled.winner, ['atreides']); }); void test('Advanced simultaneous qualifiers vote after Mentat and a unanimous agreement advances once', () => { const game = finishMentat(enterMentat(position({ simultaneous: true }).game)); const offer = game.victoryContinuation; assert.ok(offer); assert.equal(offer.resume, 'advance'); assert.deepEqual(new Set(offer.voters), new Set(['atreides', 'emperor'])); let next = applyAction(game, offer.voters[0], { type: 'victoryContinuation', event: offer.event, mode: 'continue', extra: 1 }); for (const voter of offer.voters.slice(1)) next = applyAction(next, voter, { type: 'victoryContinuation', event: offer.event, mode: 'accept' }); assert.equal(next.status, 'playing'); assert.equal(next.turn, game.turn + 1); assert.equal(next.phase, 0); assert.equal(next.victoryExtraStrongholds, 1); assert.ok(!next.victoryContinuation); }); void test('all four minimal bot profiles can finish Advanced Mentat before victory', () => { const initial = enterMentat(position().game); for (const difficulty of DIFFICULTIES) { let game = structuredClone(initial); for (const player of game.players) { const view = viewGame(game, player.id); view.players.find(seat => seat.id === player.id)!.bot = difficulty; const action = botActions(view).find(candidate => candidate.type === 'ready'); assert.ok(action, `${difficulty} can finish ${player.id}'s Mentat opportunity`); game = applyAction(game, player.id, action); } assert.equal(game.status, 'finished'); assert.deepEqual(game.winner, ['atreides']); } });