Heads-up no-limit Hold’em · open research

PokerAlpha

An open research and engineering project for game-theoretic decision-making under hidden information.

A custom Hold’em engine, CFR/MCCFR solvers, a bit-exact C++ training backend, exact reduced-game validation and a calibrated abstention layer. The question behind it: how do imperfect-information systems behave when their abstractions are wrong, their policies disagree, and compute is limited?

How it works

The answer is a distribution, and nobody tells you whether it is right

In poker you never see the other player’s cards, so good play is randomized and has to be learned rather than looked up. Heads-up no-limit Hold’em has around 10160 states, so any strategy is trained on a simplified abstraction of the game, and that simplification has an error of its own. PokerAlpha handles this in three steps.

01
Learn by self-play

Counterfactual regret minimization plays the game against itself and shifts weight toward actions it regrets not taking. 3 independent runs of 2,000,000 iterations each: one is the release strategy, and the disagreement between them is measured.

02
Check against exact answers

Small games (Kuhn, Leduc, single-street river subgames) can be solved exactly, so the solver and the abstraction are measured there instead of being assumed correct.

03
Refuse when unsure

A calibrated confidence gate looks at training visits and disagreement between runs, and shows no numbers at all for states it cannot trust.

Try it yourself

Three demos that run in your browser

Nothing to install. The first two compute live on your machine.

All demos and research explorers →

What the research found

Five results, with their limits

Each comes with a matched control and a confirmation run. Most are exact measurements in small or single-street games, which is where exact answers exist; none is a full-Hold’em theorem.

01

Error-guided abstraction refinement

Splitting the bucket with the largest measured error lowered exploitability 24% below a uniform refinement with identical capacity on independent confirmation (31% in both the discovery and double-capacity runs).

MethodExact 52-card river subgames, vectorized CFR+, capacity-matched uniform and random controls, 45 confirmation instances on new seeds and boards.

LimitSingle-street, perfect-recall domain. No Hold'em abstraction was retrained from it.

Write-up and data →

02

Profile-driven native optimization

A further 1.96× training throughput over the first native port, with an identical strategy digest.

MethodSampling profile, then A/B builds with identical Release flags run in alternating processes; parity suite and sanitizer self-test re-run.

LimitTraining throughput only. End-user analysis latency is a separate measurement.

Write-up and data →

03

Off-tree bet translation

With a release-like 33/75/150% menu, randomized pseudo-harmonic mapping cost 0.076 chips per hand against 0.601 for nearest-in-log-space, about 8× less. In a separate run, safe re-solving after the off-tree bet lowered pseudo-harmonic's cost from 0.061 to 0.048.

MethodExact best response in river subgames where the opponent may bet off-menu; 27 instances, repeated on independent seeds.

LimitReduced single-street subgames. The release still uses its nearest-in-log-space matcher; this is a recommendation, not a shipped change.

Write-up and data →

04

Decision-time resolving

Re-solving a subtree with a safe gadget closed 9% to 31% of a coarse blueprint's gap to equilibrium at a 1 s budget, and from 1 s up it was never worse than the blueprint by more than 1e-4 chips. Unsafe resolving was worse than the blueprint in most instances.

MethodRe-solve gadget with best-response counterfactual values (Burch et al. 2014), exact evaluation, budgets from 10 ms to 5 s.

LimitExact single-street subgames with a research blueprint. "Safe" is measured there, not guaranteed for full Hold'em; the release does not resolve.

Write-up and data →

05

Calibrated abstention

Confidence v2 accepts 1.68× as many decisions as v1 at a similar accepted wrong-action rate (3.0% vs 3.3%).

Method8,880 information states with exact EV-regret targets from nine solved games, evaluated leave-one-game-out. An ablation showed which signals carry it.

LimitCalibrated on small exact games; transfer to full Hold'em is an assumption, which is why flop and turn stay capped.

Write-up and data →

Charts and controls for these results → · the null and negative results are on the same page.

How it was engineered

From 21.9 to 1,625 iterations per second

