115 lines
5.5 KiB
TypeScript
115 lines
5.5 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 {
|
|
createSalusaGholaFixture, actSalusaGhola as act,
|
|
advanceSalusaGhola as advance, salusaGholaClean as clean,
|
|
salusaGholaArmy as army, salusaGholaPlayer as p, salusaGholaInventory as inventory,
|
|
type SalusaGholaFixture,
|
|
} from './fixture-salusa-ghola';
|
|
|
|
const sources = new Map<number,SalusaGholaFixture>();
|
|
function source(answer: 1|2) {
|
|
let f = sources.get(answer);
|
|
if (!f) {
|
|
f = createSalusaGholaFixture({firstTerritory:'imperial_basin',inspectionDial:answer});
|
|
sources.set(answer,f);
|
|
}
|
|
return f;
|
|
}
|
|
function reject(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game,actor,action));
|
|
assert.deepEqual(game,before);
|
|
}
|
|
function leader(game: Game, actor: string, strongest: boolean) {
|
|
const choices = p(game,actor).leaders.filter(l => !l.dead && !l.usedAt && !l.capturedBy);
|
|
choices.sort((a,b) => strongest ? b.strength-a.strength : a.strength-b.strength);
|
|
assert.ok(choices[0]); return choices[0].id;
|
|
}
|
|
function finish(f: SalusaGholaFixture, state: Game, dial: 1|2) {
|
|
let game = act(state,f.actor,{type:'battlePlan',leader:leader(state,f.actor,true),dial,support:0});
|
|
game = act(game,f.opponent,{type:'battlePlan',leader:leader(game,f.opponent,false),dial:0,support:0});
|
|
return advance(game,g => !g.battle && clean(g));
|
|
}
|
|
|
|
void test('earned Basin-first source answers real Atreides dial Prescience before either plan is sealed', () => {
|
|
const f = source(1), inspection = f.inspection;
|
|
assert.ok(inspection);
|
|
assert.equal(f.firstTerritory,'imperial_basin');
|
|
assert.equal(f.firstOpponent,'guild');
|
|
assert.equal(f.territory,'arrakeen');
|
|
assert.equal(f.opponent,'atreides');
|
|
assert.ok(viewGame(f.beforeFirstBattle,f.beforeFirstBattle.active!).battleChoices
|
|
.some(choice => choice.territory === 'imperial_basin'));
|
|
assert.equal(inspection.beforeDeclaration.battle!.preparation!.kind,'prescience');
|
|
assert.equal(inspection.beforeDeclaration.battle!.preparation!.owner,'atreides');
|
|
assert.equal(inspection.steps[0].actor,'atreides');
|
|
assert.deepEqual(inspection.steps[0].action,{type:'prescience',field:'dial'});
|
|
assert.equal(inspection.beforeAnswer.battle!.preparation!.owner,f.actor);
|
|
assert.ok(inspection.steps.some(step => step.actor === f.actor &&
|
|
step.action.type === 'prescienceAnswer' && step.action.value === 1));
|
|
assert.equal(f.game.battle!.prescience!.player,'atreides');
|
|
assert.equal(f.game.battle!.prescience!.field,'dial');
|
|
assert.equal(f.game.battle!.prescience!.value,1);
|
|
assert.deepEqual(f.game.battle!.plans,{});
|
|
assert.equal(f.game.battle!.preparation,undefined);
|
|
assert.equal(p(f.game).elites!.tanks,1);
|
|
assert.deepEqual(army(f.game),{normal:0,elite:1});
|
|
assert.equal(f.sourceCount,f.sourceActions.length);
|
|
assert.deepEqual(inventory(f.game),f.original);
|
|
});
|
|
|
|
void test('Low Salusa rejection preserves the original native odd-dial Prescience answer', () => {
|
|
const f = source(1);
|
|
let game = structuredClone(f.game);
|
|
reject(game,f.actor,f.action);
|
|
assert.deepEqual(army(game),{normal:0,elite:1});
|
|
assert.equal(game.battle!.event,f.game.battle!.event);
|
|
assert.equal(game.battle!.prescience!.field,'dial');
|
|
assert.equal(game.battle!.prescience!.player,'atreides');
|
|
assert.equal(game.battle!.prescience!.value,1);
|
|
assert.equal(game.battle!.preparation,undefined);
|
|
assert.deepEqual(game.battle!.plans,{});
|
|
assert.equal(viewGame(game,f.actor).battle!.ownForces!.eliteFreeSupport,undefined);
|
|
assert.equal(game.discard.filter(c => c.id === f.card).length,0);
|
|
assert.equal(p(game).elites!.tanks,1);
|
|
reject(game,f.actor,{type:'battlePlan',leader:leader(game,f.actor,true),dial:2,support:0});
|
|
game = finish(f,game,1);
|
|
assert.equal(game.lastBattleContext!.winner,f.actor);
|
|
assert.equal(p(game).elites!.tanks,2);
|
|
assert.deepEqual(army(game),{normal:0,elite:1});
|
|
assert.equal(game.discard.filter(c => c.id === f.card).length,0);
|
|
assert.deepEqual(inventory(game),f.original);
|
|
});
|
|
|
|
void test('Low Salusa rejection retains a native dial2 answer and finishes without another inspection', () => {
|
|
const f = source(2), before = structuredClone(f.game);
|
|
assert.equal(f.game.battle!.prescience!.value,2);
|
|
reject(before,f.actor,f.action);
|
|
assert.equal(before.battle!.prescience!.value,2);
|
|
assert.equal(before.battle!.preparation,undefined);
|
|
const done = finish(f,before,2);
|
|
assert.equal(done.lastBattleContext!.winner,f.actor);
|
|
assert.equal(done.discard.filter(c => c.id === f.card).length,0);
|
|
assert.deepEqual(inventory(done),f.original);
|
|
});
|
|
|
|
void test('all four minimal profiles honor the existing Salusa Prescience answer after rejection', () => {
|
|
const f = source(1), pending = structuredClone(f.game);
|
|
reject(pending,f.actor,f.action);
|
|
for (const profile of ['Easy','Medium','Hard','Brutal'] as const) {
|
|
const game = structuredClone(pending), view = viewGame(game,f.actor);
|
|
view.players.find(p => p.id === f.actor)!.bot = profile;
|
|
const action = botActions(view).find(action => action.type === 'battlePlan' && action.dial === 1);
|
|
assert.ok(action,`${profile} needs a legal plan honoring the actual answered dial.`);
|
|
const next = act(game,f.actor,action);
|
|
assert.equal(next.battle!.prescience!.value,1);
|
|
assert.equal(next.battle!.plans[f.actor].dial,1);
|
|
assert.equal(next.discard.filter(c => c.id === f.card).length,0);
|
|
assert.deepEqual(army(next),{normal:0,elite:1});
|
|
assert.deepEqual(inventory(next),f.original);
|
|
}
|
|
});
|