Files
Dune/tests/reinforcements-homeworld-choice.test.ts
T
Quintine 1cf90f1328
Verify and publish checkpoint container / container (push) Successful in 11m11s
Allow chosen Reinforcements reserve costs
2026-10-29 04:41:26 +11:00

117 lines
5.8 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { NativeReserveError, type NativeReserveSelections } from '../game/homeworld-native-reserves';
import { type HomeworldCustody, type HomeworldCustodyContext } from '../game/homeworld-custody';
import {
quoteReinforcementsHomeworldCost as quote,
reinforcementsHomeworldSources as sources,
} from '../game/reinforcements-homeworld';
import { ReinforcementsError } from '../game/reinforcements';
import { combatArmy } from '../game/combat-location';
import {
assertReinforcementsHomeworldCustody,
createReinforcementsHomeworldFixture,
finishReinforcementsHomeworldBattle,
reinforcementsHomeworldPlayer as player,
reinforcementsHomeworldPool as pool,
sealReinforcementsHomeworldPlan,
} from './fixture-reinforcements-homeworld';
const kaitain = 'homeworld:emperor';
const salusa = 'homeworld:emperor:salusa';
const ix = 'homeworld:ixians';
const units = (normal: number, elite = 0) => ({ normal, elite });
function fixture(): { context: HomeworldCustodyContext; custody: HomeworldCustody } {
return {
context: { advanced: true, players: [
{ id: 'e', faction: 'emperor', reserves: 10, eliteReserves: 3 },
{ id: 'i', faction: 'ixians', reserves: 10, eliteReserves: 2 },
] },
custody: { salusa: units(3, 2), visitors: {
[kaitain]: { i: units(2) },
[ix]: { e: units(1, 1) },
} },
};
}
void test('Homeworld options and costs honor the selected typed fee and preserve normal-first defaults', () => {
const { context, custody } = fixture();
const before = structuredClone({ context, custody });
const defaults = sources(context, custody, 'e');
assert.deepEqual(defaults, sources(context, custody, 'e', 0));
assert.equal(defaults.length, 4);
const mixed = sources(context, custody, 'e', 2);
assert.equal(mixed.length, 4);
for (const allocation of mixed) {
assert.equal(allocation[kaitain].normal + allocation[salusa].normal, 1);
assert.equal(allocation[kaitain].elite + allocation[salusa].elite, 2);
const result = quote(context, custody, 'e', allocation, 2);
assert.deepEqual({ normal: result.normal, elite: result.elite, bonus: result.bonus },
{ normal: 1, elite: 2, bonus: 2 });
assert.deepEqual(result.sources, allocation);
assert.equal(result.native.players[0].reserves, 7);
assert.equal(result.native.players[0].eliteReserves, 1);
}
assert.throws(() => quote(context, custody, 'e', undefined, 2), NativeReserveError);
const defaultResult = quote(context, custody, 'e', defaults[0]);
assert.deepEqual({ normal: defaultResult.normal, elite: defaultResult.elite }, { normal: 3, elite: 0 });
assert.deepEqual({ context, custody }, before);
});
void test('single native Homeworld can automatically pay an explicit elite choice', () => {
const { context, custody } = fixture();
assert.deepEqual(sources(context, custody, 'i', 2), [{ [ix]: units(1, 2) }]);
const result = quote(context, custody, 'i', undefined, 2);
assert.deepEqual(result.sources, { [ix]: units(1, 2) });
assert.deepEqual({ normal: result.normal, elite: result.elite, bonus: result.bonus },
{ normal: 1, elite: 2, bonus: 2 });
assert.equal(result.native.players[1].eliteReserves, 0);
});
void test('selected types and explicit native allocations are both validated', () => {
const { context, custody } = fixture();
const before = structuredClone({ context, custody });
for (const choice of [-1, 4, 1.5, NaN, Infinity]) {
assert.throws(() => sources(context, custody, 'e', choice), ReinforcementsError);
assert.throws(() => quote(context, custody, 'e', undefined, choice), ReinforcementsError);
}
for (const allocation of [
{ [kaitain]: units(3) }, // valid default fee, wrong for the chosen mix
{ [ix]: units(1, 2) }, // visitor forces cannot pay the fee
{ [kaitain]: units(1, 2) }, // Kaitain has only one elite
{ [kaitain]: units(1, 1) }, // does not sum to the chosen fee
{ [kaitain]: { normal: 1, elite: 2, extra: 0 } },
]) {
assert.throws(() => quote(context, custody, 'e', allocation as unknown as NativeReserveSelections, 2), NativeReserveError);
}
assert.deepEqual({ context, custody }, before);
});
void test('Advanced Emperor seals and pays a mixed Homeworld fee from the selected Worlds', () => {
const allocation = { [kaitain]: units(1), [salusa]: units(1, 1) };
const f = createReinforcementsHomeworldFixture({ advanced: true, sources: allocation });
const before = player(f.game, f.owner);
assert.deepEqual(pool(f.game, f.owner, kaitain), units(10));
assert.deepEqual(pool(f.game, f.owner, salusa), units(2, 1));
assert.deepEqual(combatArmy(f.game, f.owner, f.location), units(3, 4));
const action = { ...f.planActions[0].action, reinforcementsElite: 1 };
let game = sealReinforcementsHomeworldPlan(f.game, { actor: f.owner, action });
assert.deepEqual(game.homeworlds, f.game.homeworlds, 'Sealing does not pay the fee');
assert.equal(player(game, f.owner).elites!.reserves, before.elites!.reserves);
game = sealReinforcementsHomeworldPlan(JSON.parse(JSON.stringify(game)), f.planActions[1]);
const done = finishReinforcementsHomeworldBattle(game);
const after = player(done, f.owner);
assert.equal(done.lastBattleContext!.winner, f.opponent);
assert.deepEqual(pool(done, f.owner, kaitain), units(9));
assert.deepEqual(pool(done, f.owner, salusa), units(1));
assert.deepEqual(combatArmy(done, f.owner, f.location), units(0));
assert.equal(after.reserves, before.reserves - 3);
assert.equal(after.elites!.reserves, before.elites!.reserves - 1);
assert.equal(after.tanks, before.tanks + 10, 'Three fee forces and seven defeated invaders');
assert.equal(after.elites!.tanks, before.elites!.tanks + 5, 'One fee elite and four defeated elites');
assert.equal(after.battleLosses, before.battleLosses + 10);
assertReinforcementsHomeworldCustody(f.game);
assertReinforcementsHomeworldCustody(done);
});