From: Chris Shea Date: 2007-03-12T01:40:18+09:00 Subject: Re: SimFrost (#117) I used RMagick to create animated gifs. I've never programatically generated images before, so I was very pleased to discover that it's as easy as I hoped it would be. On my (slowly dying) 1.3GHz Celeron M, a 200 x 200 movie with 40% vapor fill takes 3 minutes to make, resulting in a movie of about 25 seconds and 700KB. The memory usage while running gets up to about 80MB. An example output is here: http://www.tie-rack.org/images/frost.gif I decided to place the initial point of ice at a random location instead of the center. The vapor isn't in the images, as it would dramatically increase the time it takes to render each frame (or at least it would the way I'm doing it). There's still some ugliness in there, but I became more interested in tweaking than beautifying. -Chris Shea frost.rb -------- require 'RMagick' module Frost ICE = 0 NEWICE = 1 VAPOR = 2 VACUUM = 3 ICECOLOR = 'blue' class Window def initialize(width, height, vapor_chance) unless width % 2 == 0 and height % 2 == 0 raise ArgumentError, "divisible by 2" end @width = width @height = height row = Array.new(width, Frost::VACUUM) @glass = Array.new(height) { row.dup } @image = Magick::ImageList.new #place random vapor 0.upto(height - 1) do |row| 0.upto(width - 1) do |col| @glass[row][col] = Frost::VAPOR if rand < vapor_chance end end #place first ice #@glass[height / 2][width / 2] = Frost::NEWICE @glass[rand(height)][rand(width)] = Frost::NEWICE @step = 0 make_gif end def step neighborhood_starts.each do |start| n = find_neighbors(start) n.step @glass[start[0]][start[1]] = n.layout[0] @glass[start[0]][(start[1]+1) % @width] = n.layout[1] @glass[(start[0]+1) % @height][start[1]] = n.layout[2] @glass[(start[0]+1) % @height][(start[1]+1) % @width] = n.layout[3] end @step += 1 end def neighborhood_starts starts = [] offset = @step % 2 offset.step(@height - 1, 2) do |row| offset.step(@width - 1, 2) do |col| starts << [row,col] end end starts end def find_neighbors(start) one = @glass[start[0]][start[1]] two = @glass[start[0]][(start[1] + 1) % @width] three = @glass[(start[0] + 1) % @height][start[1]] four = @glass[(start[0] + 1) % @height][(start[1] + 1) % @width] Frost::Neighborhood.new(one,two,three,four) end def done? @glass.each do |row| return false if row.include? Frost::VAPOR end true end def make_gif if @image.empty? @image.new_image(@width, @height) else @image << @image.last.copy end @glass.each_with_index do |row, y| row.each_with_index do |cell, x| if cell == Frost::NEWICE point = Magick::Draw.new point.fill(Frost::ICECOLOR) point.point(x,y) point.draw(@image) end end end end def create_animation @image.write("frost_#{Time.now.strftime("%H%M")}.gif") end def go until done? step make_gif print '.' end print "\ncreating animation... " create_animation puts 'done' end end class Neighborhood def initialize(one,two,three,four) @layout = [one,two,three,four] transform(Frost::NEWICE, Frost::ICE) end attr_reader :layout def step if ice? ice_over else rotate end end def ice? @layout.include? Frost::ICE end def rotate if rand(2).zero? @layout = [@layout[1],@layout[3],@layout[0],@layout[2]] else @layout = [@layout[2],@layout[0],@layout[3],@layout[1]] end end def transform(from, to) @layout.map! {|cell| cell == from ? to : cell} end def ice_over transform(Frost::VAPOR, Frost::NEWICE) end end end if __FILE__ == $0 if ARGV.size != 3 puts "frost.rb " puts "This shouldn't take too long: frost.rb 100 100 0.3" exit end width = ARGV[0].to_i height = ARGV[1].to_i vapor_percent = ARGV[2].to_f window = Frost::Window.new(width,height,vapor_percent).go end