Snake and Ladder

Classic Board Game

Design the board game: a numbered board with snakes and ladders, a die, players who take turns, and a win rule. The interviewer watches how you split rules into classes and how you test code that rolls dice.

Overview

Players start off the board and race to the last cell. On a turn a player rolls a die and moves forward that many cells. Some cells are the head of a snake: land there and you slide down to its tail. Some are the foot of a ladder: land there and you climb to its top. The first player to reach the last cell wins.

The game is small, which is the point. Interviewers use it to see whether you can turn a page of rules into a handful of classes with one job each, keep the rules configurable instead of hard-coded, and write code that can be tested even though it depends on a random die. A strong answer names the classes in the first two minutes, draws the turn as a flow, and shows a scripted die before writing the game loop.

Scoping the problem

The rules vary from one family to the next, so ask before you design. Every answer below changes one line of code, not the shape of the design.

  • How many cells, and how many dice faces? — Both become constructor arguments, not constants — 100 cells, one six-sided die
  • Does rolling the highest face give another turn? — Decides whether the turn queue rotates after that roll — Yes, a flag
  • What if the roll goes past the last cell? — Stay put, bounce back, or win anyway — Stay put; the player must land exactly
  • Can a ladder land on a snake, or a snake on a ladder? — Chained jumps need a loop check — No; the board rejects such a setup
  • How many players? — Two or more; the turn order is a queue — 2 to 4
  • Who supplies the randomness? — A die you can script makes the game testable — The die takes a random generator you can seed or replace

Core entities

Give each rule its own class. Board knows where every jump starts and ends and nothing about turns. Dice knows how to roll and nothing about the board. Game owns the turn order and applies the rules of a move. A snake and a ladder are the same thing, a Jump from one cell to another, and only the direction differs.

Keeping both kinds of jump in one dict, keyed by the start cell, makes the move rule a single lookup: destination(cell) returns the jump's end if the cell starts a jump, else the cell itself. The direction is still recoverable from the two numbers, so nothing is lost.

One turn

take_turn is the whole game loop in one method. It rolls, checks for an overshoot, applies the jump, checks the win, then decides who moves next.

A worked game on a 20-cell board

The demo in the implementation plays this exact game with a scripted die. The board has a ladder from 3 to 11 and a snake from 17 to 5. Ann and Bob start at 0.

  • 1 — Ann — 3 — 3 — 11 — ladder
  • 2 — Bob — 6 — 6 — 6 — rolled a 6, keeps the turn
  • 3 — Bob — 5 — 11 — 11 — plain move
  • 4 — Ann — 5 — 16 — 16 — plain move
  • 5 — Bob — 6 — 17 — 5 — snake, but a 6 still earns another turn
  • 6 — Bob — 1 — 6 — 6 — plain move
  • 7 — Ann — 2 — 18 — 18 — plain move
  • 8 — Bob — 5 — 11 — 11 — plain move
  • 9 — Ann — 3 — 21 — 18 — past the end, stays on 18
  • 10 — Bob — 2 — 13 — 13 — plain move
  • 11 — Ann — 2 — 20 — 20 — exactly the last cell, Ann wins

Validating the board up front

A bad board should fail when it is built, not in the middle of a game. The Board refuses these setups with a ValueError.

  • A snake's tail is below its head; a ladder's top is above its foot — Otherwise the names mean nothing
  • Both cells are on the board — A jump that ends off the board has nowhere to send the player
  • Nothing starts on the last cell — The last cell ends the game; a jump there could never fire
  • No two jumps start on the same cell — The dict can hold only one; the second would silently replace the first
  • A jump may not end where another jump starts — Chains would need a loop check; refusing them keeps a move to one lookup

Testing a game that rolls dice

The Dice takes any object with a randint method. In production that is a random.Random instance. The Python docs describe the module-level functions as bound methods of one hidden Random instance, and say you can create your own instances so that generators do not share state. In tests you pass a Random with a fixed seed, which replays the same game every run, or a ScriptedDice that returns rolls from a list, which lets you script the exact ladder, snake, overshoot and win cases.

  • random.Random() with no seed — Real play
  • random.Random(7) — A reproducible full game, for example to assert that the same seed gives the same winner
  • ScriptedDice([3, 6, 5]) — Unit tests that target one rule at a time

What you will be able to do

  • Split the rules into Board, Dice, Player and Game and say what each one does not know.
  • Explain why snakes and ladders share one value type and one dict.
  • Draw the turn as a flow with the overshoot, jump, win and extra-turn decisions.
  • Make a random game testable by injecting the die.
  • List the board rules to validate before play starts.

Functional requirements

  • Players start off the board (position 0) and move toward the last cell
  • A turn is one roll of a die with a configurable number of faces; the player moves forward that many cells
  • Landing on a snake head moves the player to its tail; landing on a ladder foot moves the player to its top
  • A roll that would pass the last cell leaves the player where they are
  • Rolling the highest face gives the same player another turn (a flag; on by default)
  • The first player to land exactly on the last cell wins and the game ends
  • Two or more players take turns in a fixed order

Non-functional requirements

  • A move costs one dictionary lookup, whatever the number of snakes and ladders
  • An invalid board (a snake going up, two jumps on one cell, a jump off the board) is rejected at setup with a clear error
  • Board size, dice faces and the extra-turn rule are constructor arguments, not constants
  • The die is injectable so a game can be replayed from a seed or scripted in tests

Interview questions

  • Why keep snakes and ladders in one dict instead of two lists?
  • How would you add special cells, like a cell that skips your next turn or doubles your roll?
  • Why a deque for turn order rather than an index into a list?
  • How do you test code that depends on a random die?
  • What if a ladder ends on a snake head? Should the player slide again?
  • Does this need thread safety?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio