Files
Dune/tests/emperor-special-revival-exemption-runtime.test.ts

122 lines
6.2 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import {
actAdvancedRevivalOrder as act,
advanceAdvancedRevivalOrder as advance,
advancedRevivalOrderClean as clean,
advancedRevivalOrderInventory as inventory,
advancedRevivalOrderPlayer as player,
type AdvancedRevivalOrderStep,
} from './fixture-advanced-revival-order';
/** A genuine Advanced lobby, native setup, paid shipment and Storm deaths.
* Only original unopened starting-deal and Spice card positions are selected;
* no played hand, force, Tank, revival counter or clock is assigned. */
function source() {
let game = createGame('EMPERORSPECIAL', newPlayer('emperor', 'Emperor', 'emperor'), true, ['ix']);
joinGame(game, newPlayer('atreides', 'Atreides', 'atreides'));
joinGame(game, newPlayer('tleilaxu', 'Tleilaxu', 'tleilaxu'));
const actions: AdvancedRevivalOrderStep[] = [];
const send = (actor: string, action: Action) => { game = act(game, actor, action, actions); };
const reach = (until: (state: Game) => boolean) => { game = advance(game, until, actions); };
for (const seat of game.players) send(seat.id, { type: 'ready' });
send('emperor', { type: 'start', advancedPreview: true });
assert.equal(game.status, 'setup');
assert.ok(game.players.every(seat => seat.hand.length === 0));
const originalCards = inventory(game);
const index = game.deck.findIndex(card => card.effect === 'karama');
assert.ok(index >= 0);
[game.deck[0], game.deck[index]] = [game.deck[index], game.deck[0]];
const karama = game.deck[0].id;
reach(state => state.status === 'playing');
assert.ok(player(game, 'emperor').hand.some(card => card.id === karama));
for (let position = 0; position < 4; position++) {
const from = game.spiceDeck.findIndex((card, index) => index >= position && 'territory' in card && !card.discovery);
assert.ok(from >= position);
game.spiceDeck.splice(position, 0, game.spiceDeck.splice(from, 1)[0]);
}
reach(state => state.turn === 1 && state.phase === 5 && state.active === 'emperor' && clean(state));
const beforeShipment = structuredClone(game);
send('emperor', { type: 'ship', territory: 'hagga_basin', sector: 13, amount: 3, elite: 3, allyPayment: 0 });
reach(clean);
assert.equal(player(game, 'emperor').elites?.forces['hagga_basin:13'], 3);
assert.equal(player(beforeShipment, 'emperor').spice - player(game, 'emperor').spice, 6);
reach(state => state.turn === 2 && state.phase === 1);
assert.equal(player(game, 'emperor').tanks, 3);
assert.equal(player(game, 'emperor').elites?.tanks, 3);
assert.equal(player(game, 'emperor').elites?.revived, 0);
reach(state => state.turn === 2 && state.phase === 4 && clean(state) &&
(!state.revivalOrder || state.revivalOrder.remaining[0] === 'emperor'));
assert.deepEqual(inventory(game), originalCards);
return { game, karama, originalCards, actions };
}
function special(game: Game, card: string, amount: number, elite: number): Game {
return act(game, 'emperor', { type: 'card', mode: 'special', card, amount, elite });
}
function ordinary(game: Game): Game {
return advance(act(game, 'emperor', { type: 'revive', amount: 1, elite: 1 }), clean);
}
function reject(game: Game, action: Action) {
const before = structuredClone(game);
assert.throws(() => act(game, 'emperor', action));
assert.deepEqual(game, before);
}
void test('Advanced Emperor may revive two extra Sardaukar after an ordinary elite revival', () => {
const f = source();
let game = ordinary(f.game);
assert.equal(player(game, 'emperor').elites?.revived, 1);
assert.equal(player(game, 'emperor').elites?.tanks, 2);
const before = structuredClone(player(game, 'emperor'));
game = advance(special(game, f.karama, 2, 2), clean);
assert.equal(player(game, 'emperor').elites?.tanks, 0);
assert.equal(player(game, 'emperor').elites?.reserves, before.elites!.reserves + 2);
assert.equal(player(game, 'emperor').elites?.revived, 1);
assert.equal(player(game, 'emperor').revived, before.revived);
assert.equal(player(game, 'emperor').spice, before.spice);
assert.equal(player(game, 'emperor').specialKaramaUsed, true);
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
assert.deepEqual(inventory(game), f.originalCards);
});
void test('Advanced Emperor retains ordinary elite allowance after special two-Sardaukar revival', () => {
const f = source();
let game = advance(special(f.game, f.karama, 2, 2), clean);
assert.equal(player(game, 'emperor').elites?.tanks, 1);
assert.equal(player(game, 'emperor').elites?.revived, 0);
assert.equal(player(game, 'emperor').revived, 0);
game = ordinary(game);
assert.equal(player(game, 'emperor').elites?.tanks, 0);
assert.equal(player(game, 'emperor').elites?.revived, 1);
assert.equal(player(game, 'emperor').revived, 1);
assert.deepEqual(inventory(game), f.originalCards);
});
void test('Advanced Emperor may use all three special slots for Sardaukar without exceeding real Tanks', () => {
const f = source();
reject(f.game, { type: 'card', mode: 'special', card: f.karama, amount: 4, elite: 4 });
const game = advance(special(f.game, f.karama, 3, 3), clean);
assert.equal(player(game, 'emperor').tanks, 0);
assert.equal(player(game, 'emperor').elites?.tanks, 0);
assert.equal(player(game, 'emperor').elites?.reserves, 5);
assert.equal(player(game, 'emperor').elites?.revived, 0);
assert.equal(player(game, 'emperor').revived, 0);
assert.deepEqual(inventory(game), f.originalCards);
});
void test('the minimal bot can use the special revival with three actual Sardaukar', () => {
const f = source(), view = viewGame(f.game, 'emperor');
view.players.find(seat => seat.id === 'emperor')!.bot = 'Easy';
const action = botActions(view).find(candidate => candidate.type === 'card' && candidate.mode === 'special');
assert.ok(action);
assert.equal(action.amount, 3);
assert.equal(action.elite, 3);
const game = advance(act(f.game, 'emperor', action), clean);
assert.equal(player(game, 'emperor').elites?.tanks, 0);
assert.equal(player(game, 'emperor').elites?.revived, 0);
assert.deepEqual(inventory(game), f.originalCards);
});