Prepare allied shipment quotes for larger alliances
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
cea310b030
commit
77376c39d6
5 files changed
+210
-28
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createGame, newPlayer } from '../game/engine';
|
||||
import { alliedShipmentWitnesses, isAlliedShipmentVisit, makeBasicAlliedShipment,
|
||||
quoteBasicAlliedShipment } from '../game/allied-separation';
|
||||
import type { CoalitionState } from '../game/coalition-alliance';
|
||||
|
||||
function fixture() {
|
||||
const g = createGame('GROUPSHIP', newPlayer('a', 'Atreides', 'atreides'), false);
|
||||
g.players.push(newPlayer('b', 'Emperor', 'emperor'), newPlayer('c', 'Fremen', 'fremen'),
|
||||
newPlayer('d', 'Harkonnen', 'harkonnen'));
|
||||
Object.assign(g, { advanced: false, phase: 5, turn: 4, active: 'a' });
|
||||
for (const p of g.players) { p.forces = {}; p.ally = null; }
|
||||
Object.assign(g.players[0], { shipped: false, moved: 0 });
|
||||
const coalitionState: CoalitionState = { version: 1, revision: 1, proposal: null, usedEvents: [],
|
||||
groups: [{ id: 'abc', members: ['a', 'b', 'c'], formedTurn: 2, edges: [
|
||||
{ members: ['a', 'b'], formedTurn: 2 }, { members: ['a', 'c'], formedTurn: 4 },
|
||||
{ members: ['b', 'c'], formedTurn: 4 },
|
||||
] }] };
|
||||
return { g, coalitionState, p: g.players[0] };
|
||||
}
|
||||
|
||||
void test('group arrival records every present ally and a single physical baseline', () => {
|
||||
const { g, coalitionState, p } = fixture();
|
||||
p.forces = { 'red_chasm:7': 3 };
|
||||
g.players[1].forces = { 'red_chasm:7': 1 };
|
||||
g.players[2].forces = { 'red_chasm:7': 1 };
|
||||
const context = { ...g, coalitionState };
|
||||
const before = structuredClone(context);
|
||||
assert.deepEqual(alliedShipmentWitnesses(context, p, 'red_chasm'), ['b', 'c']);
|
||||
const receipt = makeBasicAlliedShipment(context, p, 'red_chasm')!;
|
||||
assert.deepEqual(receipt, { player: 'a', ally: 'b', allies: ['b', 'c'], turn: 4,
|
||||
territory: 'red_chasm', baseline: { normal: 3, elite: 0 } });
|
||||
assert.deepEqual(context, before);
|
||||
p.forces['red_chasm:7'] += 2;
|
||||
assert.deepEqual(quoteBasicAlliedShipment(context, p, receipt), { territory: 'red_chasm', normal: 2, elite: 0 });
|
||||
g.players[1].forces = {};
|
||||
assert.equal(quoteBasicAlliedShipment(context, p, receipt)?.normal, 2);
|
||||
g.players[2].forces = {};
|
||||
assert.equal(quoteBasicAlliedShipment(context, p, receipt), null);
|
||||
});
|
||||
|
||||
void test('group quote follows a current ally who was not present at shipment', () => {
|
||||
const { g, coalitionState, p } = fixture();
|
||||
g.players[1].forces = { 'red_chasm:7': 1 };
|
||||
const context = { ...g, coalitionState };
|
||||
const receipt = makeBasicAlliedShipment(context, p, 'red_chasm')!;
|
||||
assert.deepEqual(receipt.allies, ['b']);
|
||||
p.forces = { 'red_chasm:7': 2 };
|
||||
g.players[1].forces = {};
|
||||
g.players[2].forces = { 'red_chasm:7': 1 };
|
||||
assert.equal(quoteBasicAlliedShipment(context, p, receipt)?.normal, 2);
|
||||
p.forces = {};
|
||||
assert.equal(quoteBasicAlliedShipment(context, p, receipt), null);
|
||||
});
|
||||
|
||||
void test('group quote separately retains the normal and elite physical baseline', () => {
|
||||
const { g, coalitionState, p } = fixture();
|
||||
p.forces = { 'red_chasm:7': 3 };
|
||||
p.elites = { reserves: 4, tanks: 0, revived: 0, forces: { 'red_chasm:7': 1 } };
|
||||
g.players[1].forces = { 'red_chasm:7': 1 };
|
||||
const context = { ...g, coalitionState };
|
||||
const receipt = makeBasicAlliedShipment(context, p, 'red_chasm')!;
|
||||
assert.deepEqual(receipt.baseline, { normal: 2, elite: 1 });
|
||||
p.forces['red_chasm:7'] = 6;
|
||||
p.elites.forces['red_chasm:7'] = 2;
|
||||
assert.deepEqual(quoteBasicAlliedShipment(context, p, receipt), { territory: 'red_chasm', normal: 2, elite: 1 });
|
||||
});
|
||||
|
||||
void test('legacy pair receipt stays compatible and requires its original reciprocal ally', () => {
|
||||
const { g, p } = fixture();
|
||||
p.ally = 'b'; g.players[1].ally = 'a';
|
||||
p.forces = { 'red_chasm:7': 2 }; g.players[1].forces = { 'red_chasm:7': 1 };
|
||||
const receipt = makeBasicAlliedShipment(g, p, 'red_chasm')!;
|
||||
assert.equal(receipt.allies, undefined);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(receipt)), receipt);
|
||||
p.forces['red_chasm:7'] += 3;
|
||||
assert.equal(quoteBasicAlliedShipment(g, p, receipt)?.normal, 3);
|
||||
g.players[1].ally = null;
|
||||
assert.equal(quoteBasicAlliedShipment(g, p, receipt), null);
|
||||
});
|
||||
|
||||
void test('shipment eligibility keeps turn and destination guards and rejects invalid ledgers', () => {
|
||||
const { g, coalitionState, p } = fixture();
|
||||
g.players[2].forces = { 'red_chasm:7': 1, 'polar_sink:0': 1 };
|
||||
const context = { ...g, coalitionState };
|
||||
assert.equal(isAlliedShipmentVisit(context, p, 'red_chasm'), true);
|
||||
assert.equal(isAlliedShipmentVisit(context, p, 'polar_sink'), false);
|
||||
assert.equal(isAlliedShipmentVisit({ ...context, active: 'b' }, p, 'red_chasm'), false);
|
||||
p.moved = 1;
|
||||
assert.equal(isAlliedShipmentVisit(context, p, 'red_chasm'), false);
|
||||
assert.throws(() => isAlliedShipmentVisit({ ...context, coalitionState: null as unknown as CoalitionState }, p, 'red_chasm'), /ledger cannot be null/);
|
||||
p.moved = 0;
|
||||
assert.throws(() => isAlliedShipmentVisit({ ...context, advanced: true }, p, 'red_chasm'), /Basic play without Ecaz/);
|
||||
g.players[3].faction = 'ecaz';
|
||||
assert.throws(() => isAlliedShipmentVisit(context, p, 'red_chasm'), /Basic play without Ecaz/);
|
||||
});
|
||||
Reference in new issue
Block a user