Files
Dune/tests/fremen-victory.test.ts

178 lines
6.1 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import type { BoardContext } from '../game/board-resolution-quote';
import { fremenSpecialVictory } from '../game/fremen-victory';
import { VictoryProgressError } from '../game/victory-progress';
function fixture(advanced = false): BoardContext {
return {
advanced,
storm: 18,
order: ['x', 'f', 'e', 'r'],
players: [
{ id: 'f', faction: 'fremen', ally: 'e', forces: {} },
{ id: 'e', faction: 'ecaz', ally: 'f', forces: {} },
{ id: 'x', faction: 'beneGesserit', ally: null, forces: {} },
{ id: 'r', faction: 'richese', ally: null, forces: {} },
],
};
}
function seat(g: BoardContext, id: string) {
return g.players.find((player) => player.id === id)!;
}
function place(g: BoardContext, id: string, key: string) {
const player = seat(g, id);
player.forces = { ...player.forces, [key]: 1 };
}
void test('allied Ecaz co-occupation at Sietch Tabr permits Fremen victory in Basic and Advanced', () => {
for (const advanced of [false, true]) {
const g = fixture(advanced);
place(g, 'f', 'sietch_tabr:14');
place(g, 'e', 'sietch_tabr:14');
const result = fremenSpecialVictory(g)!;
assert.equal(result.player, 'f');
assert.deepEqual(result.members, ['f', 'e']);
assert.equal(result.qualifies, true);
assert.deepEqual(result.sietches, [
{
territory: 'sietch_tabr',
blockers: [],
ecazCooccupation: true,
},
{
territory: 'habbanya_ridge_sietch',
blockers: [],
ecazCooccupation: false,
},
]);
assert.deepEqual(result.tueksBlockers, []);
}
});
void test('a valid board without Fremen has no special-victory prospect', () => {
const g = fixture();
seat(g, 'f').faction = 'atreides';
assert.equal(fremenSpecialVictory(g), null);
});
void test('solitary, nonallied and third-party Ecaz presence do not receive the Tabr exception', () => {
const solitary = fixture();
place(solitary, 'e', 'sietch_tabr:14');
assert.deepEqual(fremenSpecialVictory(solitary)!.sietches[0].blockers, ['e']);
const nonallied = fixture();
seat(nonallied, 'f').ally = null;
seat(nonallied, 'e').ally = null;
place(nonallied, 'f', 'sietch_tabr:14');
place(nonallied, 'e', 'sietch_tabr:14');
assert.deepEqual(fremenSpecialVictory(nonallied)!.sietches[0].blockers, [
'e',
]);
const third = fixture();
place(third, 'f', 'sietch_tabr:14');
place(third, 'e', 'sietch_tabr:14');
place(third, 'r', 'sietch_tabr:14');
assert.deepEqual(fremenSpecialVictory(third)!.sietches[0].blockers, ['r']);
assert.equal(fremenSpecialVictory(third)!.qualifies, false);
});
void test('Basic Ecaz co-occupation at Habbanya remains a blocker', () => {
const g = fixture();
place(g, 'f', 'habbanya_ridge_sietch:17');
place(g, 'e', 'habbanya_ridge_sietch:17');
const result = fremenSpecialVictory(g)!;
assert.equal(result.sietches[1].ecazCooccupation, true);
assert.deepEqual(result.sietches[1].blockers, ['e']);
assert.equal(result.qualifies, false);
});
void test('supplied Advanced Ecaz cooccupation permits Habbanya while preserving Basic and solitary Ecaz blockers', () => {
const g = fixture(true);
place(g, 'f', 'habbanya_ridge_sietch:17');
place(g, 'e', 'habbanya_ridge_sietch:17');
assert.equal(fremenSpecialVictory(g)!.qualifies, true);
seat(g, 'f').forces = {};
assert.deepEqual(fremenSpecialVictory(g)!.sietches[1].blockers, ['e']);
});
void test('accompanied BG advisors do not block, while mandatory lone-advisor release restores fighter presence', () => {
const accompanied = fixture(true);
place(accompanied, 'f', 'habbanya_ridge_sietch:17');
place(accompanied, 'x', 'habbanya_ridge_sietch:17');
seat(accompanied, 'x').advisors = { habbanya_ridge_sietch: {} };
assert.deepEqual(fremenSpecialVictory(accompanied)!.sietches[1].blockers, []);
const released = fixture(true);
place(released, 'x', 'sietch_tabr:14');
seat(released, 'x').advisors = { sietch_tabr: {} };
const before = structuredClone(released);
assert.deepEqual(fremenSpecialVictory(released)!.sietches[0].blockers, ['x']);
assert.deepEqual(released, before);
});
void test('Tuek blockers preserve Basic and Advanced faction scope and never inspect marker secrets', () => {
const basic = fixture(false);
place(basic, 'r', 'tueks_sietch:5');
assert.deepEqual(fremenSpecialVictory(basic)!.tueksBlockers, []);
const advanced = fixture(true);
seat(advanced, 'r').noField = {
deployed: { location: { territory: 'tueks_sietch', sector: 5 } },
};
Object.defineProperty(seat(advanced, 'r').noField!.deployed, 'value', {
get() {
throw new Error('Hidden marker denomination read');
},
});
for (const player of advanced.players)
for (const key of ['hand', 'spice', 'leaders', 'prediction'])
Object.defineProperty(player, key, {
get() {
throw new Error(`Private field read: ${key}`);
},
});
const result = fremenSpecialVictory(advanced)!;
assert.deepEqual(result.tueksBlockers, ['r']);
assert.equal(result.qualifies, false);
});
void test('members and blockers follow public order rather than player-array order', () => {
const g = fixture();
g.order = ['r', 'e', 'x', 'f'];
place(g, 'f', 'sietch_tabr:14');
place(g, 'e', 'sietch_tabr:14');
place(g, 'r', 'habbanya_ridge_sietch:17');
place(g, 'x', 'habbanya_ridge_sietch:17');
assert.deepEqual(fremenSpecialVictory(g), {
player: 'f',
members: ['e', 'f'],
qualifies: false,
sietches: [
{
territory: 'sietch_tabr',
blockers: [],
ecazCooccupation: true,
},
{
territory: 'habbanya_ridge_sietch',
blockers: ['r', 'x'],
ecazCooccupation: false,
},
],
tueksBlockers: [],
});
});
void test('invalid shared board and alliance state is reported through VictoryProgressError', () => {
const invalid = fixture();
seat(invalid, 'e').ally = null;
assert.throws(() => fremenSpecialVictory(invalid), VictoryProgressError);
const malformed = fixture();
seat(malformed, 'x').forces = { 'sietch_tabr:99': 1 };
assert.throws(() => fremenSpecialVictory(malformed), VictoryProgressError);
});