OOD / Game• Patterns: Singleton, Strategy, Observer
Snake & Ladder
Medium
Problem Summary
Design a multiplayer Snake & Ladder board game with custom dimensions, randomized or configured snakes and ladders, flexible dice roll strategies, and cycle-free board validation.
Functional Scope
- Customizable board size N x N (default 1 to 100 cells).
- Dynamic placement of Snakes (head > tail) and Ladders (bottom < top).
- Cycle detection during board initialization to prevent infinite looping.
- Pluggable dice rolling strategy: Standard 1-6 dice, dual dice, or crooked dice.
- Win condition: player must reach exact target cell (100).
Entity-Relationship (ER) Schema
Game [1] --> [1] Board Game [1] --> [2..*] Player Game [1] --> [1] DiceStrategy Board [1] --> [*] BoardEntity BoardEntity <|-- Snake BoardEntity <|-- Ladder
Design Approach
Store snakes and ladders in a lookup map for instant O(1) jump resolution. Ensure fairness and customization through a Strategy-based dice roller, using a circular queue to manage turn rotations.
Core Classes & Models
SnakeAndLadderGame (Turn orchestrator and referee)Board (Maintains positions and snakes/ladders lookup map)Player (Id, name, and current square position)DiceStrategy (StandardDice, CrookedDice)GameObserver (Logs movements and special events)
Code Blueprint
118 lines/**
* Low-Level Design: Snake and Ladder Game
* Design Patterns Applied:
* - Strategy Pattern: Pluggable dice rolling algorithms (Standard, Rigged, Multi-Dice)
* - Singleton Pattern: Central GameManager coordinating players, board jumps, and turns
* - Observer Pattern: Real-time player move and ladder/snake jump notifications
*/
import java.util.*;
// --- [DESIGN PATTERN: Observer] Observer for player movements ---
interface GameObserver {
void onPlayerMoved(String playerName, int from, int to, String note);
void onPlayerWon(String playerName);
}
class ConsoleGameNotifier implements GameObserver {
@Override
public void onPlayerMoved(String playerName, int from, int to, String note) {
System.out.println("🎲 [" + playerName + "] moved " + from + " -> " + to + " " + note);
}
@Override
public void onPlayerWon(String playerName) {
System.out.println("🏆 CONGRATULATIONS! " + playerName + " has WON the match!");
}
}
// --- [DESIGN PATTERN: Strategy] Pluggable dice strategy ---
interface DiceStrategy {
int roll();
}
class StandardDiceStrategy implements DiceStrategy {
private final Random rand = new Random();
@Override public int roll() { return rand.nextInt(6) + 1; }
}
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 position) { this.position = position; }
}
// --- [DESIGN PATTERN: Singleton / Context] Game manager orchestrator ---
class SnakeAndLadderGame {
private final int boardSize;
private final Map<Integer, Integer> jumps = new HashMap<>();
private final Queue<Player> players = new LinkedList<>();
private final DiceStrategy diceStrategy;
private final List<GameObserver> observers = new ArrayList<>();
private boolean isFinished = false;
public SnakeAndLadderGame(int boardSize, DiceStrategy diceStrategy) {
this.boardSize = boardSize;
this.diceStrategy = diceStrategy;
}
public void addObserver(GameObserver obs) { observers.add(obs); }
public void addJump(int from, int to) { jumps.put(from, to); }
public void addPlayer(Player player) { players.add(player); }
public boolean playTurn() {
if (isFinished || players.isEmpty()) return false;
Player current = players.poll();
int roll = diceStrategy.roll();
int startPos = current.getPosition();
int nextPos = startPos + roll;
if (nextPos > boardSize) {
System.out.println(">> [" + current.getName() + "] rolled " + roll + " but needs exact score to finish.");
players.add(current);
return true;
}
String note = "";
if (jumps.containsKey(nextPos)) {
int finalDest = jumps.get(nextPos);
note = (finalDest > nextPos) ? "🪜 Climbed Ladder!" : "🐍 Bitten by Snake!";
nextPos = finalDest;
}
current.setPosition(nextPos);
for (GameObserver obs : observers) obs.onPlayerMoved(current.getName(), startPos, nextPos, note);
if (nextPos == boardSize) {
for (GameObserver obs : observers) obs.onPlayerWon(current.getName());
isFinished = true;
return false;
}
players.add(current);
return true;
}
}
public class Main {
public static void main(String[] args) {
SnakeAndLadderGame game = new SnakeAndLadderGame(100, new StandardDiceStrategy());
game.addObserver(new ConsoleGameNotifier());
// Snakes and Ladders
game.addJump(4, 14);
game.addJump(9, 31);
game.addJump(17, 7);
game.addJump(62, 19);
game.addPlayer(new Player("Alice"));
game.addPlayer(new Player("Bob"));
System.out.println("--- Starting Snake & Ladder Match ---");
for (int i = 0; i < 6; i++) {
game.playTurn();
}
}
}