Life

Jason Tang 2002-09-25: Here is another implementation of the cellular automata game of Life.

Use the mouse and left drag to add life, right drag to remove. Hit the enter key to resume simulating. As cells "age" they change colors. Hope you like...If you want to try a Starkit version download it here [L1 ].

An optimized version of the program may be downloaded from http://jtang.org/tcl/life/

Screenshot

jtang-life.gif

Code

### Life by Jason Tang
### configurable parameters below

package require Tk

set CELL_SIZE 15  ;# size of each cell, in pixels
set NUM_ROWS 20   ;# number of rows in the grid
set NUM_COLS 20   ;# number of columns in the grid
set REFRESH 10   ;# how often to calculate next iteration, in milliseconds
set RESPAWN 120   ;# number of iterations before spawning new life
set DENSITY 0.6   ;# how crowded to make world when generating life
set NUM_ROWS 15   ;# number of rows in the grid
set NUM_COLS 15   ;# number of columns in the grid

### end configuration

proc life {start_col end_col start_row end_row} {
   global life
   for {set row $start_row} {$row <= $end_row} {incr row} {
       for {set col $start_col} {$col <= $end_col} {incr col} {
           set count($col,$row) 0
           if {$life($col,$row) == 1} {
               set count($col,$row) -1
           }
           for {set x [expr $col - 1]} {$x <= [expr $col + 1]} {incr x} {
               for {set y [expr $row - 1]} {$y <= [expr $row + 1]} {incr y} {
                   if {$x < $start_col} {
                       set x_col $end_col
                   } elseif {$x > $end_col} {
                       set x_col $start_col
                   } else {
                       set x_col $x
                   }
                   if {$y < $start_row} {
                       set y_row $end_row
                   } elseif {$y > $end_row} {
                       set y_row $start_row
                   } else {
                       set y_row $y
                   }
                   incr count($col,$row) $life($x_col,$y_row)
               }
           }
       }
   }
   for {set row $start_row} {$row <= $end_row} {incr row} {
       for {set col $start_col} {$col <= $end_col} {incr col} {
           if {$life($col,$row) == 0 && $count($col,$row) == 3} {
               make_life $col $row
           } elseif {$life($col,$row) != 1 || \
                   [expr {$count($col,$row) != 2} && \
                   {$count($col,$row) != 3}]} {
               kill_life $col $row
           } else {
               incr life($col,$row:age)
               if {$life($col,$row:age) >= 10} {
                   .c itemconfigure $life($col,$row:id) -fill red
               } elseif {$life($col,$row:age) >= 7} {
                   .c itemconfigure $life($col,$row:id) -fill orange
               } elseif {$life($col,$row:age) >= 5} {
                   .c itemconfigure $life($col,$row:id) -fill yellow
               } elseif {$life($col,$row:age) >= 4} {
                   .c itemconfigure $life($col,$row:id) -fill greenyellow
               } elseif {$life($col,$row:age) >= 3} {
                   .c itemconfigure $life($col,$row:id) -fill green
               } elseif {$life($col,$row:age) >= 2} {
                   .c itemconfigure $life($col,$row:id) -fill turquoise
               }
           }
       }
   }
}

proc make_life {col row} {
   global life CELL_SIZE
   set life($col,$row:id) [.c create rectangle \
           [expr $col * $CELL_SIZE + 1] \
           [expr $row * $CELL_SIZE + 1] \
           [expr [expr $col + 1] * $CELL_SIZE - 1] \
           [expr [expr $row + 1] * $CELL_SIZE - 1] \
           -fill blue -width 0]
   set life($col,$row:age) 1
   set life($col,$row) 1
}

proc kill_life {col row} {
   global life
   set life($col,$row) 0
   if {[info exists life($col,$row:id)]} {
       .c delete $life($col,$row:id)
       unset life($col,$row:id)
       unset life($col,$row:age)
   }
}

proc generate_life {start_col end_col start_row end_row ratio} {
   global life
   for {set row $start_row} {$row <= $end_row} {incr row} {
       for {set col $start_col} {$col <= $end_col} {incr col} {
           if {[expr rand() < $ratio]} {
               make_life $col $row
           }
       }
   }
}

proc init_life {start_col end_col start_row end_row} {
   global life
   for {set row $start_row} {$row <= $end_row} {incr row} {
       for {set col $start_col} {$col <= $end_col} {incr col} {
           kill_life $col $row
       }
   }
}

proc button_down {x y new_life} {
   global CELL_SIZE
   set col [expr int($x / $CELL_SIZE)]
   set row [expr int($y / $CELL_SIZE)]
   kill_life $col $row
   if {$new_life} {
       make_life $col $row
   }
}

proc life_timer {} {
   global age NUM_ROWS NUM_COLS REFRESH RESPAWN DENSITY
   incr age -1

   if {$age < 0} {
       set age $RESPAWN
       init_life 0 [expr $NUM_COLS - 1] 0 [expr $NUM_ROWS - 1]
       generate_life 0 [expr $NUM_COLS - 1] 0 [expr $NUM_ROWS - 1] $DENSITY
   } else {
       life 0 [expr $NUM_COLS - 1] 0 [expr $NUM_ROWS - 1]
   }

   update idletasks

   after $REFRESH life_timer
} 

canvas .c -bg black -relief flat \
       -scrollregion [list 0 0 [expr $NUM_COLS * $CELL_SIZE] \
                               [expr $NUM_ROWS * $CELL_SIZE]] \
       -width [expr $NUM_COLS * $CELL_SIZE] \
       -height [expr $NUM_ROWS * $CELL_SIZE]

pack .c

wm protocol . WM_DELETE_WINDOW { exit }
wm title . "Life"
bind . <ButtonPress-1> {after cancel life_timer; button_down %x %y 1}
bind . <ButtonPress-3> {after cancel life_timer; button_down %x %y 0}
bind . <B1-Motion> {button_down %x %y 1}
bind . <B3-Motion> {button_down %x %y 0}
bind . <Key-Return> {after cancel life_timer; set age $RESPAWN; life_timer}
bind . <Key-space> {after cancel life_timer}

update idletasks

set age 0
life_timer