Open In App

Design Snake and Ladder Game using Python OOPS

Last Updated : 31 Oct, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

This article covers designing a complete Snake and Ladder game using object-oriented programming (Python OOP) principles with the following rules and requirements. Below we will discuss the rules of the game:

  1. Here we create a board of size 10 and dice of side 6.
  2. Each player puts their counter on the board at starting position at 1 and takes turns to roll the dice.
  3. Move your counter forward the number of spaces shown on the dice.
  4. If your counter lands at the bottom of a ladder, you can move up to the top of the ladder. If your counter lands on the head of a snake, you must slide down to the bottom of the snake.
  5. Each player will get a fair chance to roll the dice.
  6. On the dice result of 6, the user gets one more chance to roll the dice again. However, the same user can throw the dice a maximum of 3 times.
    Note: if the result of the dice is 6,6,6 the user can not throw the dice again as the maximum attempts are over and the next user will get to throw the dice.
  7. When the user rolls dice and it leads to an invalid move, the player should remain in the same position.
    Ex: when the user is in position 99 and rolling of dice yields any number more than one the user remains in the same position.
  8. Print the ranks of users who finished first, second, and so on…
Design Snake and Ladder Game using Python OOPS

 

Steps to Design Snake and Ladder game using OOPS:

Step 1: Define Game Player Class. This class encapsulates a player’s properties like _id, rank, and its current_position on board.

Python3




class GamePlayer:
 
    def __init__(self, _id):
        # initial dummy rank -1
        self._id = _id
        # starting position for every player is 1
        self.rank = -1
        self.position = 1
 
    def set_position(self, pos):
        self.position = pos
 
    def set_rank(self, rank):
        self.rank = rank
 
    def get_pos(self):
        return self.position
 
    def get_rank(self):
        return self.rank


Step 2: Define the Moving Entity class. This class can be extended by anything that can move the player to another position, like a Snake or ladder. This is done in order to keep code extensible and abstract common behavior like end position. We can define this class using fixed end_position for a moving entity or can override the get_end_pos() function in order to get dynamic end_pos as well.

Python3




class MovingEntity:
    """
    You can create any moving entity , like snake or ladder or
    wormhole by extending this
    """
 
    def __init__(self, end_pos=None):
        self.end_pos = end_pos  # end pos where player would be send on board
        self.desc = None  # description of moving entity
 
    def set_description(self, desc):
        self.desc = None
 
    def get_end_pos(self):
        if self.end_pos is None:
            raise Exception("no_end_position_defined")
        return self.end_pos


Step 3: Define Snake and Ladder by extending the moving entity.

Python3




class Snake(MovingEntity):
    """Snake entity"""
 
    def __init__(self, end_pos=None):
        super(Snake, self).__init__(end_pos)
        self.desc = "Snake"
 
 
class Ladder(MovingEntity):
    """Ladder entity"""
 
    def __init__(self, end_pos=None):
        super(Ladder, self).__init__(end_pos)
        self.desc = "Ladder"


Step 4: Defining board class:

  1. The board contains a moving entity that can be set at a specific position using a function(set_moving_entity), for e.g. to add a snake at position 10, we can just do set_moving_entity(10, snake)
  2. Board also provides the next position from a given position. In this case, if the player lands on 10, and the board knows there is a snake at 10, it will return the end_pos of the snake. If no moving entity is present on the position, then just return the position itself.

Python3




class Board:
    """
    define board with size and moving entities
    """
 
    def __init__(self, size):
        self.size = size
        self.board = {}  # instead of using list, we can use map of {pos:moving_entity} to save space
 
    def get_size(self):
        return self.size
 
    def set_moving_entity(self, pos, moving_entity):
        # set moving entity to pos
        self.board[pos] = moving_entity
 
    def get_next_pos(self, player_pos):
        # get next pos given a specific position player is on
        if player_pos > self.size:
            return player_pos
        if player_pos not in self.board:
            return player_pos
        return self.board[player_pos].get_end_pos()
 
    def at_last_pos(self, pos):
        if pos == self.size:
            return True
        return False


Step 5: Define the Dice class as it contains a side and exposes a roll method, which returns a random value from 1 to 6.

Python3




