Snake and Ladder is a classic turn-based board game played by two or more players on a grid, typically numbered from 1 to 100. Each player starts at cell 1 and takes turns rolling a dice to determine how many steps to move forward.
The game includes:
The first player to land exactly on the final cell (e.g., cell 100) is declared the winner.
Click to roll
Roll to start!
In this chapter, we will explore the low-level design of a snake and ladder game in detail.
Lets start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions and better define the scope of the system.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: Should the game support a standard 10x10 board with 100 cells, or should the board size be configurable?
Interviewer: For this version, let’s stick with the standard 10x10 board.
Candidate: Should the number and positions of snakes and ladders be fixed, or should they be configurable?
Interviewer: They should be configurable. The board should allow us to define the number and positions of snakes and ladders at initialization.
Candidate: How many players should the game support? Should it be limited to two, or should we support multiple players?
Interviewer: The game should support multiple players—at least two, but potentially more. Player turns should rotate in order.
Candidate: How should dice rolls be handled? Should we simulate a dice roll in the code or take it as input?
Interviewer: Let’s simulate dice rolls using random number generation from 1 to 6. No need for user input for the roll itself.
Candidate: What should happen if a player rolls a 6? Should they get another turn?
Interviewer: Yes, if a player rolls a 6, they get an extra turn immediately.
Candidate: Should a player roll exact number to land on cell 100, or can they overshoot and still win?
Interviewer: A player must land exactly on 100 to win. If the roll takes them beyond 100, their turn is skipped.
Candidate: Can multiple players occupy the same square at the same time?
Interviewer: Yes, more than one player can land on the same square. There's no interaction or conflict when this happens—no "bumping" or penalties.
After gathering the details, we can summarize the key system requirements.
After the requirements are clear, lets identify the core entities/objects we will have in our system.
Core entities are the fundamental building blocks of our system. We derive them by analyzing the functional requirements and extracting key nouns and responsibilities that naturally map to object-oriented abstractions such as classes, enums, or interfaces.
Let’s go through the functional requirements and identify the relevant entities:
This clearly suggests the need for a Board entity that maintains the overall structure and layout of the game. The board should internally manage the mapping of special cells (snakes and ladders) and allow querying for cell transitions.
This indicates two distinct entities: Snake and Ladder, each with a start and end position. These entities define how a player's position changes if they land on a particular cell.
This calls for a Player entity to encapsulate information like the player’s name, ID, and current position on the board.
We need a Dice entity to simulate random rolls between 1 and 6. The dice logic can also handle special behavior like giving an extra turn for a 6.
This behavior should be managed by a Game entity that acts as the central controller. It will handle player turns, dice rolls, player movement, snake/ladder transitions, win condition checks, and game state updates.
Board: Represents the 10x10 grid and manages snakes, ladders, and board navigation.Snake: Represents a snake with a head (start) and tail (end). If a player lands on the head, they are sent back to the tail.Ladder: Represents a ladder with a base (start) and top (end). If a player lands at the base, they move up to the top.Player: Represents a participant in the game, maintaining their current position and identifier (e.g., name or ID).Dice: Simulates dice rolls with random values between 1 and 6. Can include logic for handling an extra turn on rolling a 6.Game: Acts as the central controller. Manages game state, player turns, board movement, snake/ladder activation, and win condition.These core entities define the key abstractions of the game and will guide the structure of our low-level design and class diagrams.
GameStatusRepresents the lifecycle state of the game.
NOT_STARTED: The initial state before the game loop begins.RUNNING: The state when players are actively taking turns.FINISHED: The terminal state after a winner has been determined.PlayerEncapsulates all relevant information about a player
name: String: Name or identifier of the player.position: int: The current position of the player on the board (typically from 1 to 100).SnakeA specific type of BoardEntity that represents a snake. It holds a start and end position.
LadderA specific type of BoardEntity that represents a ladder. It holds a start and end position.
DiceA utility class responsible for simulating a dice roll.
BoardRepresents the game board's logic.
BoardEntityCan be introduced to generalize Snake and Ladder
GameThe central engine that orchestrates the entire game.
The relationships between classes define the overall architecture and how different components collaborate.
This relationship implies that an object is an integral part of another.
This relationship implies that an object contains other objects, but the contained objects can exist independently.
This is a more general relationship where one class uses another.
This relationship defines a hierarchy between classes.
The Game class acts as the central entry point. It serves as a Facade, providing a simplified interface (createGame, playGame) that hides the complex interactions between the game, board, players, and dice.
This is the most prominent design pattern used in the system. The Game class uses a nested static Builder class (Game.Builder) to construct Game objects.
The BoardEntity abstract class and its subclasses (Snake, Ladder) implicitly follow the structure of this pattern. The base class constructor handles the common logic of storing start and end positions, while the subclasses' constructors are responsible for the specific validation logic (e.g., start > end for Snake), effectively "filling in" a step of the creation template.
1class GameStatus(Enum):
2 NOT_STARTED = "NOT_STARTED"
3 RUNNING = "RUNNING"
4 FINISHED = "FINISHED"Defines the different states of the game:
NOT_STARTED: The game is initialized but not yet started.RUNNING: The game is actively being played.FINISHED: A player has won the game.1class Dice:
2 def __init__(self, min_value: int, max_value: int):
3 self.min_value = min_value
4 self.max_value = max_value
5
6 def roll(self) -> int:
7 return int(random.random() * (self.max_value - self.min_value + 1) + self.min_value)Encapsulates the behavior of a dice roll. The dice can be customized with a configurable minimum and maximum value (e.g., 1–6). The roll() method simulates a random roll.
1class Player:
2 def __init__(self, name: str):
3 self.name = name
4 self.position = 0
5
6 def get_name(self) -> str:
7 return self.name
8
9 def get_position(self) -> int:
10 return self.position
11
12 def set_position(self, position: int):
13 self.position = positionRepresents a player in the game. Each player has a name and a position on the board, starting from 0. The position is updated after every move.
BoardEntity (Abstract Base Class)1class BoardEntity(ABC):
2 def __init__(self, start: int, end: int):
3 self.start = start
4 self.end = end
5
6 def get_start(self) -> int:
7 return self.start
8
9 def get_end(self) -> int:
10 return self.endRepresents either a Snake or a Ladder. Each entity has a start and end position, and both subclasses (Snake and Ladder) impose validation on these.
Snake1class Snake(BoardEntity):
2 def __init__(self, start: int, end: int):
3 super().__init__(start, end)
4 if start <= end:
5 raise ValueError("Snake head must be at a higher position than its tail.")Represents a snake on the board. The head of the snake (start) must be positioned after the tail (end), enforcing a downward movement.
Ladder1class Ladder(BoardEntity):
2 def __init__(self, start: int, end: int):
3 super().__init__(start, end)
4 if start >= end:
5 raise ValueError("Ladder bottom must be at a lower position than its top.")Represents a ladder on the board. The bottom (start) must be before the top (end), enforcing an upward jump.
1class Board:
2 def __init__(self, size: int, entities: List[BoardEntity]):
3 self.size = size
4 self.snakes_and_ladders = {}
5
6 for entity in entities:
7 self.snakes_and_ladders[entity.get_start()] = entity.get_end()
8
9 def get_size(self) -> int:
10 return self.size
11
12 def get_final_position(self, position: int) -> int:
13 return self.snakes_and_ladders.get(position, position)Represents the game board.
size: Number of squares (typically 100).snakesAndLadders: Maps positions that trigger snakes or ladders to their destination square.getFinalPosition(): Returns the adjusted position after hitting a snake or ladder, if applicable.The main controller class that manages the gameplay loop, player turns, game rules, and winning condition.
1class Game:
2 class Builder:
3 def __init__(self):
4 self.board = None
5 self.players = None
6 self.dice = None
7
8 def set_board(self, board_size: int, board_entities: List[BoardEntity]):
9 self.board = Board(board_size, board_entities)
10 return self
11
12 def set_players(self, player_names: List[str]):
13 self.players = deque()
14 for player_name in player_names:
15 self.players.append(Player(player_name))
16 return self
17
18 def set_dice(self, dice: Dice):
19 self.dice = dice
20 return self
21
22 def build(self):
23 if self.board is None or self.players is None or self.dice is None:
24 raise ValueError("Board, Players, and Dice must be set.")
25 return Game(self)
26
27 def __init__(self, builder: 'Game.Builder'):
28 self.board = builder.board
29 self.players = deque(builder.players)
30 self.dice = builder.dice
31 self.status = GameStatus.NOT_STARTED
32 self.winner = None
33
34 def play(self):
35 if len(self.players) < 2:
36 print("Cannot start game. At least 2 players are required.")
37 return
38
39 self.status = GameStatus.RUNNING
40 print("Game started!")
41
42 while self.status == GameStatus.RUNNING:
43 current_player = self.players.popleft()
44 self.take_turn(current_player)
45
46 # If the game is not finished and the player didn't roll a 6, add them back to the queue
47 if self.status == GameStatus.RUNNING:
48 self.players.append(current_player)
49
50 print("Game Finished!")
51 if self.winner is not None:
52 print(f"The winner is {self.winner.get_name()}!")
53
54 def take_turn(self, player: Player):
55 roll = self.dice.roll()
56 print(f"\n{player.get_name()}'s turn. Rolled a {roll}.")
57
58 current_position = player.get_position()
59 next_position = current_position + roll
60
61 if next_position > self.board.get_size():
62 print(f"Oops, {player.get_name()} needs to land exactly on {self.board.get_size()}. Turn skipped.")
63 return
64
65 if next_position == self.board.get_size():
66 player.set_position(next_position)
67 self.winner = player
68 self.status = GameStatus.FINISHED
69 print(f"Hooray! {player.get_name()} reached the final square {self.board.get_size()} and won!")
70 return
71
72 final_position = self.board.get_final_position(next_position)
73
74 if final_position > next_position: # Ladder
75 print(f"Wow! {player.get_name()} found a ladder 🪜 at {next_position} and climbed to {final_position}.")
76 elif final_position < next_position: # Snake
77 print(f"Oh no! {player.get_name()} was bitten by a snake ðŸ at {next_position} and slid down to {final_position}.")
78 else:
79 print(f"{player.get_name()} moved from {current_position} to {final_position}.")
80
81 player.set_position(final_position)
82
83 if roll == 6:
84 print(f"{player.get_name()} rolled a 6 and gets another turn!")
85 self.take_turn(player)play() MethodControls the game loop:
takeTurn() MethodHandles the actual gameplay mechanics for each player's move:
Builder Inner ClassImplements the Builder pattern to construct a game instance in a flexible and readable way. Each component (board, players, dice) must be set before calling build().
1class SnakeAndLadderDemo:
2 @staticmethod
3 def main():
4 board_entities = [
5 Snake(17, 7), Snake(54, 34),
6 Snake(62, 19), Snake(98, 79),
7 Ladder(3, 38), Ladder(24, 33),
8 Ladder(42, 93), Ladder(72, 84)
9 ]
10
11 players = ["Alice", "Bob", "Charlie"]
12
13 game = Game.Builder() \
14 .set_board(100, board_entities) \
15 .set_players(players) \
16 .set_dice(Dice(1, 6)) \
17 .build()
18
19 game.play()
20
21
22if __name__ == "__main__":
23 SnakeAndLadderDemo.main()Demonstrates how to use the Game class to create and play a full game:
Builder API.In the object-oriented design of a Snake and Ladder game, which entity is responsible for storing the positions of all snakes and ladders?
No comments yet. Be the first to comment!