Wireworld: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|Tcl}}: added libheader)
Line 666: Line 666:
=={{header|Tcl}}==
=={{header|Tcl}}==
{{works with|Tcl|8.6}}
{{works with|Tcl|8.6}}
<br>
{{libheader|Tk}}
<lang tcl>package require Tcl 8.6
<lang tcl>package require Tcl 8.6
package require Tk
package require Tk

Revision as of 07:56, 10 September 2009

Task
Wireworld
You are encouraged to solve this task according to the task description, using any language you may know.

Wireworld is a cellular automaton with some similarities to Conway's Game of Life. It is capable of doing sophisticated computations (e.g., calculating primeness!) with appropriate programs, and is much simpler to program for.

A wireworld arena consists of a cartesian grid of cells, each of which can be in one of four states. All cell transitions happen simultaneously. The cell transition rules are this:

Input State Output State Condition
empty empty
electron head  electron tail 
electron tail  conductor
conductor electron head  if 1 or 2 cells in the neighborhood of the cell are in the state “electron head
conductor conductor otherwise

To implement this task, create a program that reads a wireworld program from a file and displays an animation of the processing. Here is a sample description file (using “H” for an electron head, “t” for a tail, “.” for a conductor and a space for empty) you may wish to test with, which demonstrates two cycle-3 generators and an inhibit gate:

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

While text-only implementations of this task are possible, mapping cells to pixels is advisable if you wish to be able to display large designs. The logic is not significantly more complex.

Forth

<lang forth> 16 constant w

8 constant h
rows w * 2* ;

1 rows constant row h rows constant size

create world size allot world value old old w + value new

init world size erase ;
age new old to new to old ;
foreachrow ( xt -- )
 size 0 do  I over execute  row +loop drop ;

0 constant EMPTY 1 constant HEAD 2 constant TAIL 3 constant WIRE create cstate bl c, char H c, char t c, char . c,

showrow ( i -- ) cr
 old + w over + swap do I c@ cstate + c@ emit loop ;
