Files
Dune/game/choam-sale-cancellation.ts
T
2026-09-08 07:37:52 +10:00

63 lines
2.3 KiB
TypeScript

import type { ChoamMarket } from './choam-market';
import type { Game, ResponseWindow } from './engine';
export class ChoamSaleCancellationError extends Error {}
const id = (v: unknown): v is string => typeof v === 'string' && v.length > 0;
const record = (v: unknown): v is Record<string, unknown> =>
!!v && typeof v === 'object' && !Array.isArray(v);
function requireSale(value: unknown, message: string): asserts value {
if (!value) throw new ChoamSaleCancellationError(message);
}
type Context = Pick<Game, 'status' | 'phase' | 'turn' | 'choamMarket'> & {
players: readonly Pick<Game['players'][number], 'id' | 'faction'>[];
};
/** Denying a declared sale does not sell a card or require its former witness
* to remain available. The returned market stops at CHOAM's next real choice. */
export function quoteChoamSaleCancellation(
g: Context,
response: ResponseWindow,
) {
if (response.kind !== 'choamSale') return null;
const market = g.choamMarket;
requireSale(
g.status === 'playing' &&
Number.isSafeInteger(g.phase) &&
g.phase >= 0 &&
g.phase <= 8 &&
Number.isSafeInteger(g.turn) &&
g.turn > 0 &&
record(market) &&
market.owner === response.owner &&
g.players.some((p) => p.id === market.owner && p.faction === 'choam') &&
(market.resume === 'phase' ||
(market.resume === 'storm' && g.phase === 0)),
'This canceled CHOAM sale needs its current owner and market.',
);
const sale = market.sale;
requireSale(
Array.isArray(market.blocked) &&
market.blocked.every(id) &&
new Set(market.blocked).size === market.blocked.length &&
record(sale) &&
id(sale.card) &&
!market.blocked.includes(sale.card) &&
(sale.witness === undefined
? sale.price === 2
: id(sale.witness) && sale.witness !== sale.card && sale.price === 3) &&
!market.trade,
'This canceled CHOAM sale has no valid unblocked declaration.',
);
const next: ChoamMarket = {
owner: market.owner,
resume: market.resume,
blocked: [...market.blocked, sale.card],
...(market.tradeAttempted === undefined
? {}
: { tradeAttempted: market.tradeAttempted }),
};
return {
market: next,
decision: { kind: 'choamMarket' as const, player: market.owner },
};
}