69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
export type NormalAuctionNext =
|
|
| { kind: 'unbid' }
|
|
| { kind: 'payment'; player: string }
|
|
| { kind: 'bid'; player: string };
|
|
|
|
export type NormalAuctionBid =
|
|
| { ok: true; amount: number }
|
|
| { ok: false; reason: string };
|
|
|
|
/** Capacity can change after a lot opens, for example through Distrans. The
|
|
* existing active player may still pass; only a new purchase bid is barred. */
|
|
export function normalAuctionHandBlock(held: number, limit: number): string | null {
|
|
return held >= limit ? 'Your Treachery hand is full; pass this bid.' : null;
|
|
}
|
|
|
|
/**
|
|
* Quote the next ordinary auction step. The engine owns card custody, payment,
|
|
* phase progression and the hand-limit eligibility calculation.
|
|
*/
|
|
export function quoteNormalAuctionNext(context: {
|
|
order: readonly string[];
|
|
eligible: readonly string[];
|
|
active: string;
|
|
bid: number;
|
|
bidder: string | null;
|
|
passed: readonly string[];
|
|
}): NormalAuctionNext {
|
|
const eligible = new Set(context.eligible);
|
|
const others = context.eligible.filter((id) => id !== context.bidder);
|
|
if (others.every((id) => context.passed.includes(id)))
|
|
return context.bidder
|
|
? { kind: 'payment', player: context.bidder }
|
|
: { kind: 'unbid' };
|
|
|
|
const activeIndex = context.order.indexOf(context.active);
|
|
for (let offset = 1; offset <= context.order.length; offset++) {
|
|
const index = (Math.max(activeIndex, -1) + offset) % context.order.length;
|
|
const player = context.order[index];
|
|
if (eligible.has(player) && player !== context.bidder && !context.passed.includes(player))
|
|
return { kind: 'bid', player };
|
|
}
|
|
|
|
// A malformed restored state must fail closed instead of spinning forever or
|
|
// fabricating an auction outcome.
|
|
throw new Error(
|
|
'Ordinary auction has no eligible next bidder in table order.',
|
|
);
|
|
}
|
|
|
|
/** The engine supplies the freshly calculated funding maximum. */
|
|
export function quoteNormalAuctionBid(context: {
|
|
currentBid: number;
|
|
amount: unknown;
|
|
maximum: number;
|
|
}): NormalAuctionBid {
|
|
const minimum = context.currentBid + 1;
|
|
if (
|
|
typeof context.amount !== 'number' ||
|
|
!Number.isSafeInteger(context.amount) ||
|
|
context.amount < minimum ||
|
|
context.amount > context.maximum
|
|
)
|
|
return {
|
|
ok: false,
|
|
reason: `Bid must be an integer from ${minimum} to ${context.maximum}.`,
|
|
};
|
|
return { ok: true, amount: context.amount };
|
|
}
|