156 lines
9.8 KiB
TypeScript
156 lines
9.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 { presenceAt } from '../game/force-presence';
|
|
import { reserveShipmentCost } from '../game/shipment-price';
|
|
import { territory } from '../game/board';
|
|
import { quoteRicheseHomeworldForces } from '../game/richese-homeworld-shipment';
|
|
import {
|
|
createRicheseHomeworldShipmentFixture as fixture,
|
|
actRicheseHomeworldShipment as act,
|
|
commitRicheseHomeworldShipment as commit,
|
|
revealRicheseHomeworldShipment as reveal,
|
|
finishRicheseHomeworldShipment as finish,
|
|
richeseHomeworldShipmentPlayer as player,
|
|
richeseHomeworldShipmentPopulation as population,
|
|
richeseHomeworldShipmentInventory as inventory,
|
|
} from './fixture-richese-homeworld-shipment';
|
|
|
|
function reject(game: Game, actor: string, action: Action, reason?: RegExp) {
|
|
const before = structuredClone(game);
|
|
if (reason) assert.throws(() => applyAction(game, actor, action), reason);
|
|
else assert.throws(() => applyAction(game, actor, action));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
|
|
for (const advanced of [false, true]) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native HIGH Richese commits eleven paid reserves and one concealed marker, then may reveal after crossing LOW`, () => {
|
|
const f = fixture({ advanced }), before = structuredClone(player(f.game, f.owner));
|
|
assert.equal(f.game.turn, 2); assert.equal(f.game.phase, 5); assert.equal(f.game.active, f.owner);
|
|
assert.equal(f.funding.length, 1);
|
|
assert.deepEqual(f.funding[0].steps, [{ actor: f.holder, action: { type: 'bribe', target: f.owner, amount: 7 } }]);
|
|
assert.equal(before.spice, 12); assert.equal(before.bribes, 0); assert.equal(before.reserves, 20);
|
|
assert.equal(before.shipped, false); assert.equal(before.moved, 0);
|
|
assert.equal(population(f.game, f.owner).side, 'high');
|
|
assert.equal(viewGame(f.game, f.owner).richeseNoField?.homeworldForcesEnabled, true);
|
|
assert.deepEqual(quoteRicheseHomeworldForces({ enabled: true, reserves: before.reserves, amount: f.companions }),
|
|
{ paid: 11, physical: 11, priced: 12 });
|
|
const action: Action = advanced ? { ...f.action, amount: 1 } : f.action;
|
|
const result = commit(f.game, f, action), requested = player(result.requested, f.owner), after = player(result.game, f.owner);
|
|
assert.equal(result.requested.response?.kind, 'richeseNoField');
|
|
assert.equal(result.requested.pendingShipment?.noFieldReserves, 11);
|
|
assert.equal(result.requested.pendingShipment?.cost, 12);
|
|
assert.ok(result.requested.pendingShipment?.noFieldSkillProof);
|
|
assert.equal(requested.reserves, before.reserves); assert.equal(requested.spice, before.spice);
|
|
assert.equal(requested.shipped, false); assert.deepEqual(requested.forces, {});
|
|
assert.deepEqual(requested.noField, before.noField); assert.equal(requested.noFieldEvent, before.noFieldEvent);
|
|
assert.equal(after.reserves, 9); assert.deepEqual(after.forces, { [f.location]: 11 });
|
|
assert.equal(after.spice, 0); assert.equal(after.shipped, true); assert.equal(after.moved, 0);
|
|
assert.equal(after.noField?.deployed?.tokenId, f.token);
|
|
assert.deepEqual(after.noField?.deployed?.location, { territory: f.territory, sector: f.sector });
|
|
assert.equal(after.noField?.lastShipped, f.token);
|
|
assert.notEqual(after.noFieldEvent, before.noFieldEvent);
|
|
assert.equal(presenceAt(after, f.territory), 12, 'The marker counts once beside eleven physical counters');
|
|
assert.equal(population(result.game, f.owner).side, 'low');
|
|
assert.equal(viewGame(result.game, f.owner).richeseNoField?.homeworldForcesEnabled, false);
|
|
const opponent = viewGame(result.game, f.holder);
|
|
assert.equal(opponent.richeseNoField?.private, null);
|
|
assert.equal(opponent.richeseNoField?.public.deployed?.effectiveForces, 1);
|
|
assert.equal(opponent.richeseNoField?.public.lastUsed, null);
|
|
assert.ok(player(result.game, f.holder).hand.some(card => card.id === f.karama));
|
|
assert.deepEqual(inventory(result.game), f.originalInventory);
|
|
const revealed = reveal(result.game, f), done = player(revealed.game, f.owner);
|
|
assert.equal(done.noField?.deployed, null); assert.equal(done.noField?.lastShipped, f.token);
|
|
assert.equal(done.reserves, 4); assert.deepEqual(done.forces, { [f.location]: 16 });
|
|
assert.equal(done.spice, after.spice); assert.equal(done.moved, 0); assert.equal(done.shipped, true);
|
|
reject(revealed.game, f.owner, revealed.action, /No concealed/);
|
|
assert.deepEqual(inventory(revealed.game), f.originalInventory);
|
|
assert.equal(f.sourceActionCount, f.actions.length);
|
|
});
|
|
}
|
|
|
|
void test('the original single-marker omission still pays one without withdrawing HIGH physical reserves', () => {
|
|
const f = fixture(), before = structuredClone(player(f.game, f.owner));
|
|
const action: Action = { type: 'ship', noField: f.token, event: f.event,
|
|
territory: f.territory, sector: f.sector, allyPayment: 0 };
|
|
const result = commit(f.game, f, action), after = player(result.game, f.owner);
|
|
assert.equal(result.requested.pendingShipment?.noFieldReserves, undefined);
|
|
assert.equal(after.spice, before.spice - 1);
|
|
assert.equal(after.reserves, 20); assert.deepEqual(after.forces, {});
|
|
assert.equal(after.noField?.deployed?.tokenId, f.token);
|
|
assert.equal(presenceAt(after, f.territory), 1);
|
|
assert.equal(population(result.game, f.owner).side, 'high');
|
|
assert.deepEqual(inventory(result.game), f.originalInventory);
|
|
});
|
|
|
|
void test('sixteen genuine paid companions leave only four native reserves for a printed five marker to reveal', () => {
|
|
const f = fixture({ companions: 16 }), before = structuredClone(player(f.game, f.owner));
|
|
assert.equal(f.game.turn, 3);
|
|
assert.deepEqual(f.funding.map(record => record.steps[0].action.amount), [10, 2]);
|
|
assert.ok(f.actions.some(step => step.actor === f.holder && step.action.type === 'charity'));
|
|
assert.equal(before.spice, 17); assert.equal(before.reserves, 20);
|
|
const result = commit(f.game, f), after = player(result.game, f.owner);
|
|
assert.equal(after.reserves, 4); assert.deepEqual(after.forces, { [f.location]: 16 });
|
|
assert.equal(before.spice - after.spice, 17);
|
|
assert.equal(after.noField?.deployed?.tokenId, f.token);
|
|
const revealed = reveal(result.game, f), done = player(revealed.game, f.owner);
|
|
assert.equal(done.reserves, 0); assert.deepEqual(done.forces, { [f.location]: 20 });
|
|
assert.equal(done.tanks, 0); assert.equal(done.noField?.deployed, null);
|
|
assert.equal(done.noField?.lastShipped, f.token);
|
|
assert.deepEqual(inventory(revealed.game), f.originalInventory);
|
|
});
|
|
|
|
void test('the genuinely dealt opposing Karama prevents the entire unpaid mixed declaration and preserves ordinary replacement shipment', () => {
|
|
const f = fixture(), before = structuredClone(player(f.game, f.owner));
|
|
const pending = act(f.game, f.owner, f.action);
|
|
assert.equal(pending.response?.kind, 'richeseNoField');
|
|
assert.equal(pending.pendingShipment?.noFieldReserves, 11);
|
|
const prevented = finish(act(pending, f.holder, f.cancel)), after = player(prevented, f.owner);
|
|
assert.equal(after.reserves, before.reserves); assert.equal(after.spice, before.spice);
|
|
assert.deepEqual(after.forces, before.forces); assert.deepEqual(after.noField, before.noField);
|
|
assert.equal(after.noFieldEvent, before.noFieldEvent);
|
|
assert.equal(after.shipped, false); assert.equal(after.moved, 0);
|
|
assert.equal(prevented.pendingShipment ?? null, null);
|
|
assert.equal(prevented.discard.filter(card => card.id === f.karama).length, 1);
|
|
reject(prevented, f.owner, f.action, /prevented No-Field/);
|
|
const replaced = finish(act(prevented, f.owner, f.replacement)), replacement = player(replaced, f.owner);
|
|
assert.equal(replacement.reserves, before.reserves - 1);
|
|
assert.deepEqual(replacement.forces, { [f.location]: 1 });
|
|
assert.equal(replacement.spice, before.spice - 1);
|
|
assert.equal(replacement.shipped, true); assert.equal(replacement.moved, 0);
|
|
assert.deepEqual(replacement.noField, before.noField);
|
|
assert.deepEqual(inventory(replaced), f.originalInventory);
|
|
});
|
|
|
|
void test('mixed payloads preserve the one-marker amount and require positive available physical reserves', () => {
|
|
const f = fixture();
|
|
reject(f.game, f.owner, { ...f.action, amount: 2 }, /one No-Field/);
|
|
reject(f.game, f.owner, { ...f.action, noFieldReserves: 0 });
|
|
reject(f.game, f.owner, { ...f.action, noFieldReserves: 21 });
|
|
reject(f.game, f.owner, { ...f.replacement, noFieldReserves: 1 });
|
|
assert.deepEqual(inventory(f.game), f.originalInventory);
|
|
});
|
|
|
|
void test('all four existing profiles propose a legal positive companion shipment through the native response and payment', () => {
|
|
const f = fixture();
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view = viewGame(f.game, f.owner); view.players.find(player => player.id === f.owner)!.bot = difficulty;
|
|
const action = botActions(view).find(action => action.type === 'ship' && action.noField &&
|
|
typeof action.noFieldReserves === 'number' && action.noFieldReserves > 0);
|
|
assert.ok(action, `${difficulty} retains a minimal positive HIGH companion path`);
|
|
const companions = Number(action.noFieldReserves);
|
|
const priced = 1 + companions;
|
|
const expectedCost = reserveShipmentCost({ faction: 'richese', halfRate: false },
|
|
territory(String(action.territory)).type, priced);
|
|
const result = commit(structuredClone(f.game), f, action), after = player(result.game, f.owner);
|
|
assert.equal(after.reserves, 20 - companions);
|
|
assert.equal(Object.values(after.forces).reduce((total, count) => total + count, 0), companions);
|
|
assert.equal(after.spice, player(f.game, f.owner).spice - expectedCost);
|
|
assert.equal(after.shipped, true); assert.equal(after.moved, 0);
|
|
assert.ok(after.noField?.deployed);
|
|
assert.deepEqual(inventory(result.game), f.originalInventory);
|
|
}
|
|
});
|