72 lines
4.2 KiB
TypeScript
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);
|
|
}
|
|
});
|