# Ruby Game of Life with matrices

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

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
• For nice printing you should use curses. Ruby's implementation has some problems, but they are googlable and are not so awful. May 6, 2013 at 17:29