Keith Vetter 2003-02-19 -- this is an implementation of the popular Ataxx arcade game. You can play against the computer, against another player or have the computer play against itself. It uses a multi-level game tree search with alpha-beta pruning. By controlling how deep it searches you can control how smart the computer is. (I grabbed the game engine from here when I wrote TkOverload.)
This is program I've had lying around for a while. I originally wrote this in 1995 and you can still find copies of that version floating around on the web.
There's another program on the web called tkAtaxx [L1 ] that was written later in 1995. That one, however, requires compiling C code and only works on Unix boxes.
Jeff Smith 2019-05-03 : Below is an online demo using CloudTk
################################################################# # # TkAtaxx -- a tcl/tk implementation of the Ataxx arcade game. # The computer uses a multi-level game tree search routine with # alpha-beta pruning. # by Keith P. Vetter # # Revision history: # KPV 1/6/95 - Initial revision # KPV 8/22/95 - Ported to tk 4.0 # KPV Feb 19, 2003 - cleaned up and ported to 8.4 package require Tk ##+########################################################################## # # Init -- sets up some global variables # proc Init {{cs 50}} { global state newb index set state(msg) "Welcome to TkAtaxx" set state(cs) $cs ;# Size of a cell set state(bs) [expr {round($cs * .9)}] ;# Size of a blob set state(brd) -1 ;# Last board used set state(c,1) Red ;# Colors for each player set state(c,2) Green set state(1) 0 ;# Human set state(2) 1 ;# Computer set state(level,max) 4 set state(level,0) Random set state(level,1) Greedy set state(level,2) Brainy set state(level,3) Genius set state(level,4) Einstein set state(level,5) Einstein5 ;# Just be safe set state(level,6) Einstein6 set state(level,7) Einstein7 set state(level) 1 ;# Current search level # Various boards to play on set newb(0) {{2,0} {4,0} {2,1} {4,1} {0,2} {6,2} {0,3} {3,3} {6,3} {0,4} {6,4} {2,5} {4,5} {2,6} {4,6}} set newb(1) {{3,0} {3,1} {3,2} {0,3} {1,3} {2,3} {4,3} {5,3} {6,3} {3,4} {3,5} {3,6}} set newb(2) {{3,0} {1,2} {2,2} {4,2} {5,2} {0,3} {1,3} {5,3} {6,3} {1,4} {2,4} {4,4} {5,4} {3,6}} set newb(3) {{1,0} {5,0} {0,1} {6,1} {3,3} {0,5} {6,5} {1,6} {5,6}} set newb(4) {{2,0} {4,0} {2,1} {4,1} {0,3} {6,3} {2,5} {4,5} {2,6} {4,6}} set newb(5) {{3,0} {3,1} {0,3} {1,3} {5,3} {6,3} {3,5} {3,6}} set newb(6) {{3,1} {2,2} {4,2} {1,3} {5,3} {2,4} {4,4} {3,5}} set newb(7) {{2,0} {4,0} {0,2} {6,2} {0,4} {6,4} {2,6} {4,6}} set newb(8) {{3,0} {2,1} {4,1} {1,2} {5,2} {0,3} {6,3} {1,4} {5,4} {2,5} {4,5} {3,6}} set newb(9) {{2,1} {4,1} {1,2} {5,2} {1,4} {5,4} {2,5} {4,5}} set newb(10) {{2,0} {4,0} {2,2} {4,2} {1,3} {5,3} {2,4} {4,4} {2,6} {4,6}} set newb(11) {{3,1} {3,2} {1,3} {2,3} {3,3} {4,3} {5,3} {3,4} {3,5}} set newb(12) {{1,1} {2,1} {3,1} {4,1} {5,1} {1,2} {5,2} {1,3} {5,3} {1,4} {5,4} {1,5} {2,5} {3,5} {4,5} {5,5}} set newb(13) {{2,1} {3,1} {4,1} {1,2} {5,2} {1,3} {5,3} {1,4} {5,4} {2,5} {3,5} {4,5}} set newb(14) {{2,1} {4,1} {1,2} {2,2} {4,2} {5,2} {1,4} {2,4} {4,4} {5,4} {2,5} {4,5}} set newb(15) {{1,1} {5,5} {1,5} {5,1}} set newb(16) {{1,1} {2,1} {4,1} {5,1} {1,2} {5,2} {1,4} {5,4} {1,5} {2,5} {4,5} {5,5}} set newb(17) {{3,2} {2,3} {3,3} {4,3} {3,4}} set newb(18) {{3,2} {2,3} {4,3} {3,4}} set newb(19) {{3,1} {3,2} {1,3} {2,3} {4,3} {5,3} {3,4} {3,5}} set newb(20) {{2,0} {3,0} {4,0} {3,1} {0,2} {6,2} {0,3} {1,3} {5,3} {6,3} {0,4} {6,4} {3,5} {2,6} {3,6} {4,6}} set newb(21) {{2,0} {4,0} {3,1} {0,2} {6,2} {1,3} {5,3} {0,4} {6,4} {3,5} {2,6} {4,6}} set newb(22) {{3,1} {1,3} {3,3} {5,3} {3,5}} set newb(23) {{1,1} {3,1} {5,1} {1,3} {3,3} {5,3} {1,5} {3,5} {5,5}} set newb(24) {} set newb(25) {{2,2} {4,2} {3,3} {2,4} {4,4}} set newb(26) {{1,1} {5,1} {2,2} {4,2} {3,3} {2,4} {4,4} {1,5} {5,5}} set newb(27) {{2,0} {3,0} {4,0} {2,1} {4,1} {0,3} {6,3} {2,5} {4,5} {2,6} {3,6} {4,6}} set newb(28) {{1,0} {3,0} {5,0} {0,1} {2,1} {4,1} {6,1} {1,2} {3,2} {5,2} {0,3} {2,3} {4,3} {6,3} {1,4} {3,4} {5,4} {0,5} {2,5} {4,5} {6,5} {1,6} {3,6} {5,6}} set newb(29) {{1,1} {5,1} {2,2} {4,2} {2,4} {4,4} {1,5} {5,5}} set newb(30) {{3,0} {2,1} {4,1} {0,3} {6,3} {2,5} {4,5} {3,6}} set newb(31) {{3,0} {0,3} {6,3} {3,6}} set newb(32) {{3,1} {1,3} {5,3} {3,5}} set newb(33) {{2,0} {3,0} {4,0} {0,2} {1,2} {3,2} {5,2} {6,2} {0,3} {6,3} {0,4} {1,4} {3,4} {5,4} {6,4} {2,6} {3,6} {4,6}} set newb(34) {{2,1} {4,1} {0,2} {1,2} {3,2} {5,2} {6,2} {3,3} {0,4} {1,4} {3,4} {5,4} {6,4} {2,5} {4,5}} set state(b) 35 ;# Number of boards for {set r 0} {$r < 7} {incr r} { ;# Precompute index values for {set c 0} {$c < 7} {incr c} { set index($r,$c) [expr {24 + 11*$r + $c}] } } } ##+################################################## # # Display -- Sets up the display # proc Display {} { global state wm title . "TkAtaxx" wm minsize . 250 250 pack [frame .fbot] -side bottom -fill both DrawMenus set wi [expr {$state(cs) * 7}] ;# Total width canvas .c -width $wi -height $wi -bd 2 -relief raised .c xview moveto 0; .c yview moveto 0 bind .c <1> {MouseDown %x %y} bind .c <Configure> Resize pack .c -side top -fill both -expand 1 ShowGrid label .msg -relief ridge -textvariable state(msg) -anchor w frame .fsc -bd 2 -relief ridge foreach n {1 2} { canvas .c_p$n -width 16 -height 16 .c_p$n create oval 2 2 15 15 -fill $state(c,$n) label .p$n -text "Score: " label .psc_$n -textvariable state(sc,$n) -width 2 grid .c_p$n .p$n .psc_$n -in .fsc -row [expr {$n - 1}] } scale .level -orient h -from 0 -to $state(level,max) -relief ridge \ -showvalue 0 -variable state(level) trace variable state(level) w TraceLevel set state(level) $state(level) pack .msg -side top -fill x -in .fbot pack .fsc -side left -ipadx 5 -expand yes -fill y -in .fbot pack .level -side right -expand yes -in .fbot -fill y bind .level <2> {after 1 {hint -1} ; break} bind .level <3> {after 1 {hint -2} ; break} } ####################################################################### # # DrawMenus -- Displays the menus on the screen # proc DrawMenus {} { global state menu .m -tearoff 0 . configure -menu .m .m add cascade -menu .m.game -label "Game" -underline 0 .m add cascade -menu .m.opp -label "Opponent" -underline 0 .m add cascade -menu .m.help -label "Help" -underline 0 menu .m.game -tearoff 0 .m.game add command -label "New Board" -under 0 -command Go .m.game add command -label "Restart" -under 0 -command [list Go -1] .m.game add separator .m.game add command -label "Hint" -under 0 -command hint .m.game add command -label "Undo" -under 0 -command undo .m.game add separator .m.game add command -label "Exit" -under 0 -command exit menu .m.opp -tearoff 0 .m.opp add check -label "Red - Computer" -under 0 -variable state(1) \ -command Start .m.opp add check -label "Green - Computer" -under 0 -variable state(2) \ -command Start .m.opp add separator for {set lvl 0} {$lvl <= $state(level,max)} {incr lvl} { .m.opp add radio -label $state(level,$lvl) -variable state(level) \ -value $lvl \ -under [expr {$lvl == 3 ? 2 : 0}] \ } menu .m.help -tearoff 0 .m.help add command -label Help -under 0 -command Help .m.help add command -label About -under 0 -command About } ##+################################################## # # TraceLevel -- Handles changes in the scale for the depth of search # proc TraceLevel {var1 var2 op} { .level config -label "Skill: $::state(level,$::state(level))" } ##+################################################## # # RedrawBoard -- redraws all the pips and obstacles on the board # proc RedrawBoard {{brd ""}} { global state bb index if {$brd != ""} {set bb $brd} ShowGrid .c delete blob set state(sc,0) 0 ;# Reset the scores set state(sc,1) 0 ;# 0 is blanks, 1 is player 1 set state(sc,2) 0 ;# 2 is player 2 set state(sc,3) 0 ;# 3 is barriers for {set r 0} {$r < 7} {incr r} { for {set c 0} {$c < 7} {incr c} { set cell [lindex $bb $index($r,$c)] ;# What's in the cell incr state(sc,$cell) ;# Update score info if {$cell == 3} { MakeObstacle $r $c } elseif {$cell > 0} { MakeBlob $cell $r $c } } } set bb [lreplace $bb 121 end $state(sc,0) $state(sc,1) $state(sc,2) \ $state(sc,3)] } ##+################################################## # # ShowGrid -- toggles the display of a grid on the board # proc ShowGrid {} { global state .c delete grid set wi [expr {$state(cs) * 7}] .c create rect 0 0 $wi $wi -width 5 -fill {} -tag grid for {set i 1} {$i < 7} {incr i} { set xy [expr {$i * $state(cs)}] .c create line 0 $xy $wi $xy -tag grid .c create line $xy $wi $xy 0 -tag grid } } proc Resize {} { set w [winfo width .c] set h [winfo height .c] set ::state(cs) [expr {(($w <= $h ? $w : $h) -10) / 7.0}] set ::state(bs) [expr {round($::state(cs) * .9)}] RedrawBoard } ##+################################################## # # CellBBox -- returns the bounding box for a given row, col cell # proc CellBBox {r c} { global state set bs2 [expr {$state(bs) / 2.0}] set x [expr {round(($c+.5) * $state(cs) - $bs2)}] set y [expr {round(($r+.5) * $state(cs) - $bs2)}] set x2 [expr {$x + $state(bs)}] set y2 [expr {$y + $state(bs)}] return [list $x $y $x2 $y2] } ##+################################################## # # MakeBlob -- creates a new blob at location Row Col for WHO # proc MakeBlob {who r {c -1}} { global state bb index if {$c == -1} { set c [expr {($r % 11) - 2}] set r [expr {($r / 11) - 2}] } set col $state(c,$who) set xy [CellBBox $r $c] .c create oval $xy -fill ${col}3 -tag "blob blob${r}${c}" eval .c create arc $xy -start 45 -extent 180 -fill ${col}1 -outline {{}} \ -tag \"blob blob${r}${c}\" .c create oval [Shrink $xy 5] -fill ${col}2 -outline {} \ -tag "blob blob${r}${c}" set p $index($r,$c) ;# Update board info set bb [lreplace $bb $p $p $who] ;# Put new piece there } ##+################################################## # # Shrink -- shrinks rectangle specified by x,y x2,y2 # proc Shrink {xy n} { foreach {x y x2 y2} $xy break set x [expr {$x + $n}] set y [expr {$y + $n}] set x2 [expr {$x2 - $n}] set y2 [expr {$y2 - $n}] return [list $x $y $x2 $y2] } ##+################################################## # # GrowBlob -- grows a blob at R,C # proc GrowBlob {who r c} { global state set xy [CellBBox $r $c] for {set i [expr {$state(bs) / 2}]} {$i >= 0} {incr i -1} { set now [clock clicks -milliseconds] set bbox [Shrink $xy $i] .c create oval $bbox -tag grow -fill $state(c,$who) update idletasks set now [expr {[clock clicks -milliseconds] - $now}] set delay [expr {20 - $now}] if {$delay > 0} { after $delay } } MakeBlob $who $r $c .c delete grow } ##+################################################## # # Highlight -- highlights cell R, C # proc highlight {r c} { if {$r == -1} { .c delete high return } .c create rect [CellBBox $r $c] -fill {} -tag "blob high" -width 5 .c lower high } ##+################################################## # # DeleteBlob -- deletes the blob from cell Row Col # proc DeleteBlob {r {c -1}} { global bb index if {$c == -1} { set c [expr {($r % 11) - 2}] set r [expr {($r / 11) - 2}] } .c delete blob${r}${c} set p $index($r,$c) ;# Update board info set bb [lreplace $bb $p $p 0] ;# Cell now empty } ##+################################################## # # MakeObstacle -- creates an obstacle in cell Row Col # proc MakeObstacle {r c} { global bb state set xy [CellBBox $r $c] foreach {x y x2 y2} $xy break .c create poly $x $y $x $y2 $x2 $y -fill white -tag blob .c create poly $x2 $y2 $x $y2 $x2 $y -fill gray45 -tag blob .c create rect [Shrink $xy 2] -fill gray -outline "" -tag blob set xy [Shrink $xy [expr {$state(cs) / 5}]] .c create rect $xy -fill $state(c,1) -outline "" -tag "blob center" } ##+################################################## # # CleanBoard -- deletes everything off the board # proc CleanBoard {} { global bb .c delete blob set bb "4 4 4 4 4 4 4 4 4 4 4" ;# BB is the board info append bb " 4 4 4 4 4 4 4 4 4 4 4" ;# ...w/ 2 row/col of sentinels append bb " 4 4 0 0 0 0 0 0 0 4 4" ;# Actual board part append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 0 0 0 0 0 0 0 4 4" append bb " 4 4 4 4 4 4 4 4 4 4 4" ;# Bottom row sentinels append bb " 4 4 4 4 4 4 4 4 4 4 4" append bb " 45 2 2 0" ;# Cnt: empty, p1, p2, barriers } ##+################################################## # # FillBoard -- fills all blanks board positions with a blob. Called # when the game is over. # proc FillBoard {who} { global state bb index for {set r 0} {$r < 7} {incr r} { for {set c 0} {$c < 7} {incr c} { set p $index($r,$c) if {[lindex $bb $p] == 0} { MakeBlob $who $r $c incr state(sc,$who) update idletasks } } } } proc Go {{restart 0}} { set who -1 if {$restart} { set who $::state(brd)} NewBoard $who Start } ##+################################################## # # NewBoard -- creates a new board with obstacles of type N # proc NewBoard {{who -1}} { global newb state bb mm index if {$who == -1} { set who [expr {int(rand() * $state(b))}] if {$who == $state(brd)} { set who [expr {int(rand() * $state(b))}] } } set state(brd) $who CleanBoard catch {unset mm} set xy $index(0,0) ; set bb [lreplace $bb $xy $xy 1] set xy $index(6,6) ; set bb [lreplace $bb $xy $xy 1] set xy $index(6,0) ; set bb [lreplace $bb $xy $xy 2] set xy $index(0,6) ; set bb [lreplace $bb $xy $xy 2] foreach p $newb($who) { ;# Add the obstacles set xy $index($p) set bb [lreplace $bb $xy $xy 3] } RedrawBoard set state(init) $bb set state(turn) 1 set state(state) 0 set state(n) 0 set state(msg) "" set state(tc) 0 set state(c) 0 } ##+################################################## # # Legal1 -- tests whether cell R,C is legal as a first move for # player WHO. The cell must be in range, contain a WHO blob and can # has a place to move. # proc Legal1 {r c who} { global bb index set xy $index($r,$c) if {[lindex $bb $xy] != $who} { return 0 } foreach i {1 2 9 10 11 12 13 20 21 22 23 24} {;# Neighbors 1 & 2 cells away if {[lindex $bb [expr {$xy + $i}]] == 0} { return 1} if {[lindex $bb [expr {$xy - $i}]] == 0} { return 1} } return 0 } ##+################################################## # # Legal2 -- Tests whether cell R,C is legal as a second move. # Already we know the cell is empty, so we must check that # its within 2 of the from cell. # proc Legal2 {to from} { foreach {r c} $to break foreach {fr fc} $from break set dr [expr {abs($r - $fr)}] if {$dr > 2} { return 0 } set dc [expr {abs($c - $fc)}] if {$dc > 2} { return 0 } if {$dr == 2 || $dc == 2} { return 2} return 1 } ##+################################################## # # MouseDown -- Called on a mouse down event. Handles moving pieces # and checking legality. # proc MouseDown {x y} { global state bb index set r [expr {int($y / $state(cs))}] set c [expr {int($x / $state(cs))}] if {$r < 0 || $r > 6 || $c < 0 || $c > 6} return set where [list $r $c] set xy $index($r,$c) set cell [lindex $bb $xy] if {$cell == $state(turn)} { highlight -1 -1 if {$state(state) == 1 && $state(from) == $where} { set state(state) 0 return } if [Legal1 $r $c $state(turn)] { highlight $r $c set state(state) 1 set state(from) $where return } } if {$state(state) != 1} return if {$cell != 0} return set n [Legal2 $where $state(from)] if $n { DoMove $where $state(from) $n } else { highlight -1 -1 } set state(state) 0 } ##+################################################## # # DoMove -- does the move from FR,FC to R,C. Updates the blobs, toggles any # neighbors of the new cell and checks for end-of-game, and can move? # proc DoMove {to from type} { global state bb mm foreach {r c} $to break foreach {fr fc} $from break set mm($state(n)) [list $state(turn) $r $c $fr $fc $type];# Undo info incr state(n) set who $state(turn) set opp [expr {3 - $who}] highlight -1 -1 if {$type != -1} { GrowBlob $state(turn) $r $c set cnt [ToggleCells $r $c $state(turn)] incr state(sc,$who) $cnt incr state(sc,$opp) [expr {-1 * $cnt}] if {$type > 1} { ;# Long jump??? DeleteBlob $fr $fc ;# ...then delete old blob } else { incr state(sc,$who) incr state(sc,0) -1 } set bb [lreplace $bb 121 123 $state(sc,0) $state(sc,1) $state(sc,2)] update } if {$state(sc,0) == 0 || $state(sc,1) == 0 || $state(sc,2) == 0} { EndGame return } set mv [CanMove $opp] ;# Can opponent move? if {$mv == 0} { set state(msg) "$state(c,$opp) can't move. " set state(msg) "$state(msg) $state(c,$who)'s turn" } else { set state(turn) $opp .c itemconfig center -fill $state(c,$state(turn)) } update if {$state($state(turn))} robot ;# Do the computer move } ##+################################################## # # ToggleCells -- turns all neighbors of R,C of into WHO blobs # proc ToggleCells {r c who} { global bb index set opp [expr {3 - $who}] set cnt 0 set xy $index($r,$c) foreach i {1 -1 10 -10 11 -11 12 -12} { ;# Immediate neighbors set p [expr {$xy + $i}] if {[lindex $bb $p] == $opp} { DeleteBlob $p MakeBlob $who $p incr cnt } } return $cnt } ##+################################################## # # CanMove -- determines if WHO has a legal move # proc CanMove {who} { global state bb index for {set r 0} {$r < 7} {incr r} { for {set c 0} {$c < 7} {incr c} { set xy $index($r,$c) if {[lindex $bb $xy] != $who} continue if [Legal1 $r $c $who] { return 1 } } } return 0 } ##+################################################## # # EndGame -- handles end-of-game stuff # proc EndGame {} { global state if {$state(sc,0) != 0} { FillBoard [expr {($state(sc,1) > $state(sc,2)) ? 1 : 2}] } if {$state(sc,1) > $state(sc,2)} { ;# Player 1 won set state(msg) "Game over: $state(c,1) won" } elseif {$state(sc,2) > $state(sc,1)} { ;# Player 2 own set state(msg) "Game over: $state(c,2) won" } else { set state(msg) "Game over: it's a tie" } } ##+################################################## # # Index -- given row, col returns the corresponding index into the board # proc rindex {i} { return [list [expr {($i / 11) - 2}] [expr {($i % 11) - 2}]] } ##+################################################## # # Undo -- undo last move. Works by replaying all but the last moves. # proc undo {} { global state mm bb if {$state(n) == 0} { set state(msg) "Nothing to undo" return } set state(msg) "Undoing last move" set brd $state(init) ;# Starting position set n [expr {$state(n) - 1}] ;# Number of moves to undo set w [lindex $mm($n) 0] ;# Who made last turn if {$state($w)} { ;# Last move by computer incr n -1 ;# So undo both moves set w [expr {3 - $w}] ;# Whose turn it is } for {set i 0} {$i < $n} {incr i} { ;# Re-do each move set brd [move2 $brd $mm($i)] } set state(n) $n set bb $brd RedrawBoard set state(state) 0 highlight -1 -1 set state(turn) $w .c itemconfig center -fill $state(c,$state(turn)) } ##+################################################## # # Robot -- moves the pieces for the robot player. # Does a game-tree search for the best move. # proc robot {{level -1}} { global state bb set who $state(turn) if {$level == -1} { set level $state(level) } if {$level == 0} { ;# Random skill level set m [lindex [AllMoves $who $bb] 0] } else { set state(c) 0 set state(msg) "Thinking ($state(level,$level))" busy 1 set t [time {set mv [veb $who $bb $level 10000]}];# Get best move set state(msg) "" set tt [expr {[lindex $t 0] / 1000000.0}] set state(msg) "Rating: [lindex $mv 0] ($state(c) calls in $tt seconds)" incr state(tc) $state(c) busy 0 set m [lindex $mv 1] } foreach {from to type} $m break DoMove [rindex $to] [rindex $from] $type } proc busy {onoff} { if {$onoff} {set how watch} {set how {}} foreach w [winfo children .] { $w config -cursor $how } update idletasks } ##+################################################## # # Hint -- suggest a move # proc hint {{level -1}} { global state bb if {$level == -1} { ;# Was level specified? set level $state(level) if {$level == 0} { ;# Level 0 is not a hint set level 1 } } if {$level == -2} { ;# -2 is smart as possible set level $state(level,max) } if {$level < 0} { set level [expr {abs($level)}] } highlight -1 -1 set state(c) 0 set state(msg) "Thinking ($state(level,$level))" busy 1 set t [time {set mv [veb $state(turn) $bb $level 10000]}];# Find best move set state(msg) "" set tt [expr {[lindex $t 0] / 1000000.0}] set state(msg) "Rating: [lindex $mv 0] ($state(c) calls in $tt seconds)" busy 0 set m [lindex $mv 1] set from [lindex $m 0] set to [lindex $m 1] foreach {from to} [lindex $mv 1] break eval highlight [rindex $from] eval highlight [rindex $to] } ##+################################################## # # AllMoves -- returns a list of all legal moves for WHO on board BRD. # Format is (from to type). # proc AllMoves {who brd} { set m "" for {set i 24} {$i < 97} {incr i} { set c [lindex $brd $i] if {$c == 4} { ;# Is it a border cell? incr i 3 continue } if {$c != $who} continue foreach j {1 10 11 12 -1 -10 -11 -12} { ;# Immediate neighbors set xy [expr {$i + $j}] if {[lindex $brd $xy] == 0} { lappend m [list $i $xy 1] set brd [lreplace $brd $xy $xy -1];# So we don't go here twice } } foreach j {2 9 13 20 21 22 23 24} { ;# Neighbors 2 away if {[lindex $brd [expr {$i + $j}]] <= 0} { lappend m [list $i [expr {$i + $j}] 2] } if {[lindex $brd [expr {$i - $j}]] <= 0} { lappend m [list $i [expr {$i - $j}] 2] } } } set n [llength $m] if {$n == 0} { return {{0 0 -1}} } set n [expr {int(rand() * $n)}] ;# Randomize the order set m [concat [lrange $m $n end] [lrange $m 0 [expr {$n - 1}]]] return $m } ##+################################################## # # Move -- returns new board with WHO moving FROM to TO on board BRD. # Does no screen updates. # proc move {who brd M} { foreach {frm to type} $M break if {$type == -1} { return $brd } set opp [expr {3 - $who}] set sw [lindex $brd [expr {121 + $who}]] set so [lindex $brd [expr {121 + $opp}]] set brd [lreplace $brd $to $to $who] if {$type == 2} { set brd [lreplace $brd $frm $frm 0] } else { incr sw set e [lindex $brd 121] set brd [lreplace $brd 121 121 [expr {$e - 1}]] } foreach i {1 10 11 12 -1 -10 -11 -12} { ;# Immediate neighbors set xy [expr {$to + $i}] if {[lindex $brd $xy] == $opp} { set brd [lreplace $brd $xy $xy $who] incr sw incr so -1 } } if {$who == 1} { set brd [lreplace $brd 122 123 $sw $so] } else { set brd [lreplace $brd 122 123 $so $sw] } return $brd } proc move2 {brd MM} { foreach {who r c fr fc type} $MM break global index set b [move $who $brd [list $index($fr,$fc) $index($r,$c) $type]] return $b } ##+################################################## # # E -- evaluates a position for WHO. Simply the difference in number of men. # proc e {who brd} { set me [lindex $brd [expr {121 + $who}]] set you [lindex $brd [expr {124 - $who}]] if {$you == 0} { return 10000 } if {$me == 0} { return -10000 } return [expr {$me - $you}] } ##+################################################## # # Veb -- game-tree search with alpha-beta pruning. See _Fundamentals of Data # Structures_, Horowitz, page 268. # # Initial call: veb (who board level infinity) # proc veb {who brd l d} { global state incr state(c) ;# Stats if {$l == 0 || [lindex $brd 121] == 0} { ;# Terminal position? return [e $who $brd] ;# ...just evaluate position } set ans -10000 ;# Lower bound on value set best "" ;# Current best move incr l -1 set moves [AllMoves $who $brd] foreach m $moves { set b [move $who $brd $m] set e [veb [expr {3 - $who}] $b $l [expr {-1 * $ans}]] set a [expr {-1 * [lindex $e 0]}] if {$a > $ans} { ;# Is it a better move? set ans $a ;# Yep, so use it set best [list $m] } if {$ans >= $d} break ;# BETA rule } return [concat $ans $best] } ##+################################################## # # Start -- starts/continues the game if it's the computer's turn # proc Start {} { if {$::state(sc,0) == 0 || $::state(sc,1) == 0} return if {$::state($::state(turn)) == 1} robot } proc About {} { set msg "TkAtaxx\n\nby Keith Vetter\nFebruary, 2003" tk_messageBox -title About -message $msg } ##+################################################## # # Help -- displays a help screen # proc Help {} { destroy .help toplevel .help wm title .help "TkAtaxx Help" wm geom .help "+[expr {[winfo x .] + [winfo width .] + 10}]+[winfo y .]" text .help.t -relief raised -wrap word -width 70 -height 32 .help.t config -padx 10 -pady 10 button .help.dismiss -text Dismiss -command {destroy .help} pack .help.dismiss -side bottom -pady 10 pack .help.t -side top -expand 1 -fill both set bold "[font actual [.help.t cget -font]] -weight bold" .help.t tag configure title -justify center -foreground 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 insert end "TkAtaxx\n" title "by Keith Vetter\n\n" title2 set msg "TkAtaxx is a tcl/tk implementation of the popular " append msg "arcade video Ataxx. The goal of the game is end up " append msg "with more pieces of your color than your " append msg "opponent. The game ends when there are no more " append msg "places to move. " .help.t insert end "DESCRIPTION\n" bullet $msg n \n\n set msg "You can move a piece in two different ways, either " append msg "sliding or jumping. To slide a piece, click on it " append msg "with the mouse, then click on an immediately " append msg "adjacent empty cell. The piece will split and " append msg "occupy both cells. To jump a piece, click on it " append msg "with the mouse, then click on an empty cell which " append msg "is exactly two positions away from the starting piece. The " append msg "piece will jump to the new position over any " append msg "intervening obstacles vacating the original " append msg "position. If there are no possible moves for a " append msg "player then the move if forfeited. " append msg "\n\nWhen a piece moves to a new cells, all surrounding " append msg "cells of the opponent's color will be captured and " append msg "turn into your color." .help.t insert end "MOVING\n" bullet $msg n \n\n set msg "You can adjust how smart the computer opponent " append msg "is. Random skill picks any move at " append msg "random. Greedy picks the move which maximizes how " append msg "many pieces he has at the end of the turn. Brainy " append msg "searches two moves ahead of the best move. Genius " append msg "searches three moves ahead for the best move.\n\n" append msg "More technically, TkAtaxx uses a Min-Max search " append msg "algorithm with alpha-beta pruning to find the best move. " append msg "The skill level corresponds to the depth of the search." .help.t insert end "SKILL LEVEL\n" bullet $msg n .help.t config -state disabled } ##+################################################## Init Display NewBoard