81 lines
4.5 KiB
TypeScript
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 };
|
|
}
|