108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
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);
|
|
}
|
|
});
|