Files
Dune/tests/fixture-semuta-shipment-price.ts
T

69 lines
3.8 KiB
TypeScript

import assert from 'node:assert/strict';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { TERRITORIES } from '../game/board';
import { presenceAt } from '../game/force-presence';
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
import { createSemutaGuildTransport } from './fixture-semuta-guild-transport';
import { nextSemutaGuildIncomeNativeStep } from './fixture-semuta-guild-income';
export function createSemutaShipmentPrice(options: { advanced?: boolean; homeworlds?: boolean } = {}) {
const original = createSemutaGuildTransport({ advanced: options.advanced,
route: options.homeworlds ? 'world' : 'reserve' });
let game = structuredClone(original.boughtKarama);
const actions: { actor: string; action: Action }[] = [];
for (let n = 0; !(game.phase === 5 && game.active === 'a' && !game.response &&
!game.decision && !game.phaseOpening && !game.pendingTreacheryDiscard) && n < 1600; n++) {
const next = nextSemutaGuildIncomeNativeStep(game); actions.push(next);
game = applyAction(game, next.actor, next.action);
}
assert.equal(game.phase, 5); assert.equal(game.active, 'a');
assert.ok(game.players.find(p => p.id === 'e')!.hand.some(card => card.id === original.karama));
assert.ok(game.players.find(p => p.id === 'g')!.hand.some(card => card.id === original.semuta));
const beforeAction = structuredClone(game);
const action: Action = { type: 'card', card: original.karama, mode: 'shipment', target: 'a' };
game = applyAction(game, 'e', action);
assert.equal(game.pendingTreacheryDiscard?.batch.cause, 'karama:shipment');
assert.equal(game.karamaShipping, null);
return { original, actions, beforeAction, action, game,
owner: 'e', beneficiary: 'a', guild: 'g', holder: 'g',
karama: original.karama, semuta: original.semuta,
provenance: [...original.provenance,
'Fork from actual paid Karama purchase; real native progression to the original Atreides shipment turn.',
'Emperor actually spends its own printed card for the other active player; no played state assigned.'] };
}
export function reactSemutaShipmentPrice(state: Game, claim: boolean) {
const pending = state.pendingTreacheryDiscard; assert.ok(pending);
const reaction = pending.reaction; assert.ok(reaction?.stage === 'offer');
const event = pending.batch.event;
let game = state;
if (claim) return applyAction(game, 'g', { type: 'semutaCommit', event });
for (const seat of game.players.filter(p => !reaction.passed.includes(p.id)))
game = applyAction(game, seat.id, { type: 'semutaPass', event });
return game;
}
export function semutaPurchasedShipment(game: Game, homeworlds = false) {
if (homeworlds) {
const quote = homeworldShipmentChoice(viewGame(game, 'a'), 'homeworld:emperor',
{ 'homeworld:atreides': { normal: 3, elite: 0 } });
assert.ok(quote.action, quote.blocked ?? undefined);
return { action: quote.action, target: 'homeworld:emperor', amount: 3, cost: 2 };
}
const destination = TERRITORIES.find(t => t.type === 'sand' &&
t.sectors.some(s => s !== game.storm) && game.players.every(p => presenceAt(p, t.id) === 0));
assert.ok(destination); const sector = destination.sectors.find(s => s !== game.storm)!;
const action: Action = { type: 'ship', territory: destination.id, sector,
amount: 3, elite: 0, allyPayment: 0 };
return { action, target: `${destination.id}:${sector}`, amount: 3, cost: 3 };
}
export function allowSemutaPurchasedShipment(state: Game) {
let game = state;
if (game.decision?.kind === 'guildShipment')
game = applyAction(game, 'g', { type: 'decision', allow: true });
else if (game.decision?.kind === 'homeworldShipmentGuild')
game = applyAction(game, 'g', { type: 'decision', event: game.decision.event, allow: true });
return game;
}