73 lines
3.9 KiB
TypeScript
73 lines
3.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
AutomaticAdvisorFlipError, quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip,
|
|
type AutomaticAdvisorFlipContext, type AutomaticAdvisorFlipSource, type AutomaticAdvisorWormRide,
|
|
} from '../game/advisor-automatic-flip';
|
|
|
|
type WormSource = Extract<AutomaticAdvisorFlipSource, {producer:'wormRide'}>;
|
|
|
|
/** A native Fremen ride has already deposited its physical forces in Hagga.
|
|
* Bene Gesserit's original Imperial Basin advisors are now alone. */
|
|
function source() {
|
|
const ride:AutomaticAdvisorWormRide = {event:'ride-1',player:'f',origin:'imperial_basin',
|
|
destination:'hagga_basin',sector:12,amount:3,elite:1};
|
|
const game:AutomaticAdvisorFlipContext = {status:'playing',advanced:true,turn:2,phase:1,
|
|
players:[
|
|
{id:'b',faction:'beneGesserit',forces:{'imperial_basin:9':2,'imperial_basin:10':1},
|
|
advisors:{imperial_basin:{aloneFlipHold:'flip-1'}}},
|
|
{id:'f',faction:'fremen',forces:{'hagga_basin:12':3},elites:{forces:{'hagga_basin:12':1}}},
|
|
{id:'a',faction:'atreides',forces:{}},
|
|
]};
|
|
const flip:WormSource = {event:'flip-1',owner:'b',territory:'imperial_basin',turn:2,phase:1,
|
|
forces:{'imperial_basin:9':2,'imperial_basin:10':1},producer:'wormRide',wormRide:ride};
|
|
return {game,flip};
|
|
}
|
|
|
|
void test('native committed worm ride binds its held advisor group without mutating it', () => {
|
|
const {game,flip}=source(), before=structuredClone({game,flip});
|
|
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,flip));
|
|
assert.deepEqual({game,flip},before);
|
|
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,JSON.parse(JSON.stringify(flip)) as WormSource));
|
|
});
|
|
|
|
void test('worm ride rejects altered or malformed identity, origin, destination and force custody', () => {
|
|
const changes:Array<(game:AutomaticAdvisorFlipContext,flip:WormSource)=>void> = [
|
|
game => {game.phase=0;}, game => {game.advanced=false;}, game => {game.turn++;},
|
|
(_game,flip) => {flip.wormRide.event='';},
|
|
(_game,flip) => {flip.wormRide.player='missing';},
|
|
(game) => {game.players[1].faction='atreides';},
|
|
(_game,flip) => {flip.wormRide.origin='invalid';},
|
|
(_game,flip) => {flip.wormRide.destination='invalid';},
|
|
(_game,flip) => {flip.wormRide.destination=flip.wormRide.origin;},
|
|
(_game,flip) => {flip.wormRide.sector=10;},
|
|
(_game,flip) => {flip.wormRide.amount=0;},
|
|
(_game,flip) => {flip.wormRide.amount=4;},
|
|
(_game,flip) => {flip.wormRide.elite=4;},
|
|
(game) => {game.players[1].elites!.forces={'hagga_basin:12':0};},
|
|
(game) => {game.players[1].elites!.forces={'hagga_basin:12':3};},
|
|
(_game,flip) => {flip.territory='arrakeen';},
|
|
(game) => {game.players[0].forces={'imperial_basin:9':1};},
|
|
(game) => {game.players[2].forces={'imperial_basin:11':1};},
|
|
];
|
|
for (const change of changes) {
|
|
const {game,flip}=source(); change(game,flip); const before=structuredClone({game,flip});
|
|
assert.throws(() => validateAutomaticAdvisorFlip(game,flip),AutomaticAdvisorFlipError);
|
|
assert.deepEqual({game,flip},before);
|
|
}
|
|
const {game,flip}=source();
|
|
assert.throws(() => validateAutomaticAdvisorFlip(game,{...flip,wormRide:{...flip.wormRide,extra:true}} as WormSource),AutomaticAdvisorFlipError);
|
|
});
|
|
|
|
void test('worm ride quote uses public force presence and does not inspect hidden hands', () => {
|
|
const {game,flip}=source();
|
|
delete game.players[0].advisors!.imperial_basin.aloneFlipHold;
|
|
const quote=quoteAutomaticAdvisorFlips(game);
|
|
assert.deepEqual(quote,[{player:'b',territory:'imperial_basin',
|
|
forces:{'imperial_basin:9':2,'imperial_basin:10':1}}]);
|
|
game.players[0].advisors!.imperial_basin.aloneFlipHold=flip.event;
|
|
for (const player of game.players) for (const field of ['hand','leaders','traitors','spice'])
|
|
Object.defineProperty(player,field,{get() {throw new Error(`Private ${field} inspected`);}});
|
|
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,flip));
|
|
});
|