Wireworld/Ruby: Difference between revisions

From Rosetta Code
Content added Content deleted
m (add Shoes version)
No edit summary
 
Line 1: Line 1:
<lang ruby>class WireWorld
{{libheader|Ruby/Tk}}
EMPTY = ' '

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

class WireWorld
EMPTY = ' '
HEAD = 'H'
TAIL = 't'
CONDUCTOR = '.'
CONDUCTOR = '.'
NEIGHBOURS = [-1,0,1].product([-1,0,1]) - [0,0]

def initialize(string)
def initialize(string)
max_row = 0
@grid = string.each_line.collect do |line|
@grid = string.each_line.collect do |line|
line.chomp!
line.chomp.each_char.collect do |char|
max_row = [max_row, line.length].max
line.each_char.collect do |char|
case char
case char
when EMPTY, HEAD, TAIL, CONDUCTOR then char
when EMPTY, HEAD, TAIL, CONDUCTOR
else EMPTY
char
else
EMPTY
end
end
end
end
end
end
@width = @grid.collect{|row| row.length}.max + 1
@original_grid = Marshal.restore(Marshal.dump @grid) # this is a deep copy
@width = max_row
@height = @grid.length
@height = @grid.length
pad_grid
pad_grid
@original_grid = Marshal.restore(Marshal.dump @grid) # this is a deep copy
end
end

# initialize from a file
# initialize from a file
def self.open(filename)
def self.open(filename)
self.new(File.read(filename))
self.new(File.read(filename))
end
end

def reset
def reset
@grid = @original_grid
@grid = @original_grid
end
end

# ensure all rows are the same length by padding short rows with empty cells
# ensure all rows are the same length by padding short rows with empty cells
def pad_grid
def pad_grid
@grid << []
@grid.each do |row|
@grid.each do |row|
if @width > row.length
row.concat(Array.new(@width - row.length, EMPTY))
row.concat(Array.new(@width - row.length, EMPTY))
end
end
end
end
end

# the "to_string" method
# the "to_string" method
def to_s
def to_s
@grid.inject('') {|str, row| str << row.join('') << "\n"}
@grid.collect {|row| row.join}.join("\n")
end
end

# transition all cells simultaneously
# transition all cells simultaneously
def transition
def transition
Line 59: Line 53:
end
end
end
end

# how to transition a single cell
# how to transition a single cell
def transition_cell(current, x, y)
def transition_cell(current, x, y)
Line 66: Line 60:
when HEAD then TAIL
when HEAD then TAIL
when TAIL then CONDUCTOR
when TAIL then CONDUCTOR
else neighbours_with_state(HEAD, x, y).between?(1,2) ? HEAD : CONDUCTOR
else neighbours_with_state(x, y).between?(1,2) ? HEAD : CONDUCTOR
end
end
end
end

