16.7. (Optional) Case Study: TicTacToe |
You have learned about objects, classes, arrays, class inheritance, GUI, event-driven programming, and applets from the many examples in this chapter and the preceding chapters. Now it is time to put what you have learned to work in developing comprehensive projects. In this section, you will develop a Java applet with which to play the popular game of TicTacToe.
In a game of TicTacToe, two players take turns marking an available cell in a 3 x 3 grid with their respective tokens (either X or O). When one player has placed three tokens in a horizontal, vertical, or diagonal row on the grid, the game is over and that player has won. A draw (no winner) occurs when all the cells on the grid have been filled with tokens and neither player has achieved a win. Figure 16.10 shows two representative sample runs of the example.
All the examples you have seen so far show simple behaviors that are easy to model with classes. The behavior of the TicTacToe game is somewhat more complex. To create classes that model the behavior, you need to study and understand the game.
Assume that all the cells are initially empty, and that the first player takes the X token, and the second player takes the O token. To mark a cell, the player points the mouse to the cell and clicks it. If the cell is empty, the token (X or O) is displayed. If the cell is already filled, the player's action is ignored.
From the preceding description, it is obvious that a cell is a GUI object that handles the mouse-click event and displays tokens. Such an object could be either a button or a panel. Drawing on panels is more flexible than on buttons , because the token (X or O) can be drawn on a panel in any size , but on a button it can only be displayed as a text label. Therefore, a panel should be used to model a cell. How do you know the state of the cell (empty, X, or O)? You use a property named token of char type in the Cell class. The Cell class is responsible for drawing the token when an empty cell is clicked. So you need to write the code for listening to the MouseEvent and for painting the shapes for tokens X and O. The Cell class can be defined as shown in Figure 16.11.
The TicTacToe board consists of nine cells, declared using new Cell[3][3] . To determine which player's turn it is, you can introduce a variable named whoseTurn of char type. whoseTurn is initially X, then changes to O, and subsequently changes between X and O whenever a new cell is occupied. When the game is over, set whoseTurn to ' ' .
How do you know whether the game is over, whether there is a winner, and who the winner, if any, is? You can create a method named isWon(char token) to check whether a specified token has won and a method named isFull() to check whether all the cells are occupied.
Clearly, two classes emerge from the foregoing analysis. One is the Cell class, which handles operations for a single cell; and the other is the TicTacToe class, which plays the whole game and deals with all the cells. The relationship between these two classes is shown in Figure 16.12.
Since the Cell class is only to support the TicTacToe class, it can be defined as an inner class in TicTacToe . The complete program is given in Listing 16.7.
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 import javax.swing.border.LineBorder; 5 6 public class TicTacToe extends JApplet { 7 // Indicate which player has a turn, initially it is the X player 8 private char whoseTurn = 'X' ; 9 10 // Create and initialize cells 11 private Cell[][] cells = new Cell[ 3 ][ 3 ]; 12 13 // Create and initialize a status label 14 private JLabel jlblStatus = new JLabel( "X's turn to play" ); 15 16 /** Initialize UI */ 17 public TicTacToe() { 18 // Panel p to hold cells 19 JPanel p = new JPanel( new GridLayout( 3 , 3 , , )); 20 for ( int i = ; i < 3 ; i++) 21 for ( int j = ; j < 3 ; j++) 22 p.add(cells[i][j] = new Cell()); 23 24 // Set line borders on the cells panel and the status label 25 p.setBorder( new LineBorder(Color.red, 1 )); 26 jlblStatus.setBorder( new LineBorder(Color.yellow, 1 )); 27 28 // Place the panel and the label to the applet 29 add(p, BorderLayout.CENTER); 30 add(jlblStatus, BorderLayout.SOUTH); 31 } 32 33 /** Determine if the cells are all occupied */ 34 public boolean isFull() { 35 for ( int i = ; i < 3 ; i++) 36 for ( int j = ; j < 3 ; j++) 37 if (cells[i][j].getToken() == ' ' ) 38 return false ; 39 40 return true ; 41 } 42 43 /** Determine if the player with the specified token wins */ 44 public boolean isWon( char token) { 45 for ( int i = ; i < 3 ; i++) 46 if ((cells[i][ ].getToken() == token) 47 && (cells[i][ 1 ].getToken() == token) 48 && (cells[i][ 2 ].getToken() == token)) { 49 return true ; 50 } 51 52 for ( int j = ; j < 3 ; j++) 53 if ((cells[ ][j].getToken() == token) 54 && (cells[ 1 ][j].getToken() == token) 55 && (cells[ 2 ][j].getToken() == token)) { 56 return true ; 57 } 58 59 if ((cells[ ][ ].getToken() == token) 60 && (cells[ 1 ][ 1 ].getToken() == token) 61 && (cells[ 2 ][ 2 ].getToken() == token)) { 62 return true ; 63 } 64 65 if ((cells[ ][ 2 ].getToken() == token) 66 && (cells[ 1 ][ 1 ].getToken() == token) 67 && (cells[ 2 ][ ].getToken() == token)) { 68 return true ; 69 } 70 71 return false ; 72 } 73 74 // An inner class for a cell 75 public class Cell extends JPanel { 76 // Token used for this cell 77 private char token = ' ' ; 78 79 public Cell() { 80 setBorder( new LineBorder(Color.black, 1 )); // Set cell's border 81 addMouseListener( new MouseListener()); // Register listener 82 } 83 84 /** Return token */ 85 public char getToken() { 86 return token; 87 } 88 89 /** Set a new token */ 90 public void setToken( char c) { 91 token = c; 92 repaint(); 93 } 94 95 /** Paint the cell */ 96 protected voi d paintComponent(Graphics g) { 97 super .paintComponent(g); 98 99 if (token == 'X' ) { 100 g.drawLine( 10 , 10 , getWidth() - 10 , getHeight() - 10 ); 101 g.drawLine(getWidth() - 10 , 10 , 10 , getHeight() - 10 ); 102 } 103 else if (token == 'O' ) { 104 g.drawOval( 10 , 10 , getWidth() - 20 , getHeight() - 20 ); 105 } 106 } 107 108 private class MouseListener extends MouseAdapter { 109 /** Handle mouse click on a cell */ 110 public void mouseClicked(MouseEvent e) { 111 // If cell is empty and game is not over 112 if (token == ' ' && whoseTurn != ' ' ) { 113 setToken(whoseTurn); // Set token in the cell 114 115 // Check game status 116 if (isWon(whoseTurn)) { 117 jlblStatus.setText(whoseTurn + " won! The game is over" ); 118 whoseTurn = ' ' ; // Game is over 119 } 120 else if (isFull()) { 121 jlblStatus.setText( "Draw! The game is over" ); 122 whoseTurn = ' ' ; // Game is over 123 } 124 else { 125 whoseTurn = (whoseTurn == 'X' ) ? 'O' : 'X' ; 126 jlblStatus.setText(whoseTurn + "'s turn" ); 127 } 128 } 129 } 130 } 131 } 132 } |
The TicTacToe class initializes the user interface with nine cells placed in a panel of GridLayout (lines 19 “22). A label named jlblStatus is used to show the status of the game (line 14). The variable whoseTurn (line 8) is used to track the next type of token to be placed in a cell. The methods isFull (lines 34 “41) and isWon (lines 44 “72) are for checking the status of the game.
Since Cell is an inner class in TicTacToe , the variable ( whoseTurn ) and methods ( isFull and isWon ) defined in TicTacToe can be referenced from the Cell class. The inner class makes programs simple and concise . If Cell were not declared as an inner class of TicTacToe , you would have to pass an object of TicTacToe to Cell in order for the variables and methods in TicTacToe to be used in Cell . You will rewrite the program without using an inner class in Exercise 16.6.
The listener for MouseEvent is registered for the cell (line 81). If an empty cell is clicked and the game is not over, a token is set in the cell (line 113). If the game is over, whoseTurn is set to ' ' (lines 118, 122). Otherwise, whoseTurn is alternated to a new turn (line 125).
Tip
Use an incremental approach in developing and testing a Java project of this kind. The foregoing program can be divided into five steps.
|