// = Tic Tac Toe Game Example using Tcl Tk :id: 856c41a2-abb3-41c8-b328-d669b95a1379 :author: Andrei Clinciu :website: https://andreiclinciu.net/ :publish_at: 2017-01-05 20:11:00Z :heading_image: \N :description: \N :type: article :tags: :keywords: :toc: left :imagesdir: ../assets/
image::{heading_image}[] This is Example 2 in the series about learning to program by making games with Tcl and Tk.
Today we will be creating a Tic tac Toe game.
Again as in the previous example https://andreiclinciu.net/number-guessing-game-tcl-tk/ we will be doing this in multiple versions
== Version 1
I won’t explain everything, but what we will do is very simple:
- We draw a 3x3 grid of buttons
- When we click a button it switches by changing it to X or O
- depending on the previous setting, registering this in the global gameboard variable
- Switching X or O after each click, disabling the command ( so you can’t click 10 times)
- Initialize the Gameboard
…. package require Tkset settings(current) Xarray set gameboard ““proc init {} {ttk::label .lblInfo -text “X or O"grid .lblInfo -row 0 -column 1for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tgrid [button .btn$x$y -text " " -command [list placeXorO $x $y] \\t\t-width 5 -height 2 \t\t\t] -row $y -column $x\t\tset gameboard($x,$y) “"\t}}}proc placeXorO {x y} {global gameboard settingsset gameboard($x,$y) $settings(current).btn$x$y configure -text $settings(current) -command {}switch – $settings(current) {\tX { set settings(current) O }\tO { set settings(current) X }}}#Function that takes min and max and generates a random integer numberproc rnd {min max} {\texpr {int(($max - $min + 1) * rand()) + $min}}init ….
== VERSION 2
We now implement some functions that verify if there are 3 X’s or O’s in a row + Diagonally (2 diagonals), horizontally or vertically after each move + Verifying the whole board for wins. + Optimally we should verify from the last move + something which we will do in the 4 and 5 in a row games with a more difficult + but efficient algorithm. For now this will do.
Inform the user when it’s his turn via the label. + NOTE: View how we split everything in it’s own function. + The VerifyWinner could have contained 8 If’s and other things + making it a function longer than 20 lines. + By splitting functions up we make our code maintainable and easier to read. + We spend between 5:1 and 10:1 time in reading to writing ration when coding, keep this in mind!
[source,theme:obsidian,lang:default,decode:true]
package require Tkset settings(current) Xarray set gameboard ““proc init {} {global gameboardttk::label .lblInfo -text “Player X’s turn"grid .lblInfo -row 0 -column 1 -columnspan 3for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tgrid [button .btn$x$y -text " " -command [list placeXorO $x $y] \\t\t-width 5 -height 2 \t\t\t] -row $y -column $x\t\tset gameboard($x,$y) “"\t}}}proc placeXorO {x y} {global gameboard settingsset gameboard($x,$y) $settings(current).btn$x$y configure -text $settings(current) -command {}verifyWinner $settings(current)switch – $settings(current) {\tX { set settings(current) O }\tO { set settings(current) X }}.lblInfo configure -text “Player $settings(current)’s turn”}#Version 2#Verify the winnerproc verifyWinner {type} {set win 0incr win [verifyDiagonals $type]incr win [verifyHorizontal $type]incr win [verifyVertical $type]if {$win} {\tset text “Player $type wins!"\ttk_messageBox -message $text\t.lblInfo configure -text $text}}proc verifyDiagonals {type} {global gameboardif {$gameboard(1,1) == $type && $gameboard(2,2) == $type && $gameboard(3,3) == $type} {\treturn 1}if {$gameboard(1,3) == $type && $gameboard(2,2) == $type && $gameboard(3,1) == $type} {\treturn 1}return 0}#verify the 3 horizontal linesproc verifyHorizontal {type} {global gameboardfor {set y 1} {$y<=3} {incr y} {\tif {$gameboard(1,$y) == $type && $gameboard(2,$y) == $type \t\t&& $gameboard(3,$y) == $type} {\t\treturn 1\t}}return 0}#Verify the 3 vertical linesproc verifyVertical {type} {global gameboardfor {set x 1} {$x<=3} {incr x} {\tif {$gameboard($x,1) == $type && $gameboard($x,2) == $type \t\t&& $gameboard($x,3) == $type} {\t\treturn 1\t}}return 0}#Function that takes min and max and generates a random integer numberproc rnd {min max} {\texpr {int(($max - $min + 1) * rand()) + $min}}init
== Version 3
This is the 3d version of the game. We will bring the following new things to the board:
Button to restart game + Reinitialize buttons and gameboard array + Checkboxes to specify + Player Vs Player + Player VS AI + Basic AI implementation: + 1st turn places a O on an empty space
{empty}[places O next to the other O’s + blocks X’es]
Verify Draw (3x3 board is full) + Disable all other buttons when someone wins untill he clicks the restart buttons
The code:
[source,theme:obsidian,lang:default,decode:true]
package require Tkset settings(current) Xarray set gameboard ““proc init {} {global gameboardttk::label .lblInfo -text “Player X’s turn"grid .lblInfo -row 0 -column 1 -columnspan 3for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tgrid [button .btn$x$y -text " " -command [list placeXorO $x $y] \\t\t-width 5 -height 2 \t\t\t] -row $y -column $x\t\tset gameboard($x,$y) “"\t}}\tgrid [ttk::label .lblGameMode -text “Game Mode”] -row 0 -column 0 grid [ttk::radiobutton .radHuman -text “Player VS Player” -variable gamemode -value human ] -row 1 -column 0grid [ttk::radiobutton .radAI -text “Player vs AI” -variable gamemode -value ai ] -row 2 -column 0.radHuman invokegrid [ttk::button .btnRestart -text “Restart Game” -command reinitializeBoard ] -row 3 -column 0}proc placeXorO {x y} {global gameboard settingsset gameboard($x,$y) $settings(current).btn$x$y configure -text $settings(current) -command {}if {![verifyWinner $settings(current)]} {\tswitch – $settings(current) {\t\tX { set settings(current) O }\t\tO { set settings(current) X }\t}\t.lblInfo configure -text “Player $settings(current)’s turn”\tmoveAI}}#Version 2#Verify the winnerproc verifyWinner {type} {set win 0incr win [verifyDiagonals $type]incr win [verifyHorizontal $type]incr win [verifyVertical $type]if {$win} {\tmessage “Player $type wins!"\t\t\treinitializeBoard 1\treturn 1}if {[verifyDraw]} { \tmessage “Draw!"\t; return 0 } return 0}#Version 3proc message {message} {tk_messageBox -message $message.lblInfo configure -text $message}proc verifyDiagonals {type} {global gameboardif {$gameboard(1,1) == $type && $gameboard(2,2) == $type && $gameboard(3,3) == $type} {\treturn 1}if {$gameboard(1,3) == $type && $gameboard(2,2) == $type && $gameboard(3,1) == $type} {\treturn 1}return 0}#verify the 3 horizontal linesproc verifyHorizontal {type} {global gameboardfor {set y 1} {$y<=3} {incr y} {\tif {$gameboard(1,$y) == $type && $gameboard(2,$y) == $type \t\t&& $gameboard(3,$y) == $type} {\t\treturn 1\t}}return 0}#Verify the 3 vertical linesproc verifyVertical {type} {global gameboardfor {set x 1} {$x<=3} {incr x} {\tif {$gameboard($x,1) == $type && $gameboard($x,2) == $type \t\t&& $gameboard($x,3) == $type} {\t\treturn 1\t}}return 0}#Version 3proc verifyDraw {} {global gameboardset totalMoves 0for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tif {$gameboard($x,$y) !=””} {\t\t\tincr totalMoves\t\t}\t}}if {$totalMoves == 9} {\treturn 1}return 0}proc reinitializeBoard {{disableButtons 0}} {global gameboard settings.lblInfo configure -text “Player X’s turn"for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tif {!$disableButtons} {\t\t\t.btn$x$y configure -text " " -command [list placeXorO $x $y] \t\t\tset gameboard($x,$y) “"\t\t} else {\t\t\t.btn$x$y configure -command {}\t\t}\t}}set settings(current) X}#Simple AI, chose a random location that’s empty to place an O#TODO verify thisproc moveAI {} {global gameboard settings gamemodeif {$gamemode != “ai”} { return }set aiMoved 0set ok 9for {set x 1} { $x<=3} { incr x } {\tfor {set y 1} { $y<=3} { incr y } {\t\tif {$gameboard($x,$y) != “”} {\t\t\tincr ok -1\t\t}\t}}if {!$ok} { puts “Gameboard is full! moveAI” ; return 0 }while {!$aiMoved} {\tset x [rnd 1 3]\tset y [rnd 1 3]\tputs “AI moving $x $y”\tif {$gameboard($x,$y) == “”} {\t\tset aiMoved 1\t\tset gameboard($x,$y) $settings(current)\t\t.btn$x$y configure -text $settings(current)\t\tif {[verifyWinner $settings(current)]} { \t\t\tputs “AI Won!” }\t}\tincr movesBeforeGivingUp -1}set settings(current) [expr {$settings(current) == “X” ? “O” : “X”}].lblInfo configure -text “Player $settings(current)’s turn”}#Function that takes min and max and generates a random integer numberproc rnd {min max} {\texpr {int(($max - $min + 1) * rand()) + $min}}initputs “GAME MODE $gamemode”
== Version 4
Version 4 is left as a homework exercise.
Make the game more beautiful by adding special effects and playing around with Tk themes. + Adding images and marking the win on the board + #BTN FLASH
EXERCISES + Making the AI Smarter is an exercise left to you + You will need to rewrite the way we detect wins, this way the AI detects your potential wins and tries to block you while trying to win.