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