poker-ai

Architecture

A from-scratch implementation of a complete game-theoretically optimal (GTO) poker AI, built across five progressive stages — from foundational CFR to real-time search — following the architecture of Pluribus (Brown & Sandholm, Science, 2019).

Each stage exists to solve a specific limitation of the one before it. None of the five are optional shortcuts — each is a real bottleneck in scaling CFR from toy games to full No-Limit Hold’em.

Stage 1+2  Vanilla CFR + MCCFR      Leduc Hold'em (6-card toy game)
    ↓
Stage 3    Card Abstraction          k-means with Earth Mover's Distance
    ↓                                8 preflop / 12 flop / 12 turn / 8 river buckets
Stage 4    Deep CFR                  Neural network approximation of regret
    ↓                                Reservoir buffers + linear CFR weighting
Stage 5    Real-Time Search          Depth-limited subgame solving
                                     Blueprint oracle at leaf nodes

Stage 1+2: Counterfactual Regret Minimization

Vanilla CFR and Monte Carlo CFR (external sampling), implemented on Leduc Hold’em — a tractable 2-player, 6-card game that is the standard research testbed for poker AI algorithms.

Core idea: at each information set, compute counterfactual regret for each action not taken. Strategy at the next iteration is proportional to cumulative positive regret. The average strategy across iterations — not the final one — converges to Nash equilibrium.

MCCFR replaces full game-tree traversal with sampling, which is what makes the approach computationally tractable at all beyond toy games. This is the bottleneck Stage 1+2 solves: compute.


Stage 3: Card Abstraction

Full No-Limit Hold’em has roughly 10¹⁶⁰ game states — far beyond anything that can be enumerated directly. Card abstraction groups strategically similar hands into buckets so the resulting game tree becomes solvable.

EMD clustering groups hands by the distribution of equity across possible runouts, not just the mean — which is what distinguishes a made hand from a draw with an identical average equity. This is the bottleneck Stage 3 solves: state-space scale.


Stage 4: Deep CFR

Scales CFR to full NLHE by replacing the tabular strategy/regret store with neural networks that generalize across similar, even previously-unseen, game states.

This is the bottleneck Stage 4 solves: generalization — moving from a strategy table that only knows states it has explicitly seen, to a function that can estimate strategy for any state.


The technique that distinguishes Pluribus from earlier poker AI systems. At each decision point, the current subgame is re-solved in real time, using the Stage 4 blueprint network as a value estimator at leaf nodes rather than relying purely on the pre-trained strategy.

This is the bottleneck Stage 5 solves: blind spots — a pre-trained blueprint strategy that hasn’t seen a specific live situation in enough depth gets sharpened against what’s actually happening in that subgame, rather than falling back on a coarser pre-computed approximation.


How this compares to Pluribus

Property This repo Pluribus
Algorithm Deep CFR + depth-limited search Blueprint CFR + subgame solving
Players 2 (heads-up) 6
Bet abstraction 5 sizes 14 sizes
Traversals ~50k (demo) 12,400 × 1,000 iterations
Hardware Single CPU 64-core CPU
Training time ~30 min ~8 days

The architecture is faithful to the published papers; scale is the main difference. This is a heads-up (2-player) implementation — multi-way extension is a real, unsolved engineering challenge in this codebase, not a small extension of the current code.


Project structure

poker_ai/
├── main.py                     # Stage 1+2 entry point
├── train_abstracted.py         # Stage 3 entry point
│
├── cfr/
│   └── leduc_cfr.py            # Vanilla CFR for Leduc Hold'em
├── mccfr/
│   └── leduc_mccfr.py          # External sampling MCCFR
├── abstraction/
│   ├── equity.py               # MC equity, histograms, EMD
│   ├── card_abstraction.py     # Multi-street k-means clustering
│   └── abstracted_cfr.py       # MCCFR on abstracted game
├── deep_cfr/
│   ├── game_engine.py          # Full NLHE: stacks, all-ins, features
│   ├── networks.py             # AdvantageNet, StrategyNet, buffers
│   ├── traversal.py            # Deep CFR external sampling traversal
│   └── run_convergence.py      # Tight convergence run
└── stage5/
    ├── search.py                # Subgame solver, blueprint oracle
    └── evaluate.py               # Tournament evaluation framework

See Algorithms for implementation-level detail on the math behind each stage, and Results for full benchmark methodology.