77 lines
4.5 KiB
TypeScript
77 lines
4.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import type { PlanClaim } from '../game/battle-promises';
|
|
import type { TruthQuestion } from '../game/truthtrance';
|
|
import {
|
|
createSalusaGholaFixture, actSalusaGhola, advanceSalusaGhola, salusaGholaClean,
|
|
salusaGholaPlayer, salusaGholaInventory, type SalusaGholaStep,
|
|
} from './fixture-salusa-ghola';
|
|
|
|
export const actSalusaGholaTruth = actSalusaGhola;
|
|
export const salusaGholaTruthPlayer = salusaGholaPlayer;
|
|
export const salusaGholaTruthInventory = salusaGholaInventory;
|
|
export const salusaGholaTruthClean = salusaGholaClean;
|
|
|
|
/** Original public source and native Truth protocol. The optional inspection
|
|
* was really declared/answered before this original Truth card is used. No
|
|
* binding, frozen answer, battle commitment or physical receipt is installed. */
|
|
export function createSalusaGholaTruthFixture(options: {
|
|
claim?: PlanClaim; answer?: 'yes'|'no'; inspectionDial?: 1|2;
|
|
} = {}) {
|
|
const base = createSalusaGholaFixture({firstTerritory:'imperial_basin',truthtrance:true,
|
|
...(options.inspectionDial === undefined ? {} : {inspectionDial:options.inspectionDial})});
|
|
const actor = base.actor, opponent = base.opponent;
|
|
assert.equal(opponent,'atreides');
|
|
const truth = salusaGholaPlayer(base.game,opponent).hand.find(c => c.effect === 'truthtrance');
|
|
assert.ok(truth);
|
|
const claim: PlanClaim = structuredClone(options.claim ?? {kind:'dial',compare:'eq',value:2});
|
|
const answer = options.answer ?? 'yes';
|
|
const question: Extract<TruthQuestion,{kind:'battlePlan'}> = {
|
|
kind:'battlePlan',target:actor,territory:base.territory,claim,
|
|
};
|
|
const actions: SalusaGholaStep[] = [...base.sourceActions], at = actions.length;
|
|
let game = actSalusaGhola(structuredClone(base.game),opponent,{type:'card',card:truth.id},actions);
|
|
const declared = structuredClone(game);
|
|
for (let n = 0; game.truthtrance?.stage === 'priority' && n < game.players.length; n++) {
|
|
const owner = game.players.find(p => !game.truthtrance!.passed.includes(p.id)); assert.ok(owner);
|
|
game = actSalusaGhola(game,owner.id,{type:'truthPass'},actions);
|
|
}
|
|
assert.equal(game.truthtrance?.stage,'ask');
|
|
const beforeQuestion = structuredClone(game);
|
|
game = actSalusaGhola(game,opponent,{type:'truthAsk',question},actions);
|
|
const asked = structuredClone(game);
|
|
assert.ok(viewGame(game,actor).truthBattleAnswers?.includes(answer),
|
|
'The actual native Truth question must offer the requested definite answer.');
|
|
game = actSalusaGhola(game,actor,{type:'truthAnswer',answer},actions);
|
|
assert.equal(game.truthtrance,null);
|
|
assert.deepEqual(game.battle!.plans,{});
|
|
assert.equal(game.discard.filter(c => c.id === truth.id).length,1);
|
|
assert.deepEqual(salusaGholaInventory(game),base.original);
|
|
return {...base,base,game,source:structuredClone(game),actor,opponent,truthCard:truth.id,
|
|
claim,answer,question,declared,beforeQuestion,asked,event:game.battle!.event!,
|
|
actions,sourceActions:actions,sourceCount:actions.length,truthActions:actions.slice(at),
|
|
disclosure:base.disclosure+' Atreides actually received its original unopened Truthtrance instead of Shield. Its native printed declaration, original priority passes, battle-plan question and Emperor definite answer create the live binding; no promise is installed.'};
|
|
}
|
|
export type SalusaGholaTruthFixture = ReturnType<typeof createSalusaGholaTruthFixture>;
|
|
|
|
export function salusaGholaTruthPlan(game: Game, dial = 2, support = 0): Action {
|
|
const leaders = salusaGholaPlayer(game).leaders.filter(l => !l.dead && !l.usedAt && !l.capturedBy)
|
|
.sort((a,b) => b.strength-a.strength);
|
|
assert.ok(leaders[0]);
|
|
return {type:'battlePlan',leader:leaders[0].id,dial,support};
|
|
}
|
|
|
|
/** Actual native seals and calls only. It does not play Ghola or manufacture a
|
|
* replacement inspection answer; the caller must finish the current chooser. */
|
|
export function finishSalusaGholaTruthBattle(f: SalusaGholaTruthFixture, state: Game,
|
|
plan: Action = salusaGholaTruthPlan(state), actions: SalusaGholaStep[] = []) {
|
|
let game = actSalusaGhola(state,f.actor,plan,actions);
|
|
const afterPlan = structuredClone(game);
|
|
const enemy = salusaGholaPlayer(game,f.opponent).leaders.filter(l => !l.dead && !l.usedAt && !l.capturedBy)
|
|
.sort((a,b) => a.strength-b.strength)[0]; assert.ok(enemy);
|
|
game = actSalusaGhola(game,f.opponent,{type:'battlePlan',leader:enemy.id,dial:0,support:0},actions);
|
|
const revealed = structuredClone(game);
|
|
game = advanceSalusaGhola(game,g => !g.battle && salusaGholaClean(g),actions);
|
|
return {game,afterPlan,revealed,actions};
|
|
}
|