2
\$\begingroup\$

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

Instead of magic numbers, extract them to either explaining variables or constants. For example, in neighbor_counts, what are -1, 0, and 1? People who are familiar with the GoL might know what you're trying to do, but assume you're passing this off to someone who is new to the GoL and doesn't know what these constants represent.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.