Skip to main content
Code Review

Return to Question

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

I'm not a beginner to programming, but I am to programming in OOP (effectively at least). So to learn better I wrote an object oriented game of Snakes and Ladders. For background, see this question this question.

I'm not a beginner to programming, but I am to programming in OOP (effectively at least). So to learn better I wrote an object oriented game of Snakes and Ladders. For background, see this question.

I'm not a beginner to programming, but I am to programming in OOP (effectively at least). So to learn better I wrote an object oriented game of Snakes and Ladders. For background, see this question.

Tweeted twitter.com/#!/StackCodeReview/status/537941843615707136
edited title
Link
Mohamad
  • 2k
  • 16
  • 30

Object Ruby object oriented Snakes and Ladders implementation

Source Link
Mohamad
  • 2k
  • 16
  • 30

Object oriented Snakes and Ladders implementation

I'm not a beginner to programming, but I am to programming in OOP (effectively at least). So to learn better I wrote an object oriented game of Snakes and Ladders. For background, see this question.

I wrote four classes: Board, Cell, Portal, and Player. All are self explanatory except for Portal. A Portal object represents a snake or a ladder cell. Portals are just cells with behaviour. Their behaviour is identical save for the direction they teleport you to. Snakes send you back, while ladders send you forward.

A Cell object keeps track of any players that land on it. It is also aware of its location.

class Cell
 attr_reader :location, :players
 def initialize(input)
 @location = input.fetch(:location)
 @players = []
 end
 def exit(player)
 evict player
 end
 def enter(player, board)
 admit player
 player.set_cell(self)
 end
private
 def admit(player)
 players << player
 end
 def evict(player)
 players.delete(player)
 end
end

A Portal object inherits from Cell. It adds behaviour, and has a destination attribute. It overrides the enter method in its superclass. Instead of admitting the player it tells the board to send it to the portal's destination.

class Portal < Cell
 attr_reader :destination
 def initialize(input)
 @destination = input.fetch(:destination)
 super
 raise ArgumentError, "Location and destination can not be the same" if location.equal?(destination)
 end
 def enter(player, board)
 board.move player, location, destination
 end
end

The Player object must know of the Cell it resides in. It is also responsible for rolling the die.

class Player
 attr_reader :name
 attr_accessor :current_cell
 def initialize(input)
 @name = input.fetch(:name)
 end
 def to_s
 name
 end
 def roll_dice
 rand(1..6)
 end
 def set_cell(cell)
 self.current_cell = cell
 end
 def position
 if current_cell
 current_cell.location
 else
 -1 # represents off the board state
 end
 end
end

Finally, the Board keeps track of the current player, the number of turns, and moves players in between cells. It also decides if the game has a winner.

class Board
 attr_reader :grid, :players, :die
 attr_accessor :turn
 def initialize(input = {})
 @grid = input.fetch(:grid, default_grid)
 @players = []
 @turn = 0
 end
 def add_player(player)
 players << player
 end
 def get_cell(index)
 grid[index]
 end
 def move(player, from, to)
 get_cell(from).exit player if game_started?
 get_cell(to).enter player, self
 end
 def play_turn
 current_player_position = current_player.position
 roll = current_player.roll_dice
 if winner?(roll)
 puts "#{current_player} rolls a #{roll} and wins! Congratulations!"
 exit
 end
 move current_player, current_player.position, current_player.position + roll
 puts "#{current_player} rolls a #{roll} and moves from #{current_player.position - roll} to #{current_player.position}"
 increment_turn
 play_turn
 end
private
 def default_grid
 Array.new(100)
 end
 def current_player
 players.fetch(turn % players.size)
 end
 def increment_turn
 self.turn += 1
 end
 def game_started?
 turn > 0
 end
 def winner?(last_roll)
 # +1 is to account for arrays starting at 0. So cell 1 is in fact cell 2 on a board.
 (current_player.position + 1 + last_roll) >= grid.size
 end
end

Concerns

I think I've done a decent job for my first crack, but I know this can be improved.

  1. I am not sure about the die rolling and player moving mechanic. It feels clunky. Is passing the cell to the user a necessary dependency? I can't think of a better way to get the player to move and change its position.
  2. The play turn uses recursion, but again, I'm not sure how to write it better.

That arrays in Ruby start at 0 is causing me all sorts of confusion.

  1. To render the board I must add 1 to each cell location.
  2. I must add 1 to the player position or deduct 1 from the grid size to work out the winner.
  3. Players start off the board. I must check if the player has a cell, and if not, I have to return -1 to represent the off the board state.

There are probably lots of issues I'm not aware of. My objectives are the following. I think so far I might have succeeded with 2, but failed with 1 and 3.

  1. Make the code easy to modify. Let's say I want to add a Rule object that can store different winning criteria (roll to an excess of 100 or land exactly on 100).
  2. Introduce other cell subclasses with different effects.
  3. Make the game state easy to save so that I can player across HTTP requests.

Here's an example of running the game:

game = SnakesAndLadders.classic
m = SnakesAndLadders::Player.new name: "Mario", color: "Red"
l = SnakesAndLadders::Player.new name: "Luigi", color: "Green"
game.add_player m
game.add_player l
game.play_turn
lang-rb

AltStyle によって変換されたページ (->オリジナル) /