show ['] showrow foreachrow  ;


line ( row addr len -- )
 bounds do
   i c@
   case
   bl of EMPTY over c! endof
   'H of HEAD  over c! endof
   't of TAIL  over c! endof
   '. of WIRE  over c! endof
   endcase
   1+
 loop drop ;
load ( filename -- )
 r/o open-file throw
 init  old row + 1+  ( file row )
 begin  over pad 80 rot read-line throw
 while  over pad rot line
        row +
 repeat
 2drop close-file throw
 show cr ;


+head ( sum i -- sum )
 old + c@ HEAD = if 1+ then ;
conductor ( i WIRE -- i HEAD|WIRE )
 drop 0
 over 1- row - +head
 over    row - +head
 over 1+ row - +head
 over 1-       +head
 over 1+       +head
 over 1- row + +head
 over    row + +head
 over 1+ row + +head
 1 3 within if HEAD else WIRE then ;

\ before: empty head tail wire

create transition ' noop , ' 1+ , ' 1+ , ' conductor ,

\ after: empty tail wire head|wire

new-state ( i -- )
 dup  old + c@
 dup cells transition + @ execute
 swap new + c! ;
newrow ( i -- )
 w over + swap do I new-state loop ;
gen ['] newrow foreachrow age ;
wireworld begin gen 0 0 at-xy show key? until ;

</lang>

Output:

s" wireworld.diode" load
                
        ..      
 tH...... .Ht   
        ..      
                
                
                
                
 ok
gen show                
                
        ..      
 .tH..... Ht.   
        ..      
                
                
                
                 ok
gen show 
                
        .H      
 ..tH.... t..   
        .H      
                
                
                
                 ok
gen show 
                
        Ht      
 ...tH..H ...   
        Ht      
                
                
                
                 ok
gen show 
                
        t.      
 ....tH.t ...   
        t.      
                
                
                
                 ok
gen show 
                
        ..      
 .....tH. ...   
        ..      
                
                
                
                 ok
gen show 
                
        H.      
 ......tH ...   
        H.      
                
                
                
                 ok
gen show 
                
        tH      
 .......t ...   
        tH      
                
                
                
                 ok
gen show 
                
        .t      
 ........ H..   
        .t      
                
                
                
                 ok
gen show 
                
        ..      
 ........ tH.   
        ..      
                
                
                
                 ok
gen show 
                
        ..      
 ........ .tH   
        ..      
                
                
                
                 ok
gen show 
                
        ..      
 ........ ..t   
        ..      
                
                
                
                 ok
gen show 
                
        ..      
 ........ ...   
        ..      
                
                
                
                 ok

Python

<lang python> Wireworld implementation.

from io import StringIO from collections import namedtuple from pprint import pprint as pp import copy

WW = namedtuple('WW', 'world, w, h') head, tail, conductor, empty = allstates = 'Ht. '


infile = StringIO(\ tH......... . .

  ...

. . Ht.. ......\ )

def readfile(f):

   'file > initial world configuration'
   world  = f.readlines()
   world  = [row.rstrip('\r\n') for row in world]
   height = len(world)
   width  = max(len(row) for row in world)
   # fill right and frame in empty cells
   nonrow = [ " %*s " % (-width, "") ]
   world  = ( nonrow
              + [ " %*s " % (-width, row) for row in world ]
              + nonrow[:] )    
   world = [list(row) for row in world]
   return WW(world, width, height)

def newcell(currentworld, x, y):

   istate = currentworld[y][x]
   assert istate in allstates, 'Wireworld cell set to unknown value "%s"' % istate
   if istate == head:
       ostate = tail
   elif istate == tail:
       ostate = conductor
   elif istate == empty:
       ostate = empty
   else: # istate == conductor
       n = sum( currentworld[y+dy][x+dx] == head
                for dx,dy in ( (-1,-1), (-1,+0), (-1,+1),
                               (+0,-1),          (+0,+1),
                               (+1,-1), (+1,+0), (+1,+1) ) )
       ostate = head if 1 <= n <= 2 else conductor
   return ostate

def nextgen(ww):

   'compute next generation of wireworld'
   world, width, height = ww
   newworld = copy.deepcopy(world)
   for x in range(1, width+1):
       for y in range(1, height+1):
           newworld[y][x] = newcell(world, x, y)
   return WW(newworld, width, height)

def world2string(ww):

   return '\n'.join( .join(row[1:-1]).rstrip() for row in ww.world[1:-1] )

ww = readfile(infile) infile.close()

for gen in range(10):

   print ( ("\n%3i " % gen) + '=' * (ww.w-4) + '\n' )
   print ( world2string(ww) )
   ww = nextgen(ww)

</lang>

Sample Output

  0 =======

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

  1 =======

.tH........
H   .
   ...
H   .
t... ......

  2 =======

H.tH.......
t   .
   ...
t   .
.H.. ......

  3 =======

tH.tH......
.   H
   ...
.   .
HtH. ......

  4 =======

.tH.tH.....
H   t
   HHH
H   .
t.tH ......

  5 =======

H.tH.tH....
t   .
   ttt
t   .
.H.t ......

  6 =======

tH.tH.tH...
.   H
   ...
.   .
HtH. ......

  7 =======

.tH.tH.tH..
H   t
   HHH
H   .
t.tH ......

  8 =======

H.tH.tH.tH.
t   .
   ttt
t   .
.H.t ......

  9 =======

tH.tH.tH.tH
.   H
   ...
.   .
HtH. ......

Ruby

Text only.

<lang ruby>class WireWorld

 def initialize(string)
   p string if $DEBUG
   max_row = 0
   @grid = string.each_line.collect do |line|
             line.chomp!
             max_row = [max_row, line.length].max
             line.each_char.collect {|char| Cell.new(char)}
           end
   @width = max_row
   @height = grid.length
   p @grid if $DEBUG
   pad_grid
 end
 attr_reader :grid, :width, :height
 def self.open(filename)
   self.new(File.read(filename))
 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, Cell.new(' ')))
     end
   end
 end
 # the "to_string" method
 def to_s
   @grid.inject() do |str, row|
     str << row.join() << "\n"
   end
 end
 # find the neighbour cells of a position in the grid
 def neighbours(x, y)
   neighbours = []
   ([x-1, 0].max .. [x+1, @width-1].min).each do |xx|
     ([y-1, 0].max .. [y+1, @height-1].min).each do |yy|
       neighbours << @grid[yy][xx] unless x == xx and y == yy
     end
   end
   neighbours
 end
 # transition all cells
 def transition
   changed = 0
   new_grid = []
   @grid.each_with_index do |row, y|
     new_row = []
     row.each_with_index do |cell, x|
       new_cell = cell.transition( neighbours(x,y) )  
       changed += 1 if cell != new_cell
       puts "#{cell} -> #{new_cell}" if $DEBUG
       new_row << new_cell
     end
     new_grid << new_row
   end
   @grid = new_grid
   changed
 end
 def run(iterations = 25)
   puts "size = #{width} x #{height}" if $DEBUG
   puts self
   seen = [@grid.to_s]
   count = 0
   loop do
     puts 
     transition
     count += 1
     puts self
     if seen.include?(@grid.to_s)
       puts "I've seen this grid before... after #{count} iterations"
       break
     end 
     if count == iterations
       puts "ran through #{iterations} iterations"
       break
     end
     seen << @grid.to_s
   end
 end

