Skip to content

Instantly share code, notes, and snippets.

@iain
Created April 30, 2010 00:06
Show Gist options
  • Save iain/384487 to your computer and use it in GitHub Desktop.
Save iain/384487 to your computer and use it in GitHub Desktop.
Conway's Game of Life, in one line of Ruby
# Copyright 2010, Iain Hecker. All Rights Reserved
# Conway's Game of Life, in one line of Ruby.
# http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
# Tested and found working on Ruby 1.8.7 and 1.9.2
# The grid is spherical or "wrap around", so the left is connected to the right and top to bottom.
#
# Generate a random grid, 30 cells wide and 10 cells high
#
# grid = "30x10".to_grid
#
# Use a predefined grid. The lower case letter 'o' is used as an alive cell, any other character is dead.
# Newlines (\n) delimit rows in the grid.
#
# grid = some_grid_string.to_grid
#
# Get the next generation, with a grid you just created
#
# grid.next
#
# Get the string representation back:
#
# grid.to_s
String.class_eval { define_method(:to_grid) { (self =~ /\A(\d+)x(\d+)\z/ ? (0...split('x').last.to_i).map { |_| (0...split('x').first.to_i).map { |_| rand > 0.5 } } : split("\n").map { |row| row.split(//).map { |cell_string| cell_string == "o" } }).tap { |grid| grid.class.class_eval { define_method(:next) { each { |row| row.each { |cell| cell.class.class_eval { define_method(:next?) { |neighbours| (self && (2..3).include?(neighbours.select { |me| me }.size)) || (!self && neighbours.select { |me| me }.size == 3) } } } } && enum_for(:each_with_index).map { |row, row_num| row.enum_for(:each_with_index).map { |cell, col_num| cell.next?([ at(row_num - 1).at(col_num - 1), at(row_num - 1).at(col_num), at(row_num - 1).at((col_num + 1) % row.size), row.at((col_num + 1) % row.size), row.at(col_num - 1), at((row_num + 1) % size).at(col_num - 1), at((row_num + 1) % size).at(col_num), at((row_num + 1) % size).at((col_num + 1) % row.size) ]) } } } && define_method(:to_s) { map { |row| row.map { |cell| cell ? "o" : "." }.join }.join("\n") } } } } }
# Copyright 2010, Iain Hecker. All Rights Reserved
require 'game_of_life'
require 'test/unit'
class GameOfLifeTest < Test::Unit::TestCase
def test_block_example
before = <<GRID.strip
....
.oo.
.oo.
....
GRID
expected = before
assert_equal expected, before.to_grid.next.to_s
assert_equal before, expected.to_grid.next.to_s
end
def test_toad_example
before = <<GRID.strip
......
......
..ooo.
.ooo..
......
......
GRID
expected = <<GRID.strip
......
...o..
.o..o.
.o..o.
..o...
......
GRID
assert_equal expected, before.to_grid.next.to_s
assert_equal before, expected.to_grid.next.to_s
end
def test_beacon_example
before = <<GRID.strip
......
.oo...
.oo...
...oo.
...oo.
......
GRID
expected = <<GRID.strip
......
.oo...
.o....
....o.
...oo.
......
GRID
assert_equal expected, before.to_grid.next.to_s
assert_equal before, expected.to_grid.next.to_s
end
end
# Copyright 2010, Iain Hecker. All Rights Reserved
# The oneliner, but now with newlines, indenting and documentation
# Within the following closure the class String is "self"
String.class_eval {
# define an instance method
define_method(:to_grid) {
# check if this string contains dimensions
(self =~ /\A(\d+)x(\d+)\z/ ?
# Generate a range for the last part of the dimensions and change each item.
(0...split('x').last.to_i).map { |_|
# And also for the first part of the dimensions, thus making a 2-dimensional array (aka "the grid")
(0...split('x').first.to_i).map { |_|
# random true or false.
rand > 0.5
}
} :
# if it's not dimensions, assume it's a completly drawn grid, which must be splitted in rows
split("\n").map { |row|
# and every row splitted on every character
row.split(//).map { |cell_string|
# it's true (alive) when it's a lower case 'o'
cell_string == "o"
}
}
# modify (but always return the original object) with the result (which is the grid)
).tap { |grid|
# the class of the grid becomes "self"
grid.class.class_eval {
# and define the instance method "next" on it
define_method(:next) {
# for each row
each { |row|
# and each cell in each row
row.each { |cell|
# become the class of the content (TrueClass or FalseClass)
cell.class.class_eval {
# and define the instance method "next?", which accepts neighbours to know if it should die or live
define_method(:next?) { |neighbours|
# self is true or false (instances of resp. TrueClass and FalseClass), so this expression just returns a new boolean
(self && (2..3).include?(neighbours.select { |me| me }.size)) || (!self && neighbours.select { |me| me }.size == 3)
}
}
}
# map with index, is shorter in Ruby 1.9, but this strange construction works also in Ruby 1.8
} && enum_for(:each_with_index).map { |row, row_num|
# same, we're dealing a 2-dimensional array, remember?
row.enum_for(:each_with_index).map { |cell, col_num|
# call the next?-method we defined about 10 lines earlier and find all its neighbours
cell.next?([
at(row_num - 1).at(col_num - 1),
at(row_num - 1).at(col_num),
at(row_num - 1).at((col_num + 1) % row.size),
row.at((col_num + 1) % row.size),
row.at(col_num - 1),
at((row_num + 1) % size).at(col_num - 1),
at((row_num + 1) % size).at(col_num),
at((row_num + 1) % size).at((col_num + 1) % row.size)
])
}
}
# and define the method to_s
} && define_method(:to_s) {
# which loops
map { |row|
# the 2-dimensional array
row.map { |cell|
# and replaces booleans with strings
cell ? "o" : "."
# and join a row together
}.join
# and join the lines together
}.join("\n")
}
}
}
}
}
An example of a 100x30 grid:
...........................o...o...........................o................ooo....o..oo.....ooo....
......oo...........oo..........o.......o..............o..o..................o..o...ooo.......oo..o..
.......o.o........o..o.................oo....o..o.....oo..............oo.....o.o....o.oo....ooo.o...
...........oo......oo....oo..o..o.....o..o.oo...ooo...oo......ooo....o.o......o.....o.oo.....ooo....
........o...o..........o....o.ooo.................o....o......ooo....ooo............o.........o.....
.........ooo................o.........oo.o...oo...o....o....oo.oo....o...............o.............o
............................o........o.oo.....o..o......o...oo....................................oo
.......................oo.oo........ooo..o.....oo...oo..oo.o...............oo.....................o.
.........................o........o...o............oo.oo................oo.oo.......................
.....................................oo............oo.oo...oo...........oo..o.......................
...............................o.....oo............o..oo...oo.oo...o..o..oo.......oo...........oo..o
...............................o..oooo..............oo......o.oo...ooo....ooo....o..o.............o.
.o..............................o...............................o..........o......oo...........o....
o.o.............................o..o...........................o................................oo..
...o.............................o.o.................................o.............................o
o..o..oo.........................ooo................................ooo.............................
.ooo..o.o.........................o.o..............................ooo.o................ooo.........
...o....o..........................o..............................oo...o............................
....o.o...o.......................................................o.ooo...............o.............
...oo.o...o........................................................ooo................o.........ooo.
...o....oo...............................................................oo...........o.........o.o.
....o..o..................................................................o.o.oo................ooo.
....oo..o....oo..........................................o................o.....o.......ooo.........
......ooo......................................oo.......oooo.............oo......o..................
.......oo.....................................oo.......oo................oooo....o..................
.......................o.......................o..o...oo.oo.oo................o..o...oo...........oo
......................o.o.................oo.......oo.ooooo.oo................o.o....oo........oo.oo
o.....................o.o.............ooooooo.....oo...o.o....................................o.....
.......................o....oo.......o......oo.........oo..oo........................o........o...oo
...........................o..o.......oooo..............o...o.......................oooo.....o.....o
# Copyright 2010, Iain Hecker. All Rights Reserved
require 'game_of_life'
# generate pattern glider, which continues forever
grid = <<GRID.to_grid
..............
..o...........
...o..........
.ooo..........
..............
..............
..............
..............
GRID
interrupted = false
generations = 0
trap("INT") { puts "\rExiting after #{generations} generations..."; interrupted = true }
until interrupted do
system "clear"
puts grid.to_s
grid = grid.next
generations += 1
sleep 0.1
end
# Copyright 2010, Iain Hecker. All Rights Reserved
require 'game_of_life'
# generate a random grid
grid = "100x30".to_grid
interrupted = false
generations = 0
trap("INT") { puts "\rExiting after #{generations} generations..."; interrupted = true }
until interrupted do
system "clear"
puts grid.to_s
grid = grid.next
generations += 1
sleep 0.1
end
@blizzarddreams
Copy link

This is perfect. 10/10.

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