Align Advanced Ix starting cards with Harkonnen pool
This commit is contained in:
1 parent
e530d267a3
commit
84fdd404c9
14 files changed
+637
-12
No files matched your search
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user