Wireworld: Difference between revisions
(Ada solution added) |
(added ocaml) |
||
Line 736: | Line 736: | ||
HtH. ..... |
HtH. ..... |
||
</pre> |
</pre> |
||
=={{header|OCaml}}== |
|||
<lang ocaml>let w = [| |
|||
" ......tH "; |
|||
" . ...... "; |
|||
" ...Ht... . "; |
|||
" .... "; |
|||
" . ..... "; |
|||
" .... "; |
|||
" tH...... . "; |
|||
" . ...... "; |
|||
" ...Ht... "; |
|||
|] |
|||
let is_head w x y = |
|||
try if w.(x).[y] = 'H' then 1 else 0 |
|||
with _ -> 0 |
|||
let neighborhood_heads w x y = |
|||
let n = ref 0 in |
|||
for _x = pred x to succ x do |
|||
for _y = pred y to succ y do |
|||
n := !n + (is_head w _x _y) |
|||
done; |
|||
done; |
|||
(!n) |
|||
let step w = |
|||
let n = Array.init (Array.length w) (fun i -> String.copy w.(i)) in |
|||
let width = Array.length w |
|||
and height = String.length w.(0) |
|||
in |
|||
for x = 0 to pred width do |
|||
for y = 0 to pred height do |
|||
n.(x).[y] <- ( |
|||
match w.(x).[y] with |
|||
| ' ' -> ' ' |
|||
| 'H' -> 't' |
|||
| 't' -> '.' |
|||
| '.' -> |
|||
(match neighborhood_heads w x y with |
|||
| 1 | 2 -> 'H' |
|||
| _ -> '.') |
|||
| _ -> assert false) |
|||
done; |
|||
done; |
|||
(n) |
|||
let print = (Array.iter print_endline) |
|||
let () = |
|||
let rec aux w = |
|||
Unix.sleep 1; |
|||
let n = step w in |
|||
print n; |
|||
aux n |
|||
in |
|||
aux w</lang> |
|||
=={{header|Python}}== |
=={{header|Python}}== |
Revision as of 20:32, 12 September 2009
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.
Ada
<lang Ada> with Ada.Text_IO; use Ada.Text_IO;
procedure Test_Wireworld is
type Cell is (' ', 'H', 't', '.'); type Board is array (Positive range <>, Positive range <>) of Cell; -- Perform one transition of the cellular automation procedure Wireworld (State : in out Board) is function "abs" (Left : Cell) return Natural is begin if Left = 'H' then return 1; else return 0; end if; end "abs"; Above : array (State'Range (2)) of Cell := (others => ' '); Left : Cell := ' '; Current : Cell; begin for I in State'First (1) + 1..State'Last (1) - 1 loop for J in State'First (2) + 1..State'Last (2) - 1 loop Current := State (I, J); case Current is when ' ' => null; when 'H' => State (I, J) := 't'; when 't' => State (I, J) := '.'; when '.' => if abs Above ( J - 1) + abs Above ( J) + abs Above ( J + 1) + abs Left + abs State (I, J + 1) + abs State (I + 1, J - 1) + abs State (I + 1, J) + abs State (I + 1, J + 1) in 1..2 then State (I, J) := 'H'; else State (I, J) := '.'; end if; end case; Above (J - 1) := Left; Left := Current; end loop; end loop; end Wireworld; -- Print state of the automation procedure Put (State : Board) is begin for I in State'First (1) + 1..State'Last (1) - 1 loop for J in State'First (2) + 1..State'Last (2) - 1 loop case State (I, J) is when ' ' => Put (' '); when 'H' => Put ('H'); when 't' => Put ('t'); when '.' => Put ('.'); end case; end loop; New_Line; end loop; end Put; Oscillator : Board := (" ", " tH ", " . .... ", " .. ", " ");
begin
for Step in 0..9 loop Put_Line ("Step" & Integer'Image (Step) & " ---------"); Put (Oscillator); Wireworld (Oscillator); end loop;
end Test_Wireworld; </lang> The solution assumes that the border of the board is empty. When transition is performed these cells are not changed. Automation transition is an in-place operation that allocates memory for to keep one row of the board size.
Step 0 --------- tH . .... .. Step 1 --------- .t . H... .. Step 2 --------- .. . tH.. .H Step 3 --------- .. . .tH. Ht Step 4 --------- .. H ..tH t. Step 5 --------- H. t ...t .. Step 6 --------- tH . .... .. Step 7 --------- .t . H... .. Step 8 --------- .. . tH.. .H Step 9 --------- .. . .tH. Ht
C
Evolution is key-driven instead of being time-driven: any key (except 'q' which terminates the program) evolves the system.
<lang c>#include <stdio.h>
- include <stdlib.h>
- include <GL/glut.h>
- include <assert.h>
- define WIDTH 20
- define HEIGHT 20
- define MAX_SIZE 256
// Pixels Per Point (every "point" is an "element" of the wireworld) int PPP = 10; int width = WIDTH, height = HEIGHT;
int win;
int fc = 0; char *field[2];
// free mem hook
void freemem(void)
{
free(field[0]); free(field[1]);
}
// BLACK
- define EMPTY 0.0, 0.0, 0.0
// head WHITE
- define E_HEAD 1.0, 1.0, 1.0
// tail GRAY
- define E_TAIL 0.7, 0.7, 0.7
// conductor RED
- define CONDUCTOR 1.0, 0.0, 0.0
- define cEMPTY ' '
- define cHEAD 'H'
- define cTAIL 't'
- define cCONDUCTOR '.'
// GL coords are from -1 to 1
- define GLCOORD_X(a) ( (double)(2.0*(a))/(double)(width*PPP) - 1.0 )
- define GLCOORD_Y(a) ( (double)(2.0*(a))/(double)(height*PPP) - 1.0 )
// show wireworld void wireworld_show(void) {
int j, i;
glClearColor(EMPTY, 1.0); glClear(GL_COLOR_BUFFER_BIT);
for(j=0; j < height; j++) { for(i=0; i < width; i++) { switch( *(field[fc%2] + j*width + i) ) { case cTAIL: glColor3d(E_TAIL); break; case cHEAD: glColor3d(E_HEAD); break; case cCONDUCTOR: glColor3d(CONDUCTOR); break; default: glColor3d(EMPTY); break; } glRectd(GLCOORD_X(i*PPP), GLCOORD_Y(j*PPP), GLCOORD_X((i+1)*PPP-1), GLCOORD_Y((j+1)*PPP-1)); } } glFlush();
}
- define THIS(I,J) *(field[fc%2] + (I) + (J)*width)
- define NEXT(I,J) *(field[(fc+1)%2] + (I) + (J)*width)
int count_heads(int i, int j) {
int e = 0; int ax, ay; for(ax=-1; ax <= 1; ax++) { for(ay=-1; ay <= 1; ay++) { if ( ((i+ax) < 0) || ( (j+ay) < 0) || ( (i+ax) >= width ) || ( (j+ay) >= height ) ) continue; if ( (ax==0) && (ay==0) ) continue; if ( THIS(i+ax, j+ay) == cHEAD ) e++; } } return e;
}
void evolve_wireworld(void) {
int j, i; int ehn; for(i=0; i < width; i++) { for(j=0; j < height; j++) { switch( THIS(i, j) ) { case cHEAD: NEXT(i, j) = cTAIL; break; case cTAIL: NEXT(i, j) = cCONDUCTOR; break; case cCONDUCTOR: ehn = count_heads(i, j); if ( (ehn == 1) || (ehn == 2) ) NEXT(i, j) = cHEAD; else NEXT(i, j) = cCONDUCTOR; break; default: NEXT(i, j) = THIS(i, j); } } } fc++;
}
// key hit
void key_hit(unsigned char k, int x, int y)
{
if ( k == 'q' ) { glFinish(); glutDestroyWindow(win); exit(EXIT_SUCCESS); } else { evolve_wireworld(); wireworld_show(); }
}
int main(int argc, char *argv[])
{
FILE *p; int i, j;
if ( argc < 2 ) { fprintf(stderr, "specify a wireworld initial state\n"); exit(EXIT_FAILURE); }
if ( (p = fopen(argv[1], "r")) != NULL ) {
field[0] = malloc(MAX_SIZE*MAX_SIZE*sizeof(char)); assert(field[0] != NULL); //lazy a-check field[1] = malloc(MAX_SIZE*MAX_SIZE*sizeof(char)); assert(field[1] != NULL); atexit(freemem); fc = 0;
char buf[MAX_SIZE]; j = 0; while( !feof(p) && fgets(buf, MAX_SIZE, p) && ( j < MAX_SIZE )) { for(i=0; (buf[i] != 0) && (buf[i] != 10) && (i < MAX_SIZE); i++ ) { *(field[fc%2] + j*width + i) = buf[i]; } for( ; i < MAX_SIZE; i++) *(field[fc%2] + j*width + i) = 0; j++; } fclose(p);
// no more error check :) glutInit(&argc, argv);
win = glutCreateWindow("Wireworld"); glutInitWindowPosition(0,0); glutInitWindowSize(width*PPP, height*PPP);
glutDisplayFunc(wireworld_show); glutKeyboardFunc(key_hit);
// (-1,-1) from lower leftmost to upper leftmost corner float matrix[16] = { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; glLoadMatrixf(matrix);
glutMainLoop(); } else { fprintf(stderr, "cannot open %s\n", argv[1]); exit(EXIT_FAILURE); }
exit(EXIT_SUCCESS);
}</lang>
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
Haskell
<lang Haskell> import Data.List import Control.Monad import Control.Arrow import Data.Maybe
states=" Ht." shiftS=" t.."
borden bc xs = bs: (map (\x -> bc:(x++[bc])) xs) ++ [bs]
where r = length $ head xs bs = replicate (r+2) bc
take3x3 = ap ((.). taken. length) (taken. length. head) `ap` borden '*'
where taken n = transpose. map (take n.map (take 3)).map tails
nwState xs | e =='.' && noH>0 && noH<3 = 'H'
| otherwise = shiftS !! (fromJust $ findIndex (==e) states) where e = xs!!1!!1 noH = length $ filter (=='H') $ concat xs
runCircuit = iterate (map(map nwState).take3x3) </lang> Example executed in GHCi: <lang Haskell> oscillator= [" tH ",
". ....", " .. " ]
example = mapM_ (mapM_ putStrLn) .map (borden ' ').take 9 $ runCircuit oscillator </lang> Ouptput:
*Main> example tH . .... .. .t . H... .. .. . tH.. .H .. . .tH. Ht .. H ..tH t. H. t ...t .. tH . .... .. .t . H... .. .. . tH.. .H (0.01 secs, 541764 bytes)
J
The example circuit: <lang J> circ0=:}: ] ;. _1 LF, 0 : 0 tH........ . .
...
. . Ht.. ..... ) </lang> A 'boarding' verb board and the next cell state verb nwS: <lang J>
board=: ' ' ,.~ ' ' ,. ' ' , ' ' ,~ ]
nwS=: 3 : 0
e=. (<1 1){y if. ('.'=e)*. e.&1 2 +/'H'=,y do. 'H' return. end. ' t..' {~ ' Ht.' i. e
) </lang> The 'most' powerful part is contained in the following iterating sentence, namely the dyad cut ;. [1]. In this way verb nwS can work on all the 3x3 matrices containing each cell surrounded by its 8 relevant neighbors. <lang J> (3 3 nwS;. _3 board)^: (<10) circuit</lang> Example run:
(3 3 nwS;. _3 board)^: (<10) circ0 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.t . H ... . . HtH. .....
OCaml
<lang ocaml>let w = [|
" ......tH "; " . ...... "; " ...Ht... . "; " .... "; " . ..... "; " .... "; " tH...... . "; " . ...... "; " ...Ht... "; |]
let is_head w x y =
try if w.(x).[y] = 'H' then 1 else 0 with _ -> 0
let neighborhood_heads w x y =
let n = ref 0 in for _x = pred x to succ x do for _y = pred y to succ y do n := !n + (is_head w _x _y) done; done; (!n)
let step w =
let n = Array.init (Array.length w) (fun i -> String.copy w.(i)) in let width = Array.length w and height = String.length w.(0) in for x = 0 to pred width do for y = 0 to pred height do n.(x).[y] <- ( match w.(x).[y] with | ' ' -> ' ' | 'H' -> 't' | 't' -> '.' | '.' -> (match neighborhood_heads w x y with | 1 | 2 -> 'H' | _ -> '.') | _ -> assert false) done; done; (n)
let print = (Array.iter print_endline)
let () =
let rec aux w = Unix.sleep 1; let n = step w in print n; aux n in aux w</lang>
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
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>
Tcl
<lang tcl>package require Tcl 8.6 package require Tk
- The color scheme.
- The order is: empty, conductor, electronTail, electronHead
set colors "#000000 #000080 #8080ff #ffffff"
- Encapsulate the per-cell logic in a class to simplify it
oo::class create wireCell {
variable X Y S0 S1 Neighbours constructor {state x y} {
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\"" } } set at($x,$y) [self]
} # 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} method x {} {return $X} method y {} {return $Y} # 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 {} {
set S0 $S1
}
}
- A subclass that knows how to display state changes on the GUI
oo::class create GUIwireCell {
superclass wireCell variable S0 S1 X Y constructor {state x y} {
global colors next $state $x $y pixels put [lindex $colors $S0] -to $X $Y
} method transition1 {} {
# Only do the plot of the state changed; more efficient if {$S0 != $S1} { global colors pixels put [lindex $colors [next]] -to $X $Y }
}
}
- 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 { lappend cells [GUIwireCell new electronHead $x $y] } t { lappend cells [GUIwireCell new electronTail $x $y] } . { lappend cells [GUIwireCell new conductor $x $y] } } incr x } incr y
} # Now inform each cell about its connectivity foreach cell $cells {
$cell initConnectivity
} unset at
}
- How to save the current state as a layout that [loadWires] can load
proc saveWires {filename} {
global cells
# Make a grid of empty characters of the right size set chs [lrepeat [image height pixels] [lrepeat [image width pixels] " "]]
# Transcribe the non-empty cells into the grid foreach cell $cells {
lset chs [$cell y] [$cell x] [string index " .tH" [$cell state]]
}
# Write the characters to stdout or a file if {$filename eq "-"} {set f stdout} else {set f [open $filename w]} foreach row $chs {
puts $f [join $row ""]
} if {$f ne "stdout"} {close $f}
}
- 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]
}
- 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] bind . <KeyPress> {saveWires -} after 1000 timeStep 250</lang>