Skip to content

Instantly share code, notes, and snippets.

@avdi
Created September 30, 2012 00:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avdi/3805513 to your computer and use it in GitHub Desktop.
Save avdi/3805513 to your computer and use it in GitHub Desktop.
Game of Life, mildly functional version
#!/usr/bin/env ruby
# The game board, at one point in time
class BoardGeneration
# How an instance will build a copy with a new grid
def self.from_grid(grid)
allocate.tap do |bg|
bg.grid = grid
end
end
# This is only used in initialization from .from_grid
attr_writer :grid
# Build a new board from a text representation
def initialize(text)
lines = text.lines.map(&:chomp)
@grid = grid_from_lines(*lines)
end
# The board can generate a next generation of itself
def next_generation
# self
self.class.from_grid(next_grid_generation(@grid, self))
end
# produce a list of neighbors (either LiveCell or DeadCell) for a
# given x/y coordinate
def neighbors(x,y)
[
self[x-1,y-1], self[x, y-1], self[x+1, y-1],
self[x-1,y], self[x+1, y],
self[x-1,y+1], self[x, y+1], self[x+1, y+1]
]
end
# Get a cell (LiveCell or DeadCell) at the given coords. Coords "off
# the grid" are considered DeadCells.
def [](x,y)
@grid.fetch(y){ [] }.fetch(x){ DeadCell }
end
# The board can represent itself as text
def to_s
@grid.map do |row|
row.map(&:to_s).join + "\n"
end.join
end
private
# Return a grid (list of lists of cells) from a list of strings
def grid_from_lines(*lines)
lines.map do |line|
row_from_line(line)
end
end
# Return a list of cells from a string
def row_from_line(line)
line.chars.map do |char|
cell_from_char(char)
end
end
# Given a character 'o' or '.', return LiveCell or DeadCell
def cell_from_char(char)
case char
when ?o then LiveCell
when ?. then DeadCell
else raise Exception, "Bug: #{char.inspect}"
end
end
# Given a grid (list of lists of cells), return the next generation
# grid
def next_grid_generation(grid, board)
grid.map.each_with_index { |row, y|
row.map.each_with_index { |cell, x|
cell.next_generation(x, y, board)
}
}
end
end
# Since cells will have no mutable state, we don't really need
# multiple instances. So We'll just define some singleton objects.
class <<(LiveCell = Object.new)
def to_s() 'o' end
def next_generation(x, y, board)
case board.neighbors(x,y).count(LiveCell)
when 2..3 then self
else DeadCell
end
end
end
class <<(DeadCell = Object.new)
def to_s() '.' end
def next_generation(x, y, board)
case board.neighbors(x,y).count(LiveCell)
when 3 then LiveCell
else self
end
end
end
# The imperative shell
def life(initial_board)
board = BoardGeneration.new(initial_board)
loop do
# Clear the screen.
print "\e[H\e[2J"
puts board.to_s
board = board.next_generation
sleep 0.5
end
end
glider = <<END
.o......
..o.....
ooo.....
........
........
........
........
........
END
life(glider)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment