Design Snake and Ladder Game using Python OOPS
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:
- Here we create a board of size 10 and dice of side 6.
- Each player puts their counter on the board at starting position at 1 and takes turns to roll the dice.
- Move your counter forward the number of spaces shown on the dice.
- 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.
- Each player will get a fair chance to roll the dice.
- 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. - 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. - Print the ranks of users who finished first, second, and so on…

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:
- 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)
- 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:
- 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.
- The game can be initialized using the initialize_game() function, which needs a board object, dice sides, and no_of_players.
- The game can be played by calling the play() function:
- 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().
- 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
- The dice roll is done in order to get the dice result.
- Using the board we get the next position of the player.
- 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
- 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.
- 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
- We print the game state using print_game_state(), which prints all player and their current positions and move back to step 1.
- 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
Please Login to comment...