Programming The Tic Tac Toe Game

How to Use Visual Basic to Program a Tic Tac Toe Game

Programming computer games may be the most technically challenging (and possibly the best paying) job that a programmer can have. Top level games require the best from both programmers and computers.

Visual Basic 6 has now been thoroughly bypassed as a platform for game programming. (It never really was one. Even in the "good ol' days", serious game programmers would never use a high level language like VB 6 because you just couldn't get the cutting edge performance that most games require.) But the simple "Tic Tac Toe" game is a great introduction to programming that is a little more advanced than "Hello World".

This is a great introduction to many of the fundamental concepts of programming since it combines techniques including:

  • The use of arrays.The X and O markers are kept in separate arrays and the entire arrays are passed between functions to keep track of the progress of the game.
  • Using VB 6 level graphics: VB 6 doesn't offer great graphical capability, but the game is a good introduction to what is available. Much of the rest of this series is an exploration of how GDI+, the next generation of Microsoft graphics, replaces the VB 6 level graphics.
  • Using math calculations for program control: The program uses clever modulo (Mod) and integer division calculations using the two game marker arrays to determine when a three-element "win" has occurred.

The class of programming in this article is perhaps just a little past the beginning level but it should be good for "intermediate" programmers. But let's start at an elementary level to illustrate some of the concepts and get you started with your Visual Basic game programming career.

Even students more advanced than that may find that it's slightly challenging to get the objects in the form just right.

To download the source code for the program Click Here!

Theory of the Game

If you've never played Tic Tac Toe, here are the rules. Two players alternate at placing X's and O's into 3 x 3 playing field.

Before the game starts, both players have to agree about who will go first and who will mark his moves with which symbol. After the first move, the players alternately place their marks in any empty cell. The goal of the game is to be the first player with three marks in a horizontal, diagonal or vertical line. If there are no empty cells and neither player has a winning combination, the game is a draw.

Starting the Program

Before starting any actual coding, it's always a good idea to change the names of any components you use. Once you start coding, the name will be used automatically by Visual Basic so you want it to be the right name. We'll use the form name frmTicTacToe and we'll also change the caption to "About Tic Tac Toe."

With the form established, use the line toolbox control to draw a 3 x 3 grid. Click the line tool, then draw a line where you want it. You'll have to create four lines this way and adjust their length and position to make them look right. Visual Basic also has some convenient tools under the Format menu that will help. This is a great chance to practice with them.

In addition to the playing grid, we'll need some objects for the X and O symbols that will be placed on the grid.

Since there are nine spaces in the grid, we'll create an object array with nine spaces, called elements in Visual Basic.

There are several ways to do just about everything in the Visual Basic development environment, and creating control arrays is is no exception. Probably the easiest way is to create the first label (click and draw just like the line tool), name it, set all of the attributes (such as Font and ForeColor), and then make copies of it. VB 6 will ask if you want to create a control array. Use the name lblPlayGround for the first label.

To create the other eight elements of the grid, select the first label object, set the Index property to zero, and press CTRL+C (copy). Now you can press CTRL+V (paste) to create another label object. When you copy objects like this, each copy will inherit all properties except Index from the first one.

Index will increase by one for each copy. This is a control array because they all have the same name, but different index values.

If you create the array this way, all of the copies will be stacked on top of each other in the upper left corner of the form. Drag each label to one of the playing grid positions. Be sure that index values are sequential in the grid. The logic of the program depends on it. The label object with index value 0 should be in the top left corner, and the bottom right label should have index 8. If the labels cover the playing grid, select each label, right click, and select Send to Back.

Since there are eight possible ways to win the game, we'll need eight different lines to show the win on the playing grid. We will use the same technique to create another control array. First, draw the line, name it linWin, and set the Index property to zero. Then use copy-paste technique to produce seven more lines. The following illustration shows how to set the index numbers correctly.

In addition to the label and line objects, we need some command buttons to play the game and more labels to keep score. We won't go through the steps to create these in detail, but here are all the objects you need.

two button objects

  • cmdNewGame
  • cmdResetScore

frame object fraPlayFirst containing two option buttons

  • optXPlayer
  • optOPlayer

frame object fraScoreBoard containing six labels
Only lblXScore and lblOScore are changed in the program code.

  • lblX
  • lblXScore
  • lblO
  • lblOScore
  • lblMinus
  • lblColon

Finally, we also need the label object lblStartMsg to 'mask' the cmdNewGame button when it shouldn't be clicked.

This isn't visible in the illustration below because it occupies the same space in the form as the command button. You may have to move the command button temporarily to draw this label on the form.

So far, no VB coding has been done, but we're finally ready to do that.

Initialization

Now we finally start coding our program. If you haven't already, you might want to download the source code to follow along as the operation of the program is explained.

One of the first design decisions to make is how to keep track of the current 'state' of the game. In other words, what are the current X's and O's on the playing grid and who moves next. The concept of 'state' is critical in a lot of programming, and in particular, it's important in programming ASP and ASP.NET for the web

There are several ways that this could be done, so it's a critical step in the analysis. If you were solving this problem on your own, you might want to draw a flow chart and try out different options with 'scratch paper' before starting any coding.

Variables

Our solution uses two 'two dimensional arrays' because that helps keep track of 'state' by simply changing the array indexes in program loops. The state of the top-left corner will be in the array element with index (1, 1), the top-right corner will be in (1, 3), the bottom-right in (3,3), and so forth. The two arrays that do this are:

iXPos(x, y)

and

iOPos(x, y)

There are a lot of different ways this can be done and the final VB.NET solution in this series shows you how to do it with just a single one dimensional array.

The programming to translate these arrays into player win decisions and visible displays in the form are on the next page.

We also need a few global variables as follows. Notice that these are in the General and Declarations code for the form. This makes them "module level" variables that can be referenced anywhere in the code for this form. For more on this, check Understanding the Scope of Variables in Visual Basic Help.

There are two areas where variables are initialized in our program. First, a few variables are initialized while the form frmTicTacToe is loading.

Private Sub Form_Load()

Second, before each new game, all variables that need to be reset to starting values are assigned in an initialization subroutine.

Sub InitPlayGround()

Note that the form load initialization also calls the playground initialization.

One of the critical skills of a programmer is the ability to use the debugging facilities to understand what the code is doing. You can use this program to try
Stepping through the code with the F8 key
Setting a watch on key variables, such as sPlaySign or iMove
Setting a breakpoint and querying the value of variables. For example, in the inner loop of the initialization
lblPlayGround((i - 1) * 3 + j - 1).Caption = ""

Note that this program clearly shows why it's a good programming practice to keep data in arrays whenever possible. If we did not have arrays in this program, we would have to write code something like this:

Line0.Visible = False
Line1.Visible = False
Line2.Visible = False
Line3.Visible = False
Line4.Visible = False
Line5.Visible = False
Line6.Visible = False
Line7.Visible = False

instead of this:
For i = 0 To 7
linWin(i).Visible = False
Next i

Making a Move

If any part of the system can be thought of as 'the heart', it's subroutine lblPlayGround_Click. This subroutine is called every time a player clicks the playing grid. (Clicks must be inside one of the nine lblPlayGround elements.) Notice that this subroutine has an argument: (Index As Integer). Most of the other 'event subroutines', like cmdNewGame_Click() do not. Index indicates which label object has been clicked. For example: Index would contain the value zero for the top-left corner of the grid and the value eight for the bottom-right corner.

After a player clicks a square in the game grid, the command button to start another game, cmdNewGame, is "turned on' by making it visible. The state of this command button does double duty because it's also used as a boolean decision variable later in the program. Using a property value as a decision variable is usually discouraged because if it ever becomes necessary to change the program (say, for example, to make the cmdNewGame command button visible all the time), then the program will unexpectedly fail because you might not remember that it's also used as part of the program logic. For this reason, it's always a good idea to search through program code and check the use of anything you change when doing program maintenance, even property values. This program violates the rule partly to make this point and partly because this is a relatively simple piece of code where it's easier to see what is being done and avoid problems later.

A player selection of a game square is processed by calling the GamePlay subroutine with Index as the argument.
Processing the Move
First, we check to see if an unoccupied square was clicked.

If lblPlayGround(xo_Move).Caption = "" Then

Once we're sure this is a legitimate move, the move counter (iMove) is incremented. The next two lines are very interesting since they translate the coordinates from the one-dimensional If lblPlayGround component array to two-dimensional indexes that we can use in either iXPos or iOPos. Mod and integer division (the 'backslash') are mathematical operations that you don't use everyday, but here's a great example showing how they can be very useful.

 If lblPlayGround(xo_Move).Caption = "" Then
  iMove = iMove + 1
  x = Int(xo_Move / 3) + 1
  y = (xo_Move Mod 3) + 1

The xo_Move value 0 will be translated to (1, 1), 1 to (1, 2) ... 3 to (2, 1) ... 8 to (3, 3).

The value in sPlaySign, a variable with module scope, keeps track of which player made the move. Once the move arrays are updated, the label components in the playing grid can be updated with the appropriate sign.

  If sPlaySign = "O" Then
   iOPos(x, y) = 1
   iWin = CheckWin(iOPos())
  Else
   iXPos(x, y) = 1
   iWin = CheckWin(iXPos())
  End If
  lblPlayGround(xo_Move).Caption = sPlaySign

For example, when the X player clicks the top left corner of the grid, variables will have following values:

The user screen shows only an X in the upper left box, while the iXPos has a 1 in the upper left box and 0 in all of the others. The iOPos has 0 in every box.

The values changes when the O player clicks the center square of the grid. Now th iOPos shows a 1 in the center box while the user screen shows an X in the upper left and an O in the center box. The iXPos shows only the 1 in the upper left corner, with 0 in all of the other boxes.

Now that we know where a player clicked, and which player did the clicking (using the value in sPlaySign), all we have to do is find out if someone won a game and figure out how to show that in the display. All of this will be revealed on the next page!

Finding a Winner

After each move the CheckWin function checks for the winning combination. CheckWin works by adding down each row, across each column and through each diagonal. Tracing the steps through CheckWin using Visual Basic's Debug feature can be very educational. Finding a win is a matter of first, checking whether three 1's were found in each of the individual checks in the variable iScore, and then returning a unique "signature" value in Checkwin that is used as the array index to change the Visible property of one element in the linWin component array. If there is no winner, CheckWin will contain the value -1. If there is a winner, the display is updated, the scoreboard is changed, a congratulation message is displayed, and the game is restarted.

Let's go through one of the checks in detail to see how it works. The others are similar.

 ' Check Rows for 3
 For i = 1 To 3
  iScore = 0
  CheckWin = CheckWin + 1
  For j = 1 To 3
   iScore = iScore + iPos(i, j)
  Next j
  If iScore = 3 Then
   Exit Function
  End If
 Next i

The first thing to notice is that the first index counter i counts down the rows while the second j counts across the columns. The outer loop, then simply moves from one row to the next. The inner loop counts the 1's in the current row. If there are three, then we have a winner.

Notice that we also keep track of the total number of squares tested in the variable CheckWin, which is the value passed back when this function terminates. Each winning combination will end up with a unique value in CheckWin from 0 to 7 which is used to select one of the elements in the linWin() component array. This makes the order of the code in function CheckWin important too! If you moved one of the blocks of loop code (like the one above), the wrong line would be drawn on the playing grid when someone wins. Try it and see!

Finishing Details

The only code we haven't discussed are the subroutine for a new game and the subroutine that will reset the score. The rest of the logic in the system makes creating these quite easy. To start a new game, we have only to call the InitPlayGround subroutine. As a convenience for players since the button could be clicked in the middle of a game, we ask for confirmation before going ahead. We also ask for confirmation before restarting the scoreboard.