Model a configurable Snake and Ladder board game with multiple players, pluggable dice strategies, and observer-based game-event notifications.
Overview
Snake and Ladder is a classic board game where players roll dice and advance tokens; landing on a ladder head teleports the player up, landing on a snake head sends them back. Board holds two maps: snakes (head → tail) and ladders (bottom → top). Player tracks its position. Dice is a Strategy interface supporting standard, weighted, or fixed (testing) implementations. Observer fires events for snake bites, ladder climbs, and game completion. GameController drives the turn loop: roll → move → check teleport → check win → notify observers → advance turn.
Requirements Analysis
Functional: 2–6 players, configurable snakes and ladders, roll dice to advance, detect win condition (exact landing on 100), support multiple dice. Non-functional: pluggable dice (swap standard for loaded dice in tests), extensible event notifications, board configuration injectable at construction (no hard-coding).
// Entities : Game, Board, Player, Dice, Snake, Ladder, Cell, GameController
// Patterns : Strategy (dice rolling), Observer (game events), Factory (board setup)Core Classes & Relationships
DiceStrategy interface has roll(). StandardDice uses Random; FixedDice accepts a list of preset values for testing. GameEvent enum: NORMAL_MOVE, SNAKE_BITE, LADDER_CLIMB, GAME_WON. GameObserver interface has onEvent(GameEvent, Player, int from, int to). Snake and Ladder are simple records storing start and end positions. Board validates positions and resolves teleports.
public interface DiceStrategy {
int roll();
}
public class StandardDice implements DiceStrategy {
private final Random rng = new Random();
private final int sides;
public StandardDice(int sides) { this.sides = sides; }
@Override public int roll() { return rng.nextInt(sides) + 1; }
}
public class FixedDice implements DiceStrategy { // deterministic for tests
private final Queue<Integer> values;
public FixedDice(List<Integer> vals) { this.values = new LinkedList<>(vals); }
@Override public int roll() { return values.isEmpty() ? 6 : values.poll(); }
}
public enum GameEvent { NORMAL_MOVE, SNAKE_BITE, LADDER_CLIMB, GAME_WON }
public interface GameObserver {
void onEvent(GameEvent event, Player player, int fromCell, int toCell);
}
public record Snake(int head, int tail) {}
public record Ladder(int bottom, int top){}Java Implementation
Board resolves a position to its final cell after snake or ladder teleport. Player holds current position. GameController runs the turn loop: roll, compute new position (capped at board size), resolve teleport, notify observers, check win. The loop continues until a player lands exactly on cell 100.
public class Player {
private final String name;
private int position = 0;
public Player(String name) { this.name = name; }
public String getName() { return name; }
public int getPosition() { return position; }
public void setPosition(int p) { this.position = p; }
}
public class Board {
private final int size;
private final Map<Integer, Integer> snakes; // head -> tail
private final Map<Integer, Integer> ladders; // bottom -> top
public Board(int size, List<Snake> snakes, List<Ladder> ladders) {
this.size = size;
this.snakes = snakes.stream().collect(Collectors.toMap(Snake::head, Snake::tail));
this.ladders = ladders.stream().collect(Collectors.toMap(Ladder::bottom, Ladder::top));
}
public int getSize() { return size; }
public int resolve(int position) {
if (snakes.containsKey(position)) return snakes.get(position);
if (ladders.containsKey(position)) return ladders.get(position);
return position;
}
public boolean isSnake(int pos) { return snakes.containsKey(pos); }
public boolean isLadder(int pos) { return ladders.containsKey(pos); }
}
public class GameController {
private final Board board;
private final List<Player> players;
private final DiceStrategy dice;
private final List<GameObserver> observers = new ArrayList<>();
private int currentIndex = 0;
private boolean gameOver = false;
public GameController(Board board, List<Player> players, DiceStrategy dice) {
if (players.size() < 2) throw new IllegalArgumentException("Need at least 2 players");
this.board = board; this.players = players; this.dice = dice;
}
public void addObserver(GameObserver o) { observers.add(o); }
public void playTurn() {
if (gameOver) { System.out.println("Game already over."); return; }
Player player = players.get(currentIndex);
int rolled = dice.roll();
int from = player.getPosition();
int target = from + rolled;
if (target > board.getSize()) {
System.out.printf("%s rolled %d but cannot move (would exceed %d).%n",
player.getName(), rolled, board.getSize());
advanceTurn();
return;
}
int resolved = board.resolve(target);
player.setPosition(resolved);
GameEvent event;
if (resolved == board.getSize()) event = GameEvent.GAME_WON;
else if (board.isSnake(target)) event = GameEvent.SNAKE_BITE;
else if (board.isLadder(target)) event = GameEvent.LADDER_CLIMB;
else event = GameEvent.NORMAL_MOVE;
observers.forEach(o -> o.onEvent(event, player, from, resolved));
if (event == GameEvent.GAME_WON) {
gameOver = true;
System.out.printf("%s wins!%n", player.getName());
return;
}
advanceTurn();
}
private void advanceTurn() { currentIndex = (currentIndex + 1) % players.size(); }
public boolean isGameOver() { return gameOver; }
public void play() { while (!gameOver) playTurn(); }
}Key Points to Remember
- 1Strategy pattern for dice means swapping StandardDice for FixedDice in unit tests requires zero changes to GameController.
- 2Storing snakes and ladders as Map<Integer, Integer> gives O(1) teleport resolution; no iteration over the board is needed.
- 3Observer decouples event reactions (logging, UI updates, sound effects) from the core game loop entirely.
- 4Capping movement at exactly board size (not wrapping) is a rules-correctness detail interviewers specifically probe.
Interview Questions
Sign in to ask AriaHow would you modify the design to support multiple dice per roll and arbitrary board sizes?
How would you detect and prevent an infinite game if a snake immediately follows every ladder?
How would you persist and resume an in-progress game state across server restarts?
Ask Aria about Design Snake and Ladder Game
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.