Side Pot Management
Relevant source files
The following files were used as context for generating this wiki page:
The Side Pot Management system is a critical module within the poker game engine responsible for handling betting scenarios where one or more players go all-in with fewer chips than their opponents. Its primary purpose is to ensure that pots are calculated and distributed fairly, allowing players to only win the amount of chips from each opponent that they were able to match. The system creates one or more "side pots" for bets made by players with larger stacks, which the all-in player is not eligible to win.
This system is encapsulated in lib/side-pot-manager.ts and is tightly integrated with the main lib/poker-engine.ts. The user-facing concept is explained in the Game Rules section of the UI.
Sources: lib/side-pot-manager.ts, components/rules-page.tsx:329-335
Key Data Structures
The side pot logic relies on data structures defined in types/poker.ts to track pots and player states.
Sources: types/poker.ts
The SidePot Interface
A SidePot object represents a single pot created during a betting round. The GameState can contain an array of these objects.
| Field | Type | Description |
|---|---|---|
amount | number | The total chip amount in this specific side pot. |
eligiblePlayers | string[] | An array of player IDs who are eligible to win this pot. |
cap | number | The maximum contribution per player for this pot. |
Sources: types/poker.ts:40-44
The PlayerState Interface
The PlayerState interface contains fields essential for calculating side pots, particularly bet and allIn.
| Field | Type | Description |
|---|---|---|
id | string | The unique identifier for the player. |
chips | number | The player's current chip stack. |
bet | number | The amount the player has bet in the current round. |
allIn | boolean | A flag indicating if the player has bet all their chips. |
Sources: types/poker.ts:30-38
The following diagram illustrates how these data structures are related within the overall GameState.
This diagram shows that a GameState contains multiple PlayerState and SidePot objects.
Sources: types/poker.ts:30-58
Core Logic
The core functionality resides in lib/side-pot-manager.ts, which exports two key functions: calculateSidePots and distributePots.
Sources: lib/side-pot-manager.ts
Calculating Side Pots
The calculateSidePots function is responsible for creating the structure of side pots based on the bets made by players in a round where at least one player is all-in.
The logic follows these steps:
- Filters for players who have placed a bet (
bet > 0). - Sorts these players by their bet amount in ascending order.
- Initializes a set of
remainingPlayerswho are still eligible for future pots. - Iterates through the sorted player bets. For each unique bet amount (a "cap"), it creates a new side pot.
- The amount for each side pot is calculated by taking the difference from the previous cap (
cap - previousCap) and multiplying it by the number of players still eligible for that pot. - If a player in the iteration is all-in, they are removed from the
remainingPlayersset, making them ineligible for any subsequent, larger side pots.
Sources: lib/side-pot-manager.ts:3-33
The following flowchart visualizes the calculateSidePots process.
Sources: lib/side-pot-manager.ts:3-33
// lib/side-pot-manager.ts:3-33
export function calculateSidePots(players: PlayerState[]): SidePot[] {
const sidePots: SidePot[] = []
// Get all players who made bets, sorted by bet amount
const playerBets = players
.filter((p) => p.bet > 0)
.map((p) => ({ id: p.id, bet: p.bet, allIn: p.allIn }))
.sort((a, b) => a.bet - b.bet)
if (playerBets.length === 0) return sidePots
let previousCap = 0
const remainingPlayers = new Set(playerBets.map((p) => p.id))
for (let i = 0; i < playerBets.length; i++) {
const currentBet = playerBets[i].bet
if (currentBet > previousCap) {
const cap = currentBet
const contributionPerPlayer = cap - previousCap
const eligiblePlayers = Array.from(remainingPlayers)
const potAmount = eligiblePlayers.length * contributionPerPlayer
sidePots.push({
amount: potAmount,
eligiblePlayers,
cap,
})
previousCap = cap
}
// If player went all-in, they can't win pots beyond this point
if (playerBets[i].allIn) {
remainingPlayers.delete(playerBets[i].id)
}
}
return sidePots
}
Distributing Pots
The distributePots function is called at the end of a hand (showdown) to award chips to the winner(s). It handles both the main pot and any side pots that were created.
The logic is as follows:
- Iterate through each
SidePot. - Filter the list of hand winners to find those who are also eligible for the current side pot.
- If there are eligible winners for the pot, divide the pot's amount evenly among them.
- Add the share to each winning player's chip stack.
- After all side pots are distributed, the main pot is divided among all hand winners and awarded.
Sources: lib/side-pot-manager.ts:35-59
// lib/side-pot-manager.ts:35-59
export function distributePots(sidePots: SidePot[], mainPot: number, winners: string[], players: PlayerState[]): void {
// Distribute each side pot to eligible winners
for (const pot of sidePots) {
const eligibleWinners = winners.filter((id) => pot.eligiblePlayers.includes(id))
if (eligibleWinners.length > 0) {
const share = Math.floor(pot.amount / eligibleWinners.length)
for (const winnerId of eligibleWinners) {
const player = players.find((p) => p.id === winnerId)
if (player) {
player.chips += share
}
}
}
}
// Distribute main pot to winners
if (mainPot > 0 && winners.length > 0) {
const share = Math.floor(mainPot / winners.length)
for (const winnerId of winners) {
const player = players.find((p) => p.id === winnerId)
if (player) {
player.chips += share
}
}
}
}
Integration with the Poker Engine
The side pot manager is invoked by the PokerEngine at key moments in the game flow, specifically when advancing the game phase.
Sources: lib/poker-engine.ts
-
Calculation Trigger: In the
advancePhasefunction, the engine checks if any player has gone all-in. If so, it callscalculateSidePotsto structure the pots before moving to the next phase (e.g., from flop to turn).
Sources: lib/poker-engine.ts:544-547 -
Distribution Trigger: When the game phase advances to
showdown(after theriver), the engine first callsdetermineWinners. Immediately after, it callsdistributePots, passing the calculated side pots, the main pot, the list of winner IDs, and the player state array.
Sources: lib/poker-engine.ts:570-573
The following sequence diagram shows the typical flow from a player action leading to an all-in, through pot calculation and final distribution.
This diagram shows how the PokerEngine uses the SidePotManager to calculate pots when an all-in occurs and distribute them at showdown.
Sources: components/poker-table.tsx:132-136, lib/poker-engine.ts:488-586, lib/side-pot-manager.ts
Conclusion
The Side Pot Management system is a self-contained but essential module that correctly handles complex all-in scenarios. By separating the logic for pot calculation (calculateSidePots) and distribution (distributePots), it maintains a clean architecture. Its integration into the PokerEngine's state machine ensures that pots are managed accurately at the correct moments, preserving the integrity and fairness of the game.