OOD / 03
Design a deck of cards
A standard 52-card deck, plus the bones of any card game. The trap is to overfit to Blackjack or Poker — the right design treats Game as a pluggable layer on top of Deck and Hand, so the deck doesn't know what game it's in.
1 · Clarifying questions
| One game or any game? | Any. The deck is reusable across games; one specific game implementation is the concrete demo. |
| Jokers? | Default no. Allow a with_jokers flag on the deck for games that need them. |
| Multiple decks? | Yes (Blackjack uses six). Deck takes a count. |
| Card value? | Game-dependent. A King is worth 13 in War, 10 in Blackjack, "high" in Poker. Value is not a property of Card; it's looked up by the game. |
| Shuffle quality? | Fisher-Yates. Not the "sort by random key" approach — it's biased. |
2 · Entities
Suitenum: HEARTS, DIAMONDS, CLUBS, SPADES.Rankenum: TWO..TEN, JACK, QUEEN, KING, ACE.Card— immutable.(suit, rank).Deck— owns a list of cards; supportsshuffle()anddeal().Hand— a collection of cards held by a player.Player— has a hand, can be dealt to.Game(interface) — manages players + deck, defines rules.
3 · Class sketch
enum Suit: HEARTS, DIAMONDS, CLUBS, SPADES
enum Rank: TWO, THREE, ..., TEN, JACK, QUEEN, KING, ACE
@dataclass(frozen=True)
class Card:
suit: Suit
rank: Rank
class Deck:
def __init__(num_decks=1, with_jokers=False):
self.cards = [Card(s, r) for s in Suit for r in Rank] * num_decks
if with_jokers: self.cards.extend([Joker(), Joker()] * num_decks)
def shuffle(rng=secrets):
# Fisher-Yates: for i = n-1..1, swap cards[i] with cards[randint(0, i)].
for i in range(len(self.cards) - 1, 0, -1):
j = rng.randint(0, i)
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
def deal(n=1) -> list[Card]:
if n > len(self.cards): raise EmptyDeck()
out, self.cards = self.cards[:n], self.cards[n:]
return out
def __len__(self): return len(self.cards)
class Hand:
cards: list[Card]
def add(card): self.cards.append(card)
def remove(card): self.cards.remove(card)
class Player:
name: str
hand: Hand
class Game(ABC):
players: list[Player]
deck: Deck
def play(): ...
class BlackjackGame(Game):
def value_of(card) -> int | tuple[int, int]: # Aces are 1 or 11
if card.rank in {JACK, QUEEN, KING}: return 10
if card.rank == ACE: return (1, 11)
return card.rank.value
def play(): ... # deal initial 2, loop hit/stand, settle
class PokerGame(Game):
RANK_ORDER = [...]
def best_hand(cards) -> HandRank: ... # high card .. royal flush
def play(): ...4 · Why Card doesn't carry a value
Putting value on the card binds the deck to one game. Card value is contextual — game decides. The deck stays game-agnostic; Game subclasses provide the value-of-card map.
Same logic for "ordering" — Poker's "Aces high" vs. War's "Aces low" lives in the game, not the card.
5 · Shuffling, done correctly
Fisher-Yates in place is O(n), produces a uniform permutation, and uses one pass. The naive alternatives all bias:
sort(by: lambda _: random())— biased (the comparator isn't a total order on random values; sort behaviour is undefined).for each card, swap with a random card(loop over all i, swap with random across 0..n-1) — produces a biased, non-uniform distribution; the correct bound is 0..i, not 0..n-1.
Use secrets (or any CSPRNG) for any game where money is on the line.
6 · Edge cases
- Deal more than remaining. Raise; never silently return a short list.
- Reshuffle mid-game. Some games shuffle the discard back in. Deck supports
add(cards)for this; the game owns the policy. - Card equality. If you allow multiple decks, two different physical 7♥ cards may need to be distinguishable. Add a hidden
deck_idfield, or accept that equality is by face value only. - Determinism for tests. Allow injecting an RNG seed.
7 · Extensions
- Networked play. Server holds the deck; clients see only their own hand.
Cardbecomes serialisable. - Provably fair shuffle. Server commits to a shuffle hash before play, reveals the seed after. Used in online casino sites.
- Game state persistence. Serialise
Game(players, hands, deck position) so a paused game can resume.
8 · What gets graded
- Did you separate card from card-value?
- Did you use Fisher-Yates (or at least flag the biased alternatives)?
- Did you propose a
Gameinterface, or did you only model Blackjack? - Did you make
Cardimmutable?
Found this useful?