Files
Dune/tests/fixture-selected-nexus-no-cards.ts
T
Quintine 91d45d1dec
Verify and publish checkpoint container / container (push) Canceled after 0s
Support unused selected decks with Nexus and Discovery
2026-10-29 20:19:58 +11:00

81 lines
4.5 KiB
TypeScript

import assert from 'node:assert/strict';
import { applyAction, createGame, joinGame, newPlayer, viewGame, type Action } from '../game/engine';
import { botActions } from '../game/bots';
import type { FactionId } from '../game/catalog';
import { withClassicDiscoveryNexusLottery } from './fixture-discovery-classic-nexus';
import { advanceSelectedIndependentNexus, actSelectedIndependentNexus,
selectedIndependentNexusClean } from './fixture-selected-independent-nexus-cards';
export interface SelectedNexusNoCardsOptions {
factions: readonly FactionId[];
advanced?: boolean;
discovery?: boolean;
}
/** Uses the public lobby controls and real private setup; the three selected
* faction decks do not imply the separate Ecaz independent-card option. */
export function createSelectedNexusNoCards(options: SelectedNexusNoCardsOptions) {
const [host, ...others] = options.factions;
let game = withClassicDiscoveryNexusLottery(() => createGame('SELECTEDNEXUSNOCARDS',
newPlayer(host, host, host), options.advanced ?? false, ['ix', 'choam', 'ecaz']));
for (const faction of others) joinGame(game, newPlayer(faction, faction, faction));
const actions: { actor: string; action: Action }[] = [];
const act = (actor: string, action: Action) => {
actions.push({ actor, action: structuredClone(action) });
game = withClassicDiscoveryNexusLottery(() => applyAction(game, actor, action));
};
act(host, { type: 'moduleOption', module: 'nexusCards', enabled: true });
if (options.discovery) act(host, { type: 'moduleOption', module: 'discoveries', enabled: true });
for (const player of game.players) act(player.id, { type: 'ready' });
const lobby = structuredClone(game);
act(host, options.advanced ? { type: 'start', advancedPreview: true } : { type: 'start' });
const setup = structuredClone(game);
for (let count = 0; game.status === 'setup' && count < 100; count++) {
let next: { actor: string; action: Action } | undefined;
for (const player of game.players) {
const view = viewGame(game, player.id);
view.players.find(seat => seat.id === player.id)!.bot = 'Easy';
const action = botActions(view)[0];
if (action) { next = { actor: player.id, action }; break; }
}
assert.ok(next, `Original setup must expose an owned choice at ${game.setupStage}`);
act(next.actor, next.action);
}
assert.equal(game.status, 'playing');
return { lobby, setup, game, actions };
}
/** Real second-turn worm and closing Nexus. Reorder only undealt singleton
* spice and Nexus cards; all hands and alliances result from player actions. */
export function selectedNexusNoCardsDraw(advanced = false) {
const fixture = createSelectedNexusNoCards({ factions: ['atreides', 'guild', 'harkonnen', 'emperor'], advanced });
let game = structuredClone(fixture.game);
const take = (worm: boolean) => {
const index = game.spiceDeck.findIndex(card => worm
? 'worm' in card && !card.greatMaker && !card.thumper && !card.suppressed
: 'territory' in card && !card.discovery);
assert.ok(index >= 0);
return game.spiceDeck.splice(index, 1)[0];
};
const first = [take(false), ...(advanced ? [take(false)] : [])];
const second = [take(true), take(false), ...(advanced ? [take(false)] : [])];
game.spiceDeck.unshift(...first, ...second);
game = advanceSelectedIndependentNexus(game, state => state.turn === 2 && !!state.nexus &&
!state.spiceWindow && !state.spiceResolution && selectedIndependentNexusClean(state), fixture.actions);
game = actSelectedIndependentNexus(game, 'guild', { type: 'alliance', target: 'harkonnen' }, fixture.actions);
game = actSelectedIndependentNexus(game, 'harkonnen', { type: 'alliance', target: 'guild' }, fixture.actions);
game = advanceSelectedIndependentNexus(game, state => state.nexusCards?.phase?.stage === 'drawing' &&
selectedIndependentNexusClean(state), fixture.actions);
const cards = game.nexusCards!.cards!, index = cards.deck.indexOf('richese');
assert.ok(index >= 0);
cards.deck.unshift(cards.deck.splice(index, 1)[0]);
const beforeDraw = structuredClone(game);
const draw: Action = { type: 'nexusCardChoice', turn: game.turn, card: cards.hands.atreides,
choice: 'draw', ownRedraws: 0 };
game = actSelectedIndependentNexus(game, 'atreides', draw, fixture.actions);
const afterDraw = structuredClone(game);
game = advanceSelectedIndependentNexus(game, state => state.phase === 5 && state.active === 'atreides' &&
selectedIndependentNexusClean(state), fixture.actions);
return { ...fixture, beforeDraw, draw, afterDraw, game };
}