class Dice:
    def __init__(self, sides):
      # no of sides in the dice
        self.sides = sides 
 
    def roll(self):
        # return random number between 1 to sides
        import random
        ans = random.randrange(1, self.sides + 1)
        return ans


Step 6: In this step we will define Game class and its functions as below points:

  1. The Game class encapsulates everything needed to play the game i.e. players, dice, and board. Here we have last_rank(which is the last rank achieved in the game currently i.e. no of players finished playing), and we have a turn that states which player turn it is.
  2. The game can be initialized using the initialize_game() function, which needs a board object, dice sides, and no_of_players.
  3. The game can be played by calling the play() function:
    1. The game is not finished until the last rank assigned to the player is not equal to the total players, this is checked by can_play().
    2. The get_next_player() is used to get the next player to play, which is the player which has the current turn. In case the current turn player has already won, it returns the next player which still has not reached the last position
    3. The dice roll is done in order to get the dice result.
    4. Using the board we get the next position of the player.
    5. If the next position is valid, i.e. within bounds(this is checked by can_move()), we change the player position to the next position
    6. The next position for the player is set by move_player(), here we also check if the player is at the last position, so we assign the rank as well.
    7. Next, we change the turn using change_turn(), here we will only change the turn if the current dice roll does not deal 6 or we have crossed the consecutive 3 6’s limit
    8. We print the game state using print_game_state(), which prints all player and their current positions and move back to step 1.
    9. After all, players have finished playing, we print the final game state i.e. ranks of each player using print_game_result()

Python3




class Game:
 
    def __init__(self):
        self.board = None  # game board object
        self.dice = None  # game dice object
        self.players = []  # list of game player objects
        self.turn = 0  # curr turn
        self.winner = None
        self.last_rank = 0  # last rank achieved
        self.consecutive_six = 0  # no of consecutive six in one turn, resets every turn
 
    def initialize_game(self, board: Board, dice_sides, players):
        """
      Initialize game using board, dice and players
      """
        self.board = board
        self.dice = Dice(dice_sides)
        self.players = [GamePlayer(i) for i in range(players)]
 
    def can_play(self):
        if self.last_rank != len(self.players):
            return True
        return False
 
    def get_next_player(self):
        """
      Return curr_turn player but if it has already won/completed game , return
      next player which is still active
      """
        while True:
            # if rank is -1 , player is still active so return
            if self.players[self.turn].get_rank() == -1:
                return self.players[self.turn]
            # check next player
            self.turn = (self.turn + 1) % len(self.players)
 
    def move_player(self, curr_player, next_pos):
        # Move player to next_pos
        curr_player.set_position(next_pos)
        if self.board.at_last_pos(curr_player.get_pos()):
            # if at last position set rank
            curr_player.set_rank(self.last_rank + 1)
            self.last_rank += 1
 
    def can_move(self, curr_player, to_move_pos):
        # check if player can move or not ie. between board bound
        if to_move_pos <= self.board.get_size() and curr_player.get_rank() == -1:
            return True
        return False
 
    def change_turn(self, dice_result):
        # change player turn basis dice result.
        # if it's six, do not change .
        # if it's three consecutive sixes or not a six, change
        self.consecutive_six = 0 if dice_result != 6 else self.consecutive_six + 1
        if dice_result != 6 or self.consecutive_six == 3:
            if self.consecutive_six == 3:
                print("Changing turn due to 3 consecutive sixes")
            self.turn = (self.turn + 1) % len(self.players)
        else:
            print(f"One more turn for player {self.turn+1} after rolling 6")
 
    def play(self):
        """
      starting point of game
      game will be player until all players have not been assigned a rank
      # get curr player to play
      # roll dice
      # get next pos of player
      # see if pos is valid to move
      # move player to next pos
      # change turn
      Note: Currently everything is automated, ie. dice roll input is not taken from user.
      if required , we can change that to give some control to user.
      """
        while self.can_play():
            curr_player = self.get_next_player()
            player_input = input(
                f"Player {self.turn+1}, Press enter to roll the dice")
            dice_result = self.dice.roll()
            print(f'dice_result: {dice_result}')
            _next_pos = self.board.get_next_pos(
                curr_player.get_pos() + dice_result)
            if self.can_move(curr_player, _next_pos):
                self.move_player(curr_player, _next_pos)
            self.change_turn(dice_result)
            self.print_game_state()
        self.print_game_result()
 
    def print_game_state(self):
        # Print state of game after every turn
        print('-------------game state-------------')
        for ix, _p in enumerate(self.players):
            print(f'Player: {ix+1} is at pos {_p.get_pos()}')
        print('-------------game state-------------\n\n')
 
    def print_game_result(self):
        # Print final game result with ranks of each player
        print('-------------final result-------------')
        for _p in sorted(self.players, key=lambda x: x.get_rank()):
            print(f'Player: {_p._id+1} , Rank: {_p.get_rank()}')


Complete Code:

In the code below, there is a sample_run() function defined, which will start the game basis the current configuration defined. We can change the configurations as per our liking and start a new game using any size of the board, any size of dice, and any number of players.

Python3




class GamePlayer:
    """
    Encapsulates a player properties
    """
 
    def __init__(self, _id):
        self._id = _id
        # initial dummy rank -1
        self.rank = -1
        # starting position for every player is 1
        self.position = 1
 
    def set_position(self, pos):
        self.position = pos
 
    def set_rank(self, rank):
        self.rank = rank
 
    def get_pos(self):
        return self.position
 
    def get_rank(self):
        return self.rank
 
 
class MovingEntity:
    """
    You can create any moving entity , like snake or ladder or
    wormhole by extending this
    """
 
    def __init__(self, end_pos=None):
      # end pos where player would be send on board
        self.end_pos = end_pos
        # description of moving entity
        self.desc = None
 
    def set_description(self, desc):
        self.desc = None
 
    def get_end_pos(self):
        if self.end_pos is None:
            raise Exception("no_end_position_defined")
        return self.end_pos
 
 
class Snake(MovingEntity):
    """Snake entity"""
 
    def __init__(self, end_pos=None):
        super(Snake, self).__init__(end_pos)
        self.desc = "Bit by Snake"
 
 
class Ladder(MovingEntity):
    """Ladder entity"""
 
    def __init__(self, end_pos=None):
        super(Ladder, self).__init__(end_pos)
        self.desc = "Climbed Ladder"
 
 
class Board:
    """
    define board with size and moving entities
    """
 
    def __init__(self, size):
        self.size = size
        # instead of using list, we can use map of
        # {pos:moving_entity} to save space
        self.board = {}
 
    def get_size(self):
        return self.size
 
    def set_moving_entity(self, pos, moving_entity):
        # set moving entity to pos
        self.board[pos] = moving_entity
 
    def get_next_pos(self, player_pos):
        # get next pos given a specific position player is on
        if player_pos > self.size:
            return player_pos
        if player_pos not in self.board:
            return player_pos
        print(f'{self.board[player_pos].desc} at {player_pos}')
        return self.board[player_pos].get_end_pos()
 
    def at_last_pos(self, pos):
        if pos == self.size:
            return True
        return False
 
 
class Dice:
    def __init__(self, sides):
      # no of sides in the dice
        self.sides = sides
 
    def roll(self):
        # return random number between 1 to sides
        import random
        ans = random.randrange(1, self.sides + 1)
        return ans
 
 
