Files
Dune/tests/fixture-truth-travel-order.ts

97 lines
6.6 KiB
TypeScript

import assert from 'node:assert/strict';
import { type Action, type Game } from '../game/engine';
import type { TruthQuestion } from '../game/truthtrance';
import {
actTruthBid, advanceTruthBid, createTruthBidFixture, truthBidClean, truthBidInventory,
truthBidPlayer, type TruthBidStep,
} from './fixture-truth-bid';
import {
settleTruthTravel, truthTravelGroup, truthTravelMove, truthTravelShip,
type TruthTravelExpression,
} from './fixture-truth-travel';
export type TruthTravelOrderStep = TruthBidStep;
export const actTruthTravelOrder = actTruthBid;
export const truthTravelOrderClean = truthBidClean;
export const truthTravelOrderInventory = truthBidInventory;
export const truthTravelOrderPlayer = truthBidPlayer;
export const settleTruthTravelOrder = settleTruthTravel;
/** The existing public H/A/E base33 start genuinely deals both printed
* Truthtrances to Harkonnen. Continue its actual auction/Revival and Harkonnen
* movement ending until Atreides' first unused native turn. Only the inherited
* unopened deal/ordinary lots/Spice selections and scalar lotteries are used.
* No hand, card custody, wallet, army, played clock, promise or receipt is set. */
export function createTruthTravelOrderFixture() {
const base = createTruthBidFixture({ window: 'waiting' });
const actions = structuredClone(base.sourceActions), suffixFrom = actions.length;
const game = advanceTruthBid(structuredClone(base.game), state => state.turn === 1 && state.phase === 5 &&
state.active === base.opponent && truthBidClean(state), actions);
const asker = base.asker, target = base.opponent, observer = base.target;
assert.equal(game.advanced, false); assert.deepEqual(game.expansions, []);
assert.deepEqual(truthBidPlayer(game, asker).hand.map(card => card.id), base.truths);
assert.equal(base.truths.length, 2); assert.equal(new Set(base.truths).size, 2);
assert.equal(truthBidPlayer(game, target).shipped, false); assert.equal(truthBidPlayer(game, target).moved, 0);
assert.deepEqual(truthBidPlayer(game, target).forces, { 'arrakeen:10': 10 });
assert.equal(truthBidPlayer(game, target).reserves, 10);
assert.ok(!game.movementRemaining?.includes(asker)); assert.ok(game.movementRemaining?.includes(target));
assert.equal(game.travelPromises?.length ?? 0, 0); assert.equal(game.shipmentPromises?.length ?? 0, 0);
assert.equal(game.movementPromises?.length ?? 0, 0); assert.equal(game.movementExpressionPromises?.length ?? 0, 0);
assert.deepEqual(truthBidInventory(game), base.originalCards);
const mixed = truthTravelGroup('or', truthTravelShip(), truthTravelMove());
return {
game, source: structuredClone(game), base, asker, target, observer, truths: structuredClone(base.truths),
setup: base.setup, afterSetup: base.afterSetup, beforeStartingDeal: base.beforeStartingDeal,
originalCards: base.originalCards, originalSpice: base.originalSpice,
dealOrder: base.dealOrder, spiceOrder: base.spiceOrder,
actions, sourceActions: actions, sourceActionCount: actions.length, sourceCount: actions.length,
continuation: structuredClone(actions.slice(suffixFrom)),
sourceCounts: { inheritedBidding: suffixFrom, nativeContinuation: actions.length - suffixFrom, selected: actions.length },
mixed,
travelQuestion: { kind: 'travel', target, turn: game.turn, mixed } satisfies TruthQuestion,
shipmentQuestion: { kind: 'shipment', target, territory: 'polar_sink', minimum: 1 } satisfies TruthQuestion,
movementQuestion: { kind: 'movement', target, territory: 'imperial_basin', minimum: 1 } satisfies TruthQuestion,
shipment: { type: 'ship', territory: 'polar_sink', sector: 0, amount: 1, elite: 0, allyPayment: 0 } satisfies Action,
alternativeShipment: { type: 'ship', territory: 'arrakeen', sector: 10, amount: 1, elite: 0, allyPayment: 0 } satisfies Action,
move: { type: 'move', from: 'arrakeen:10', territory: 'imperial_basin', sector: 9, amount: 1 } satisfies Action,
alternativeMove: { type: 'move', from: 'arrakeen:10', territory: 'carthag', sector: 11, amount: 1 } satisfies Action,
end: { type: 'endMovement' } satisfies Action,
sourceDisclosure: `${base.sourceDisclosure} This adapter continues the recorded original auction, normal Revival and ` +
'Harkonnen endMovement to clean phase5 Atreides. Both native opening Truthtrances remain physically in Harkonnen custody; ' +
'each later declaration uses a distinct genuine card. All selected action counts come from actual recorded arrays. ' +
'No native stopping card is present to demonstrate an opponent-caused loss of the final route, so this source proves ' +
'actual accepted-order binding and native compatibility, without claiming an opponent-release path.',
};
}
export type TruthTravelOrderFixture = ReturnType<typeof createTruthTravelOrderFixture>;
export function declareTruthTravelOrder(f: TruthTravelOrderFixture, source = f.game, copy: 0 | 1 = 0,
actions: TruthTravelOrderStep[] = []) {
const before = structuredClone(source), from = actions.length;
let game = actTruthTravelOrder(source, f.asker, { type: 'card', card: f.truths[copy] }, actions);
while (game.truthtrance?.stage === 'priority') {
const actor = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(actor);
game = actTruthTravelOrder(game, actor.id, { type: 'truthPass' }, actions);
}
assert.equal(game.truthtrance?.stage, 'ask');
return { game, before, actions, steps: structuredClone(actions.slice(from)), actionCount: actions.length - from };
}
export function askTruthTravelOrder(game: Game, asker: string, question: TruthQuestion,
actions: TruthTravelOrderStep[] = []): Game {
return actTruthTravelOrder(game, asker, { type: 'truthAsk', question: structuredClone(question) }, actions);
}
export function answerTruthTravelOrder(game: Game, target: string, answer: 'yes' | 'no',
actions: TruthTravelOrderStep[] = []): Game {
return actTruthTravelOrder(game, target, { type: 'truthAnswer', answer }, actions);
}
export function bindTruthTravelOrder(f: TruthTravelOrderFixture, source: Game, copy: 0 | 1,
question: TruthQuestion, answer: 'yes' | 'no' = 'yes', actions: TruthTravelOrderStep[] = []) {
const declared = declareTruthTravelOrder(f, source, copy, actions);
const asked = askTruthTravelOrder(declared.game, f.asker, question, actions);
const game = answerTruthTravelOrder(asked, f.target, answer, actions);
return { game, declared, asked, actions };
}
export function truthTravelOrderQuestion(f: TruthTravelOrderFixture, mixed: TruthTravelExpression): TruthQuestion {
return { kind: 'travel', target: f.target, turn: f.game.turn, mixed: structuredClone(mixed) };
}