patternrubyMinor
Ruby implementation of Conway's Game of Life
Viewed 0 times
conwaygamerubylifeimplementation
Problem
I'm just starting with Ruby and decided to do two different implementations of Conway's Game of Life. In this first one I'm not using
Anyway, I want this code to display good Object-Oriented programming basics as well as construction via test-driven development, using rspec.
Please critique away and tell me if it's under/over engineered for the problem, not extensible enough or too extended, as well as if my TDD progression was worthwhile, did I do enough or too few tests, or maybe not the right tests.
board.rb:
```
class Board
attr_accessor :width,:height
def initialize(cells=[], width = 3, height = 3)
@cells = cells
@height = height
@width = width
end
def live_cells
@cells
end
def is_alive?(x,y)
@cells.include?([x,y])
end
def neighbors_to(x,y)
@cells.select do |cell_x, cell_y|
dx = (x-cell_x).abs
dy = (y-cell_y).abs
#If the tuple x,y is a neighbor of that cell then the following is true
dx <= 1 and dy <= 1 and not (dx == 0 and dy == 0)
end.length
#We only care how many there are and do not care which cells in particular
end
def evolve
survivors = []
#Scan the entire board (may be able to be more clever here but you need to check each dead cell for ressurection)
@width.times do |x|
@height.times do |y|
survivors << [x,y] if should_survive?(x,y) or should_ressurect?(x,y)
end
end
#Populating the new board from survivors allows reference to the current generation while populating the next generation
Board.new(survivors, self.width,self.height)
end
def should_survive?(x,y)
(neighbors_to(x,y) == 2 or neighbors_to(x,y) == 3) and is_alive?(x,y)
end
def should_ressurect?(x,y)
neighbors_to(x,y) == 3 and not is_alive?(x,y)
end
def print_board
puts "Current Generation"
puts
@width.times do |x|
@hei
cells as a separate class and instead just track everything as a location on the board, using a state pattern I guess. Anyway, I want this code to display good Object-Oriented programming basics as well as construction via test-driven development, using rspec.
Please critique away and tell me if it's under/over engineered for the problem, not extensible enough or too extended, as well as if my TDD progression was worthwhile, did I do enough or too few tests, or maybe not the right tests.
board.rb:
```
class Board
attr_accessor :width,:height
def initialize(cells=[], width = 3, height = 3)
@cells = cells
@height = height
@width = width
end
def live_cells
@cells
end
def is_alive?(x,y)
@cells.include?([x,y])
end
def neighbors_to(x,y)
@cells.select do |cell_x, cell_y|
dx = (x-cell_x).abs
dy = (y-cell_y).abs
#If the tuple x,y is a neighbor of that cell then the following is true
dx <= 1 and dy <= 1 and not (dx == 0 and dy == 0)
end.length
#We only care how many there are and do not care which cells in particular
end
def evolve
survivors = []
#Scan the entire board (may be able to be more clever here but you need to check each dead cell for ressurection)
@width.times do |x|
@height.times do |y|
survivors << [x,y] if should_survive?(x,y) or should_ressurect?(x,y)
end
end
#Populating the new board from survivors allows reference to the current generation while populating the next generation
Board.new(survivors, self.width,self.height)
end
def should_survive?(x,y)
(neighbors_to(x,y) == 2 or neighbors_to(x,y) == 3) and is_alive?(x,y)
end
def should_ressurect?(x,y)
neighbors_to(x,y) == 3 and not is_alive?(x,y)
end
def print_board
puts "Current Generation"
puts
@width.times do |x|
@hei
Solution
I won't talk about style, because @tokland's comments are spot-on.
However, some quick notes about efficiency. For large board sizes, this implementation is inefficient.
For example,
You can reduce the call to one, and swap the order so you don't have to call it at all if the cell is alive:
The implementation of
This also means you need to be able to query a cell directly, rather than scan all live cells as your
However, some quick notes about efficiency. For large board sizes, this implementation is inefficient.
For example,
neighbors_to scans every cell on the board, and it is called multiple times. It is called twice from the should_survive? method:def should_survive?(x,y)
(neighbors_to(x,y) == 2 or neighbors_to(x,y) == 3) and is_alive?(x,y)
endYou can reduce the call to one, and swap the order so you don't have to call it at all if the cell is alive:
def should_survive?(x,y)
is_alive?(x,y) and [2,3].include? neighbors_to(x,y)
endThe implementation of
neighbors_to should simply collect each of the surrounding eight (maximum) spaces rather than scan the entire set of live cells.This also means you need to be able to query a cell directly, rather than scan all live cells as your
is_alive? method currently does. I would recommend representing a board using a 2D array of spaces. I'd probably rename @cells to @live_cells to avoid confusion.Code Snippets
def should_survive?(x,y)
(neighbors_to(x,y) == 2 or neighbors_to(x,y) == 3) and is_alive?(x,y)
enddef should_survive?(x,y)
is_alive?(x,y) and [2,3].include? neighbors_to(x,y)
endContext
StackExchange Code Review Q#20402, answer score: 5
Revisions (0)
No revisions yet.