[Keith Vetter] 2004-09-10 : Here's another version of the children's game Tic-Tac-Toe. My 5-year old daughter has just discovered this game and so I threw this together this morning. There's another tic-tac-toe page--[A little Tic Tac Toe game]--but I wanted a sexier interface. [MG] - A nice little game :) [KPV] - Added two levels of computer opponent: random and smart--which will win if possible, block if necessary or else do a random move. Playing perfect tic-tac-toe is non-trivial problem, and so is left as an exersize for the reader (plus my 5 year old likes to win when playing). ---- ##+########################################################################## # # tictactoe.tcl - plays tic tac toe # by Keith Vetter Sept 10, 2004 # # Sept 11 : added 2 levels of computer opponent: random and smart--which # will win if possible, block if necessary or do a random move package require Tk array set S {title "Tic Tac Toe" who,1 "X" who,0 "" who,-1 "O" robot "0"} array set C {bars red X blue O green win yellow} namespace eval ::Robot { variable skill Smart } proc DoDisplay {} { wm title . $::S(title) frame .ctrl -relief ridge -bd 2 -padx 5 -pady 5 canvas .c -relief raised -bd 2 -height 500 -width 500 -highlightthickness 0 pack .c -side top -fill both -expand 1 pack .ctrl -side top -fill both bind all {console show} bind .c {ReCenter %W %h %w} DoCtrlFrame } proc DrawBoard {{redraw 0}} { global S B GAME C if {$redraw} { ;# Must redraw everything .c delete all set w2 [expr {$B(w2) - 15}] ;# Make a little margins set h2 [expr {$B(h2) - 15}] set hbar [expr {$h2 / 3.0}] set vbar [expr {$w2 / 3.0}] set B(0) [list -$w2 -$h2 -$vbar -$hbar] ;# All 9 cells set B(1) [list -$vbar -$h2 $vbar -$hbar] set B(2) [list $vbar -$h2 $w2 -$hbar] set B(3) [list -$w2 -$hbar -$vbar $hbar] set B(4) [list -$vbar -$hbar $vbar $hbar] set B(5) [list $vbar -$hbar $w2 $hbar] set B(6) [list -$w2 $hbar -$vbar $h2] set B(7) [list -$vbar $hbar $vbar $h2] set B(8) [list $vbar $hbar $w2 $h2] for {set i 0} {$i < 9} {incr i} { ;# Rectangle for each cell .c create rect $B($i) -tag b$i -fill {} -outline {} .c bind b$i [list DoClick $i] set B($i) [ShrinkBox $B($i) 25] } .c create line -$w2 $hbar $w2 $hbar -tag bar ;# Draw the cross bars .c create line -$w2 -$hbar $w2 -$hbar -tag bar .c create line $vbar -$h2 $vbar $h2 -tag bar .c create line -$vbar -$h2 -$vbar $h2 -tag bar .c itemconfig bar -width 20 -fill $::C(bars) -capstyle round } .new config -state [expr {$GAME(tcnt) == 0 ? "disabled" : "normal"}] for {set i 0} {$i < 9} {incr i} { .c itemconfig b$i -fill {} ;# Erase any win lines DrawXO $GAME(board,$i) $i } foreach i $GAME(win) { ;# Do we have a winner??? .c itemconfig b$i -fill $C(win) } } proc DoCtrlFrame {} { button .new -text "New Game" -command NewGame -bd 4 .new configure -font "[font actual [.new cget -font]] -weight bold" option add *Button.font [.new cget -font] label .status -textvariable S(msg) -font {Times 36 bold} -bg white \ -bd 5 -relief ridge button .about -text About -command \ [list tk_messageBox -message "$::S(title)\nby Keith Vetter, Sept 2004"] frame .r -bd 2 -relief ridge pack .r -side bottom label .r.lc -text "Computer" -font [.new cget -font] label .r.lrobot -text "Plays: " spinbox .r.robot -values {O None X} -textvariable S(robot) -wrap 1 \ -width 6 -justify center -command ::Robot::IsTurn label .r.llevel -text "Level: " spinbox .r.level -values {Smart Random} -textvariable ::Robot::skill \ -wrap 1 -width 8 -justify center grid .r.lc - -row 0 grid .r.lrobot .r.robot -sticky we grid .r.llevel .r.level -sticky we pack .status -in .ctrl -side right -fill both -expand 1 pack .r -in .ctrl -side right -fill both -padx 5 pack .new .about -in .ctrl -side top -fill x -pady 2 } proc ShrinkBox {xy d} { foreach {x y x1 y1} $xy break return [list [expr {$x+$d}] [expr {$y+$d}] [expr {$x1-$d}] [expr {$y1-$d}]] } ##+########################################################################## # # Recenter -- keeps 0,0 at the center of the canvas during resizing # proc ReCenter {W h w} { ;# Called by configure event set ::B(h2) [expr {$h / 2}] set ::B(w2) [expr {$w / 2}] $W config -scrollregion [list -$::B(w2) -$::B(h2) $::B(w2) $::B(h2)] DrawBoard 1 } ##+########################################################################## # # DrawXO -- draws appropriate mark in a given cell # proc DrawXO {who where} { global S B C if {$S(who,$who) eq "X"} { foreach {x0 y0 x1 y1} $B($where) break .c create line $x0 $y0 $x1 $y1 -width 20 -fill $C(X) -capstyle round \ -tag xo$where .c create line $x0 $y1 $x1 $y0 -width 20 -fill $C(X) -capstyle round \ -tag xo$where } elseif {$S(who,$who) eq "O"} { .c create oval $B($where) -width 20 -outline $C(O) -tag xo$where } else { .c delete xo$where } } ##+########################################################################## # # InitGame -- resets all variables to start a new game # proc InitGame {} { global GAME S set GAME(state) play set GAME(turn) 1 set GAME(tcnt) 0 set GAME(win) {} for {set i 0} {$i < 9} {incr i} { set GAME(board,$i) 0 } set S(msg) "X starts" } ##+########################################################################## # # NewGame -- starts a new game # proc NewGame {} { InitGame DrawBoard if {$::S(who,$::GAME(turn)) == $::S(robot)} { after idle ::Robot::Go } } ##+########################################################################## # # DoClick -- handles button click in a cell # proc DoClick {where} { global GAME S if {$GAME(state) ne "play"} return ;# Game over if {$GAME(board,$where) != 0} return ;# Not empty set GAME(board,$where) $GAME(turn) set GAME(turn) [expr {- $GAME(turn)}] incr GAME(tcnt) set S(msg) "$S(who,$GAME(turn))'s turn" set n [WhoWon] ;# Do we have a winner??? if {$n != 0} { set GAME(state) finished set GAME(win) [lrange $n 1 end] set S(msg) "$S(who,[lindex $n 0]) Wins!" } elseif {$GAME(tcnt) == 9} { ;# Is the game a draw??? set GAME(state) finished set S(msg) "Draw" } DrawBoard if {$S(who,$GAME(turn)) == $S(robot)} { after idle ::Robot::Go } } ##+########################################################################## # # WhoWon -- determines if anyone has won the game # proc WhoWon {} { foreach {a b c} {0 1 2 3 4 5 6 7 8 0 3 6 1 4 7 2 5 8 0 4 8 2 4 6} { set who $::GAME(board,$a) if {$who == 0} continue if {$who != $::GAME(board,$b) || $who != $::GAME(board,$c)} continue return [list $who $a $b $c] } return 0 } ##+########################################################################## # # ::Robot::Go -- gets and does robot's move # proc ::Robot::Go {} { variable skill if {$::GAME(state) ne "play"} return ;# Game over set move [::Robot::$skill] if {$move == {}} return ::DoClick $move } proc ::Robot::Random {} { ;# Picks a random move set empty {} for {set i 0} {$i < 9} {incr i} { if {$::GAME(board,$i) == 0} { lappend empty $i } } return [lindex $empty [expr {int(rand() * [llength $empty])}]] } ##+########################################################################## # # ::Robot::Smart -- does winning move if possible, blocks if necessary # or does a random move # proc ::Robot::Smart {} { global GAME set blockers {} foreach {aa bb cc} {0 1 2 3 4 5 6 7 8 0 3 6 1 4 7 2 5 8 0 4 8 2 4 6} { set a $GAME(board,$aa) set b $GAME(board,$bb) set c $GAME(board,$cc) if {$a * $b * $c != 0} continue ;# No empty slots if {$a + $b + $c == 2*$GAME(turn)} { ;# Winning move if {$a == 0} { return $aa} if {$b == 0} { return $bb} if {$c == 0} { return $cc} error "no empty spot" ;# Can't happen } if {$a + $b + $c == -2*$GAME(turn)} { ;# Blocking move if {$a == 0} { lappend blockers $aa} if {$b == 0} { lappend blockers $bb} if {$c == 0} { lappend blockers $cc} } } if {$blockers != {}} { return [lindex $blockers [expr {int(rand() * [llength $blockers])}]] } return [::Robot::Random] } ##+########################################################################## # # ::Robot::IsTurn -- called when who robot is changes and we may need to move # proc ::Robot::IsTurn {} { if {$::S(who,$::GAME(turn)) == $::S(robot)} { after idle ::Robot::Go } } InitGame DoDisplay NewGame ---- ---- ***Screenshots*** [http://farm5.static.flickr.com/4032/4685168612_fd9b15c527_m.jpg] [gold] added pix ---- [uniquename] 2013jul29 In case the image above (stored at 'external site' flickr.com) disappears, here is a 'locally stored' image. [vetter_TicTacToe_wiki12374_screenshot_535x629.jpg] (Thanks to 'gnome-screenshot', 'mtpaint', and ImageMagick 'convert' on Linux for, respectively, capturing the screen in a PNG file, cropping the image, and converting the resulting PNG file to a JPEG file less than 10% the size of the PNG file. Thanks to FOSS developers everywhere --- including Linux kernel and Gnu developers. The 'mv' command and the ImageMagick 'identify' command were used in a shell script to easily rename the cropped image file to contain the image dimensions in pixels. And the 'mtpaint', convert-to-JPEG, and rename programs/scripts were triggered by use of the 'Open with' and 'Nautilus scripts' capabilities of the Nautilus 2.28.1 file manager --- thanks to the old Gnome 2.x developers, who have been unfortunately replaced by Gnome 3.x developers, who have no idea what the Gnome 2.x developers wrought and are decimating a great desktop environment.) This is an image of the Tic Tac Toe GUI when it first comes up ---- before any controls on the GUI are used. ---- !!!!!! %| [Category Games] | [Category Application] |% [Tcl/Tk Games] !!!!!!