64 lines
3.4 KiB
TypeScript
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);
|
|
}
|