Files
Dune/tests/classic-independent-nexus-utility.test.ts

95 lines
5.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, type Action, type Game } from '../game/engine';
import { normalRevivalAllowance } from '../game/revival';
import { advanceClassicIndependentNexus } from './fixture-classic-independent-nexus';
import { classicDiscoveryNexusClean } from './fixture-discovery-classic-nexus';
import { createIndependentNexusBankPurchase, createIndependentNexusRecruitsRevival, utilityAction, utilityPlayer } from './fixture-classic-independent-nexus-utility';
function rejected(game: Game, action: Action) {
const before = structuredClone(game);
assert.throws(() => applyAction(game, 'atreides', action));
assert.deepEqual(game, before, 'A rejected consumer action cannot spend cards, counters or money');
}
const quiet = (game: Game) => advanceClassicIndependentNexus(game,
state => state.phase === 4 && classicDiscoveryNexusClean(state));
void test('real Recruits and Tleilaxu draw: fixed paid five precedes remaining ordinary free returns', () => {
const f = createIndependentNexusRecruitsRevival();
const before = utilityPlayer(f.game);
assert.equal(utilityPlayer(f.afterBattle).tanks, 8);
assert.equal(before.leaders.find(disc => disc.id === f.leader)!.dead, true);
let game = utilityAction(f.game, 'atreides', { type: 'card', card: 'ecaz-recruits' });
const rate = normalRevivalAllowance(game, utilityPlayer(game));
assert.equal(rate.freeRate, 4);
assert.equal(rate.limit, 7);
rejected(game, { ...f.action, amount: 6 });
rejected(game, { ...f.action, leader: '' });
game = quiet(utilityAction(game, 'atreides', f.action));
const granted = utilityPlayer(game);
assert.equal(granted.spice, before.spice - 5);
assert.equal(granted.tanks, 3);
assert.equal(granted.reserves, before.reserves + 5);
assert.equal(granted.freeForcesRevived, 0, 'Paid card counters do not consume or refund the native free quota');
assert.equal(granted.leaderRevived, true);
assert.equal(granted.leaders.find(disc => disc.id === f.leader)!.dead, false);
assert.equal(game.discard.filter(card => card.id === 'ecaz-recruits').length, 1);
assert.equal(game.nexusCards!.cards!.discard.filter(face => face === 'tleilaxu').length, 1);
rejected(game, { ...f.action, amount: 0 });
game = quiet(utilityAction(game, 'atreides', { type: 'revive', amount: 2 }));
assert.equal(utilityPlayer(game).revived, 7);
assert.equal(utilityPlayer(game).freeForcesRevived, 2);
assert.equal(utilityPlayer(game).spice, before.spice - 5);
assert.equal(utilityPlayer(game).tanks, 1);
rejected(game, { type: 'revive', amount: 1 });
game = advanceClassicIndependentNexus(game, state => state.phase === 5 && classicDiscoveryNexusClean(state));
assert.equal(game.turn, 2);
assert.equal(utilityPlayer(game).spice, before.spice - 5);
assert.equal(utilityPlayer(game).leaderRevived, true);
});
void test('earlier ordinary paid returns consume Recruits seven ceiling without a Secret Ally refund', () => {
const f = createIndependentNexusRecruitsRevival();
const before = utilityPlayer(f.game);
let game = utilityAction(f.game, 'atreides', { type: 'card', card: 'ecaz-recruits' });
game = quiet(utilityAction(game, 'atreides', { type: 'revive', amount: 5 }));
assert.equal(utilityPlayer(game).spice, before.spice - 2);
assert.equal(utilityPlayer(game).freeForcesRevived, 4);
rejected(game, { ...f.action, amount: 3 });
game = quiet(utilityAction(game, 'atreides', { ...f.action, amount: 2 }));
assert.equal(utilityPlayer(game).spice, before.spice - 4);
assert.equal(utilityPlayer(game).revived, 7);
assert.equal(utilityPlayer(game).freeForcesRevived, 4);
assert.equal(utilityPlayer(game).tanks, 1);
assert.equal(utilityPlayer(game).leaderRevived, true);
rejected(game, { type: 'revive', amount: 1 });
});
void test('real Emperor draw and selected independent bank lot retain Pay-or-Use and one-card continuation', () => {
const f = createIndependentNexusBankPurchase();
const before = utilityPlayer(f.game);
const card = f.game.auction!.cards[f.game.auction!.index];
assert.equal(card.id, 'ecaz-reinforcements');
rejected(f.beforeBid, { type: 'bid', amount: utilityPlayer(f.beforeBid).spice + 1 });
const paid = utilityAction(f.game, 'atreides', { type: 'decision', karama: false });
assert.equal(utilityPlayer(paid).spice, before.spice - 2);
assert.equal(paid.nexusCards!.cards!.hands.atreides, 'emperor');
assert.equal(utilityPlayer(paid).hand.filter(held => held.id === card.id).length, 1);
let used = utilityAction(f.game, 'atreides', f.action);
assert.equal(utilityPlayer(used).spice, before.spice);
assert.equal(utilityPlayer(used).hand.length, before.hand.length + 1);
assert.deepEqual(utilityPlayer(used).hand.find(held => held.id === card.id), card);
assert.equal(used.nexusCards!.cards!.hands.atreides, null);
assert.equal(used.nexusCards!.cards!.discard.filter(face => face === 'emperor').length, 1);
assert.equal(used.nexusEmperorPurchaseHistory![0].price, 2);
assert.equal(used.nexusEmperorPurchaseHistory![0].card, card.id);
assert.equal(used.nexusEmperorPurchaseHistory![0].beforeSpice, before.spice);
assert.equal(used.nexusEmperorPurchaseHistory![0].afterSpice, before.spice);
rejected(used, f.action);
used = advanceClassicIndependentNexus(used, state => state.phase === 4 && classicDiscoveryNexusClean(state));
assert.equal(used.turn, 2);
assert.equal(used.auction, null);
assert.equal(utilityPlayer(used).hand.filter(held => held.id === card.id).length, 1);
assert.equal(utilityPlayer(used).spice, before.spice);
});