# given a position in the grid, find the neighbour cells with a particular state
# given a position in the grid, find the neighbour cells with a particular state
def neighbours_with_state(state, x, y)
def neighbours_with_state(x, y)
count = 0
NEIGHBOURS.count {|dx, dy| @grid[y+dy][x+dx] == HEAD}
([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
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
# run a simulation up to a limit of transitions, or until a recurring
# pattern is found
# pattern is found
# This will print text to the console
# This will print text to the console
def run(iterations = 25)
def run(iterations = 25)
seen = []
seen = {}
count = 0
for count in 0..iterations
puts "Generation : #{count}"
loop do
puts self
puts to_s
puts
if seen[@grid]

if seen.include?(@grid)
puts "I've seen this grid before... after #{count} iterations"
puts "I've seen this grid before... after #{count} iterations"
break
return
end
end
if count == iterations
seen[@grid] = count
puts "ran through #{iterations} iterations"
break
end

seen << @grid
count += 1
transition
transition
end
end
puts "ran through #{iterations} iterations"
end
end
end</lang>

Test (Text version)
<lang ruby># this is the "2 Clock generators and an XOR gate" example from the wikipedia page
text = <<WORLD
......tH
. ......
...Ht... .
....
. .....
....
tH...... .
. ......
...Ht...
WORLD


ww = WireWorld.new text
# the gui version
def run_tk
ww.run
@tk_root = TkRoot.new("title" => "WireWorld")
puts 'bye'</lang>
{{out}}
<pre style="height:64ex;overflow:scroll">
Generation : 0
......tH
. ......
...Ht... .
....
. .....
....
tH...... .
. ......
...Ht...
Generation : 1
.......t
. H.....
..Ht.... .
....
. .....
....
.tH..... .
. ......
..Ht....
Generation : 2
........
. tH....
.Ht....H .
....
. .....
....
..tH.... .
. ......
.Ht.....
Generation : 3
........
. .tH...
Ht....Ht .
....
. .....
....
...tH... .
. ......
Ht......
Generation : 4
........
H ..tH..
t....Ht. .
....
. .....
....
....tH.. .
H ......
t.......
Generation : 5
H.......
t ...tH.
....Ht.. .
....
. .....
....
H....tH. .
t ......
........
Generation : 6
tH......
. ....tH
...Ht... .
....
. .....
....
tH....tH .
. ......
........
Generation : 7
.tH.....
. .....t
..Ht.... H
....
. .....
....
.tH....t .
. H.....
........
Generation : 8
..tH....
. ......
.Ht..... t
HHH.
. .....
....
..tH.... .
. tH....
.......H
Generation : 9
...tH...
. ......
Ht...... .
tttH
H H....
....
...tH... .
. .tH...
......Ht
Generation : 10
....tH..
H ......
t....... .
...t
t tH...
HHHH
....tH.. .
. ..tH..
.....Ht.
Generation : 11
H....tH.
t ......
........ .
....
. .tH..
tttt
.....tH. .
. ...tH.
....Ht..
Generation : 12
tH....tH
. ......
........ .
....
. ..tH.
....
......tH .
. ....tH
...Ht...
Generation : 13
.tH....t
. H.....
........ .
....
. ...tH
....
.......t H
. H....t
..Ht....
Generation : 14
..tH....
. tH....
.......H .
....
. ....t
HHH.
........ t
. tH....
.Ht....H
Generation : 15
...tH...
. .tH...
......Ht .
....
H H....
tttH
........ .
. .tH...
Ht....Ht
Generation : 16
....tH..
. ..tH..
.....Ht. .
HHHH
t tH...
...t
........ .
H ..tH..
t....Ht.
Generation : 17
.....tH.
. ...tH.
....Ht.. .
tttt
. .tH..
....
H....... .
t ...tH.
....Ht..
Generation : 18
......tH
. ....tH
...Ht... .
....
. ..tH.
....
tH...... .
. ....tH
...Ht...
Generation : 19
.......t
. H....t
..Ht.... H
....
. ...tH
....
.tH..... H
. .....t
..Ht....
Generation : 20
........
. tH....
.Ht....H t
HHH.
. ....t
HHH.
..tH.... t
. ......
.Ht.....
Generation : 21
........
. .tH...
Ht....Ht .
tttH
. H....
tttH
...tH... .
. ......
Ht......
Generation : 22
........
H ..tH..
t....Ht. .
...t
. t....
...t
....tH.. .
H ......
t.......
Generation : 23
H.......
t ...tH.
....Ht.. .
....
. .....
....
H....tH. .
t ......
........
I've seen this grid before... after 23 iterations
bye
</pre>


The GUI version
@tk_text = TkText.new(@tk_root,
{{libheader|Ruby/Tk}}
:width => @width,
:height => @height,
:font => 'courier')
@tk_text.insert('end', self.to_s).state('disabled')


The GUI is somewhat "halfway", in that it animates a text widget so it's not "real" graphics.
<lang ruby>require 'tk'
class WireWorld
def run_tk
@tk_root = TkRoot.new(title: "WireWorld")
@tk_text = TkText.new(width: @width, height: @height, font: 'courier')
@tk_text.insert('end', self.to_s).state('disabled')
@tk_after_interval = 150
@tk_after_interval = 150
faster_cmd = proc {@tk_after_interval = [25, @tk_after_interval-25].max}
faster_cmd = proc {@tk_after_interval = [25, @tk_after_interval-25].max}
Line 130: Line 399:
@tk_root.destroy
@tk_root.destroy
end
end

controls = TkFrame.new(@tk_root)
controls = TkFrame.new
[ TkButton.new(controls, :text => 'Slower', :command => slower_cmd),
[ TkButton.new(controls, text: 'Slower', command: slower_cmd),
TkButton.new(controls, :text => 'Faster', :command => faster_cmd),
TkButton.new(controls, text: 'Faster', command: faster_cmd),
TkButton.new(controls, :text => 'Reset', :command => reset_cmd),
TkButton.new(controls, text: 'Reset', command: reset_cmd),
TkButton.new(controls, :text => 'Close', :command => close_cmd),
TkButton.new(controls, text: 'Close', command: close_cmd),
].each {|btn| btn.pack(:expand => 1, :fill => 'x', :side => 'left')}
].each {|btn| btn.pack(expand: 1, fill: 'x', side: 'left')}

@tk_text.pack(:expand => 1, :fill => 'both')
@tk_text.pack(expand: 1, fill: 'both')
controls.pack(:fill => 'x')
controls.pack(fill: 'x')

@tk_after_id = @tk_root.after(500) {animate}
@tk_after_id = @tk_root.after(500) {animate}
Tk.mainloop
Tk.mainloop
end
end

def animate
def animate
transition
transition
Line 155: Line 424:
end
end


ww = WireWorld.new text
# this is the "2 Clock generators and an XOR gate" example from the wikipedia page
ww.run_tk</lang>
ww = WireWorld.new <<WORLD

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

WORLD

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


{{libheader|Shoes}}
{{libheader|Shoes}}
<lang ruby>$ww = WireWorld.new <<WORLD
<lang ruby>ww = WireWorld.new text
Shoes.app(title: "Wireworld") do

world = para('', family: 'monospace')
......tH
. ......
...Ht... .
....
. .....
....
tH...... .
. ......
...Ht...

WORLD

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

Latest revision as of 07:14, 23 June 2014

<lang ruby>class WireWorld

 EMPTY     = ' '
 HEAD      = 'H'
 TAIL      = 't'
 CONDUCTOR = '.'
 NEIGHBOURS = [-1,0,1].product([-1,0,1]) - [0,0]
 
 def initialize(string)
   @grid = string.each_line.collect do |line|
             line.chomp.each_char.collect do |char| 
               case char
               when EMPTY, HEAD, TAIL, CONDUCTOR
                 char
               else
                 EMPTY
               end 
             end
           end
   @width = @grid.collect{|row| row.length}.max + 1
   @height = @grid.length
   pad_grid
   @original_grid = Marshal.restore(Marshal.dump @grid)   # this is a deep copy
 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 << []
   @grid.each do |row|
     row.concat(Array.new(@width - row.length, EMPTY))
   end
 end
 
 # the "to_string" method
 def to_s
   @grid.collect {|row| row.join}.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(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(x, y)
   NEIGHBOURS.count {|dx, dy| @grid[y+dy][x+dx] == HEAD}
 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 = {}
   for count in 0..iterations
     puts "Generation : #{count}"
     puts to_s
     
     if seen[@grid]
       puts "I've seen this grid before... after #{count} iterations"
       return
     end
     
     seen[@grid] = count
     transition
   end
   puts "ran through #{iterations} iterations"
 end

end</lang>

Test (Text version) <lang ruby># this is the "2 Clock generators and an XOR gate" example from the wikipedia page text = <<WORLD

......tH

. ......

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

. ......

...Ht...

WORLD

ww = WireWorld.new text

ww.run puts 'bye'</lang>

Output:
Generation : 0
 ......tH              
.        ......        
 ...Ht...      .       
              ....     
              .  ..... 
              ....     
 tH......      .       
.        ......        
 ...Ht...              
                       
Generation : 1
 .......t              
.        H.....        
 ..Ht....      .       
              ....     
              .  ..... 
              ....     
 .tH.....      .       
.        ......        
 ..Ht....              
                       
Generation : 2
 ........              
.        tH....        
 .Ht....H      .       
              ....     
              .  ..... 
              ....     
 ..tH....      .       
.        ......        
 .Ht.....              
                       
Generation : 3
 ........              
.        .tH...        
 Ht....Ht      .       
              ....     
              .  ..... 
              ....     
 ...tH...      .       
.        ......        
 Ht......              
                       
Generation : 4
 ........              
H        ..tH..        
 t....Ht.      .       
              ....     
              .  ..... 
              ....     
 ....tH..      .       
H        ......        
 t.......              
                       
Generation : 5
 H.......              
t        ...tH.        
 ....Ht..      .       
              ....     
              .  ..... 
              ....     
 H....tH.      .       
t        ......        
 ........              
                       
Generation : 6
 tH......              
.        ....tH        
 ...Ht...      .       
              ....     
              .  ..... 
              ....     
 tH....tH      .       
.        ......        
 ........              
                       
Generation : 7
 .tH.....              
.        .....t        
 ..Ht....      H       
              ....     
              .  ..... 
              ....     
 .tH....t      .       
.        H.....        
 ........              
                       
Generation : 8
 ..tH....              
.        ......        
 .Ht.....      t       
              HHH.     
              .  ..... 
              ....     
 ..tH....      .       
.        tH....        
 .......H              
                       
Generation : 9
 ...tH...              
.        ......        
 Ht......      .       
              tttH     
              H  H.... 
              ....     
 ...tH...      .       
.        .tH...        
 ......Ht              
                       
Generation : 10
 ....tH..              
H        ......        
 t.......      .       
              ...t     
              t  tH... 
              HHHH     
 ....tH..      .       
.        ..tH..        
 .....Ht.              
                       
Generation : 11
 H....tH.              
t        ......        
 ........      .       
              ....     
              .  .tH.. 
              tttt     
 .....tH.      .       
.        ...tH.        
 ....Ht..              
                       
Generation : 12
 tH....tH              
.        ......        
 ........      .       
              ....     
              .  ..tH. 
              ....     
 ......tH      .       
.        ....tH        
 ...Ht...              
                       
Generation : 13
 .tH....t              
.        H.....        
 ........      .       
              ....     
              .  ...tH 
              ....     
 .......t      H       
.        H....t        
 ..Ht....              
                       
Generation : 14
 ..tH....              
.        tH....        
 .......H      .       
              ....     
              .  ....t 
              HHH.     
 ........      t       
.        tH....        
 .Ht....H              
                       
Generation : 15
 ...tH...              
.        .tH...        
 ......Ht      .       
              ....     
              H  H.... 
              tttH     
 ........      .       
.        .tH...        
 Ht....Ht              
                       
Generation : 16
 ....tH..              
.        ..tH..        
 .....Ht.      .       
              HHHH     
              t  tH... 
              ...t     
 ........      .       
H        ..tH..        
 t....Ht.              
                       
Generation : 17
 .....tH.              
.        ...tH.        
 ....Ht..      .       
              tttt     
              .  .tH.. 
              ....     
 H.......      .       
t        ...tH.        
 ....Ht..              
                       
Generation : 18
 ......tH              
.        ....tH        
 ...Ht...      .       
              ....     
              .  ..tH. 
              ....     
 tH......      .       
.        ....tH        
 ...Ht...              
                       
Generation : 19
 .......t              
.        H....t        
 ..Ht....      H       
              ....     
              .  ...tH 
              ....     
 .tH.....      H       
.        .....t        
 ..Ht....              
                       
Generation : 20
 ........              
.        tH....        
 .Ht....H      t       
              HHH.     
              .  ....t 
              HHH.     
 ..tH....      t       
.        ......        
 .Ht.....              
                       
Generation : 21
 ........              
.        .tH...        
 Ht....Ht      .       
              tttH     
              .  H.... 
              tttH     
 ...tH...      .       
.        ......        
 Ht......              
                       
Generation : 22
 ........              
H        ..tH..        
 t....Ht.      .       
              ...t     
              .  t.... 
              ...t     
 ....tH..      .       
H        ......        
 t.......              
                       
Generation : 23
 H.......              
t        ...tH.        
 ....Ht..      .       
              ....     
              .  ..... 
              ....     
 H....tH.      .       
t        ......        
 ........              
                       
I've seen this grid before... after 23 iterations
bye

The GUI 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

 def run_tk
   @tk_root = TkRoot.new(title: "WireWorld")
   
   @tk_text = TkText.new(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
   [ 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

ww = WireWorld.new text ww.run_tk</lang>

Library: Shoes

<lang ruby>ww = WireWorld.new text Shoes.app(title: "Wireworld") do

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

end</lang>