UNPKG

@idealic/poker-engine

Version:

Professional poker game engine and hand evaluator with built-in iterator utilities

129 lines 4.65 kB
import { setPlayerAnte, setPlayerBet } from '../actions/betting'; import { Game } from '../Game'; import { getRemainingPlayers } from '../utils/position'; import { getGameId } from '../utils/state'; /** * Creates a new table state from a game */ export function createTable(hand, actions = hand.actions) { // Figure out who's SB/BB and button based on stradles, ignoring inactive players const tableName = String(hand.table || Math.random()); const timestamp = hand.timestamp || Date.now(); const smallBlindIndex = hand.blindsOrStraddles.indexOf(Math.min(...hand.blindsOrStraddles.filter(b => b > 0))); let bigBlindIndex = (smallBlindIndex + 1) % hand.players.length; while (hand._inactive?.[bigBlindIndex]) { bigBlindIndex = (bigBlindIndex + 1) % hand.players.length; } let activePlayers = hand.players.filter((_, i) => !hand._inactive?.[i]); let buttonIndex = activePlayers.length == 2 ? smallBlindIndex : (smallBlindIndex - 1 + hand.players.length) % hand.players.length; while (hand._inactive?.[buttonIndex]) { buttonIndex = (buttonIndex - 1 + hand.players.length) % hand.players.length; } // Create initial table state let game = { venue: hand.venue, tableId: tableName, hand: hand.hand || 0, gameId: getGameId(hand), gameTimestamp: timestamp, lastTimestamp: timestamp, author: hand.author, variant: hand.variant, players: hand.players.map((name, i) => ({ name, stack: hand.startingStacks[i], cards: [], currentBet: 0, totalBet: 0, roundBet: 0, rake: 0, hasActed: false, hasFolded: false, isAllIn: false, hasShownCards: false, position: i, winnings: 0, isInactive: !!hand._inactive?.[i], })), board: [], buttonIndex, smallBlindIndex, bigBlindIndex, pot: 0, bet: 0, minBet: Math.max(...hand.blindsOrStraddles), street: 'preflop', isBettingComplete: false, isHandComplete: false, usedCards: 0, rake: hand.rake, rakePercentage: hand.rakePercentage ?? 0, seed: hand.seed, stats: [], nextPlayerIndex: -1, isShowdown: false, }; // Copy timing-related properties from Hand to Game if they exist // Use type assertion to add properties not in Game interface const gameWithTiming = game; if (hand.timeLimit !== undefined) { gameWithTiming.timeLimit = hand.timeLimit; } if (hand.timeBanks !== undefined) { gameWithTiming.timeBank = hand.timeBanks; // Convert timeBanks -> timeBank } if (hand.timeBankUsed !== undefined) { gameWithTiming.timeBankUsed = hand.timeBankUsed; } if (hand.actionStartTime !== undefined) { gameWithTiming.actionStartTime = hand.actionStartTime; } // Post blinds/antes for (let i = 0; i < hand.players.length; i++) { setPlayerAnte(game, i, (hand.antes?.[i] ?? 0) + (hand._deadBlinds?.[i] ?? 0)); setPlayerBet(game, i, hand.blindsOrStraddles?.[i] ?? 0); } // Now parse & apply actions from the game for (let i = 0; i < actions.length; i++) { game = Game.applyAction(game, actions[i]); } // Preserve the actions in the game object for export functionality game.actions = actions; return game; } /** * Determines if we're in showdown */ export function isShowdown(game) { return game.board.length === 5 || getRemainingPlayers(game).length === 1; } // Does this variant typically have blinds? (vs. stud bring-in) export function needsBlinds(variant) { switch (variant) { case 'F7S': case 'F7S/8': case 'FR': return false; // Stud-like default: return true; } } /** * Creates a censored version of the table for testing purposes. * This function hides cards that were not shown during the hand. * It's useful for testing that players can only see the cards they're supposed to see. */ export function createCensoredTable(hand, actions = hand.actions, playerName = hand.author) { const game = createTable(hand, actions); // Hide cards for players who haven't shown their cards game.players = game.players.map(player => ({ ...player, cards: player.name == playerName || player.hasShownCards ? player.cards : player.cards?.map(() => '??'), })); return game; } //# sourceMappingURL=table.js.map