Machine Coding Problem

Snake & Ladder

maco30maco60macoAllgamesentity-modelinggame-loop
Commonly Asked By:AmazonFlipkartMicrosoftGrab

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic Board Config: The board must accept customizable size and configurable head-to-tail Snakes and base-to-top Ladders.
  • Turn-Based Flow: Support N sequential players managed in a round-robin turn order.
  • Modular Dice Control: Allow rolling M concurrent dice of configurable sides (default single 6-sided die).
  • Strict Win Condition: Win occurs only on exact landing on cell 100. Overshoots are rejected, leaving player position unchanged.

Explicit Boundaries (Out-of-Scope)

  • No UI Render Loop: Outputs status text sequentially via command-line logger.
  • No Game Lobby Network Logic: Run locally within a single synchronous app thread.

Class Diagram & Entity Relationships

Structural layout showing clean encapsulation of state and simulation rules:

Loading...
  • Encapsulated Game State: Board exposes cell traversal logic but holds no player state. It remains entirely immutable during execution.
  • Polymorphic Entities: Snakes and Ladders are simplified to key-value entries in a specialCells lookup map to minimize class proliferation.

Design Patterns & SOLID Principles

  • Single Responsibility Principle (SRP):Dice is only responsible for generating deterministic random values. Board solely controls the structural pathways. GameManager handles the execution loop rules.
  • Strategy Pattern (Optionally Swappable Dice): Using a clean abstraction for Dice allows swapping standard rolling logic for loaded dice configurations or rigged testing dice interfaces seamlessly.

Core Execution Workflows

Simulation Turn Loop

  1. De-queue active Player from top of sequence queue.
  2. Request a numeric value from the Dice.
  3. Compute new position: nextPosition = currentPosition + diceValue.
  4. If nextPosition exceeds board limit:
    1. Log the overshoot, leave position unchanged, re-queue player, and skip to next turn.
  5. If nextPosition matches a key in Board.specialCells:
    1. Resolve target location dynamically (slide down snake tail or climb up ladder base).
  6. Check victory: If resolved location matches Board limit, terminate simulation and declare winner.
  7. Otherwise, re-queue player and proceed.

Concurrency & Thread Safety Strategy

Because Snake & Ladder is fundamentally a sequential, single-active-actor turn loop, there is no concurrent race condition between players. However, robust design prevents external thread mutation:

  • Single-Threaded Engine Execution: Turn state and positions are updated synchronously.
  • Immutability of Rules: Keeping the Board configuration entirely read-only after setup eliminates thread contention when reading snake/ladder cell positions.

Complete Clean Code Blueprint

Modular clean implementations in all 3 target languages:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;

interface DiceStrategy {
    int roll();
}

class StandardDice implements DiceStrategy {
    private final int sides;
    private final Random random = new Random();

    public StandardDice(int sides) {
        this.sides = sides;
    }

    @Override
    public int roll() {
        return random.nextInt(sides) + 1;
    }
}

class Player {
    private final String name;
    private int position;

    public Player(String name) {
        this.name = name;
        this.position = 0; // Starts off-board or at 0
    }

    public String getName() { return name; }
    public int getPosition() { return position; }
    public void setPosition(int position) { this.position = position; }
}

class Board {
    private final int size;
    private final Map<Integer, Integer> specialCells = new HashMap<>(); // Holds snakes & ladders

    public Board(int size) {
        this.size = size;
    }

    public int getSize() { return size; }

    public void addSnake(int head, int tail) {
        if (head <= tail) throw new IllegalArgumentException("Snake head must be above tail.");
        specialCells.put(head, tail);
    }

    public void addLadder(int start, int end) {
        if (start >= end) throw new IllegalArgumentException("Ladder start must be below end.");
        specialCells.put(start, end);
    }

    public int getNextPosition(int position) {
        if (specialCells.containsKey(position)) {
            int destination = specialCells.get(position);
            if (destination < position) {
                System.out.println("[Board] Swallowed by Snake at " + position + " -> slid down to " + destination);
            } else {
                System.out.println("[Board] Climbed Ladder at " + position + " -> went up to " + destination);
            }
            return destination;
        }
        return position;
    }
}

class GameManager {
    private final Board board;
    private final DiceStrategy dice;
    private final Queue<Player> players = new LinkedList<>();
    private Player winner = null;

    public GameManager(Board board, DiceStrategy dice, List<Player> initialPlayers) {
        this.board = board;
        this.dice = dice;
        this.players.addAll(initialPlayers);
    }

    public boolean isGameOver() {
        return winner != null;
    }

    public void takeTurn() {
        if (isGameOver()) return;

        Player currentPlayer = players.poll();
        int roll = dice.roll();
        int currentPos = currentPlayer.getPosition();
        int nextPos = currentPos + roll;

        System.out.println("[Game] " + currentPlayer.getName() + " rolled a " + roll + " (Current: " + currentPos + ")");

        if (nextPos > board.getSize()) {
            System.out.println("[Game] " + currentPlayer.getName() + " overshoot! Needs exact roll to win. Turn skipped.");
            nextPos = currentPos;
        } else {
            nextPos = board.getNextPosition(nextPos);
        }

        currentPlayer.setPosition(nextPos);
        System.out.println("[Game] " + currentPlayer.getName() + " moves to cell: " + nextPos);

        if (nextPos == board.getSize()) {
            winner = currentPlayer;
            System.out.println("[Victory] Player " + currentPlayer.getName() + " has won the game!");
            return;
        }

        players.add(currentPlayer); // Re-queue for next turn
    }

    public Player getWinner() { return winner; }
}

public class Main {
    public static void main(String[] args) {
        Board board = new Board(100);
        
        // Setup snakes
        board.addSnake(14, 4);
        board.addSnake(37, 17);
        board.addSnake(62, 19);
        board.addSnake(96, 56);

        // Setup ladders
        board.addLadder(3, 38);
        board.addLadder(9, 31);
        board.addLadder(40, 59);
        board.addLadder(75, 95);

        DiceStrategy standardDice = new StandardDice(6);
        List<Player> players = Arrays.asList(
            new Player("Alice"),
            new Player("Bob"),
            new Player("Charlie")
        );

        GameManager game = new GameManager(board, standardDice, players);

        System.out.println("--- Snake & Ladder Game Starting ---");
        int turnCount = 0;
        while (!game.isGameOver() && turnCount < 100) {
            game.takeTurn();
            turnCount++;
        }
        System.out.println("--- Simulation Ended. Total Turns: " + turnCount + " ---");
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...