import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, type Game } from '../game/engine'; import { territory } from '../game/board'; import { createMoritaniStrongholdsFixture } from './fixture-moritani-strongholds'; /** This inherited fixture uses real Advanced Moritani/Guild/Emperor setup and * native phase actions through the opening Moritani placement decision, with * the Stronghold Cards module. Only physical board groups are staged after * that opening to expose a rare three-stronghold Guild victory. The prevention * case also transfers one original Karama to Guild's hand without duplication. * It is not a naturally earned full game or module-combination certification. */ function position() { const fixture = createMoritaniStrongholdsFixture(); const game = structuredClone(fixture.beforePlacement); assert.equal(game.phase, 8); assert.equal(game.decision?.kind, 'moritaniPlacement'); 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 winner = game.players.find(player => player.faction === 'guild')!; for (const name of ['arrakeen', 'carthag', 'sietch_tabr']) winner.forces[`${name}:${territory(name).sectors[0]}`] = 1; winner.reserves -= 3; const owner = game.players.find(player => player.faction === 'moritani')!; return { game, winner: winner.id, owner: owner.id }; } function pending(game: Game) { assert.equal(game.phase, 8); assert.equal(game.status, 'playing'); assert.deepEqual(game.winner, []); assert.ok(!game.victoryContinuation); } function finish(game: Game, winner: string) { const turn = game.turn; for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); assert.equal(game.turn, turn); assert.equal(game.status, 'finished'); assert.deepEqual(game.winner, [winner]); assert.equal(game.strongholdCards?.claimedTurn, turn); } function passPlacement(game: Game) { while (game.response) { const actor = game.players.find(player => !game.response!.passed.includes(player.id)); assert.ok(actor); game = applyAction(game, actor.id, { type: 'passResponse' }); } return game; } function stageKarama(game: Game, id: string) { const player = game.players.find(seat => seat.id === id)!; const held = player.hand.find(card => card.effect === 'karama'); if (held) return held.id; const deckIndex = game.deck.findIndex(card => card.effect === 'karama'); let card = deckIndex >= 0 ? game.deck.splice(deckIndex, 1)[0] : undefined; if (!card) { const donor = game.players.find(seat => seat.id !== id && seat.hand.some(candidate => candidate.effect === 'karama')); assert.ok(donor, 'One original Karama is still in the unplayed supply'); card = donor.hand.splice(donor.hand.findIndex(candidate => candidate.effect === 'karama'), 1)[0]; } if (player.hand.length >= 4) game.deck.push(player.hand.pop()!); player.hand.push(card); return card.id; } void test('Advanced Moritani declining placement leaves the winner pending until Mentat closes', () => { const f = position(); const game = applyAction(f.game, f.owner, { type: 'decision', decline: true }); pending(game); assert.equal(game.decision, null); assert.equal(game.moritaniTerror!.placementTurn, game.turn); finish(game, f.winner); }); void test('Advanced Moritani placement completes before the later victory check', () => { const f = position(); const token = f.game.moritaniTerror!.tokens.find(candidate => candidate.status === 'available')!; let game = applyAction(f.game, f.owner, { type: 'decision', token: token.id, territory: 'tueks_sietch' }); game = passPlacement(game); pending(game); assert.equal(game.moritaniTerror!.placementTurn, game.turn); assert.equal(game.moritaniTerror!.tokens.find(candidate => candidate.id === token.id)?.location, 'tueks_sietch'); finish(game, f.winner); }); void test('Advanced Moritani placement canceled by Karama still leaves victory until Mentat closes', () => { const f = position(), card = stageKarama(f.game, f.winner); const token = f.game.moritaniTerror!.tokens.find(candidate => candidate.status === 'available')!; const original = structuredClone(f.game.moritaniTerror!.tokens); let game = applyAction(f.game, f.owner, { type: 'decision', token: token.id, territory: 'tueks_sietch' }); assert.equal(game.response?.kind, 'moritaniPlacement'); game = applyAction(game, f.winner, { type: 'card', mode: 'cancel', card }); pending(game); assert.equal(game.moritaniTerror!.placementTurn, game.turn); assert.deepEqual(game.moritaniTerror!.tokens, original); assert.ok(game.discard.some(candidate => candidate.id === card)); finish(game, f.winner); });