This is Example 2 in the series about learning to program by making games with Tcl and Tk.
https://andreiclinciu.net/introduction-getting-started-first-steps-learn-to-code-by-making-games-tcl/
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!
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
[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:
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.