Skip to content

Instantly share code, notes, and snippets.

@jacegu
Created December 9, 2012 19:38
Show Gist options
  • Save jacegu/4246676 to your computer and use it in GitHub Desktop.
Save jacegu/4246676 to your computer and use it in GitHub Desktop.
Functional take on Conway's Game of Life
module Cell
def cell
{ state: :alive, neighbours: [] }
end
def dead_cell
{state: :dead, neighbours: []}
end
def neighbours_of(cell, neighbour_info)
cell.merge!(neighbours: neighbour_info[:are])
end
def evolve_cell(cell)
if underpopulated?(cell) or overpopulated?(cell)
dead_cell
else
cell
end
end
def underpopulated?(cell)
neighbour_count(cell) < 2
end
def overpopulated?(cell)
neighbour_count(cell) > 3
end
def neighbour_count(cell)
cell[:neighbours].length
end
end
module Organism
include Cell
def organism(cells = {})
cells
end
def evolved(organism)
organism.each_with_object({}){ |(name, cell), evolved| evolved[name] = evolve_cell(cell) }
end
def cell_named(name, organism_info)
organism_info[:from][name]
end
end
RSpec.configure do |config|
config.include(Organism)
end
RSpec::Matchers.define :be_alive do
match do |cell|
cell[:state] == :alive
end
end
RSpec::Matchers.define :be_dead do
match do |cell|
cell[:state] == :dead
end
end
describe 'evolution' do
context 'in an organism with a cell without neighbours' do
it 'kills that cell' do
cell_named(:c1, from: evolved(organism(c1: cell))).should be_dead
end
end
context 'in an organism with a cell with 2 neighbours' do
it 'keeps that cell alive' do
c1, c2, c3 = cell, cell, cell
neighbours_of c1, are: [c2, c3]
neighbours_of c2, are: [c1]
neighbours_of c3, are: [c1]
evolved_organism = evolved(organism(c1: c1, c2: c2, c3: c3))
cell_named(:c1, from: evolved_organism).should be_alive
cell_named(:c2, from: evolved_organism).should be_dead
cell_named(:c3, from: evolved_organism).should be_dead
end
end
context 'in an organism with a cell with 3 neighbours' do
it 'keeps that cell alive' do
c1, c2, c3, c4 = cell, cell, cell, cell
neighbours_of c1, are: [c2, c3, c4]
neighbours_of c2, are: [c1, c3]
neighbours_of c3, are: [c1, c2]
neighbours_of c4, are: [c1]
evolved_organism = evolved(organism(c1: c1, c2: c2, c3: c3, c4: c4))
cell_named(:c1, from: evolved_organism).should be_alive
cell_named(:c2, from: evolved_organism).should be_alive
cell_named(:c3, from: evolved_organism).should be_alive
cell_named(:c4, from: evolved_organism).should be_dead
end
end
context 'in an organism with a cell with 4 neighbours' do
it 'kills that cell' do
c1, c2, c3, c4, c5 = cell, cell, cell, cell, cell
neighbours_of c1, are: [c2, c3, c4, c5]
neighbours_of c2, are: [c1, c3]
neighbours_of c3, are: [c1, c2]
neighbours_of c4, are: [c1]
neighbours_of c5, are: [c1]
evolved_organism = evolved(organism(c1: c1, c2: c2, c3: c3, c4: c4, c5: c5))
cell_named(:c1, from: evolved_organism).should be_dead
cell_named(:c2, from: evolved_organism).should be_alive
cell_named(:c3, from: evolved_organism).should be_alive
cell_named(:c4, from: evolved_organism).should be_dead
cell_named(:c5, from: evolved_organism).should be_dead
end
end
end
@jacegu
Copy link
Author

jacegu commented Dec 9, 2012

This is a functional take on Conway's Game of Life written in Ruby. We ended up taking this approach because of the restriction for that iteration: no properties.

@alberto
Copy link

alberto commented Dec 12, 2012

Nice, quite readable.

You have a couple of bugs, though:

  • A dead cell with 3 neighbours should come to life, which I don't think your implementation does.
  • You don't keep the neighbours on evolving to a dead_cell, so subsequent generations will deviate from the expected results.

Also, there are two things I would do different:

  • Neighbourship is reciprocal, so I don't like the fact that you have two define it both ways.
  • evolve_cell returns a new dead_cell or the cell itself. It's a bit subtle, since the parameter name is the same as the "new cell" function. Maybe a more symmetrical behaviour would be clearer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment