Connect group allied transit custody
Verify and publish checkpoint container / container (push) Successful in 10m27s
Verify and publish checkpoint container / container (push) Successful in 10m27s
This commit is contained in:
1 parent
ea3b21f92c
commit
4798a3579b
5 files changed
+152
-23
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { baseDeck } from '../game/cards';
|
||||
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { createRicheseNoField } from '../game/richese-no-field';
|
||||
import type { CoalitionState } from '../game/coalition-alliance';
|
||||
|
||||
/** Controlled Basic phase and force placement; the three-member group is
|
||||
* staged because Nexus cannot create one yet. No full-game claim is made. */
|
||||
function fixture(marker = false): Game {
|
||||
const game = createGame('GROUPTRANSIT', newPlayer('a', 'Visitor', marker ? 'richese' : 'atreides'),
|
||||
false, marker ? ['choam'] : []);
|
||||
game.players.push(newPlayer('b', 'First ally', 'emperor'), newPlayer('c', 'Second ally', 'fremen'),
|
||||
newPlayer('d', 'Outsider', 'harkonnen'));
|
||||
game.status = 'playing'; game.phase = 5; game.turn = 2; game.active = 'a'; game.storm = 18;
|
||||
game.order = ['a', 'b', 'c', 'd']; game.movementRemaining = ['a', 'b', 'c', 'd'];
|
||||
game.deck = baseDeck();
|
||||
for (const player of game.players) {
|
||||
player.forces = {}; player.reserves = 20; player.tanks = 0; player.spice = 10;
|
||||
player.hand = []; player.traitorChoices = []; player.traitors = [];
|
||||
player.ally = null; delete player.allySinceTurn;
|
||||
player.shipped = false; player.moved = 0;
|
||||
}
|
||||
game.players[0].forces['imperial_basin:11'] = 2; game.players[0].reserves = 18;
|
||||
game.players[1].forces['imperial_basin:11'] = 2; game.players[1].reserves = 18;
|
||||
game.players[2].forces['polar_sink:0'] = 2; game.players[2].reserves = 18;
|
||||
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: 2 },
|
||||
{ members: ['b', 'c'], formedTurn: 2 },
|
||||
] }] };
|
||||
game.coalitionState = coalitionState;
|
||||
if (marker) {
|
||||
game.players[0].noField = createRicheseNoField(['zero', 'three', 'five']);
|
||||
game.players[0].noFieldEvent = 'marker-event';
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
||||
const send = (game: Game, id: string, action: Action) => applyAction(game, id, action);
|
||||
function finishResponse(game: Game) {
|
||||
for (let count = 0; game.response && count < 10; count++) {
|
||||
const next = game.players.find(player => !game.response!.passed.includes(player.id));
|
||||
assert.ok(next);
|
||||
game = send(game, next.id, {type: 'passResponse'});
|
||||
}
|
||||
assert.equal(game.pendingShipment ?? null, null);
|
||||
return game;
|
||||
}
|
||||
function ship(game: Game): Game {
|
||||
return finishResponse(send(game, 'a', {type: 'ship', amount: 3, elite: 0,
|
||||
territory: 'imperial_basin', sector: 9, allyPayment: 0}));
|
||||
}
|
||||
function warn(game: Game) { return viewGame(game, 'a').basicAlliedShipment; }
|
||||
function forceTotal(game: Game, id: string, prefix: string) {
|
||||
return Object.entries(game.players.find(player => player.id === id)!.forces)
|
||||
.reduce((total, [key, value]) => total + (key.startsWith(`${prefix}:`) ? value : 0), 0);
|
||||
}
|
||||
|
||||
void test('ordinary movement cannot enter either group ally territory', () => {
|
||||
const game = fixture();
|
||||
// Put the second ally in a non-polar site without changing its physical pool.
|
||||
delete game.players[2].forces['polar_sink:0'];
|
||||
game.players[2].forces['red_chasm:7'] = 2;
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'a', {type: 'move', forces: {'imperial_basin:11': 1},
|
||||
territory: 'red_chasm', sector: 7}), /occupied by your ally/);
|
||||
assert.deepEqual(game, before);
|
||||
});
|
||||
|
||||
void test('a three-member group visitor can ship then depart without losing its pre-arrival forces', () => {
|
||||
const original = fixture(); let game = ship(original);
|
||||
assert.equal(game.players[0].spice, 4);
|
||||
assert.deepEqual(game.basicAlliedShipment?.allies, ['b']);
|
||||
assert.equal(warn(game)?.normal, 3);
|
||||
game = send(game, 'a', {type: 'move', forces: {'imperial_basin:9': 3},
|
||||
territory: 'polar_sink', sector: 0});
|
||||
assert.equal(warn(game), null);
|
||||
game = send(game, 'a', {type: 'endMovement'});
|
||||
assert.equal(game.players[0].tanks, 0);
|
||||
assert.equal(forceTotal(game, 'a', 'imperial_basin'), 2);
|
||||
assert.equal(forceTotal(game, 'a', 'polar_sink'), 3);
|
||||
assert.equal(forceTotal(game, 'b', 'imperial_basin'), 2);
|
||||
});
|
||||
|
||||
void test('group visitor warning and loss follow a different current ally with partial departure', () => {
|
||||
let game = ship(fixture());
|
||||
// Staged witness switch: relocate existing counters only, preserving each
|
||||
// physical pool; gameplay does not move another ally during this turn.
|
||||
delete game.players[1].forces['imperial_basin:11']; game.players[1].forces['polar_sink:0'] = 2;
|
||||
delete game.players[2].forces['polar_sink:0']; game.players[2].forces['imperial_basin:11'] = 2;
|
||||
assert.equal(warn(game)?.normal, 3);
|
||||
game = send(game, 'a', {type: 'move', forces: {'imperial_basin:9': 2},
|
||||
territory: 'polar_sink', sector: 0});
|
||||
assert.equal(warn(game)?.normal, 1);
|
||||
game = send(game, 'a', {type: 'endMovement'});
|
||||
assert.equal(game.players[0].tanks, 1);
|
||||
assert.equal(forceTotal(game, 'a', 'imperial_basin'), 2);
|
||||
assert.equal(forceTotal(game, 'c', 'imperial_basin'), 2);
|
||||
assert.equal(forceTotal(game, 'b', 'polar_sink'), 2);
|
||||
});
|
||||
|
||||
void test('group visiting zero No-Field remains visible as a marker obligation and reveals without loss', () => {
|
||||
let game = fixture(true);
|
||||
game = finishResponse(send(game, 'a', {type: 'ship', noField: 'zero', event: 'marker-event',
|
||||
territory: 'imperial_basin', sector: 9, allyPayment: 0}));
|
||||
assert.deepEqual(warn(game), {territory: 'Imperial Basin', normal: 0, elite: 0, noField: true});
|
||||
assert.equal(viewGame(game, 'b').basicAlliedShipment, null);
|
||||
game = send(game, 'a', {type: 'endMovement'});
|
||||
assert.equal(game.players[0].noField!.deployed, null);
|
||||
assert.equal(game.players[0].noField!.lastShipped, 'zero');
|
||||
assert.equal(game.players[0].tanks, 0);
|
||||
assert.equal(forceTotal(game, 'a', 'imperial_basin'), 2);
|
||||
assert.equal(forceTotal(game, 'b', 'imperial_basin'), 2);
|
||||
});
|
||||
Reference in new issue
Block a user