@idealic/poker-engine
Version:
Professional poker game engine and hand evaluator with built-in iterator utilities
129 lines • 4.65 kB
JavaScript
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