Files
Dune/tests/coalition-guild-truth-transport.test.ts
T
Quintine f667383565
Verify and publish checkpoint container / container (push) Successful in 12m6s
Connect Kull bid answers and group transport
2026-10-29 00:17:48 +11:00

92 lines
4.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { baseDeck } from '../game/cards';
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
import { applyAction, createGame, newPlayer, type Action, type Game } from '../game/engine';
const send = (game: Game, id: string, action: Action) => applyAction(game, id, action);
const seat = (game: Game, id: string) => game.players.find(player => player.id === id)!;
/** The group is formed through actual Nexus actions. The turn, forces and
* unrelated accepted Truthtrance No answer are staged for this interaction. */
function fixture() {
let game = createGame('GROUPTRUTHSHIP', newPlayer('g', 'Guild', 'guild'), false);
game.players.push(newPlayer('e', 'Emperor', 'emperor'), newPlayer('f', 'Fremen', 'fremen'),
newPlayer('h', 'Harkonnen', 'harkonnen'));
Object.assign(game, {status:'playing', phase:1, turn:2, nexus:true, order:['g','e','f','h'],
ready:[], storm:18, deck:baseDeck(), response:null, decision:null, phaseOpening:null});
game.coalitionState = emptyCoalitionState();
game = send(game, 'g', {type:'alliance', target:'e'});
game = send(game, 'e', {type:'alliance', target:'g'});
const pair = coalitionOf(game.players, game.coalitionState, 'g'); assert.ok(pair);
game = send(game, 'g', {type:'coalitionPropose', groupId:pair.id, entrant:'f',
expectedRevision:game.coalitionState!.revision});
for (const id of ['e', 'f']) {
const offer = game.coalitionState!.proposal!;
game = send(game, id, {type:'coalitionAnswer', event:offer.event, nexus:offer.nexus,
turn:offer.turn, accept:true, expectedRevision:game.coalitionState!.revision});
}
Object.assign(game, {phase:5, nexus:false, active:'e', ready:[],
movementRemaining:['e','g','f','h'], guildTimingGranted:false, guildTimingLocked:false});
for (const player of game.players) {
player.forces = {}; player.reserves = 20; player.tanks = 0; player.spice = 10;
player.hand = []; player.traitors = []; player.traitorChoices = [];
player.shipped = false; player.moved = 0;
}
seat(game, 'e').forces = {'imperial_basin:10':3}; seat(game, 'e').reserves = 17;
const index = game.deck.findIndex(card => card.effect === 'karama'); assert.ok(index >= 0);
const [karama] = game.deck.splice(index, 1); seat(game, 'h').hand.push(karama);
game.bribePromises = [{event:'other-player-no', player:'h', asker:'e', turn:game.turn,
recipient:'f', minimum:1, answer:false, progress:0}];
return {game, karama};
}
function passKind(game: Game, kind: string): Game {
for (let count = 0; game.response?.kind === kind && count < 24; count++) {
const next = game.players.find(player => !game.response!.passed.includes(player.id));
assert.ok(next); game = send(game, next.id, {type:'passResponse'});
}
assert.notEqual(game.response?.kind, kind);
return game;
}
function passAll(game: Game): Game {
for (let count = 0; game.response && count < 32; 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.response ?? null, null);
return game;
}
const transport: Action = {type:'guildShip', forces:{'imperial_basin:10':3},
territory:'red_chasm', sector:7, allyPayment:0};
void test('an unrelated Truthtrance No retains self-funded group Guild transport and both response windows', () => {
let {game} = fixture();
game = send(game, 'e', transport);
assert.equal(game.response?.kind, 'guildTransportPermission');
assert.equal(seat(game, 'e').forces['imperial_basin:10'], 3);
game = passKind(game, 'guildTransportPermission');
assert.equal(game.response?.kind, 'guildRate');
assert.equal(game.pendingGuildTransport?.cost, 3);
game = passAll(game);
assert.equal(seat(game, 'e').forces['red_chasm:7'], 3);
assert.equal(seat(game, 'e').spice, 7);
assert.equal(seat(game, 'g').spice, 13);
assert.ok(game.bribePromises?.some(row => row.event === 'other-player-no' && !row.fulfilled && !row.released));
});
void test('Karama still cancels the native group Guild rate when another player has an unrelated commitment', () => {
const fixtureState = fixture();
let game = fixtureState.game;
const {karama} = fixtureState;
game = send(game, 'e', transport);
game = passKind(game, 'guildTransportPermission');
assert.equal(game.response?.kind, 'guildRate');
game = send(game, 'h', {type:'card', mode:'cancel', card:karama.id});
game = passAll(game);
assert.equal(seat(game, 'e').forces['red_chasm:7'], 3);
assert.equal(seat(game, 'e').spice, 4, 'The shipper pays the full six-spice rate.');
assert.equal(seat(game, 'g').spice, 16);
assert.equal(game.discard.filter(card => card.id === karama.id).length, 1);
assert.ok(game.bribePromises?.some(row => row.event === 'other-player-no' && !row.fulfilled && !row.released));
});