160 lines
9.1 KiB
TypeScript
160 lines
9.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import {
|
|
actCaladanFaceDance, caladanFaceDanceInventory, caladanFaceDancePhysical,
|
|
caladanFaceDancePlayer as player, caladanFaceDancePopulation, createCaladanFaceDanceFixture,
|
|
faceDanceCaladanFaceDance, finishCaladanFaceDance, reinforceCaladanFaceDance,
|
|
type CaladanFaceDanceFixture,
|
|
} from './fixture-caladan-face-dance';
|
|
|
|
function rejected(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => actCaladanFaceDance(game, actor, action));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
function source(f: CaladanFaceDanceFixture) {
|
|
assert.equal(f.game.status, 'playing');
|
|
assert.equal(f.game.turn, f.beforeBattle.turn);
|
|
assert.equal(f.game.phase, 6);
|
|
assert.deepEqual(f.game.expansions, ['ix']);
|
|
assert.ok(f.game.homeworlds?.custody);
|
|
assert.ok(!f.game.nexusCards && !f.game.leaderSkills && !f.game.techTokens &&
|
|
!f.game.strongholdCards && !f.game.discoveryEnabled && !f.game.ecazTreachery);
|
|
assert.ok(f.game.decision?.kind === 'caladanReinforcement');
|
|
assert.equal(f.game.decision.player, f.owner);
|
|
assert.equal(f.game.decision.event, f.event);
|
|
assert.equal(f.game.response ?? null, null);
|
|
assert.equal(f.game.phaseOpening ?? null, null);
|
|
assert.equal(f.game.pendingTreacheryDiscard ?? null, null);
|
|
assert.equal(f.game.pendingFaceDance!.player, f.dancer);
|
|
assert.equal(f.game.pendingFaceDance!.winner, f.owner);
|
|
assert.equal(f.game.pendingFaceDance!.identity, f.leader);
|
|
assert.equal(f.game.homeworldVictoryReinforcement!.stage, 'choice');
|
|
assert.equal(f.game.homeworldVictoryReinforcement!.offer!.blocked, null);
|
|
assert.equal(f.game.homeworldVictoryReinforcement!.offer!.population, 6);
|
|
assert.equal(f.game.homeworldVictoryReinforcement!.offer!.survivors, 10);
|
|
assert.equal(caladanFaceDancePopulation(f.afterSetup), 10);
|
|
assert.equal(caladanFaceDancePopulation(f.game), 6);
|
|
assert.equal(player(f.game, f.owner).forces[f.location], 10);
|
|
assert.equal(player(f.game, f.owner).forces['polar_sink:0'], 4);
|
|
assert.equal(player(f.game, f.dancer).reserves, 20);
|
|
assert.equal(f.shipments.length, 2);
|
|
assert.equal(f.shipments[0].step.actor, f.owner);
|
|
assert.equal(f.shipments[0].step.action.amount, 4);
|
|
assert.equal(f.shipments[0].step.action.territory, 'polar_sink');
|
|
assert.ok(f.shipments[0].step.action.homeworldSources);
|
|
assert.equal(f.shipments[1].step.actor, f.opponent);
|
|
assert.equal(f.shipments[1].step.action.amount, 1);
|
|
assert.equal(f.shipments[1].step.action.territory, f.territory);
|
|
assert.ok(player(f.afterSetup, f.dancer).faceDancers!.some(card => card.leader === f.leader && !card.revealed));
|
|
assert.ok(!player(f.game, f.owner).leaders.find(leader => leader.id === f.leader)!.dead);
|
|
assert.equal(f.game.lastBattleContext!.winner, f.owner);
|
|
assert.equal(f.game.lastBattleContext!.event, f.event);
|
|
assert.equal(f.sourceActionCount, f.actions.length);
|
|
assert.deepEqual(caladanFaceDanceInventory(f.game), f.originalInventory);
|
|
}
|
|
function completedGrant(f: CaladanFaceDanceFixture, game: Game, give: boolean) {
|
|
assert.equal(game.homeworldVictoryReinforcement!.event, f.event);
|
|
assert.equal(game.homeworldVictoryReinforcement!.stage, 'complete');
|
|
assert.equal(game.homeworldVictoryReinforcement!.destination, give ? f.destination : 'decline');
|
|
assert.equal(game.lastBattleContext!.caladanReinforcement!.event, f.event);
|
|
assert.equal(game.lastBattleContext!.caladanReinforcement!.completed, true);
|
|
}
|
|
|
|
for (const advanced of [false, true]) {
|
|
for (const give of [true, false]) for (const reveal of [true, false]) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native Caladan ${give ? 'reinforces before' : 'declines before'} Face Dance ${reveal ? 'reveal' : 'decline'}`, () => {
|
|
const f = createCaladanFaceDanceFixture({ advanced });
|
|
source(f);
|
|
let game = reinforceCaladanFaceDance(f, structuredClone(f.game), give);
|
|
const amount = give ? 11 : 10;
|
|
assert.equal(game.decision?.kind, 'faceDance');
|
|
assert.deepEqual(game.pendingFaceDance, f.originalFaceDance);
|
|
assert.equal(player(game, f.owner).forces[f.location], amount);
|
|
assert.equal(caladanFaceDancePopulation(game), give ? 5 : 6);
|
|
assert.equal(player(game, f.owner).spice, player(f.game, f.owner).spice);
|
|
assert.equal(player(game, f.owner).shipped, player(f.game, f.owner).shipped);
|
|
assert.equal(player(game, f.owner).moved, player(f.game, f.owner).moved);
|
|
completedGrant(f, game, give);
|
|
rejected(game, f.owner, f.action);
|
|
rejected(game, f.dancer, { type: 'decision', reveal: true, sources: { reserves: amount + 1 }, sector: 10 });
|
|
const beforeDance = structuredClone(game);
|
|
game = faceDanceCaladanFaceDance(f, game, reveal);
|
|
assert.equal(player(game, f.owner).spice, player(beforeDance, f.owner).spice);
|
|
assert.equal(player(game, f.dancer).spice, player(beforeDance, f.dancer).spice);
|
|
// Later collection may pay the original or replacement stronghold owner.
|
|
game = finishCaladanFaceDance(f, game);
|
|
completedGrant(f, game, give);
|
|
assert.equal(game.pendingFaceDance ?? null, null);
|
|
assert.notEqual(game.decision?.kind, 'caladanReinforcement');
|
|
assert.notEqual(game.decision?.kind, 'faceDance');
|
|
assert.equal(player(game, f.owner).forces['polar_sink:0'], 4);
|
|
if (reveal) {
|
|
assert.equal(player(game, f.owner).forces[f.location] ?? 0, 0);
|
|
assert.equal(player(game, f.dancer).forces[f.location], amount);
|
|
assert.equal(player(game, f.dancer).reserves, player(beforeDance, f.dancer).reserves - amount);
|
|
assert.equal(caladanFaceDancePopulation(game), 16,
|
|
'All live winning counters, including the granted one, return to the same native Caladan pool');
|
|
assert.equal(player(game, f.owner).leaders.find(leader => leader.id === f.leader)!.dead, true);
|
|
assert.equal(player(game, f.owner).leaders.find(leader => leader.id === f.leader)!.deaths,
|
|
player(f.game, f.owner).leaders.find(leader => leader.id === f.leader)!.deaths + 1);
|
|
assert.equal(player(game, f.dancer).faceDancers!.filter(card => card.leader === f.leader && card.revealed).length, 1);
|
|
} else {
|
|
assert.equal(player(game, f.owner).forces[f.location], amount);
|
|
assert.equal(player(game, f.dancer).forces[f.location] ?? 0, 0);
|
|
assert.equal(player(game, f.dancer).reserves, 20);
|
|
assert.equal(caladanFaceDancePopulation(game), give ? 5 : 6);
|
|
assert.equal(player(game, f.owner).leaders.find(leader => leader.id === f.leader)!.dead, false);
|
|
assert.ok(player(game, f.dancer).faceDancers!.some(card => card.leader === f.leader && !card.revealed));
|
|
}
|
|
rejected(game, f.owner, f.action);
|
|
assert.deepEqual(caladanFaceDanceInventory(game), f.originalInventory);
|
|
});
|
|
}
|
|
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native Caladan pending Face Dance rejects stale, foreign and early reveal choices`, () => {
|
|
const f = createCaladanFaceDanceFixture({ advanced });
|
|
source(f);
|
|
rejected(f.game, f.dancer, f.action);
|
|
rejected(f.game, f.opponent, f.action);
|
|
rejected(f.game, f.owner, { ...f.action, event: 'stale' });
|
|
rejected(f.game, f.owner, { ...f.action, amount: 2 });
|
|
rejected(f.game, f.dancer, { type: 'decision', reveal: true, sources: { reserves: 11 }, sector: 10 });
|
|
assert.deepEqual(caladanFaceDancePhysical(f.game), f.originalPhysical);
|
|
assert.deepEqual(f.game.pendingFaceDance, f.originalFaceDance);
|
|
assert.equal(f.game.homeworldVictoryReinforcement!.stage, 'choice');
|
|
assert.deepEqual(caladanFaceDanceInventory(f.game), f.originalInventory);
|
|
});
|
|
}
|
|
|
|
void test('four minimal profiles choose the real Caladan reinforcement and native Face Dance without a second grant', () => {
|
|
for (const advanced of [false, true]) {
|
|
const f = createCaladanFaceDanceFixture({ advanced });
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const ownerView = viewGame(f.game, f.owner);
|
|
ownerView.players.find(seat => seat.id === f.owner)!.bot = difficulty;
|
|
const reinforcement = botActions(ownerView)[0];
|
|
assert.deepEqual(reinforcement, f.action, `${advanced}/${difficulty} reinforces the actual surviving army`);
|
|
let game = actCaladanFaceDance(structuredClone(f.game), f.owner, reinforcement);
|
|
assert.equal(player(game, f.owner).forces[f.location], 11);
|
|
assert.equal(game.decision?.kind, 'faceDance');
|
|
const dancerView = viewGame(game, f.dancer);
|
|
dancerView.players.find(seat => seat.id === f.dancer)!.bot = difficulty;
|
|
const dance = botActions(dancerView)[0];
|
|
assert.ok(dance);
|
|
assert.equal(dance.type, 'decision');
|
|
assert.equal(dance.reveal, true);
|
|
assert.deepEqual(dance.sources, { reserves: 11 });
|
|
game = actCaladanFaceDance(game, f.dancer, dance);
|
|
game = finishCaladanFaceDance(f, game);
|
|
assert.equal(player(game, f.dancer).forces[f.location], 11);
|
|
assert.equal(caladanFaceDancePopulation(game), 16);
|
|
completedGrant(f, game, true);
|
|
assert.deepEqual(caladanFaceDanceInventory(game), f.originalInventory);
|
|
}
|
|
}
|
|
});
|