Step 7


Overview

In this step you will implement a game playing strategy for the computer. This involves adding code to the TTTCell, TTTLine, and TicTacToe classes.

In the TTTCell class, you will add an instance variable votes and three methods, resetVotes(), addVotes(), and getVotes(). The variable and the methods create a mechanism for counting votes. To aid in debugging, the paintComponent() method is also modified to display the number of votes received by each cell.

The votes will be cast by the TTTLine objects. This requires adding a castVotes() method to the TTTLine class. In this method, a TTTLine sends addVotes() messages to each of its cells. The number of votes added depends on the number of X and O marks in the line. This method is the heart of the game playing strategy for the computer.

Voting is controlled in the TicTacToe class. You will add two methods to this class, resetVotes() and castVotes(). The first just uses a loop to invoke the method of the same name for each TTTCell. It initializes the vote count to zero for all of the cells. The second invokes resetVotes(), then uses a loop to invoke castVotes() for each TTTLine, and finally repaints the board to show the most recent vote count.

The castVotes() method is invoked in the updateStatus() method, which is invoked at the start of a new game and after each move. This ensures that cells always display an up-to-date count. The most important use of the voting mechanism is in the getBestPlay() method. There, the cell with the largest number of votes is selected for the computer's next move.

The Vote Counting Mechanism

For this class, you will need to add an int instance variable named votes for counting the number of votes for the cell. This variable should be initialized to 0.

Now add three methods to the class. The resetVotes() method just sets the vote count to 0. It has no parameters and no returned value.

The getVotes() method just returns the vote count. It has no parameters and an int returned value.

The addVotes() method has an int parameter. It has no returned value. The parameter is added to the vote count, but only when the mark in the cell is TTTCell.NO_ONE. This ensures that cells that are already marked do not receive votes.

For debugging, the vote count can be displayed by adding code at the end of the paintComponent() method. This can be accomplished with the following statement.

    g.drawString("" + votes, 10, 14);
If you nest this statement inside an if statement whose condition is the equality of the cell mark and NO_ONE, then the vote count will only be displayed in cells that have not yet been marked.

After you have completed the changes for the TTTCell class, recompile it to check for syntax errors.

The Vote Casting Mechanism

The only change for this class is adding a castVotes() method. This method has an int parameter specifying the cell mark of a player. It first invokes the winner() method and returns immediately if its returned value is not TTCell.NO_ONE. This is implemented with an if statement whose nested statement is a return statement. It implies that a line which is drawn cast no votes.

The code immediately following the if statement first defines an int local variable votesToCast that determines how many votes should be cast, then uses if statements to assign a value to this variable. The number of votes is 4 if X owns 2 cells in the line or O owns 2 cells in the line. The number of votes is 3 if X owns 1 cell in the line O owns 1 cell in the line. Otherwise, the number of votes is 1. Finally, a loop iterates through the cells of the line, sending addVotes() messages to all of the cells. The parameter for each is the votesToCast local variable.

After you have added the method, recompile your TTTLine class to check for syntax errors.

Using the Vote Counting and Casting Mechanisms

The vote counting and casting mechanisms are used in the TicTacToe class. The primary objective is determining the best play for the computer. A secondary objective is displaying the vote count for debugging. Although the vote count only determines the computer's play, it is displayed for both players.

First, you need to add two utility methods to the class. These methods apply the TTTCell or TTTLine methods with the same name to all of the cells or lines.

The resetVotes() method has void return type and no parameters. It loops through all of the cells, sending each a resetVotes() message. This loop should be similar in structure to the one in the startNewGame() method.

The castVotes() method also has void return type. It has an int parameter named plyr to specify the player for whom votes are cast. It first invokes resetVotes(). Then it loops through all of the lines, sending each a castVotes(myMark) message. This loop should be similar in structure to the ones in the playerWins() and gameDrawn() methods. After the end of the loop, it should send a repaint() message to the board instance variable. This ensures that cells always display the most recent vote count.

In order to be useful, the castVotes() method must be invoked whenever a new game is started and after each play by either the user or the computer. You already have a method, updateStatus() that is invoked in both of these situations. In this method, you should invoke castVotes() with toPlay as a parameter. The best place to add the statement is just before the statements that enable or disable the buttons.

Finally, modify the getBestPlay() method. You need to modify the loop that selects the best cell so that it selects the one with the highest number of votes. To do this, add an int local variable named bestVotes just before the start of the loop. This variable should be initialized to 0. While the loop is executing, it will be updated to reflect the largest number of votes received by the cells that have been examined.

Now add an int local variable named cVotes just before the if statement in the loop. It should be initialized by the return value of a getVotes() message sent to the current cell c. Then replace the if statement condition with one that is true when cVotes is greater than bestCount. When you are executing code inside the if statement, it means that you have found a cell c that has more votes than cells that have been examined earlier. Then you want to assign c as the value of bestCell (that statement should already be there) and cVotes as the value of bestVotes (you will need a new statement for this). This code ensures that after each iteration through the loop, bestVotes is the largest number of votes that has been seen so far, and bestCell is a cell that had the largest number of votes.

After you have added modified the TicTacToe, recompile it and run the applet with appletviewer. Your applet should look and behave like the following demonstration applet. You should play several games to see how well the computer's strategy works.

The strategy is pretty good, but not perfect. Most of the time when the user plays first, the computer should be able to force at least a draw. When the user does not select the center cell, the computer should always make its first move there. One case where the strategy fails is when the user plays in one corner, the computer responds in the center, and the user then selects the opposite corner. Using a short-sighted greedy strategy, the computer will play in one of the remaining corners. A smart user can then win the game.

When the computer plays first, it will always make its play in the center. If the user selects one of the side cells, the computer should be able to win the game most of the time.

Some of the failings of the strategy could be addressed by adjusting the number of votes cast by lines. However, the short-sightedness is inherent and cannot be easily fixed.

Demonstration Applet