Files
Dune/tests/unused-e3-discovery-runtime.test.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

72 lines
4.2 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, joinGame, newPlayer, viewGame, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { spiceDeck, treacheryDeck } from '../game/cards';
import { DISCOVERY_CARD_PLACEMENTS, DISCOVERY_SPICE_CARDS, validateDiscoveryState } from '../game/discoveries';
import { actBasicSemutaAmbassador as act, basicSemutaAmbassadorClean } from './fixture-basic-semuta-ambassador';
import { nextRicheseStrongholdsNativeStep } from './fixture-richese-strongholds';
function advance(game: Game, until: (state: Game) => boolean): Game {
for (let count = 0; count < 1600; count++) {
if (until(game)) return game;
assert.notEqual(game.status, 'finished');
const step = nextRicheseStrongholdsNativeStep(game);
assert.ok(step, `No native continuation at ${game.status}/${game.turn}/${game.phase}`);
game = act(game, step.actor, step.action);
}
throw Error('The original public game did not reach the requested boundary');
}
for (const advanced of [false, true]) void test(
`${advanced ? 'Advanced' : 'Basic'} classic seats with only E3 selected resolve an original Discovery Spice Blow`, () => {
let game = createGame('UNUSEDE3DISCOVERY', newPlayer('guild', 'Guild', 'guild'), advanced, ['ecaz']);
for (const faction of ['atreides', 'emperor'] as const)
joinGame(game, newPlayer(faction, faction, faction));
game = act(game, 'guild', {type:'moduleOption', module:'discoveries', enabled:true});
for (const seat of game.players) game = act(game, seat.id, {type:'ready'});
assert.equal(game.discoveryEnabled, true);
game = act(game, 'guild', {type:'start', ...(advanced ? {advancedPreview:true} : {})});
assert.equal(game.status, 'setup');
assert.equal(game.advanced, advanced);
assert.deepEqual(game.expansions, ['ecaz']);
assert.deepEqual(game.players.map(p => p.faction).sort(), ['atreides', 'emperor', 'guild']);
assert.ok(game.discoveries);
assert.equal(game.discoveries.tokens.length, 8);
assert.deepEqual(game.deck.map(c => c.id).sort(), treacheryDeck(['ecaz']).map(c => c.id).sort());
assert.equal(game.spiceDeck.length, spiceDeck().length + 7);
assert.deepEqual(game.spiceDeck.flatMap(c => 'territory' in c && c.discovery ? [c.discovery] : []).sort(),
DISCOVERY_SPICE_CARDS.map(c => c.discovery).sort());
assert.ok(!game.nexusCards && !game.ecazTreachery && !game.leaderSkills);
assert.ok(!game.ecazAmbassadors && !game.moritaniTerror);
validateDiscoveryState(game.discoveries);
game = advance(game, state => state.status === 'playing');
// Reorder original unopened cards only; all draws, token selection and phase
// transitions still pass through the native engine.
for (const position of 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]];
}
game = advance(game, state => state.turn === 1 && state.phase === 5 && basicSemutaAmbassadorClean(state));
assert.ok(game.spiceDiscard.flat().some(card => 'territory' in card && card.discovery === 'discovery-hagga-basin'));
const placed = game.discoveries!.tokens.filter(token => token.status === 'placed');
assert.equal(placed.length, 1);
const source = DISCOVERY_CARD_PLACEMENTS['discovery-hagga-basin'];
assert.deepEqual([placed[0].type, placed[0].territory, placed[0].sector],
[source.type, source.territory, source.sector]);
validateDiscoveryState(game.discoveries!);
assert.ok(!game.nexusCards && !game.ecazTreachery);
assert.ok(game.active);
for (const difficulty of DIFFICULTIES) {
const view = viewGame(game, game.active);
view.players.find(p => p.id === game.active)!.bot = difficulty;
const action = botActions(view)[0];
assert.ok(action, difficulty);
assert.doesNotThrow(() => act(game, game.active!, action), difficulty);
}
});