Files
Dune/tests/fixture-truth-future-battle-revival.ts
Quintine e896570f07
Verify and publish checkpoint container / container (push) Successful in 11m2s
Add optional short alliance victory
2026-10-28 21:40:24 +11:00

92 lines
5.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import { TERRITORIES } from '../game/board';
import { viewGame, type Action, type Game } from '../game/engine';
import {
actTruthRevival, createTruthRevivalFixture, nextTruthRevival, truthRevivalClean,
truthRevivalInventory, truthRevivalPlayer,
} from './fixture-truth-revival';
import { futureBattleLeaderRecords } from './fixture-truth-future-battle-leader';
export const actFutureBattleRevival = actTruthRevival;
export const futureBattleRevivalInventory = truthRevivalInventory;
export const futureBattleRevivalRecords = futureBattleLeaderRecords;
/** The source has real Basic turn-two storm casualties and has reached quiet
* Revival, with one original Truthtrance still in Harkonnen's hand. */
export function createFutureBattleRevivalFixture() {
const base = createTruthRevivalFixture();
const game = structuredClone(base.game);
assert.equal(game.phase, 4); assert.equal(game.advanced, false);
assert.deepEqual(game.expansions, []); assert.ok(truthRevivalClean(game));
const target = truthRevivalPlayer(game, base.target);
const available = target.leaders.filter(leader => !leader.dead && !leader.capturedBy && !leader.gholaBy && !leader.usedAt);
assert.ok(available.length >= 2);
const leader = available[0].id, alternative = available[1].id;
const question = {kind: 'futureBattleLeader', target: base.target, turn: game.turn, leader};
return { base, game, source: structuredClone(game), asker: base.asker, target: base.target,
truth: base.truth, leader, alternative, question, originalCards: base.originalCards,
ask: {type: 'truthAsk', question} as unknown as Action };
}
export type FutureBattleRevivalFixture = ReturnType<typeof createFutureBattleRevivalFixture>;
export function askFutureBattleRevival(f: FutureBattleRevivalFixture): Game {
let game = actFutureBattleRevival(f.game, f.asker, {type: 'card', card: f.truth});
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
const seat = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(seat);
game = actFutureBattleRevival(game, seat.id, {type: 'truthPass'});
}
assert.equal(game.truthtrance?.stage, 'ask');
return actFutureBattleRevival(game, f.asker, f.ask);
}
/** Finish Revival and skip unrelated Movement opportunities, preserving the
* target's own turn for its explicit decision or no-battle expiry. */
export function reachFutureBattleRevivalMovement(game: Game, f: FutureBattleRevivalFixture): Game {
for (let count = 0; count < 800; count++) {
if (game.phase === 5 && game.active === f.target && truthRevivalClean(game)) return game;
if (game.phase === 5 && game.active && truthRevivalClean(game)) {
game = actFutureBattleRevival(game, game.active, {type: 'endMovement'}); continue;
}
const next = nextTruthRevival(game); assert.ok(next, 'Finish the original native Revival opportunity.');
game = actFutureBattleRevival(game, next.actor, next.action);
}
throw Error('The original Revival source did not reach the respondent Movement turn.');
}
/** Make a real one-force shipment into an existing unallied opponent group,
* then use ordinary battle-selection and preparation actions. */
export function reachFutureBattleRevivalBattle(game: Game, f: FutureBattleRevivalFixture): Game {
const target = truthRevivalPlayer(game, f.target);
const location = game.players.filter(player => player.id !== f.target && player.id !== target.ally)
.flatMap(player => Object.entries(player.forces)).find(([key, count]) => {
const separator = key.lastIndexOf(':'), name = key.slice(0, separator), sector = Number(key.slice(separator + 1));
return count > 0 && sector !== game.storm && TERRITORIES.some(territory => territory.id === name && territory.sectors.includes(sector));
});
assert.ok(location, 'The native source retains an eligible opponent group.');
const separator = location[0].lastIndexOf(':'), territory = location[0].slice(0, separator);
const sector = Number(location[0].slice(separator + 1));
game = actFutureBattleRevival(game, f.target, {type: 'ship', territory, sector, amount: 1, elite: 0, allyPayment: 0});
for (let count = 0; count < 800; count++) {
if (game.phase === 5 && game.active === f.target && truthRevivalClean(game)) {
game = actFutureBattleRevival(game, f.target, {type: 'endMovement'}); continue;
}
if (game.phase === 5 && game.active && truthRevivalClean(game)) {
game = actFutureBattleRevival(game, game.active, {type: 'endMovement'}); continue;
}
if (game.phase === 6 && game.battle && [game.battle.attacker, game.battle.defender].includes(f.target) &&
truthRevivalClean(game) && !game.battle.preparation && game.battle.preLeader?.closed !== false) return game;
if (game.phase === 6 && !game.battle && game.active && truthRevivalClean(game)) {
const choice = viewGame(game, game.active).battleChoices.find(row => row.territory === territory &&
[row.attacker, row.defender].includes(f.target));
if (choice) {
game = actFutureBattleRevival(game, choice.chooser, {type: 'chooseBattle', territory: choice.territory,
target: choice.attacker === choice.chooser ? choice.defender : choice.attacker});
continue;
}
}
const next = nextTruthRevival(game); assert.ok(next, 'Finish the original native battle preparation.');
game = actFutureBattleRevival(game, next.actor, next.action);
}
throw Error('The native source did not reach the target’s first battle.');
}