Files
Dune/tests/nexus-guild-cunning-interruptions.test.ts

161 lines
5.2 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
applyAction,
viewGame,
normalizeAutomaticGame,
type Game,
} from '../game/engine';
import {
nexusGuildCunningFixture,
originalGuildCunningTurn,
nexusGuildCunningRequest,
holdGuildCunningCard,
nexusGuildCunningAllow,
nexusGuildCunningInventory,
nexusGuildCunningReload,
} from './fixture-nexus-guild-cunning';
function stable(g: Game) {
nexusGuildCunningInventory(g);
for (const p of g.players)
assert.deepEqual(
viewGame(nexusGuildCunningReload(g), p.id),
viewGame(g, p.id),
);
assert.deepEqual(normalizeAutomaticGame(nexusGuildCunningReload(g)), g);
}
function nested() {
const f = nexusGuildCunningFixture({
advanced: true,
opponentFaction: 'beneGesserit',
});
const worthless = holdGuildCunningCard(f.g, f.target, 'worthless');
const karama = holdGuildCunningCard(f.g, f.observer, 'karama');
let g = originalGuildCunningTurn(f, false);
assert.equal(g.decision?.kind, 'advisor');
g = applyAction(g, f.target, { type: 'decision', accept: false });
g = applyAction(g, f.owner, {
type: 'move',
from: 'arrakeen:10',
territory: 'hagga_basin',
sector: 12,
amount: 5,
});
const original = nexusGuildCunningReload(g);
g = applyAction(g, f.owner, nexusGuildCunningRequest(f, g));
assert.equal(g.response?.kind, 'nexusGuildCunning');
const response = structuredClone(g.response);
g = applyAction(g, f.target, {
type: 'card',
card: worthless.id,
mode: 'cancel',
});
assert.equal(g.response?.kind, 'worthlessKarama');
assert.equal(g.pendingKarama?.use.kind, 'cancel');
if (g.pendingKarama?.use.kind === 'cancel')
assert.deepEqual(g.pendingKarama.use.response, response);
assert.equal(g.discard.filter((c) => c.id === worthless.id).length, 1);
stable(g);
return { f, g, original, worthless, karama };
}
void test('Guild Cunning survives an actual paid BG Worthless cancellation and its printed Karama counter with exact original movement and custody', () => {
for (const counter of [false, true]) {
const { f, original, worthless, karama, g: pending } = nested();
let g = counter
? applyAction(nexusGuildCunningReload(pending), f.observer, {
type: 'card',
card: karama.id,
mode: 'cancel',
})
: nexusGuildCunningAllow(nexusGuildCunningReload(pending));
assert.equal(
g.nexusGuildCunningHistory!.at(-1)!.stage,
counter ? 'secondShipment' : 'canceled',
);
assert.equal(g.nexusCards!.cards!.hands[f.owner], null);
assert.equal(
g.nexusCards!.cards!.discard.filter((c) => c === 'guild').length,
1,
);
assert.deepEqual(g.players[0].forces, original.players[0].forces);
assert.equal(g.players[0].spice, original.players[0].spice);
assert.equal(g.players[0].moved, 1);
assert.equal(g.players[0].shipped, true);
assert.equal(g.discard.filter((c) => c.id === worthless.id).length, 1);
assert.equal(
g.discard.filter((c) => c.id === karama.id).length,
counter ? 1 : 0,
);
assert.equal(g.pendingKarama, null);
assert.equal(g.active === f.owner, counter);
assert.equal(g.movementRemaining!.includes(f.owner), counter);
stable(g);
if (counter) {
g = applyAction(g, f.owner, {
type: 'ship',
territory: 'carthag',
sector: 11,
amount: 2,
});
if (g.decision?.kind === 'guildShipment')
g = applyAction(g, f.owner, { type: 'decision', allow: true });
if (g.decision?.kind === 'advisor')
g = applyAction(g, f.target, { type: 'decision', accept: false });
g = nexusGuildCunningAllow(g);
assert.equal(g.players[0].forces['carthag:11'], 2);
assert.equal(g.players[0].spice, original.players[0].spice - 1);
assert.equal(g.nexusGuildCunningHistory!.at(-1)!.stage, 'extraMove');
stable(g);
}
}
});
void test('nested Guild cancellation rejects altered parent, last marker and saved native source without mutation', () => {
const { f, g: pending, karama } = nested();
const changes: ((g: Game) => void)[] = [
(g) => {
g.nexusGuildCunningHistory![0].parent = '[]';
},
(g) => {
g.nexusGuildCunningLast!.stage = 'secondShipment';
},
(g) => {
delete g.nexusGuildCunningLast;
},
(g) => {
delete g.nexusGuildCunningHistory;
},
(g) => {
if (g.pendingKarama!.use.kind === 'cancel')
g.pendingKarama!.use.response.intent = 'old';
},
(g) => {
if (g.pendingKarama!.use.kind === 'cancel')
g.pendingKarama!.use.response.owner = f.observer;
},
(g) => {
g.players[0].moved = 0;
},
(g) => {
g.movementRemaining = g.movementRemaining!.filter((id) => id !== f.owner);
},
];
for (const change of changes) {
const bad = nexusGuildCunningReload(pending);
change(bad);
const original = nexusGuildCunningReload(bad);
for (const p of bad.players) assert.throws(() => viewGame(bad, p.id));
assert.throws(() => normalizeAutomaticGame(bad));
assert.throws(() =>
applyAction(bad, f.observer, {
type: 'card',
card: karama.id,
mode: 'cancel',
}),
);
assert.deepEqual(bad, original);
}
});