end

class Cell

 EMPTY = ' '
 HEAD = 'H'
 TAIL = 't'
 CONDUCTOR = '.'
 def initialize(char)
   unless [EMPTY, HEAD, TAIL, CONDUCTOR].include?(char)
     if $DEBUG
       warn "found invalid character '#{char}' -- ignoring it and using empty"
     end
     char = EMPTY
   end
   @state = char 
 end
 attr_reader :state
 def empty?; @state == EMPTY; end
 def head?; @state == HEAD; end
 def tail?; @state == TAIL; end
 def conductor?; @state == CONDUCTOR; end
 def transition(neighbours)
   p [neighbours] if $DEBUG
   next_state = if    empty? then EMPTY
                elsif head?  then TAIL
                elsif tail?  then CONDUCTOR
                elsif neighbours.count {|cell| cell.head?}.between?(1,2) then HEAD 
                else CONDUCTOR
                end
   self.class.new(next_state)
 end 
 def to_s
   @state
 end
 def inspect
   @state.inspect
 end
 def ==(other)
   @state == other.state
 end

end

ww = WireWorld.open('wireworld.data') ww.run

program = DATA.read ww = WireWorld.new(program) ww.run(10)

__END__ ....... ....... HHHHHHH ....... .......</lang>

Outputs

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

.tH........
H   .
   ...
H   .
t... ......

H.tH.......
t   .
   ...
t   .
.H.. ......

tH.tH......
.   H
   ...
.   .
HtH. ......

.tH.tH.....
H   t
   HHH
H   .
t.tH ......

H.tH.tH....
t   .
   ttt
t   .
.H.t ......

tH.tH.tH...
.   H
   ...
.   .
HtH. ......

.tH.tH.tH..
H   t
   HHH
H   .
t.tH ......

H.tH.tH.tH.
t   .
   ttt
t   .
.H.t ......

tH.tH.tH.tH
.   H
   ...
.   .
HtH. ......

.tH.tH.tH.t
H   t
   HHH
H   .
t.tH ......

H.tH.tH.tH.
t   .
   ttt
t   .
.H.t ......
I've seen this grid before... after 11 iterations
.......
.......
HHHHHHH
.......
.......

