uniquename 2013aug01Here is an image that shows the options on the 'Game' drop-down menu --- as well as an image of the Help window.
package require Tk
proc Init {} {
array set ::S {
sz 40
rows 4
cols 4
size 0
robot -1
color,1 Red
color,-1 Blue
turn,1 "Red Player's turn"
turn,-1 "Blue Player's turn"
anim 60
turn 1
moves 0
level 3
level,max 8
won 0}
array set ::M {1 0 -1 1 2 2 -2 3}
}
proc DoDisplay {} {
wm title . "TkOverload"
wm minsize . 240 290
DoMenus
frame .fmsg -relief ridge -bd 2
label .msg -bd 0 -textvariable S(msg) -padx 5
label .msg3 -bd 0 -textvariable S(msg3) -padx 5
label .msg2 -relief ridge -textvariable S(msg2)
scale .level -orient horiz -from 1 -to $::S(level,max) -relief ridge \
-command DoLevel -showvalue 0 -variable S(level)
button .new -text "New Game" -command NewBoard
button .hint -text Hint -command Hint
frame .ftop
frame .ftop2
grid .ftop - -sticky news -row 0
grid .fmsg - -sticky ew -row 1
grid .msg2 - -sticky ew -row 2
grid .level .new -row 3
grid ^ .hint -sticky ew -row 4 -padx 5
grid configure .new -sticky ew -padx 5
grid rowconfigure . 0 -weight 1
grid columnconfigure . {0 1} -weight 1
pack .msg -in .fmsg -side left -fill x -expand 1
pack .msg3 -in .fmsg -side right
DisplayBoard
}
proc DisplayBoard {} {
global S
foreach w [grid slaves .ftop2] { destroy $w }
catch {destroy .ftop2}
frame .ftop2
pack .ftop2 -in .ftop -expand 1 -fill both
for {set row 0} {$row < $S(rows)} {incr row} {
for {set col 0} {$col < $S(cols)} {incr col} {
set w ".c$row,$col"
canvas $w -width $S(sz) -height $S(sz) -bd 2 -relief sunken
$w config -highlightthickness 0
bind $w <ButtonRelease-1> [list ButtonUp %W %X %Y $row $col]
bind $w <Configure> {ReCenter %W %h %w}
grid $w -row $row -in .ftop2 -column $col -sticky news
}
grid rowconfigure .ftop2 $row -weight 1
grid columnconfigure .ftop2 $row -weight 1
}
}
proc DoMenus {} {
. configure -menu [menu .m -tearoff 0]
.m add cascade -menu [menu .m.game -tearoff 0] -label "Game" -underline 0
.m add cascade -menu [menu .m.help -tearoff 0] -label "Help" -underline 0
.m.game add command -label "New Game" -under 0 -command NewBoard
.m.game add separator
.m.game add checkbutton -label "Computer Opponent" -under 0 \
-command GoRobot -variable S(robot) -onvalue -1 -offvalue 0
.m.game add command -label Hint -under 0 -command Hint
.m.game add separator
.m.game add checkbutton -label "Beginner" -under 0 -command Resize \
-variable S(size) -onvalue 0 -offvalue 1
.m.game add checkbutton -label "Expert" -under 0 -command Resize \
-variable S(size) -onvalue 1 -offvalue 0
.m.game add separator
.m.game add command -label Exit -under 0 -command exit
.m.help add command -label Help -under 0 -command Help
}
proc ReCenter {W h w} { ;# Called by configure event
set h [expr {$h / 2.0}]
set w [expr {$w / 2.0}]
$W config -scrollregion [list -$w -$h $w $h];# Recenter everything
if {[regexp {^.c(\d+),(\d+)$} $W -> r c]} { DoCircle $::B $r $c } ;# Resize
}
proc IDX {r c} {expr {$::S(cols)*$r+$c+4}}
proc GET {b r c} {lindex $b [expr {$::S(cols) * $r + $c + 4}]}
proc GETM {b x} {lindex $b $::M($x)}
proc SET {b r c v} {set i [expr {$::S(cols)*$r+$c+4}];lreplace $b $i $i $v}
proc SETM {b x v} {lreplace $b $::M($x) $::M($x) $v}
proc INCR {b r c {d 1}} {
set i [expr {$::S(cols)*$r+$c+4}]
lreplace $b $i $i [expr {[lindex $b $i] + $d}]
}
proc INCRM {b x {d 1}} {
lreplace $b $::M($x) $::M($x) [expr {[lindex $b $::M($x)] + $d}]
}
proc INFO {msg {who ""}} {
set ::S(msg$who) $msg
update idletasks
}
proc DoLevel {lvl} { .level config -label "Skill: $::S(level)" }
proc Resize {} {
global S
set S(rows) [set S(cols) [expr {$S(size) == 0 ? 4 : 6}]]
DisplayBoard ;# Redo the board
NewBoard
}
proc DoMove {row col} { ;# Move piece to row,col
global S B
Unhint ;# Turn off any hint
AnimateBox $row $col ;# Make it look a bit sexy
set B [AddOne $B $S(turn) $row $col 1] ;# Add the piece
WinOrLose $B ;# Is the game over???
NewTurn ;# Make it next player's turn
}
proc ButtonUp {w X Y row col} { ;# Called on mouse click
global S B
if {$S(won)} return ;# Game already over
if {$w != [winfo containing $X $Y]} return ;# Mouse moved out of cell
set val [GET $B $row $col]
set turn $S(turn)
if {$val < 0 && $turn > 0} return ;# Opponent's cell
if {$val > 0 && $turn < 0} return
DoMove $row $col
}
proc NewBoard {} {
global B S
set cnt [expr {$S(rows) * $S(cols) - 2}] ;# How many empty cells
set B "1 1 1 1 " ;# Metadata
append B "1 [string repeat "0 " $cnt]-1" ;# ...actual board
set S(turn) 1 ;# Player 1 goes first
set S(won) 0 ;# Game not over yet
set S(moves) 0 ;# How many turns
ShowBoard
}
proc ShowBoard {} {
global S B
for {set row 0} {$row < $S(rows)} {incr row} {
for {set col 0} {$col < $S(cols)} {incr col} {
DoCircle $B $row $col
}
}
INFO "Welcome to TkOverload"
INFO "" 2
INFO "" 3
}
proc DoCircle {brd row col} { ;# Draws the circles for a cell
global S
set w ".c$row,$col"
$w delete all
set val [GET $brd $row $col]
set size [expr {abs($val)}]
if {$size == 0} return
set width [winfo width $w]
set height [winfo height $w]
set min [expr {$width < $height ? $width : $height}]
set r [expr {$min / 6}] ;# Radius of circle
set r4 [expr {($min / 4) - 2}] ;# Position of circle
set fill $S(color,[expr {$val / $size}])
if {$size == 1} {
$w create oval -$r -$r $r $r -fill $fill -outline {}
} elseif {$size == 2} {
$w create oval [MakeBox -$r4 0 $r] -fill $fill -outline {}
$w create oval [MakeBox $r4 0 $r] -fill $fill -outline {}
} else {
$w create oval [MakeBox -$r4 $r $r] -fill $fill -outline {}
$w create oval [MakeBox $r4 $r $r] -fill $fill -outline {}
$w create oval [MakeBox 0 -$r $r] -fill $fill -outline {}
}
}
proc MakeBox {x y r} {
return [list [expr {$x-$r}] [expr {$y-$r}] [expr {$x+$r}] [expr {$y+$r}]]
}
proc AddOne {brd who row col show} {
global S
set val [GET $brd $row $col]
if {$val == 0} {
set brd [INCRM $brd $who] ;# Count cells occupied
}
if {$val == 3 || $val == -3} { ;# Will it explode
set brd [DoExplode $brd $who $row $col $show]
} else {
set brd [INCR $brd $row $col $who] ;# One more piece in cell
set brd [INCRM $brd [expr {2*$who}] 1] ;# One more total pieces
if {$show} {
DoCircle $brd $row $col
}
}
return $brd
}
proc DoExplode {brd who row col show} {
global S
set who2 [expr {2*$who}]
set brd [SET $brd $row $col 0] ;# Exploded cell is empty
set brd [INCRM $brd $who -1] ;# One less cell occupied
set brd [INCRM $brd $who2 -3] ;# Fewer total pieces
if {$show} {DoCircle $brd $row $col} ;# Erase it
foreach {dr dc} {-1 0 1 0 0 -1 0 1} { ;# Scatter in 4 directions
set r [expr {$row + $dr}]
set c [expr {$col + $dc}]
if {$r < 0 || $r >= $S(rows) || $c < 0 || $c >= $S(cols)} continue
set val [GET $brd $r $c] ;# Current cell value
set aval [expr {abs($val)}]
if {$who * $val < 0} { ;# Take ownership
set brd [INCRM $brd $who 1] ;# One more cell owned
set brd [INCRM $brd [expr {-$who}] -1] ;# One fewer cell owned
set brd [INCRM $brd $who2 $aval] ;# More total pieces
set brd [INCRM $brd [expr {-$who2}] [expr {-$aval}]]
set brd [SET $brd $r $c [expr {$who * $aval}]] ;# Update board
}
set brd [AddOne $brd $who $r $c $show] ;# Add another piece
}
return $brd
}
proc AnimateBox {row col} {
set w ".c$row,$col"
$w config -relief sunken
set width [winfo width $w] ; set height [winfo height $w]
set min [expr {($width < $height ? $width : $height)/2}]
for {set r 2} {$r < $min} {incr r 2} {
set start [clock clicks -milliseconds]
$w create rect -$r -$r $r $r -tag box
update idletasks
set remaining [expr {$::S(anim)-([clock clicks -milliseconds]-$start)}]
if {$remaining > 0} { after $remaining }
}
}
proc WinOrLose {brd} {
if {[GETM $brd 1] == 0} {
INFO "$::S(color,-1) Player won"
} elseif {[GETM $brd -1] == 0} {
INFO "$::S(color,1) Player won"
} else return
set ::S(won) 1
}
proc NewTurn {} {
global B S
if {$S(won)} return ;# Game already over
incr S(moves) ;# One more total moves
set S(turn) [expr {-$S(turn)}] ;# Other player's turn
if {$S(turn) == $S(robot)} {INFO "Computer's turn"} {INFO $S(turn,$S(turn))}
INFO "moves: $S(moves)" 3
GoRobot ;# Do possible robot move
}
proc Help {} {
catch {destroy .help}
toplevel .help
wm title .help "TkOverload Help"
wm geom .help "+[expr {[winfo x .] + [winfo width .] + 10}]+[winfo y .]"
text .help.t -relief raised -wrap word -width 70 -height 23 \
-padx 10 -pady 10 -cursor {} -yscrollcommand {.help.sb set}
scrollbar .help.sb -orient vertical -command {.help.t yview}
button .help.dismiss -text Dismiss -command {destroy .help}
pack .help.dismiss -side bottom -pady 10
pack .help.sb -side right -fill y
pack .help.t -side top -expand 1 -fill both
set bold "[font actual [.help.t cget -font]] -weight bold"
set italic "[font actual [.help.t cget -font]] -slant italic"
.help.t tag config title -justify center -foregr red -font "Times 20 bold"
.help.t tag configure title2 -justify center -font "Times 12 bold"
.help.t tag configure bullet -font $bold
.help.t tag configure n -lmargin1 15 -lmargin2 15
.help.t tag configure ital -font $italic
.help.t insert end "TkOverload\n" title
.help.t insert end "by Keith Vetter\n\n" title2
set m "TkOverload is a logic game for two people based on the game by "
append m "Overload by Astatix (see http://www.astatix.com/overload.php). "
append m "That games is shareware with a really annoying nag screen, so I "
append m "decided to write my own version. "
append m "The object is to capture all your opponents pieces.\n\n"
.help.t insert end "Overview\n" bullet $m n
set m "Each player alternate turns by clicking on either an empty cell or "
append m "a cell already containing his pieces. Each click increases the "
append m "number of pieces in the cell. When the number of pieces "
append m "reaches four, the cell "
.help.t insert end "How to Play\n" bullet $m n explodes.\n\n ital
set m "When the number of pieces in the cell reaches four, it explodes, "
append m "scattering the four pieces north, south, east and west. "
append m "Those four cells immediately change ownership to the current "
append m "player, and, if the addition of the piece causes a "
append m "cell to have four pieces, it too will explode. "
append m "Such chain reactions are a major part of the game, "
append m "and can cause the momentum in the game to change quickly."
.help.t insert end "Exploding Cells\n" bullet $m n
.help.t config -state disabled
}
################################################################
#
# Computer player code
#
# Game-tree min-max search with alpha-beta pruning. See _Fundamentals of
# Data Structures_, Horowitz, page 268.
#
proc veb {who brd lvl d} {
incr ::S(veb)
if {$lvl == 0 || [GETM $brd 1] == 0 || [GETM $brd -1] == 0} { ;# Terminal?
return [e $who $brd] ;# ...just evaluate position
}
set ans -100000 ;# Lower bound on value
set best "" ;# Current best move
set l $lvl
incr lvl -1 ;# Go down a level
set moves [AllMoves $who $brd] ;# Get all legal moves
foreach m $moves { ;# Try each possible move
foreach {row col} $m break
set brd2 [AddOne $brd $who $row $col 0] ;# Do the move
set e [veb [expr {-$who}] $brd2 $lvl [expr {-1 * $ans}]]
foreach {a bm} $e break
set a [expr {-$a}]
if {$a >= $ans} { ;# Is it a better move?
set ans $a ;# Yep, so use it
set best [concat $bm [list $m]]
}
if {$ans >= $d} break ;# BETA rule
}
return [list $ans $best]
}
proc e {who brd} { ;# Evaluate a board
if {[GETM $brd [expr {-$who}]] == 0} { return 10000 }
if {[GETM $brd $who] == 0} { return -10000 }
set me2 [GETM $brd [expr {2*$who}]]
set you2 [GETM $brd [expr {-2*$who}]]
return [expr {$me2 - $you2}]
}
proc Robot {lvl} { ;# Figure out the best move
global S B
if {$S(won)} return ;# Game already over
INFO "thinking (depth $lvl)..." 2
set S(veb) 0 ;# Count number of calls
set t [time {set mv [veb $S(turn) $B $lvl 10000]}]
foreach {val S(best)} $mv break
foreach {row col} [lindex $S(best) end] break
set tt [expr {[lindex $t 0] / 1000000.0}]
if {$tt > .001} {set tt [expr {round($tt * 1000) / 1000.0}]}
set m [expr {1000 * $tt / $S(veb)}]
INFO "Rating: $val ($S(veb) in $tt seconds)" 2
return [list $row $col]
}
proc AllMoves {who brd} { ;# Get all possible moves
global S
set moves {}
for {set row 0} {$row < $S(rows)} {incr row} {
for {set col 0} {$col < $S(cols)} {incr col} {
set v [GET $brd $row $col]
if {$v == 0} {
lappend moves [list $row $col]
} elseif {$v / abs($v) == $who} {
lappend moves [list $row $col]
}
}
}
# Randomly rearrange order of the move list
set n [expr {int(rand() * [llength $moves])}]
set moves [concat [lrange $moves $n end] [lrange $moves 0 [expr {$n - 1}]]]
return $moves
}
proc Hint {{lvl 4}} {
Unhint
if {$::S(won)} return ;# Game already over
foreach {row col} [Robot $lvl] break
set w ".c$row,$col"
set ::S(hint) [list $w [$w cget -bg]]
$w configure -bg green
after 10000 Unhint
return $::S(best)
}
proc Unhint {} { ;# Turn off hint highlighting
global S
foreach a [after info] {after cancel $a}
if {! [info exists S(hint)]} return
foreach {w bg} $S(hint) break
$w configure -bg $bg
}
proc GoRobot {} { ;# Do the robot turn
global S
if {$S(turn) == $S(robot)} {
eval DoMove [Robot $S(level)]
}
}
################################################################
################################################################
################################################################
Init
DoDisplay
NewBoard
