Created
September 30, 2012 00:48
-
-
Save avdi/3805513 to your computer and use it in GitHub Desktop.
Game of Life, mildly functional version
This file contains 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
#!/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