[QUIZ] SimFrost (#117)

What's a "small grid" and what's "quite a bit"? :wink:

#!/usr/bin/env ruby -w

TRIALS = 10_000
SIZE = 80*22

sum = 0

high = low = nil

TRIALS.times do
   grid = Array.new(SIZE) { rand < 0.3 ? "." : " " }
   percent = (grid.grep(/\./).size / SIZE.to_f * 100).round

   puts "Actual percentage: #{percent}" if $DEBUG

   sum += percent
   low = percent if low.nil? or percent < low
   high = percent if high.nil? or percent > high
end

puts <<END_OVERALL
Average: #{sum / TRIALS}
High: #{high}
Low: #{low}
END_OVERALL
# >> Average: 29
# >> High: 35
# >> Low: 26

James Edward Gray II

···

On Mar 11, 2007, at 3:07 PM, Christoffer Lernö wrote:

It's interesting that it looks like everyone populated their grid using a randomizer for each position in the grid.

This is is obviously fast, but for small grids (and low percentages), the percentage of actual generated vapour particles may be off by quite a bit.

I'd use this (only moderately tested) :

#! /usr/local/bin/ruby

p = 0.3
w = 100
h = 100
t = h * w
v = (t * p).floor

g = Array.new(v, '.') + Array.new(t - v, '.')

0.upto(t - 1) do |i|
  j = i + rand(t - i)
  z = g[j] ; g[j] = g[i] ; g[i] = z
  # g[j], g[i] = g[i], g[j]
end

(And then split the grid in w chunks.)

The fun part is that the version using a parallel assignment (commented)
is actually much slower (about two times) than the version using the
temporary variable, to the point that it was beaten by sort_by (which is
supposed to be O(n*log(n)) versus O(n) for my versions).

200*200 :
                          user system total real
scramble / temp : 15.281250 0.000000 15.281250 ( 15.587794)
scramble / swap : 31.398438 0.007812 31.406250 ( 32.220921)
sort_by rand : 28.625000 0.101562 28.726562 ( 29.168337)

500*500 :
                          user system total real
scramble / temp : 97.195312 0.000000 97.195312 ( 98.037395)
scramble / swap : 297.937500 0.929688 298.867188 (302.746914)
sort_by rand : 213.906250 0.468750 214.375000 (217.172667)

Fred

···

Le 11 mars 2007 à 21:07, Christoffer Lernö a écrit :

Anyone else with a more elegant solution to the problem?

--
Young at heart an' it gets so hard to wait
When no one I know can seem to help me now
Old at heart but I mustn't hesitate
If I'm to find my way out (Guns n' Roses, Estranged)

Right, here's the graphical version, using RMagick (thanks for the idea,
Chris).
Much improved from the last try, with bits and pieces stolen from many ppl.
BTW, ImageMagic does not like 200x200 grids written bit by bit, just fyi...

#!/usr/bin/env ruby -w

class SimFrost

  require 'RMagick'
  VACUUM=" " #Chris Shea reminded me of constants...
  VAPOR="+"
  ICE="*"
  ICECOLOR='blue'
  VAPORCOLOR='grey'
  VACUUMCOLOR='white'
  attr_reader :grid, :vapor

  def initialize (width=30,height=24, vapor_percent=30, showvapor=true)
    @x_size=width/2*2 #this should take care of those odd numbers
    @y_size=height/2*2
    @vapor_percent=vapor_percent
    @offset=1
    @image = Magick::ImageList.new
    @showvapor=showvapor
    create_grid
  end

  def create_grid
    @grid=Array.new(@x_size){Array.new(@y_size)}
    @bitmap = Array.new(@x_size){Array.new(@y_size,0)}
    @grid.each_with_index do |row, x|
      row.each_with_index do |square, y|
        if rand(100) < @vapor_percent
          @grid[x][y]= VAPOR
          @bitmap[x][y]=1 if @showvapor
        else
          @grid[x][y]= VACUUM
        end
      end
    end
    @grid[@x_size/2][@y_size/2]=ICE
    @bitmap[@x_size/2][@y_size/2]=1
  end

  def check_neighborhoods #interesting bits shamelessly stolen from Dave
Burt
    @offset ^= 1
    (@offset...@x_size).step(2) do |x0|
      (@offset...@y_size).step(2) do |y0|
        x1=(x0+1) % @x_size
        y1=(y0+1) % @y_size
        neighborhood=[@grid[x0][y0], @grid[x0][y1], @grid[x1][y0],
@grid[x1][y1]]
        if neighborhood.include?(VAPOR)
          if neighborhood.include?(ICE)
#there's got to be a rubyer way of doing this...
            if @grid[x0][y0] == VAPOR #top left corner
              @grid[x0][y0] = ICE
              @bitmap[x0][y0] = 1
            end
            if @grid[x0][y1] == VAPOR #one right
              @grid[x0][y1] = ICE
              @bitmap[x0][y1]
            end
            if @grid[x1][y0] == VAPOR #one down
              @grid[x1][y0] = ICE
              @bitmap[x1][y0]
            end
            if @grid[x1][y1] == VAPOR #right and down
              @grid[x1][y1] = ICE
              @bitmap[x1][y1] = 1
            end
          elsif rand(2)==1
            @grid[x0][y0], @grid[x0][y1], @grid[x1][y0], @grid[x1][y1] =
@grid[x1][y0], @grid[x0][y0], @grid[x1][y1], @grid[x0][y1]
            if @showvapor
              @bitmap[x0][y0], @bitmap[x0][y1], @bitmap[x1][y0],
@bitmap[x1][y1] = 1, 1, 1, 1
            end
          else #It's the correct sequence, maybe... I think...
            @grid[x0][y0], @grid[x0][y1], @grid[x1][y0], @grid[x1][y1] =
@grid[x0][y1], @grid[x1][y1], @grid[x0][y0], @grid[x1][y0]
            if @showvapor
              @bitmap[x0][y0], @bitmap[x0][y1], @bitmap[x1][y0],
@bitmap[x1][y1] = 1, 1, 1, 1
            end
          end
        end
      end
    end
  end

  def to_s
    @grid.transpose.collect{|row| row.join}.join("\n")
  end

  def generate_gif
    something = false
    if @image.empty?
      @image.new_image(@x_size, @y_size)
    else
      @image << @image.last.copy
    end
    frame = Magick::Draw.new
    @grid.each_with_index do | row, x |
      row.each_with_index do |square, y|
        if @bitmap[x][y] == 1
          if square == ICE
            frame.fill(ICECOLOR).point(x,y)
            something = true
          elsif square == VAPOR
            frame.fill(VAPORCOLOR).point(x,y)
            something = true
          elsif square == VACUUM
            frame.fill(VACUUMCOLOR).point(x,y)
            something = true
          end
          @bitmap[x][y] =0
        end
      end
    end
    frame.draw(@image) if something
    puts "On to next frame"
  end

  def create_animation
    @image.write("frost_#{Time.now.strftime("%H%M")}.gif")
  end
end

s=SimFrost.new(200,200,40)
step = 0
puts "Sit back, this may take a while"
while s.grid.flatten.include?(SimFrost::VAPOR) #flatten inspired by James
Edward Gray
  puts "Step #{step}: creating frame"
  s.generate_gif
  s.check_neighborhoods
  step += 1
end
  s.create_animation
  puts "Done"

SimFrost2.rb (3.95 KB)

I eventually used NetPBM on Linux but the mpegs get quite big it seems
quite difficult to tune. I have a nice 512**2 movie with the initial
freezer up left but it got >30MB brrr.
Robert

···

On 3/12/07, Ruben Medellin <chubas7@gmail.com> wrote:

James Gray wrote:
> The three rules of Ruby Quiz:

Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time :smiley:

As the console version wouldn't let me be happy, I tried to do it using
OpenGL. I got it at the end, although it's pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I'm happy with it for being my first try using GL.

I'll probably spend another little time tuning it up (as I stated again
that premature optimization is evil), and perhaps designing the 3D view
someone suggested :O.

Thanks again for the great quizes!
____________

# Quiz 117 : SimFrost
# Ruben Medellin <chubas7@gmail.com>
# Usage> ruby quiz117.rb width height vapor_density

#Based on OpenGL
require 'opengl'
require 'glut'

# Each pixel represents an element.
class Element

  attr_accessor :element

  def initialize(element)
    @element = element
  end

  #Just a change of state. Don't forget to decrement the vapor number.
  def freeze
    if @element == :vapor
      $VAPOR -= 1
      @element = :ice
    end
  end