Warm MCCFR training throughput on the release configuration. The two benchmark endpoints and the first port come from the same benchmark script; the three optimization steps are an A/B experiment between native builds, reported as cumulative speedups.

  1. Python reference21.9 it/s
    External-sampling MCCFR in NumPy. Correct, and still the reference everything else is checked against, but a 2M-iteration run would take more than a day.
  2. Move the whole hot path to C++17837 it/s · 38.11×
    Rules, dealing, evaluator, infoset encoder, regret matching, sampling and the recursion itself. Moving only helper functions was tried first and lost its gains to per-node boundary crossings.
  3. Profile the native trainer69.5% of CPU in one function
    macOS sample on a symbolized -O3 build. Almost every new river board rebuilt a 1,081-hand percentile table, and external sampling rarely sees the same board twice.
  4. Deduplicate river percentile construction1.60× (A/B)
    A hand's 7-card value depends only on canonical card ids (exact card for the one flush-capable suit, rank otherwise). Evaluate each distinct pair once and keep cumulative counts instead of sorting.
  5. Reuse evaluator board counts1.70× cumulative
    Rank and suit histograms for the five board cards are built once per table; each holding adds two cards.
  6. Flat open-addressing infoset index1.96× cumulative
    Linear probing with a splitmix64 hash replaces std::unordered_map on the per-node lookup.
  7. Fresh end-to-end benchmark1,625 it/s · 74.2×
    Same script and protocol as the original benchmark: three trials in fresh processes, warm median.
What is in the repository

Scope

Everything below is implemented in this repository. Off-the-shelf pieces are the foundations: NumPy, pybind11, FastAPI, Next.js, Electron and pytest.

Interfaces
Web labthis site; static, bundled research dataapps/web
Desktop appElectron with the engine bundled insideapps/desktop
Local APIFastAPI, binds 127.0.0.1 onlypoker_alpha/api
Screen observerresearch only, excluded from the productpoker_alpha/observer
Decisions
Decision engineranges, rollouts with paired SEs, solver / rollout / refusepoker_alpha/decision
Confidence gatevisits, seed disagreement, movement, street capspoker_alpha/decision/solver_gate.py
Learning
CFR · CFR+ · MCCFRone generic extensive-form Game interfacepoker_alpha/solvers
Abstractioncompact imperfect-recall encoder, river percentilespoker_alpha/abstraction
Trainingmulti-seed runs, exact resume, SHA-bound artifactsexperiments
Core
Game engineHold'em rules: side pots, min-raise reopening, all-inspoker_alpha/holdem
C++17 backendpybind11, GIL released, bit-exactcpp/src
Evaluatorhistogram 5-7 card evaluator, exhaustively pinnedpoker_alpha/poker
Research tooling
Exact gamesKuhn, Leduc, reduced Hold'em, river subgamespoker_alpha/games
Red team + benchstate search, confirmation runs, benchmark suitepoker_alpha/research

One real decision through the pipeline

HAND STATE

A♥K♦ on Q♠7♦2♣T♥3♥ · pot 5bb · checked to the hero on the river

OPPONENT BELIEF

modelled range after three passive streets: 7.64 bits, about 200 effective combos (a belief, not their cards)

TRUST CHECK

gate verdict ACCEPTED, 226 training visits, low cross-seed disagreement

LEARNED STRATEGY

check 83% · bet_33 9% · bet_150 5%

Precomputed engine output, stamped with the generating commit. Walk through the whole hand.

Where it fails

What the system does not know

The interesting part is not pretending uncertainty disappears. PokerAlpha measures where trust breaks down, and refuses to answer there.

Full Hold'em is not solved.

The release strategy is an abstract MCCFR average, not an equilibrium. A restricted local best response still finds at least ~100 bb/100 of exploitability.

The abstraction forgets.

The encoder is imperfect-recall, which voids CFR's convergence guarantee. In exact Leduc tests, one forgetful abstraction got worse with more training.

Flop and turn are lossy.

Exact flop and turn subgames show the encoder merging strategically different hands, so those streets are permanently capped at low confidence.

Some decisions stay uncertain.

Seeds disagree on many preflop spots and on jam / 3-bet defense. Doubling training twice removed only about a tenth of the disagreement each time, so the gate rejects there.

Screen recognition is experimental.

The screen observer has limited real PokerNow tuning but has not yet been independently validated on a held-out real-world dataset. It is excluded from the product.

No profitability claim.

Nothing here is evidence of real-money win rate. The project never automates actions on a poker client.

Trust map · Red team · System card

Get PokerAlpha

Source now, desktop builds later

The full source, experiments and result files are on GitHub. Desktop builds for macOS, Windows and Linux are planned; until signed releases exist, the app runs from source.

About
Cody Chen
University of Mississippi · Mathematics + Computer Science

Interested in quantitative research, high-performance systems, machine learning, and game-theoretic systems.