154 lines
7.8 KiB
TypeScript
154 lines
7.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Game } from '../game/engine';
|
|
import { guildAmbassadorShipments } from '../game/guild-ambassador';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { advancedSneakWormFixture, act, player, type WormOrder } from './fixture-advanced-sneak-worm';
|
|
|
|
function reveal(game: Game): Game {
|
|
assert.equal(game.pendingTerrorEntry?.stage, 'offer');
|
|
assert.equal(viewGame(game, 'm').terrorEntry?.sneakAttack?.blocked, null);
|
|
game = act(game, 'm', {type:'decision', reveal:true});
|
|
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
|
|
return game;
|
|
}
|
|
|
|
for (const withBG of [false,true]) void test(
|
|
`Advanced worm Sneak resolves its own ${withBG ? 'BG and ' : ''}Ambassador before the next ride`, () => {
|
|
const f = advancedSneakWormFixture('direct', 'terror', withBG);
|
|
assert.equal(f.game.pendingArrivalOverlap ?? null, null, 'Fremen cannot trigger its own Ambassador');
|
|
assert.equal(f.game.pendingTerrorEntry?.resume, 'wormRide');
|
|
let game = reveal(f.game);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view = viewGame(game, 'm');
|
|
view.players.find(player => player.id === 'm')!.bot = difficulty;
|
|
const choice = botActions(view)[0]; assert.ok(choice, difficulty);
|
|
assert.doesNotThrow(() => act(game, 'm', choice), difficulty);
|
|
}
|
|
const beforeReserves = player(game, 'm').reserves, beforeSpice = player(game, 'm').spice;
|
|
game = act(game, 'm', {type:'decision', amount:1});
|
|
assert.equal(game.phase, 1);
|
|
assert.equal(player(game, 'm').reserves, beforeReserves - 1);
|
|
assert.equal(player(game, 'm').forces['arrakeen:10'], 1);
|
|
assert.equal(player(game, 'm').spice, beforeSpice);
|
|
assert.notDeepEqual(game.decision, f.nextRide, 'The next ride cannot overwrite this arrival');
|
|
if (withBG) {
|
|
assert.equal(game.decision?.kind, 'advisor');
|
|
assert.equal(game.decision?.player, 'bg');
|
|
game = act(game, 'bg', {type:'decision', accept:false});
|
|
}
|
|
const child = game.pendingAmbassador; assert.ok(child, 'Moritani earns the still-placed Ambassador');
|
|
assert.equal(child.token, f.token);
|
|
assert.equal(child.entrant, 'm');
|
|
game = act(game, 'ec', {type:'decision', event:child.event, decline:true});
|
|
assert.deepEqual(game.decision, f.nextRide);
|
|
assert.deepEqual(game.wormRides, []);
|
|
assert.equal(game.pendingAmbassador ?? null, null);
|
|
assert.equal(game.pendingTerrorEntry ?? null, null);
|
|
assert.equal(player(game, 'm').reserves, beforeReserves - 1);
|
|
assert.equal(player(game, 'in').forces['arrakeen:10'], 2);
|
|
game = act(game, 'in', {type:'decision', accept:false});
|
|
assert.notEqual(game.decision?.kind, 'wormRide');
|
|
assert.deepEqual(game.wormRides, []);
|
|
});
|
|
|
|
void test('Advanced worm Sneak choosing zero resumes the ride without a child arrival', () => {
|
|
const f = advancedSneakWormFixture('direct', 'terror', true);
|
|
let game = reveal(f.game);
|
|
const beforeReserves = player(game, 'm').reserves;
|
|
game = act(game, 'm', {type:'decision', amount:0});
|
|
assert.equal(player(game, 'm').reserves, beforeReserves);
|
|
assert.equal(game.pendingAmbassador ?? null, null);
|
|
assert.equal(game.pendingTerrorEntry ?? null, null);
|
|
assert.deepEqual(game.decision, f.nextRide);
|
|
});
|
|
|
|
void test('Advanced worm Sneak with no child reaction resumes the next ride once', () => {
|
|
const f = advancedSneakWormFixture('direct');
|
|
const source = structuredClone(f.game);
|
|
const token = source.ecazAmbassadors!.tokens.find(row => row.id === f.token)!;
|
|
token.zone = 'supply'; token.location = null;
|
|
let game = reveal(source);
|
|
game = act(game, 'm', {type:'decision', amount:1});
|
|
assert.deepEqual(game.decision, f.nextRide);
|
|
assert.equal(game.pendingSneakWorm ?? null, null);
|
|
assert.equal(game.pendingAmbassador ?? null, null);
|
|
assert.deepEqual(game.wormRides, []);
|
|
});
|
|
|
|
void test('Advanced Sneak on the last worm ride settles its child before ending the ride queue', () => {
|
|
const f = advancedSneakWormFixture('direct');
|
|
const source = structuredClone(f.game);
|
|
// Remove the fixture's staged extra ride to exercise the final-ride path.
|
|
source.wormRides = [];
|
|
let game = reveal(source);
|
|
game = act(game, 'm', {type:'decision', amount:1});
|
|
const child = game.pendingAmbassador; assert.ok(child);
|
|
assert.equal(child.entrant, 'm');
|
|
game = act(game, 'ec', {type:'decision', event:child.event, decline:true});
|
|
assert.equal(game.pendingAmbassador ?? null, null);
|
|
assert.equal(game.pendingTerrorEntry ?? null, null);
|
|
assert.notEqual(game.decision?.kind, 'wormRide');
|
|
assert.deepEqual(game.wormRides, []);
|
|
assert.equal(player(game, 'm').forces['arrakeen:10'], 1);
|
|
});
|
|
|
|
for (const first of ['terror','ambassador'] as const satisfies readonly WormOrder[]) void test(
|
|
`Advanced worm ${first}-first overlap finishes its original owners before the Sneak child and next ride`, () => {
|
|
const f = advancedSneakWormFixture('overlap', first);
|
|
let game = f.game;
|
|
assert.equal(game.pendingArrivalOverlap?.first, first);
|
|
assert.equal(game.pendingArrivalOverlap?.resume, 'wormRide');
|
|
if (first === 'ambassador') {
|
|
const original = game.pendingAmbassador; assert.ok(original);
|
|
game = act(game, 'ec', {type:'decision', event:original.event, decline:true});
|
|
}
|
|
game = reveal(game);
|
|
const beforeReserves = player(game, 'm').reserves;
|
|
game = act(game, 'm', {type:'decision', amount:1});
|
|
if (first === 'terror') {
|
|
const original = game.pendingAmbassador; assert.ok(original);
|
|
assert.equal(original.entrant, 'in');
|
|
game = act(game, 'ec', {type:'decision', event:original.event, decline:true});
|
|
}
|
|
const child = game.pendingAmbassador; assert.ok(child);
|
|
assert.equal(child.entrant, 'm');
|
|
assert.equal(child.token, f.token);
|
|
assert.notDeepEqual(game.decision, f.nextRide);
|
|
game = act(game, 'ec', {type:'decision', event:child.event, decline:true});
|
|
assert.deepEqual(game.decision, f.nextRide);
|
|
assert.equal(game.pendingArrivalOverlap ?? null, null);
|
|
assert.equal(player(game, 'm').reserves, beforeReserves - 1);
|
|
assert.equal(player(game, 'm').forces['arrakeen:10'], 1);
|
|
});
|
|
|
|
void test('Advanced worm Guild Ambassador child Sneak resolves before the parent rider resumes', () => {
|
|
const f = advancedSneakWormFixture('ambassador', 'ambassador');
|
|
let game = f.game;
|
|
const entry = game.pendingAmbassador; assert.ok(entry);
|
|
assert.equal(game.ecazAmbassadors?.tokens.find(token => token.id === entry.token)?.effect, 'guild');
|
|
assert.equal(entry.resume, 'wormRide');
|
|
const spice = player(game, 'ec').spice, ecReserves = player(game, 'ec').reserves;
|
|
game = act(game, 'ec', {type:'decision', event:entry.event, trigger:true, beneficiary:'ec'});
|
|
assert.equal(game.pendingAmbassador?.stage, 'ship');
|
|
assert.ok(guildAmbassadorShipments(game, 'ec').destinations.some(row =>
|
|
row.territory === f.childTerritory && row.sector === f.childSector));
|
|
game = act(game, 'ec', {type:'decision', event:entry.event, amount:1, elite:0,
|
|
territory:f.childTerritory, sector:f.childSector});
|
|
assert.equal(game.pendingTerrorEntry?.resume, 'ambassador');
|
|
assert.equal(game.pendingTerrorEntry?.token, f.sneak);
|
|
game = reveal(game);
|
|
const reserves = player(game, 'm').reserves;
|
|
game = act(game, 'm', {type:'decision', amount:1});
|
|
assert.equal(game.pendingAmbassador ?? null, null);
|
|
assert.equal(game.pendingTerrorEntry ?? null, null);
|
|
assert.deepEqual(game.decision, f.nextRide);
|
|
assert.equal(player(game, 'm').reserves, reserves - 1);
|
|
assert.equal(player(game, 'm').forces[`${f.childTerritory}:${f.childSector}`], 1);
|
|
assert.equal(player(game, 'ec').reserves, ecReserves - 1);
|
|
assert.equal(player(game, 'ec').spice, spice);
|
|
assert.equal(player(game, 'ec').forces[`${f.childTerritory}:${f.childSector}`], 1);
|
|
assert.equal(player(game, 'in').forces['arrakeen:10'], 2);
|
|
});
|