end

# Main class
class Freeze_Simulator

  #Standard initializer. It prepares the windows to be called.
  def initialize

    $WIDTH = ARGV[0] && ARGV[0].to_i || 100
    $HEIGHT = ARGV[1] && ARGV[1].to_i || 100

    $WIDTH += 1 if $WIDTH % 2 != 0
    $HEIGHT += 1 if $HEIGHT % 2 != 0

    $DENSITY = ARGV[2] && ARGV[2].to_i || 30

    $VAPOR = 0

    # We create a matrix, assigning randomly (according to the
    # percentage) the density of vapor. Vacuum is represented by nil.
    $GRID = Array.new($HEIGHT) do
      Array.new($WIDTH) do
        if rand(100) > $DENSITY
          nil
        else
          # We need this counter if we want a nice quick
          # checker for all_frozen? method
          $VAPOR += 1
          Element.new(:vapor)
        end
      end
    end

    #We set the center to be frozen
    ($GRID[$HEIGHT/2][$WIDTH/2] = Element.new(:vapor)).freeze

    $TICK_COUNT = 0

    $PIXELS =

    #Standard GL methods
    GLUT.Init
    GLUT.InitDisplayMode(GLUT::SINGLE)
    GLUT.InitWindowSize($WIDTH, $HEIGHT)
    GLUT.InitWindowPosition(100, 100)
    GLUT.CreateWindow('SimFrost : Quiz #117 - by CHubas')
    GL.ShadeModel(GL::FLAT)

    make_matrix
    GLUT.DisplayFunc(method(:display).to_proc)
    GLUT.KeyboardFunc(Proc.new{|k, x, y| exit if k == 27})
    GLUT.ReshapeFunc(method(:reshape).to_proc)

    # IdleFunc takes a proc object and calls it continously whenever it
can.
    GLUT.IdleFunc(method(:tick).to_proc)
  end

  # Here we create the pixel information.
  # Open GL takes an array of integers and splits it in groups of three
  # that represent one color component each.
  def make_matrix
    for i in 0..$HEIGHT-1
          for j in 0..$WIDTH-1
        index = (i * $WIDTH + j) * 3
        if particle = $GRID[i][j]
          case particle.element
            when :vapor
              # A blue-ish color
              $PIXELS[index] = 50
              $PIXELS[index+1] = 100
              $PIXELS[index+2] = 255
            when :ice
              # White ice
              $PIXELS[index] = 255
              $PIXELS[index+1] = 255
              $PIXELS[index+2] = 255
          end
        else
          # Black
          $PIXELS[index] = 0
          $PIXELS[index+1] = 0
          $PIXELS[index+2] = 0
        end
      end
  end

  # Some basic window behavior
  def reshape(width, height)
    GL.PixelZoom(width / $WIDTH.to_f, height / $HEIGHT.to_f)
    display
  end

  # Draws the pixel bitmap
  def display
     GL.DrawPixels($WIDTH, $HEIGHT, GL::RGB, GL::UNSIGNED_BYTE,
$PIXELS.pack("C*"))
     GL.Flush
  end

  # We split the board into 2*2 squares with this method
  def each_square( tick_number )
    start = 0
    w_end = $WIDTH
    h_end = $HEIGHT

    if tick_number % 2 != 0 #odd
      start -= 1
      w_end -= 1
      h_end -= 1
    end

    (start...h_end).step(2) do |row|
      (start...w_end).step(2) do |column|
        s = yield *get_square_at(row, column)
        set_square_at(row, column, s)
      end
    end

  end

  # Checks for each 2*2 square and does the proper transformation
  def tick
    each_square( ($TICK_COUNT += 1) ) do |*square|
      if square.any?{|e| e != nil && e.element == :ice}
        for e in square
          e.freeze
        end
        square
      else
        rotate(square)
      end
    end

    # Having modified the matrix, now we have to rebuild the pixel map
    make_matrix
    GLUT.PostRedisplay
    GLUT.SwapBuffers

    #Stop doing this if everything is frozen already
    GLUT.IdleFunc(nil) if all_frozen?
  end

  # Some dirty methods
  def get_square_at(row, column)
    [$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]]
  end

  def set_square_at(row, column, new_square)
    $GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]
