From: Dave Burt Date: 2007-03-12T01:00:12+09:00 Subject: Re: [QUIZ] SimFrost (#117) # # SimFrost # # A response to Ruby Quiz #117 [ruby-talk:242714] # # SimFrost simulates the growth of frost in a finite but unbounded plane. # # The simulation begins with vapor and vacuum cells, and a single ice cell. # As the simulation progresses, the vapor and vacuum move around, and vapor # coming into contact with ice becomes ice. Eventually no vapor remains. # # SimFrost is the simulator core, about 50 lines. # # SimFrost::Console is a console interface. It parses command-line options, # runs the simulator, and draws it in ASCII on a terminal. # # You can run the script from the command-line: # usage: sim_frost.rb [options] # -w, --width N number of columns # -h, --height N number of rows # -p, --vapor-percentage N % of cells that start as vapor # -d, --delay-per-frame T delay per frame in seconds # -i, --ice S ice cell # -v, --vapor S vapor cell # -0, --vacuum S vacuum cell # --help show this message # # Author: dave@burt.id.au # Created: 10 Mar 2007 # Last modified: 11 Mar 2007 # class SimFrost attr_reader :width, :height, :cells def initialize(width, height, vapor_percentage) unless width > 0 && width % 2 == 0 && height > 0 && height % 2 == 0 throw ArgumentError, "width and height must be even, positive numbers" end @width = width @height = height @cells = Array.new(width) do Array.new(height) do :vapor if rand * 100 <= vapor_percentage end end @cells[width / 2][height / 2] = :ice @offset = 0 end def step @offset ^= 1 @new_cells = Array.new(width) { Array.new(height) } @offset.step(width - 1, 2) do |x| @offset.step(height - 1, 2) do |y| process_neighbourhood(x, y) end end @cells = @new_cells nil end def contains_vapor? @cells.any? {|column| column.include? :vapor } end private def process_neighbourhood(x0, y0) x1 = (x0 + 1) % width y1 = (y0 + 1) % height hood = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]] if hood.any? {|x, y| @cells[x][y] == :ice } hood.each do |x, y| @new_cells[x][y] = @cells[x][y] && :ice end else hood.reverse! if rand < 0.5 4.times do |i| j = (i + 1) % 4 @new_cells[hood[i][0]][hood[i][1]] = @cells[hood[j][0]][hood[j][1]] end end nil end module Console DEFAULT_RUN_OPTIONS = { :width => 78, :height => 24, :vapor_percentage => 30, :delay_per_frame => 0.1, :ice => " ", :vapor => "O", :vacuum => "#" } def self.run(options = {}) opts = DEFAULT_RUN_OPTIONS.merge(options) sim = SimFrost.new(opts[:width], opts[:height], opts[:vapor_percentage]) puts sim_to_s(sim, opts) i = 0 while sim.contains_vapor? sleep opts[:delay_per_frame] sim.step puts sim_to_s(sim, opts) i += 1 end puts "All vapor frozen in #{i} steps." end def self.sim_to_s(sim, options = {}) sim.cells.transpose.map do |column| column.map do |cell| case cell when :ice: options[:ice] || "*" when :vapor: options[:vapor] || "." else options[:vacuum] || " " end end.join(options[:column_separator] || "") end.join(options[:row_separator] || "\n") end def self.parse_options(argv) require 'optparse' opts = {} op = OptionParser.new do |op| op.banner = "usage: #{$0} [options]" op.on("-w","--width N",Integer,"number of columns"){|w|opts[:width] = w} op.on("-h","--height N",Integer,"number of rows") {|h|opts[:height] = h} op.on("-p", "--vapor-percentage N", Integer, "% of cells that start as vapor"){|p| opts[:vapor_percentage] = p} op.on("-d", "--delay-per-frame T", Float, "delay per frame in seconds") {|d| opts[:delay_per_frame] = d } op.on("-i", "--ice S", String, "ice cell") {|i| opts[:ice] = i } op.on("-v", "--vapor S", String, "vapor cell") {|v| opts[:vapor] = v } op.on("-0", "--vacuum S", String, "vacuum cell"){|z| opts[:vacuum] = z } op.on_tail("--help", "just show this message") { puts op; exit } end begin op.parse!(ARGV) rescue OptionParser::ParseError => e STDERR.puts "#{$0}: #{e}" STDERR.puts op exit end opts end end end if $0 == __FILE__ SimFrost::Console.run SimFrost::Console.parse_options(ARGV) end