Files
Dune/tests/ix-starting-pool-runtime.test.ts

154 lines
8.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 { botActions } from '../game/bots';
import type { Difficulty } from '../game/bot-profiles';
import {
actIxStartingPool as act, createIxStartingPoolFixture as fixture,
createLegacyIxStartingPoolSupplement as legacy, finishIxStartingPool as finish,
ixStartingPoolChoice as choice, ixStartingPoolInventory as inventory,
ixStartingPoolPlayer as p, ixStartingPoolReload as reload, type IxStartingPoolStep,
} from './fixture-ix-starting-pool';
function reject(game: Game, actor: string, action: Action) {
const before = reload(game);
assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
for (const advanced of [false, true]) for (const skills of [false, true]) {
void test(`genuine ${advanced ? 'Advanced' : 'Basic'} Ix starting pool ${skills ? 'with all14' : 'without Skills'} preserves original stock and native setup order`, () => {
const f = fixture({ advanced, skills });
assert.equal(f.sourceCounts.ready, f.readyActions.length);
assert.equal(f.sourceCounts.beforeDraft, f.setupActions.length);
assert.equal(f.sourceCounts.source, f.actions.length);
assert.equal(f.pool.length, f.game.players.length + (advanced ? 1 : 0));
assert.equal(f.game.players.every(player => player.hand.length === 0), true);
assert.deepEqual(viewGame(f.game, 'ixians').ixTechnology?.setup?.map(card => card.id), f.pool.map(card => card.id));
assert.deepEqual(inventory(f.game).cards, f.stock.cards);
if (skills) {
assert.equal(Object.keys(f.game.leaderSkills!.offers).length, 0);
assert.equal(f.game.leaderSkills!.assignments.length, 0);
assert.equal(f.actions.some(step => step.action.type === 'leaderSkill' || step.action.type === 'traitor'), false);
} else assert.ok(f.actions.some(step => step.action.type === 'traitor'));
});
}
void test('fresh Advanced Ix may choose the actual fourth card and Harkonnen receives two from that same inspected remainder', () => {
for (const skills of [false, true]) {
const f = fixture({ advanced: true, skills }), beforeTop = f.game.deck[0].id;
assert.equal(f.pool[3].id, f.extra);
const game = finish(act(reload(f.game), 'ixians', choice(f.game, f.extra)));
assert.deepEqual(p(game, 'ixians').hand.map(card => card.id), [f.extra]);
const remainder = f.pool.filter(card => card.id !== f.extra).map(card => card.id);
assert.equal(p(game, 'harkonnen').hand.length, 2);
assert.ok(p(game, 'harkonnen').hand.every(card => remainder.includes(card.id)));
assert.deepEqual([...p(game, 'harkonnen').hand, ...p(game, 'atreides').hand].map(card => card.id).sort(), remainder.sort());
assert.equal(game.deck[0].id, beforeTop);
assert.equal(game.deck[0].id, f.nextAfterFour, 'Fresh Advanced consumes no extra undealt card after the pool is dealt');
assert.deepEqual(inventory(game).cards, f.stock.cards);
assert.deepEqual(inventory(game).skills, f.stock.skills);
}
});
void test('Basic keeps the fourth card outside Ix inspection and actually deals it as the separate Harkonnen top-deck bonus', () => {
for (const skills of [false, true]) {
const f = fixture({ advanced: false, skills });
assert.equal(f.game.deck[0].id, f.extra);
assert.equal(f.pool.some(card => card.id === f.extra), false);
reject(f.game, 'ixians', { type: 'decision', card: f.extra });
const selected = f.pool[0].id;
const game = finish(act(reload(f.game), 'ixians', choice(f.game, selected)));
assert.deepEqual(p(game, 'ixians').hand.map(card => card.id), [selected]);
assert.equal(p(game, 'harkonnen').hand.length, 2);
assert.ok(p(game, 'harkonnen').hand.some(card => card.id === f.extra));
assert.equal(p(game, 'harkonnen').hand.filter(card => f.pool.some(source => source.id === card.id)).length, 1);
assert.equal(game.deck[0].id, f.nextAfterFour);
assert.deepEqual(inventory(game).cards, f.stock.cards);
}
});
void test('without a Harkonnen seat both Basic and Advanced retain the native one-card-per-faction draft', () => {
for (const advanced of [false, true]) for (const skills of [false, true]) {
const f = fixture({ advanced, skills, harkonnen: false });
assert.equal(f.pool.length, f.game.players.length);
const before = f.game.deck[0].id;
const game = finish(act(reload(f.game), 'ixians', choice(f.game, f.pool[0].id)));
assert.ok(game.players.every(player => player.hand.length === 1));
assert.equal(game.deck[0].id, before);
assert.deepEqual(inventory(game).cards, f.stock.cards);
}
});
void test('native Ix choice precedes all real skill offers assignments and Traitor selections in both modes', () => {
for (const advanced of [false, true]) {
const f = fixture({ advanced, skills: true }), actions: IxStartingPoolStep[] = [];
const selected = f.pool.at(-1)!.id;
let game = act(reload(f.game), 'ixians', choice(f.game, selected), actions);
assert.equal(game.setupStage, 'leaderSkills');
assert.equal(Object.keys(game.leaderSkills!.offers).length, game.players.length);
assert.equal(game.leaderSkills!.assignments.length, 0);
assert.equal(p(game, 'harkonnen').hand.length, 2);
game = finish(game, actions);
assert.equal(actions[0].action.card, selected);
const firstSkill = actions.findIndex(step => step.action.type === 'leaderSkill');
const firstTraitor = actions.findIndex(step => step.action.type === 'traitor');
assert.ok(firstSkill > 0 && firstTraitor > firstSkill);
assert.equal(game.leaderSkills!.assignments.length, game.players.length);
assert.deepEqual(inventory(game).cards, f.stock.cards);
assert.deepEqual(inventory(game).skills, f.stock.skills);
}
});
void test('native draft rejects another actor foreign card and replay after its actual one choice', () => {
const f = fixture({ advanced: true, skills: true }), packet = choice(f.game, f.extra);
reject(f.game, 'harkonnen', packet);
reject(f.game, 'ixians', { type: 'decision', card: f.game.deck[0].id });
const game = act(reload(f.game), 'ixians', packet);
reject(game, 'ixians', packet);
assert.equal(game.ixSetupCards, null);
assert.equal(p(game, 'ixians').hand.filter(card => card.id === f.extra).length, 1);
assert.deepEqual(inventory(game).cards, f.stock.cards);
});
void test('controlled conserved old Advanced N-card draft completes its old separate top-deck Harkonnen bonus', () => {
for (const skills of [false, true]) {
const f = legacy(skills);
assert.match(f.sourceDisclosure, /Controlled conserved old-count unit supplement/);
assert.equal(f.pool.length, f.game.players.length);
assert.equal(f.game.deck[0].id, f.extra);
const game = finish(act(reload(f.game), 'ixians', choice(f.game, f.pool[0].id)));
assert.equal(p(game, 'harkonnen').hand.length, 2);
assert.ok(p(game, 'harkonnen').hand.some(card => card.id === f.extra));
assert.equal(game.deck[0].id, f.nextAfterFour);
assert.deepEqual(inventory(game).cards, f.stock.cards);
assert.deepEqual(inventory(game).skills, f.stock.skills);
}
});
void test('all four minimal profiles legally choose from the fresh Advanced pool and finish original all14 setup', () => {
const f = fixture({ advanced: true, skills: true });
for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'] satisfies Difficulty[]) {
let game = reload(f.game), accepted = 0;
const own = viewGame(game, 'ixians'); own.players.find(player => player.id === 'ixians')!.bot = difficulty;
const proposal = botActions(own).find(action => action.type === 'decision' && f.pool.some(card => card.id === action.card));
assert.ok(proposal, `${difficulty} chooses one actual inspected card`);
game = act(game, 'ixians', proposal); accepted++;
for (let count = 0; game.status === 'setup' && count < 100; count++) {
let progressed = false;
for (const player of game.players) {
const view = viewGame(game, player.id); view.players.find(row => row.id === player.id)!.bot = difficulty;
const action = botActions(view)[0];
if (!action) continue;
game = act(game, player.id, action); accepted++; progressed = true; break;
}
assert.ok(progressed, `${difficulty} finishes actual original setup`);
}
assert.equal(game.status, 'playing'); assert.ok(accepted > 1);
assert.equal(p(game, 'harkonnen').hand.length, 2);
assert.equal(game.deck[0].id, f.nextAfterFour);
assert.deepEqual(inventory(game).cards, f.stock.cards);
assert.deepEqual(inventory(game).skills, f.stock.skills);
}
});