Files
Dune/tests/moritani-replacement-exchange.test.ts

119 lines
6.6 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { leaders } from '../game/cards';
import { viewGame, type Action } from '../game/engine';
import {
assertMixedE3MoritaniPhysical, assertMixedE3MoritaniRejects, mixedE3MoritaniPlayer,
quoteMixedE3MoritaniBattle,
} from './fixture-mixed-e3-moritani-nexus';
import {
createMoritaniReplacementExchangeFixture, createMoritaniReplacementRedrawProgramme,
finishMoritaniReplacementExchange, moritaniReplacementDrawStep,
} from './fixture-moritani-replacement-exchange';
void test('an original Harkonnen Secret Ally return gives the historical Mentat replacement back to the physical deck without disturbing its public receipt', () => {
const f = createMoritaniReplacementExchangeFixture();
assertMixedE3MoritaniPhysical(f.ready);
assert.deepEqual(mixedE3MoritaniPlayer(f.ready, f.moritani).traitors, [f.historicalReplacement]);
const own = viewGame(f.ready, f.moritani);
assert.equal(own.nexusCards!.card, 'harkonnen');
assert.deepEqual(own.nexusTraitors!.pending, null);
assert.equal(own.nexusTraitors!.offer!.draw, 2);
assert.equal(own.nexusTraitors!.offer!.blocked, null);
const result = finishMoritaniReplacementExchange(f);
const exchange = result.exchanged.nexusTraitorExchanges!.at(-1)!;
assert.equal(exchange.stage, 'complete');
assert.deepEqual(exchange.drawn, [...f.drawn]);
assert.deepEqual([...exchange.returned].sort(), [...result.returnCards].sort());
assert.ok(result.returnCards.includes(f.historicalReplacement),
'The owner really gave the historical replacement back.');
assert.deepEqual(exchange.before, [f.historicalReplacement]);
assert.equal(result.exchanged.traitorReserve![0], f.historicalReplacement,
'The returned historical replacement is a physical reserve card again.');
assert.deepEqual(mixedE3MoritaniPlayer(result.exchanged, f.moritani).traitors, [f.guildTraitor],
'Only the real newly drawn Traitor Card stays in the owner hand.');
assert.equal(result.exchanged.traitorReserve!.includes(f.retiredVictim), false);
for (const player of result.exchanged.players)
assert.equal(player.traitors.includes(f.retiredVictim), false);
const receipt = result.exchanged.moritaniAssassinate!.opportunities.at(-1)!;
assert.equal(receipt.stage, 'replaced');
assert.equal(receipt.replacement, f.historicalReplacement);
assert.deepEqual({ event: receipt.event, card: receipt.card, replacement: receipt.replacement,
signature: receipt.signature }, f.history,
'The recorded public victim, its private replacement and the receipt signature must not be rewritten.');
assert.equal(result.exchanged.turn, f.ready.turn);
assert.equal(result.exchanged.phase, 8);
assert.equal(result.exchanged.nexusTraitorPending ?? null, null);
assert.deepEqual(result.exchanged.decision, f.ready.decision,
'The owner keeps its suspended original Mentat placement after returning physical cards.');
assert.equal(result.exchanged.nexusCards!.cards!.discard.filter(card => card === 'harkonnen').length, 1);
assertMixedE3MoritaniPhysical(result.exchanged);
});
void test('the physical return stays owner-only and cannot be replayed against the real custody', () => {
const f = createMoritaniReplacementExchangeFixture();
const guild = f.seats.guild!;
const rival = viewGame(f.ready, guild).nexusTraitors!;
assert.equal(rival.offer, null);
assert.equal(rival.pending, null);
assertMixedE3MoritaniRejects(f.ready, guild, f.draw);
const drawn = moritaniReplacementDrawStep(f);
const pending = viewGame(drawn.after, f.moritani).nexusTraitors!.pending!;
assert.deepEqual(pending.choices.map(choice => choice.id), [f.historicalReplacement, ...f.drawn]);
assert.deepEqual(mixedE3MoritaniPlayer(drawn.after, f.moritani).traitors,
[f.historicalReplacement, ...f.drawn]);
const rivalPending = viewGame(drawn.after, guild).nexusTraitors!.pending!;
assert.equal(rivalPending.event, pending.event);
assert.deepEqual(rivalPending.choices, []);
const giveBack: Action = { type: 'nexusTraitorReturn', event: pending.event,
cards: [f.historicalReplacement, f.guildTraitor] };
assertMixedE3MoritaniRejects(drawn.after, guild, giveBack);
assertMixedE3MoritaniRejects(drawn.after, f.moritani,
{ type: 'nexusTraitorReturn', event: pending.event,
cards: [f.historicalReplacement, f.historicalReplacement] });
assertMixedE3MoritaniRejects(drawn.after, f.moritani, f.draw);
});
void test('a later original battle loss lets the next Mentat draw the same physical card again while the older Nexus history stays unchanged', () => {
const programme = createMoritaniReplacementRedrawProgramme();
const f = programme.fixture;
const guild = f.seats.guild!;
assertMixedE3MoritaniPhysical(programme.exchanged);
const historical = structuredClone(programme.exchanged.nexusTraitorExchanges);
assert.equal(quoteMixedE3MoritaniBattle(programme.revealed).winner, guild,
'The real second loss must be a surviving Guild win.');
const receipts = programme.redrawn.moritaniAssassinate!.opportunities;
assert.equal(receipts.length, 2);
const [first, second] = receipts;
assert.equal(first.stage, 'replaced');
assert.equal(second.stage, 'replaced');
assert.equal(second.turn, 3);
assert.equal(second.card, f.guildTraitor, 'The second public marker retires the held Guild Traitor.');
assert.equal(second.replacement, first.replacement,
'The later original Mentat replacement may be the very card the older Nexus history returned.');
assert.equal(second.replacement, f.historicalReplacement);
assert.equal(second.bounty, leaders('guild').find(leader => leader.id === f.guildTraitor)!.strength);
assert.equal(mixedE3MoritaniPlayer(programme.redrawn, guild).leaders
.find(leader => leader.id === f.guildTraitor)!.dead, true);
assert.deepEqual(mixedE3MoritaniPlayer(programme.redrawn, f.moritani).traitors, [f.historicalReplacement],
'The same physical card is really held again.');
assert.equal(programme.redrawn.traitorReserve!.includes(f.historicalReplacement), false);
assert.equal(programme.redrawn.traitorReserve!.includes(f.guildTraitor), false);
assert.equal(programme.redrawn.traitorReserve!.includes(f.retiredVictim), false);
for (const player of programme.redrawn.players)
for (const retired of [f.retiredVictim, f.guildTraitor])
assert.equal(player.traitors.includes(retired), false);
assert.deepEqual(programme.redrawn.nexusTraitorExchanges, historical,
'A later public marker must not rewrite or reinterpret the earlier Nexus exchange history.');
assert.deepEqual({ event: first.event, card: first.card, replacement: first.replacement,
signature: first.signature }, f.history);
assertMixedE3MoritaniPhysical(programme.redrawn);
});