Go Fish

Summary

In Go Fish, players are dealt 5 cards from a shuffled deck. Anytime a player has 4 cards with the same rank, they discard those cards from their hand and record them as a match. On a player’s turn, they can ask any other player to give them cards that match the rank of one of the cards in the asking player’s hand. If the asked player has cards of that rank, they are passed to the asking player. If not, the asking player draws the top card from the deck. When a player makes a match, they get another turn, otherwise it is the next player’s turn. The game continues until one player has zero cards, or the end of the deck is reached. The player with the most matches wins the game.

Rules of Play

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

Go Fish is a quartet game.

Information Flow

Card Movement Graph

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

You can also Download the code.

;; Go Fish
;; https://www.pagat.com/quartet/gofish.html

(game  
 (declare (A, TWO, THREE, FOUR, FIVE, SIX, 
           SEVEN, EIGHT, NINE, TEN, J, Q, K) 'RANKS) 
 (setup  
  ;; Set up the players
  (create players 4)
  ;; Create the deck source
  (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 ( 
  (shuffle (game iloc STOCK))
  (all player 'P 
   (do (
    ;; Deal each player 5 cards
    (repeat 5 (move (top (game iloc STOCK)) 
                    (top ('P iloc HAND))))
                
    ;; Check for rank match on deal               
    (all (partition RANK ('P iloc HAND)) 'MATCH
     ((== (size 'MATCH) 4) 
      (do (
       (inc ('P sto SCORE))
       (repeat all
        (move (top 'MATCH)
              (top ('P vloc TRICKSTACK)))))))))))))
 
 ;; play until the end of the deck or someone has 0 cards
 (stage player 
  (end (or (== (size (game iloc STOCK)) 0) 
           (any player 'P (== (size ('P iloc HAND)) 0))))
     
  ;; For each rank you have
  ;; ask any other player for all cards of that rank
  (choice (
   (any 'RANKS 'R    
    ((> (size (filter ((current player) iloc HAND) 'MR 
               (== (cardatt RANK 'MR) 'R))) 0)
     (any (other player) 'OP
      (do (
       (set (game sto ASKED) (pid (current player)))
       (set (game str RANK) 'R) 
       (cycle current 'OP)))))))) ;; change to the asked player                        

  ;; asked player checks
  (do (
   ;; Makes a collection of cards that new player has in rank R
   (let (filter ((current player) iloc HAND) 'TR 
         (== (cardatt RANK 'TR) (game str RANK))) 'THEIRRANK 
    (do (						

     ;; If they have cards in rank R, pass them back
     ((> (size 'THEIRRANK) 0) 
      (repeat all
       (move (top 'THEIRRANK)
             (top (game vloc PASSING)))))))) 

   ;; switch back to the asking player
   (cycle current ((game sto ASKED) player))

   ;; if they had no cards to give, pick a card from the stock
   ((== (size (game vloc PASSING)) 0)
    (do (
     (move (top (game iloc STOCK))
           (top ((current player) iloc HAND)))

     ;; did you get what you asked for? It counts as a match
     ((== (cardatt RANK (top ((current player) iloc HAND))) 
          (game str RANK))
      (do (
       (move (top ((current player) iloc HAND))
             (top (game vloc PASSING)))
       (move (top (game vloc PASSING))
             (top ((current player) iloc HAND))) 
       (set ((current player) str MATCH) TRUE)))))))

   ;; if they passed you a card, take them and remember
   ((> (size (game vloc PASSING)) 0)
    (do (
     (repeat all
      (move (top (game vloc PASSING))
            (top ((current player) iloc HAND))))
     (set ((current player) str MATCH) TRUE))))
 
   ;; Check for rank match             
   (all (partition RANK ((current player) iloc HAND)) 'MATCH
    ((== (size 'MATCH) 4)  
     (do (
      (inc ((current player) sto SCORE))
      (repeat all
       (move (top 'MATCH)
             (top ((current player) vloc TRICKSTACK))))))))

   ;; determine if player goes again
   ((== ((current player) str MATCH) TRUE)
    (do (
     (set ((current player) str MATCH) FALSE)
     (cycle next (current player))))))))

 ;; Highest score wins
 (scoring max ((current player) sto SCORE)))