Table of Contents
- Recap: From RPS to Poker
- Kuhn Poker: The Simplest Real Poker Game
- The Game Tree
- Information Sets
- The CFR Algorithm
- Counterfactual Values & Reach Probabilities
- Implementation Walkthrough
- The Nash Equilibrium of Kuhn Poker
- Poker Concepts in Miniature
- Interactive Demo: Play Kuhn Poker
- Interactive Demo: CFR Training
- Interactive Demo: Equilibrium Explorer
- Bridge to Larger Games
- References
1. Recap: From RPS to Poker
In Chapter 1, we built regret matching for Rock-Paper-Scissors — a normal-form game with one simultaneous decision. We showed that two regret-matching players converge to Nash Equilibrium, and we introduced exploitability as a convergence metric.
Poker is fundamentally different. It's an extensive-form game:
- Sequential: Players act in turns, not simultaneously
- Hidden information: Each player sees their own cards but not the opponent's
- Tree structure: The game unfolds through a tree of decision points
To handle this, we need a way to run regret matching at every decision point in the tree, coordinating updates across the entire game. That algorithm is Counterfactual Regret Minimization (CFR).
We'll develop it using Kuhn Poker — the smallest poker game that captures bluffing, value betting, and positional advantage.
2. Kuhn Poker: The Simplest Real Poker Game
Setup: Both players ante 1 chip. Each is dealt one card.
Play:
- Player 1 acts: Check or Bet (1 chip)
- If Player 1 checked:
- Player 2 can Check (showdown) or Bet (1 chip)
- If Player 2 bet: Player 1 can Fold (lose ante) or Call (1 chip)
- If Player 1 bet:
- Player 2 can Fold (lose ante) or Call (1 chip)
Despite its simplicity, Kuhn Poker has several features that make it a perfect teaching tool:
- Small enough to solve analytically (we know the exact Nash Equilibrium)
- Large enough to exhibit bluffing, value betting, and position effects
- Has 12 information sets — small enough to inspect every one
- Introduced by Harold Kuhn in 1950, the same era as Nash's equilibrium theorem
3. The Game Tree
The game tree shows every possible sequence of actions. Each path from root to leaf is a history — a complete record of how the hand played out.
We'll use a compact notation: c = check/fold (pass), b = bet/call.
| History | Meaning | Terminal? | Pot |
|---|---|---|---|
cc |
Both check → showdown | Yes | 2 (1+1) |
cb |
P1 checks, P2 bets → P1 must act | No | — |
cbc |
P1 checks, P2 bets, P1 folds | Yes | P1 loses 1 |
cbb |
P1 checks, P2 bets, P1 calls → showdown | Yes | 4 (2+2) |
bc |
P1 bets, P2 folds | Yes | P2 loses 1 |
bb |
P1 bets, P2 calls → showdown | Yes | 4 (2+2) |
Here's the full game tree visualized. Player 1 nodes are blue, Player 2 nodes are purple, and terminal nodes are green:
There are 5 terminal histories and 4 decision points. But with 3 possible card deals per player (and 2 possible opponent cards for each), the full tree has 6 × 5 = 30 terminal states. This is small enough to enumerate completely — no sampling or abstraction needed.
4. Information Sets
In poker: your information set = your card + the betting history. You can't see the opponent's card, so all states with different opponent cards but the same betting history belong to the same information set.
In Kuhn Poker, the information sets are:
Player 1's Information Sets (6 total)
| Info Set | Situation | Actions | Possible Opponent Cards |
|---|---|---|---|
J: |
Holding Jack, opening action | Check / Bet | Q or K |
Q: |
Holding Queen, opening action | Check / Bet | J or K |
K: |
Holding King, opening action | Check / Bet | J or Q |
J:cb |
Holding Jack, checked, P2 bet | Fold / Call | Q or K |
Q:cb |
Holding Queen, checked, P2 bet | Fold / Call | J or K |
K:cb |
Holding King, checked, P2 bet | Fold / Call | J or Q |
Player 2's Information Sets (6 total)
| Info Set | Situation | Actions | Possible Opponent Cards |
|---|---|---|---|
J:c |
Holding Jack, P1 checked | Check / Bet | Q or K |
Q:c |
Holding Queen, P1 checked | Check / Bet | J or K |
K:c |
Holding King, P1 checked | Check / Bet | J or Q |
J:b |
Holding Jack, P1 bet | Fold / Call | Q or K |
Q:b |
Holding Queen, P1 bet | Fold / Call | J or K |
K:b |
Holding King, P1 bet | Fold / Call | J or Q |
5. The CFR Algorithm
CFR was introduced by Zinkevich et al. (2007). The core idea is elegantly simple:
For each iteration t = 1, 2, 3, ...:
- For each possible deal of cards:
-
a. Traverse the game tree recursively
b. At each info set, compute the current strategy from regrets (exactly as in Chapter 1)
c. Compute the counterfactual value of each action
d. Update regrets: R(I, a) += π-i(h) · [v(I, a) - v(I)]
e. Accumulate strategy: Σ(I, a) += πi(h) · σ(I, a)
Output: The average strategy σ̄(I, a) = Σ(I, a) / Σa' Σ(I, a')
If you strip away the terminology, CFR is doing the same thing as Chapter 1 at every info set — accumulate regrets, derive strategy from positive regrets, report the average. The new ingredient is the counterfactual value and reach probabilities.
6. Counterfactual Values & Reach Probabilities
In a game tree, we can't simply compute "EV of action Rock against opponent's strategy" like we did in RPS. We need to account for the fact that reaching an information set depends on both players' strategies and the chance node (card deal).
In Kuhn Poker, πc = 1/6 for each deal (we handle this by averaging over all deals).
Theorem (Zinkevich et al., 2007): If each player's counterfactual regret at every information set is minimized, then the player's overall regret in the full game is also minimized. Formally:
Worked Example
Consider Player 1 at info set Q: (holding Queen, opening action).
The opponent could have Jack or King, each equally likely. Suppose Player 2's current strategy
at J:b is [Fold=1.0, Call=0.0] and at K:b is [Fold=0.0, Call=1.0].
If Player 1 bets:
- vs Jack: P2 folds → P1 wins 1 chip
- vs King: P2 calls → showdown, P1 loses 2 chips
Counterfactual value of betting = 1 · (+1) + 1 · (−2) = −1
(Both opponents have reach probability 1 since no player actions precede this node.)
If Player 1 checks, the game continues to Player 2's info set
(J:c or K:c), and the value depends on Player 2's check/bet strategy there.
7. Implementation Walkthrough
The entire CFR algorithm for Kuhn Poker fits in a single recursive function. Let's walk through the key pieces.
Data Structures
We need just two dictionaries, mapping each info set to an array of size 2 (one per action):
# Per info set: cumulative regrets and strategy sums
cumulative_regret = {} # info_set -> [regret_pass, regret_bet]
strategy_sum = {} # info_set -> [sum_pass, sum_bet]
Get Strategy (Same as Chapter 1)
def get_strategy(info_set):
# Identical to Chapter 1 — normalize positive regrets
regret = cumulative_regret[info_set]
positive = np.maximum(regret, 0)
total = positive.sum()
if total > 0:
return positive / total
return np.array([0.5, 0.5]) # uniform if no positive regrets
The CFR Traversal
def cfr(cards, history, reach_0, reach_1):
# Base case: terminal node
if is_terminal(history):
return terminal_payoff(cards, history) # from P0's perspective
player = len(history) % 2
info_set = cards[player] + ":" + history
strategy = get_strategy(info_set)
# Recurse into each action
action_values = [0.0, 0.0]
node_value = 0.0
for action in [0, 1]: # 0=pass, 1=bet
if player == 0:
action_values[action] = cfr(cards, history + action_char,
reach_0 * strategy[action], reach_1)
else:
action_values[action] = cfr(cards, history + action_char,
reach_0, reach_1 * strategy[action])
node_value += strategy[action] * action_values[action]
# Update regrets (weighted by OPPONENT's reach)
opponent_reach = reach_1 if player == 0 else reach_0
for action in [0, 1]:
regret = action_values[action] - node_value
if player == 1:
regret = -regret # P1 values are negated (zero-sum)
cumulative_regret[info_set][action] += opponent_reach * regret
# Accumulate strategy (weighted by OWN reach)
my_reach = reach_0 if player == 0 else reach_1
strategy_sum[info_set] += my_reach * strategy
return node_value
- Regrets are weighted by π-i (opponent's reach) — this is the counterfactual part
- Strategy sums are weighted by πi (own reach) — this gives the correct average strategy
Training Loop
for iteration in range(num_iterations):
# Traverse tree for every possible deal
for p0_card in [J, Q, K]:
for p1_card in [J, Q, K]:
if p0_card != p1_card:
cfr([p0_card, p1_card], "", 1.0, 1.0)
Each iteration traverses the full tree for all 6 possible deals. This is vanilla CFR — no sampling. For larger games, Monte Carlo CFR (MCCFR) samples deals instead of enumerating them, trading variance for speed.
8. The Nash Equilibrium of Kuhn Poker
Unlike RPS (which has a unique Nash Equilibrium), Kuhn Poker has a family of equilibria parameterized by a single value α ∈ [0, 1/3].
| Info Set | Strategy | Meaning |
|---|---|---|
K: | Bet with prob 3α | Value bet |
Q: | Always check | Marginal hand |
J: | Bet with prob α | Bluff |
K:cb | Always call | Nuts |
Q:cb | Call with prob ⅓ + α | Bluff-catch |
J:cb | Always fold | Air |
| Info Set | Strategy | Meaning |
|---|---|---|
K:c | Always bet | Value bet |
Q:c | Always check | Showdown value |
J:c | Bet with prob ⅓ | Bluff |
K:b | Always call | Nuts |
Q:b | Call with prob ⅓ | Bluff-catch |
J:b | Always fold | Air |
Player 2 has a positional advantage: acting second is worth ~5.6% of the ante.
All values of α give the same game value. CFR may converge to any equilibrium in this family.
9. Poker Concepts in Miniature
The Kuhn Poker equilibrium is a microcosm of real poker strategy. Let's unpack the key concepts.
9.1 Bluffing
Player 1 bets with Jack (the worst hand) with probability α. This is a pure bluff — the only way to win with Jack is to make the opponent fold. But why does the equilibrium include bluffing at all?
If Player 1 never bluffed, Player 2 would know every bet means King and would always fold. Then Player 1 would never get value from Kings. By bluffing sometimes with Jack, Player 1 forces Player 2 to call sometimes, which extracts value from King bets.
9.2 The Bluff-to-Value Ratio
Notice: P1 bluffs with probability α and value bets with probability 3α. The ratio is 1:3 (bluff : value). This is not arbitrary — it's calibrated to make Player 2 indifferent to calling with Queen.
When facing a bet, Player 2 with Queen risks 1 chip to win a pot of 3 chips (the 2 antes + 1 bet). The pot odds are 3:1, so Player 2 needs the bet to be a bluff at least 1/4 of the time. With P1 bluffing α and value betting 3α, the bluff frequency is α / (α + 3α) = 1/4 — exactly the pot odds.
9.3 Position
The game value is −1/18 for Player 1. Acting first is a disadvantage in Kuhn Poker (and in most poker variants). Player 2 gets to see Player 1's action before deciding, which provides valuable information.
This mirrors real poker: the button (last to act post-flop) has a significant strategic advantage. In the solver output, you'll see this reflected in the asymmetric equilibrium strategies.
9.4 Indifference
At Nash Equilibrium, Player 2 with Queen is indifferent to calling or folding when facing a bet — both actions give the same expected value. Similarly, Player 1's bluff and value bet frequencies are set to make Player 2 indifferent. This is a defining property of mixed-strategy Nash Equilibria: you mix between actions only when they're equally good.
10. Interactive Demo: Play Kuhn Poker
Before we look at the solver output, play a few hands of Kuhn Poker against the Nash Equilibrium strategy. You'll be Player 1 (first to act). Try bluffing, try value betting — and see how the equilibrium responds. Your long-run EV should approach −1/18 ≈ −0.056 per hand (the positional disadvantage).
You (P1) vs. Nash Equilibrium Bot (P2)
- Always check-fold with Jack: You'll lose the ante slowly but safely.
- Always bet with Jack (over-bluff): The bot calls with Queen 1/3 of the time — you'll get caught.
- Never bet with King (miss value): You win the pot but miss out on the extra chip from a call.
- Play the equilibrium: Match the Nash frequencies. Your average loss will converge to ~0.056 per hand — the cost of acting first.
11. Interactive Demo: CFR Training
Watch CFR learn the Nash Equilibrium of Kuhn Poker in real time. The chart shows exploitability dropping toward zero as strategies converge.
CFR Training on Kuhn Poker
12. Interactive Demo: Equilibrium Explorer
Explore how the strategies at each information set change as CFR converges. Run the training first, then inspect each info set.
Learned Strategies at Each Information Set
Run CFR training above first, then strategies will appear here.
13. Bridge to Larger Games
Kuhn Poker has 12 information sets. Real poker has billions. How does CFR scale?
12.1 Monte Carlo CFR (MCCFR)
Vanilla CFR traverses the entire tree every iteration. In NLHE, this is impossible. MCCFR samples random deals and traverses only those branches, giving unbiased regret estimates with much less work per iteration.
12.2 CFR+
Instead of allowing negative cumulative regrets (which slow convergence), CFR+ floors regrets at zero after each iteration. This was used to essentially solve Heads-Up Limit Hold'em.
12.3 Abstraction
For NLHE, the game tree is too large even for MCCFR. Abstraction groups similar hands into "buckets" (card abstraction) and limits the bet sizes (action abstraction), reducing the tree to a tractable size. Chapter 4 will cover this.
- Replace the 3-card deck with a 52-card deck
- Replace the single betting round with multiple streets
- Add card abstraction and action abstraction
- Switch from full traversal to Monte Carlo sampling
- Add engineering: compression, parallelism, disk-backed storage
14. References
About this paper
Harold Kuhn invented this simplified poker variant specifically so that its game-theoretic solution could be computed by hand. Motivated by Von Neumann and Morgenstern's observation that poker was a natural example of imperfect-information games but too complex for rigorous analysis, Kuhn stripped it down to three cards and a single betting round — the minimum needed to exhibit strategic deception.
Despite its simplicity, Kuhn showed that even this tiny game already captures the central aspects of poker strategy: bluffing (betting with a weak hand), value betting (betting with a strong hand), and the interplay between the two. He proved that Player 1 has infinitely many equilibrium strategies parameterized by a single value (the bluff frequency α), while Player 2 has a unique equilibrium. The game remains the standard first test case for poker AI algorithms 75 years later.
Read the paper (PDF) →About this paper
The paper that introduced Counterfactual Regret Minimization (CFR) — the algorithm implemented in this chapter and the foundation of all modern poker solvers. The key insight is a decomposition theorem: overall regret in an extensive-form game can be bounded by the sum of counterfactual regrets at each information set. This allows you to run simple regret matching independently at each decision point while guaranteeing convergence of the full strategy profile.
CFR immediately achieved practical impact, solving poker abstractions with up to 1012 states — two orders of magnitude beyond prior linear programming approaches. Strategies produced by CFR competed in the 2007 Computer Poker Competition and Man-vs-Machine Championship. The paper spawned an entire family of algorithms (CFR+, MCCFR, Discounted CFR) that remain the state of the art for imperfect-information game solving.
Read the paper (NeurIPS, PDF) →About this paper
This paper announced that Heads-Up Limit Texas Hold'em — with 3.19 × 1014 information sets — had been "essentially weakly solved." Their program Cepheus plays so close to the game-theoretic optimum that it cannot be statistically distinguished from perfect play over a human lifetime. This was the first imperfect-information game played competitively by humans to be solved.
The breakthrough came from CFR+, a seemingly minor variant of CFR that floors negative regrets to zero at each iteration rather than allowing them to accumulate. This change dramatically accelerates convergence. Cepheus was trained using 4,800 CPUs over 68 days. After publication, their website allowed visitors to play against Cepheus — over 39,000 users played 3.5 million hands, and Cepheus maintained a clear positive win rate.
Read the paper (Science) →About this paper
Vanilla CFR (as implemented in this chapter) traverses the entire game tree every iteration. For games like No-Limit Hold'em with 10160+ states, this is impossible. This paper introduced Monte Carlo CFR (MCCFR) — a general family of sampling-based CFR variants that explore only a subset of the tree each iteration while maintaining the same convergence guarantees in expectation.
The paper defines two key sampling schemes: outcome sampling (sample a single play-through per iteration) and external sampling (sample chance nodes and opponent actions, but enumerate the current player's actions). External sampling is particularly efficient — it requires only a constant-factor increase in iterations but achieves an order-of-magnitude reduction in cost per iteration, yielding an asymptotic speedup. MCCFR is what makes CFR practical for real poker variants and is the basis for all large-scale poker solvers.
Read the paper (PDF) →