116 lines
6.5 KiB
TypeScript
116 lines
6.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Game } from '../game/engine';
|
|
import { reserveShipmentCost } from '../game/shipment-price';
|
|
import { guildAmbassadorShipments } from '../game/guild-ambassador';
|
|
import { nextBasicSemutaAmbassadorStep } from './fixture-basic-semuta-ambassador';
|
|
import { act, player, advancedOverlapChildFixture, type ChildEffect, type First } from './fixture-advanced-overlap-child';
|
|
|
|
function continueTo(game: Game, until: (state: Game) => boolean): Game {
|
|
for (let n = 0; n < 100 && !until(game); n++) {
|
|
if (game.decision?.kind === 'advisor') {
|
|
game = act(game, game.decision.player, { type:'decision', accept:false });
|
|
continue;
|
|
}
|
|
if (game.decision?.kind === 'guildShipment') {
|
|
game = act(game, game.decision.player, { type:'decision', allow:true });
|
|
continue;
|
|
}
|
|
const next = nextBasicSemutaAmbassadorStep(game);
|
|
assert.ok(next, `Unanticipated original continuation ${game.decision?.kind ?? game.response?.kind}`);
|
|
game = act(game, next.actor, next.action);
|
|
}
|
|
assert.ok(until(game), 'The native source reached the intended reaction');
|
|
return game;
|
|
}
|
|
|
|
function originalEntry(effect: ChildEffect, first: First, withBG = false) {
|
|
const f = advancedOverlapChildFixture(effect, first, withBG);
|
|
const originalSpice = player(f.game, f.entrant).spice;
|
|
const shipper = player(f.game, f.entrant);
|
|
const price = reserveShipmentCost({ faction:shipper.faction, halfRate:shipper.faction === 'guild' }, 'stronghold', 1);
|
|
let game = act(f.game, f.entrant, f.shipment);
|
|
game = continueTo(game, state => !!state.pendingArrivalOverlap);
|
|
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
|
assert.equal(game.pendingArrivalOverlap?.first, first);
|
|
if (first === 'terror') {
|
|
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
|
|
game = act(game, f.moritani, { type:'decision', decline:true });
|
|
}
|
|
const entry = game.pendingAmbassador; assert.ok(entry);
|
|
assert.equal(entry.token, f.token);
|
|
assert.equal(viewGame(game, f.owner).ambassadorEntry?.beneficiaries[0].blocked, null);
|
|
const parentEvent = game.pendingArrivalOverlap!.event;
|
|
game = act(game, f.owner, { type:'decision', event:entry.event, trigger:true, beneficiary:f.owner });
|
|
assert.equal(game.pendingAmbassador?.stage, effect === 'fremen' ? 'move' : 'ship');
|
|
return { f, game, parentEvent, entryEvent:entry.event, originalSpice, price };
|
|
}
|
|
|
|
for (const effect of ['fremen','guild'] as const)
|
|
for (const first of ['ambassador','terror'] as const)
|
|
for (const reveal of [false,true])
|
|
void test(`Advanced ${effect} child ${first} first and ${reveal ? 'revealed' : 'declined'} Terror`, () => {
|
|
const start = originalEntry(effect, first), f = start.f;
|
|
let game = start.game;
|
|
const before = player(game, f.owner);
|
|
const reserves = before.reserves, spice = before.spice;
|
|
if (effect === 'fremen') {
|
|
game = act(game, f.owner, { type:'decision', event:start.entryEvent,
|
|
forces:{ [f.moveFrom]:1 }, territory:f.destination, sector:f.sector });
|
|
} else {
|
|
assert.ok(guildAmbassadorShipments(game, f.owner).destinations.some(option =>
|
|
option.territory === f.destination && option.sector === f.sector));
|
|
game = act(game, f.owner, { type:'decision', event:start.entryEvent,
|
|
amount:1, elite:0, territory:f.destination, sector:f.sector });
|
|
}
|
|
game = continueTo(game, state => state.pendingTerrorEntry?.cause === 'ambassador');
|
|
assert.equal(game.pendingTerrorEntry?.entrant, f.owner);
|
|
assert.equal(game.pendingTerrorEntry?.token, f.terror);
|
|
assert.equal(game.pendingArrivalOverlap?.event, start.parentEvent);
|
|
assert.equal(player(game, f.owner).reserves, reserves - (effect === 'guild' ? 1 : 0));
|
|
assert.equal(player(game, f.owner).spice, spice);
|
|
if (reveal) {
|
|
game = act(game, f.moritani, { type:'decision', reveal:true });
|
|
assert.equal(game.pendingTerrorEntry?.stage, 'robbery');
|
|
game = act(game, f.moritani, { type:'decision', choice:'spice' });
|
|
assert.equal(game.moritaniTerror!.tokens.find(token => token.id === f.terror)?.status, 'removed');
|
|
} else game = act(game, f.moritani, { type:'decision', decline:true });
|
|
if (first === 'ambassador' && !reveal) {
|
|
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
|
|
assert.equal(game.pendingTerrorEntry?.token, f.terror);
|
|
game = act(game, f.moritani, { type:'decision', decline:true });
|
|
}
|
|
assert.equal(game.pendingArrivalOverlap, null);
|
|
assert.equal(game.pendingAmbassador, null);
|
|
assert.equal(game.pendingTerrorEntry, null);
|
|
assert.equal(player(game, f.entrant).spice, start.originalSpice - start.price);
|
|
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
|
|
assert.ok((player(game, f.owner).forces[`${f.destination}:${f.sector}`] ?? 0) >= 1);
|
|
});
|
|
|
|
void test('Advanced child Sneak and Bene Gesserit accompaniment wait for the original Terror', () => {
|
|
const start = originalEntry('fremen', 'ambassador', true), f = start.f;
|
|
assert.ok(f.alternate && f.sneak);
|
|
const alternate = f.alternate;
|
|
let game = act(start.game, f.owner, { type:'decision', event:start.entryEvent,
|
|
forces:{ [alternate.sector.key]:1 }, territory:alternate.destination.territory, sector:alternate.destination.sector });
|
|
game = continueTo(game, state => state.pendingTerrorEntry?.token === f.sneak);
|
|
const reserves = player(game, f.moritani).reserves;
|
|
game = act(game, f.moritani, { type:'decision', reveal:true });
|
|
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
|
|
game = act(game, f.moritani, { type:'decision', amount:1 });
|
|
assert.equal(player(game, f.moritani).reserves, reserves - 1);
|
|
assert.equal(game.pendingTerrorEntry?.token, f.terror);
|
|
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
|
|
assert.equal(game.decision?.kind, 'moritaniTerror');
|
|
game = act(game, f.moritani, { type:'decision', decline:true });
|
|
assert.equal(game.pendingArrivalOverlap, null);
|
|
assert.equal(game.decision?.kind, 'advisor');
|
|
assert.equal(game.decision?.player, 'beneGesserit');
|
|
game = act(game, 'beneGesserit', { type:'decision', accept:false });
|
|
assert.equal(game.pendingAmbassador, null);
|
|
assert.equal(game.pendingTerrorEntry, null);
|
|
assert.equal(player(game, f.moritani).forces[`${alternate.destination.territory}:${alternate.destination.sector}`], 1);
|
|
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
|
|
});
|