Visual
Basic Gaming Quiz 2
This game is very different from what you are
learning in the textbook but I hope you will be able to learn something new
from this tic tac toe game as a quiz creation from the text was near
impossible. This is the first time this
class has been taught so let me know if you find any errors in the quiz.
Create the
Project
Create a new Visual
Basic.NET project. I used the Windows Application template and called it
"Tic Tac Toe"
To make things
easier for me I renamed Form1.vb to frmTicTacToe.vb by right clicking the file
in the solutions explorer. I then selected the form and changed the text to
"Tic-Tac-Toe" and the name to "frmTicTacToe".
Building
the Main Menu
I then went to the
toolbox and dragged over a MainMenu object. I decided to add two headings:
"Options", which would have all the features of the game, and
"About" which would have an all people to see a little information on
the program. Under Options I added New Game, Difficulty, and Quit. And then I added
the three difficulty settings: Easy, Medium, and Hard.
To make my code
easier to understand, I gave each of these design elements a more descriptive
name. The Options heading was originally called MenuItem1, so it was renamed
"menuOptions". I repeated this process for all of the recently
created menus.
Building
the Grid
Instead of making
images for the X's and O's, I decided to keep things simple and use the letter
X and O. To create the grid I used 9 labels laid out in a 3x3 square. To create
the first label I used the Toolbox and dragged it out to 8x8 using the dots as
my reference measurement. I then changed the following settings:
BackColor: White
BorderStyle: FixedSingle
Cursor: Hand
Font: Tahoma, 36pt, style=bold
Text: X
TextAlign: MiddleCenter
Name: lblGrid00
After everything was
set, I dragged the label over to the top-left corner and then made 8 copies of
it arranged in a 3x3 grid. I then renamed each label to correspond to the grid.
lblGrid00 lblGrid01 lblGrid02
lblGrid10 lblGrid11 lblGrid12
lblGrid20 lblGrid21 lblGrid22
Since that's all
that I'm putting in the windows I got rid of the negative space by dragging the
window frame until only the grid could fit. Here's what the initial build looks
like.
Game Actions
In every game there
needs to be a way to start a new game, an opponent, a win/lose condition, and
the ability to quit.
Starting
the Game
There are two events
that will trigger a new game. The first one is when the application first
opens. The second will be when someone goes to Options ? New Game from the
menu. For this part of the process there will be some actual coding instead of
the drag and drop "programming".
We're going to need
a few variables throughout the duration of the program.
Public Shared arrGrid(3, 3) As Integer
Public Shared intAIDifficulty As Integer
Public Shared intWhosTurn As Integer
Public Shared intWhosWinner As Integer
arrGrid is a 3x3
array that represents the game board, a 0 in the board will mean that the spot
is still available. A 1 will mean that the player has possession of the space,
while a 2 means that the computer opponent does. intAIDifficulty will store
difficulty level which will be used when we started adding in the computer
opponent. intWhosTurn knows whose turn it is, player 1 or 2. And, intWhosWinner
will store a value between 0-3 representing if anybody has won yet. 0 means the
game is still going on, while 3 means the game is a tie.
Private Enum Difficulty
Easy = 0
Medium = 1
Hard = 2
End Enum
I also decided to
enumerate the difficulty level. The Enum Difficulty isn't required, but if the
program got long and the difficulty value was used a lot, it would save a few
headaches. Some people might also want to enumerate the players or who's
winning; it's up to you.
Below the Window
Form Designer generated code I added the following lines:
Private Sub frmTicTacToe_Load(ByVal sender As _
System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Call InitGrid()
Call SetDifficulty(Difficulty.Medium)
intWhosTurn = 1
End Sub ' frmTicTacToe_Load
The
frmTicTacToe_Load() subroutine may already be there, but if not, you'll
definitely need to add it. This is what is executed when the program is first
loaded (the programs first starting condition). Because there hasn't been any
user input yet, I'll need to set up all the defaults and clear the grid.
Drawing The
Grid
I created two
subroutines and also let the system know that the player should have the first
move. InitGrid() will simply set every value in our 3x3 array to 0, and then
clear the game board. SetDifficulty() will not only set a difficulty variable,
but also put a pretty check on next to difficulty selected.
Private Sub InitGrid()
' Sets all values in the grid array to 0
For i As Integer = 0 To 2
For j As Integer = 0 To 2
arrGrid(i, j) = 0
Next
Next
Call DrawGrid()
End Sub ' InitGrid
The first thing that
happens in the program is to ensure all values in the array are set to 0. I
accomplished this by using a nested for loop. It travels down each row setting
the values to 0 and before it calls DrawGrid(). DrawGrid() will make the board
representative of the array by replacing 1s with Os and 2s with Xs.
Private Sub DrawGrid()
If arrGrid(0, 0) = 1 Then
Me.lblGrid00.Text = "O"
ElseIf arrGrid(0, 0) = 2 Then
Me.lblGrid00.Text = "X"
Else
Me.lblGrid00.Text = ""
End If
End Sub ' DrawGrid
This is part of the
DrawGrid() sub. For each position in the array, it'll check the value and set
the label to its corresponding character. Nine of these condition statements
are required to draw the whole board.
Setting The
Difficulty
Private Sub SetDifficulty(ByVal intDifficulty As Integer)
intAIDifficulty = intDifficulty
Select Case intDifficulty
Case 0
Me.menuEasy.Checked = True
Me.menuMedium.Checked = False
Me.menuHard.Checked = False
Case 1
Me.menuEasy.Checked = False
Me.menuMedium.Checked = True
Me.menuHard.Checked = False
Case 2
Me.menuEasy.Checked = False
Me.menuMedium.Checked = False
Me.menuHard.Checked = True
End Select
End Sub ' SetDifficulty
Whenever we called
SetDifficult() with Difficulty.Medium, it stuck a 1 there and set
intAIDifficulty to 1. I also wanted the players to know what level opponent
they were competing against so Me.menuMedium.Check = True puts a little check
to the left of Medium on our menu. Setting the other values to false ensure
that there aren't any extra checks.
Now that the
defaults are set up, all the program does is waits for a player to click a
square. Using many of the routines already programmed with can set up the board
for when a player calls a new game from the menu.
Using the
Options Menu
I programmed a
behavior for each item on the Options menu: new game, difficulty, and quit.
Private Sub menuNewGame_Click(ByVal sender _
As System.Object, ByVal e As System.EventArgs) Handles menuNewGame.Click
Call InitGrid()
intWhosWinner = 0
If intWhosTurn = 2 Then
Call AIMove()
End If
End Sub ' menuNewGame_Click
New game is similar
to when the program starts, except that it will reset the intWhosWinner
variable, and tell the computer opponent move if it is its turn. If you wanted
the game to be two players, you could remove the AIMove() call.
Private Sub menuEasy_Click(ByVal sender _
As System.Object, ByVal e As System.EventArgs) Handles menuEasy.Click
Call SetDifficulty(Difficulty.Easy)
End Sub ' menuEasy_Click
For each difficulty
level I added the following to the program. These lines use the SetDifficulty
subroutine we set up earlier.
Private Sub menuQuit_Click(ByVal sender _
As System.Object, ByVal e As System.EventArgs) Handles menuQuit.Click
Application.Exit()
End Sub ' menuQuit_Click
To quit I just used
the built-in Application.Exit() function.
Time for Xs
and Os
Well, I've
programmed a lot of code, but still can't play tic-tac-toe. Here's where that
all changes.
Private Sub lblGrid00_Click(ByVal sender _
As System.Object, ByVal e As System.EventArgs) Handles lblGrid00.Click
Call GridClick(0, 0)
End Sub ' lblGrid00_Click
I created a
subroutine for each label in the 3x3 grid that looks pretty close to this.
Seems pretty simple, but let's take a look at what GridClick() does.
Programming
the Meat
The first thing to
do when a square is clicked, is make sure that the square isn't occupied and
also that a winner hasn't already been declared.
If (arrGrid(intRow, intCol) = 0) And (intWhosWinner = 0) Then
End If
If the spot on the
grid is empty and the game is still being played, then we'll give the square to
whoever's turn it is.
If intWhosTurn = 1 Then
arrGrid(intRow, intCol) = 1
intWhosTurn = 2
ElseIf intWhosTurn = 2 Then
arrGrid(intRow, intCol) = 2
intWhosTurn = 1
End If
Call DrawGrid()
After that the
display is redrawn by calling the DrawGrid() subroutine.
Call CheckWinLose()
If (intWhosTurn = 2) And (intWhosWinner = 0) Then
Call AIMove()
End If
Near the end of each
move, the program will need to check whether someone has got three in a row.
CheckWinLose() will handle this and I'll show you the code in just a minute. If
there isn't a winner and the player just moved, then it's time for the computer
opponent. Once again, if you want this to be two players rather than playing a
computer, just remove this line.
' Declare the winner
If intWhosWinner = 3 Then
MessageBox.Show("It's a draw")
ElseIf intWhosWinner = 2 Then
MessageBox.Show("You Lose")
ElseIf intWhosWinner = 1 Then
MessageBox.Show("You Win")
End If
After CheckWinLose()
is called, the program knows if we have a winner, tie, or whether to keep
playing.
Have I Won
Yet?
CheckWinLose() is
the main reason I programmed this with a 3x3 array instead of just using the
labels. Rather than having to do comparisons for every possible way to win,
which is eight by the way, I was able to divide it out into three directions.
Dim intOCount As Integer = 0
Dim intXCount As Integer = 0
Dim intTotalCount As Integer = 0
First I'll introduce
the counters. intOCount will let the game know if player 1 is the winner,
intXCount player 2, and intTotalCount if there's a tie.
' Check horizontal for win or lose
For i As Integer = 0 To 2
For j As Integer = 0 To 2
If arrGrid(i, j) = 1 Then
intOCount += 1
intTotalCount += 1
End If
If arrGrid(i, j) = 2 Then
intXCount += 1
intTotalCount += 1
End If
Next
If intOCount = 3 Then
intWhosWinner = 1
ElseIf intXCount = 3 Then
intWhosWinner = 2
End If
intOCount = 0
intXCount = 0
Next
Starting out by
checking each row, number of spaces that player one and two have possession of
are added up. If either has three, then the game is over. If not, then the
counters are reset and the next row is checked. This is nearly the same for the
columns. But before the columns are checked, if all squares are full then a tie
is declared.
' Check for tie
If (intTotalCount = 9) And (intWhosWinner = 0) Then
intWhosWinner = 3
Else
intTotalCount = 0
End If
Since intTotalCount
incremented any time there was an X or an O, if there are nine spaces filled
then it might be a tie. The program will call it a tie unless a win overrides
the assumption. After the check is performed, the program no longer needs to be
incrementing intTotalCount.
' Check diagonal for win or lose
If arrGrid(0, 0) = 1 And arrGrid(1, 1) = 1 And arrGrid(2, 2) = 1 Then
intWhosWinner = 1
ElseIf arrGrid(0, 0) = 2 And arrGrid(1, 1) = 2 And arrGrid(2, 2) = 2 Then
intWhosWinner = 2
ElseIf arrGrid(0, 2) = 1 And arrGrid(1, 1) = 1 And arrGrid(2, 0) = 1 Then
intWhosWinner = 1
ElseIf arrGrid(0, 2) = 2 And arrGrid(1, 1) = 2 And arrGrid(2, 0) = 2 Then
intWhosWinner = 2
End If
After it checks for
a tie, it'll need see if any of the players won vertically by using the same
method as for horizontal winners. After that, the game checks to see if a
diagonal winner exists.
At this point, if
you removed the AIMove() lines, then you have a perfectly good version of two
player tic-tac-toe.
Programming
the AI
The intelligence was
probably the most difficult part of programming the game. You have to learn how
to win at tic-tac-toe, every time. Since I decided that our program was going
to have 3 different difficulty settings: easy, medium, and hard, I had to
decide what strategy each of these opponents would use. For E-Z mode, the
program will just place Xs randomly (not much strategy at all). For the
mid-level difficulty, it'll be able to know when it's about to win and lose and
play accordingly (this is how most people actually play tic-tac-toe). For hard
difficulty, it'll know the best way to play during any situation. A draw with
this opponent is considered a perfect game (this is how experts play).
Private Sub AIMove()
Dim intRandomX As Integer = RandomNumber(3, 0)
Dim intRandomY As Integer = RandomNumber(3, 0)
Select Case intAIDifficulty
Case Difficulty.Easy
Case Difficulty.Medium
Case Difficulty.Hard
End Select
End Sub ' AIMove
I created a
subroutine with a case statement for each difficulty. Since Easy is going to be
completely random I created a couple variables to store a random horizontal
position and a random vertical position.
E-Z Mode
Public Function RandomNumber(ByVal MaxNumber _
As Integer, ByVal MinNumber As Integer)
Dim r As New Random(System.DateTime.Now.Millisecond)
Return r.Next(MinNumber, MaxNumber)
End Function ' RandomNumber
Here's the
RandomNumber function. It uses the built in random class to generate a value
between the numbers supplied, in our case 0, 1, or 2.
While arrGrid(intRandomX, intRandomY) <> 0
intRandomX = RandomNumber(3, 0)
intRandomY = RandomNumber(3, 0)
End While
Call GridClick(intRandomX, intRandomY)
The easy strategy
looks like this. It gets a random position over and over until a blank spot is
found. The other difficulty settings will also use this strategy after the
programmed intelligence is exhausted.
Programming
An Intermediate Opponent
' Check to see if it can win
For i As Integer = 0 To 2
For j As Integer = 0 To 2
If arrGrid(i, j) = 0 Then
arrGrid(i, j) = 2
Call CheckWinLose()
If intWhosWinner = 2 Then
intWhosWinner = 0
arrGrid(i, j) = 0
GridClick(i, j)
Return
Else
arrGrid(i, j) = 0
End If
End If
Next
Next
The first thing the
medium opponent should do is see if it has a chance to win. It accomplishes
this by placing a hypothetical move for every open position on the board. It
then calls the CheckWinLose subroutine. If the move results in a win then it
goes ahead and takes that move, otherwise, it'll set that spot back to empty
move to the next space.
' Check to see if it will lose
For i As Integer = 0 To 2
For j As Integer = 0 To 2
If arrGrid(i, j) = 0 Then
arrGrid(i, j) = 1
Call CheckWinLose()
If intWhosWinner = 1 Then
intWhosWinner = 0
arrGrid(i, j) = 0
GridClick(i, j)
Return
Else
intWhosWinner = 0
arrGrid(i, j) = 0
End If
End If
Next
Next
If it's not possible
for the computer to win, then it'll need to make sure that it won't lose on the
players next turn. It accomplishes this in much the same manner as when it
checked for winning conditions. A hypothetical move is made, but this time as
the player. If the player would win based on this move, then the computer will
move there instead. If the computer opponent can't win or lose in the next
move, then it'll just play randomly.
The Game
That Knew Too Much
The hard difficulty
is where it gets tough. I read a tic-tac-toe strategy manual (how lame is that)
and found out that the game is decided in the first 4 moves, everything after
that can be won (or tied) by playing like the medium difficulty opponent. So
what I needed to do was count have many moves had been made. Based on this and
the position of the Xs and Os, the program knows how to move accordingly.
Dim intSquaresUsed As Integer = 0
For i As Integer = 0 To 2
For j As Integer = 0 To 2
If arrGrid(i, j) <> 0 Then
intSquaresUsed += 1
End If
Next
Next
Select Case intSquaresUsed
Case 0
Case 1
Case 2
Case 3
Case 4
Case Is > 4
End Select
While I'm not going
to go into all the possible moves (you can read the strategy guide if you're
that interested), I'll cover the basic structure. First it'll go through each
spot on the grid to see how many are filled. This will let us know what move
the game is on and let us pick the appropriate strategy. After the fourth move,
then it'll just play using the same strategy as the medium difficulty opponent.
The End
Put all this
together and you've programmed a game that will be fun for almost five minutes.
But hopefully you learned some useful Visual Basic.NET programming in the
process.