Go Fish

Turn-based card game

Design the card game Go Fish: a deck, hands grouped by rank, an ask that either takes cards or fishes, books of four, and a turn rule that decides who plays next.

Overview

Go Fish is played with a normal 52-card deck. On your turn you name a rank you already hold and ask one opponent for it. If they have cards of that rank they must hand over all of them and you ask again. If they have none, they say 'go fish' and you draw one card from the stock. Four cards of one rank make a book; you lay it down at once. The game ends when all thirteen books are on the table, and the player with the most books wins.

Interviewers like this problem because the rules are small but every rule has a place to live. The deck knows how to shuffle and draw. A hand knows which ranks it holds. A player owns a hand and a pile of books. The game owns the players, the stock, whose turn it is, and the rules. A strong answer keeps those four jobs apart, so the game never reaches into a hand to count cards and a hand never knows whose turn it is.

The part candidates get wrong is the turn rule. A turn does not always pass to the next player: a catch, a lucky fish or a completed book lets you go again. Get that right and write it as data the caller can read, not as a printed string.

Scoping the problem

Ask these questions before you write code; each answer changes a class.

  • Which rule set? Does a lucky fish give another turn? Does a book? — These are the branches in the turn logic — Bicycle rules: catch, lucky fish and book all keep the turn
  • How many players, and how many cards each? — Sets the deal — 2 to 5; seven cards each for 2 or 3 players, five for 4 or 5
  • What happens when a player has no cards? — An empty hand cannot ask — Draw one from the stock on your turn, then ask; skip if the stock is empty too
  • Human players only, or computer players too? — Decides whether move choice is part of the design — Humans; a computer player is a follow-up
  • Should the game be replayable in tests? — Decides how randomness enters the deck — Yes: inject the random source
  • One process, or a networked game? — Decides whether locks and an event log are needed — One process, single thread

Core entities

Turn is a small result object. The game returns what happened (the outcome, which cards moved, which books were made, and whether the same player goes again) so a text UI, a web UI or a test can each read it. The original version of this problem returned a sentence, which a test cannot check without string matching.

Who plays next

The turn rule is a short decision. A catch, a lucky fish or a new book keeps the turn; anything else passes it.

The trace runs the demo in the code below. Ann and Bob are dealt seven cards each from a fixed deck; the stock holds two cards, 7 of hearts on top and then 2 of diamonds.

  • 1 — Ann — K — Bob hands over K of clubs; Ann now has four kings — CAUGHT, book of K — Ann
  • 2 — Ann — 7 — Bob has none; Ann draws 7 of hearts — LUCKY_FISH — Ann
  • 3 — Ann — 5 — Bob has none; Ann draws 2 of diamonds — FISHED — Bob
  • 4 — Bob — 2 — Ann hands over 2 of spades and 2 of diamonds — CAUGHT — Bob
  • 5 — Bob — 9 — Ann has none; the stock is empty — STOCK_EMPTY — Ann

Storing a hand by rank

Every rule in the game is about ranks, never suits: do you hold a rank, hand over all of a rank, do you have four of a rank. So the hand is a dictionary from rank to the cards of that rank. Each operation touches one entry.

  • has(rank) — O(n) scan — O(1) lookup
  • take_all(rank) — O(n) filter, builds a new list — O(1) pop of one entry
  • complete books — O(n) count pass after every change — O(1): check the one rank that just grew
  • show the hand — in order received — grouped by rank, which is how players hold cards

Hands are small (a few dozen cards at the very most), so the speed difference is not the point. The point is that the dict matches the rules, so each method is one or two lines and hard to get wrong.

Deterministic tests

Deck takes a random.Random instance and shuffles with it. Production passes a fresh one; a test passes a seeded one, or an explicit list of cards. Python's docs say the same seed reproduces the same sequence run to run, as long as only one thread uses the generator. The random module is fine for a card game; it is not for security tokens, where the docs point to secrets.

What you will be able to do

  • Split the game into Deck, Hand, Player and Game and say what each one owns.
  • Write the ask as one method that returns a result object, not a printed sentence.
  • State the turn rule from the rules text and trace it on a small deal.
  • Explain why a hand is a dict keyed by rank and what each operation costs.
  • Answer the follow-ups: computer players, rule variants, a stuck end game, and many games on one server.

Functional requirements

  • Deal seven cards each to 2 or 3 players and five each to 4 or 5, one card at a time from a shuffled deck
  • The current player names a rank they hold and one opponent; asking for a rank they do not hold, or asking themselves, is refused
  • If the opponent holds the rank they hand over every card of it and the asker goes again
  • Otherwise the asker draws one card from the stock; drawing the asked rank keeps the turn, any other card passes it
  • Four cards of one rank are laid down as a book at once; making a book keeps the turn
  • A player with no cards draws one from the stock at the start of their turn; with an empty stock they are skipped
  • The game ends when the stock and every hand are empty; the player or players with the most books win

Non-functional requirements

  • has, take_all and book detection are O(1) per rank because the hand is keyed by rank
  • The random source is injected so a game can be replayed exactly in tests
  • Rule violations raise ValueError; the game state does not change on a refused ask
  • ask() returns a Turn result object that any UI or test can read; nothing is printed

Interview questions

  • Why key the hand by rank instead of keeping a list of cards?
  • How would you add computer players?
  • When should books be detected: after every card, or at the end of a turn?
  • Can the game get stuck? How does it end?
  • How would you support rule variants without rewriting the game?
  • Two players are in different browsers. What changes on the server?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio