Resolve direct advanced Sneak arrivals
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
8a39f5a172
commit
b79e448deb
8 files changed
+213
-8
No files matched your search
@@ -0,0 +1,108 @@
|
||||
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 { reserveShipmentCost } from '../game/shipment-price';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { nextBasicSemutaAmbassadorStep } from './fixture-basic-semuta-ambassador';
|
||||
import { act, player } from './fixture-advanced-overlap-child';
|
||||
import { advancedDirectSneakFixture } from './fixture-advanced-sneak-direct';
|
||||
|
||||
function advanceTo(game: Game, until: (state: Game) => boolean): Game {
|
||||
for (let i = 0; i < 60 && !until(game); i++) {
|
||||
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, `The original arrival must continue: ${game.decision?.kind ?? game.response?.kind}`);
|
||||
game = act(game, next.actor, next.action);
|
||||
}
|
||||
assert.ok(until(game), 'The requested native arrival reaction is reached');
|
||||
return game;
|
||||
}
|
||||
|
||||
function revealSneak(withBG: boolean) {
|
||||
const f = advancedDirectSneakFixture(withBG);
|
||||
const originalSpice = player(f.game, f.entrant).spice;
|
||||
const price = reserveShipmentCost({faction:'guild', halfRate:true}, 'stronghold', 1);
|
||||
let game = act(f.game, f.entrant, f.shipment);
|
||||
game = advanceTo(game, state => !!state.pendingTerrorEntry);
|
||||
assert.equal(game.pendingArrivalOverlap ?? null, null, 'Guild cannot trigger its own Ambassador');
|
||||
assert.equal(game.pendingTerrorEntry?.token, f.sneak);
|
||||
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
|
||||
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
||||
game = act(game, f.moritani, {type:'decision', reveal:true});
|
||||
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
|
||||
return {f, game, originalSpice, price};
|
||||
}
|
||||
|
||||
for (const withBG of [false, true]) void test(
|
||||
`Advanced direct Sneak opens the Moritani Ambassador entry ${withBG ? 'after BG accompaniment' : 'without BG'}`, () => {
|
||||
const {f, game: before, originalSpice, price} = revealSneak(withBG);
|
||||
const reserves = player(before, f.moritani).reserves;
|
||||
let game = act(before, f.moritani, {type:'decision', amount:1});
|
||||
if (withBG) {
|
||||
assert.equal(game.decision?.kind, 'advisor');
|
||||
assert.equal(game.decision?.player, 'beneGesserit');
|
||||
game = act(game, 'beneGesserit', {type:'decision', accept:false});
|
||||
}
|
||||
game = advanceTo(game, state => !!state.pendingAmbassador);
|
||||
const entry = game.pendingAmbassador; assert.ok(entry);
|
||||
assert.equal(entry.token, f.ambassador);
|
||||
assert.equal(entry.entrant, f.moritani);
|
||||
assert.equal(player(game, f.moritani).reserves, reserves - 1);
|
||||
assert.equal(player(game, f.moritani).forces[`${f.destination}:${f.sector}`], 1);
|
||||
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
|
||||
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
||||
for (const difficulty of DIFFICULTIES) {
|
||||
const view = viewGame(game, 'ecaz');
|
||||
view.players.find(p => p.id === 'ecaz')!.bot = difficulty;
|
||||
const action = botActions(view)[0]; assert.ok(action, difficulty);
|
||||
assert.doesNotThrow(() => act(game, 'ecaz', action), difficulty);
|
||||
}
|
||||
game = act(game, 'ecaz', {type:'decision', event:entry.event, decline:true});
|
||||
assert.equal(game.pendingAmbassador, null);
|
||||
assert.equal(game.pendingTerrorEntry, null);
|
||||
assert.equal(game.pendingArrivalOverlap ?? null, null);
|
||||
assert.equal(player(game, f.moritani).reserves, reserves - 1);
|
||||
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
||||
});
|
||||
|
||||
void test('Advanced direct Sneak sending zero opens no separate entry or accompaniment', () => {
|
||||
const {f, game: before, originalSpice, price} = revealSneak(true);
|
||||
const reserves = player(before, f.moritani).reserves;
|
||||
const game = act(before, f.moritani, {type:'decision', amount:0});
|
||||
assert.equal(player(game, f.moritani).reserves, reserves);
|
||||
assert.equal(game.pendingAmbassador ?? null, null);
|
||||
assert.equal(game.pendingTerrorEntry, null);
|
||||
assert.notEqual(game.decision?.kind, 'advisor');
|
||||
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
||||
});
|
||||
|
||||
void test('Advanced direct Sneak can trigger the retained Guild Ambassador exactly once', () => {
|
||||
const {f, game: before, originalSpice, price} = revealSneak(false);
|
||||
const reserves = player(before, f.owner).reserves, spice = player(before, f.owner).spice;
|
||||
let game = act(before, f.moritani, {type:'decision', amount:1});
|
||||
game = advanceTo(game, state => !!state.pendingAmbassador);
|
||||
const entry = game.pendingAmbassador; assert.ok(entry);
|
||||
game = act(game, f.owner, {type:'decision', event:entry.event, trigger:true, beneficiary:f.owner});
|
||||
assert.equal(game.pendingAmbassador?.stage, 'ship');
|
||||
const target = guildAmbassadorShipments(game, f.owner).destinations.find(row => row.territory === 'polar_sink');
|
||||
assert.ok(target);
|
||||
game = act(game, f.owner, {type:'decision', event:entry.event, amount:1, elite:0,
|
||||
territory:target.territory, sector:target.sector});
|
||||
game = advanceTo(game, state => !state.pendingAmbassador);
|
||||
assert.equal(game.pendingAmbassador, null);
|
||||
assert.equal(game.pendingTerrorEntry, null);
|
||||
assert.equal(player(game, f.owner).reserves, reserves - 1);
|
||||
assert.equal(player(game, f.owner).spice, spice);
|
||||
assert.equal(player(game, f.entrant).spice, originalSpice - price);
|
||||
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
|
||||
assert.equal(game.ecazAmbassadors?.tokens.find(token => token.id === f.ambassador)?.zone, 'used');
|
||||
});
|
||||
Reference in new issue
Block a user