Here is my attempt at writing Conway's Game of Life (http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Rules) in Ruby.
I have a very specific question about the "count_neighbours" method. Basically when I get to the edge of the grid I get some odd behaviour. When I parse Row 0 and reach the last column (Cloumn 4) it does something like this:
Evaluating cell: R: 0 C: 4
One good thing is that "R: -1" essentially wraps the evaluation around to the bottom of the grid as if the edges to the grid are really connected. I like the idea of an edgeless grid.
The bad thing here is that the method is trying to evaluate "C: 5" (Column 5) which doesn't exist because in this example the grid is columns 0-4. So that is the first problem I am seeking help to fix. Ideally here I want to evaluate column 0.
The next problem is when the method attempts to evaluate the last row on the grid, Row 4. When the method attempts to evaluate "R: 5" there is an error thrown. The error is "undefined method `[ ]' for nil:NilClass (NoMethodError)" because that row does not exist. To avoid the error being thrown I have added the line below the comment "#This line is a hack" but I want to be able to evaluate this row without an error.
The last question I have is, why does going beyond the last row to row 5 throw and error when going beyond the last column does not throw and error?
#Dimensions for the game grid
WIDTH = 5
HEIGHT = 5
def rand_cell
rand(2)
end
def starting_grid
#Initialise the playing grid
@start_grid = Array.new(WIDTH){Array.new(HEIGHT)}
#Randomly generate starting state for each cell on the grid
@start_grid.each_with_index do |row, rindex|
row.each_with_index do |col, cindex|
@start_grid[rindex][cindex] = rand_cell
end
end
end
def next_grid
#build the next generation's grid to load values into
@next_gen_grid = Array.new(WIDTH){Array.new(HEIGHT)}
#parse each cell in the start grid to see if it lives in the next round
@start_grid.each_with_index do |row, rindex|
row.each_with_index do |col, cindex|
puts "\n\nEvaluating cell: R: #{rindex} C: #{cindex}"
@next_gen_grid[rindex][cindex] = will_cell_survive(rindex, cindex)
end
end
#move the newly generated grid to the start grid as a sterting point for the next round
@start_grid = @next_gen_grid
end
def show_grid(grid)
#Display the evolving cell structures in the console
grid.each_with_index do |row, rindex|
row.each_with_index do |col, cindex|
if grid[rindex][cindex] == 1
print "️⬛️ "
else
print "⬜️ ️"
end
end
puts "\n"
end
end
def count_neighbours(row, col)
cell_count = 0
rows = [-1, 0, 1]
cols = [-1, 0, 1]
rows.each do |r|
cols.each do |c|
#ingnore the cell being evaluated
unless c == 0 && r == 0
#This line is a hack to stop an error when evaluating beyond the last row
if row != HEIGHT-1
puts "Evaluating neighbor R: #{row+r} C: #{col+c}. State: #{@start_grid[(row+r)][(col+c)]}"
if @start_grid[(row+r)][(col+c)] == 1
cell_count += 1
end
end
end
end
end
puts "Neighbour count is #{cell_count}"
return cell_count
end
def will_cell_survive(rindex, cindex)
count = count_neighbours(rindex, cindex)
#If the cell being evaluated is currently alive
if @start_grid[rindex][cindex] == 1
#test rule 1
if alive_rule1(count)
puts "Rule 1"
return 0
#test rule 2
elsif alive_rule2(count)
puts "Rule 2"
return 1
elsif
#test rule 3
puts "Rule 3"
return 0
end
#If the cell being evaluated is currently dead
else
#test rule 4
alive_rule4(count)
puts "Rule 4"
return 1
end
end
def alive_rule1(neighbour_count)
neighbour_count < 2
end
def alive_rule2(neighbour_count)
neighbour_count == 2 || neighbour_count == 3
end
def alive_rule3(neighbour_count)
neighbour_count > 3
end
def alive_rule4(neighbour_count)
neighbour_count == 3
end
#Run just one round of the game
system "clear"
starting_grid
show_grid(@start_grid)
puts "\n\n"
next_grid
show_grid(@next_gen_grid)
#Initiate the game grid
# system "clear"
# starting_grid
#Run the game
# 200.times do |t|
# system "clear"
# puts "\n\n"
# next_grid
# puts "Grid #{t}"
# show_grid(@next_gen_grid)
# sleep(0.25)
# end
[EDIT]: The code with the answer implemented is at https://github.com/AxleMaxGit/ruby-conways-game
If you want to connect the edges to each other (which by the way creates a "torus" shape, or if you prefer is the "asteroids" world model where you can never leave the screen) then the simplest adjustment is to work in modular arithmetic:
Change:
if @start_grid[(row+r)][(col+c)] == 1
To:
if @start_grid[(row+r) % HEIGHT][(col+c) % WIDTH] == 1
The operator symbol %
is modular arithmetic, and does the wrap-around logic precisely as you need it.
The reason why going beyond the last row behaves differently to going beyond the last column is because:
@start_grid[ 3 ][ 5 ] == nil
which returns false in your check for neighbour, and everything else works as normal.
However,
@start_grid[ 5 ][ 3 ]
is a problem, because @start_grid[ 5 ]
is nil
, so it is effectively
nil[ 3 ]
the error is thrown because Ruby has no logic for resolving what []
means on a nil
.