Low Level Design of Snakes & Ladders

Puja Deshmukh
6 min readNov 27, 2022

--

Low-level design (LLD) is a component-level design process that follows a step-by-step refinement process. The LLD phase is where the actual software components are designed. LLD describes the class diagrams with the methods and relations between classes and program specs.

Problem statement :

Design snakes and ladder game. Create a board based on the dimensions that are provided as input by the user.

Rules of the game:

  1. Start from cell 1.
  2. Throw the dice and whatever number the player gets, move up to that cell number.
  3. If there is a ladder at the cell number the player has reached he/she gets to climb the ladder and move to the next position without a dice throw.
  4. If there is a snake at the cell number that the player has reached, he/she has to move down to the cell number where the snake ends without a dice throw.

Implementation :

Let us first consider the entities that will be involved in the design of this game. The first entity of this problem statement will be the game. The game will contain players, a board, and dice.

Let us try to break down these entities further

  1. Board: List of cells.
  2. Dice: A random function returning values between 1–6
  3. Move: To decide where the player should move from a higher cell to lower(snake) or lower cell to higher (ladder) upon getting a number on the dice.

All the entities are mentioned in the UML diagram below

  1. The code for creating the Game class is mentioned below. Here we start by creating a board and getting the dimension details from the user. We create a list to store the usernames of the players and winners. The launchGame() method is used to launch the game and display the board to the user. Upon pressing the ‘r’ button a dice is rolled and based on the output of the dice a move is made for the user. The makeMove() method is used to determine the next position to which the player should move to. This method checks if there is a snake/ladder and calculates the next position accordingly. The printPosition() method is used to print the position of the user on the board.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Game {
Board board;
Queue<Player> players;
Queue<Player> winners;
int diceCount;
int dimension;
int size;

public Board getBoard() {
return board;
}

public void setBoard(Board board) {
this.board = board;
}

public Queue<Player> getPlayers() {
return players;
}

public void setPlayers(Queue<Player> players) {
this.players = players;
}

public Queue<Player> getWinners() {
return winners;
}

public void setWinners(Queue<Player> winners) {
this.winners = winners;
}

public int getDiceCount() {
return diceCount;
}

public void setDiceCount(int diceCount) {
this.diceCount = diceCount;
}

public int getDimension() {
return dimension;
}

public void setDimension(int dimension) {
this.dimension = dimension;
}

public int getSize() {
return size;
}

public void setSize(int size) {
this.size = size;
}

public Game(int dimension, int diceCount){
this.size = dimension * dimension;
this.diceCount = diceCount;
board = new Board(dimension);
players = new LinkedList<Player>();
winners = new LinkedList<Player>();
}

public void launchGame(){
this.board.printBoard();
while (players.size()>1){
Player currentPlayer = players.poll();
System.out.println();
System.out.println("Turn to play: "+currentPlayer.getUserName());
System.out.println("Press 'r' to roll the dice");
Scanner scn = new Scanner(System.in);
char c = scn.next().charAt(0);
makeMove(currentPlayer);
if(currentPlayer.getPosition() == size){
System.out.println(currentPlayer.getUserName()+ " Won!!");
winners.add(currentPlayer);
}else{
players.add(currentPlayer);
}
printPosition();
}
}

private void printPosition() {
for(Player player : players){
System.out.println(player.getUserName()+":"+player.getPosition());
}
}

private void makeMove(Player currentPlayer) {
int currenPosition = currentPlayer.getPosition();
int move = Dice.roll(this.diceCount);
System.out.println("You got: "+move);
int finalPosition = currenPosition + move;
if(finalPosition <= size){
if(board.hasSnakeOrLadder(finalPosition)){
System.out.println(board.getEnity(finalPosition).getEncounterMessage());
finalPosition = board.getEnity(finalPosition).getEndPosition();
}
System.out.println("Taking you to: "+finalPosition);
}else{
System.out.println("Try again in next turn");
finalPosition = currenPosition;
}
currentPlayer.setPosition(finalPosition);
}
public void addPlayer(String userName){
players.add(new Player(userName));
}
}

2. The code for creating a Board is mentioned below. Here we create a board as per the dimensions entered by the user. Math.random method is used to generate random placing for the snakes and ladders on the board. The hasSnakeOrLadder() return true if the cell contains a snake/ladder. The printBoard() method is used to print the board on the console. The setEntity() method is used to put a snake/ladder at a particular cell on the board.

import java.util.HashMap;

public class Board {
HashMap<Integer,Move> cells;
int cellCount;
Board(int dimensions){
this.cellCount = dimensions * dimensions;
cells = new HashMap<Integer,Move>();
//Randomly generate Snakes and Ladders
for(int i=0;i<dimensions;i++){
int min = 2;
int max = cellCount -1;
int start = (int)Math.floor(Math.random()*(max-min+1)+min);
max = start -1;
int end = (int)Math.floor(Math.random()*(max-min+1)+min);
if(!hasSnakeOrLadder(start)){
setEntity(start,new Snake(start,end));
}
max = cellCount - 1;
end = (int)Math.floor(Math.random()*(max-min+1)+min);
max = end-1;
start = (int)Math.floor(Math.random()*(max-min+1)+min);
if(!hasSnakeOrLadder(start)){
setEntity(start,new Ladder(start,end));
}

}
}

private void setEntity(int start, Move move) {
cells.put(start,move);
}

public Move getEnity(int start){
return cells.get(start);
}
boolean hasSnakeOrLadder(int start) {
return cells.containsKey(start);
}
public void printBoard(){
for(int i=cellCount-1;i>0;i--){
System.out.print(i+ " ");
if(hasSnakeOrLadder(i)){
System.out.print(cells.get(i).getString());
}else{
System.out.print(" ");
}
if(i % 10 == 0){
System.out.println();
}
}
System.out.println();
}
}

3. The following code is for an abstract class Move and will have different implementations for Snake class and Ladder class. The getEncounterMessage() method is used to get the encounter of a snake or ladder on the cell.

public abstract class Move {
private int startPostion;
private int endPosition;


public Move(int startPostion, int endPosition) {
this.startPostion = startPostion;
this.endPosition = endPosition;
}

public abstract String getEncounterMessage();
public abstract String getString();

public int getEndPosition() {
return endPosition;
}
}

4. Following is the code for the Snake class. This class extends the abstract Move class and prints a message as “Oops you encountered a SNAKE” whenever a player encounters a snake.

public class Snake extends Move{
public Snake(int startPostion, int endPosition) {
super(startPostion, endPosition);
}

@Override
public String getEncounterMessage() {
return "Oops you encountered a SNAKE";
}

@Override
public String getString() {
return "S("+this.getEndPosition()+") " ;
}
}

5. Following is the code for Ladder class.

public class Ladder extends Move{
public Ladder(int startPostion, int endPosition) {
super(startPostion, endPosition);
}

@Override
public String getEncounterMessage() {
return "Wohoo!! You got a laddder";
}

@Override
public String getString() {
return "L("+this.getEndPosition()+") ";
}
}

6. The Player class is used to instantiate a new player with the given username.

public class Player {
int position;
String userName;

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

public int getPosition() {
return position;
}

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

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}
}

7. The Dice class has a method roll() that returns a random number between 1– 6. If there are n dice then it generates random numbers between 1– 6*n.

public class Dice {
public static int roll(int n){
int min = 1;
int max = n * 6;
return (int) Math.floor(Math.random()*(max-min+1)+min);
}
}

8. The Driver class in the entry point for the game. It contains the main() method wherein we can create an instance of the Game class, add players to it and eventually launch the game.

public class Driver {
public static void main(String[] args) {
Game game = new Game(10,1);
game.addPlayer("A");
game.addPlayer("B");
game.addPlayer("C");
game.launchGame();
}
}

Summary :

  1. We have tried to make the code more reusable by introducing abstraction in the form of the Move class.
  2. We have tried to implement the Single responsibility principle by making sure that each class has only a single responsibility. Ex: The Player class has the data and methods which are only related to a player.
  3. We have tried to make the code more extensible to easily extend the current solution to future requirements.
  4. We have tried to adhere to the DRY principle by making our code more modular.
  5. We have also learned about the working of a Random number generator and its application

--

--

No responses yet