122 lines
6.2 KiB
TypeScript
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);
|
|
});
|