From: James Edward Gray II Date: 2007-08-13T01:21:54+09:00 Subject: Re: [SOLUTION][QUIZ] Cellular Automata (#134) On Aug 12, 2007, at 8:13 AM, Jesse Merriman wrote: > Here's my fairly straightforward, no bells-and-whistles solution. My solution does the required tasks and makes PPM graphics. Here's the code: #!/usr/bin/env ruby -wKU require "ppm" require "enumerator" require "optparse" options = {:rule => 30, :steps => 20, :cells => "1", :output => :ascii} ARGV.options do |opts| opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [OPTIONS]" opts.separator "" opts.separator "Specific Options:" opts.on( "-r", "--rule RULE", Integer, "The rule for this simulation." ) do |rule| raise "Rule out of bounds" unless rule.between? 0, 255 options[:rule] = rule end opts.on( "-s", "--steps STEPS", Integer, "The number of steps to render." ) do |steps| options[:steps] = steps end opts.on( "-c", "--cells CELLS", String, "The starting cells (1s and 0s)." ) do |cells| raise "Malformed cells" unless cells =~ /\A[01]+\z/ options[:cells] = cells end opts.on( "-o", "--output FORMAT", [:ascii, :ppm], "The output format (ascii or ppm)." ) do |output| options[:output] = output end opts.separator "Common Options:" opts.on( "-h", "--help", "Show this message." ) do puts opts exit end begin opts.parse! rescue puts opts exit end end RULE_TABLE = Hash[ *%w[111 110 101 100 011 010 001 000]. zip(("%08b" % options[:rule]).scan(/./)).flatten ] cells = [options[:cells]] options[:steps].times do cells << "00#{cells.last}00".scan(/./). enum_cons(3). inject("") { |nc, n| nc + RULE_TABLE [n.join] } end width = cells.last.length if options[:output] == :ascii cells.each { |cell| puts cell.tr("10", "X ").center(width) } else image = PPM.new( :width => width, :height => cells.length, :background => PPM::Color::BLACK, :foreground => PPM::Color[0, 0, 255], :mode => "P3" ) cells.each_with_index do |row, y| row.center(width).scan(/./).each_with_index do |cell, x| image.draw_point(x, y) if cell == "1" end end image.save("rule_#{options[:rule]}_steps_#{options[:steps]}") end __END__ It requires this tiny PPM library: #!/usr/bin/env ruby -wKU # Updated by James Edward Gray II from the Turtle Graphics quiz. class PPM class Color def self.[](*args) args << args.last while args.size < 3 new(*args) end def initialize(red, green, blue) @red = red @green = green @blue = blue end BLACK = new(0, 0, 0) WHITE = new(255, 255, 255) def inspect "PPM::Color[#{@red}, #{@green}, #{@blue}]" end def to_s(mode) if mode == "P6" [@red, @green, @blue].pack("C*") else "#{@red} #{@green} #{@blue}" end end end DEFAULT_OPTIONS = { :width => 400, :height => 400, :background => Color::BLACK, :foreground => Color::WHITE, :mode => "P6" } def initialize(options = Hash.new) options = DEFAULT_OPTIONS.merge(options) @width = options[:width] @height = options[:height] @background = options[:background] @foreground = options[:foreground] @mode = options[:mode] @canvas = Array.new(@height) { Array.new(@width) { @background } } end def draw_point(x, y, color = @foreground) return unless x.between? 0, @width - 1 return unless y.between? 0, @height - 1 @canvas[y][x] = color end # http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm def draw_line(x0, y0, x1, y1, color = @foreground) steep = (y1 - y0).abs > (x1 - x0).abs if steep x0, y0 = y0, x0 x1, y1 = y1, x1 end if x0 > x1 x0, x1 = x1, x0 y0, y1 = y1, y0 end deltax = x1 - x0 deltay = (y1 - y0).abs error = 0 ystep = y0 < y1 ? 1 : -1 y = y0 (x0..x1).each do |x| if steep draw_point(y, x, color) else draw_point(x, y, color) end error += deltay if 2 * error >= deltax y += ystep error -= deltax end end end def save(file) File.open(file.sub(/\.ppm$/i, "") + ".ppm", "w") do |image| image.puts @mode image.puts "#{@width} #{@height} 255" @canvas.each do |row| pixels = row.map { |pixel| pixel.to_s(@mode) } image.send( @mode == "P6" ? :print : :puts, pixels.join(@mode == "P6" ? "" : " ") ) end end end end __END__ James Edward Gray II