Skip to content

Instantly share code, notes, and snippets.

@piersadrian
Created January 28, 2014 20:59
Show Gist options
  • Save piersadrian/8676401 to your computer and use it in GitHub Desktop.
Save piersadrian/8676401 to your computer and use it in GitHub Desktop.
class GameOfLife
def initialize(size)
@size = size
@grid = make_grid(true)
@next_grid = make_grid(false)
@grid_hash = nil
# Create offset masks excluding [0, 0], which is the cell in question. The
# masks look like [-1, 0] or [1, 1] and represent the offset from the
# current cell to each of its eight neighbors.
offsets = [-1, 0, 1]
@masks = offsets.product(offsets) - [[0, 0]]
end
def play!
while continue_game?
tick
print_grid
sleep(0.25)
end
end_game!
end
private
# Advances the state of the game grid by looping over every row and cell,
# determining whether each cell should live or die, and creates a new
# game grid full of new cells.
#
def tick
@grid.each_with_index do |row, x|
row.each_with_index do |cell, y|
# Calling #reduce lets us loop through each of the offset masks and
# keep passing the sum, starting at 0, of alive neighbors. The sum
# (called 'count') is returned from the do..end block each time
# the block is called, and then immediately passed into the next
# call of the block as the new value of 'count'.
#
# That means 'count' is incremented by either 1 or 0 depending on
# whether or not the current neighbor is alive. The final sum
# is returned from #reduce and stored in 'neighbors'.
#
# The 'offset' variable looks like [-1, 0] or [1, 1]. See above
# where '@masks' is created in #initialize.
neighbors = @masks.reduce(0) do |count, offset|
# Splits the 'offset' out into two variables. If 'offset'
# is [-1, 0], then 'offset_x' becomes -1 and 'offset_y'
# becomes 0.
offset_x, offset_y = offset
neighbor_x = (x + offset_x) % @size
neighbor_y = (y + offset_y) % @size
count += @grid[neighbor_x][neighbor_y] ? 1 : 0
end
cell_alive = @grid[x][y]
# This describes all four Game of Life rules in one statement, and
# sets the current cell's value in the next grid as 'true' or 'false'.
@next_grid[x][y] = (cell_alive && neighbors == 2) || (neighbors == 3)
end
end
@grid = @next_grid
end
# Returns 'true' if the game state has changed since the last tick and if
# there are still living cells. Returns 'false' otherwise.
#
def continue_game?
previous_grid_hash = @grid_hash
@grid_hash = compute_grid_hash
(previous_grid_hash != @grid_hash) && (@grid_hash.count("1") > 0)
end
# Does what it says on the tin.
#
def end_game!
puts "FINISHED!"
end
# Creates a string that uniquely identifies the current state of the grid
# by converting each 'true' cell into the string "1" and each 'false' cell
# into the string "0".
#
# This results in a string that looks like "01101101010010010010101101".
# It's used in the #continue_game? method to determine whether or not the
# game grid has changed since the last tick. If not, the game ends.
#
def compute_grid_hash
@grid.flatten.reduce("") do |string, cell|
string << (cell ? "1" : "0")
end
end
# Creates a two-dimensional grid as an array of arrays. Pass 'true' to
# fill each cell with true or false randomly, or 'false' to leave the
# cells empty.
#
def make_grid(fill_grid)
Array.new(@size).fill do
ary = Array.new(@size)
ary.fill { [true, false].sample } if fill_grid
ary
end
end
# Prints the current state of the game grid to the screen.
#
def print_grid
cell_width = 3
puts "\n\n"
puts "-" * (@size * cell_width + 2)
@grid.each do |row|
print "|"
row.each do |cell|
print (cell ? "O" : " ").center(cell_width)
end
print "|"
puts
end
puts "-" * (@size * cell_width + 2)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment