UNPKG

@idealic/poker-engine

Version:

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

148 lines 5.87 kB
import { compareHands } from '../actions/showdown'; export function finalizeStacks(game, compare = compareHands) { // 1) Initialize final stacks const finishingStacks = game.players.map(p => p.stack); const winnings = game.players.map(_ => 0); // Early returns if we can't distribute yet if (game.isHandComplete || game.pot === 0 || !game.isBettingComplete) { return finishingStacks; } // We'll store how much pot is "fully called" (used for rake) here game.finalPot = game.pot; // 3) Identify active (non-folded) players const activePlayerIndices = game.players .map((p, i) => (!p.hasFolded ? i : -1)) .filter(x => x !== -1); // If not all active players have "shown cards," we cannot distribute yet const allHaveShown = activePlayerIndices.every(i => game.players[i].hasShownCards) || activePlayerIndices.length === 1; if (!allHaveShown) { return finishingStacks; } /** * STEP A: Determine which players actually contributed (totalBet > 0). */ const involved = game.players.map((p, i) => (p.totalBet > 0 ? i : -1)).filter(x => x !== -1); if (involved.length === 0) { // No contributions => no pot to distribute return finishingStacks; } /** * STEP B: We gather all relevant "bet levels": * 1. The distinct totalBet values of each involved player * 2. The blindOrStraddle values (e.g. 50, 100) for players who actually posted them * so we don't skip from 0 -> 200 ignoring a big blind of 100. */ const betLevelsSet = new Set(); for (const idx of involved) { // A player's totalBet betLevelsSet.add(game.players[idx].totalBet); } // Always ensure we start from bet=0 so the first slice covers that range. betLevelsSet.add(0); // Convert to a sorted array const uniqueBetLevels = [...betLevelsSet].sort((a, b) => a - b); /** * STEP C: Build pot slices by iterating through consecutive bet levels. * * Example betLevels might be [0, 50, 100, 200, 8303]. * We'll slice from 0->50, 50->100, 100->200, 200->8303, etc. */ let pots = []; let prevBet = 0; let totalCalled = 0; for (let i = 1; i < uniqueBetLevels.length; i++) { const currentBet = uniqueBetLevels[i]; if (currentBet <= prevBet) { // skip duplicates or weirdness continue; } // Find all players whose totalBet >= currentBet const contributors = involved.filter(idx => game.players[idx].totalBet >= currentBet); const betDiff = currentBet - prevBet; const potSlice = contributors.length * betDiff; // Advance for next iteration prevBet = currentBet; if (potSlice === 0) { continue; } // Check for uncalled bet scenario: exactly 1 contributor if (contributors.length === 1 && !game.players[contributors[0]].hasFolded) { // Return this slice to that single contributor finishingStacks[contributors[0]] += potSlice; // No need to add to pots, not "fully called" } else { // This chunk is fully called totalCalled += potSlice; pots.push({ bet: currentBet, potSize: potSlice, contributors, isUncalled: false, }); } } /** * STEP D: Apply rake only on the portion that was fully called. */ const everyoneFolded = activePlayerIndices.length === 1; const shouldTakeRake = !everyoneFolded && game.street !== 'preflop' && game.board.length > 0; const rakeBase = shouldTakeRake ? totalCalled : 0; const rake = typeof game.rake === 'number' ? game.rake // if an explicit amount is provided : shouldTakeRake ? Math.floor(rakeBase * (game.rakePercentage ?? 0)) : 0; game.finalPot = totalCalled - rake; /** * STEP E: Distribute each pot among eligible winners. */ let remainingRake = rake; pots.forEach(thePot => { let potSize = thePot.potSize; // Among contributors, only non-folded players can win const eligible = thePot.contributors.filter(c => !game.players[c].hasFolded); // Find best hand(s) let winners = []; for (const playerIndex of eligible) { if (winners.length === 0) { winners = [playerIndex]; } else { const cNew = game.players[playerIndex].cards.concat(game.board); const cWin = game.players[winners[0]].cards.concat(game.board); const cmp = compare(cNew, cWin); if (cmp > 0) { winners = [playerIndex]; } else if (cmp === 0) { winners.push(playerIndex); } } } // Take proportional rake from this pot const potRake = (potSize / totalCalled) * rake; potSize -= potRake; remainingRake -= potRake; // Split pot among winners if (winners.length > 0) { const share = Math.floor(potSize / winners.length); const remainder = potSize % winners.length; for (const w of winners) { game.players[w].rake += potRake / winners.length; finishingStacks[w] += share; winnings[w] += share; } // Give leftover remainder to first winner finishingStacks[winners[0]] += remainder; winnings[winners[0]] += remainder; } }); game.players.forEach((p, i) => { p.winnings = winnings[i]; p.stack = finishingStacks[i]; }); return finishingStacks; } //# sourceMappingURL=stacks.js.map