98 lines
4.8 KiB
TypeScript
98 lines
4.8 KiB
TypeScript
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);
|
|
});
|