Build Your Own Poker Equity Calculator in Python
Build a poker equity calculator in Python: Monte Carlo vs exact enumeration, the core loop, trial counts for accuracy, and porting to Android.
On this page · 5 sections
To build a poker equity calculator in Python you need four steps: represent the deck, deal the known cards, simulate the unknown ones many times, and count how often each hand wins. That’s the whole algorithm, and a working heads-up version fits in well under a hundred lines. Writing it yourself is one of the best ways to actually understand equity, because you can’t fake a result you had to compute from scratch.
The two algorithms
There are exactly two ways to compute equity, and picking between them is the first design decision.
Exact enumeration deals every remaining board, evaluates each one, and averages. It’s precise, and preflop heads-up it’s cheap enough to run instantly. But the number of runouts explodes as you add unknown cards and extra players.
Monte Carlo deals random runouts thousands of times and converges on the true equity as trials grow. It’s the practical choice for multiway pots and range-vs-range spots where full enumeration is far too slow.
A good calculator enumerates when the space is small and falls back to Monte Carlo when it isn’t.
The core loop
The structure is the same in any language:
1. Build a 52-card deck; remove all known cards (hole + board).
2. Repeat N times:
a. Shuffle the remaining deck.
b. Deal any missing board cards.
c. Evaluate each player's best 5-card hand.
d. Credit 1 to the winner, or split among ties.
3. equity = (wins + 0.5 x ties) / N for each player.
Step (c), the hand evaluator, is what makes or breaks it. A naive evaluator scores every five-card combination and works fine for study; production tools use lookup tables that turn a seven-card hand into an integer rank in a single array read.
A working Python version
Here’s the whole thing in Python — deck as a list of (rank, suit) tuples, random.sample for the runout, and a compact evaluator that scores any 5-card hand as a comparable tuple. It runs heads-up Monte Carlo in a few dozen lines:
import random
from collections import Counter
from itertools import combinations
RANKS = "23456789TJQKA"
SUITS = "cdhs"
DECK = [(r, s) for r in RANKS for s in SUITS]
def parse(cards): # "AhKh" -> [('A','h'), ('K','h')]
return [(cards[i], cards[i + 1]) for i in range(0, len(cards), 2)]
def score5(hand):
"""Rank a 5-card hand as a sortable tuple; higher is better."""
vals = sorted((RANKS.index(r) for r, _ in hand), reverse=True)
counts = Counter(vals)
# order ranks by (count, value) so pairs/trips sort ahead of kickers
by_count = sorted(vals, key=lambda v: (counts[v], v), reverse=True)
uniq = sorted(set(vals), reverse=True)
is_flush = len({s for _, s in hand}) == 1
is_straight = len(uniq) == 5 and uniq[0] - uniq[4] == 4
wheel = uniq == [12, 3, 2, 1, 0] # A-2-3-4-5
if wheel:
is_straight, by_count = True, [3, 2, 1, 0, -1]
shape = sorted(counts.values(), reverse=True)
if is_straight and is_flush: cat = 8
elif shape == [4, 1]: cat = 7
elif shape == [3, 2]: cat = 6
elif is_flush: cat = 5
elif is_straight: cat = 4
elif shape == [3, 1, 1]: cat = 3
elif shape == [2, 2, 1]: cat = 2
elif shape == [2, 1, 1, 1]: cat = 1
else: cat = 0
return (cat, by_count)
def best7(seven):
return max(score5(list(c)) for c in combinations(seven, 5))
def equity(hero, villain, board="", trials=100_000):
hero, villain, board = parse(hero), parse(villain), parse(board)
known = set(hero + villain + board)
live = [c for c in DECK if c not in known]
need = 5 - len(board)
wins = ties = 0
for _ in range(trials):
runout = board + random.sample(live, need)
h, v = best7(hero + runout), best7(villain + runout)
if h > v: wins += 1
elif h == v: ties += 1
return (wins + 0.5 * ties) / trials
print(equity("AhKh", "QsQd")) # ~0.46
Swap random.sample for itertools.combinations(live, need) and average over every runout instead of sampling to get exact enumeration — same evaluator, no randomness.
How many trials you need
Monte Carlo error shrinks with the square root of the sample, so it roughly halves each time you quadruple the trials:
| Trials | Approx. error (±) |
|---|---|
| 10,000 | ~0.5% |
| 100,000 | ~0.15% |
| 1,000,000 | ~0.05% |
For most study, 100,000 trials is the sweet spot — well within a fraction of a percent and near-instant on a modern CPU. Preflop heads-up, skip sampling and enumerate all 1,712,304 possible boards for an exact answer.
Validate against known spots
Test on results you can look up. AhAs versus KdKc preflop should return roughly 82% / 18%. AhKh versus QsQd — the classic race — should land near 46% / 54%. If your numbers match, your evaluator and loop are correct. If they don’t, the bug is almost always in tie handling or in forgetting to remove the known cards from the deck before dealing.
From hands to ranges
Real analysis is hand-versus-range. Extend the loop: before each trial, deal the villain a random combo from their range — say {QQ+, AKs} — rejecting any combo that collides with the known cards, then average across the range. That’s exactly what a solver does under the hood, and it’s where the project stops being a toy and starts teaching real poker math from odds and equity fundamentals.
If you later want speed, the two standard tricks are precomputed lookup tables (encode a hand as an integer, store its rank in a flat array so evaluation is one memory read) and bitmask representation (store a hand as a 52-bit integer and detect flushes and straights with bit tricks). You don’t need either to learn — a slow, obviously correct evaluator is better for that — but they’re why production calculators feel instant.
Python libraries that do the heavy lifting
If you’d rather not hand-roll the evaluator, several well-known Python packages already implement the fast lookup-table approach:
- treys — a small, pure-Python 7-card evaluator built on the classic Cactus Kev lookup tables; easy to read and a common teaching reference. deuces is the older library it forks from.
- eval7 — a C-accelerated evaluator with range parsing and a built-in
py_hand_vs_range_monte_carlohelper, so it covers the range work above out of the box. - PokerKit — a broader, actively maintained library (from the University of Toronto Computer Poker Research Group) that models full game state and many variants, not just equity.
For learning, write your own evaluator first; reach for one of these once you want speed or need range-vs-range equity without building the plumbing yourself.
The same Python core ports anywhere without touching the math — deck, deal, evaluate, tally. On Android you’d rewrite the loop in Kotlin (or wrap the compiled core via JNI) and add a card-picker UI, but the algorithm is identical. When you’re ready to see how others structured it, study the open-source poker tools and compare your output against a reference equity calculator in the wider poker tools ecosystem.
Frequently asked
How do you build a poker equity calculator in Python?
Represent the deck, remove the known cards, then either enumerate every possible runout for an exact answer or deal random runouts thousands of times for Monte Carlo. Each trial, evaluate every player's best five-card hand and credit the winner, then divide wins plus half of ties by total trials to get each hand's equity.
How many Monte Carlo trials do you need?
Accuracy improves with the square root of trials, so error roughly halves each time you quadruple the count. Around 100,000 trials puts you within a few tenths of a percent for most spots, and a million tightens it further. Preflop heads-up you can enumerate every board exactly instead of sampling.
Can you build a poker equity calculator on Android?
Yes — the algorithm is identical to Python: deck, deal, evaluate, tally. You port the hand evaluator and Monte Carlo loop into Kotlin or Java, or wrap a compiled core, keep the trial count modest so it stays responsive on a phone CPU, and add a card-picker UI on top.