83 lines
4.2 KiB
TypeScript
83 lines
4.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { emptyCoalitionState, type CoalitionEdge } from '../game/coalition-alliance';
|
|
import { coalitionBattleBeneficiary } from '../game/coalition-battle-preparation';
|
|
import {
|
|
createCoalitionWormProtection, currentCoalitionWormAlly,
|
|
decideCoalitionWormProtection, protectedCoalitionWormAllies,
|
|
resolveCoalitionWormProtection, validateCoalitionWormProtection,
|
|
} from '../game/coalition-worm-protection';
|
|
import { projectFremenGroupRevivalGrant, quoteFremenRevivalGrant } from '../game/fremen-revival-grant';
|
|
import { createGame, newPlayer, type Game } from '../game/engine';
|
|
|
|
/** Staged unlimited ledger; lobby entry and actual admission are covered by
|
|
* integration tests. Every alliance edge has its original public turn. */
|
|
function source(members: string[]): Game {
|
|
const game = createGame('GROUPPOWERS', newPlayer('f', 'Fremen', 'fremen'), false);
|
|
game.players.push(newPlayer('a', 'Atreides', 'atreides'),
|
|
newPlayer('b', 'Bene Gesserit', 'beneGesserit'), newPlayer('e', 'Emperor', 'emperor'),
|
|
newPlayer('g', 'Guild', 'guild'), newPlayer('h', 'Harkonnen', 'harkonnen'));
|
|
Object.assign(game, {status:'playing', phase:1, turn:3, order:['f','e','g','a','h','b']});
|
|
const sorted = [...members].sort();
|
|
const edges: CoalitionEdge[] = [];
|
|
for (let i = 0; i < sorted.length; i++)
|
|
for (let j = i + 1; j < sorted.length; j++)
|
|
edges.push({members:[sorted[i], sorted[j]], formedTurn:2});
|
|
game.coalitionState = {...emptyCoalitionState(), variant:'unlimited' as const,
|
|
groups:[{id:'group', members:sorted, formedTurn:2, edges}]};
|
|
for (const seat of game.players) {
|
|
seat.forces = {}; seat.reserves = 20; seat.tanks = 0;
|
|
seat.ally = null;
|
|
}
|
|
return game;
|
|
}
|
|
|
|
void test('one worm resolves five independently selected allied protections in storm order', () => {
|
|
const game = source(['f','a','b','e','g','h']);
|
|
for (const seat of game.players.filter(player => player.id !== 'f')) {
|
|
seat.forces = {'red_chasm:7':3}; seat.reserves = 17;
|
|
}
|
|
let frame = createCoalitionWormProtection(game, 'f', 'red_chasm', 'worm-event');
|
|
assert.ok(frame);
|
|
assert.deepEqual(frame.order, ['e','g','a','h','b']);
|
|
for (const [ally, outcome] of [['e','allowed'], ['g','canceled'], ['a','declined'],
|
|
['h','allowed'], ['b','allowed']] as const) {
|
|
assert.equal(currentCoalitionWormAlly(game, frame), ally);
|
|
frame = decideCoalitionWormProtection(game, frame, 'f', 'worm-event', ally, outcome !== 'declined');
|
|
if (outcome !== 'declined') frame = resolveCoalitionWormProtection(game, frame, 'f', 'worm-event', ally, outcome === 'canceled');
|
|
}
|
|
assert.equal(frame.stage, 'complete');
|
|
assert.deepEqual(protectedCoalitionWormAllies(game, frame), ['e','h','b']);
|
|
assert.deepEqual(frame.outcomes.map(row => row.outcome), ['allowed','canceled','declined','allowed','allowed']);
|
|
const stale = structuredClone(frame);
|
|
stale.order = [...stale.order].reverse();
|
|
assert.throws(() => validateCoalitionWormProtection(game, stale));
|
|
});
|
|
|
|
void test('Fremen can independently select each of five allied revival recipients', () => {
|
|
const game = source(['f','a','b','e','g','h']);
|
|
game.phase = 4;
|
|
const grants = projectFremenGroupRevivalGrant(game, 'f');
|
|
assert.ok(grants);
|
|
assert.deepEqual(grants.map(row => row.recipient).sort(), ['a','b','e','g','h']);
|
|
for (const ally of ['a','b','e','g','h']) {
|
|
assert.deepEqual(quoteFremenRevivalGrant(game, 'f', ally), {turn:3,owner:'f',recipient:ally});
|
|
game.freeRevival.push(ally);
|
|
assert.throws(() => quoteFremenRevivalGrant(game, 'f', ally));
|
|
}
|
|
assert.throws(() => quoteFremenRevivalGrant(game, 'f', 'f'));
|
|
game.advanced = true;
|
|
assert.equal(projectFremenGroupRevivalGrant(game, 'f'), null);
|
|
});
|
|
|
|
void test('group battle preparation selects any single member and rejects two allied sides', () => {
|
|
const game = source(['f','a','b','e']);
|
|
game.phase = 6;
|
|
for (const ally of ['f','b','e'])
|
|
assert.equal(coalitionBattleBeneficiary(game, 'a', ally, 'g'), ally);
|
|
assert.equal(coalitionBattleBeneficiary(game, 'a', 'e', 'b'), null);
|
|
assert.equal(coalitionBattleBeneficiary(game, 'a', 'g', 'h'), null);
|
|
const unsupported = {...game, advanced:true};
|
|
assert.equal(coalitionBattleBeneficiary(unsupported, 'a', 'e', 'g'), null);
|
|
});
|