Rummy

Summary

To play Rummy, shuffle a French deck and deal 10 cards to each player. Turn the top card of the remaining deck face-up to start the discard pile. Play alternates between players until one player holds no cards. On their turn, players draw the top card from the face-down deck or the face-up discard pile. If the player has at least three cards in a run of one suit in sequence, or a set of equal ranked cards, they make create a meld a play these cards face-up in between the players. Cards can then be added to existing melds on the table. Finally, the player discards a card from their hand. The player with the least points in their hand when the game ends wins.

Rules of Play

These rules are summarized from https://www.pagat.com/rummy/rummy.html.

Variants Used

We use the Block Rummy variant, and add the ending condition of an empty deck. This prevents random players, commonly used in Game AI, from getting stuck in a loop.

Rummy is a draw and discard game. The Rummy family of card games includes Gin Rummy, Canasta, and the tile game Mah Jong.

Information Flow

Card Movement Graph

The diagram above visualizes information flow in Rummy 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 Rummy, 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 Rummy, 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 Rummy, 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 Rummy are coded in RECYCLE, a card game description language, to encourage standardized implementations across different systems.

You can also Download the code.

;; Block Rummy
;; https://www.pagat.com/rummy/rummy.html

(game
 (setup
  (create players 2)
  (create deck (game iloc STOCK) 
   (deck (RANK (A, TWO, THREE, FOUR, FIVE, SIX, 
                SEVEN, EIGHT, NINE, TEN, J, Q, K))
         (COLOR (RED (SUIT (HEARTS, DIAMONDS)))
                (BLACK (SUIT (CLUBS, SPADES)))))))
 (do (
  ;; set scoring points for the cards
  (set (game points POINTS)
   (((RANK : K)    10) ((RANK : Q)    10) ((RANK : J)    10)
    ((RANK : TEN)  10) ((RANK : NINE)  9) ((RANK : EIGHT) 8)
    ((RANK : SEVEN) 7) ((RANK : SIX)   6) ((RANK : FIVE)  5)
    ((RANK : FOUR)  4) ((RANK : THREE) 3) ((RANK : TWO)   2)
    ((RANK : A)     1)))
  (set (game points SEQUENCE)
   (((SUIT : HEARTS)   100) ((SUIT : CLUBS) 200) ((SUIT : SPADES) 300)
    ((SUIT : DIAMONDS) 400) ((RANK : K)      13) ((RANK : Q)       12) 
    ((RANK : J)         11) ((RANK : TEN)    10) ((RANK : NINE)     9) 
    ((RANK : EIGHT)      8) ((RANK : SEVEN)   7) ((RANK : SIX)      6) 
    ((RANK : FIVE)       5) ((RANK : FOUR)    4) ((RANK : THREE)    3) 
    ((RANK : TWO)        2) ((RANK : A)       1)))

  ;; Shuffle the deck, give each player 10 cards
  (shuffle (game iloc STOCK))    
  (all player 'P
   (repeat 10 (move (top (game iloc STOCK)) (top ('P iloc HAND)))))
  (move (top (game iloc STOCK)) (top (game vloc DISCARD)))))

 ;; play the game until one player has no cards
 (stage player
  (end (or (any player 'P (== (size ('P iloc HAND)) 0))
           (== (size (game iloc STOCK)) 0)))

  (do (
   (set ((current player) str LAYOFF) DRAWING)))
        
  ;; Draw a card
  (choice (
   (move (top (game iloc STOCK)) 
         (top ((current player) iloc HAND)))
           
   (do (
    (move (top (game vloc DISCARD)) 
          (top((current player) iloc HAND)))
    (remember (top ((current player) iloc HAND))
              (top ((current player) mem DRAW)))))))

  (do (
   (set ((current player) str LAYOFF) MELDING)))

  ;; play a meld
  (choice (
   ;; run
   (any (runs all 3 ((current player) iloc HAND) using (game points SEQUENCE)) 'R
    (do (
     (repeat all (move (top 'R)
                       (top (game vloc RUN (game sto RUNCOUNT)))))
     (inc (game sto RUNCOUNT)))))  

   ;; set
   (any (partition RANK ((current player) iloc HAND)) 'P 
    ((>= (size 'P) 3)
     (do (
      (repeat all (move (top 'P)
       (top (game vloc SET (game sto SETCOUNT)))))
      (inc (game sto SETCOUNT))))))

   ;; do nothing
   (turn pass)))

  (do (
   (set ((current player) str LAYOFF) READY)))

  ;; layoff to other melds.
  (stage player 
   (end (== ((current player) str LAYOFF) DONE))

   (choice (
    ;; sets
    (any ((current player) iloc HAND) 'C 
     (any (indexed game vloc SET) 'S
      ((== (cardatt RANK 'C)
           (cardatt RANK (top 'S)))
       (do (
        (move 'C (top 'S))
        (cycle next (current player)))))))

    ;; runs
    (any ((current player) iloc HAND) 'C 
     (any (filter (indexed game vloc RUN) 'FS (> (size 'FS) 0)) 'S
      (do (
       ;; Add a card that is 1 smaller than smallest
       ((== (+ (score 'C using (game points SEQUENCE)) 1)
            (score (top 'S) using (game points SEQUENCE)))
        (do (
         (move 'C (top 'S))
         (cycle next (current player)))))

       ;; Add a card that is 1 bigger than biggest
       ((== (- (score 'C using (game points SEQUENCE)) 1)
            (score (bottom 'S) using (game points SEQUENCE)))
        (do (
         (move 'C (bottom 'S))
         (cycle next (current player)))))))))

    ;; or pass
    (set ((current player) str LAYOFF) DONE))))

  ;; Discard a card
  (choice (
   ;; if you have no cards left, pass
   ((== (size ((current player) iloc HAND)) 0) 
    (turn pass))
             
   (any ((current player) iloc HAND) 'C 
    ;; cannot discard the card you drew from the discard pile
    ((or (<= (size ((current player) iloc HAND)) 1) ;; unless it is your last card
         (== (size ((current player) mem DRAW)) 0)
         (and (> (size ((current player) mem DRAW)) 0)
              (!= 'C (top ((current player) mem DRAW)))))
     (move 'C (top (game vloc DISCARD)))))))

  ;; reset the draw tracker
  (do (
   ((> (size ((current player) mem DRAW)) 0)
    (forget (top ((current player) mem DRAW)))))))

 (scoring min (sum ((current player) iloc HAND) using (game points POINTS))))