npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@creatiosoft/poker-odds-calculator

v1.0.0

Published

Monte Carlo poker odds calculator for NLH, PLO4, PLO5, and PLO6

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

npm install @creatiosoft/poker-odds-calculator

Quick 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 player

API 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)
}): PlayerOdds

Returns: 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
}): TableOdds

Returns: 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 average

TableOdds

{
  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] / totalIterations

Hand 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 board

We 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 Clubs

Accuracy 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