#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"

# Life by Jason Tang
# http://mini.net/tcl/life

### configurable parameters below

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

proc get_color {age} {
    if {$age == 0} {
        return black
    } elseif {$age == 1} {
        return blue 
    } elseif {$age == 2} {
        return turquoise
    } elseif {$age == 3} {
        return green 
    } elseif {$age == 4} {
        return greenyellow
    } elseif {$age <= 6} {
        return yellow
    } elseif {$age <= 9} {
        return orange
    } else {
        return red
    }
}

### end configuration

array set deltas {
    0:x  0   0:y -1
    1:x  1   1:y -1
    2:x  1   2:y  0
    3:x  1   3:y  1
    4:x  0   4:y  1
    5:x -1   5:y  1
    6:x -1   6:y  0
    7:x -1   7:y -1
}

proc life {end_col end_row} {
    global life deltas
    for {set row 0} {$row < $end_row} {incr row} {
        for {set col 0} {$col < $end_col} {incr col} {
            set numneighbors 0
            for {set i 0} {$i <= 7} {incr i} {
                set x [expr ($col + $deltas($i:x)) % $end_col]
                set y [expr ($row + $deltas($i:y)) % $end_row]
                incr numneighbors $life($x,$y)
            }
            set count($col,$row) $numneighbors
        }
    }
    for {set row 0} {$row < $end_row} {incr row} {
        for {set col 0} {$col < $end_col} {incr col} {
            if {$life($col,$row) == 0} {
                if {$count($col,$row) == 3} {
                    set life($col,$row) 1
                    set life($col,$row:age) 1
                    change_life $col $row
                }
            } else {
                if {[expr $count($col,$row) == 2] || \
                        [expr $count($col,$row) == 3]} {
                    incr life($col,$row:age)
                    change_life $col $row
                } else {
                    kill_life $col $row
                }
            }
        }
    }
}

proc change_life {col row} {
    global life
    .c itemconfigure $life($col,$row:id) \
        -fill [get_color $life($col,$row:age)]
}

proc make_life {col row type} {
    global life CELL_SIZE
    set colorval [get_color $type]
    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 $colorval -width 0]
    set life($col,$row) $type
    set life($col,$row:age) $type
}

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

proc kill_life {col row} {
    global life
    set life($col,$row) 0
    set life($col,$row:age) 0
    .c itemconfigure $life($col,$row:id) -fill [get_color 0]
}

proc init_life {end_col end_row} {
    global life
    for {set row 0} {$row < $end_row} {incr row} {
        for {set col 0} {$col < $end_col} {incr col} {
            kill_life $col $row
	    .c delete $life($col,$row:id)
        }
    }
}

proc button_down {x y new_life} {
    global life CELL_SIZE
    set col [expr int($x / $CELL_SIZE)]
    set row [expr int($y / $CELL_SIZE)]
    kill_life $col $row
    if {$new_life} {
        set life($col,$row) 1
        set life($col,$row:age) 1
        change_life $col $row
    }
}

proc stop {} {
    after cancel life_timer
}

proc life_timer {} {
    global age NUM_ROWS NUM_COLS REFRESH RESPAWN DENSITY
    incr age -1
    if {$age < 0} {
        set age $RESPAWN
        init_life $NUM_COLS $NUM_ROWS
        generate_life $NUM_COLS $NUM_ROWS $DENSITY
    } else {
        life $NUM_COLS $NUM_ROWS
    }
    update idletasks
    after $REFRESH life_timer
}

canvas .c -bg [get_color 0] -relief flat -borderwidth 0 -highlightthickness 0 \
    -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 . <Key-F2> {console show}
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

generate_life $NUM_COLS $NUM_ROWS $DENSITY
set age $RESPAWN
life_timer

