Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
/**
|
|
* Projection helpers that turn the full table state into safe views.
|
|
*
|
|
* The full state (including every hole card and the deck order) must never
|
|
* leave the server; clients only ever receive these views. Hole cards are
|
|
* hidden except for the viewer's own seats and for live hands at showdown.
|
|
*/
|
|
|
|
import { getLegalActions } from "./betting";
|
|
import type {
|
|
Card,
|
|
PokerSeatId,
|
|
PokerTableState,
|
|
PrivatePlayerView,
|
|
PublicPlayerView,
|
|
PublicTableView,
|
|
} from "./types";
|
|
|
|
function sumCommitted(state: PokerTableState): number {
|
|
return state.players.reduce((total, player) => total + player.committed, 0);
|
|
}
|
|
|
|
function projectPlayer(
|
|
state: PokerTableState,
|
|
player: PokerTableState["players"][number],
|
|
viewerSeat: PokerSeatId | null,
|
|
): PublicPlayerView {
|
|
const isViewer = viewerSeat !== null && player.seatId === viewerSeat;
|
|
const reveal = (state.phase === "showdown" && !player.folded) || isViewer;
|
|
const holeCards: readonly Card[] | null = reveal && player.holeCards.length > 0
|
|
? player.holeCards
|
|
: null;
|
|
return {
|
|
seatId: player.seatId,
|
|
name: player.name,
|
|
stack: player.stack,
|
|
committed: player.committed,
|
|
totalCommitted: player.totalCommitted,
|
|
folded: player.folded,
|
|
allIn: player.allIn,
|
|
hasActed: player.hasActed,
|
|
eliminated: player.eliminated,
|
|
holeCards,
|
|
cardCount: player.holeCards.length,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Public view of the table. Pass `viewerSeat` to also reveal that seat's own
|
|
* hole cards; pass null for a fully spectator-safe view.
|
|
*/
|
|
export function buildPublicView(
|
|
state: PokerTableState,
|
|
viewerSeat: PokerSeatId | null,
|
|
): PublicTableView {
|
|
return {
|
|
players: state.players.map((player) => projectPlayer(state, player, viewerSeat)),
|
|
buttonSeat: state.buttonSeat,
|
|
smallBlindSeat: state.smallBlindSeat,
|
|
bigBlindSeat: state.bigBlindSeat,
|
|
street: state.street,
|
|
phase: state.phase,
|
|
potTotal: state.collectedPot + sumCommitted(state),
|
|
communityCards: state.communityCards,
|
|
currentBet: state.currentBet,
|
|
toActSeat: state.toActSeat,
|
|
handNumber: state.handNumber,
|
|
config: state.config,
|
|
showdown: state.showdown,
|
|
finalPlacements: state.finalPlacements,
|
|
};
|
|
}
|
|
|
|
/** Everything one seated player is allowed to know, including their options. */
|
|
export function buildPrivateView(state: PokerTableState, seatId: PokerSeatId): PrivatePlayerView {
|
|
const viewer = state.players[seatId];
|
|
const publicView = buildPublicView(state, seatId);
|
|
return {
|
|
...publicView,
|
|
viewerSeat: seatId,
|
|
yourHoleCards: viewer?.holeCards ?? [],
|
|
legalActions: getLegalActions(state, seatId),
|
|
yourTurn: state.phase === "betting" && state.toActSeat === seatId,
|
|
};
|
|
}
|