@creatiosoft/poker-odds-calculator
v1.0.0
Published
Monte Carlo poker odds calculator for NLH, PLO4, PLO5, and PLO6
Maintainers
Readme
@creatiosoft/poker-odds-calculator
A TypeScript package that calculates winning probability for poker hands across all major variants using Monte Carlo simulation.
Supports NLH, PLO4, PLO5, and PLO6.
Table of Contents
- Installation
- Quick Start
- API Reference
- Understanding the Result
- What is Monte Carlo Simulation?
- How We Use It
- Why Not Use poker-evaluator's Built-in Odds?
- Supported Variants
- Card Format
- Accuracy vs Iterations
Installation
npm install @creatiosoft/poker-odds-calculatorQuick Start
import { calculateOdds, calculateTableOdds } from '@creatiosoft/poker-odds-calculator';
// How likely am I to win with pocket Aces against 2 unknown opponents?
const result = calculateOdds({
variant: 'NLH',
holeCards: ['Ah', 'As'],
communityCards: [],
playerCount: 3,
iterations: 5000
});
console.log(result.winRate); // e.g. 0.73 → 73% chance to win outright
console.log(result.splitRates); // e.g. [{ rate: 0.01, ways: 2 }] → 1% chance to split with 1 playerAPI Reference
calculateOdds(input) — Single Player
Use this when only your cards are known and opponents' cards are random (the most common real-time table scenario).
calculateOdds({
variant: 'PLO4', // game variant (see Supported Variants)
holeCards: ['Ah','As','Kh','Kd'], // your hole cards
communityCards: ['2c','7h','Jd'], // board cards dealt so far (0–5)
playerCount: 3, // total players including you
iterations: 5000 // Monte Carlo cycles (optional, default 1000)
}): PlayerOddsReturns: PlayerOdds — your odds only (player 0).
calculateTableOdds(input) — All Players Known
Use this when all hole cards are visible — e.g. a run-it-out, a showdown analysis, or a training tool.
calculateTableOdds({
variant: 'NLH',
hands: [
['Ah', 'As'], // player 0
['Kh', 'Kd'], // player 1
['Qh', 'Qd'], // player 2
],
communityCards: ['2c', '7h', 'Jd'],
iterations: 5000
}): TableOddsReturns: TableOdds — an array of PlayerOdds, one per player, in the same order as hands.
Understanding the Result
PlayerOdds
{
winRate: 0.72, // fraction of simulated games this player wins outright
splitRates: [
{ rate: 0.03, ways: 2 }, // 3% of games end in a 2-way split
{ rate: 0.01, ways: 3 }, // 1% of games end in a 3-way split
]
}winRate
The fraction of iterations where this player won the entire pot outright — no ties, no splits. A value of 0.72 means the player wins 72 out of every 100 simulated hands.
splitRates
An array describing how often the pot is split and how many players share it.
| Field | Type | Meaning |
|---|---|---|
| rate | number | Fraction of iterations where the pot was split this way |
| ways | number | How many players split the pot (2 = heads-up tie, 3 = three-way tie, …) |
Example — reading a split result:
winRate: 0.68
splitRates: [{ rate: 0.04, ways: 2 }]This means:
- 68% of the time → you win the whole pot
- 4% of the time → you and exactly one other player tie (split 50/50)
- 28% of the time → you lose
Expected pot share formula:
expectedShare = winRate + Σ (splitRate.rate / splitRate.ways)
= 0.68 + (0.04 / 2)
= 0.68 + 0.02
= 0.70 → you expect 70% of the pot on averageTableOdds
{
players: [PlayerOdds, PlayerOdds, ...] // same order as the hands[] you passed in
}The winRate values across all players do not sum to 1 — splits account for the remainder. But winRate + splitContribution summed across all players does equal 1 (the whole pot is always distributed).
What is Monte Carlo Simulation?
Monte Carlo simulation is a technique for estimating probabilities by running a scenario thousands of times with random inputs and counting outcomes.
In poker, the number of possible combinations of cards is astronomically large. For example, dealing 2 unknown opponents on a blank board has over 1 billion possible runouts. Calculating exact probabilities by enumerating every combination would take too long for real-time use.
Monte Carlo solves this by sampling:
Instead of checking all 1 billion possibilities, deal out the remaining cards randomly 5,000 times. Count how often you win. 5,000 samples gives you a result accurate to within ~1%.
Analogy
Imagine you want to know the probability of rolling two dice and getting a sum of 7. You could:
- Exact method: list all 36 combinations, count the 6 that sum to 7 → 6/36 = 16.7%
- Monte Carlo: roll the dice 10,000 times, count the 7s → you'll get roughly 16.7%
For poker, the "exact method" is too slow for real-time use. Monte Carlo gives us a fast, accurate-enough answer.
How We Use It
Here is exactly what happens inside each call to calculateOdds:
Step 1 — Build the Remaining Deck
We start with a full 52-card deck and remove every card that is already known (your hole cards + any community cards already dealt).
Full deck (52 cards)
− your hole cards (2–6 cards)
− community cards already on board (0–5 cards)
= remaining deck (41–50 cards)Step 2 — Run N Iterations
For each iteration:
1. Shuffle the remaining deck using the Fisher-Yates algorithm (the same unbiased shuffle used in poker-evaluator).
2. Deal cards to fill in the unknowns:
- Each opponent gets hole cards dealt from the top of the shuffled deck
- The community cards are completed to 5 if not already
3. Evaluate each player's best hand using the correct rule for the variant (see below).
4. Find the winner — the player with the highest hand value wins. If multiple players have equal values, the pot is split.
5. Record the result — increment that player's win or split counter.
Step 3 — Calculate Rates
After all iterations:
winRate = wins[player] / totalIterations
splitRate = splits[player][ways-2] / totalIterationsHand Evaluation per Variant
This is where our package differs from poker-evaluator's built-in odds calculator:
| Variant | Rule | How we evaluate |
|---|---|---|
| NLH | Best 5 from any 7 cards | Pass all 7 cards to evalHand — it picks the best 5 |
| PLO4 | Exactly 2 hole + 3 board | Generate all C(4,2)=6 hole pairs × C(5,3)=10 board triples = 60 combinations, evaluate each, take the best |
| PLO5 | Exactly 2 hole + 3 board | C(5,2)=10 × C(5,3)=10 = 100 combinations per player |
| PLO6 | Exactly 2 hole + 3 board | C(6,2)=15 × C(5,3)=10 = 150 combinations per player |
For NLH the library's own evalHand does the work. For PLO we enumerate every legal 2+3 combination ourselves, call evalHand on each 5-card hand, and take the highest value.
Visual Walkthrough — One Iteration (PLO4, 2 Players)
Your hand : [Ah, As, Kh, Kd] (known)
Board : [2c, 7h, Jd] (known, flop)
Deck left : 45 cards
Shuffle deck → [Qc, 3h, 9s, Tc, 6d, ...]
Deal opponent: Qc, 3h (first 2 from shuffled deck)
Complete board: 9s, Tc (next 2 cards → board = [2c,7h,Jd,9s,Tc])
Evaluate YOUR hand (PLO4 — must use exactly 2 hole + 3 board):
Ah+As + 2c+7h+Jd → evalHand([Ah,As,2c,7h,Jd]) → one pair aces
Ah+As + 2c+7h+9s → evalHand([Ah,As,2c,7h,9s]) → one pair aces
Ah+As + 2c+7h+Tc → evalHand([Ah,As,2c,7h,Tc]) → one pair aces
Ah+Kh + 2c+7h+Jd → evalHand([Ah,Kh,2c,7h,Jd]) → high card
Ah+As + Jd+9s+Tc → evalHand([Ah,As,Jd,9s,Tc]) → one pair aces
... (60 total combos)
Best value for you → one pair aces (value: 3553)
Evaluate OPPONENT hand [Qc, 3h] (PLO4):
Qc+3h + 2c+7h+Jd → high card queen
... (60 combos)
Best value for opponent → high card queen (value: 1210)
3553 > 1210 → YOU WIN this iteration
wins[0]++Repeat 5000 times → winRate = wins[0] / 5000
Why Not Use poker-evaluator's Built-in Odds?
poker-evaluator ships winningOddsForPlayer and winningOddsForTable but they are NLH-only. Two lines in their source code make this impossible to reuse for PLO:
// poker-evaluator source — hardcoded 2 hole cards, no way to change
var card1 = numHands[p][0] ?? startingDeck[deckPosition++];
var card2 = numHands[p][1] ?? startingDeck[deckPosition++];
holeCards.push([card1, card2]); // ← always 2 cards, PLO needs 4/5/6
// NLH rule: best 5 from any 7
evalHand([...hand, ...communityCards]).value // ← wrong for PLO
// PLO rule: must use exactly 2 from hole + 3 from boardWe reuse evalHand (the fast O(1) lookup table) and write our own loop around it that correctly handles variable hole card counts and the PLO 2+3 constraint.
Supported Variants
| Variant | Hole Cards | Evaluation Rule |
|---|---|---|
| NLH | 2 | Best 5 from any 7 (hole + board) |
| PLO4 | 4 | Exactly 2 from hole + exactly 3 from board |
| PLO5 | 5 | Exactly 2 from hole + exactly 3 from board |
| PLO6 | 6 | Exactly 2 from hole + exactly 3 from board |
Card Format
<rank><suit>
Ranks : A K Q J T 9 8 7 6 5 4 3 2
Suits : s (spades) h (hearts) d (diamonds) c (clubs)
Examples:
'As' → Ace of Spades
'Kh' → King of Hearts
'Td' → Ten of Diamonds
'2c' → Two of ClubsAccuracy vs Iterations
Both winRate and splitRates become more accurate as you increase iterations. More iterations = smaller margin of error but slower response.
| Iterations | Typical error | Time (NLH) | Time (PLO4) | Recommended for | |---:|---:|---:|---:|---| | 1 000 | ±2–3% | ~13 ms | ~200 ms | Development / unit tests | | 5 000 | ±1% | ~44 ms | ~990 ms | Real-time display at the table | | 10 000 | ±0.5% | ~100 ms | ~2 000 ms | High-accuracy analysis | | 100 000 | ±0.1% | ~1 s | ~20 s | Pre-computation / batch jobs |
For real-time use in a running game, 5 000 iterations is the recommended balance — fast enough to feel instant for NLH, and accurate to within 1%.
For PLO at real-time speed, consider running the simulation in a Node.js worker thread so it does not block the main event loop.
Full Example
import { calculateOdds, calculateTableOdds } from '@creatiosoft/poker-odds-calculator';
// ── Example 1: Your hand only (most common) ──────────────────────────────────
const myOdds = calculateOdds({
variant: 'PLO4',
holeCards: ['Ah', 'As', 'Kh', 'Kd'], // double-suited aces and kings
communityCards: ['2c', '7h', 'Jd'], // flop is out
playerCount: 3, // you + 2 opponents
iterations: 5000
});
console.log(`Win rate : ${(myOdds.winRate * 100).toFixed(1)}%`);
myOdds.splitRates.forEach(s =>
console.log(`${s.ways}-way split: ${(s.rate * 100).toFixed(1)}%`)
);
// ── Example 2: Full table (all hands known) ──────────────────────────────────
const table = calculateTableOdds({
variant: 'NLH',
hands: [
['Ah', 'As'], // player 0 — pocket aces
['Kh', 'Kd'], // player 1 — pocket kings
],
communityCards: [], // preflop
iterations: 10000
});
table.players.forEach((p, i) =>
console.log(`Player ${i}: ${(p.winRate * 100).toFixed(1)}% win`)
);
// Player 0: 81.2% win
// Player 1: 17.8% win