99 lines
6.5 KiB
TypeScript
99 lines
6.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { type HomeworldCustody, HomeworldCustodyError } from '../game/homeworld-custody';
|
|
import type { HomeworldShipmentContext } from '../game/homeworld-shipment';
|
|
import { homeworldCombatArmy, homeworldCombatLocation, type HomeworldCombatContext } from '../game/homeworld-combat';
|
|
import { currentHomeworldOccupationSnapshot, homeworldOccupationSnapshotPresence } from '../game/homeworld-occupation-history';
|
|
import { createRicheseNoField, deployRicheseNoField } from '../game/richese-no-field';
|
|
import { quoteRicheseHomeworldMarkerShipment, quoteRicheseHomeworldMarkerTransfer } from '../game/richese-homeworld-marker-shipment';
|
|
import { homeworldNoFieldPresence, quoteHomeworldBattlesWithNoField } from '../game/richese-homeworld-marker-combat';
|
|
|
|
const native = 'homeworld:richese';
|
|
const foreign = 'homeworld:atreides';
|
|
const other = 'homeworld:guild';
|
|
|
|
function fixture(tokenId = 'zero') {
|
|
const context: HomeworldShipmentContext = { advanced: true, players: [
|
|
{ id: 'r', faction: 'richese', reserves: 7, eliteReserves: 0, ally: null },
|
|
{ id: 'a', faction: 'atreides', reserves: 17, eliteReserves: 0, ally: null },
|
|
{ id: 'g', faction: 'guild', reserves: 20, eliteReserves: 0, ally: null },
|
|
] };
|
|
const custody: HomeworldCustody = { salusa: null, visitors: { [foreign]: { r: { normal: 2, elite: 0 } } } };
|
|
const noField = deployRicheseNoField(createRicheseNoField(['zero', 'three', 'five']),
|
|
{ tokenId, controller: 'r', location: { homeworld: foreign } });
|
|
return { context, custody, noField };
|
|
}
|
|
|
|
for (const tokenId of ['zero', 'three', 'five']) {
|
|
void test(`Advanced native marker return and later exit preserve ${tokenId} without materializing it`, () => {
|
|
const f = fixture(tokenId), before = structuredClone(f);
|
|
const returnQuote = quoteRicheseHomeworldMarkerTransfer(f.context, f.custody,
|
|
{ player: 'r', tokenId, source: foreign, destination: native, forces: { normal: 2, elite: 0 } }, f.noField);
|
|
assert.equal(returnQuote.amount, 3);
|
|
assert.equal(returnQuote.cost, 3);
|
|
assert.equal(returnQuote.players!.find(player => player.id === 'r')!.reserves, 9);
|
|
assert.equal(returnQuote.state!.visitors[foreign]?.r, undefined);
|
|
assert.deepEqual(returnQuote.noField.deployed, { tokenId, controller: 'r', location: { homeworld: native } });
|
|
assert.equal(returnQuote.noField.lastShipped, tokenId);
|
|
assert.deepEqual(returnQuote.noField.tokens, f.noField.tokens);
|
|
assert.deepEqual(f, before);
|
|
|
|
const context: HomeworldShipmentContext = { ...f.context, players: f.context.players.map(seat => {
|
|
const next = returnQuote.players!.find(player => player.id === seat.id)!;
|
|
return { ...seat, reserves: next.reserves, eliteReserves: next.eliteReserves };
|
|
}) };
|
|
const exit = quoteRicheseHomeworldMarkerTransfer(context, returnQuote.state!,
|
|
{ player: 'r', tokenId, source: native, destination: other, forces: { normal: 2, elite: 0 } }, returnQuote.noField);
|
|
assert.equal(exit.amount, 3);
|
|
assert.equal(exit.cost, 3);
|
|
assert.equal(exit.players!.find(player => player.id === 'r')!.reserves, 7);
|
|
assert.deepEqual(exit.state!.visitors[other].r, { normal: 2, elite: 0 });
|
|
assert.deepEqual(exit.noField.deployed, { tokenId, controller: 'r', location: { homeworld: other } });
|
|
assert.equal(exit.noField.lastShipped, tokenId);
|
|
assert.deepEqual(exit.noField.tokens, f.noField.tokens);
|
|
});
|
|
}
|
|
|
|
void test('Basic keeps foreign-only marker endpoints and fresh native placement remains unavailable', () => {
|
|
const f = fixture(), basic = { ...f.context, advanced: false };
|
|
const intent = { player: 'r', tokenId: 'zero', source: foreign, destination: native };
|
|
assert.throws(() => quoteRicheseHomeworldMarkerTransfer(basic, f.custody, intent, f.noField), HomeworldCustodyError);
|
|
const nativeMarker = deployRicheseNoField(createRicheseNoField(['zero', 'three', 'five']),
|
|
{ tokenId: 'zero', controller: 'r', location: { homeworld: native } });
|
|
assert.throws(() => quoteRicheseHomeworldMarkerTransfer(basic, f.custody,
|
|
{ player: 'r', tokenId: 'zero', source: native, destination: other }, nativeMarker), HomeworldCustodyError);
|
|
assert.throws(() => quoteRicheseHomeworldMarkerShipment(f.context, f.custody,
|
|
{ player: 'r', tokenId: 'three', destination: native }, createRicheseNoField(['zero', 'three', 'five'])), HomeworldCustodyError);
|
|
assert.throws(() => quoteRicheseHomeworldMarkerTransfer(f.context, f.custody,
|
|
{ player: 'r', tokenId: 'zero', source: foreign, destination: foreign }, f.noField), HomeworldCustodyError);
|
|
const allied: HomeworldShipmentContext = { ...f.context, players: f.context.players.map(seat =>
|
|
seat.id === 'r' ? { ...seat, ally: 'g' } : seat.id === 'g' ? { ...seat, ally: 'r' } : seat) };
|
|
assert.throws(() => quoteRicheseHomeworldMarkerTransfer(allied, f.custody,
|
|
{ player: 'r', tokenId: 'zero', source: foreign, destination: other }, f.noField), HomeworldCustodyError);
|
|
});
|
|
|
|
for (const reserves of [0, 7]) {
|
|
void test(`Advanced native marker preserves physical population ${reserves} and adds only presence`, () => {
|
|
const f = fixture();
|
|
f.context.players[0].reserves = reserves;
|
|
f.custody.visitors[native] = { a: { normal: 1, elite: 0 } };
|
|
const context: HomeworldCombatContext = { ...f.context, order: ['r', 'a', 'g'] };
|
|
const marker = { controller: 'r', location: { homeworld: native } };
|
|
const before = structuredClone({ context, custody: f.custody });
|
|
assert.deepEqual(homeworldNoFieldPresence(context, f.custody, marker),
|
|
{ controller: 'r', homeworld: native, effectiveForces: 1 });
|
|
assert.equal(homeworldCombatLocation(context, f.custody, native).population, reserves);
|
|
assert.equal(homeworldCombatArmy(context, f.custody, native, 'r').normal, reserves);
|
|
const battles = quoteHomeworldBattlesWithNoField(context, f.custody, marker);
|
|
assert.equal(battles.filter(battle => battle.territory === native && battle.attacker === 'r' && battle.defender === 'a').length, 1);
|
|
const snapshot = currentHomeworldOccupationSnapshot(context, f.custody,
|
|
{ homeworld: native, controller: 'r' });
|
|
assert.deepEqual(homeworldOccupationSnapshotPresence(snapshot, native), ['a', 'r']);
|
|
assert.deepEqual({ context, custody: f.custody }, before);
|
|
const basic = { ...context, advanced: false };
|
|
assert.throws(() => homeworldNoFieldPresence(basic, f.custody, marker), /own Homeworld/);
|
|
assert.throws(() => currentHomeworldOccupationSnapshot(basic, f.custody,
|
|
{ homeworld: native, controller: 'r' }), /foreign world/);
|
|
});
|
|
}
|