117 lines
5.8 KiB
TypeScript
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);
|
|
});
|