Files
Dune/tests/bot-plan-commitment.test.ts
2026-09-08 07:37:52 +10:00

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'));
}
});