Design Snake and Ladder
Snake and Ladder is another game problem, a different flavor from tic-tac-toe: a fixed board encodes the rules, a deterministic event (the dice value) drives state forward, and multiple players share a single game object. Because the board is immutable after setup and the rules are self-contained, the design separates cleanly into a board that knows where snakes and ladders are, players that track position, and a game that sequences turns and delegates outcome decisions to the board.
See It in Action
The demo below runs the finished design on a 1–100 board: roll the die to advance the token, climb ladders, slide down snakes, and win by landing exactly on 100. A roll that would overshoot 100 leaves the token where it is. The rest of the article builds exactly this behavior.
Clarifying Requirements
A few questions about "design a Snake and Ladder game" fix the board and the turn order before any code. Functional requirements we commit to: the board runs from 1 to 100; before any rolls, players are placed at position 0 (off the board) and advance from there; the dice value is supplied by the caller rather than randomly generated, so the game is deterministic and testable; any number of players cycle through turns in order; landing on a snake head slides the player to its tail (a lower square); landing on a ladder bottom climbs the player to its top (a higher square); if a roll would push a player past 100, the player stays put rather than moving; and landing exactly on 100 ends the game with that player declared the winner.
The decision flow for a single roll:
Core Entities
Scanning the requirements for the nouns that carry state or enforce rules gives four candidates. Two of them — "snake" and "ladder" — are tempting to promote to classes. Before reading on, decide how a snake or a ladder should be represented: as its own object, or as something simpler on the Board.
Decision checkpoint
A snake connects a head square to a tail square; a ladder connects a bottom to a top. Should each snake and ladder be its own class, or modeled some other way?
A Board holds two maps: snake-head to tail, and ladder-bottom to top. After setup these maps
are read-only. The Board's apply method is the single entry point for rule lookup — callers
hand it a position and receive the final position plus a modifier tag.
A Player carries an id (1..n) and a current position on the board (0 at the start). It is a plain data holder; it does not decide anything about its own movement.
A Game orchestrates turns. It holds the board, the list of players, and a turn index
pointing to whichever player moves next. It cycles that index after every roll and delegates all
board questions to Board.
Designing the Classes
Board
Board's job is to answer one question: "if I land on square N, where do I end up and what
happened?" Two dictionaries capture all that knowledge — snakes mapping each head to its tail,
and ladders mapping each bottom to its top. The apply method checks snakes first, then
ladders, then returns the original position unchanged if neither applies. It also returns a
modifier string (" (snake)", " (ladder)", or "") so the caller can produce the right
output line without needing to know which map was checked.
apply queries both maps in O(1). The setup methods add_snake and add_ladder accept
individual entries rather than a batch so the caller can add snakes and ladders incrementally,
matching the command-driven test protocol.
A Player holds a position, so it is tempting to give it a move(value) method. But a move
depends on the overshoot rule, the board's snakes and ladders, and the turn order — none of which
the Player knows. Before reading on, decide where the movement logic belongs.
Decision checkpoint
Resolving a roll involves the overshoot-past-100 rule, the snake/ladder lookup, and advancing the turn. Should that logic live on Player or on Game?
Player
Player carries its id (1-based) and position (starting at 0). There is no behavior: the Game
tells the Player where to go by writing to position directly. Keeping Player as a plain data
holder avoids the temptation to put movement logic there — that logic belongs in Game where
the turn index and overshoot rule live.
Game
Game orchestrates turns. roll(value) is the single public method:
- Read the current player using
self.turnas an index. - Compute the tentative new position:
player.position + value. - Advance
self.turnimmediately — it must advance whether the move is an overshoot, a normal move, a snake, or a ladder. Only a win keeps the same turn index, but a finished game produces no further rolls, so it does not matter. - If
new_pos > 100, the player stays put and the method returns with the player's current position. - Delegate to
board.apply(new_pos)to get the final position and modifier. - Write the final position to the player.
- If
final_pos == 100, return the win message. Otherwise return the normal move message.
The turn is advanced before the overshoot check because the test expects turn cycling even
when a player overshoots. Advancing before writing to player.position is safe because
the final write happens only for non-overshoot moves.
Try It Yourself
Implement the classes above so that run_game(instructions) replays a command stream and
returns one output line per roll. The commands are:
| Command | Effect | Output |
|---|---|---|
players n | Create n players (ids 1..n) at position 0. Turns cycle 1, 2, ... n, 1, ... | none |
snake head tail | Register a snake from head (higher) to tail (lower) | none |
ladder bottom top | Register a ladder from bottom (lower) to top (higher) | none |
roll value | Current player advances by value. See rules above. | one line |
Output format for a roll:
Player N -> pos— plain move (including overshoot-stays)Player N -> pos (snake)— player slid down a snakePlayer N -> pos (ladder)— player climbed a ladderPlayer N wins!— player reached exactly 100