108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
applyAction,
|
|
createGame,
|
|
newPlayer,
|
|
viewGame,
|
|
type Game,
|
|
} from '../game/engine';
|
|
import { baseDeck, ixBattleCards } from '../game/cards';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
|
|
const cards = [...baseDeck(), ...ixBattleCards()];
|
|
const card = (kind: string) => cards.find((c) => c.kind === kind)!;
|
|
function fixture() {
|
|
let g = createGame('PLANFIX', newPlayer('p', 'Planner', 'emperor'));
|
|
g.players.push(newPlayer('q', 'Inspector', 'atreides'));
|
|
Object.assign(g, {
|
|
status: 'playing',
|
|
phase: 6,
|
|
turn: 2,
|
|
storm: 18,
|
|
active: 'p',
|
|
order: ['p', 'q'],
|
|
});
|
|
for (const p of g.players)
|
|
Object.assign(p, {
|
|
hand: [],
|
|
spice: 10,
|
|
forces: { 'arrakeen:10': 4 },
|
|
reserves: 16,
|
|
traitors: [],
|
|
});
|
|
g.players[0].hand = ['worthless', 'projectile', 'snooper', 'chemistry'].map(
|
|
card,
|
|
);
|
|
g = applyAction(g, 'p', {
|
|
type: 'chooseBattle',
|
|
territory: 'arrakeen',
|
|
target: 'q',
|
|
});
|
|
assert.equal(g.battle!.preparation!.kind, 'prescience');
|
|
return g;
|
|
}
|
|
function asked(field: string) {
|
|
return applyAction(fixture(), 'q', { type: 'prescience', field });
|
|
}
|
|
function offered(g: Game) {
|
|
return DIFFICULTIES.map((difficulty) => {
|
|
const v = viewGame(g, 'p');
|
|
v.players[0].bot = difficulty;
|
|
const before = structuredClone(v),
|
|
actions = botActions(v);
|
|
assert.deepEqual(v, before);
|
|
assert.ok(actions.length, difficulty);
|
|
for (const action of actions)
|
|
assert.doesNotThrow(
|
|
() => applyAction(g, 'p', action),
|
|
`${difficulty}: ${JSON.stringify(action)}`,
|
|
);
|
|
return actions;
|
|
});
|
|
}
|
|
void test('Prescience fixed weapon and defense retain physical slot uniqueness and valid alternate roles for every profile', () => {
|
|
for (const [field, value] of [
|
|
['weapon', card('worthless').id],
|
|
['defense', card('chemistry').id],
|
|
] as const) {
|
|
const g = applyAction(asked(field), 'p', {
|
|
type: 'prescienceAnswer',
|
|
value,
|
|
});
|
|
for (const actions of offered(g)) {
|
|
assert.ok(actions.every((a) => a[field] === value));
|
|
assert.ok(actions.every((a) => !a.weapon || a.weapon !== a.defense));
|
|
}
|
|
}
|
|
});
|
|
void test('Prescience committed leader and dial constrain the final plans without inventing leaderless combinations', () => {
|
|
for (const [field, value] of [
|
|
['leader', 'emperor-4'],
|
|
['dial', 2],
|
|
] as const) {
|
|
const g = applyAction(asked(field), 'p', {
|
|
type: 'prescienceAnswer',
|
|
value,
|
|
});
|
|
for (const actions of offered(g)) {
|
|
assert.ok(actions.every((a) => a[field] === value));
|
|
assert.ok(actions.every((a) => a.leader !== null));
|
|
}
|
|
}
|
|
});
|
|
void test('every offered Prescience answer has an authoritative completion and ignores unrevealed opposing cards', () => {
|
|
for (const field of ['weapon', 'defense', 'leader', 'dial']) {
|
|
const g = asked(field),
|
|
changed = structuredClone(g);
|
|
changed.players[1].hand = [card('poison'), card('shield')];
|
|
changed.players[1].spice = 999;
|
|
const a = offered(g),
|
|
b = offered(changed);
|
|
assert.deepEqual(a, b);
|
|
for (const actions of a)
|
|
assert.ok(actions.every((action) => action.type === 'prescienceAnswer'));
|
|
}
|
|
});
|