# Snake and ladder problem oops application

I am learning and trying to implement the best practice for snake and ladder game.

Rules of the game

1. The board will have 100 cells numbered from 1 to 100.
2. The game will have a six sided dice numbered from 1 to 6 and will always give a random number on rolling it.
3. Each player has a piece which is initially kept outside the board (i.e., at position 0).
4. 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).
5. A player wins if it exactly reaches the position 100 and the game ends there.
6. After the dice roll, if a piece is supposed to move outside position 100, it does not move.
7. 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.
8. 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.
9. 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.
• 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.snake_list.append(snake)


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 = []

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

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)
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)

board = Board()

player1 = Player("python")
player2 = Player("java")
player3 = Player("go")

game = Game(board)

game.start()



GameRunner.run_game()

Please suggest places of improvements and corrections on this.

## 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)
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


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) \$$.