I am learning and trying to implement the best practice for snake and ladder game.
Rules of the game
- The board will have 100 cells numbered from 1 to 100.
- The game will have a six sided dice numbered from 1 to 6 and will always give a random number on rolling it.
- Each player has a piece which is initially kept outside the board (i.e., at position 0).
- Each player rolls the dice when their turn comes. Based on the dice value, the player moves their piece forward that number of cells. Ex: If the dice value is 5 and the piece is at position 21, the player will put their piece at position 26 now (21+5).
- A player wins if it exactly reaches the position 100 and the game ends there.
- After the dice roll, if a piece is supposed to move outside position 100, it does not move.
- The board also contains some snakes and ladders. Each snake will have its head at some number and its tail at a smaller number. Whenever a piece ends up at a position with the head of the snake, the piece should go down to the position of the tail of that snake.
- Each ladder will have its start position at some number and end position at a larger number. Whenever a piece ends up at a position with the start of the ladder, the piece should go up to the position of the end of that ladder.
- There could be another snake/ladder at the tail of the snake or the end position of the ladder and the piece should go up/down accordingly.
Assumptions
- There won’t be a snake at 100.
- There won’t be multiple snakes/ladders at the same start/head point.
- It is possible to reach 100, i.e., it is possible to win the game.
- Snakes and Ladders do not form an infinite loop.
class Snake:
def __init__(self, start, end):
self.start = start
self.end = end
class Ladder:
def __init__(self, start, end):
self.start = start
self.end = end
class Board:
def __init__(self, size = 100):
self.size = size
self.snake_list = []
self.ladder_list = []
def add_snake(self, snake):
self.snake_list.append(snake)
def add_ladder(self, ladder):
self.snake_list.append(ladder)
class Dice:
dice_count = 1
@staticmethod
def roll():
return random.randint(1 * Dice.dice_count, 6 * Dice.dice_count)
class Player:
def __init__(self, name):
self.name = name
class PlayerPosition:
def __init__(self, player, position):
self.player = player
self.position = position
def update_position(self, new_position):
self.position = new_position
class Game:
def __init__(self, board):
self.board = board
self.players_position = []
def add_players(self, player, position=0):
player_position = PlayerPosition(player, position)
self.players_position.append(player_position)
def check_win_condition(self, position):
if position == self.board.size:
return True
return False
def check_for_snake(self, new_position):
for snake in self.board.snake_list:
start, end = snake.start, snake.end
if start == new_position:
return end
def check_for_ladder(self, new_position):
for ladder in self.board.ladder_list:
start, end = ladder.start, ladder.end
if start == new_position:
return end
def find_new_position(self, new_position):
if self.check_for_snake(new_position):
return self.check_for_snake(new_position)
elif self.check_for_ladder(new_position):
return self.check_for_ladder(new_position)
else:
return new_position
def start(self):
still_playing = len(self.players_position)
while(still_playing):
for player_position in self.players_position:
dice_value = Dice.roll()
current_position = player_position.position
new_position = current_position + dice_value
if new_position < self.board.size:
new_position = self.find_new_position(new_position)
player_position.update_position(new_position)
print(player_position.player.name, 'moved from', current_position, 'to', new_position)
if self.check_win_condition(new_position):
print("player", player_position.player.name, "wins!")
player_position.update_position(new_position + 1)
still_playing -= 1
class GameRunner:
@classmethod
def run_game(cls):
board = Board()
s1 = Snake(62, 5)
s2 = Snake(33, 6)
s3 = Snake(49, 9)
s4 = Snake(56, 53)
s5 = Snake(98, 64)
s6 = Snake(88, 16)
s7 = Snake(93, 73)
s8 = Snake(95, 75)
l1 = Ladder(2,37)
l2 = Ladder(27, 46)
l3 = Ladder(10, 32)
l4 = Ladder(51, 68)
l5 = Ladder(61, 79)
l6 = Ladder(65, 84)
l7 = Ladder(71, 91)
l8 = Ladder(81, 100)
board = Board()
board.add_ladder(l1)
board.add_ladder(l2)
board.add_ladder(l3)
board.add_ladder(l4)
board.add_ladder(l5)
board.add_ladder(l6)
board.add_ladder(l7)
board.add_ladder(l8)
board.add_snake(s1)
board.add_snake(s2)
board.add_snake(s3)
board.add_snake(s4)
board.add_snake(s5)
board.add_snake(s6)
board.add_snake(s7)
board.add_snake(s8)
player1 = Player("python")
player2 = Player("java")
player3 = Player("go")
game = Game(board)
game.add_players(player1)
game.add_players(player2)
game.add_players(player3)
game.start()
GameRunner.run_game()
Please suggest places of improvements and corrections on this.
1 Answer 1
Unnecessary classes
The snake, ladder and dice classes are not at all useful. They can simply be replaced with a namedtuple or a dataclass.
Similarly, player and playerposition should both be a single class element. A player object should be responsible for keeping track of their position.
Verbosity
s1, s2, ... s8 and similary l1, l2, ... l8 are not really used. Keep a tuple of positions, and iterate over them, calling either add_ladder
or add_snake
accordingly.
Control flow
The position updates should happen at the player's end, and not the game. The print statement for when player position gets updated would happen inside player class. The game's object is only to control and validate moves.
There are no statements showing when a player encounters a ladder or a snake. just their position changes in that verbose print statement mesh.
Double execution
if self.check_for_snake(new_position):
return self.check_for_snake(new_position)
elif self.check_for_ladder(new_position):
return self.check_for_ladder(new_position)
else:
return new_position
You have the same function being called twice, twice. For a major set of the board cells, there is neither a snake, nor a ladder. Yet, you keep calling both the functions.
Alternatives
Dice roll
sum(random.choices(range(1, 7), k=dice_count))
Win condition
def check_win_condition(self, position):
return position == self.board.size
Snake head or ladder base check
Keep an account using a set
or tuple
for board's snakes and ladder being added. Board should validate if a position has either snake's head or ladder's base there. You are currently iterating over all the snakes and ladders (twice, as mentioned above) for each position, whereas a lookup would be \$ O(1) \$.
-
\$\begingroup\$ Just too many irrelevant classes \$\endgroup\$theProgrammer– theProgrammer2020年10月16日 11:47:59 +00:00Commented Oct 16, 2020 at 11:47
Explore related questions
See similar questions with these tags.