A little while ago I wrote this implementation of Conway's Game of Life in Ruby. It's based on this Game of Life in APL video, which I think is rather elegant, though a bit dense. Feel free to give feedback on any aspect of the program, but the parts I most care about are readability and the way I work with matrices.
Unlike most implementations, the neighbor count used to determine who's alive in the next generation includes self (the cell who's neighbors we are counting). This technique is used in the APL version as well and allows us to express the rules of the game in a more concise manner than normal: i.e. a cell in the next generation is alive if its neighbor count including self is three or if it was alive in the last generation and its neighbor count including self is four.
The program currently monkey-patches Matrix
, which I'm not thrilled about. I'm pretty sure Matrix#translate
can be removed in favor of some sort of matrix math that would do the equivalent, but I'm not familiar enough with the subject to know how to do this well.
It would also probably be nice to blank the screen without shelling out.
The program requires Ruby 1.9 or later.
#!/usr/bin/env ruby
# encoding: utf-8
require 'matrix'
class Matrix
def translate(row_count, col_count)
Matrix[
*row_vectors.map do |row|
row.to_a.rotate(row_count)
end.rotate(col_count)
]
end
end
class Board
attr_reader :width, :height
def self.random(size)
board = Matrix.build(size) { rand(10) == 0 ? 1 : 0 }
new(board)
end
def self.parse(board_string)
new(Matrix[board_string.split("\n").map { |row| row.split.map(&:to_i) }])
end
def initialize(board)
@width = board.column_size
@height = board.row_size
@board = board
end
def next
board_state = neighbor_counts.map.with_index do |count, idx|
count == 3 || count == 4 && @board[idx / width, idx % width] == 1 ? 1 : 0
end
Board.new(board_state)
end
def to_s
@board.row_vectors.map do |row|
row.map do |n|
n == 1 ? "B" : "·"
end.to_a.join(" ")
end.join("\n")
end
def neighbor_counts
[-1,0,1].product([-1,0,1]).map do |row, col|
@board.translate(row, col)
end.reduce(:+)
end
end
if __FILE__ == $0
b = Board.random(40)
system("clear")
loop do
puts b
sleep 0.2
system("clear")
b = b.next
end
end