Initial commit
This commit is contained in:
commit
8c709fb52f
771 files changed
+222983
No files matched your search
@@ -0,0 +1,128 @@
|
||||
import type { Game, ResponseWindow } from './engine';
|
||||
|
||||
export class IxTechnologyCancellationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'IxTechnologyCancellationError';
|
||||
}
|
||||
}
|
||||
export type NormalAuctionPeekContext = Pick<
|
||||
Game,
|
||||
| 'status'
|
||||
| 'phase'
|
||||
| 'turn'
|
||||
| 'order'
|
||||
| 'active'
|
||||
| 'auction'
|
||||
| 'richeseAuction'
|
||||
> & {
|
||||
players: readonly Pick<Game['players'][number], 'id' | 'faction'>[];
|
||||
};
|
||||
export type IxTechnologyCancellationContext = NormalAuctionPeekContext &
|
||||
Pick<Game, 'advanced' | 'ixTechnologyTurn' | 'pendingIxTechnology'>;
|
||||
export type NormalAuctionPeekQuote = {
|
||||
peekKnown: false;
|
||||
response: { kind: 'atreidesAuction'; owner: string; passed: [] } | null;
|
||||
};
|
||||
export type IxTechnologyCancellationQuote = {
|
||||
kind: 'ixTechnology';
|
||||
owner: string;
|
||||
pendingIxTechnology: null;
|
||||
peek: NormalAuctionPeekQuote;
|
||||
};
|
||||
function requireTechnology(
|
||||
condition: unknown,
|
||||
message: string,
|
||||
): asserts condition {
|
||||
if (!condition) throw new IxTechnologyCancellationError(message);
|
||||
}
|
||||
const id = (value: unknown): value is string =>
|
||||
typeof value === 'string' && value.length > 0;
|
||||
const whole = (value: unknown): value is number =>
|
||||
Number.isSafeInteger(value) && (value as number) >= 0;
|
||||
|
||||
/** The finite transition shared by normal lot opening, declined Technology,
|
||||
* and completed/canceled Technology. It offers knowledge only; automatic
|
||||
* response settlement and subsequent bidding remain the engine's work. */
|
||||
export function quoteNormalAuctionPeek(
|
||||
g: NormalAuctionPeekContext,
|
||||
): NormalAuctionPeekQuote {
|
||||
requireTechnology(
|
||||
g.status === 'playing' &&
|
||||
g.phase === 3 &&
|
||||
whole(g.turn) &&
|
||||
g.turn > 0 &&
|
||||
!g.richeseAuction &&
|
||||
Array.isArray(g.players) &&
|
||||
g.players.length > 0,
|
||||
'Normal auction inspection needs its current Bidding-phase lot.',
|
||||
);
|
||||
const seats = g.players.map((p) => p.id);
|
||||
requireTechnology(
|
||||
seats.every(id) &&
|
||||
new Set(seats).size === seats.length &&
|
||||
Array.isArray(g.order) &&
|
||||
g.order.length > 0 &&
|
||||
g.order.every((p) => seats.includes(p)) &&
|
||||
new Set(g.order).size === g.order.length,
|
||||
'Normal auction inspection needs a unique seated bidder order.',
|
||||
);
|
||||
const a = g.auction;
|
||||
requireTechnology(
|
||||
a &&
|
||||
Array.isArray(a.cards) &&
|
||||
whole(a.index) &&
|
||||
a.index < a.cards.length &&
|
||||
a.cards[a.index] &&
|
||||
id(a.cards[a.index].id) &&
|
||||
whole(a.opener) &&
|
||||
a.opener < g.order.length &&
|
||||
a.active === g.order[a.opener] &&
|
||||
g.active === a.active &&
|
||||
a.bid === 0 &&
|
||||
a.bidder === null &&
|
||||
(a.allyPayment ?? 0) === 0 &&
|
||||
Array.isArray(a.passed) &&
|
||||
a.passed.length === 0,
|
||||
'Normal auction inspection needs its unbid current card and opening bidder.',
|
||||
);
|
||||
const atreides = g.players.filter((p) => p.faction === 'atreides');
|
||||
requireTechnology(
|
||||
atreides.length <= 1,
|
||||
'Normal auction inspection has an ambiguous Atreides owner.',
|
||||
);
|
||||
return {
|
||||
peekKnown: false,
|
||||
response: atreides.length
|
||||
? { kind: 'atreidesAuction', owner: atreides[0].id, passed: [] }
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/** Denial consumes the existing once-per-turn attempt but no exchange. The
|
||||
* replacement ID is an original declaration, not a current custody demand.
|
||||
* No hands, capacities, random draws, UUIDs or hidden card faces are read. */
|
||||
export function quoteIxTechnologyCancellation(
|
||||
g: IxTechnologyCancellationContext,
|
||||
response: ResponseWindow,
|
||||
): IxTechnologyCancellationQuote | null {
|
||||
if (response.kind !== 'ixTechnology') return null;
|
||||
const peek = quoteNormalAuctionPeek(g);
|
||||
const ixians = g.players.filter((p) => p.faction === 'ixians');
|
||||
requireTechnology(
|
||||
g.advanced &&
|
||||
ixians.length === 1 &&
|
||||
ixians[0].id === response.owner &&
|
||||
g.ixTechnologyTurn === g.turn &&
|
||||
g.pendingIxTechnology &&
|
||||
id(g.pendingIxTechnology.card) &&
|
||||
g.auction?.peekKnown !== true,
|
||||
'Canceled Ixian Technology needs its current Advanced declaration and used attempt.',
|
||||
);
|
||||
return {
|
||||
kind: 'ixTechnology',
|
||||
owner: response.owner,
|
||||
pendingIxTechnology: null,
|
||||
peek,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user