class Game:
 
    def __init__(self):
      # game board object
        self.board = None
        # game dice object
        self.dice = None
        # list of game player objects
        self.players = []
        # curr turn
        self.turn = 0
        self.winner = None
        # last rank achieved
        self.last_rank = 0
        # no of consecutive six in one turn, resets every turn
        self.consecutive_six = 0
 
    def initialize_game(self, board: Board, dice_sides, players):
        """
      Initialize game using board, dice and players
      """
        self.board = board
        self.dice = Dice(dice_sides)
        self.players = [GamePlayer(i) for i in range(players)]
 
    def can_play(self):
        if self.last_rank != len(self.players):
            return True
        return False
 
    def get_next_player(self):
        """
      Return curr_turn player but if it has already won/completed game , return
      next player which is still active
      """
        while True:
            # if rank is -1 , player is still active so return
            if self.players[self.turn].get_rank() == -1:
                return self.players[self.turn]
            # check next player
            self.turn = (self.turn + 1) % len(self.players)
 
    def move_player(self, curr_player, next_pos):
        # Move player to next_pos
        curr_player.set_position(next_pos)
        if self.board.at_last_pos(curr_player.get_pos()):
            # if at last position set rank
            curr_player.set_rank(self.last_rank + 1)
            self.last_rank += 1
 
    def can_move(self, curr_player, to_move_pos):
        # check if player can move or not ie. between board bound
        if to_move_pos <= self.board.get_size() and curr_player.get_rank() == -1:
            return True
        return False
 
    def change_turn(self, dice_result):
        # change player turn basis dice result.
        # if it's six, do not change .
        # if it's three consecutive sixes or not a six, change
        self.consecutive_six = 0 if dice_result != 6 else self.consecutive_six + 1
        if dice_result != 6 or self.consecutive_six == 3:
            if self.consecutive_six == 3:
                print("Changing turn due to 3 consecutive sixes")
            self.turn = (self.turn + 1) % len(self.players)
        else:
            print(f"One more turn for player {self.turn+1} after rolling 6")
 
    def play(self):
        """
      starting point of game
      game will be player until all players have not been assigned a rank
      # get curr player to play
      # roll dice
      # get next pos of player
      # see if pos is valid to move
      # move player to next pos
      # change turn
      Note: Currently everything is automated, ie. dice roll input is not taken from user.
      if required , we can change that to give some control to user.
      """
        while self.can_play():
            curr_player = self.get_next_player()
            player_input = input(
                f"Player {self.turn+1}, Press enter to roll the dice")
            dice_result = self.dice.roll()
            print(f'dice_result: {dice_result}')
            _next_pos = self.board.get_next_pos(
                curr_player.get_pos() + dice_result)
            if self.can_move(curr_player, _next_pos):
                self.move_player(curr_player, _next_pos)
            self.change_turn(dice_result)
            self.print_game_state()
        self.print_game_result()
 
    def print_game_state(self):
        # Print state of game after every turn
        print('-------------game state-------------')
        for ix, _p in enumerate(self.players):
            print(f'Player: {ix+1} is at pos {_p.get_pos()}')
        print('-------------game state-------------\n\n')
 
    def print_game_result(self):
        # Print final game result with ranks of each player
        print('-------------final result-------------')
        for _p in sorted(self.players, key=lambda x: x.get_rank()):
            print(f'Player: {_p._id+1} , Rank: {_p.get_rank()}')
 
 
def sample_run():
    # a simple flow of the game
    # here we create a board of size 10 and dice of side 6
    # set snake at 5 with end at 2
    # set ladder at 4 with end at 6
    board = Board(10)
    board.set_moving_entity(7, Snake(2))
    board.set_moving_entity(4, Ladder(6))
    game = Game()
    game.initialize_game(board, 6, 2)
    game.play()
 
 
sample_run()


Output:

Player 1, Press enter to roll the dice
dice_result: 4
-------------game state-------------
Player: 1 is at pos 5
Player: 2 is at pos 1
-------------game state-------------


Player 2, Press enter to roll the dice
dice_result: 2
-------------game state-------------
Player: 1 is at pos 5
Player: 2 is at pos 3
-------------game state-------------


Player 1, Press enter to roll the dice
dice_result: 1
-------------game state-------------
Player: 1 is at pos 6
Player: 2 is at pos 3
-------------game state-------------


Player 2, Press enter to roll the dice
dice_result: 1
Climbed Ladder at 4
-------------game state-------------
Player: 1 is at pos 6
Player: 2 is at pos 6
-------------game state-------------


Player 1, Press enter to roll the dice
dice_result: 5
-------------game state-------------
Player: 1 is at pos 6
Player: 2 is at pos 6
-------------game state-------------


Player 2, Press enter to roll the dice
dice_result: 5
-------------game state-------------
Player: 1 is at pos 6
Player: 2 is at pos 6
-------------game state-------------


Player 1, Press enter to roll the dice
dice_result: 1
Bit by Snake at 7
-------------game state-------------
Player: 1 is at pos 2
Player: 2 is at pos 6
-------------game state-------------


......

Player 2, Press enter to roll the dice
dice_result: 1
-------------game state-------------
Player: 1 is at pos 10
Player: 2 is at pos 10
-------------game state-------------


-------------final result-------------
Player: 1 , Rank: 1
Player: 2 , Rank: 2


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads