Design (LLD) Snake and Ladder Game - Machine Coding

Design (LLD) Snake and Ladder Game - Machine Coding

Features Required:

  1. Board Initialization: A board with a predefined size.

  2. Players: Multiple players can play the game.

  3. Snakes and Ladders: Random placement of snakes and ladders.

  4. Dice Roll: A player can roll a dice and move accordingly.

  5. Game Logic: Movement according to dice roll and handling snakes and ladders.

Design Patterns Involved:

  1. Singleton Pattern: Used for creating a single instance of the board to ensure that all players interact with the same board.

  2. Factory Pattern: Used to create snakes and ladders on the board.

  3. Observer Pattern: Used to notify players about their turns.

  4. Strategy Pattern: Used to define the dice rolling strategy, which can be changed dynamically.

  5. Command Pattern: Used to encapsulate a request as an object to parameterize clients with queues, requests, and operations.

Detailed Implementation

Singleton Pattern for Board

public class Board {
    private static Board instance;
    private int size;
    private Map<Integer, Integer> snakes;
    private Map<Integer, Integer> ladders;

    private Board(int size) {
        this.size = size;
        this.snakes = new HashMap<>();
        this.ladders = new HashMap<>();
    }

    public static Board getInstance(int size) {
        if (instance == null) {
            instance = new Board(size);
        }
        return instance;
    }

    public int getSize() {
        return size;
    }

    public Map<Integer, Integer> getSnakes() {
        return snakes;
    }

    public Map<Integer, Integer> getLadders() {
        return ladders;
    }

    public void addSnake(int start, int end) {
        snakes.put(start, end);
    }

    public void addLadder(int start, int end) {
        ladders.put(start, end);
    }
}

Factory Pattern for Creating Snakes and Ladders

public class ObstacleFactory {
    public static void createSnakes(Board board, List<int[]> snakes) {
        for (int[] snake : snakes) {
            board.addSnake(snake[0], snake[1]);
        }
    }

    public static void createLadders(Board board, List<int[]> ladders) {
        for (int[] ladder : ladders) {
            board.addLadder(ladder[0], ladder[1]);
        }
    }
}

Observer Pattern for Player Turns

public interface Observer {
    void update(String message);
}

public class Player implements Observer {
    private String name;
    private int position;

    public Player(String name) {
        this.name = name;
        this.position = 0;
    }

    public String getName() {
        return name;
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    @Override
    public void update(String message) {
        System.out.println(name + ": " + message);
    }
}

public class Game {
    private List<Player> players;
    private int currentPlayerIndex;

    public Game() {
        this.players = new ArrayList<>();
        this.currentPlayerIndex = 0;
    }

    public void addPlayer(Player player) {
        players.add(player);
    }

    public void notifyPlayers(String message) {
        for (Player player : players) {
            player.update(message);
        }
    }

    public Player getCurrentPlayer() {
        return players.get(currentPlayerIndex);
    }

    public void nextTurn() {
        currentPlayerIndex = (currentPlayerIndex + 1) % players.size();
    }
}

Strategy Pattern for Dice Roll

public interface DiceStrategy {
    int rollDice();
}

public class NormalDice implements DiceStrategy {
    private Random random;

    public NormalDice() {
        this.random = new Random();
    }

    @Override
    public int rollDice() {
        return random.nextInt(6) + 1;
    }
}

// The Loaded Dice is an example of a biased dice that doesn't generate numbers randomly. 
// Instead, it skews the probabilities, typically favoring higher numbers, making it "loaded" to produce certain outcomes more frequently.
public class LoadedDice implements DiceStrategy {
    private Random random;

    public LoadedDice() {
        this.random = new Random();
    }

    @Override
    public int rollDice() {
        return random.nextInt(3) + 4;  // Rolls between 4 and 6
    }
}

Command Pattern for Game Moves

public interface Command {
    void execute();
}

public class MoveCommand implements Command {
    private Player player;
    private int steps;
    private Board board;

    public MoveCommand(Player player, int steps, Board board) {
        this.player = player;
        this.steps = steps;
        this.board = board;
    }

    @Override
    public void execute() {
        int newPosition = player.getPosition() + steps;
        if (newPosition > board.getSize()) {
            newPosition = board.getSize();
        }
        if (board.getSnakes().containsKey(newPosition)) {
            newPosition = board.getSnakes().get(newPosition);
        } else if (board.getLadders().containsKey(newPosition)) {
            newPosition = board.getLadders().get(newPosition);
        }
        player.setPosition(newPosition);
    }
}

Main Game Class

public class SnakeAndLadderGame {
    public static void main(String[] args) {
        Board board = Board.getInstance(100);
        ObstacleFactory.createSnakes(board, Arrays.asList(new int[][]{{16, 6}, {48, 26}, {49, 11}, {56, 53}, {62, 19}, {64, 60}, {87, 24}, {93, 73}, {95, 75}, {98, 78}}));
        ObstacleFactory.createLadders(board, Arrays.asList(new int[][]{{1, 38}, {4, 14}, {9, 31}, {21, 42}, {28, 84}, {36, 44}, {51, 67}, {71, 91}, {80, 100}}));

        Game game = new Game();
        Player player1 = new Player("Alice");
        Player player2 = new Player("Bob");

        game.addPlayer(player1);
        game.addPlayer(player2);

        DiceStrategy dice = new NormalDice();

        while (true) {
            Player currentPlayer = game.getCurrentPlayer();
            int diceRoll = dice.rollDice();
            Command moveCommand = new MoveCommand(currentPlayer, diceRoll, board);
            moveCommand.execute();

            game.notifyPlayers(currentPlayer.getName() + " rolled a " + diceRoll + " and moved to " + currentPlayer.getPosition());

            if (currentPlayer.getPosition() == board.getSize()) {
                game.notifyPlayers(currentPlayer.getName() + " wins!");
                break;
            }

            game.nextTurn();
        }
    }
}

Issues in the Above Design (Covered in our premium course)

  1. Single-threaded Design:

    • Issue: The current design is single-threaded, which may not be suitable for real-time multiplayer games.

    • Resolution: Introduce multithreading to handle simultaneous player actions and improve performance.

  2. Scalability Issues:

    • Issue: The design may not scale well with a large number of players or an extensive board size.

    • Resolution: Optimize data structures and algorithms to handle larger scales efficiently.

  3. No Separation of Concerns:

    • Issue: The design mixes game logic, user notifications, and command execution.

    • Resolution: Refactor the code to separate these concerns, adhering to the Single Responsibility Principle (SRP).

  4. Limited Extensibility:

    • Issue: Adding new features or modifying existing ones may require significant code changes.
  5. Performance Bottlenecks:

    • Issue: Random number generation and frequent lookups in maps may cause performance issues.

    • Resolution: Optimize these operations and consider more efficient data structures if necessary.

Extensions of the Design

  1. Customizable Board:

    • Description: Allow users to create and play on custom boards.

    • Implementation: Implement a configuration file or a GUI-based board editor where users can define the board size, and the positions of snakes and ladders. - Covered in ourpremium course.

  2. **Support Multi-Board -Covered in ourpremium course.

  3. Save and Load Game State:

    • Description: Provide functionality to save and load the game's state.

    • Implementation: Covered in ourpremium course.

  4. AI Players:

    • Description: Introduce computer-controlled players with varying difficulty levels.

    • Implementation: Implement AI strategies for making moves and integrate them into the game logic. Allow users to choose between human and AI players - Covered in ourpremium course.

  5. Different Dice Types:

    • Description: Offer various types of dice with different numbers of faces.

    • Implementation: Modify the DiceStrategy to include different dice types. Allow players to select their preferred dice type before starting the game - Covered in ourpremium course.

  6. Enhanced Game Rules:

    • Description: Add optional and customizable game rules.

    • Implementation: Implement additional rules such as extra turns for rolling doubles or penalties for specific squares. Allow players to configure these rules at the start of the game - Covered in ourpremium course.

  7. Player Statistics and Leaderboards:

    • Description: Track player performance and maintain leaderboards.

    • Implementation: Store player statistics such as wins, losses, and total games played. Display leaderboards showing top players based on these statistics - Covered in ourpremium course.

Playground (test/run above code) - https://ide.lldcoding.com/snake-and-ladder-game

Soon will add YouTube video on this channel - https://www.youtube.com/channel/UCgdIgkU_hq0VtGPhVPA0CPQ

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!