Skip to content

🐍 Machine Coding: Modular Snake & Ladder Game

πŸ“ Overview

Design and implement a robust Snake & Ladder game engine. This challenge focuses on creating a modular, turn-based system that handles multiple players, dynamic board configurations with "jumps" (snakes and ladders), and fair dice mechanics.

Why This Challenge?

  • Entity Modeling Mastery: Tests your ability to represent real-world game components (Board, Players, Snakes, Ladders) as interacting objects.
  • State Machine Logic: Evaluates your management of turn-based transitions and game states until a victory condition is met.
  • Extensibility: Challenges you to design a system where new rules (e.g., special squares, multiple dice) can be added without refactoring core logic.

🏭 The Scenario & Requirements

😑 The Problem (The Villain)

"The God Class." A messy implementation where board layout, player turns, and dice logic are all crammed into one giant 500-line while True loop. Adding a simple "Special Booster" square or a second die requires rewriting half the codebase, and the game is prone to infinite loops between snakes and ladders.

🦸 The System (The Hero)

"The Decoupled Engine." An automated game manager that separates the Board (the physical layout) from the Game Controller (the turn logic). By treating snakes and ladders as generic "jumps" and using a strict player queue, the system ensures fairness, prevents infinite loops, and scales easily.

πŸ“œ Requirements & Constraints

  1. Functional:
    • Dynamic Board: Support for a configurable grid (default 10x10) with custom snake and ladder positions.
    • Multi-Player: Handle \(N\) players with unique identifiers and a persistent turn order.
    • Dice Mechanics: Implement a standard 6-sided die with an option for configurable \(K\)-sided dice.
    • Victory Logic: Detect and announce the winner exactly when they reach the final square.
  2. Technical:
    • Exact Finish: A player must roll the exact number required to land on square 100; otherwise, they remain in place.
    • Fairness: Maintain a strict queue of players to ensure turn integrity.
    • Encapsulation: Players should not be able to modify board state directly.

πŸ—οΈ Design & Architecture

🧠 Thinking Process

To transform these requirements into a clean system, we identify four core entities:
1. Player: Tracks identity and current position on the board.
2. Board: Acts as a data structure containing the "jump" mappings (Snakes/Ladders).
3. Dice: A utility for generating random movements.
4. Game: The orchestrator that manages the loop, turn transitions, and win conditions.

🧩 Class Diagram

classDiagram
    direction TB
    class Game {
        -List~Player~ players
        -Board board
        +start_game(num_players)
        -_play_turn(player)
        +check_winner(player)
    }
    class Board {
        -dict snake
        -dict ladder
        +check_position(player)
    }
    class Player {
        +string name
        +int position
        +roll_dice()
        +move(steps)
    }
    Game --> Board : owns
    Game --> Player : manages

βš™οΈ Design Patterns Applied

  • Strategy Pattern: Used for the dice mechanics (easily swap a StandardDie for a LoadedDie or MultiDie).
  • State Pattern: (Implicitly) Managing the game flow through states: Initialization, Running, and Finished.
  • Command Pattern: Moves are encapsulated, allowing for future "Undo" functionality or move logging.

πŸ’» Solution Implementation

The Code
from random import randint


class Player:
    # Constructor
    def __init__(self: Player, name: str, position: int = 0):
        self.name = name
        self.position = position

    def roll_dice(self) -> int:
        return randint(1, 6)

    def move(self, steps: int) -> None:
        self.position += steps


class Board:
    # Constructor
    def __init__(self: Board):
        self.snake = {
            99: 13,
            78: 21,
            81: 6,
            45: 34,
        }
        self.ladder = {
            5: 22,
            14: 45,
            60: 76,
            77: 91,
            44: 66,
        }

    def check_position(self, player: Player) -> None:
        """Check if the player's current position is a snake or ladder and update accordingly."""
        if player.position in self.snake:
            print(f"Oops! {player.name} landed on a snake!")
            player.position = self.snake[player.position]
            print(f"{player.name} slides down to position {player.position}")

        elif player.position in self.ladder:
            print(f"Yay! {player.name} landed on a ladder!")
            player.position = self.ladder[player.position]
            print(f"{player.name} climbs up to position {player.position}")

        else:
            print(f"{player.name} is on square {player.position}")


class Game:
    # Constructor
    def __init__(self: Game):
        self.players: list[Player] = []
        self.board = Board()

    def start_game(self: Game, num_players: int) -> None:
        self._initialize_players(num_players)

        # Game loop
        game_over: bool = False
        while not game_over:
            for player in self.players:
                game_over = self._play_turn(player)
                if game_over:
                    break

            print("\nCurrent positions of all players:")
            for p in self.players:
                print(f"{p.name}: {p.position}")

    def check_winner(self: Game, player: Player) -> bool:
        # Check if player reached position 100
        return player.position == 100

    def _initialize_players(self: Game, num_players: int) -> None:
        # Initialize players
        for _ in range(num_players):
            name = input("Enter your name: ")
            self.players.append(Player(name))

    def _play_turn(self: Game, player: Player) -> bool:
        print(f"\n{player.name}'s turn:")
        steps = player.roll_dice()
        print(f"{player.name} rolled a {steps}")

        if steps + player.position > 100:
            print(f"{player.name} can't move, roll too high. Try again next turn!")
            input("Press Enter to continue to the next turn...")
            return False

        player.move(steps)

        self.board.check_position(player)

        # Check if player won
        if self.check_winner(player):
            print(f"\nCongratulations {player.name}, you won the game!")
            return True

        input("Press Enter to continue to the next turn...")
        return False


game = Game()
try:
    num_players = int(input("Enter the number of players: "))
    if num_players <= 0:
        print("Number of players must be at least 1!")
    else:
        game.start_game(num_players)
except ValueError:
    print("Invalid input! Please enter a valid number of players.")

πŸ”¬ Why This Works (Evaluation)

The implementation separates movement logic from board constraints. When a player moves, the Game first updates their position and then asks the Board to "validate" it. If the player lands on a snake or ladder, the Board updates the player's position internally. This decoupling ensures that the Game loop doesn't need to know why a player moved from square 14 to 45β€”it only cares that the move was processed.


βš–οΈ Trade-offs & Limitations

Decision Pros Cons / Limitations
Dictionary-based Jumps \(O(1)\) lookups for snakes and ladders. Harder to implement complex "conditional" squares (e.g., move back 2 spots only on your first turn).
Simple Integer Position Easy to calculate and compare. Doesn't natively support 2D grid coordinates for UI visualization.
Blocking input() loop Simple to implement for a CLI. Cannot be easily converted to a real-time multiplayer web app without refactoring to an event-driven model.

🎀 Interview Toolkit

  • Concurrency Probe: How would you handle 4 players playing simultaneously from different computers? (Focus on a central game server and state synchronization).
  • Extensibility: How would you add a "Booster" square that gives an extra turn? (Implement as a special jump type that returns a flag to the Game loop).
  • Data Persistence: If the game crashes mid-way, how do we resume? (Serialize the Game state, including player queue and positions, to a JSON file).