Files
Dune/tests/selected-discovery-skills-fixture.ts
T
Quintine 77eb2392eb
Verify and publish checkpoint container / container (push) Canceled after 0s
Support mixed Discovery decks with Leader Skills
2026-10-30 17:49:26 +11:00

64 lines
3.4 KiB
TypeScript

import assert from 'node:assert/strict';
import { createGame, joinGame, newPlayer, type Action, type Game } from '../game/engine';
import type { FactionId } from '../game/catalog';
import { withClassicDiscoveryNexusLottery } from './fixture-discovery-classic-nexus';
import { nextPairedChoamSkillsStep } from './fixture-paired-choam-nexus-skills';
import { actSelectedIndependentNexus, advanceSelectedIndependentNexus,
type SelectedIndependentNexusStep } from './fixture-selected-independent-nexus-cards';
export type DiscoverySkillsOptions = {
advanced: boolean;
factions: readonly FactionId[];
expansions?: Game['expansions'];
};
/** Fresh public lobby, real original private training and native setup. Only
* the original random lottery is fixed; no cards, leaders or forces are added. */
export function selectedDiscoverySkillsLobby(options: DiscoverySkillsOptions) {
const [host, ...others] = options.factions;
const expansions = options.expansions ?? ['ecaz', 'ix', 'choam'];
let game = withClassicDiscoveryNexusLottery(() => createGame('SELECTEDDISCOVERYSKILLS',
newPlayer(host, host, host), options.advanced, expansions));
for (const faction of others) joinGame(game, newPlayer(faction, faction, faction));
const actions: SelectedIndependentNexusStep[] = [];
const step = (actor: string, action: Action) => {
game = actSelectedIndependentNexus(game, actor, action, actions);
};
if (expansions.includes('ix') && expansions.includes('choam'))
step(host, {type:'moduleOption', module:'kullPreview', enabled:false});
step(host, {type:'moduleOption', module:'leaderSkills', enabled:true});
step(host, {type:'moduleOption', module:'discoveries', enabled:true});
for (const player of game.players) step(player.id, {type:'ready'});
return {game, host, step, actions};
}
export function selectedDiscoverySkillsStart(options: DiscoverySkillsOptions) {
const fixture = selectedDiscoverySkillsLobby(options);
let game = fixture.game;
const lobby = structuredClone(game);
const start: Action = options.advanced ? {type:'start', advancedPreview:true} : {type:'start'};
game = actSelectedIndependentNexus(game, fixture.host, start, fixture.actions);
const setup = structuredClone(game);
for (let count = 0; game.status === 'setup' && count < 300; count++) {
const next = nextPairedChoamSkillsStep(game);
assert.ok(next, `Original setup must expose a choice at ${game.setupStage ?? game.decision?.kind}`);
game = actSelectedIndependentNexus(game, next.actor, next.action, fixture.actions);
}
assert.equal(game.status, 'playing');
return {...fixture, game, lobby, setup};
}
/** Reorder only the unopened original Spice Cards. The engine resolves the
* Discovery and the following bidding and movement through ordinary actions. */
export function advanceDiscoverySkillsBlow(game: Game, actions: SelectedIndependentNexusStep[]) {
for (const position of game.advanced ? [0, 1] : [0]) {
const index = game.spiceDeck.findIndex((card, i) => i >= position && 'territory' in card &&
(position === 0 ? card.discovery === 'discovery-hagga-basin' : !card.discovery));
assert.ok(index >= position);
[game.spiceDeck[position], game.spiceDeck[index]] = [game.spiceDeck[index], game.spiceDeck[position]];
}
return advanceSelectedIndependentNexus(game, state => state.turn === 1 && state.phase === 5 &&
!state.phaseOpening && !state.response && !state.decision && !state.truthtrance &&
!state.pendingTreacheryDiscard, actions);
}