Wireworld/Ruby: Difference between revisions
Content added Content deleted
(Moving from main task page to shorten it) |
No edit summary |
||
(One intermediate revision by one other user not shown) | |||
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 |
when EMPTY, HEAD, TAIL, CONDUCTOR |
||
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| |
||
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. |
@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( |
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( |
def neighbours_with_state(x, y) |
||
count = |
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 |
for count in 0..iterations |
||
puts "Generation : #{count}" |
|||
loop do |
|||
puts |
puts to_s |
||
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" |
||
return |
|||
end |
end |
||
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 |
controls = TkFrame.new |
||
[ TkButton.new(controls, : |
[ TkButton.new(controls, text: 'Slower', command: slower_cmd), |
||
TkButton.new(controls, : |
TkButton.new(controls, text: 'Faster', command: faster_cmd), |
||
TkButton.new(controls, : |
TkButton.new(controls, text: 'Reset', command: reset_cmd), |
||
TkButton.new(controls, : |
TkButton.new(controls, text: 'Close', command: close_cmd), |
||
].each {|btn| btn.pack(: |
].each {|btn| btn.pack(expand: 1, fill: 'x', side: 'left')} |
||
@tk_text.pack(: |
@tk_text.pack(expand: 1, fill: 'both') |
||
controls.pack( |
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 |
|||
{{libheader|Shoes}} |
|||
......tH |
|||
<lang ruby>ww = WireWorld.new text |
|||
. ...... |
|||
Shoes.app(title: "Wireworld") do |
|||
...Ht... . |
|||
world = para('', family: 'monospace') |
|||
.... |
|||
animate(4) do |
|||
. ..... |
|||
world.text = ww.to_s |
|||
ww.transition |
|||
tH...... . |
|||
end |
|||
. ...... |
|||
end</lang> |
|||
...Ht... |
|||
WORLD |
|||
ww.run |
|||
ww.reset |
|||
ww.run_tk |
|||
puts 'bye'</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
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>
<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>