70 lines
3.7 KiB
TypeScript
70 lines
3.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createGame, newPlayer, type Game } from '../game/engine';
|
|
import { acquireDuke, captureDuke, createDukeVidal } from '../game/duke-vidal';
|
|
import { ecazDukeAcquisitionBlock, quoteEcazDukeAcquisition } from '../game/ecaz-duke-acquisition';
|
|
import { DUKE_CAPTURE_UNCERTAIN_BLOCK, dukeCaptureUncertaintyBlock, plainAdvancedEcazDukeProfile } from '../game/ecaz-duke-capture-policy';
|
|
|
|
function table(): Game {
|
|
const game = createGame('DUKEPOLICY', newPlayer('e', 'Ecaz', 'ecaz'), true, ['ecaz']);
|
|
game.players.push(newPlayer('h', 'Harkonnen', 'harkonnen'), newPlayer('g', 'Guild', 'guild'));
|
|
game.status = 'playing'; game.turn = 2; game.dukeVidal = createDukeVidal();
|
|
game.dukeCaptureUncertain = false;
|
|
return game;
|
|
}
|
|
|
|
void test('a known-safe plain Advanced table can acquire the original living Duke', () => {
|
|
const game = table(), before = structuredClone(game);
|
|
assert.equal(plainAdvancedEcazDukeProfile(game), true);
|
|
assert.equal(dukeCaptureUncertaintyBlock(game), null);
|
|
const next = quoteEcazDukeAcquisition(game, 'e');
|
|
assert.equal(next.controller, 'e'); assert.equal(next.leader.id, game.dukeVidal!.leader.id);
|
|
assert.deepEqual(game, before, 'A quote does not change custody.');
|
|
const ecaz = game.players.find(player => player.id === 'e')!;
|
|
const guild = game.players.find(player => player.id === 'g')!;
|
|
ecaz.ally = 'g'; guild.ally = 'e';
|
|
assert.equal(quoteEcazDukeAcquisition(game, 'e', 'g').controller, 'g');
|
|
});
|
|
|
|
void test('uncertain and legacy Advanced tables block all hidden custody identically', () => {
|
|
const free = table();
|
|
free.dukeVidal = acquireDuke(free.dukeVidal!, 'g', free.turn, 'ecaz');
|
|
const captured = structuredClone(free);
|
|
captured.dukeVidal = captureDuke(captured.dukeVidal!, 'h', 'g', captured.turn, 'arrakeen');
|
|
for (const uncertain of [true, undefined] as const) {
|
|
for (const game of [free, captured]) {
|
|
if (uncertain === undefined) delete game.dukeCaptureUncertain;
|
|
else game.dukeCaptureUncertain = uncertain;
|
|
const before = structuredClone(game);
|
|
assert.equal(ecazDukeAcquisitionBlock(game, 'e'), DUKE_CAPTURE_UNCERTAIN_BLOCK);
|
|
assert.throws(() => quoteEcazDukeAcquisition(game, 'e'));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
}
|
|
});
|
|
|
|
void test('uncertainty does not change Basic or tables without Harkonnen', () => {
|
|
const basic = table(); basic.advanced = false; delete basic.dukeCaptureUncertain;
|
|
assert.equal(dukeCaptureUncertaintyBlock(basic), null);
|
|
assert.equal(quoteEcazDukeAcquisition(basic, 'e').controller, 'e');
|
|
const noHarkonnen = table(); noHarkonnen.players = noHarkonnen.players.filter(player => player.id !== 'h');
|
|
delete noHarkonnen.dukeCaptureUncertain;
|
|
assert.equal(dukeCaptureUncertaintyBlock(noHarkonnen), null);
|
|
assert.equal(quoteEcazDukeAcquisition(noHarkonnen, 'e').controller, 'e');
|
|
});
|
|
|
|
void test('the plain profile keeps optional modules separate and validates the selected roster', () => {
|
|
const game = table();
|
|
assert.equal(plainAdvancedEcazDukeProfile(game), true);
|
|
const mixed = structuredClone(game); mixed.expansions.push('ix');
|
|
mixed.players.push(newPlayer('i', 'Ixians', 'ixians'));
|
|
assert.equal(plainAdvancedEcazDukeProfile(mixed), true);
|
|
mixed.sandtrout = true;
|
|
assert.equal(plainAdvancedEcazDukeProfile(mixed), true, 'Active native Sandtrout is not an optional selection.');
|
|
mixed.leaderSkills = {} as NonNullable<Game['leaderSkills']>;
|
|
assert.equal(plainAdvancedEcazDukeProfile(mixed), false);
|
|
assert.match(ecazDukeAcquisitionBlock(mixed, 'e')!, /Advanced Harkonnen/);
|
|
delete mixed.leaderSkills; mixed.expansions = ['ecaz'];
|
|
assert.equal(plainAdvancedEcazDukeProfile(mixed), false, 'An unselected expansion cannot seat Ixians.');
|
|
});
|