Wireworld/Ruby

From Rosetta Code
Revision as of 19:38, 17 October 2011 by rosettacode>Glennj (add Shoes version)
Library: Ruby/Tk

The GUI is somewhat "halfway", in that it animates a text widget so it's not "real" graphics. <lang ruby>require 'tk'

class WireWorld

 EMPTY = ' '
 HEAD = 'H'
 TAIL = 't'
 CONDUCTOR = '.'
 def initialize(string)
   max_row = 0
   @grid = string.each_line.collect do |line|
             line.chomp!
             max_row = [max_row, line.length].max
             line.each_char.collect do |char| 
               case char
               when EMPTY, HEAD, TAIL, CONDUCTOR then char
               else EMPTY
               end 
             end
           end
   @original_grid = Marshal.restore(Marshal.dump @grid)   # this is a deep copy
   @width = max_row
   @height = @grid.length
   pad_grid
 end
 # initialize from a file
 def self.open(filename)
   self.new(File.read(filename))
 end
 def reset
   @grid = @original_grid
 end
 # ensure all rows are the same length by padding short rows with empty cells
 def pad_grid
   @grid.each do |row|
     if @width > row.length
       row.concat(Array.new(@width - row.length, EMPTY))
     end
   end
 end
 # the "to_string" method
 def to_s
   @grid.inject() {|str, row| str << row.join() << "\n"}
 end
 # transition all cells simultaneously
 def transition
   @grid = @grid.each_with_index.collect do |row, y| 
             row.each_with_index.collect do |state, x| 
               transition_cell(state, x, y)
             end
           end
 end
 # how to transition a single cell
 def transition_cell(current, x, y)
   case current
   when EMPTY then EMPTY
   when HEAD  then TAIL
   when TAIL  then CONDUCTOR
   else neighbours_with_state(HEAD, x, y).between?(1,2) ? HEAD : CONDUCTOR
   end
 end
 # given a position in the grid, find the neighbour cells with a particular state
 def neighbours_with_state(state, x, y)
   count = 0
   ([x-1, 0].max .. [x+1, @width-1].min).each do |xx|
     ([y-1, 0].max .. [y+1, @height-1].min).each do |yy|
       next if x == xx and y == yy
       count += 1 if state(xx, yy) == state
     end
   end
   count
 end
 # return the state of a cell given a cartesian coordinate
 def state(x, y)
   @grid[y][x]
 end
 # run a simulation up to a limit of transitions, or until a recurring
 # pattern is found
 # This will print text to the console
 def run(iterations = 25)
   seen = []
   count = 0
   loop do
     puts self
     puts
     if seen.include?(@grid)
       puts "I've seen this grid before... after #{count} iterations"
       break
     end
     if count == iterations
       puts "ran through #{iterations} iterations"
       break
     end
     seen << @grid
     count += 1
     transition
   end
 end
 # the gui version
 def run_tk
   @tk_root = TkRoot.new("title" => "WireWorld")
   @tk_text = TkText.new(@tk_root, 
                 :width => @width, 
                 :height => @height, 
                 :font => 'courier')
   @tk_text.insert('end', self.to_s).state('disabled')
   @tk_after_interval = 150
   faster_cmd = proc {@tk_after_interval = [25, @tk_after_interval-25].max}
   slower_cmd = proc {@tk_after_interval += 25}
   reset_cmd = proc {self.reset}
   close_cmd = proc do
     @tk_root.after_cancel(@tk_after_id)
     @tk_root.destroy
   end
   controls = TkFrame.new(@tk_root)
   [ TkButton.new(controls, :text => 'Slower', :command => slower_cmd),
     TkButton.new(controls, :text => 'Faster', :command => faster_cmd),
     TkButton.new(controls, :text => 'Reset',  :command => reset_cmd),
     TkButton.new(controls, :text => 'Close',  :command => close_cmd),
   ].each {|btn| btn.pack(:expand => 1, :fill => 'x', :side => 'left')}
   @tk_text.pack(:expand => 1, :fill => 'both')
   controls.pack(:fill => 'x')
   @tk_after_id = @tk_root.after(500) {animate}
   Tk.mainloop
 end
 def animate
   transition 
   @tk_text.state('normal') \
           .delete('1.0','end') \
           .insert('end', self.to_s) \
           .state('disabled')
   @tk_after_id = @tk_root.after(@tk_after_interval) {animate}
 end

end

  1. this is the "2 Clock generators and an XOR gate" example from the wikipedia page

ww = WireWorld.new <<WORLD

 ......tH
.        ......
 ...Ht...      .
              ....
              .  ..... 
              ....
 tH......      .
.        ......
 ...Ht...

WORLD

ww.run ww.reset ww.run_tk puts 'bye'</lang>

Library: Shoes

<lang ruby>$ww = WireWorld.new <<WORLD

 ......tH
.        ......
 ...Ht...      .
              ....
              .  ..... 
              ....
 tH......      .
.        ......
 ...Ht...

WORLD

Shoes.app do

 @world = para(, :family => 'monospace')
 animate(4) do
   @world.text = $ww.to_s
   $ww.transition
 end

end</lang>