125 lines
6.9 KiB
TypeScript
125 lines
6.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { act, advanceAlliedCompanions as advance, clean, createNoFieldAlliedCompanionsFixture as fixture,
|
|
inventory, player } from './fixture-no-field-allied-companions';
|
|
|
|
function ship(f: ReturnType<typeof fixture>): Game {
|
|
const before = structuredClone(f.game);
|
|
let game = act(f.game, f.actor, f.ship);
|
|
assert.deepEqual(f.game, before);
|
|
if (game.response) assert.equal(game.response.kind, 'richeseNoField');
|
|
game = advance(game, state => clean(state) && !state.pendingShipment);
|
|
const own = player(game, f.actor);
|
|
assert.equal(player(before, f.actor).spice - own.spice, 4 - f.allyPayment);
|
|
assert.equal(own.reserves, player(before, f.actor).reserves - f.companions);
|
|
assert.equal(own.forces['imperial_basin:9'], f.baseline + f.companions);
|
|
assert.equal(own.noField!.deployed!.tokenId, f.token);
|
|
const warning = viewGame(game, f.actor).basicAlliedShipment;
|
|
assert.ok(warning); assert.equal(warning.normal, f.companions);
|
|
assert.equal(warning.elite, 0); assert.equal(warning.noField, true);
|
|
assert.equal(viewGame(game, f.ally).basicAlliedShipment, null);
|
|
assert.equal(viewGame(game, f.ally).richeseNoField?.private, null);
|
|
assert.deepEqual(inventory(game), f.original);
|
|
return game;
|
|
}
|
|
|
|
for (const value of [0, 3, 5] as const) {
|
|
void test(`Basic allied paid companion and marker ${value} may leave together`, () => {
|
|
const f = fixture(value); let game = ship(f);
|
|
const event = player(game, f.actor).noFieldEvent; assert.ok(event);
|
|
game = act(game, f.actor, {type: 'move', noField: f.token, event,
|
|
forces: {'imperial_basin:9': f.companions}, territory: 'polar_sink', sector: 0});
|
|
game = advance(game, clean);
|
|
assert.deepEqual(player(game, f.actor).noField!.deployed!.location, {territory: 'polar_sink', sector: 0});
|
|
assert.equal(viewGame(game, f.actor).basicAlliedShipment, null);
|
|
game = act(game, f.actor, {type: 'endMovement'});
|
|
assert.ok(player(game, f.actor).noField!.deployed);
|
|
assert.equal(player(game, f.actor).forces['imperial_basin:9'], f.baseline);
|
|
assert.equal(player(game, f.actor).forces['polar_sink:0'], f.companions);
|
|
assert.equal(player(game, f.ally).forces['imperial_basin:11'], 1);
|
|
assert.deepEqual(inventory(game), f.original);
|
|
});
|
|
|
|
void test(`Basic allied paid companion and marker ${value} remaining at turn end are lost`, () => {
|
|
const f = fixture(value), game = ship(f), before = player(game, f.actor);
|
|
const reserves = before.reserves, tanks = before.tanks, event = before.noFieldEvent;
|
|
const after = act(game, f.actor, {type: 'endMovement'});
|
|
const own = player(after, f.actor), revealed = Math.min(value, reserves);
|
|
assert.equal(own.noField!.deployed, null); assert.notEqual(own.noFieldEvent, event);
|
|
assert.equal(own.noField!.lastShipped, f.token);
|
|
assert.equal(own.reserves, reserves - revealed);
|
|
assert.equal(own.tanks, tanks + revealed + f.companions);
|
|
assert.equal(own.forces['imperial_basin:9'], f.baseline);
|
|
assert.equal(player(after, f.ally).forces['imperial_basin:11'], 1);
|
|
assert.equal(viewGame(after, f.actor).basicAlliedShipment, null);
|
|
assert.deepEqual(inventory(after), f.original);
|
|
});
|
|
}
|
|
|
|
void test('voluntary reveal adds actual marker forces to the paid companion obligation', () => {
|
|
const f = fixture(3); let game = ship(f);
|
|
const event = player(game, f.actor).noFieldEvent; assert.ok(event);
|
|
game = act(game, f.actor, {type: 'revealNoField', event, token: f.token});
|
|
const revealedEvent = player(game, f.actor).noFieldEvent;
|
|
assert.equal(player(game, f.actor).forces['imperial_basin:9'], f.baseline + f.companions + 3);
|
|
assert.equal(viewGame(game, f.actor).basicAlliedShipment?.normal, f.companions + 3);
|
|
game = act(game, f.actor, {type: 'endMovement'});
|
|
assert.equal(player(game, f.actor).noFieldEvent, revealedEvent);
|
|
assert.equal(player(game, f.actor).forces['imperial_basin:9'], f.baseline);
|
|
assert.equal(player(game, f.actor).tanks, f.companions + 3);
|
|
assert.equal(player(game, f.ally).forces['imperial_basin:11'], 1);
|
|
assert.deepEqual(inventory(game), f.original);
|
|
});
|
|
|
|
void test('opponent Karama cancels the combined invoice before payment or custody', () => {
|
|
const f = fixture(3), before = structuredClone(f.game);
|
|
let game = act(f.game, f.actor, f.ship);
|
|
assert.equal(game.response?.kind, 'richeseNoField');
|
|
game = act(game, f.other, {type: 'card', mode: 'cancel', card: f.karama});
|
|
game = advance(game, state => clean(state) && !state.pendingShipment);
|
|
assert.equal(player(game, f.actor).spice, player(before, f.actor).spice);
|
|
assert.equal(player(game, f.actor).reserves, player(before, f.actor).reserves);
|
|
assert.deepEqual(player(game, f.actor).noField, player(before, f.actor).noField);
|
|
assert.equal(player(game, f.actor).shipped, false);
|
|
assert.equal(player(game, f.actor).forces['imperial_basin:9'], f.baseline);
|
|
assert.equal(player(game, f.ally).forces['imperial_basin:11'], 1);
|
|
assert.deepEqual(game.aid, before.aid);
|
|
assert.ok(game.basicAlliedShipment == null);
|
|
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
|
|
assert.deepEqual(inventory(game), f.original);
|
|
});
|
|
|
|
void test('each AI profile legally moves the marker and paid companion together without visitor losses', () => {
|
|
const f = fixture(3), source = ship(f);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
let game = structuredClone(source);
|
|
const tanks = player(game, f.actor).tanks;
|
|
const view = viewGame(game, f.actor);
|
|
view.players.find(seat => seat.id === f.actor)!.bot = difficulty;
|
|
const departure = botActions(view)[0]; assert.ok(departure, `${difficulty} must choose a legal departure`);
|
|
assert.equal(departure.type, 'move');
|
|
assert.equal(departure.noField, f.token);
|
|
assert.equal((departure.forces as Record<string, number>)['imperial_basin:9'], f.companions);
|
|
game = act(game, f.actor, departure);
|
|
game = advance(game, clean);
|
|
assert.notEqual(departure.territory, 'imperial_basin');
|
|
assert.deepEqual(player(game, f.actor).noField!.deployed!.location,
|
|
{territory: departure.territory, sector: departure.sector});
|
|
assert.equal(viewGame(game, f.actor).basicAlliedShipment, null);
|
|
for (let count = 0; game.phase === 5 && game.active === f.actor && count < 60; count++) {
|
|
if (!clean(game)) { game = advance(game, clean); continue; }
|
|
const nextView = viewGame(game, f.actor);
|
|
nextView.players.find(seat => seat.id === f.actor)!.bot = difficulty;
|
|
const next = botActions(nextView)[0]; assert.ok(next);
|
|
game = act(game, f.actor, next);
|
|
}
|
|
assert.notEqual(game.active, f.actor, `${difficulty} must finish its visiting turn`);
|
|
assert.equal(player(game, f.actor).tanks, tanks);
|
|
assert.equal(player(game, f.ally).forces['imperial_basin:11'], 1);
|
|
assert.deepEqual(inventory(game), f.original);
|
|
}
|
|
});
|