.......
H.....H
ttttttt
H.....H
.......

HH...HH
tH...Ht
.......
tH...Ht
HH...HH

ttH.Htt
.tH.Ht.
HHH.HHH
.tH.Ht.
ttH.Htt

..t.t..
H.t.t.H
ttt.ttt
H.t.t.H
..t.t..

HH...HH
tH...Ht
.......
tH...Ht
HH...HH
I've seen this grid before... after 5 iterations

Tcl

Works with: Tcl version 8.6


Library: Tk

<lang tcl>package require Tcl 8.6 package require Tk

  1. The color scheme.
  2. The order is: empty, conductor, electronTail, electronHead

set colors "#000000 #000080 #8080ff #ffffff"

  1. Encapsulate the per-cell logic in a class to simplify it

oo::class create wireCell {

   variable X Y S0 S1 Neighbours
   constructor {x y state} {

global cells colors upvar 1 at at

set X $x set Y $y

switch -- $state conductor { set S0 1 } electronTail { set S0 2 } electronHead { set S0 3 } default { return -code error "invalid state name \"$state\"" } lappend cells [self] set at($x,$y) [self] pixels put [lindex $colors $S0] -to $x $y

   }
   # Method used to allow each (non-background) cell to know about its
   # surrouding non-background cells. This makes the connectivity
   # calculations much simpler and faster!
   method initConnectivity {} {

upvar 1 at at foreach dx {-1 -1 -1 0 0 1 1 1} dy {-1 0 1 -1 1 -1 0 1} { set pos [expr {$X+$dx}],[expr {$Y+$dy}] if {[info exists at($pos)]} { lappend Neighbours $at($pos) } }

   }
   method state {} {return $S0}
   # Perform the transition in two stages, so that we can do the transition
   # "simultaneously" across all cells. The transition0 method calculates
   # what state we're going to change to, and the transition1 method actually
   # moves to the state.
   method transition0 {} {

if {$S0 == 3} { set S1 2 } elseif {$S0 == 2} { set S1 1 } else { set count 0 foreach n $Neighbours { incr count [expr {[$n state] == 3}] } set S1 [expr {($count == 1 || $count == 2) ? 3 : 1}] }

   }
   method transition1 {} {

if {$S0 != $S1} { global colors set S0 $S1 pixels put [lindex $colors $S0] -to $X $Y }

   }

}

  1. How to load a layout/program from a file

proc loadWires filename {

   global cells colors
   # Read the file in
   set f [open $filename]
   set data [read $f]
   close $f
   # Initialize the list of interacting cells and the connectivity map
   set cells {}
   array set at {}
   # Calculate the width of the program
   set lines [split $data \n]
   set len 0
   foreach line $lines {

if {[string length $line] > $len} { set len [string length $line] }

   }
   # Create the arena image
   image create photo pixels
   # Initialize the image to "empty cell"s; interacting parts will be overlaid
   pixels put [lindex $colors 0] -to 0 0 $len [llength $lines]
   # Parse the input data and create the interacting cells
   set y 0
   foreach line $lines {

set x 0 foreach char [split $line {}] { switch $char { H { wireCell new $x $y electronHead } t { wireCell new $x $y electronTail } . { wireCell new $x $y conductor } } incr x } incr y

   }
   # Now inform each cell about its connectivity
   foreach cell $cells {

$cell initConnectivity

   }
   unset at

}

  1. How to perform the animation timestep

proc timeStep {t} {

   global cells
   # Arm the transition for all interacting cells
   foreach cell $cells {

$cell transition0

   }
   # Perform the transition for all interacting cells
   foreach cell $cells {

$cell transition1

   }
   # Reschedule
   after $t [list timeStep $t]

}

  1. Initialize the GUI (such as it is) and load and start the animation

wm title . "Wireworld: [lindex $argv 0]" loadWires [lindex $argv 0] pack [label .l -image pixels] after 1000 timeStep 250</lang>