Align Advanced Ix starting cards with Harkonnen pool

This commit is contained in:
Quintine committed 2026-10-27 10:26:44 +11:00
1 parent e530d267a3
commit 84fdd404c9
14 files changed
+637 -12

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
IxStartingCardError,
ixStartingCardDealPlan,
ixStartingCardPoolSize,
type IxStartingCardGame,
} from '../game/ixian-starting-cards';
function game(advanced: boolean, factions: readonly string[]): IxStartingCardGame {
return { advanced, players: factions.map((faction, index) => ({ id: `seat-${index}`, faction })) };
}
const otherFactions = ['atreides', 'emperor', 'guild', 'fremen'] as const;
void test('fresh Ix pools add Harkonnen only in Advanced, from two through six seats', () => {
for (let count = 2; count <= 6; count++) {
for (const advanced of [false, true]) {
const harkonnen = game(advanced, ['ixians', 'harkonnen', ...otherFactions.slice(0, count - 2)]);
assert.equal(ixStartingCardPoolSize(harkonnen), count + (advanced ? 1 : 0));
const ordinary = game(advanced, ['ixians', ...otherFactions, 'beneGesserit'].slice(0, count));
assert.equal(ixStartingCardPoolSize(ordinary), count);
}
}
});
void test('new Advanced pools deal Harkonnen two from the shuffled remainder in original seat order', () => {
for (let count = 2; count <= 6; count++) {
for (const ixFirst of [false, true]) {
const factions = ixFirst ? ['ixians', 'harkonnen'] : ['harkonnen', 'ixians'];
const native = game(true, [...factions, ...otherFactions.slice(0, count - 2)]);
const before = structuredClone(native);
Object.freeze(native.players);
Object.freeze(native);
const plan = ixStartingCardDealPlan(native, count + 1);
assert.deepEqual(plan, {
kind: 'advanced-pool',
seats: native.players.filter(player => player.faction !== 'ixians').map(player => ({
player: player.id, amount: player.faction === 'harkonnen' ? 2 : 1,
})),
deckBonus: null,
});
assert.equal(plan.seats.reduce((amount, seat) => amount + seat.amount, 0), count);
assert.deepEqual(native, before);
plan.seats[0].amount = 99;
assert.deepEqual(native, before);
}
}
});
void test('Basic and old Advanced N-card or short pools keep one-per-other then the Harkonnen deck bonus', () => {
for (let count = 2; count <= 6; count++) {
for (const advanced of [false, true]) {
const native = game(advanced, ['harkonnen', ...otherFactions.slice(0, count - 2), 'ixians']);
for (const poolCount of new Set([1, count - 1, count])) {
assert.deepEqual(ixStartingCardDealPlan(native, poolCount), {
kind: 'legacy-deck-bonus',
seats: native.players.filter(player => player.faction !== 'ixians').map(player => ({ player: player.id, amount: 1 })),
deckBonus: 'seat-0',
});
}
}
}
});
void test('without Harkonnen both modes deal one per other seat with no extra deck draw', () => {
for (const advanced of [false, true]) {
const native = game(advanced, ['atreides', 'ixians', 'fremen', 'guild']);
for (const count of [1, 3, 4]) {
assert.deepEqual(ixStartingCardDealPlan(native, count), {
kind: 'legacy-deck-bonus',
seats: [{ player: 'seat-0', amount: 1 }, { player: 'seat-2', amount: 1 }, { player: 'seat-3', amount: 1 }],
deckBonus: null,
});
}
}
});
void test('a pool larger than its native source or an invalid count cannot select a new deal mode', () => {
const advanced = game(true, ['ixians', 'harkonnen']);
const basic = game(false, ['ixians', 'harkonnen']);
const noHarkonnen = game(true, ['ixians', 'atreides']);
for (const count of [0, -1, 1.5, Number.NaN, Number.POSITIVE_INFINITY, Number.MAX_SAFE_INTEGER + 1, 4])
assert.throws(() => ixStartingCardDealPlan(advanced, count), IxStartingCardError);
assert.throws(() => ixStartingCardDealPlan(basic, 3), IxStartingCardError);
assert.throws(() => ixStartingCardDealPlan(noHarkonnen, 3), IxStartingCardError);
});
void test('the planner rejects missing Ixians, unregistered or duplicate seats and invalid native roster bounds', () => {
const valid = game(true, ['ixians', 'harkonnen']);
const invalid: unknown[] = [
null,
{ ...valid, advanced: 'advanced' },
{ ...valid, players: undefined },
game(true, ['ixians']),
game(true, ['ixians', 'harkonnen', ...otherFactions, 'beneGesserit']),
game(true, ['atreides', 'harkonnen']),
game(true, ['ixians', 'ixians']),
game(true, ['ixians', 'unknown']),
{ ...valid, players: [{ id: 'seat', faction: 'ixians' }, { id: 'seat', faction: 'harkonnen' }] },
{ ...valid, players: [{ id: '', faction: 'ixians' }, valid.players[1]] },
{ ...valid, players: [valid.players[0], { id: ' seat ', faction: 'harkonnen' }] },
];
for (const native of invalid) {
assert.throws(() => ixStartingCardPoolSize(native as IxStartingCardGame), IxStartingCardError);
assert.throws(() => ixStartingCardDealPlan(native as IxStartingCardGame, 2), IxStartingCardError);
}
});