Files
Dune/tests/richese-homeworld-marker-fresh-mixed.test.ts
T
Quintine 4d769af8c7
Verify and publish checkpoint container / container (push) Successful in 10m7s
Expand Semuta and Richese Homeworld shipments
2026-10-28 22:26:47 +11:00

100 lines
5.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import type { GameView } from '../game/engine';
import { HomeworldCustodyError, homeworldForceGroups, type HomeworldCustody } from '../game/homeworld-custody';
import { homeworldPopulations } from '../game/homeworld-population';
import { richeseHomeworldMarkerChoice } from '../game/homeworld-shipment-options';
import type { HomeworldShipmentContext } from '../game/homeworld-shipment';
import { createRicheseNoField } from '../game/richese-no-field';
import { quoteRicheseHomeworldMarkerShipment as quote,
type RicheseHomeworldMarkerShipmentIntent } from '../game/richese-homeworld-marker-shipment';
function fixture() {
const context: HomeworldShipmentContext = {advanced: false, players: [
{id: 'r', faction: 'richese', reserves: 12, 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: {
'homeworld:atreides': {r: {normal: 2, elite: 0}},
}};
const noField = createRicheseNoField(['zero', 'three', 'five']);
const intent: RicheseHomeworldMarkerShipmentIntent = {player: 'r', destination: 'homeworld:atreides',
tokenId: 'zero', forces: {normal: 3, elite: 0}};
return {context, custody, noField, intent};
}
void test('fresh marker and paid physical companions form one detached shipment in both rulesets', () => {
for (const advanced of [false, true]) for (const tokenId of ['zero', 'three', 'five']) {
const f = fixture(); f.context.advanced = advanced; f.intent.tokenId = tokenId;
const before = structuredClone(f);
const result = quote(f.context, f.custody, f.intent, f.noField);
assert.equal(result.amount, 4); assert.equal(result.cost, 4); assert.equal(result.elite, 0);
assert.deepEqual(result.noField.deployed, {tokenId, controller: 'r', location: {homeworld: 'homeworld:atreides'}});
assert.equal(result.players?.find(player => player.id === 'r')?.reserves, 9);
assert.deepEqual(result.state?.visitors['homeworld:atreides'].r, {normal: 5, elite: 0});
assert.deepEqual(result.sources?.[0], {homeworld: 'homeworld:richese', player: 'r',
before: {normal: 12, elite: 0}, after: {normal: 9, elite: 0}});
assert.deepEqual(f, before);
result.state!.visitors['homeworld:atreides'].r.normal = 0;
assert.deepEqual(f, before);
}
});
void test('whole mixed invoice rounds once and zero companions keep the legacy quote', () => {
const f = fixture();
const legacy = {...f.intent}; delete legacy.forces;
assert.deepEqual(quote(f.context, f.custody, {...legacy, forces: {normal: 0, elite: 0}}, f.noField),
quote(f.context, f.custody, legacy, f.noField));
f.context.purchasedGuildRate = true;
for (const count of [1, 2, 3, 11, 12]) {
f.intent.forces = {normal: count, elite: 0};
const result = quote(f.context, f.custody, f.intent, f.noField);
assert.equal(result.amount, count + 1); assert.equal(result.cost, Math.ceil((count + 1) / 2));
}
});
void test('invalid companion quantities, unavailable reserves and extra sources reject without mutation', () => {
for (const forces of [
{normal: 13, elite: 0}, {normal: 1, elite: 1}, {normal: -1, elite: 0},
{normal: 0.5, elite: 0}, {normal: 1}, {normal: 1, elite: 0, extra: 0}, null,
]) {
const f = fixture();
f.intent = {...f.intent, forces} as unknown as RicheseHomeworldMarkerShipmentIntent;
const before = structuredClone(f);
assert.throws(() => quote(f.context, f.custody, f.intent, f.noField), HomeworldCustodyError);
assert.deepEqual(f, before);
}
const f = fixture(); f.intent = {...f.intent, source: 'homeworld:guild'} as RicheseHomeworldMarkerShipmentIntent;
const before = structuredClone(f);
assert.throws(() => quote(f.context, f.custody, f.intent, f.noField), HomeworldCustodyError);
assert.deepEqual(f, before);
});
void test('owned controls quote fresh reserve companions, exact source, affordability and rate', () => {
const f = fixture(), populations = homeworldPopulations(f.context, f.custody);
const view = {
me: 'r', active: 'r', advanced: false, status: 'playing', phase: 5,
homeworldShipment: {event: 'shipment', blocked: null}, aid: {available: 0, pledged: 0},
players: f.context.players.map(player => ({...player, name: player.faction, spice: player.id === 'r' ? 6 : undefined,
shipped: false, elites: {reserves: 0}})),
homeworlds: {worlds: homeworldForceGroups(f.context, f.custody).map(world => ({...world,
...populations.find(population => population.location === world.id)!}))},
richeseNoField: {owner: 'r', event: 'marker', private: f.noField, canShip: true},
} as unknown as GameView;
const before = structuredClone(view);
const chosen = richeseHomeworldMarkerChoice(view, 'homeworld:atreides', 'three', 0, undefined, {normal: 3, elite: 0});
assert.equal(chosen.amount, 4); assert.equal(chosen.cost, 4);
assert.deepEqual(chosen.action, {type: 'homeworldShip', event: 'shipment', destination: 'homeworld:atreides',
sources: {'homeworld:richese': {normal: 3, elite: 0}}, noField: 'three', noFieldEvent: 'marker', allyPayment: 0});
assert.deepEqual(view, before);
const discount = {...view, karamaShipping: {player: 'r'}} as GameView;
assert.equal(richeseHomeworldMarkerChoice(discount, 'homeworld:atreides', 'three', 0, undefined,
{normal: 2, elite: 0}).cost, 2);
view.players[0].spice = 2;
assert.equal(richeseHomeworldMarkerChoice(view, 'homeworld:atreides', 'three', 0, undefined,
{normal: 3, elite: 0}).action, null);
assert.equal(richeseHomeworldMarkerChoice(view, 'homeworld:atreides', 'three', 0, undefined,
{normal: 13, elite: 0}).action, null);
});