Step 6


Overview

In this step you will detect the end of the game and display an appropriate message in the message label. You will also disable the "Start New Game" button except when the game is over.

Your code will involve writing a TTTLine class that represents vertical, horizontal, and diagonal lines of cells on the game board. Each will keep track of how many of its cells are owned by X and by O. The class contains a countCells() method to update the X and O counts, and a winner() method that invokes countCells() and then checks the ownership counts to return one of the TTTCell constants NO_ONE, X, O, or DRAW.

A win occurs when one of the lines contains three Xs or three Os. A draw occurs when each line contains both and X and an O. An array instance variable that contains eight TTTLines is declared in the TicTacToe class. Two methods, playerWins() and gameDrawn(), are added to the TicTacToe class to check for the win or draw conditions. These methods are invoked in the updateStatus() method to determine win or draw user messages and control enabling of the "Start New Game" button.

The TTTLine Class

A TTTLine object represents a horizontal, vertical, or diagonal line of cells on the tictactoe board. In this step, you will only define enough of the class to determine when a game has been won or drawn. In the next step, you will add some code to the class to aid in selecting the best move for the computer.

The TTTLine class should be defined in its own .java file. It does not need any imports. The class extends the Object class, so you can omit the extends clause.

This class uses an array instance variable cells to keep track of the cells in a line. It should be declared as follows.

    TTTCell[] cells = new TTTCell[3];
This creates an array that can contain three TTTCell objects. In addition, the class declares two int instance variables xOwns and oOwns. These variables will be used to record the number of cells that are marked with X and O. They do not need to be initialized.

In the declaration of the cells variable, the array itself is initialized, but its entries are not because there is not enough information to do so. Java just places a null value in each of the three cells to indicate that there is no value there yet.

To make a TTTLine useful, you need to put cells into the array. The best place to do that is in the constructor. It should have three TTTCell parameters. The statements in the constructor should just assign the parameters to the three entries of cells, which are cells[0], cells[1], and cells[2].

There are two methods defined in the TTTLine class. The first, countCells(), has no parameters and void return type. It is only used inside the TTTLine class, so you do not need to declare the method as public. The method counts the number of cells in the line that are marked by X and O and records the counts in xOwns and oOwns. At the beginning of the method, both of these counts should be initialized to 0. Then loop through the cells in the line and increment the appropriate count, depending on the mark in the cell. This can be done with a for loop with the following structure.

    for (int i = 0; i < cells.length; i++; i++) {
	TTTCell c = cells[i];
	statements to increment the appropriate count for c
    }
In order to increment the appropriate count, you will need if statements that test whether c.getMark() is TTTCell.X or TTTCell.O.

The second method, winner(), has no parameters. It returns one of the int constants TTTCell.X, TTTCell.O, TTTCell.NO_ONE, or TTTCell.DRAW, indicating who has won the line. Since this method is invoked by the TicTacToe class, it must be declared to be public.

The first statement of the method should invoke countCells() to set up the xOwns and oOwns counts. Then you need if statements to implement return values as indicated in the following table.
Situation Return
xOwns == 3 TTTCell.X
oOwns == 3 TTTCell.O
xOwns > 0 and oOwns > 0 TTTCell.DRAW
all other cases TTTCell.NO_ONE
You can use if statements that contain return statements for the first three situations, followed by a simple return statement to handle all other cases. This statement will only be executed when none of the if statements has caused the method to return.

After completing the TTTLine class, you should compile it to check for syntax errors. You will need to wait until changes are made in the TicTacToe class are made to check for errors in logic.

Adding an Array of TTTLines to the TicTacToe Class

First, declare an instance variable named lines to the class. Its type is TTTLine[] indicating an array of TTTLines. It should be initialized to null. Its entries cannot be created until the cells are created in the setupBoard() method.

To simplify setting up the entries in lines, you should write a short method getCell(). Like the method of the same name that you have already written, this method returns a TTTCell. The difference is that this method has two int parameters, r and c, that specify a row and a column in the tictactoe board. The method should just invoke the original getCell() method, returning getCell(3*r + c).

Then in the setupBoard() method, assign a new TTTLines array, with initialization, to lines. You should have three lines for rows of the game board, three for columns, and two for diagonals. In the calls to the TTTLine constructor you will need to specify three cells using getCell(). Take care when specifying the rows and columns for the cells.

TicTacToe Class Methods for Detecting the End of a Game

The end of a game is detected with two methods playerWins() and gameDrawn(). Both have boolean return type. Both use the following loop structure to check the TTTLines.
    for (int i = 0; i < lines.length; i++) {
	TTTLine line = lines[i];
	if statement that returns a boolean value
    }
    default return statement

The playerWins() has an int parameter plyr for specifying a player (TTCell.X or TTTCell.O). It checks a line by sending it a winner() message. It returns true if the return value from winner() is equal to plyr. The default return statement is executed only when winner() returns something other than plyr for all lines. It should just return false.

The gameDrawn() method has no parameters. If any of the winner() returned values are not TTCell.DRAW then the method returns false. In the default case it returns true.

Updating the Status in the TicTacToe Class

The remaining changes in this step are added at the beginning of the updateStatus() method. You should first add a local variable gameOver. It should have boolean type and should be initialized to false.

To detect the end of a game, you then need three if statements. The boolean conditions in the first two just invoke playerWins() with parameters myMark and userMark respectively. The boolean condition in the third just invokes gameDrawn(). Each if statement contains two nested statements, one to set the user message and one to assign true to gameOver. The user message is set by sending a setText() message to the message variable. The parameter should be one of the following.

Finally, if the gameOver local variable is true, you should exchange marks for the myMark and userMark instance variables and assign TTTCell.NO_ONE to the toPlay variable. You should also add a statement to enable the "Start New Game" button if gameOver is true and disable the button otherwise.

After you have added code to updateStatus(), recompile your TicTacToe class and run the applet with appletviewer. Your applet should look and behave like the following demonstration applet. You should play games where you win, lose, and draw, watching for the appropriate messages and chacking the enabling of the "Start New Game" button. Since the computer does not yet have a good play strategy, it will be easy to win a game. You will have to make dumb moves to lose or draw. The computer will always select its move in the lowest possible row. Within that row, it will select the rightmost cell.

Demonstration Applet