Files
Dune/tests/native-banker-income.test.ts

145 lines
8.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame } from '../game/engine';
import { forceRevivalQuote } from '../game/revival';
import { advanceSpiceBankerIncomeToMentat, nextSpiceBankerIncomeNativeStep } from './fixture-spice-banker-income';
import {
advanceNativeBankerIncomeToPhase, createNativeBankerIncomeFixture,
payNativeBankerIncomeFixture, type NativeBankerIncomeOptions,
} from './fixture-native-banker-income';
const nativeCases: NativeBankerIncomeOptions[] = [
{ family: 'ixians' },
{ family: 'ixians', withTleilaxu: true },
{ family: 'tleilaxu' },
{ family: 'choam' },
{ family: 'moritani' },
];
for (const options of nativeCases) {
void test(`Basic ${options.family}${options.withTleilaxu ? '+Tleilaxu' : ''}: original bank auction buys its card once, defers Banker, and collects only at native Mentat`, () => {
const fixture = createNativeBankerIncomeFixture(options);
const buyer = fixture.game.players.find(p => p.id === fixture.payer)!;
const owner = fixture.game.players.find(p => p.id === fixture.owner)!;
const purchased = fixture.game.auction!.cards[fixture.game.auction!.index];
assert.notEqual(buyer.id, owner.id);
const paid = payNativeBankerIncomeFixture(fixture);
assert.equal(paid.players.find(p => p.id === buyer.id)!.spice, buyer.spice - 4);
assert.equal(paid.players.find(p => p.id === buyer.id)!.hand.filter(c => c.id === purchased.id).length, 1);
assert.equal(paid.players.find(p => p.id === owner.id)!.spice, owner.spice);
assert.deepEqual(viewGame(paid, owner.id).spiceBankerIncome!.deferred, [{ owner: owner.id, amount: 1 }]);
assert.deepEqual(paid.spiceBankerIncome!.sources.find(row => row.grant)!.source.bankLegs,
[{ payer: buyer.id, amount: 4 }]);
// Ordinary auction/Shipment/Battle/Collection suffixes continue naturally;
// Collection's own native income is already in this baseline wallet.
const collection = advanceNativeBankerIncomeToPhase(paid, 7);
const balance = collection.players.find(p => p.id === owner.id)!.spice;
assert.deepEqual(viewGame(collection, owner.id).spiceBankerIncome!.deferred, [{ owner: owner.id, amount: 1 }]);
const mentat = advanceSpiceBankerIncomeToMentat(collection);
assert.equal(mentat.players.find(p => p.id === owner.id)!.spice, balance + 1);
assert.deepEqual(viewGame(mentat, owner.id).spiceBankerIncome!.deferred, []);
assert.equal(mentat.players.find(p => p.id === buyer.id)!.hand.filter(c => c.id === purchased.id).length, 1);
});
}
void test('Basic native Tleilaxu Banker does not count a real auction paid to Emperor as a bank payment', () => {
const fixture = createNativeBankerIncomeFixture({ family: 'tleilaxu', payerFaction: 'atreides' });
const payer = fixture.game.players.find(p => p.id === fixture.payer)!;
const emperor = fixture.game.players.find(p => p.faction === 'emperor')!;
const card = fixture.game.auction!.cards[fixture.game.auction!.index];
const paid = payNativeBankerIncomeFixture(fixture);
assert.equal(paid.players.find(p => p.id === payer.id)!.spice, payer.spice - 4);
assert.equal(paid.players.find(p => p.id === emperor.id)!.spice, emperor.spice + 4);
assert.equal(paid.players.find(p => p.id === payer.id)!.hand.filter(c => c.id === card.id).length, 1);
assert.deepEqual(viewGame(paid, fixture.owner).spiceBankerIncome!.deferred, []);
});
for (const amount of [1, 3]) {
void test(`Basic native Tleilaxu revival recipient: ${amount === 1 ? 'zero payer debit/free reward' : 'four-spice player payment'} returns original forces without Banker income`, () => {
const fixture = createNativeBankerIncomeFixture({ family: 'tleilaxu', kind: 'force-revival', amount });
const payer = fixture.game.players.find(p => p.id === fixture.payer)!;
const recipient = fixture.game.players.find(p => p.faction === 'tleilaxu')!;
const quote = forceRevivalQuote(fixture.game, payer, amount);
assert.equal(quote.free, 1);
assert.equal(quote.cost, amount === 1 ? 0 : 4);
const paid = payNativeBankerIncomeFixture(fixture);
const revived = paid.players.find(p => p.id === payer.id)!;
assert.equal(revived.spice, payer.spice - quote.cost);
assert.equal(revived.reserves, payer.reserves + amount);
assert.equal(revived.tanks, payer.tanks - amount);
assert.equal(revived.revived, payer.revived + amount);
// The printed free-revival bonus comes from the Bank, not from this payer.
assert.equal(paid.players.find(p => p.id === recipient.id)!.spice, recipient.spice + quote.cost + 1);
assert.deepEqual(viewGame(paid, fixture.owner).spiceBankerIncome!.deferred, []);
assert.equal(paid.spiceBankerIncome!.sources.some(row => row.grant), false);
});
}
void test('Basic native Tleilaxu own paid revival really pays the Bank: another native Banker earns one, not the free-income reward', () => {
const fixture = createNativeBankerIncomeFixture({
family: 'tleilaxu', bankerFaction: 'atreides', payerFaction: 'tleilaxu',
kind: 'force-revival', amount: 6,
});
const payer = fixture.game.players.find(p => p.id === fixture.payer)!;
const owner = fixture.game.players.find(p => p.id === fixture.owner)!;
const quote = forceRevivalQuote(fixture.game, payer, 6);
assert.equal(quote.free, 2);
assert.equal(quote.cost, 4);
const paid = payNativeBankerIncomeFixture(fixture);
const revived = paid.players.find(p => p.id === payer.id)!;
assert.equal(revived.reserves, payer.reserves + 6);
assert.equal(revived.tanks, payer.tanks - 6);
assert.equal(revived.spice, payer.spice - 4 + 1);
assert.equal(paid.players.find(p => p.id === owner.id)!.spice, owner.spice);
assert.deepEqual(viewGame(paid, owner.id).spiceBankerIncome!.deferred, [{ owner: owner.id, amount: 1 }]);
assert.deepEqual(paid.spiceBankerIncome!.sources.find(row => row.grant)!.source.bankLegs,
[{ payer: payer.id, amount: 4 }]);
});
for (const cost of [0, 3, 4]) {
void test(`canceled native Tleilaxu income: actual payer debit ${cost}, not the extra free reward, controls the Banker threshold`, () => {
const options: NativeBankerIncomeOptions = cost === 3
? { family: 'ixians', withTleilaxu: true, bankerFaction: 'atreides', payerFaction: 'ixians', amount: 2, elite: 2 }
: { family: 'tleilaxu', bankerFaction: 'atreides', payerFaction: 'emperor', amount: cost === 0 ? 1 : 3 };
const fixture = createNativeBankerIncomeFixture({ ...options, kind: 'force-revival', stageIncomeCounter: true });
const payer = fixture.game.players.find(p => p.id === fixture.payer)!;
const recipient = fixture.game.players.find(p => p.faction === 'tleilaxu')!;
const owner = fixture.game.players.find(p => p.id === fixture.owner)!;
const counter = owner.hand.find(card => card.effect === 'karama')!;
assert.equal(fixture.amount, cost);
let game = applyAction(fixture.game, fixture.actor, fixture.paymentAction);
for (let i = 0; game.response?.kind !== 'revivalIncome' && i < 100; i++) {
const next = nextSpiceBankerIncomeNativeStep(game);
assert.ok(next);
game = applyAction(game, next.actor, next.action);
}
assert.ok(game.response?.kind === 'revivalIncome');
assert.equal(game.response.amount, cost + 1);
assert.equal(game.players.find(p => p.id === payer.id)!.spice, payer.spice - cost);
assert.equal(game.players.find(p => p.id === recipient.id)!.spice, recipient.spice);
assert.deepEqual(viewGame(game, owner.id).spiceBankerIncome!.deferred, []);
game = applyAction(game, owner.id, { type: 'card', card: counter.id, mode: 'cancel' });
for (let i = 0; (game.response || game.decision) && i < 100; i++) {
const next = nextSpiceBankerIncomeNativeStep(game);
assert.ok(next);
game = applyAction(game, next.actor, next.action);
}
assert.ok(!game.response && !game.decision && !game.pendingRevival);
assert.equal(game.players.find(p => p.id === payer.id)!.spice, payer.spice - cost);
assert.equal(game.players.find(p => p.id === recipient.id)!.spice, recipient.spice);
assert.equal(game.players.find(p => p.id === owner.id)!.spice, owner.spice);
assert.equal(game.players.find(p => p.id === payer.id)!.tanks, payer.tanks - options.amount!);
if (cost === 3) {
assert.equal(game.players.find(p => p.id === payer.id)!.elites!.tanks, payer.elites!.tanks - 2);
assert.equal(game.players.find(p => p.id === payer.id)!.elites!.reserves, payer.elites!.reserves + 2);
}
assert.equal(game.discard.filter(card => card.id === counter.id).length, 1);
assert.deepEqual(viewGame(game, owner.id).spiceBankerIncome!.deferred,
cost === 4 ? [{ owner: owner.id, amount: 1 }] : []);
assert.equal(game.spiceBankerIncome!.sources.some(row => row.grant), cost === 4);
if (cost > 0) assert.deepEqual(game.spiceBankerIncome!.sources.at(-1)!.source.bankLegs,
[{ payer: payer.id, amount: cost }]);
});
}