Files
Dune/tests/smuggler-shipment-fixture.ts

133 lines
4.2 KiB
TypeScript

import assert from 'node:assert/strict';
import { mock } from 'node:test';
import {
applyAction,
createGame,
newPlayer,
joinGame,
initializeLeaderSkillsGameForAudit,
viewGame,
type Game,
} from '../game/engine';
import { LEADER_SKILL_CARDS } from '../game/leader-skill-cards';
import type { FactionId } from '../game/catalog';
import { botActions } from '../game/bots';
/** Genuine module setup followed by a conserved, explicitly staged shipment turn. */
export function smugglerShipmentGame(
faction: FactionId = 'emperor',
advanced = false,
skill: 'smuggler' | 'sandmaster' = 'smuggler',
opponentFaction?: FactionId,
expansions: readonly string[] = [],
independent = false,
): Game {
let g = createGame('SMUGSHIP', newPlayer('p', 'Smuggler', faction), advanced, [...expansions]);
joinGame(
g,
newPlayer('h', 'Opponent', opponentFaction ?? (faction === 'guild' ? 'emperor' : 'guild')),
);
for (const p of g.players) g = applyAction(g, p.id, { type: 'ready' });
if (independent) g.ecazTreachery = true;
const index = LEADER_SKILL_CARDS.findIndex((c) => c.id === skill);
let cursor = LEADER_SKILL_CARDS.length - 1;
mock.method(globalThis.crypto, 'getRandomValues', (array: Uint32Array) => {
array[0] = cursor-- === index ? 0 : 0xffffffff;
return array;
});
try {
g = initializeLeaderSkillsGameForAudit(g);
} finally {
mock.restoreAll();
}
for (let i = 0; g.status === 'setup' && i < 60; i++) {
let accepted = false;
for (const p of g.players) {
const view = viewGame(g, p.id);
const skills = view.leaderSkills!;
const options =
g.decision?.kind === 'ixSetup'
? [
{
type: 'decision',
card: viewGame(g, g.decision.player).ixTechnology!.setup![0].id,
},
]
: g.setupStage === 'leaderSkills' && skills.offer
? [
{
type: 'leaderSkill',
event: skills.offer.event,
skill: p.id === 'p' ? skill : skills.offer.cards[0],
leader: skills.eligibleLeaders[0].id,
},
]
: g.setupStage === 'traitors' && p.traitorChoices.length
? [{ type: 'traitor', leader: p.traitorChoices[0] }]
: g.setupStage === 'prediction' && p.faction === 'beneGesserit'
? [{ type: 'predict', faction: g.players.find(other => other.id !== p.id)!.faction, turn: 3 }]
: g.setupStage === 'forces' &&
p.faction === 'fremen' &&
p.reserves === 20
? [
{
type: 'fremenSetup',
placements: {
sietch_tabr: 10,
false_wall_south: 0,
false_wall_west: 0,
},
},
]
: g.setupStage === 'forces' &&
p.faction === 'beneGesserit' &&
!p.advisorSetup
? [
{
type: 'advisorSetup',
territory: 'arrakeen',
sector: 10,
},
]
: botActions(view);
for (const a of options) {
try {
g = applyAction(g, p.id, a);
accepted = true;
break;
} catch {
/* Other seats can be waiting. */
}
}
if (accepted) break;
}
assert.ok(accepted, `setup ${g.setupStage}`);
}
assert.equal(g.status, 'playing');
Object.assign(g, {
phase: 5,
turn: 2,
storm: 18,
active: 'p',
order: ['p', 'h'],
movementRemaining: ['p', 'h'],
ready: [],
response: null,
decision: null,
phaseOpening: null,
});
for (const p of g.players) {
g.deck.push(...p.hand);
p.hand = [];
p.forces = {};
p.reserves = 20;
p.tanks = 0;
p.shipped = false;
p.moved = 0;
p.advisors = {};
if (p.elites) p.elites = { ...p.elites, forces: {},
reserves: p.elites.reserves + p.elites.tanks + Object.values(p.elites.forces).reduce((sum, n) => sum + n, 0), tanks: 0 };
}
return g;
}