Chapter 2

Kuhn Poker & CFR

From a single decision to a full game tree — Counterfactual Regret Minimization in action
Prerequisites: Chapter 1 Companion code: lesson_02_kuhn.py

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:

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

Kuhn Poker Rules
Deck: Three cards — Jack (J), Queen (Q), King (K).
Setup: Both players ante 1 chip. Each is dealt one card.
Play:
  1. Player 1 acts: Check or Bet (1 chip)
  2. 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)
  3. If Player 1 bet:
    • Player 2 can Fold (lose ante) or Call (1 chip)
Showdown: Higher card wins the pot. K > Q > J.

Despite its simplicity, Kuhn Poker has several features that make it a perfect teaching tool:

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:

Check Bet Check Bet Fold Call Fold Call P1 P2 P2 cc Showdown (2) P1 bc P2 folds (+1) bb Showdown (4) cbc P1 folds (-1) cbb Showdown (4) Player 1 Player 2 Terminal (payoff for P1)

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

Definition: Information Set
An information set groups all game states that are indistinguishable from a player's perspective. A player's strategy can only depend on their information set, not on hidden information they can't observe.

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
Key Insight: One Regret Matcher Per Info Set
In Chapter 1, we had one regret-matching instance for the entire game. In CFR, we have one regret-matching instance per information set. Kuhn Poker has 12 info sets, so we maintain 12 separate cumulative regret vectors and 12 strategy vectors. A real poker solver might have billions — but the principle is identical.

5. The CFR Algorithm

CFR was introduced by Zinkevich et al. (2007). The core idea is elegantly simple:

Algorithm: Vanilla CFR
Initialize: For each information set I, set R(I, a) = 0 for all actions a.

For each iteration t = 1, 2, 3, ...:
  1. For each possible deal of cards:
  2. 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).

Definition: Reach Probability
The reach probability π(h) of history h is the product of all strategy probabilities along the path to h:

π(h) = π0(h) · π1(h) · πc(h)
where πi(h) is the product of player i's strategy probabilities on the path, and πc(h) is the probability of the chance events (card deal).

In Kuhn Poker, πc = 1/6 for each deal (we handle this by averaging over all deals).
Definition: Counterfactual Value
The counterfactual value of action a at information set I for player i is:

vi(I, a) = Σh∈I π-i(h) · [value of subtree from h when playing a]
The key: we weight by the opponent's reach probability π-i(h), not the player's own. This is the "counterfactual" — we ask: "what would have happened if I tried to reach this information set?"
Why Counterfactual?
Using the opponent's reach probability (instead of the full reach) is not an arbitrary choice. It's what makes CFR work:

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:

RT ≤ ΣI ∈ Ii RT,+imm(I)
This decomposition is what allows CFR to minimize regret independently at each info set, then combine the guarantees. Without counterfactual weighting, this decomposition breaks.

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:

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
Critical: Reach Probability Weighting
Notice the asymmetry in the weights:
  • 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
Getting these weights backwards is a common implementation bug.

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].

Theorem: Kuhn Poker Nash Equilibrium
Player 1:
Info SetStrategyMeaning
K:Bet with prob 3αValue bet
Q:Always checkMarginal hand
J:Bet with prob αBluff
K:cbAlways callNuts
Q:cbCall with prob ⅓ + αBluff-catch
J:cbAlways foldAir
Player 2: (independent of α)
Info SetStrategyMeaning
K:cAlways betValue bet
Q:cAlways checkShowdown value
J:cBet with prob ⅓Bluff
K:bAlways callNuts
Q:bCall with prob ⅓Bluff-catch
J:bAlways foldAir
Game value: −1/18 ≈ −0.0556 per hand for Player 1.
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.

Universal Poker Principle
In equilibrium, the bluff-to-value-bet ratio is determined by the pot odds the opponent is getting. This principle scales directly to real poker: a player betting into a pot of P with a bet of B should bluff at a frequency of B / (P + 2B) to make the opponent indifferent to calling.

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)

Your Card
?
Bot's Card
?
Pot
2 chips
0
Hands Played
0.00
Your P/L (chips)
0.000
Avg P/L per hand
Press "Deal Hand" to start playing. You are Player 1 (first to act).
What To Try
  • 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

10000
Press "Train CFR" to run the simulation.

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.

The Path to a Real Solver
The CFR code you wrote for Kuhn Poker is structurally identical to a production poker solver. The only differences are:
  • 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
The regret-matching core at each info set? Exactly the same.

14. References

Kuhn, H. (1950). Simplified two-person poker. Contributions to the Theory of Games, 1, 97–103.
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) →
Zinkevich, M., Johanson, M., Bowling, M., & Piccione, C. (2007). Regret minimization in games with incomplete information. Advances in Neural Information Processing Systems (NeurIPS), 20, 1729–1736.
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) →
Tammelin, O., Burch, N., Johanson, M., & Bowling, M. (2015). Solving heads-up limit Texas hold'em. Science, 347(6218), 145–149.
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) →
Lanctot, M., Waugh, K., Zinkevich, M., & Bowling, M. (2009). Monte Carlo sampling for regret minimization in extensive games. Advances in Neural Information Processing Systems (NeurIPS), 22.
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) →