hopefully I find some time to profile and optimise the whole thing.
···
-------------------------
# vim: sw=2 sts=2 nu tw=0 expandtab:
#
require 'fileutils'
require 'torus'
def usage msg = nil
$stderr.puts msg if msg
$stderr.puts <<-EOS
usage:
#{$0} [options] height width vapor_probability
options and their defaults
-s|--start <height>/2@<width>/2 where to put the initial freezer
please use Smalltalk syntax here
-n|--name run-<height>-<width> name of the output file
-v|--vapor 255/0/255 rgb value for PPM
O use strings for ASCII
-0|--vacuum 0/0/0 idem
<space>
-i|--ice 255/255/255 idem
*
-f|--format ppm ppm or ascii are supported
write your own plugins 
have fun
EOS
exit -1
end
@start = @name = nil
@vapor = nil
@vacuum = nil
@ice = nil
@format = "ppm"
options = { /^-f|^--format/ => :format,
/^-s|^--start/ => :start,
/^-n|^--name/ => :name,
/^-v|^--vapor/ => :vapor,
/^-0|^--vacuum/ => :vacuum,
/^-i|^--ice/ => :ice }
loop do
break if ARGV.empty?
break if ARGV.first == "--"
break unless /^-/ === ARGV.first
illegal_option = true
options.each do
> opt_reg, opt_sym |
if opt_reg === ARGV.first then
usage "Missing argument for option #{ARGV}" if ARGV.length < 2
instance_variable_set( "@#{opt_sym}", ARGV[1] )
ARGV.slice!( 0, 2 )
illegal_option = false
end
end
usage ARGV.first if illegal_option
end
usage ARGV.join(", ") unless ARGV.size == 3
require @format rescue usage
begin
mkdir( "output" ) unless File.directory?( "output" )
rescue
$stderr.puts 'Cannot create output directory "output"'
usage
end
t = Torus( *(ARGV << @start) )
t.name = @name || "run-#{ARGV[0..1].join("-")}"
t.formatter = Formatter.new( ICE => @ice, VAPOR => @vapor, VACUUM => @vacuum )
t.start_sim
535/36 > cat torus.rb
--------------------------
# vim: sw=2 sts=2 nu tw=0 expandtab nowrap:
#
ICE = Class.new
VAPOR = Class.new
VACUUM = Class.new
###############################################################
#
# a small reference to Python 
#
###############################################################
def Torus( rows, cols, vapors, start = nil )
Torus_.new( rows.to_i, cols.to_i, vapors.to_f, start )
end
class Torus_
###############################################################
#
# Torus_
#
###############################################################
attr_reader :lines, :columns
attr_reader :generation
attr_accessor :formatter, :name
def initialize rows, cols, vapors, start
@lines = rows
@columns = cols
@vapors = vapors
@generation = 0
if start then
@start = start.split("@").map{|e| e.to_i}
else
@start ||= [ rows/2, cols /2 ]
end
@nhoods = [] # we will store neighborhoods identified by
# their upper left corner index, odd is for even generations
# and even is for odd generations, which might seem odd.
reset_values
set_vapors
end
def [] line, col=nil
return @values[line] unless col
@values[line][col]
end # def [](line, col=nil)
def []= line, col, val
@values[line][col] = val
end
def each_cell
(1..@lines).each do
> line |
(1..@columns).each do
> column |
yield @values[line-1][column-1], line-1, column-1
end # (0..@columns).each do
end # (0..@lines).each do
end # def each_cell &blk
def each_line
@values.each{ |line| yield line }
end
def each_nbh
r = c = @generation % 2
loop do
yield @nhoods[ linear_idx( r, c ) ] ||=
Neighborhood.new( self, r, r.succ % @lines, c, c.succ % @columns )
c += 2
r += 2 unless c < @columns
return unless r < @lines
c %= @columns
r %= @lines
end
end
def set_from_str str
@values = []
str.strip.split("\n").each do
> line_str |
@values << []
line_str.each_byte do
> char |
@values.last << case char.chr
when ICE.to_s
ICE
when VACUUM.to_s
VACUUM
when VAPOR.to_s
VAPOR
end
end
end
end
def start_sim
until no_more_vapor? do
tick
write
end
end # def start_sim
def tick
puts "Simulation #{@name} generation #{@generation}:"
@generation += 1
each_nbh do
> nbh |
nbh.recalc
end
end
private
def no_more_vapor?
! @values.any?{ |line|
line.any?{ |v| v == VAPOR }
}
end
def reset_values
@values = Array.new(@lines){
Array.new(@columns){
VACUUM
}
}
end
def set_vapors
total = @lines * @columns
v = ( @vapors * (total-1) ).to_i
x = [*0..total-2]
at = []
v.times do
at << x.delete_at( rand(x.size) )
end
at.each do
> index |
l,c = matrix_idx index
@values[l][c] = VAPOR
end
@values[@lines-1][@columns-1] = @values[@start.first][@start.last]
@values[@start.first][@start.last] = ICE
end # def set_vapors
def linear_idx r, c
r * @columns + c
end
def matrix_idx l
return l / @columns, l % @columns
end
def write
@formatter.to_file self, "output/#{@name}.%08d" % @generation
end # def write
end # class Torus_
###############################################################
#
# Neighborhood is implementing a 2x2 window to any object
# that responds to #[]n,m and #[]=n,m,value
# It implements the operation of rotation.
#
###############################################################
class Neighborhood < Struct.new( :torus, :top, :bottom, :left, :right )
include Enumerable
# Neighborhood gives us the following indexed view to the underlying
# torus
# +---+---+ +-----------+-----------+
# | 0 | 1 | | @top,@lft | @top,@rgt |
# +---+---+ +-----------+-----------+
# | 3 | 2 | | @bot,@lft | @bot,@rgt |
# +---+---+ +-----------+-----------+
#
# The Name and the Indexer implement that mapping
Names = [
%w{ top left },
%w{ top right },
%w{ bottom right },
%w{ bottom left }
]
def initialize *args
super *args
end
alias_method :__access__, :[] # Needed b/c/o the limited access
# abilities of Struct
def [] n
__access__("torus")[ *resolve_idx( n ) ]
end
def []= n, val
__access__("torus")[ *resolve_idx( n ) ] = val
end
def each
4.times do
> idx |
yield self[idx]
end
end
def recalc
if any?{|v| v == ICE} then
4.times do
> idx |
self[ idx ] = ICE if self[ idx ] == VAPOR
end
else
rotate( rand(2) )
end
end
def rotate dir
x = self[0]
3.times do
> n |
self[ n + 2*dir*n ] = self[ n + 1 + dir*2*n.succ ]
end # 3.times do
self[ 3 + 2 * dir ] = x
end # def rotate dir
private
def resolve_idx n
[
__access__( Names[ n % 4 ].first ),
__access__( Names[ n % 4 ].last)
]
end # def resolv_idx
end # class Neighborhood
538/39 > cat ascii.rb
--------------------------
# vim: sw=2 sts=2 nu tw=0 expandtab nowrap:
class Formatter
@@default = { ICE => "*",
VAPOR => "0",
VACUUM => " "
}
def initialize chars={}
@chars =
Hash[ *chars.to_a.map{ |(k,v)| [k, v || @@default[k] ] }.flatten ]
end # def initialize colors={}
def to_file( source, file, comment = nil )
File.open( "#{file}.txt", "w" ) do
> f |
source.each_line{
>line>
line.each do
> cell |
f.print @chars[cell]
end
f.puts
}
end
end
end
539/40 > cat ppm.rb
--------------------------
# vim: sw=2 sts=2 nu tw=0 expandtab nowrap:
class Formatter
@@default = { ICE => "255/255/255",
VAPOR => "255/0/255",
VACUUM => "0/0/0"
}
def initialize colors={}
@colors = {}
colors.each do
> element, color |
color ||= @@default[element]
@colors[ element ] = " " << color.gsub("/", " ") << " "
end # colors.each do
end # def initialize colors={}
def to_file( source, file, comment = nil )
comment ||= file
File.open( "#{file}.ppm", "w" ) do
> f |
f.puts "P3 #{source.columns} #{source.lines} 255"
f.puts "#"
f.puts "# #{comment}"
f.puts "#"
source.each_line{
>line>
count = 0
line.each do
> cell |
s = @colors[cell]
if count + s.size > 70 then
f.puts
count = 0
end
count += s.size
f.print s
end
f.puts unless count.zero?
}
end
end
end