= new_square
  end

  # Rotates elements in
  # | 0 1 |
  # | 2 3 |
  def rotate(square)
    if rand(2) == 0
      square.values_at(1,3,0,2)
    else
      square.values_at(2,0,3,1)
    end
  end

  # Validates if there is any vapor particle
  def all_frozen?
    if $VAPOR > 0
      return false
    else
      puts "Welcome to the ice age!"
      puts "All frozen in #{$TICK_COUNT} thicks"
      return true
    end
  end

  # Starts the main loop
  def start
    GLUT.MainLoop
  end

end

#Let the fun begin
Freeze_Simulator.new.start

________

Now I wonder how to make a movie :open_mouth:

--
Posted via http://www.ruby-forum.com/\.

--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Nice that someone did it with OpenGL.
I just installed ruby-opengl to try your solution.
But gem always want to make me cry.

Now require 'opengl' is no longer an option
it must be
require 'rubygems'
gem 'ruby-opengl'
Thanks for that Mr. Gem.

I am on Ubuntu, but i remember having similar probs on Windows, maybe
the exact opposite :\

For now I wrote my own opengl.rb and put it in my loadpath.
Maybe someone needs it too :>

--->8---
require 'rubygems'
gem 'ruby-opengl'

require 'glut'
require 'gl'

%w(glut gl).each do |modul|

  eval <<-CODE
    #{modul.upcase} = #{modul.capitalize}
    module #{modul.capitalize}
      def self.method_missing sym, *args
        send(("#{modul.downcase}" + sym.to_s).to_sym, *args)
      end
      def self.const_missing sym
        const_set(sym, const_get("#{modul.upcase}_" + sym.to_s))
      end
    end
  CODE

end

···

On 12 Mrz., 07:22, Ruben Medellin <chub...@gmail.com> wrote:

Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time :smiley:

As the console version wouldn't let me be happy, I tried to do it using
OpenGL. I got it at the end, although it's pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I'm happy with it for being my first try using GL.

Hi Jupp

settings = get_settings
cols = settings["cols"],
rows = settings["rows"],
prob = settings["prob"]

this isn't realy what you wanted, right?

cheers

Simon

Hi again Jupp,

i don't want to start a flamewar here, trust me.
I admit that C is faster than ruby in all situations
that might matter (except developing speed).

Nevertheless some comments to your code:

  > def output_state(state, tick)

this is where half the time is spend - at least on my system

  home
  puts "Simulation tick #{tick}"
  filename = "tick_#{'%05d' % tick}.pgm"
  File.open(filename, 'w') do |file|
    file.puts <<-EOF
P2
# #{filename}
#{state.first.length} #{state.length}
2
    EOF
    state.each do |row|
      row.each do |elem|
        file.puts elem.to_s
      end
    end

replacing this with

     file.puts(
       state.map do |row|
         row.join("\n")
       end.join("\n")
     )

dramatically cuts down the time spend in IO.
A simple

file.puts state

does not. Which might give a hint on why this is so slow,
puts is recursively called for every element in the array
while join is only called once for each row.

  end
end

#####################################################################
# see if state is frozen out (i.e. no more vapor is present)
#####################################################################

class Array
  def frozen_out?
    not self.flatten.member?(1)
  end
end

You create large new arrays here every tick.
To be fair replace it with

   def frozen_out?
     not any? {|row| row.member?(1)}
   end

#####################################################################
# the simulation itself
#####################################################################

settings = get_settings
cols = settings["cols"],
rows = settings["rows"],
prob = settings["prob"]
state = initial_state(cols, rows, prob)
tick = 0
cls
while true
  output_state(state, tick)
  break if state.frozen_out?
  tick += 1
  offset = (tick + 1) % 2
  i = offset
  while i < rows
    i1 = (i + 1) % rows
    j = offset
    while j < cols
      j1 = (j + 1) % cols
      if [ state[i][j],
           state[i][j1],
           state[i1][j],
           state[i1][j1] ].member?(2)

this creates new arrays in the innermost loop....
better do a simple

         if (state[i][j] == 2 ||
             state[i][j1] == 2 ||
             state[i1][j] == 2 ||
             state[i1][j1] == 2)

if you care for speed (you do that in C)

        state[i][j] = 2 if state[i][j] == 1
        state[i][j1] = 2 if state[i][j1] == 1
        state[i1][j] = 2 if state[i1][j] == 1
        state[i1][j1] = 2 if state[i1][j1] == 1
      else
        if rand < 0.5
          state[i][j], state[i][j1], state[i1][j], state[i1][j1] =
          state[i][j1], state[i1][j1], state[i][j], state[i1][j]
        else
          state[i][j], state[i][j1], state[i1][j], state[i1][j1] =
          state[i1][j], state[i][j], state[i1][j1], state[i][j1]
        end
      end

parallel assignments do create arrays also.
do the same as in C:

       else
           h00 = state[i][j];
           h01 = state[i][j1];
           h10 = state[i1][j];
           h11 = state[i1][j1];
           if (rand < 0.5)
             state[i][j] = h01;
             state[i][j1] = h11;
             state[i1][j] = h00;
             state[i1][j1] = h10;
           else
             state[i][j] = h10;
             state[i][j1] = h00;
             state[i1][j] = h11;
             state[i1][j1] = h01;
           end
       end

      j += 2
    end
    i += 2
  end
end

[...]

Well that's it. At least my ruby version doubled its speed.
(but i only tested for small simulations, would you run these
modifications to see the difference on your system with your
data?)

Josef 'Jupp' Schugt

cheers

Simon

Maybe you would like to clarify what the center is too (I'll just use
the left hand upper corner of the center but maybe).

Cheers
Robert

···

On 3/9/07, James Edward Gray II <james@grayproductions.net> wrote:

On Mar 9, 2007, at 8:04 AM, Jason Roelofs wrote:

> Quick clarification, I would assume you mean "turn 90 degrees", not
> 90%, as
> that doesn't make any sense.

Ick, yes. Good catch. I'll fix it on the web site...

James Edward Gray II

--
We have not succeeded in answering all of our questions.
In fact, in some ways, we are more confused than ever.
But we feel we are confused on a higher level and about more important things.
-Anonymous

Code's slow :frowning:
post's fast :wink:

hopefully I find some time to profile and optimise the whole thing.

run.rb is the shell cmdline frontend
torus.rb is the freezer
ascii.rb and ppm.rb are two output plugins.

actually the output of the follwoing is nice already

rubr run.rb -f ascii 80 80 0.5 && \
for i in output/run-80-80.000000*.txt ; do clear; cat $i; sleep 1; done

GQView shows the ppm files nicely too and I guess lot's of other
viewers do but I did not mange to create movies on Linux yet, any
hints would be appreciated.

And before I forget

YET ANOTHER GREAT RUBY QUIZ :))

534/35 > cat run.rb

ascii.rb (565 Bytes)

ppm.rb (998 Bytes)

run.rb (2 KB)

torus.rb (5.38 KB)

···

-------------------------
# 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 :wink:

  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 :wink:
#
###############################################################
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

It doesn't get any faster
at least I got it shorter

649/150 > cat torus.rb
# vim: sw=2 sts=2 nu tw=0 expandtab nowrap:

ppm.rb (998 Bytes)

run.rb (2 KB)

torus.rb (4.13 KB)

···

#

ICE = Class.new
VAPOR = Class.new
VACUUM = Class.new

###############################################################
#
# a small reference to Python :wink:
#
###############################################################
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_line
    @values.each{ |line| yield line }
  end

  def each_nbh
    r = c = @generation % 2
    loop do
      yield @nhoods[ r * @lines + 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 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 |
      @values[index/@lines][index%@lines] = VAPOR
    end
    @values[@lines-1][@columns-1] = @values[@start.first][@start.last]
    @values[@start.first][@start.last] = ICE
  end # def set_vapors

  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
  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 |
  # +---+---+ +-----------+-----------+
  #

  def initialize *args
    @torus, @top, @bottom, @left, @right = *args
    @names = [ [@top, @left], [@top, @right], [@bottom, @right],
[@bottom, @left] ]
  end

  def [] n
    @torus[ *@names[n%4] ]
  end
  def []= n, val
    @torus[ *@names[n%4] ] = 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

end # class Neighborhood
650/151 > 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
651/152 > cat run.rb
# 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 :wink:

  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

Ah, I somehow glazed over this message when it originally came in, but Ken summed my opinion right up. I'm not a restrictions kind of guy.

James Edward Gray II

···

On Mar 11, 2007, at 3:10 PM, Ken Bloom wrote:

On Sat, 10 Mar 2007 04:45:26 +0900, Josef 'Jupp' Schugt wrote:

* Ruby Quiz, 09.03.2007 13:58:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

What about using using nonstandard ruby packages? To in particular I am
talking about NArray:
   
NArray is an n-dimensional numerical array class. Data types:
integer/float/complexe/Ruby object. Methods: array manipulation
including multi-dimensional slicing, fast arithmetic/matrix operations,
etc.

By all means, use it.

I'm gonna have to agree here that it matters very little whether the
proper ratio is enforced on the scale in which SimFrost produces
actually interesting results. +/- 5% in all 10,000 trials on a
terminal-window sized sim is plenty accurate for my purposes.

Harrison Reiser

···

On Mar 11, 2:43 pm, James Edward Gray II <j...@grayproductions.net> wrote:

On Mar 11, 2007, at 3:07 PM, Christoffer Lernö wrote:

> It's interesting that it looks like everyone populated their grid
> using a randomizer for each position in the grid.

> This is is obviously fast, but for small grids (and low
> percentages), the percentage of actual generated vapour particles
> may be off by quite a bit.

What's a "small grid" and what's "quite a bit"? :wink:

Cool! Thanks!

Eric

···

On Mar 11, 3:36 pm, "Gordon Thiesfeld" <gthiesf...@gmail.com> wrote:

I don't have time to write an entire implementation, but I wanted to
do some graphics. I borrowed Eric's code and used the rubysdl library
with it. I'm sure there is large room for improvement, but my wife
wants me to clean out the garage:)

----

Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com

For 10x10 with 10% vapour, the number of particles typically range between 5 and 15, that's between 50% and 150% of the desired amount. That's quite a bit, since the patterns with 15 vapour particles are quite different from one with 5.

The larger the grid, the more this value evens out (of course).

I'm not saying that this is necessarily a flaw in the solutions, I just thought it was an interesting problem - randomly mixing elements in a two-dimensional grid.

Christoffer

···

On Mar 11, 2007, at 21:43 , James Edward Gray II wrote:

On Mar 11, 2007, at 3:07 PM, Christoffer Lernö wrote:

It's interesting that it looks like everyone populated their grid using a randomizer for each position in the grid.

This is is obviously fast, but for small grids (and low percentages), the percentage of actual generated vapour particles may be off by quite a bit.

What's a "small grid" and what's "quite a bit"? :wink:

=)
Actually, I did it Windows, since I was working on it when I read the
quiz. However, on my Ubuntu, Mr. Gems wouldn't actually let me work
(that fed by the fact I did a mess on my dependences, and hadn't
cleaned it up D: )

I'll try it, thanks :smiley:

···

On Mar 13, 3:04 pm, "rretzbach" <rretzb...@googlemail.com> wrote:

On 12 Mrz., 07:22, Ruben Medellin <chub...@gmail.com> wrote:

> Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
> still enjoy watching it every time :smiley:

> As the console version wouldn't let me be happy, I tried to do it using
> OpenGL. I got it at the end, although it's pretty slow (runs at decent
> speed for size of <200*200, obviously the greater values the slowest),
> but I'm happy with it for being my first try using GL.

Nice that someone did it with OpenGL.
I just installed ruby-opengl to try your solution.
But gem always want to make me cry.

Now require 'opengl' is no longer an option
it must be
require 'rubygems'
gem 'ruby-opengl'
Thanks for that Mr. Gem.

I am on Ubuntu, but i remember having similar probs on Windows, maybe
the exact opposite :\

For now I wrote my own opengl.rb and put it in my loadpath.
Maybe someone needs it too :>

--->8---
require 'rubygems'
gem 'ruby-opengl'

require 'glut'
require 'gl'

%w(glut gl).each do |modul|

  eval <<-CODE
    #{modul.upcase} = #{modul.capitalize}
    module #{modul.capitalize}
      def self.method_missing sym, *args
        send(("#{modul.downcase}" + sym.to_s).to_sym, *args)
      end
      def self.const_missing sym
        const_set(sym, const_get("#{modul.upcase}_" + sym.to_s))
      end
    end
  CODE

end

* Simon Kröger, 15.03.2007 22:45:

