Entities

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.stream.IntStream;

public class SnakesAndLadders {
    /**
     * Requirements
     * * There is a grid board  with size MxN.
     * * Board has snakes and ladders on it.
     * * 2 players are playing the game taking turns one by one alternatively.
     * * Each player takes the turn by throwing a dice and moving their pointer by the number which comes up on the dice.
     * * If they land on a snake, they are bitten and move back.
     * * If they land on a ladder, they climb and move forward.
     * * Whoever reaches 100 first wins. Both player starts from 0.
     */****

    public class Player {
        private final String name;

        private int position = 0;

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

        public int roll(final Dice dice) {
            return dice.roll();
        }

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

        public int getPosition() {
            return position;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            final Player player = (Player) o;
            return name == player.name;
        }

        @Override
        public int hashCode() {
            return Objects.hash(name);
        }

        @Override
        public String toString() {
            return "Player{" +
                    "name=" + name +
                    ", position=" + position +
                    '}';
        }
    }

    public interface Dice {
        int roll();
    }

    public class NormalDice implements Dice {

        @Override
        public int roll() {
            final int rollNumber = new Random().nextInt(6) + 1;
            System.out.println("Dice roll number: " + rollNumber);
            return rollNumber;
        }
    }

    public class Cell {
        private final int position;
        private final Jumper jumper;

        public Cell(final int position) {
            this(position, null);
        }

        public Cell(final int position, final Jumper jumper) {
            this.position = position;
            this.jumper = jumper;
            Optional.ofNullable(this.jumper)
                    .ifPresent(jum -> jum.validatePositionOrThrowException(this.position));
        }

        public int getPosition() {
            return position;
        }

        public int getNextPosition() {
            return Optional.ofNullable(jumper).map(Jumper::getNextPosition).orElse(this.position);
        }
    }

    public abstract class Jumper {
        public final int nextPosition;

        protected Jumper(final int nextPosition) {
            this.nextPosition = nextPosition;
        }

        public int getNextPosition() {
            return nextPosition;
        }

        public abstract void validatePositionOrThrowException(int position);
    }

    public class IllegalFinalPositoinException extends RuntimeException {
    }

    public class Snake extends Jumper {

        public Snake(final int nextPosition) {
            super(nextPosition);
        }

        @Override
        public void validatePositionOrThrowException(final int position) {
            if (position <= nextPosition)
                throw new IllegalFinalPositoinException();
        }
    }

    public class Ladder extends Jumper {

        public Ladder(final int nextPosition) {
            super(nextPosition);
        }

        @Override
        public void validatePositionOrThrowException(final int position) {
            if (position >= nextPosition)
                throw new IllegalFinalPositoinException();
        }
    }

    public class Board {
        private final Map<Integer, Cell> positionCells = new HashMap<>();
        ;
        private final int length, breadth;

        private Game game;

        public Board(
                final Cell[] cells,
                final int length,
                final int breadth,
                final Game game) {
            Arrays.stream(cells)
                    .forEach(cell -> this.positionCells.put(cell.getPosition(), cell));
            this.length = length;
            this.breadth = breadth;
            this.game = game;
        }

        public int nextPosition(final int curPosition, final int steps) {
            final int nextPosition = curPosition + steps;
            if (nextPosition >= length * breadth)
                return length * breadth;

            return Optional.ofNullable(this.positionCells.get(nextPosition))
                    .map(Cell::getNextPosition)
                    .orElse(nextPosition);
        }

        public boolean reachedEnd(final int position) {
            return this.breadth * this.length == position;
        }
    }

    public class GameOverException extends RuntimeException {

    }

    public class IllegalMoveException extends RuntimeException {

    }

    enum GameStatus {
        ON_GOING,
        COMPLETED
    }

    public class Game {
        private final Player[] players = new Player[2];
        private final Board board;
        private Dice dice;

        private int nextPlayerIndex = 0;

        private GameStatus status;

        private Player winner;

        public Game(
                final int m, final int n,
                final Cell[] cells,
                final Dice dice,
                final Player[] players) {
            this.board = new Board(cells, m, n, this);
            this.dice = dice;
            IntStream.range(0, players.length).forEach(i -> this.players[i] = players[i]);
            this.status = GameStatus.ON_GOING;
        }

        public void play(final Player player) {
            if (GameStatus.COMPLETED.equals(status))
                throw new GameOverException();

            if (!player.equals(this.players[nextPlayerIndex]))
                throw new IllegalMoveException();

            final int steps = player.roll(this.dice);
            final int nextPosition = board.nextPosition(player.getPosition(), steps);
            player.setPosition(nextPosition);

            if (this.board.reachedEnd(player.getPosition())) {
                this.status = GameStatus.COMPLETED;
                this.winner = player;
            }

            nextPlayerIndex = (nextPlayerIndex + 1) % this.players.length;

            Arrays.stream(players).forEach(System.out::println);
            System.out.println("NextPlayer: " + this.players[nextPlayerIndex]);
            System.out.println("Winner:" + this.winner);
        }

        public Player getWinner() {
            return winner;
        }
    }
}

Driver
public class SnakesAndLaddersDriver {
    public static void main(String[] args) {
        new SnakesAndLaddersDriver().orchestrate();
    }

    private void orchestrate() {
        final SnakesAndLadders snakesAndLadders = new SnakesAndLadders();

        final int m = 2, n = 2;
        final SnakesAndLadders.Cell[] cells = new SnakesAndLadders.Cell[] {
                snakesAndLadders.new Cell(1, snakesAndLadders.new Ladder(3)),
                snakesAndLadders.new Cell(2),
                snakesAndLadders.new Cell(3),
                snakesAndLadders.new Cell(4, snakesAndLadders.new Snake(2))
        };

        final SnakesAndLadders.Dice dice = snakesAndLadders.new NormalDice();

        final SnakesAndLadders.Player[] players = new SnakesAndLadders.Player[] {
                snakesAndLadders.new Player("A"), snakesAndLadders.new Player("B")
        };

        final SnakesAndLadders.Game game = snakesAndLadders.new Game(m, n, cells, dice, players);

        game.play(players[0]);
        game.play(players[1]);

        System.out.println(game.getWinner());
    }

}
Comments (2)