105 lines
6.5 KiB
TypeScript
105 lines
6.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
|
import { maxCombatDial } from '../game/combat';
|
|
import { REINFORCEMENTS_CARD } from '../game/reinforcements';
|
|
import { HARASS_WITHDRAW_CARD } from '../game/harass-withdraw';
|
|
import {
|
|
createClassicIndependentNexusBattleFixture,
|
|
revealIndependentNexusBattle, finishIndependentNexusBattle,
|
|
independentNexusBattlePlayer as player,
|
|
} from './fixture-classic-independent-nexus-battle';
|
|
|
|
function rejected(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game, actor, action));
|
|
assert.deepEqual(game, before, 'A rejected native plan or stale Nexus use cannot change physical custody');
|
|
}
|
|
|
|
void test('native Advanced Emperor Cunning changes five shipped ordinary counters, while independent reserve fees and enemy withdrawal retain their physical sources', () => {
|
|
const f = createClassicIndependentNexusBattleFixture();
|
|
const before = player(f.game, f.owner), enemy = player(f.game, f.opponent);
|
|
assert.equal(player(f.afterShipment, f.owner).reserves, player(f.beforeShipment, f.owner).reserves - 5);
|
|
assert.equal(player(f.afterShipment, f.owner).spice, player(f.beforeShipment, f.owner).spice - 5);
|
|
assert.equal(f.afterDraw.nexusCards!.cards!.hands[f.owner], 'emperor');
|
|
const revealed = revealIndependentNexusBattle(f, { cunning: true });
|
|
assert.equal(revealed.nexusCards!.cards!.hands[f.owner], null);
|
|
rejected(revealed, f.owner, { type: 'nexusSardaukar', event: revealed.nexusSardaukarLast!.event });
|
|
const forces = viewGame(revealed, f.owner).battle!.ownForces!;
|
|
assert.equal(forces.normal, 5);
|
|
assert.equal(forces.elite, 0);
|
|
assert.equal(forces.temporaryElite, 5);
|
|
assert.equal(forces.eliteStrength, 2);
|
|
assert.equal(forces.freeSupport, false);
|
|
assert.equal(maxCombatDial(forces), 10);
|
|
assert.equal(revealed.battle!.plans[f.owner].support, 2);
|
|
assert.deepEqual(player(revealed, f.owner).elites, before.elites,
|
|
'Cunning never replaces the native elite reserve stock or creates elite counters');
|
|
const cleanup = finishIndependentNexusBattle(revealed);
|
|
const own = player(cleanup, f.owner), other = player(cleanup, f.opponent);
|
|
assert.equal(cleanup.lastBattleContext!.winner, f.owner);
|
|
assert.equal(own.reserves, before.reserves - 3, 'Reinforcements consumes exactly three ordinary reserve counters');
|
|
assert.equal(own.tanks, before.tanks + 5, 'Three reserve fees and two committed temporary-Sardaukar counters are distinct ordinary losses');
|
|
assert.equal(own.forces[f.key], 3);
|
|
assert.equal(own.spice, before.spice - 2 + 2, 'Support2 is paid before native Carthag Collection2');
|
|
assert.deepEqual(own.elites, before.elites);
|
|
assert.equal(other.reserves, enemy.reserves + 9, 'Harass returns the nine actual undialed enemy board counters');
|
|
assert.equal(other.tanks, enemy.tanks + 1);
|
|
assert.equal(other.forces[f.key] ?? 0, 0);
|
|
assert.equal(other.spice, enemy.spice - 1);
|
|
assert.equal(cleanup.nexusSardaukarHistory!.length, 1);
|
|
assert.equal(cleanup.nexusSardaukarHistory![0].casualties!.outcome, 'complete');
|
|
assert.equal(cleanup.nexusCards!.cards!.discard.filter(card => card === 'emperor').length, 1);
|
|
assert.equal(cleanup.discard.filter(card => card.id === REINFORCEMENTS_CARD).length, 1);
|
|
assert.equal(cleanup.discard.filter(card => card.id === HARASS_WITHDRAW_CARD).length, 1);
|
|
for (const seat of cleanup.players)
|
|
assert.equal(seat.reserves + seat.tanks + Object.values(seat.forces).reduce((sum, count) => sum + count, 0), 20);
|
|
assert.equal(cleanup.turn, 2);
|
|
assert.equal(cleanup.phase, 7);
|
|
assert.equal(viewGame(cleanup, f.owner).nexusSardaukar!.active, false);
|
|
});
|
|
|
|
void test('a genuinely dealt opposing Karama spends Cunning without converting physical fighters, and invalid over-dial plans remain immutable', () => {
|
|
const f = createClassicIndependentNexusBattleFixture();
|
|
const before = player(f.game, f.owner);
|
|
rejected(f.game, f.owner, { type: 'battlePlan', leader: f.ownerLeader,
|
|
dial: 11, support: 0, weapon: REINFORCEMENTS_CARD, defense: null });
|
|
const revealed = revealIndependentNexusBattle(f, { cunning: true, canceled: true });
|
|
const forces = viewGame(revealed, f.owner).battle!.ownForces!;
|
|
assert.equal(forces.temporaryElite ?? 0, 0);
|
|
assert.equal(maxCombatDial(forces), 5);
|
|
assert.equal(revealed.battle!.plans[f.owner].support, 4);
|
|
assert.equal(revealed.nexusSardaukarLast!.stage, 'canceled');
|
|
const cleanup = finishIndependentNexusBattle(revealed);
|
|
const own = player(cleanup, f.owner);
|
|
assert.equal(cleanup.lastBattleContext!.winner, f.owner);
|
|
assert.equal(own.forces[f.key], 1, 'The same dial now loses four ordinary board counters, not two temporary Sardaukar');
|
|
assert.equal(own.tanks, before.tanks + 7);
|
|
assert.equal(own.reserves, before.reserves - 3, 'Cancellation cannot refund or duplicate the separate Reinforcements cost');
|
|
assert.equal(own.spice, before.spice - 4 + 2, 'Canceled conversion pays support4 before native Carthag Collection2');
|
|
assert.deepEqual(own.elites, before.elites);
|
|
assert.equal(player(cleanup, f.opponent).reserves, player(f.game, f.opponent).reserves + 9);
|
|
assert.equal(cleanup.discard.filter(card => card.id === f.counter).length, 1);
|
|
assert.equal(cleanup.nexusCards!.cards!.discard.filter(card => card === 'emperor').length, 1);
|
|
assert.equal(cleanup.nexusCards!.cards!.hands[f.owner], null);
|
|
assert.equal(cleanup.phase, 7);
|
|
});
|
|
|
|
void test('Basic independent battle preserves ordinary dial losses and free support rather than borrowing the Advanced Cunning advantage', () => {
|
|
const f = createClassicIndependentNexusBattleFixture(false);
|
|
const before = player(f.game, f.owner);
|
|
const offer = viewGame(f.game, f.owner).nexusSardaukar!.offer!;
|
|
rejected(f.game, f.owner, { type: 'nexusSardaukar', event: offer.event });
|
|
const cleanup = finishIndependentNexusBattle(revealIndependentNexusBattle(f));
|
|
const own = player(cleanup, f.owner);
|
|
assert.equal(cleanup.lastBattleContext!.winner, f.owner);
|
|
assert.equal(own.spice, before.spice);
|
|
assert.equal(own.tanks, before.tanks + 7);
|
|
assert.equal(own.forces[f.key], 1);
|
|
assert.equal(own.reserves, before.reserves - 3);
|
|
assert.equal(player(cleanup, f.opponent).reserves, player(f.game, f.opponent).reserves + 9);
|
|
assert.equal(player(cleanup, f.opponent).tanks, player(f.game, f.opponent).tanks + 1);
|
|
assert.equal(cleanup.nexusCards!.cards!.hands[f.owner], 'emperor', 'The blocked Advanced ability cannot spend a genuinely drawn Basic Nexus card');
|
|
assert.equal(cleanup.phase, 7);
|
|
});
|