Wireworld/Ruby
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
- 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>