Last active
October 14, 2021 11:04
-
-
Save nicooga/6aa5c7a8814c14549304 to your computer and use it in GitHub Desktop.
Ruby gosu implementation of Cpnway's game of life
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'gosu' | |
ANIMATION_INTERVAL = 0.4 | |
module GameOfLife | |
class Cell | |
attr_accessor :x, :y | |
def initialize(hash) | |
raise 'Bad attribute keys' unless (hash.keys - [:x, :y, :alive]).empty? | |
hash.each { |k,v| eval "@#{k}= #{v}" } | |
end | |
def alive?; @alive; end | |
def alive=(bool); @alive = bool; end | |
def kill!; @alive = false; end | |
def resurrect!; @alive = true end | |
def to_s | |
alive? ? "\e[1;32mO\e[0m" : "\e[1;30mX\e[0m" | |
end | |
end | |
class Grid < Array | |
def initialize(x,y=x) | |
if x.is_a? String | |
super x.lines.map { |l| l.chomp.split '|' } | |
.transpose | |
.map.with_index do |col, x| | |
col.map.with_index { |cell, y| GameOfLife::Cell.new(x: x, y: y, alive: !!(cell =~ /o/i)) } | |
end | |
else | |
super(x) do |x| | |
Array.new(y) do | |
GameOfLife::Cell.new(alive: [true,false].sample, x: x, y: y) | |
end | |
end | |
end | |
end | |
def next! | |
cells, cells_to_kill, cells_to_resurrect = [], [], [] | |
each_cell do |cell| | |
cells << [cell, around(cell).flatten.compact.count(&:alive?)] | |
end | |
# Any live cell with fewer than two live neighbours dies, as if caused by under-population. | |
cells_to_kill.concat cells.select { |cell,ln| cell.alive? and ln < 2 } | |
# Any live cell with two or three live neighbours lives on to the next generation. | |
# Do nothing... | |
# Any live cell with more than three live neighbours dies, as if by overcrowding. | |
cells_to_kill.concat cells.select { |cell,ln| cell.alive? and ln > 3 } | |
# Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. | |
cells_to_resurrect.concat cells.select { |cell,ln| !cell.alive? and ln == 3 } | |
cells_to_kill.each { |cell,_| cell.kill! } | |
cells_to_resurrect.each { |cell,_| cell.resurrect! } | |
self | |
end | |
def next; dup.next!; end | |
def animate!(gens=Float::Infinity) | |
puts (['-']*first.size).join('-') | |
gens.times do | |
next! | |
puts (['-']*first.size).join('-') | |
sleep(ANIMATION_INTERVAL) | |
end | |
end | |
def animate(*args) | |
dup.animate! *args | |
end | |
NEGATIVE_SLICE = ->(ary,i){ ((0..ary.size-1).to_a*2)[ary.size+i,3] } | |
OVER_SLICE = ->(ary,i){ ((0..ary.size-1).to_a*2)[i%ary.size,3] } | |
SLICE_AROUND = ->(ary,i) do | |
return Array.new(3,nil) if ary.nil? | |
if i <= 0 | |
NEGATIVE_SLICE[ary,i-1] | |
elsif i >= ary.size-1 | |
OVER_SLICE[ary,i-1] | |
else | |
((i-1)..(i+1)).to_a | |
end | |
end | |
def around(x,y=nil) | |
x, y = x.x, x.y if %w(x y).map { |m| x.respond_to? m }.all? | |
ary = SLICE_AROUND[self,x].map do |xi| | |
SLICE_AROUND[self[xi],y].map do |yi| | |
self[xi][yi] | |
end | |
end | |
ary[1][1] = nil | |
self.class[*ary] | |
end | |
def each_cell | |
each do |col| | |
col.each do |cell| | |
yield cell | |
end | |
end | |
end | |
def expand(cols=0,rows=cols) | |
tmp_heigth, tmp_width = heigth, width | |
cols.times do |x| | |
self << Array.new(tmp_heigth) { |y| GameOfLife::Cell.new(x: tmp_width+x, y: y, alive: false) } | |
end | |
tmp_heigth, tmp_width = heigth, width | |
each.with_index do |col,x| | |
rows.times do |y| | |
col << GameOfLife::Cell.new(x: x, y: tmp_heigth+y, alive: false) | |
end | |
end | |
self | |
end | |
def to_s | |
self.transpose.map do |row| | |
row.map { |cell| cell.nil? ? '+' : cell.to_s }.join('|') | |
end.join("\n") | |
end | |
def heigth; first.size; end | |
def width; size; end | |
def print; puts self.to_s; end | |
end | |
class Window < Gosu::Window | |
TOP_COLOR = Gosu::Color.new(0xFFFeee) | |
BOTTOM_COLOR = Gosu::Color.new(0xFF1D4DB5) | |
def initialize(grid) | |
@grid = grid | |
@width = @grid.size*30+5 | |
@heigth = @grid.first.size*30+5 | |
super @width, @heigth, false | |
self.caption = 'Game of Life' | |
@last_frame = Gosu::milliseconds | |
end | |
def update | |
@grid.next! | |
@this_frame = Gosu::milliseconds | |
@delta = @this_frame - @last_frame | |
@last_frame = @this_frame | |
sleep ANIMATION_INTERVAL | |
end | |
def draw | |
## BACKGROUND | |
draw_quad( | |
0, 0, TOP_COLOR, | |
@width, 0, TOP_COLOR, | |
0, @heigth, BOTTOM_COLOR, | |
@width, @heigth, BOTTOM_COLOR | |
) | |
## CELLS | |
@grid.each_cell do |cell| | |
color = cell.alive? ? Gosu::Color::GREEN : Gosu::Color::BLACK | |
draw_quad( | |
cell.x*30+5, cell.y*30+5, color, | |
cell.x*30+30, cell.y*30+5, color, | |
cell.x*30+5, cell.y*30+30, color, | |
cell.x*30+30, cell.y*30+30, color | |
) | |
end | |
end | |
end | |
end | |
PULSAR = GameOfLife::Grid.new <<-GRID | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|X|X|O|O|O|X|X|X|O|O|O|X|X|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|X|X|O|O|O|X|X|X|O|O|O|X|X|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|X|X|O|O|O|X|X|X|O|O|O|X|X|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|O|X|X|X|X|O|X|O|X|X|X|X|O|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|X|X|O|O|O|X|X|X|O|O|O|X|X|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X | |
GRID | |
GameOfLife::Window.new(PULSAR).show |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This scripts requires Gosu. Basically, you need to install some dependencies in order to be able to build native extensions for gosu. Them just
gem install
it.In mac I just did:
In linux:
Then dowload this file and run it
ruby game_of_life.rb
.