83 lines
5.1 KiB
TypeScript
83 lines
5.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import type { Action, Game } from '../game/engine';
|
|
import { parseBribeExpression, type BribeExpression } from '../game/bribe-expression-promises';
|
|
import {
|
|
actTruthBribe, advanceTruthBribe, answerTruthBribe, declareTruthBribe, finishTruthBribe,
|
|
nextTruthBribe, settleTruthBribe, truthBribeBalances, truthBribeClean, truthBribeDeferred,
|
|
truthBribeInventory, truthBribePlayer, createTruthBribeFixture,
|
|
type TruthBribeStep, type TruthBribeTransition,
|
|
} from './fixture-truth-bribe';
|
|
import { createMentatBribeFixture } from './fixture-mentat-bribes';
|
|
|
|
export type TruthBribeExpressionStep = TruthBribeStep;
|
|
export type TruthBribeExpressionTransition = TruthBribeTransition;
|
|
export const actTruthBribeExpression = actTruthBribe;
|
|
export const nextTruthBribeExpression = nextTruthBribe;
|
|
export const advanceTruthBribeExpression = advanceTruthBribe;
|
|
export const settleTruthBribeExpression = settleTruthBribe;
|
|
export const finishTruthBribeExpression = finishTruthBribe;
|
|
export const truthBribeExpressionClean = truthBribeClean;
|
|
export const truthBribeExpressionPlayer = truthBribePlayer;
|
|
export const truthBribeExpressionInventory = truthBribeInventory;
|
|
export const truthBribeExpressionBalances = truthBribeBalances;
|
|
export const truthBribeExpressionDeferred = truthBribeDeferred;
|
|
|
|
/** The original public H/A/E base33 source supplies the exact unopened deals,
|
|
* ordinary auctions and ten spendable Emperor spice. Phase7/8 follows its real
|
|
* Collection/Mentat controls. Only the question changes: no played clock,
|
|
* purse, hand, receipt, answer or promise is installed by this adapter. */
|
|
export function createTruthBribeExpressionFixture(options: {
|
|
advanced?: boolean; window?: 'waiting' | 'active'; phase?: 3 | 7 | 8; claim?: BribeExpression;
|
|
} = {}) {
|
|
const phase = options.phase ?? 3;
|
|
const base = phase === 8 ? createMentatBribeFixture({ advanced: options.advanced }) :
|
|
createTruthBribeFixture({ advanced: options.advanced, window: options.window, phase });
|
|
const claim = parseBribeExpression(options.claim ?? { kind: 'and', terms: [
|
|
{ kind: 'payment', recipient: base.asker, minimum: 3 },
|
|
{ kind: 'payment', recipient: base.opponent, minimum: 2 },
|
|
] });
|
|
const question = { kind: 'bribeExpression' as const, target: base.target, turn: base.game.turn, claim };
|
|
return { ...base, base, phase, claim, question, questionAction: { type: 'truthAsk', question } as Action,
|
|
sourceDisclosure: `${base.sourceDisclosure} This thin adapter changes only the grouped ordinary-payment question; each recipient starts at zero future payment progress after its real answer.` };
|
|
}
|
|
export type TruthBribeExpressionFixture = ReturnType<typeof createTruthBribeExpressionFixture>;
|
|
export function declareTruthBribeExpression(f: TruthBribeExpressionFixture, state = f.game,
|
|
options: { copies?: 1 | 2 } = {}) {
|
|
return declareTruthBribe(f, state, options);
|
|
}
|
|
export function askTruthBribeExpression(game: Game, asker: string, target: string, claim: BribeExpression,
|
|
actions: TruthBribeExpressionStep[] = []): Game {
|
|
return actTruthBribeExpression(game, asker, { type: 'truthAsk', question: {
|
|
kind: 'bribeExpression', target, turn: game.turn, claim: parseBribeExpression(claim),
|
|
} }, actions);
|
|
}
|
|
export const answerTruthBribeExpression = answerTruthBribe;
|
|
export function bindTruthBribeExpression(f: TruthBribeExpressionFixture, answer: 'yes' | 'no' = 'yes',
|
|
claim = f.claim, state = f.game) {
|
|
const declared = declareTruthBribeExpression(f, state), actions = [...declared.actions];
|
|
const asked = askTruthBribeExpression(declared.game, f.asker, f.target, claim, actions);
|
|
const game = answerTruthBribeExpression(asked, f.target, answer, actions);
|
|
return { game, declared, asked, actions, actionCount: actions.length };
|
|
}
|
|
|
|
/** Preserve the actual opening boundary. A question asked after opening waits
|
|
* through the next real Storm and Collection; an earlier payment is credited
|
|
* at this turn's opening. The adapter never collects receipts itself. */
|
|
export function completeTruthBribeExpression(f: TruthBribeExpressionFixture, state = f.game) {
|
|
let game = state; const turn = game.turn, late = game.phase === 8;
|
|
const actions: TruthBribeExpressionStep[] = [];
|
|
const transition = (until: (state: Game) => boolean): TruthBribeExpressionTransition => {
|
|
const before = structuredClone(game), from = actions.length;
|
|
game = advanceTruthBribeExpression(game, until, actions);
|
|
return { before, steps: structuredClone(actions.slice(from)), after: structuredClone(game) };
|
|
};
|
|
const boundary = () => transition(state => state.turn === turn + 1 && state.phase === 0 && truthBribeExpressionClean(state));
|
|
const nextTurnBefore = late ? boundary() : null;
|
|
const openingTurn = turn + Number(late);
|
|
const collection = transition(state => state.turn === openingTurn && state.phase === 7 && truthBribeExpressionClean(state));
|
|
const mentat = transition(state => state.turn === openingTurn && state.phase === 8 && truthBribeExpressionClean(state));
|
|
const nextTurn = nextTurnBefore ?? boundary();
|
|
assert.deepEqual(truthBribeExpressionInventory(game), f.originalCards);
|
|
return { game, collection, mentat, nextTurn, actions, actionCount: actions.length };
|
|
}
|