Yet Another Tic Tac Toe






4.53/5 (15 votes)
Nov 3, 2003
4 min read

149813

2354
Yet Another Tic Tac Toe program. This example breaks the tic tac toe game into three parts. Model data, UI, and AI.
Introduction
I was watching my girl friend play web games the other day, including playing tic-tac-toe, and decided to give it a try in VB.NET. With-in about 30 minutes I had a game up and running in which I could play against my step-daughter. My implementation separated the game logic into three areas: model, UI, and an AI class. The associated source files for these areas are TicTacToeGame.vb, frmTicTacToe.vb, and TicTacToeAI.vb. General programming areas used in this project include Enums, Events, and Overloading a property.
After implementing my version of tic-tac-toe, I researched how others have implemented the game. Grant Richard has written a good article in the C# area here.
The Tic-Tac-Toe Model
A tic-tac-toe board can have three entries in a square: Nothing, X, and O. These are represented by an Enum called GridEntry
. The Enums are appropriately called NoEntry
, PlayerX
, and PlayerO
. A multi-dimensional grid of 3x3 squares of the type GridEntry
represents the playing board. Also note that the enum is inside the class. This allows the programmer to code CLASSNAME.ENUM from other classes.
0,0 -- 0,1 -- 0,2
1,0 -- 1,1 -- 1,2
2,0 -- 2,1 -- 2,2
Public Class TicTacToe
Public Enum GridEntry
NoEntry = 0
PlayerX = 1
PlayerO = 2
End Enum
Private m_iGrid(2,2) as GridEntry
End Class
The only property of the tic-tac-toe model class is Square
. This property gets/sets a type of GridEntry
. A user can only set a Square to either X or O. If they try to overwrite a square once it has been written to, the set property ignores the set operation. The set property also checks to see if there is a winner condition or draw condition and raises the appropriate event.
Public Event TicTacToeWinOccured(ByVal aWinner as GridEntry) 'Win Condition
Public Event Cat() ‘Draw Condition
Public Property Square(iRow as Integer, iCol as Integer) as GridEntry
Get
Return m_iGrid(iRow, iCol)
End Get
Set(ByVal Value as GridEntry)
If m_iGrid(iRow, iCol) = Grid.NoEntry And _
m_bHasWinOccured = False Then
m_iGrid(iRow, iCol) = Value
m_iWinner = CheckForWin()
If m_iWinner = GridEntry.NoEntry Then
If CheckForDraw() = True
RaiseEvent Cat()
End If
Else
m_bHasWinOccured = True
RaiseEvent TicTacToeWinOccured(m_iWinner)
End If
End If
End Set
End Property
While writing the AI class, I decided to encapsulate iRow
and iCol
. This class was called SquareCoordinate
which would allow me to pass one parameter to a function instead of iRow
and iCol
. So the property Square
was overloaded to take a SquareCoordinate
variable.
Public Property Square(aSC as SquareCoordinate) as GridEntry
Get
Return m_iGrid(aSC.Row, aSC.Column)
End Get
Set(Value as GridEntry)
Me.Square(aSC.Row, aSC.Column) = Value
End Set
End Property
The User Interface
The UI portion of this project has 9 labels in a group box which when clicked on call lblGrid_Click
. This is setup using an AddHandler
command and using the AddressOf
operator to point to lblGrid_Click
. Each control is named lblGridXX
where XX is the grid coordinates for the square as noted above. For example 00 is the first square. The subroutine DrawGrid
updates the labels with the data in the tic-tac-toe model.
AddHandler lblGrid00.Click, AddressOf lblGrid_Click.
Public Sub lblGrid_Click(sender as Object, args as EventArgs)
Try
Dim aLabelControl As Label = CType(sender, Label)
Dim sName as String = aLabelControl.Name
Dim iRow as Integer = sName.SubString(sName.Length-2,1)
Dim iCol as Integer = sName.SubString(sName.Length-1, 1)
If m_TicTacToeGame.Square(iRow, iCol) = TicTacToe.GridEntry.NoEntry Then
m_TicTacToeGame.Square(iRow, iCol) = m_iCurrentPlayer
ToggleCurrentPlayer()
DrawGrid()
End If
Catch ex as Exception
Stop ‘Error Handling to be completed
End Try
End Sub
Private Sub DrawGrid()
Dim iRow as Integer
Dim iCol as Integer
Dim aLabelControl as Label
For iRow = 0 to 2
For iCol = 0 to 2
Try
aLabelControl = GetLabelControl(iRow, iCol)
Select Case m_TicTacToeGame.Square(iRow, iCol)
Case TicTacToe.GridEntry.NoEntry
aLabelControl.Text = “”
Case TicTacToe.GridEntry.PlayerX
aLabelControl.Text = “X”
Case TicTacToe.GridEntry.PlayerO
aLabelControl.Text = “O”
End Select
Catch ex as Exception
End Try
Next
Next
End Sub
Win/Draw Condition Logic
The win condition logic in the tictactoe class checks for a win condition by checking the squares horizontally, vertically, and diagonally. If the same player is in all three squares in one direction, that player is returned from the win logic code; otherwise NoEntry
is returned.
Private Function CheckForHorizontalWin() as GridEntry
Dim iRow as Integer
Dim iFirstSquarePlayer as GridEntry
For iRow = 0 to 2
iFirstSquarePlayer = m_iGrid(iRow,0)
If iFirstSquarePlayer = m_iGrid(iRow,1) and _
iFirstSquarePlayer = m_iGrid(iRow,2) Then
Return iFirstSquarePlayer
End If
Next
Return GridEntry.NoEntry
End Function
The draw logic is pretty simple. We loop through all squares, and if we find a NoEntry
, return false; otherwise if they are all full, return true for a draw.
Private Function CheckForDraw() as Boolean
Dim iRow as Integer, iCol as Integer
For iRow = 0 to 2
For iCol = 0 to 2
If m_iGrid(iRow,iCol) = GridEntry.NoEntry Then Return False
Next
Next
Return True
End Function
AI Logic
The AI Logic is contained in the TicTacToeAI
class. There are four general rules that the AI class follows:
- Win if possible
- Block opponent from winning
- Determine a Best Bet move to setup a future win
- Make any valid move
A MoveEntry
class has been created to contain possible moves. This is a private class only accessiable to the TicTacToeAI
class. This class contains two variables: a SquareCoordinate
, and a MoveType
of Winning
, Blocking
, or Normal
.
Private Enum MoveType
Normal = 0
Blocking = 1
Winning = 2
End Enum
The TicTacToeAI
class has only one public method GetAIMove(ByVal aTTT as TicTacToe)
and returns a SquareCoordinate
. The workhorse of the AI is the private function DetermineMoveEntry(aSC as SquareCoordinate)
which classifies a SquareCoordinate
to either a winning move, a blocking move, or a normal move. It first checks horizontal, vertical, and finally diagonal. To check diagonal, another enum is used along with a function to determine which tic tac toe square is being examined.
Private Enum SquareType
UpperLeft = 1
UpperRight = 2
Center = 3
BottomLeft = 4
BottomRight = 5
Other = 6
End Enum
Private Function DetermineMoveEntry(ByVal aSC as SquareCoordinate) _
as MoveEntry
Dim iRow as Integer = aSC.Row
Dim iCol as Integer = aSC.Column
Dim iGE1 as TicTacToe.GridEntry
Dim iGE2 as TicTacToe.GridEntry
' ... Code Omitted for Horizontal and Vertical
Select Case GetSquareType(aSC)
Case SquareType.UpperLeft
iGE1 = m_CurrentBoard.Square(1,1)
iGE2 = m_CurrentBoard.Square(2,2)
If CheckPossibleWin(iGE1, iGE2, m_iAIPlayer = True Then
Return New MoveEntry(aSC, MoveType.Winning)
End If
If CheckPossibleWin(iGE1, iGE2, m_iHumanPlayer) = True Then
Return New MoveEntry(aSC, MoveType.Blocking)
End If
' ... Code omitted for other cases
End Select
Return New MoveEntry(aSC, MoveType.Normal)
End Function
Finally GetAIMove
stores each MoveEntry
into an ArrayList
, and then checks the ArrayList
for winning/blocking moves. If there are no winning moves or blocking moves, it tries to determine if there are any best moves by calling DetermineBestNormalMove(aNormalMoveList as ArrayList)
. Otherwise it picks a normal move at random.
I had fun writting this tic-tac-toe program. I may in the future examine other implementations of the base model and the logic for determining win, draw, and AI moves. For example, Grant Richard uses a single array of 9 squares numbering each square 1 to 9 with his win conditions contained in an array. His article is here.