84 lines
4.2 KiB
TypeScript
84 lines
4.2 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES, type Difficulty } from '../game/bot-profiles';
|
|
import { baseDeck } from '../game/cards';
|
|
import { quoteHarassWithdraw } from '../game/harass-withdraw';
|
|
import { harassWithdrawGame, harassCustody, takeHarassCard } from './fixture-harass-withdraw';
|
|
|
|
function projection(game: Game, profile: Difficulty) {
|
|
const view = viewGame(game, 'a');
|
|
view.players.find(player => player.id === 'a')!.bot = profile;
|
|
return view;
|
|
}
|
|
const usesHarass = (action: Action) =>
|
|
action.weapon === 'ecaz-harass-withdraw' || action.defense === 'ecaz-harass-withdraw';
|
|
|
|
void test('each AI profile can commit Harass in either slot through genuine Basic and Advanced battle setup', () => {
|
|
for (const advanced of [false, true]) for (const profile of DIFFICULTIES) {
|
|
const game = harassWithdrawGame({ advanced });
|
|
const view = projection(game, profile), before = structuredClone(view);
|
|
const candidates = botActions(view).filter(action => action.type === 'battlePlan' && usesHarass(action));
|
|
assert.deepEqual(view, before);
|
|
assert.ok(candidates.length, `${profile}/${advanced} omitted Harass`);
|
|
for (const slot of ['weapon', 'defense'] as const) {
|
|
const action = candidates.find(action => action[slot] === 'ecaz-harass-withdraw');
|
|
assert.ok(action, `${profile}/${advanced} omitted ${slot}`);
|
|
quoteHarassWithdraw(view.battle!.harassWithdraw!, Number(action.dial), Number(action.support ?? 0));
|
|
const sealed = applyAction(game, 'a', action);
|
|
assert.equal(sealed.battle!.plans.a[slot], 'ecaz-harass-withdraw');
|
|
harassCustody(sealed);
|
|
}
|
|
}
|
|
});
|
|
|
|
void test('all profiles answer category inspections without revealing Harass and can keep it in a committed empty-category slot', () => {
|
|
for (const profile of DIFFICULTIES) {
|
|
let game = harassWithdrawGame({ prepare: false });
|
|
for (let step = 0; game.response && step < 10; step++) {
|
|
const id = game.players.find(player => !game.response!.passed.includes(player.id))!.id;
|
|
game = applyAction(game, id, { type: 'passResponse' });
|
|
}
|
|
assert.equal(game.battle!.preparation?.kind, 'prescience');
|
|
game = applyAction(game, 'd', { type: 'prescience', field: 'weapon' });
|
|
const answers = botActions(projection(game, profile));
|
|
assert.ok(answers.length);
|
|
assert.ok(answers.every(action => action.type === 'prescienceAnswer' && action.value === null));
|
|
game = applyAction(game, 'a', answers[0]);
|
|
const view = projection(game, profile);
|
|
const action = botActions(view).find(action => action.type === 'battlePlan' && action.weapon === 'ecaz-harass-withdraw');
|
|
assert.ok(action, `${profile} erased the inspected slot occupant`);
|
|
const sealed = applyAction(game, 'a', action);
|
|
assert.equal(sealed.battle!.prescience!.value, null);
|
|
assert.equal(viewGame(sealed, 'd').battle!.insight!.value, null);
|
|
assert.equal(viewGame(sealed, 't').battle!.harassWithdraw, null);
|
|
harassCustody(sealed);
|
|
}
|
|
});
|
|
|
|
void test('hidden opponent card swaps leave the Harass planner input and actions identical', () => {
|
|
const game = harassWithdrawGame();
|
|
takeHarassCard(game, 'd', baseDeck()[0].id);
|
|
takeHarassCard(game, 't', baseDeck()[1].id);
|
|
const changed = structuredClone(game);
|
|
[changed.players[1].hand[0], changed.players[2].hand[0]] = [changed.players[2].hand[0], changed.players[1].hand[0]];
|
|
for (const profile of DIFFICULTIES) {
|
|
const before = projection(game, profile), after = projection(changed, profile);
|
|
assert.deepEqual(after, before);
|
|
assert.deepEqual(botActions(after), botActions(before));
|
|
}
|
|
harassCustody(changed);
|
|
});
|
|
|
|
void test('the planner excludes a publicly blocked Harass module without reading opposing holdings', () => {
|
|
const game = harassWithdrawGame();
|
|
for (const profile of DIFFICULTIES) {
|
|
const view = projection(game, profile);
|
|
view.battle!.harassWithdraw = { ...view.battle!.harassWithdraw!, blocked: 'This configured card combination awaits a ruling.' };
|
|
const actions = botActions(view);
|
|
assert.ok(actions.length);
|
|
assert.ok(actions.every(action => !usesHarass(action)));
|
|
}
|
|
});
|