Design Snake and Ladder
OOP design for a snake-and-ladder game covering board setup with configurable snakes and ladders, dice rolling, player movement with overlap handling, and win detection.
The Problem
You are building a board game platform that needs to support classic Snake and Ladder for 2-4 players. The current prototype stuffs the entire game into one function: roll a die, move a player, check if they landed on a snake or ladder, repeat. Every time a new rule is added (chain reactions, multiple dice, special cells), the function grows by another 50 lines and breaks something else.
Snake and Ladder looks simple, but it tests several core OOP skills: modeling a board with configurable hazards and boosts, handling chain reactions when a ladder top lands on a snake head, designing a clean game loop, and supporting extensibility without rewriting the core. The interviewer wants clear entity separation, a movement pipeline that handles edge cases gracefully, and a design that absorbs follow-up twists.
Design the core classes for a Snake and Ladder game that handles board setup, dice rolling, player movement with snake/ladder resolution, and win detection.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What is the board size? Classic 10x10 with 100 cells, or should we support arbitrary sizes?"
Interviewer: "Start with 100 cells. Design it so the board size is configurable."
Good. Parameterize the board size rather than hardcoding 100. Store it as a constructor argument.
You: "How many players? Is there a minimum and maximum?"
Interviewer: "2 to 4 players. Each takes turns in order."
That tells you the player list is small and turn rotation is simple: cycle through a list.
You: "Single die or multiple dice? If multiple, do we sum the values?"
Interviewer: "Start with a single six-sided die. But design it so we could swap in multiple dice or a loaded die later."
This is a clear signal for a Dice abstraction. The game should not care how many dice or how the roll is computed.
You: "What happens if a player's roll would take them past cell 100? Do they bounce back, stay put, or need an exact roll?"
Interviewer: "They stay at their current position. You need an exact roll to land on 100 and win."
Critical rule. If a player is on cell 97 and rolls a 5, they do not move. Only a roll of 3 would land them on 100.
You: "Can chain reactions happen? For example, a ladder takes you to cell 50, and cell 50 is a snake head that sends you to cell 30?"
Interviewer: "Yes, chain reactions are allowed. Keep resolving until the player lands on a cell with no snake or ladder."
That means movement is a loop, not a single lookup. You resolve the position repeatedly until it stabilizes.
You: "Can a snake head and a ladder bottom exist on the same cell?"
Interviewer: "No. Each cell has at most one: a snake, a ladder, or nothing."
Good constraint. Simplifies the cell model. No ambiguity about which to apply first.
You: "Do we need undo, move history, or any UI rendering?"
Interviewer: "No UI, no undo. Just core game logic."
Perfect. You have clarified scope and ruled out unnecessary complexity.
Final Requirements
Functional Requirements:
- A board with N cells (default 100), configurable placement of snakes and ladders
- 2-4 players take turns in rotation order
- Each turn: roll the dice, move forward by the roll value
- If the new position has a snake head, the player slides down to the snake's tail
- If the new position has a ladder bottom, the player climbs to the ladder's top
- Chain reactions: keep resolving snakes/ladders until the position is stable
- If a roll would move the player past the last cell, the player stays in place
- The first player to land exactly on cell N wins
Non-Functional Requirements:
- Dice rolling is abstracted so different dice configurations can be swapped in
- Board configuration (snakes, ladders) is injected, not hardcoded
- The design supports adding new cell effects (penalty, bonus) without modifying existing classes
Out of Scope:
- UI rendering
- Persistence / database
- Undo / move history (covered in extensibility)
- Network multiplayer
- Timer / time limits per turn
Example Inputs and Outputs
Scenario 1: Normal move with a ladder
- Board: Ladder from cell 4 to cell 25. Player is on cell 1.
- Roll: 3. New position = 1 + 3 = 4. Cell 4 has a ladder bottom.
- Expected: Player moves to cell 25.
Scenario 2: Snake sends player down
- Board: Snake from cell 47 to cell 12. Player is on cell 44.
- Roll: 3. New position = 44 + 3 = 47. Cell 47 is a snake head.
- Expected: Player slides to cell 12.
Scenario 3: Overshoot, player stays put
- Board size: 100. Player is on cell 98.
- Roll: 5. New position would be 103, which exceeds 100.
- Expected: Player stays on cell 98. Turn passes to the next player.
Scenario 4: Chain reaction
- Board: Ladder from cell 10 to cell 30. Snake from cell 30 to cell 5.
- Player is on cell 7. Roll: 3. New position = 10 (ladder bottom).
- Ladder takes player to 30. Cell 30 is a snake head. Snake sends player to 5.
- Expected: Player ends on cell 5.
Try It Yourself
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your class diagram. Focus on two things: how you model snakes and ladders on the board, and how the movement pipeline resolves chain reactions. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Look at your requirements and pull out the nouns: game, board, player, snake, ladder, dice, cell. Each noun is a candidate entity.
A common mistake is cramming snake/ladder resolution, dice rolling, and turn management into one giant Game class. If you do that, every new feature (chain reactions, special cells, multiple dice) means editing the same class. Good design means each class has a single, clear job.
| Entity | Responsibility | Key attributes |
|---|---|---|
Game | The orchestrator. Manages turn rotation, drives the game loop, checks for a winner. | board, players, currentPlayerIndex, state |
Board | The playing surface. Owns cell mappings, resolves snakes and ladders. | size, jumpMap (position to position) |
Player | Data holder with a name and current position. No game logic. | name, position |
Snake | Value object: head position and tail position. Head > tail. | head, tail |
Ladder | Value object: bottom position and top position. Top > bottom. | bottom, top |
Dice | Rolls and returns a value. Abstracted so different dice configs can be swapped. | numberOfDice, faces |
GameState | Enum representing game progress. | IN_PROGRESS, FINISHED |
Notice we separated Game from Board. Game manages "whose turn is it" and "is the game over." Board manages "where does this position lead after resolving snakes and ladders." Merging them would violate SRP because turn logic and board topology change for different reasons.
Step 2: Define Relationships and Class Design
Class Diagram
Class Interface Derivation
Game (The Orchestrator)
Game is the entry point. Every turn flows through it. It rolls the dice, computes the new position, tells the Board to resolve jumps, updates the player, and checks for a winner.
Deriving state from requirements:
| Requirement | What Game must track |
|---|---|
| "2-4 players take turns" | A list of players and whose turn it is |
| "Roll the dice each turn" | A Dice reference |
| "Moves happen on a board" | A Board reference |
| "First to land on cell N wins" | Game state and the winner |
This gives us:
Game:
board: Board
players: List<Player>
dice: Dice
currentPlayerIndex: int
state: GameState // starts as IN_PROGRESS
winner: Player // null until someone wins
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Each turn: roll, move, resolve" | playTurn(): TurnResult |
| "Know whose turn it is" | getCurrentPlayer(): Player |
| "Check if game is over" | getState(): GameState |
| "Know who won" | getWinner(): Player |
The playTurn method is the core. It encapsulates one complete turn: roll, compute, resolve, update, check. Everything else is read-only.
Board (Topology + Jump Resolution)
Board owns the mapping from positions to positions. It does not know about players, turns, or dice. It only answers the question: "If someone lands on position X, where do they end up?"
Deriving state from requirements:
| Requirement | What Board must track |
|---|---|
| "N cells (default 100)" | The board size |
| "Snakes and ladders" | A jump map: position to position |
This gives us:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.