Leduc Poker

Summary

Leduc Hold’em uses a deck with only 6 cards. The deck is shuffled, and one card is dealt face-down to each player. Two rounds are played. On their turn, each player has the choice to fold, call, or raise the bid. If a player folds, the other player wins. Players can raise once per round. If both players call in the first round, the top card in the face-down deck is flipped up, and a second round of bidding begins, with the same choices for each player. If neither player folds in the second round, players reveal their cards, and the player that can make the highest hand wins their bet.

Rules of Play

These rules are summarized from https://www.sotets.uk/pgx/leduc_holdem/.

Leduc Hold’em is a simplified version of Poker. Texas Hold’em, Seven Card Stud, and Five Card Draw

Information Flow

Card Movement Graph

The diagram above visualizes information flow in Leduc Poker using a single random rollout in CardStock. Visibility is attached to card locations, so all cards in a location share the same visibility; information is revealed or concealed as cards move between locations and ownership changes.

Public locations are shown in green, hidden locations in yellow, private locations in blue, and memory locations in light gray. Locations containing cards with distinct backs are indicated with bold borders. Rectangles denote ownership (players or table), and directed edges show card movement between locations and resulting visibility changes. Taken information is shown with red dotted edges (public to private/hidden), and shared information with blue dashed edges (private cards transferred to another player).

Branching Factor

Branching Factor Graph

The figure above illustrates the branching factor (y-axis) for each decision point (x-axis) of Leduc Poker, summarized over 100 random games. We define a decision point as a specific moment at which a player must choose from a set of possible actions. Following this definition, a player can be part of multiple distinct decision points during one of their turns, resulting in successive actions.

Decision Spread

Decision Spread Graph

The figure above illustrates the normalized spread (y-axis) for each decision point (x-axis) of Leduc Poker, summarized over 100 games with all MCTS players. At each decision point, a player evaluates the potential moves in terms of their potential final score. The spread is the difference between the best estimated move and the worst estimated move. This value is then divided by the highest spread found during the game to calculate the normalized spread.

Lead History

Lead History Graph

The figure above illustrates the normalized average lead history (y-axis) for each decision point (x-axis) of Leduc Poker, summarized over 100 games with all MCTS players. At each decision point, a player evaluates the potential moves in terms of their potential final score. For the best estimated move, they record the estimated score for all players, assigning them normalized rank estimates, where 1 is first place, and 0 is last place. Across 100 games, these histories are grouped according to final game rank, and then averaged.

RECYCLE Code

The rules for Leduc Poker are coded in RECYCLE, a card game description language, to encourage standardized implementations across different systems.

You can also Download the code.

;; Leduc Poker
;; https://www.sotets.uk/pgx/leduc_holdem/

(game
 (setup  
  ;; Set up the players
  (create players 2)
  
  ;; Create the deck source
  (repeat 2 (create deck (game iloc STOCK) (deck (RANK (J, Q, K))))))
 
 (do ( 
  ;; Assign precedence to cards for showdown
  (set (game points PRECEDENCE)
   (((RANK : K) 3) ((RANK : Q) 2) ((RANK : J) 1)))

  ;; Shuffle the cards
  (shuffle (game iloc STOCK))

  ;; Each player adds one to the pot and is dealt a card
  (all player 'P 
   (do (
    (dec ('P sto CHIPS))
    (inc ('P sto BET))
    (move (top (game iloc STOCK)) 
          (top ('P iloc HAND))))))

  ;; in the first round, players can raise by 2, 
  ;; and only raise once per player.
  (set (game sto ROUND) 1)
  (set (game sto RAISE) 2)))
 
 ;; Play the game
 (stage player 
  (end (> (game sto ROUND) 2))

  ;; Play a round
  (stage player
   (end (or (any player 'P (== ('P str MOVE) FOLD))
            (all player 'P (== ('P str MOVE) CALL))))
            
   ;; players make a choice, FOLD, CALL, or RAISE
   (choice (
    ;; FOLD and resolve the bet
    (do (
     (set ((current player) str MOVE) FOLD)
     (inc ((previous player) sto CHIPS) ((current player) sto BET))
     (set ((current player) sto BET) 0)
     (inc ((previous player) sto CHIPS) ((previous player) sto BET))
     (set ((previous player) sto BET) 0)))

    ;; CALL and match current bet
    (do (
     (set ((current player) str MOVE) CALL)
     (dec ((current player) sto CHIPS) 
          (- ((previous player) sto BET) ((current player) sto BET)))
     (inc ((current player) sto BET) 
          (- ((previous player) sto BET) ((current player) sto BET)))))

    ;; RAISE the bet by the RAISE amount and the call amount
    ((!= ((current player) str MOVE) RAISE)
     (do (
      (set ((current player) str MOVE) RAISE)
      (dec ((current player) sto CHIPS) 
           (- ((previous player) sto BET) ((current player) sto BET)))
      (inc ((current player) sto BET) 
           (- ((previous player) sto BET) ((current player) sto BET)))
      (dec ((current player) sto CHIPS) (game sto RAISE))
      (inc ((current player) sto BET) (game sto RAISE))))))))

  ;; once the round is finished
  (do (
   ;; prepare for next round, increase the RAISE amount
   (inc (game sto ROUND))
   (inc (game sto RAISE) 2)

   ;; reveal a card after the first round
   ((<= (game sto ROUND) 2)
    (move (top (game iloc STOCK)) 
          (top (game vloc FACEUP))))

   ;; if no one has folded, reset the moves
   (all player 'P ((!= ('P str MOVE) FOLD)
    (set ('P str MOVE) NONE)))

   ;; keep the same player starting each round
   (cycle next (current player)))))

 ;; Game over?
 (do (
  ;; if showdown required
  ((all player 'P (!= ('P str MOVE) FOLD))
   (do (
    (all player 'P 
     (do (
      ;; Move the card to visible
      (move (top ('P iloc HAND))
            (top ('P vloc REVEAL)))
                    
      ;; calculate hand value, matching gets +10 points 
      ;; so they always beat high card
      ((== (cardatt RANK (top ('P vloc REVEAL))) 
           (cardatt RANK (top (game vloc FACEUP))))
       (set ('P sto POINTS) 
            (+ 10 (score (top ('P vloc REVEAL)) 
                   using (game points PRECEDENCE)))))

      ;; otherwise just score the high card
      ((!= (cardatt RANK (top ('P vloc REVEAL))) 
           (cardatt RANK (top (game vloc FACEUP))))
       (set ('P sto POINTS) 
            (scoremax (union ('P vloc REVEAL) (game vloc FACEUP)) 
             using (game points PRECEDENCE))))

      ((> ('P sto POINTS) (game sto MAXPOINTS))
       (set (game sto MAXPOINTS) ('P sto POINTS))))))
            
    ;; Determine who won
    (all player 'P
     ((== ('P sto POINTS) (game sto MAXPOINTS))
      (all player 'AP 
       (inc ('P sto CHIPS) ('AP sto BET))))))))))
 
 (scoring max ((current player) sto CHIPS)))