Files
Dune/tests/truth-turn-action-funding.test.ts

101 lines
7.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Action, type Game } from '../game/engine';
import type { TruthQuestion } from '../game/truthtrance';
import {
actTruthBid as act, createTruthBidFixture, truthBidInventory as inventory, truthBidPlayer as player,
type TruthBidStep,
} from './fixture-truth-bid';
import {
createTruthRevivalEarlyFixture, reachTruthRevivalEarlyOpportunity, settleTruthRevivalEarly,
} from './fixture-truth-revival-early';
/** These two sources actually deal Harkonnen both printed Truthtrances.
* Every commitment below uses its own card and native declaration/priority/
* Ask/answer; no hand, cash, force, clock or promise is installed. */
function ask(game: Game, asker: string, card: string, question: TruthQuestion, actions: TruthBidStep[]) {
game = act(game, asker, { type: 'card', card }, actions);
while (game.truthtrance?.stage === 'priority') {
const actor = game.players.find(candidate => !game.truthtrance!.passed.includes(candidate.id)); assert.ok(actor);
game = act(game, actor.id, { type: 'truthPass' }, actions);
}
return act(game, asker, { type: 'truthAsk', question }, actions);
}
function paymentQuestion(game: Game, target: string, recipient: string, minimum: number): TruthQuestion {
return { kind: 'turnActions', target, turn: game.turn,
mixed: { kind: 'action', action: { kind: 'payment', recipient, minimum } } };
}
function rejected(game: Game, actor: string, action: Action) {
const before = structuredClone(game); assert.throws(() => act(game, actor, action)); assert.deepEqual(game, before);
}
function physicalTruths(game: Game, truths: string[], originalCards: string[]) {
for (const truth of truths) assert.equal(game.discard.filter(card => card.id === truth).length, 1);
assert.deepEqual(inventory(game), originalCards);
}
for (const first of ['bid', 'aggregate'] as const)
void test(`two original Truths reject a second unfunded payment or bid Yes with ${first} accepted first`, () => {
const f = createTruthBidFixture({ window: 'active' }), actions: TruthBidStep[] = [];
assert.equal(f.game.phase, 3); assert.equal(f.game.auction?.active, f.target);
assert.equal(player(f.game, f.target).spice, 10);
assert.equal(f.sourceActionCount, f.sourceActions.length);
assert.deepEqual(player(f.game, f.asker).hand.map(card => card.id), f.truths);
const bid: TruthQuestion = { ...f.question, minimum: 10 };
const aggregate = paymentQuestion(f.game, f.target, f.asker, 10);
let game = ask(f.game, f.asker, f.truths[0], first === 'bid' ? bid : aggregate, actions);
game = act(game, f.target, { type: 'truthAnswer', answer: 'yes' }, actions);
const earlierHistory = structuredClone(game.truthHistory![0]);
const earlierRow = structuredClone(first === 'bid' ? game.bidPromises![0] : game.turnActionPromises![0]);
game = ask(game, f.asker, f.truths[1], first === 'bid' ? aggregate : bid, actions);
const answers = first === 'bid' ? viewGame(game, f.target).truthTurnActionAnswers : viewGame(game, f.target).truthBidAnswers;
assert.deepEqual(answers, ['no']); rejected(game, f.target, { type: 'truthAnswer', answer: 'yes' });
assert.deepEqual(game.truthHistory![0], earlierHistory);
assert.deepEqual(first === 'bid' ? game.bidPromises![0] : game.turnActionPromises![0], earlierRow);
assert.ok(player(game, f.asker).hand.some(card => card.id === f.truths[1]));
assert.equal(player(game, f.target).spice, 10);
game = act(game, f.target, { type: 'truthAnswer', answer: 'no' }, actions);
assert.equal(game.truthHistory!.length, 2); assert.deepEqual(game.truthHistory![0], earlierHistory);
assert.deepEqual(first === 'bid' ? game.bidPromises![0] : game.turnActionPromises![0], earlierRow);
game = act(game, f.target, first === 'bid'
? { type: 'bid', amount: 10, allyPayment: 0 }
: { type: 'bribe', target: f.asker, amount: 10 }, actions);
assert.equal(first === 'bid' ? game.bidPromises![0].fulfilled : game.turnActionPromises![0].fulfilled, true);
assert.ok(!(first === 'bid' ? game.bidPromises![0].released : game.turnActionPromises![0].released));
physicalTruths(game, f.truths, f.originalCards);
});
for (const first of ['revival', 'aggregate'] as const)
void test(`two earned native Truths reject a second unfunded payment or Revival Yes with ${first} accepted first`, () => {
// The original source paid six for a real shipment of three; the next
// Storm killed those counters. Its four remaining spice fund exactly the
// ordinary three-force return (one free, two paid), or one payment of four.
const f = createTruthRevivalEarlyFixture({ phase: 3 }), actions = structuredClone(f.sourceActions);
assert.deepEqual(f.staging, []); assert.equal(f.sourceActionCount, f.sourceActions.length);
let game = reachTruthRevivalEarlyOpportunity(f.game, f.target, actions);
assert.equal(game.turn, 2); assert.equal(game.phase, 4); assert.equal(player(game, f.target).spice, 4);
assert.equal(player(game, f.target).tanks, 3); assert.equal(player(game, f.target).reserves, 17);
assert.deepEqual(player(game, f.asker).hand.map(card => card.id), f.truths);
const revival: TruthQuestion = { kind: 'revival', target: f.target, turn: game.turn, counter: 'total', minimum: 3 };
const aggregate = paymentQuestion(game, f.target, f.asker, 4);
game = ask(game, f.asker, f.truths[0], first === 'revival' ? revival : aggregate, actions);
game = act(game, f.target, { type: 'truthAnswer', answer: 'yes' }, actions);
const earlierHistory = structuredClone(game.truthHistory![0]);
const earlierRow = structuredClone(first === 'revival' ? game.revivalPromises![0] : game.turnActionPromises![0]);
game = ask(game, f.asker, f.truths[1], first === 'revival' ? aggregate : revival, actions);
const answers = first === 'revival' ? viewGame(game, f.target).truthTurnActionAnswers : viewGame(game, f.target).truthRevivalAnswers;
assert.deepEqual(answers, ['no']); rejected(game, f.target, { type: 'truthAnswer', answer: 'yes' });
assert.deepEqual(game.truthHistory![0], earlierHistory);
assert.deepEqual(first === 'revival' ? game.revivalPromises![0] : game.turnActionPromises![0], earlierRow);
assert.ok(player(game, f.asker).hand.some(card => card.id === f.truths[1]));
game = act(game, f.target, { type: 'truthAnswer', answer: 'no' }, actions);
assert.equal(game.truthHistory!.length, 2); assert.deepEqual(game.truthHistory![0], earlierHistory);
assert.deepEqual(first === 'revival' ? game.revivalPromises![0] : game.turnActionPromises![0], earlierRow);
game = settleTruthRevivalEarly(act(game, f.target, first === 'revival'
? { type: 'revive', amount: 3, elite: 0 }
: { type: 'bribe', target: f.asker, amount: 4 }, actions), actions);
assert.equal(player(game, f.target).spice, 0);
assert.equal(first === 'revival' ? game.revivalPromises![0].fulfilled : game.turnActionPromises![0].fulfilled, true);
assert.ok(!(first === 'revival' ? game.revivalPromises![0].released : game.turnActionPromises![0].released));
physicalTruths(game, f.truths, f.originalCards);
});