Files
Dune/tests/fixture-bureaucrat-qualifying-shipment.ts

97 lines
4.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import {
advanceBureaucratQualifyingSplit, createBureaucratQualifyingSplitSetup,
type BureaucratQualifyingSplitStep,
} from './fixture-bureaucrat-qualifying-split';
export interface BureaucratQualifyingShipmentDeclaration {
beforeAction: Game;
payer: string;
action: Action;
}
export function createBureaucratQualifyingShipmentFixture({
advanced = false, primary = 5, donor = 3, ownerFunding = false,
}: { advanced?: boolean; primary?: number; donor?: number; ownerFunding?: boolean } = {}) {
const setup = createBureaucratQualifyingSplitSetup(advanced);
const allianceIndex = setup.actions.findIndex(step => step.action.type === 'alliance');
const actions = ownerFunding ? setup.actions.slice(0, allianceIndex) : [...setup.actions];
const staging = [...setup.staging];
const donorId = ownerFunding ? setup.trainer : setup.donor;
let game = structuredClone(ownerFunding ? setup.worm : setup.game);
if (ownerFunding) {
for (const [actor, target] of [[setup.payer, donorId], [donorId, setup.payer]]) {
const action: Action = { type: 'alliance', target };
actions.push({ actor, action });
game = applyAction(game, actor, action);
}
}
const alliance = ownerFunding ? structuredClone(game) : setup.alliance;
game = advanceBureaucratQualifyingSplit(game, state => state.phase === 5 &&
state.active === setup.payer && !state.phaseOpening && !state.response && !state.decision, actions);
assert.equal(game.turn, 2);
const sourceSetup = structuredClone(game);
const sourceArmy = structuredClone(game.players.find(p => p.id === setup.payer)!);
assert.equal(sourceArmy.reserves, 10);
assert.deepEqual(sourceArmy.forces, { 'carthag:11': 10 });
assert.ok(game.players.every(p => !p.forces['habbanya_ridge_sietch:17']));
assert.equal(game.players.find(p => p.id === setup.donor)!.spice, 6);
assert.equal(game.players.find(p => p.id === setup.guild)!.spice, advanced ? 6 : 5);
// Keep the original singleton in physical custody so the original income
// cancellation window is exercised, rather than manufacturing a response.
const responder = game.players.find(p => p.id === setup.emperor)!;
let karama = responder.hand.find(card => card.effect === 'karama');
if (!karama) {
const index = game.deck.findIndex(card => card.effect === 'karama');
assert.ok(index >= 0);
karama = game.deck.splice(index, 1)[0];
responder.hand.push(karama);
staging.push(`Conserved original ${karama.id}: undealt deck to Emperor hand for native income cancellation.`);
}
const beforePledge = structuredClone(game);
const pledge: Action = { type: 'pledgeAid', amount: donor };
actions.push({ actor: donorId, action: pledge });
game = applyAction(game, donorId, pledge);
const beforeAction = structuredClone(game);
const amount = primary + donor;
const action: Action = { type: 'ship', territory: 'habbanya_ridge_sietch', sector: 17, amount, allyPayment: donor };
assert.equal(game.aid[donorId].amount, donor);
return { ...setup, donor: donorId, alliance, game, sourceSetup, sourceArmy, beforePledge, pledge, beforeAction,
action, primary, donorAmount: donor, amount, target: 'habbanya_ridge_sietch:17',
karama, responder: responder.id, actions, staging };
}
/** Declare once; the original Guild interception, when present, commits it. */
export function declareBureaucratQualifyingShipment(
fixture: BureaucratQualifyingShipmentDeclaration,
) {
const actions: BureaucratQualifyingSplitStep[] = [];
let game = applyAction(structuredClone(fixture.beforeAction), fixture.payer, fixture.action);
actions.push({ actor: fixture.payer, action: fixture.action });
const declared = structuredClone(game);
if (game.decision?.kind === 'guildShipment') {
const actor = game.decision.player;
const action: Action = { type: 'decision', allow: true };
actions.push({ actor, action });
game = applyAction(game, actor, action);
}
return { game, declared, paid: structuredClone(game), actions };
}
/** Pass only original legal Karama responders; stop at the owner choice. */
export function allowBureaucratQualifyingShipmentIncome(state: Game) {
let game = structuredClone(state);
for (let n = 0; game.response?.kind === 'guildIncome' && n < 12; n++) {
const actor = game.players.find(p => {
const controls = viewGame(game, p.id).responseControls;
return controls && !controls.hasPassed && controls.cancelCards.length > 0;
});
assert.ok(actor, 'The conserved Karama must retain its original income response');
game = applyAction(game, actor.id, { type: 'passResponse' });
}
assert.notEqual(game.response?.kind, 'guildIncome');
return game;
}