Skip to content

Instantly share code, notes, and snippets.

@JEG2
Created June 29, 2010 00:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JEG2/dee55426374e780a9870 to your computer and use it in GitHub Desktop.
Save JEG2/dee55426374e780a9870 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require "observer"
require "singleton"
class Clock
include Observable
include Singleton
def run( seconds_delay )
start seconds_delay
@thread.join
end
def start( seconds_delay )
@thread = Thread.new(self, seconds_delay) do |notifier, delay|
loop do
sleep delay
notifier.changed
notifier.notify_observers Time.now
end
end
end
def stop
unless @thread.nil?
@thread.kill
@thread = nil
end
end
end
if $0 == __FILE__
unless ARGV.size == 1 and ARGV[0] =~ /^(?:\d+\.)?\d+$/
puts "Usage: #$0 DELAY_IN_SECONDS"
exit
end
class TimePrinter
def update( time )
puts time
end
end
clock = Clock.instance
clock.add_observer TimePrinter.new
puts "Starting clock... (waiting 10 seconds)"
clock.start ARGV.shift.to_f
sleep 10
clock.stop
puts "Clock stopped... (waiting 10 seconds)"
sleep 10
end
#!/usr/bin/env ruby
$: << "libs"
require "clock"
module Life
class Cell
def initialize( state = false )
@state = @next_state = state
end
def alive?() @state end
def figure_next_state( *neighbor_cells )
neighbors = neighbor_cells.inject(0) do |sum, v|
if v.alive? then sum + 1 else sum end
end
if @state and (neighbors < 2 or neighbors > 3)
@next_state = false
elsif not @state and neighbors == 3
@next_state = true
end
not @state == @next_state
end
def transition
changed = (not @state == @next_state)
@state = @next_state
changed
end
end
class Universe
def initialize( max_turns, file, view_class = UniverseView )
@max_turns = max_turns
@turn = 0
cells = [ ]
File.open(file) do |f|
while line = f.gets
next unless line =~ /\S/
cells << [ ]
line.delete(" \t\n").each_byte do |char|
if char == ?.
cells[-1] << Cell.new
else
cells[-1] << Cell.new(true)
end
end
end
end
@cells = cells.transpose
@neighbors = { }
@active = [ ]
(0...(@cells[0].size)).each do |y|
(0...(@cells.size)).each do |x|
@active.push(*find_neighbors(x, y)) if @cells[x][y].alive?
end
end
@view = create_view view_class
@view.render
end
def create_view( view_class )
view = view_class.new(@cells.size, @cells[0].size, true)
(0...(@cells[0].size)).each do |y|
(0...(@cells.size)).each do |x|
view.update x, y, @cells[x][y].alive?
end
end
view
end
def update( time )
@active = @active.find_all do |xy|
neighbors = find_neighbors(*xy).map { |e| @cells[e[0]][e[1]] }
@cells[xy[0]][xy[1]].figure_next_state(*neighbors)
end
next_active = [ ]
@active.find_all do |xy|
if @cells[xy[0]][xy[1]].transition
@view.update xy[0], xy[1], @cells[xy[0]][xy[1]].alive?
next_active << xy
next_active.push(*find_neighbors(*xy))
end
end
@active = next_active.uniq
@view.render
@turn += 1
exit if @max_turns == @turn or @active.size == 0
end
private
def find_neighbors( x, y )
where = x.to_s + "," + y.to_s
return @neighbors[where] if @neighbors.include? where
neighbors = [ ]
[-1, 0, 1].each { |n| neighbors.push( [x + n, y + -1],
[x + n, y + 0],
[x + n, y + 1] ) }
neighbors.delete_if { |xy| xy.include?(-1) or
xy[0] >= @cells.size or
xy[1] >= @cells[0].size or
(xy[0] == x && xy[1] == y) }
@neighbors[where] = neighbors
end
end
class UniverseView
def initialize( width, height, clear = false )
@cells = Array.new(height) { Array.new(width) { " " } }
@clear = clear
end
def update( x, y, value )
@cells[y][x] = if value then "X" else " " end
end
def render
system("clear") if @clear
@cells.each { |row| STDOUT << row.join("") + "\n" }
STDOUT << "\n"
end
end
end
if $0 == __FILE__
unless ARGV.size == 2 and ARGV[0] =~ /^\d+$/
puts "Usage: $0 MAX_TURNS PATTERN_FILE"
exit
end
Thread.abort_on_exception = true
clock = Clock.instance
clock.add_observer Life::Universe.new(ARGV.shift.to_i, ARGV.shift)
clock.run 0.3
end
#!/usr/bin/env ruby
require "life"
class PPMGenerator < Life::UniverseView
@@filename = "life_"
def self.filename=( name ) @@filename = name end
def initialize( width, height, clear = false )
@cells = Array.new(height) { Array.new(width) { false } }
@turn = 0
end
def update( x, y, value ) @cells[y][x] = value end
def render
File.open("#@@filename#@turn.ppm", "w") do |f|
f.puts "P6"
f.puts "#{@cells[0].size * 7 + 1} #{@cells.size * 7 + 1} 255"
@cells.each do |row|
f.print "\x00\x00\xAA" * (@cells[0].size * 7 + 1)
f.print render_row(row)
end
end
@turn += 1
end
private
def render_row( row )
blank = "\x00\x00\xAA" + ("\0\0\0" * 6 + "\x00\x00\xAA") * row.size
divider = "\x00\x00\xAA" * (row.size * 7 + 1)
short = row.map do |cell|
if cell
"\0\0\0\xFF\x00\x00\xFF\x00\x00\0\0\0"
else
"\0\0\0" * 4
end
end.join("\0\0\0\x00\x00\xAA\0\0\0")
short = "\x00\x00\xAA\0\0\0#{short}\0\0\0\x00\x00\xAA"
long = short.gsub( /\0\0\0\xFF\x00\x00\xFF\x00\x00\0\0\0/,
"\xFF\x00\x00\xFF\x00\x00\xFF\x00\x00\xFF\x00\x00" )
blank + short + long * 2 + short + blank + divider
end
end
unless ARGV.size == 2 and ARGV[0] =~ /^\d+$/
puts "Usage: $0 MAX_TURNS PATTERN_FILE"
exit
end
turns = ARGV.shift.to_i
file = ARGV.shift
PPMGenerator.filename = "images/" + File.basename(file, ".txt") + "_"
game = Life::Universe.new(turns, file, PPMGenerator )
loop do
game.update "RUN"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment