82 lines
4.2 KiB
TypeScript
82 lines
4.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { type Game } from '../game/engine';
|
|
import { MOBILE_LOCATION } from '../game/board';
|
|
import { createSemutaIxianMovementFixture as fixture, actSemutaIxianMovement as act,
|
|
allowSemutaIxianMovement as allow, semutaIxianMovementPlayer as player,
|
|
semutaIxianMovementInventory as inventory } from './fixture-semuta-ixian-movement';
|
|
|
|
function spiceTotal(game: Game) {
|
|
return Object.values(game.spice).reduce((sum, amount) => sum + amount, 0) +
|
|
game.players.reduce((sum, p) => sum + p.spice, 0);
|
|
}
|
|
function forceTotals(game: Game) {
|
|
return game.players.map(p => ({ id:p.id,
|
|
total:p.reserves + p.tanks + Object.values(p.forces).reduce((sum, amount) => sum + amount, 0),
|
|
elites:p.elites ? p.elites.reserves + p.elites.tanks +
|
|
Object.values(p.elites.forces).reduce((sum, amount) => sum + amount, 0) : 0 }));
|
|
}
|
|
function selections(route: readonly string[], pile: string, amount: number) {
|
|
return route.map(location => ({ location, amount:location === pile ? amount : 0 }));
|
|
}
|
|
|
|
for (const advanced of [false, true]) {
|
|
const mode = advanced ? 'Advanced' : 'Basic';
|
|
void test(`${mode} native HMS collection counts foreign passengers for explicit choices while preserving legacy fallback`, () => {
|
|
const f = fixture({ kind:'mobileStronghold', advanced });
|
|
const source = structuredClone(f.beforeDeclaration), route = f.route!, pile = f.collection[0].location;
|
|
const originalForces = forceTotals(source), originalSpice = spiceTotal(source);
|
|
const ix = player(source, f.owner), foreign = player(source, 'atreides');
|
|
|
|
// Stage one Ixian and two foreign fighters aboard using existing counters.
|
|
assert.equal(ix.forces[MOBILE_LOCATION], 6);
|
|
assert.equal(ix.elites!.forces[MOBILE_LOCATION], 3);
|
|
ix.forces[MOBILE_LOCATION] = 1; ix.reserves += 5;
|
|
ix.elites!.forces[MOBILE_LOCATION] = 1; ix.elites!.reserves += 2;
|
|
assert.ok(foreign.reserves >= 2);
|
|
foreign.reserves -= 2;
|
|
foreign.forces[MOBILE_LOCATION] = (foreign.forces[MOBILE_LOCATION] ?? 0) + 2;
|
|
assert.equal(foreign.forces[MOBILE_LOCATION], 2);
|
|
|
|
// Concentrate existing spice on a visited sand sector. If the native blow
|
|
// is too small, transfer the difference from a player's existing supply.
|
|
const boardSpice = Object.values(source.spice).reduce((sum, amount) => sum + amount, 0);
|
|
source.spice = { [pile]:boardSpice };
|
|
if (boardSpice < 7) {
|
|
const difference = 7 - boardSpice;
|
|
const donor = source.players.find(p => p.spice >= difference);
|
|
assert.ok(donor);
|
|
donor.spice -= difference; source.spice[pile] += difference;
|
|
}
|
|
assert.equal(spiceTotal(source), originalSpice);
|
|
assert.deepEqual(forceTotals(source), originalForces);
|
|
const before = structuredClone(source);
|
|
|
|
assert.throws(() => act(source, f.owner, { type:'decision', route, collect:true,
|
|
collections:selections(route, pile, 7) }));
|
|
assert.deepEqual(source, before, 'Rejected choices do not mutate the game');
|
|
|
|
let pending = act(source, f.owner, { type:'decision', route, collect:true,
|
|
collections:selections(route, pile, 6) });
|
|
assert.equal(pending.response?.kind, 'mobileStronghold');
|
|
assert.deepEqual(pending.spice, source.spice, 'A cancelable move does not collect yet');
|
|
let done = allow(pending);
|
|
assert.equal(player(done, f.owner).spice, player(source, f.owner).spice + 6);
|
|
assert.equal(player(done, 'atreides').spice, player(source, 'atreides').spice);
|
|
assert.equal(done.spice[pile] ?? 0, source.spice[pile] - 6);
|
|
assert.equal(spiceTotal(done), originalSpice);
|
|
assert.deepEqual(forceTotals(done), originalForces);
|
|
assert.deepEqual(inventory(done), f.originalInventory);
|
|
|
|
// Omitted allocations retain the historical Ixian-only fallback for old
|
|
// clients and queued moves, even though explicit choices count passengers.
|
|
pending = act(source, f.owner, { type:'decision', route, collect:true });
|
|
done = allow(pending);
|
|
assert.equal(player(done, f.owner).spice, player(source, f.owner).spice + 2);
|
|
assert.equal(done.spice[pile] ?? 0, source.spice[pile] - 2);
|
|
assert.equal(spiceTotal(done), originalSpice);
|
|
assert.deepEqual(forceTotals(done), originalForces);
|
|
assert.deepEqual(inventory(done), f.originalInventory);
|
|
});
|
|
}
|