MCCFR Explained: The Algorithm Behind Competitive Poker Bots
MCCFR - Monte Carlo Counterfactual Regret Minimization - is how every serious poker bot of the last decade learned to play. How the algorithm works, and why it dominates imperfect-information games.
If you read any paper on a serious poker bot from the last decade - Cepheus, Libratus, DeepStack, Pluribus - one acronym keeps appearing: CFR, often in its Monte Carlo variant MCCFR.
What follows is an honest, working-engineer's primer: what CFR is, why it works for poker, where it breaks, and what the Monte Carlo variants do to fix it. Not a research paper - the formulas below are schematic, meant to build intuition, not to be copied into a solver. If you want rigor, read Zinkevich et al. (2007) for the original CFR or Lanctot et al. (2009) for MCCFR. This is the version you read first.
The problem CFR solves
Poker is an imperfect-information game. Unlike chess or Go, you don't know what cards your opponent has. That changes everything about how you learn to play.
In perfect-information games, the right algorithm is some form of minimax search: explore moves, evaluate positions, back up best responses. AlphaGo, Stockfish, the entire chess engine tradition - all variants of that.
In imperfect-information games, minimax doesn't work directly. You can't search a "best move" without considering that your opponent doesn't know your hand and you don't know theirs. The right solution concept is a Nash equilibrium: a strategy where neither player can improve by unilaterally switching. The challenge is computing one for a game as large as no-limit Hold'em, where the decision tree has on the order of 10^160 states.
That's where CFR comes in.
CFR in one paragraph
CFR is an iterative self-play algorithm. You and a clone of you both play the game many times. After each iteration, at every decision point you visited, you compute the regret for each action you didn't take - how much better you would have done if you'd taken that action instead. Then you adjust your strategy to play higher-regret actions more often. The key theoretical result: the average strategy across iterations converges to a Nash equilibrium (in two-player zero-sum games).
That's the entire algorithm in spirit. Two copies of an agent, playing forever, learning from imagined alternatives.
Walking through it more carefully
For a given information set (everything one player knows: their hole cards, the betting history, the board), CFR maintains:
regret[I][a]- cumulative regret for not playing actionaat info setIstrategy[I][a]- current probability of taking actionaatIavg_strategy[I][a]- running average ofstrategy[I][a]across iterations
On each iteration: 1. Traverse the game tree from the root. 2. At every info set, sample an action according to the current strategy. 3. When the hand ends, compute the actual payoff and the counterfactual payoff - what you would have gotten if you'd played each alternative action at each info set you visited. 4. Update each action's counterfactual regret - roughly, the counterfactual value of always taking action a at I minus the counterfactual value of the strategy you actually played there: regret[I][a] += v(I→a) - v(I). (Both terms are weighted by the probability the opponent's and chance's play even reach I - that's the "counterfactual" part, and it's what makes this CFR rather than a vanilla policy-gradient update.) 5. Recompute the strategy by regret matching: play each action in proportion to its positive regret - strategy[I][a] = max(0, regret[I][a]) / Σ max(0, regret[I][·]) - falling back to a uniform strategy at info sets where no action has positive regret yet. 6. Update the running average of the strategy.
After enough iterations, the average strategy converges. The intuition: actions you've "regretted not taking" get played more often; actions you've regretted taking get played less. Over enough iterations, the regret balances out and you're at equilibrium.
For tractable games - limit hold'em, abstracted no-limit hold'em - CFR-family methods converge in reasonable time. Cepheus essentially solved heads-up limit hold'em this way - technically with CFR+, a refinement (more on it below) - running on a cluster of roughly 4,000 CPUs for about two months.
Where vanilla CFR breaks
CFR walks the full game tree on every iteration. For no-limit Hold'em with realistic stack depths and bet sizings, the tree is astronomically large. You can't visit every info set on every iteration. You can't even store them all.
The two responses to this are abstraction (compress the game into a smaller game with fewer info sets - bucket hands, restrict bet sizes) and sampling (don't visit every node - sample a subset).
Abstraction is its own deep topic (we'll write about it). The sampling response is Monte Carlo CFR (MCCFR).
MCCFR: sampling the tree
The simplest MCCFR variant is outcome sampling. On each iteration, instead of walking the full tree:
- Sample a single trajectory (one path from root to leaf) under the current strategies.
- Update regrets only along that trajectory, using importance-sampling corrections to compensate for the fact that some info sets are visited less often than others.
A more efficient variant, external sampling, samples actions for the opponent and chance nodes (the cards dealt) but enumerates the player's own actions at each info set. This gives better convergence in practice.
Both variants have the same convergence guarantees in the limit, but they trade per-iteration cost for variance. Outcome sampling is cheap per iteration but noisy. External sampling is more expensive per iteration but stabler. Most modern bots use external sampling.
Sampling speeds up which nodes you touch; a parallel line of work sped up the update itself. CFR+ (the algorithm behind Cepheus) and later Linear / Discounted CFR reach a given accuracy in far fewer iterations than the original regret-matching update - typically an order of magnitude or more. The two tricks compose: you can sample the tree and use a discounted update. If you build a solver today, you almost certainly want one of these on top of MCCFR rather than the textbook regret-matching update above.
The practical result: where the original CFR might take days to converge on a tractable abstraction, these methods can produce strong play in hours of compute on commodity hardware.
Real-time search on top of MCCFR
Pluribus and Libratus both layered real-time search on top of an MCCFR-trained blueprint strategy. At decision time, instead of just looking up the trained strategy, the bot does a few hundred milliseconds of solver work on a depth-limited subtree, refining the policy locally for the actual state of the hand.
This is conceptually similar to what AlphaZero does in Go: a learned policy plus search at play time. The difference is that the search has to handle imperfect information - it can't just minimax. The algorithms for this (continual re-solving, safe subgame solving) are non-trivial and where most of the modern theoretical work has gone.
PluriBot uses this same pattern: a blueprint trained with external-sampling MCCFR, plus real-time subgame solving at the table. The Docker container Chipzen runs your bot in is sized to support this kind of real-time computation within a bounded think-time budget.
Practical: what you'd actually build
If you sat down to implement a respectable HU no-limit Hold'em bot from scratch today, the rough recipe is:
- A hand evaluator - fast 7-card lookup. Open source options exist (Rust crates, PokerKit in Python). Don't write your own from scratch.
- An abstraction - bucket hands by clustered equity and EHS (expected hand strength) features. Common sizes: 1,000 to 10,000 buckets per street.
- A bet-size abstraction - pick a small set of discrete bet sizings (e.g., 33%, 75%, 150% pot, all-in) and round real bets to the nearest.
- An MCCFR trainer - implement external-sampling MCCFR with the abstraction. Train for hours-to-days.
- A blueprint table - store the trained strategy by info set and bet-size bucket.
- A real-time policy - at play time, look up the blueprint, optionally refine with subgame solving for the actual stack/pot context.
That's a real engineering project - not a weekend. But every piece has public references and most have open-source starting points. The hardest part isn't the algorithm. It's the discipline of testing your abstractions against actual play, because a bug in your abstraction (over-bucketing similar hands, missing a bet size) shows up as an exploitable leak that a strong opponent will find.
Why this matters for competitive bots
MCCFR (with abstraction, sampling, and real-time search) is the dominant algorithm for competitive poker bots because it's the only thing we know that scales to no-limit Hold'em while preserving the theoretical convergence guarantees of CFR. Deep learning approaches exist - DeepStack used neural networks for value estimation - but the core search and update structure is still CFR-shaped.
If you're going to build a serious bot, you're building this. There are no shortcuts that produce equivalent strength. If someone tells you they have a "novel" poker AI that bypasses CFR entirely, ask them how it does at heads-up against Slumbot. The answer is usually: not very well.
The good news: every component of this pipeline has a generation of papers and open-source implementations to learn from. The state of the art is now public, even if the very best implementations remain proprietary. A determined engineer with a year and a serious laptop can build something Slumbot-class. The question Chipzen is built around is: once you have, where do you play it?
PluriBot, Chipzen's reference HU bot, is built on this same MCCFR + real-time-search foundation. Build one that beats it, heads-up, and find out where it ranks - chipzen.ai.