Files
Dune/tests/coalition-advanced-board.test.ts
T
Quintine 3ba9087405
Verify and publish checkpoint container / container (push) Successful in 10m53s
Connect staged Advanced classic group alliances
2026-10-29 02:37:23 +11:00

109 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, createGame, newPlayer, viewGame, type Game } from '../game/engine';
import { answerCoalitionAdmission, beginCoalitionAdmission, coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
import { quoteBattleBoard, quoteCoalitionAlliedSeparation } from '../game/board-resolution-quote';
import { isAlliedShipmentVisit } from '../game/allied-separation';
import { strongholdProgress } from '../game/victory-progress';
import { quoteVictory } from '../game/victory-quote';
/** Controlled staged public board and coalition. The engine cannot yet start
* this preview or negotiate Advanced groups; no native game is claimed. */
function staged(): Game {
const game = createGame('ADVANCEDGROUPBOARD', newPlayer('a', 'Atreides', 'atreides'), true);
game.players.push(newPlayer('b', 'Emperor', 'emperor'), newPlayer('c', 'Bene Gesserit', 'beneGesserit'),
newPlayer('d', 'Harkonnen', 'harkonnen'));
Object.assign(game, { status: 'playing', phase: 5, turn: 2, storm: 18,
active: 'a', order: ['a', 'b', 'c', 'd'], movementRemaining: ['a', 'b', 'c', 'd'] });
for (const player of game.players) { player.forces = {}; player.ally = null; player.shipped = false; player.moved = 0; }
game.players[0].ally = 'b'; game.players[1].ally = 'a';
game.players[0].allySinceTurn = 2; game.players[1].allySinceTurn = 2;
const state = emptyCoalitionState('three-short');
const group = coalitionOf(game.players, state, 'a'); assert.ok(group);
const offer = beginCoalitionAdmission(game.players, state, { event: 'invite', nexus: 'nexus', turn: 2,
groupId: group.id, entrant: 'c', proposer: 'a', expectedRevision: 0 });
const approval = answerCoalitionAdmission(game.players, offer.state, { event: 'invite', nexus: 'nexus',
turn: 2, actor: 'b', accept: true, expectedRevision: offer.state.revision });
const joined = answerCoalitionAdmission(game.players, approval.state, { event: 'invite', nexus: 'nexus',
turn: 2, actor: 'c', accept: true, expectedRevision: approval.state.revision });
for (const player of game.players) if (Object.hasOwn(joined.allyUpdates, player.id)) player.ally = joined.allyUpdates[player.id];
game.coalitionState = joined.state;
return game;
}
const p = (game: Game, id: string) => game.players.find(player => player.id === id)!;
void test('Advanced group separation unions all allies immediately while exempting advisors and Polar Sink', () => {
const game = staged();
p(game, 'a').forces = { 'arrakeen:10': 1, 'carthag:11': 1, 'sietch_tabr:14': 1, 'polar_sink:0': 1 };
p(game, 'b').forces = { 'arrakeen:10': 1, 'carthag:11': 1, 'polar_sink:0': 1 };
p(game, 'c').forces = { 'arrakeen:10': 1, 'sietch_tabr:14': 1 };
p(game, 'c').advisors = { sietch_tabr: {} };
const before = structuredClone(game);
const losses = quoteCoalitionAlliedSeparation({ ...game, remaining: ['a', 'b', 'c', 'd'] }, 'a');
assert.deepEqual(new Set(losses), new Set(['arrakeen', 'carthag']));
assert.deepEqual(quoteCoalitionAlliedSeparation({ ...game, remaining: ['a', 'b', 'c', 'd'] }, 'c'), ['arrakeen']);
assert.deepEqual(game, before, 'A quote does not remove any counters.');
assert.equal(isAlliedShipmentVisit(game, p(game, 'a'), 'arrakeen'), true);
});
void test('Advanced endMovement actually removes only the ending group member’s shared fighters', () => {
const game = staged();
const owner = p(game, 'a'), emperor = p(game, 'b'), bg = p(game, 'c');
owner.forces = { 'arrakeen:10': 2, 'carthag:11': 3, 'sietch_tabr:14': 1, 'polar_sink:0': 1 };
owner.reserves = 13;
emperor.forces = { 'arrakeen:10': 1, 'polar_sink:0': 1 }; emperor.reserves = 18;
bg.forces = { 'carthag:11': 1, 'sietch_tabr:14': 1 }; bg.reserves = 18;
bg.advisors = { sietch_tabr: {} };
const otherForces = structuredClone(game.players.filter(seat => seat.id !== 'a').map(seat => ({
id: seat.id, forces: seat.forces, tanks: seat.tanks,
})));
const warning = viewGame(game, 'a').advancedAllySeparation;
assert.ok(warning);
assert.deepEqual(new Set(warning.territories), new Set(['Arrakeen', 'Carthag']));
const after = applyAction(game, 'a', { type: 'endMovement' });
assert.equal(p(after, 'a').tanks, 5);
assert.equal(p(after, 'a').reserves, 13);
assert.equal(p(after, 'a').forces['arrakeen:10'] ?? 0, 0);
assert.equal(p(after, 'a').forces['carthag:11'] ?? 0, 0);
assert.equal(p(after, 'a').forces['sietch_tabr:14'], 1);
assert.equal(p(after, 'a').forces['polar_sink:0'], 1);
assert.deepEqual(after.players.filter(seat => seat.id !== 'a').map(seat => ({
id: seat.id, forces: seat.forces, tanks: seat.tanks,
})), otherForces);
assert.equal(after.active, 'b');
assert.deepEqual(after.movementRemaining, ['b', 'c', 'd']);
});
void test('Advanced group board generates enemy battles without ever pairing allies', () => {
const game = staged();
p(game, 'a').forces = { 'imperial_basin:9': 1 };
p(game, 'b').forces = { 'imperial_basin:9': 1 };
p(game, 'd').forces = { 'imperial_basin:9': 1 };
const battles = quoteBattleBoard(game).battles.filter(battle => battle.territory === 'imperial_basin');
assert.equal(battles.length, 2);
assert.deepEqual(new Set(battles.map(battle => [battle.attacker, battle.defender].sort().join('/'))),
new Set(['a/d', 'b/d']));
});
void test('Advanced three-short group wins on three distinct controlled strongholds', () => {
const game = staged(); game.phase = 8;
p(game, 'a').forces = { 'arrakeen:10': 1 };
p(game, 'b').forces = { 'carthag:11': 1 };
p(game, 'c').forces = { 'sietch_tabr:14': 1 };
const row = strongholdProgress(game).progress.find(item => item.player === 'a')!;
assert.deepEqual(row.members, ['a', 'b', 'c']);
assert.equal(row.target, 3);
assert.equal(row.qualifies, true);
assert.deepEqual(quoteVictory(game).winner, ['a', 'b', 'c']);
});
void test('Advanced groups with Ecaz or another expansion faction stay outside these board quotes', () => {
for (const faction of ['ecaz', 'ixians'] as const) {
const game = staged(); p(game, 'd').faction = faction;
assert.throws(() => quoteBattleBoard(game));
assert.throws(() => quoteCoalitionAlliedSeparation({ ...game, remaining: game.order }, 'a'));
assert.throws(() => strongholdProgress(game));
assert.throws(() => isAlliedShipmentVisit(game, p(game, 'a'), 'arrakeen'));
}
});