settings = get_settings
cols = settings["cols"],
rows = settings["rows"],
prob = settings["prob"]

this isn't realy what you wanted, right?

Well, I wanted no lines longer than 69 chars :slight_smile:
looked different before...

Josef 'Jupp' Schugt

···

--
Blog available at http://www.mynetcologne.de/~nc-schugtjo/blog/
PGP key with id 6CC6574F available at http://wwwkeys.de.pgp.net/

That's what my diagrams are for, I hope. :wink:

To eliminate the idea of rotation, we could just say this: given the neighborhood:

···

On Mar 9, 2007, at 8:16 AM, Robert Dober wrote:

On 3/9/07, James Edward Gray II <james@grayproductions.net> wrote:

On Mar 9, 2007, at 8:04 AM, Jason Roelofs wrote:

> Quick clarification, I would assume you mean "turn 90 degrees", not
> 90%, as
> that doesn't make any sense.

Ick, yes. Good catch. I'll fix it on the web site...

James Edward Gray II

Maybe you would like to clarify what the center is too (I'll just use
the left hand upper corner of the center but maybe).

+--+

12|
43|

+--+

Change to:

+--+

23|
14|

+--+

or:

+--+

41|
32|

+--+

There's a 50% chance to go either way.

James Edward Gray II

Interesting case of the profiler showing bad design, the Neighborhood
class was really bad this one makes the code run twice as fast, still
slow but less so

590/91 > cat torus.rb

torus.rb (5 KB)

···

On 3/11/07, Robert Dober <robert.dober@gmail.com> wrote:

Code's slow :frowning:
post's fast :wink:

hopefully I find some time to profile and optimise the whole thing.

---------------------------
<snip>
class Neighborhood
  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 |
  # +---+---+ +-----------+-----------+
  #

  def initialize *args
    @torus, @top, @bottom, @left, @right = *args
    @names = [ [@top, @left], [@top, @right], [@bottom, @right],
[@bottom, @left] ]
  end

  def n
    @torus[ *@names[n%4] ]
  end
  def = n, val
    @torus[ *@names[n%4] ] = 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

end # class Neighborhood

A little trick you can use is setting a class method for class Torus that will instantiate a new Torus object:

class Torus
  def self.(*args)
    self.new(*args)
  end
end

And then you can just do Torus[arguments here]

Dan

Robert Dober wrote:

···

It doesn't get any faster
at least I got it shorter

649/150 > 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 :wink:
#
###############################################################
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_line
   @values.each{ |line| yield line }
end

def each_nbh
   r = c = @generation % 2
   loop do
     yield @nhoods[ r * @lines + 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 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 |
     @values[index/@lines][index%@lines] = VAPOR
   end
   @values[@lines-1][@columns-1] = @values[@start.first][@start.last]
   @values[@start.first][@start.last] = ICE
end # def set_vapors

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
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 |
# +---+---+ +-----------+-----------+
#

def initialize *args
   @torus, @top, @bottom, @left, @right = *args
   @names = [ [@top, @left], [@top, @right], [@bottom, @right],
[@bottom, @left] ]
end

def n
   @torus[ *@names[n%4] ]
end
def = n, val
   @torus[ *@names[n%4] ] = 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

end # class Neighborhood
650/151 > 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
651/152 > cat run.rb
# 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 :wink:

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

It's interesting that it looks like everyone populated their grid using a randomizer for each position in the grid.

This is is obviously fast, but for small grids (and low percentages), the percentage of actual generated vapour particles may be off by quite a bit.

What's a "small grid" and what's "quite a bit"? :wink:

For 10x10 with 10% vapour, the number of particles typically range between 5 and 15, that's between 50% and 150% of the desired amount. That's quite a bit, since the patterns with 15 vapour particles are quite different from one with 5.

Sure, but a 10x10 isn't a sure interesting simulation. :wink:

I'm not saying that this is necessarily a flaw in the solutions, I just thought it was an interesting problem - randomly mixing elements in a two-dimensional grid.

Yes, it is interesting.

James Edward Gray II

···

On Mar 12, 2007, at 3:19 AM, Christoffer Lernö wrote:

On Mar 11, 2007, at 21:43 , James Edward Gray II wrote:

On Mar 11, 2007, at 3:07 PM, Christoffer Lernö wrote: