Files
Dune/tests/low-ecaz-battle-lead-runtime.test.ts

146 lines
7.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { ecazOccupyLeadOptions } from '../game/ecaz-occupy-options';
import { homeworldGameIntegrity, homeworldTable } from '../game/homeworld-game';
import {
createLowEcazBattleLeadFixture as fixture,
actLowEcazBattleLead as act,
chooseLowEcazBattleLead as choose,
openLowEcazBattleLeadPlans as openPlans,
lowEcazBattleLeadInventory as inventory,
lowEcazBattleLeadPlayer as player,
} from './fixture-low-ecaz-battle-lead';
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
function physical(game: Game) {
return game.players.map(seat => ({
id: seat.id, reserves: seat.reserves, tanks: seat.tanks, forces: structuredClone(seat.forces),
elites: structuredClone(seat.elites), spice: seat.spice, ally: seat.ally,
leaders: structuredClone(seat.leaders),
}));
}
for (const advanced of [false, true]) {
const mode = advanced ? 'Advanced' : 'Basic';
void test(`${mode} native Low Ecaz lets its actual opponent select either coalition lead`, () => {
const f = fixture({ advanced });
assert.equal(f.game.homeworlds !== null, true); assert.deepEqual(f.game.expansions, ['ecaz']);
assert.ok(!f.game.nexusCards); assert.ok(!f.game.leaderSkills);
assert.equal(player(f.afterSetup, f.ecaz).reserves, 14);
assert.equal(player(f.afterSetup, f.ecaz).spice, 12);
assert.equal(player(f.game, f.ecaz).reserves, 6);
assert.equal(player(f.game, f.ecaz).spice, 4);
assert.equal(player(f.game, f.ecaz).forces[f.location], 8);
assert.deepEqual(f.allianceSteps, [
{ actor: f.ecaz, action: { type: 'alliance', target: f.ally } },
{ actor: f.ally, action: { type: 'alliance', target: f.ecaz } },
]);
const shipment = f.shipments.find(step => step.actor === f.ecaz); assert.ok(shipment);
assert.equal(shipment.action.amount, 8); assert.ok(shipment.action.homeworldSources);
assert.equal(f.setupSteps.some(step => step.actor === f.ecaz && step.action.type === 'ecazSetup'), true);
assert.equal(f.sourceActionCount, f.actions.length);
const home = homeworldTable(f.game)!.find(home => home.native === f.ecaz)!;
assert.equal(home.population, 6); assert.equal(home.side, 'low');
assert.ok(f.game.decision?.kind === 'ecazBattleLead');
assert.equal(f.game.decision.player, f.opponent); assert.equal(f.game.decision.lowEcaz, true);
assert.equal(f.game.decision.event, f.game.battle!.event);
const options = ecazOccupyLeadOptions(viewGame(f.game, f.opponent));
assert.deepEqual(options.map(option => option.lead), [f.ecaz, f.ally]);
assert.ok(options.every(option => option.blocked === null));
assert.ok(ecazOccupyLeadOptions(viewGame(f.game, f.ecaz)).every(option => option.blocked !== null));
for (const lead of [f.ecaz, f.ally]) {
const selected = choose(f.game, lead);
assert.equal(selected.battle!.ecazOccupy!.lead, lead);
assert.equal(selected.response?.kind, 'ecazOccupy'); assert.equal(selected.response?.owner, f.ecaz);
assert.deepEqual(physical(selected), physical(f.game));
const game = openPlans(selected), profile = viewGame(game, lead).battle!.ecazOccupy!.profile!;
assert.equal(profile.planOwner, lead); assert.equal(profile.payer, lead);
assert.equal(profile.forceOwner, f.ally); assert.equal(profile.fixedEcazDial, 4);
assert.equal(profile.ecazForces.normal, 8); assert.equal(profile.canceled, false);
assert.ok([game.battle!.attacker, game.battle!.defender].includes(lead));
assert.ok([game.battle!.attacker, game.battle!.defender].includes(f.opponent));
assert.deepEqual(inventory(game), f.originalInventory); homeworldGameIntegrity(game);
}
});
void test(`${mode} native Low Ecaz rejects Ecaz self-selection and a stale opponent event`, () => {
const f = fixture({ advanced }), action = f.leadActions[0].action;
reject(f.game, f.ecaz, action); reject(f.game, f.ally, action);
reject(f.game, f.opponent, { ...action, event: `${f.event}:stale` });
reject(f.game, f.opponent, { ...action, lead: f.opponent });
const selected = choose(f.game, f.ally);
assert.equal(selected.battle!.ecazOccupy!.lead, f.ally);
reject(selected, f.opponent, { ...action, lead: f.ecaz });
assert.equal(selected.battle!.ecazOccupy!.lead, f.ally);
});
void test(`${mode} native seven-reserve Ecaz retains its ordinary battle-lead choice`, () => {
const f = fixture({ advanced, reserves: 7 });
assert.equal(player(f.game, f.ecaz).reserves, 7);
assert.equal(player(f.game, f.ecaz).forces[f.location], 7);
assert.equal(player(f.game, f.ecaz).spice, 5);
const home = homeworldTable(f.game)!.find(home => home.native === f.ecaz)!;
assert.equal(home.population, 7); assert.equal(home.side, 'high');
assert.ok(f.game.decision?.kind === 'ecazBattleLead');
assert.equal(f.game.decision.player, f.ecaz); assert.equal(f.game.decision.lowEcaz, undefined);
reject(f.game, f.opponent, f.leadActions[0].action);
const selected = choose(f.game, f.ally);
assert.equal(selected.battle!.ecazOccupy!.lead, f.ally);
assert.equal(selected.response?.kind, 'ecazOccupy');
assert.deepEqual(inventory(selected), f.originalInventory);
});
void test(`${mode} native Guild Karama cancels Occupy while preserving either opponent-selected lead`, () => {
const f = fixture({ advanced });
assert.deepEqual(player(f.afterSetup, f.holder).hand.map(card => card.id), [f.karama]);
for (const lead of [f.ecaz, f.ally]) {
let game = choose(f.game, lead);
game = act(game, f.holder, f.cancelAction);
game = openPlans(game);
assert.equal(game.battle!.ecazOccupy!.lead, lead);
assert.equal(game.battle!.ecazOccupy!.canceled, true);
const profile = viewGame(game, lead).battle!.ecazOccupy!.profile!;
assert.equal(profile.planOwner, lead); assert.equal(profile.payer, lead);
assert.equal(profile.forceOwner, lead); assert.equal(profile.fixedEcazDial, 0);
assert.equal(profile.forces.normal, lead === f.ecaz ? 8 : 2);
assert.equal(profile.forces.elite, lead === f.ecaz ? 0 : 2);
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
assert.equal(player(game, f.holder).hand.some(card => card.id === f.karama), false);
assert.deepEqual(physical(game), physical(f.game));
assert.equal(game.decision?.kind, undefined);
assert.deepEqual(inventory(game), f.originalInventory); homeworldGameIntegrity(game);
}
});
void test(`${mode} all four native opponent policies make a legal Low Ecaz lead selection`, () => {
const f = fixture({ advanced });
for (const difficulty of DIFFICULTIES) {
const view = viewGame(f.game, f.opponent);
view.players.find(seat => seat.id === f.opponent)!.bot = difficulty;
const before = structuredClone(view), actions = botActions(view);
assert.ok(actions.length > 0, difficulty);
assert.deepEqual(view, before);
for (const action of actions) {
assert.equal(action.type, 'decision', difficulty);
assert.equal(action.event, f.event, difficulty);
assert.ok(action.lead === f.ecaz || action.lead === f.ally, difficulty);
const selected = act(f.game, f.opponent, action);
assert.equal(selected.battle!.ecazOccupy!.lead, action.lead, difficulty);
assert.equal(selected.response?.kind, 'ecazOccupy', difficulty);
assert.deepEqual(inventory(selected), f.originalInventory);
}
const ecazView = viewGame(f.game, f.ecaz);
ecazView.players.find(seat => seat.id === f.ecaz)!.bot = difficulty;
assert.deepEqual(botActions(ecazView), []);
}
});
}