82 lines
4.7 KiB
TypeScript
82 lines
4.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { type Action, type Game } from '../game/engine';
|
|
import type { NoFieldValue } from '../game/richese-no-field';
|
|
import {
|
|
actNoFieldAlliedSeparation as act, advanceNoFieldAlliedSeparation as advance,
|
|
createNoFieldAlliedSeparationFixture, noFieldAlliedSeparationClean as clean,
|
|
noFieldAlliedSeparationInventory as inventory, noFieldAlliedSeparationPlayer as player,
|
|
nextNoFieldAlliedSeparation,
|
|
type NoFieldAlliedSeparationStep,
|
|
} from './fixture-no-field-allied-separation';
|
|
|
|
export { act, advance, clean, inventory, player };
|
|
|
|
/** Start with the existing genuine Basic setup and first Storm. Replace only
|
|
* the source's prospective marker shipment with a paid two-force shipment;
|
|
* Emperor's paid two forces are already across the actual Storm. The next
|
|
* real worm opens Nexus and both factions consent before Richese's next turn.
|
|
* Only unopened Spice-card order is selected; no played force, purse,
|
|
* alliance, phase, marker or receipt is installed. */
|
|
export function createNoFieldAlliedTransitFixture(value: NoFieldValue = 0) {
|
|
const base = createNoFieldAlliedSeparationFixture({advanced: false, value});
|
|
let game = structuredClone(base.markerShipment.before);
|
|
const actor = base.actor, ally = base.ally, other = base.other;
|
|
const actions: NoFieldAlliedSeparationStep[] = [];
|
|
const perform = (id: string, action: Action) => { game = act(game, id, action, actions); };
|
|
const reach = (predicate: (state: Game) => boolean) => {
|
|
for (let count = 0; count < 1800 && !predicate(game); count++) {
|
|
const own = player(game, actor);
|
|
// The two-force first shipment costs four, so native turn-two Charity
|
|
// must actually bring Richese from one spice to two for the marker.
|
|
if (game.turn === 2 && game.phase === 2 && clean(game) && own.spice < 2 && own.charityTurn !== game.turn) {
|
|
perform(actor, {type: 'charity'}); continue;
|
|
}
|
|
const step = nextNoFieldAlliedSeparation(game); assert.ok(step);
|
|
perform(step.actor, step.action);
|
|
}
|
|
assert.ok(predicate(game), 'The real transit source must reach its requested boundary.');
|
|
};
|
|
assert.equal(game.active, actor); assert.equal(game.turn, 1); assert.equal(game.storm, 10);
|
|
assert.equal(player(game, ally).forces['imperial_basin:11'], 2);
|
|
assert.equal(player(game, actor).noField!.deployed, null);
|
|
const beforePhysical = structuredClone(game);
|
|
perform(actor, {type: 'ship', territory: 'imperial_basin', sector: 9, amount: 2, elite: 0, allyPayment: 0});
|
|
reach(clean);
|
|
assert.equal(player(game, actor).forces['imperial_basin:9'], 2);
|
|
assert.equal(player(beforePhysical, actor).spice - player(game, actor).spice, 4);
|
|
perform(actor, {type: 'endMovement'});
|
|
reach(state => state.turn === 2 && state.phase === 0 && clean(state));
|
|
const originalSpice = game.spiceDeck.map(card => JSON.stringify(card)).sort();
|
|
for (let position = 0; position < 2; position++) {
|
|
const from = game.spiceDeck.findIndex((card, index) => index >= position && (position === 0
|
|
? 'worm' in card && !card.greatMaker && !card.suppressed
|
|
: 'territory' in card && !card.discovery && card.territory !== 'imperial_basin' &&
|
|
game.players.every(seat => !Object.entries(seat.forces).some(([key, count]) =>
|
|
count > 0 && key.startsWith(`${card.territory}:`)))));
|
|
assert.ok(from >= position);
|
|
game.spiceDeck.splice(position, 0, game.spiceDeck.splice(from, 1)[0]);
|
|
}
|
|
assert.deepEqual(game.spiceDeck.map(card => JSON.stringify(card)).sort(), originalSpice);
|
|
reach(state => state.turn === 2 && state.phase === 1 && state.nexus &&
|
|
!state.spiceWindow && !state.spiceResolution && clean(state));
|
|
perform(actor, {type: 'alliance', target: ally});
|
|
perform(ally, {type: 'alliance', target: actor});
|
|
assert.equal(player(game, actor).ally, ally); assert.equal(player(game, ally).ally, actor);
|
|
reach(state => state.turn === 2 && state.phase === 5 && state.active === actor && clean(state));
|
|
const own = player(game, actor), marker = own.noField?.tokens.find(item => item.value === value);
|
|
assert.ok(marker && own.noFieldEvent);
|
|
assert.equal(own.forces['imperial_basin:9'], 2);
|
|
assert.equal(player(game, ally).forces['imperial_basin:11'], 2);
|
|
assert.equal(own.allySinceTurn, game.turn);
|
|
assert.ok(own.spice >= 2);
|
|
const ship: Action = {type: 'ship', noField: marker.id, event: own.noFieldEvent,
|
|
territory: 'imperial_basin', sector: 9, allyPayment: 0};
|
|
return {game, actor, ally, other, value, token: marker.id, ship, actions,
|
|
baseline: 2, original: inventory(game), base};
|
|
}
|
|
|
|
/** Resolve the original No-Field response and any native arrival controls. */
|
|
export function finishAlliedMarkerShipment(game: Game): Game {
|
|
return advance(game, state => state.phase === 5 && clean(state) && !state.pendingShipment);
|
|
}
|