Created
January 28, 2014 20:59
-
-
Save piersadrian/8676401 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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