life.rb 


Life is a class that implements Conway’s Game of life, a simple cellular automaton. The Game of Life is “played” on a twodimensional grid of square cells, each of which is in one of two possible states, alive or dead. The game plays out in timesteps called ticks. At every tick, each cell interacts with its eight neighours. At each step, the following transitions occur:
I have implemented this code in a Test Driven manner in order to practice TDD and to play with the new Ruby testing library Minitest. The tests can be found here. There is also a simple Shoes app for visualizing the game. The code has been tested using Ruby 1.9.2 and 1.8.7. Github Repo: /stungeye/tdd_gam_of_life 

In the Beginning was the Word 

Our 
class Life
attr_reader :cells, :width, :height 

class OutOfBoundsError < ArgumentError; end 
When constructing a So, for example, if you had two live cells in the grid, one at x=1 and y=0 and another at x=2 and y=3, the array would be:
Side Note: Based on the number of times 
def initialize(cells, width, height)
@cells = cells
@width = width
@height = height
end 
These are the People in Your Neighbourhood 

Given two sets of x, y coordinates, are they 
def neighbour?(x1, y1, x2, y2) 
Remember, you are not your own neighbour! 
return false if x1 == x2 && y1 == y2 
A neighbour is an adjecent cell. In otherwords, neither our delta x or our delta y can be greater than one. 
(x1  x2).abs <= 1 && (y1  y2).abs <= 1
end 
The 
def neighbours(x, y) 
If the x, y arguments are out of bounds we raise our custom 
raise OutOfBoundsError if out_of_bounds x, y
neighbours_found = 0 
This method is compuationally expensive. As such, the performance degrades as our cells count grows. 
@cells.each do cx, cy
neighbours_found += 1 if neighbour? cx, cy, x, y
end
neighbours_found
end 
Life, The Next Generation 

The 
def next_gen 
We start with an empty cell array… 
@next_cells = []
@height.times do y
@width.times do x 
…and we add cells depending on the rules of the game. 
@next_cells << [x,y] if should_remain_alive?(x, y) 
should_come_to_life?(x, y)
end
end 
Finally we return a new Life object instantiated with the next generation of cells. 
Life.new @next_cells, @width, @height
end 
The Rules of Life 

Cells that are alive should remain so if they have 2 or 3 neighbours. 
def should_remain_alive?(x,y)
alive?(x,y) && (2..3).include?(neighbours(x,y))
end 
Cells that are dead should come to life if they have exactly 3 neighbours. 
def should_come_to_life?(x, y)
dead?(x,y) && neighbours(x,y) == 3
end 
Helper Methods 

A cell at a particular x,y coordinate is alive if it can be found in our
array of 
def alive?(x, y)
@cells.include?([x, y])
end 
A cell at a particular x,y coordinate is dead if it is not alive. :) 
def dead?(x,y)
!alive?(x, y)
end 
Allow us to bring specific cells to life, as long as they aren’t already alive. This method is only used in the Shoes GUI. 
def set_cell_alive!(x, y)
@cells << [x, y] if !alive?(x,y)
end

Our grid bounaries are zerobased and depend on our 
def out_of_bounds(x, y)
x < 0  x >= @width  y < 0  y >= @height
end

Our Life object can output a string representation of itself. Live cells are shown as asteriks and dead cells as minus signs. 
def to_s
output = ""
@height.times do y
@width.times do x
output << (@cells.include?([x,y]) ? '*' : '')
end
output << "\n"
end
output
end
end 
UNLICENSEThis is free and unencumbered software released into the public domain. 
