Case Study: Developing a Tic-Tac-Toe Game
Tags: java, programming, learning, beginners
Published: 2024-06-27 20:03:37

From the many examples in this and earlier chapters you have learned about objects, classes, arrays, class inheritance, GUI, and event-driven programming. Now it is time to put what you have learned to work in developing comprehensive projects. In this section, we will develop a JavaFX program with which to play the popular game of tic-tac-toe.

Two players take turns marking an available cell in a 3 * 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 below shows the representative sample runs of the game.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnprxa9ti21nm4qm7zyx.png)

All the examples you have seen so far show simple behaviors that are easy to model with classes. The behavior of the tic-tac-toe game is somewhat more complex. To define 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 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. There are many choices for this object. We will use a pane to model a cell and to display a token (X or O). How do you know the state of the cell (empty, X, or O)? You use a property named **token** of the **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 mouse-clicked action and for painting the shapes for tokens X and O. The **Cell** class can be defined as shown in Figure below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lc27r54h1qyjd3ecd2u7.png)

The tic-tac-toe board consists of nine cells, created using **new Cell[3][3]**. To determine which player’s turn it is, you can introduce a variable named **whoseTurn** of the **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? You can define 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; 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 below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8honmnritthrulkav8d.png)

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 the code below.

```
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Ellipse;

public class TicTacToe extends Application {
	// Indicate which player has a turn, initially it is the X player
	private char whoseTurn = 'X';
	
	// Create and initialize cell
	private Cell[][] cell = new Cell[3][3];
	
	// Create and initialize a status label
	private Label lblStatus = new Label("X's turn to play");
	
	@Override // Override the start method in the Application class
	public void start(Stage primaryStage) {
		// Pane to hold cell
		GridPane pane = new GridPane();
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
				pane.add(cell[i][j] = new Cell(), i, j);
		
		BorderPane borderPane = new BorderPane();
		borderPane.setCenter(pane);
		borderPane.setBottom(lblStatus);
		
		// Create a scene and place it in the stage
		Scene scene = new Scene(borderPane, 450, 170);
		primaryStage.setTitle("TicTacToe"); // Set the stage title
		primaryStage.setScene(scene); // Place the scene in the stage
		primaryStage.show(); // Display the stage
	}
	
	public static void main(String[] args) {
		Application.launch(args);
	}
	
	/** Determine if the cell are all occupied */
	public boolean isFull() {
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
				if(cell[i][j].getToken() == ' ')
					return false;
		
		return true;
	}
	
	/** Determine if the player with the specified token wins */
	public boolean isWon(char token) {
		for(int i = 0; i < 3; i++)
			if(cell[i][0].getToken() == token && cell[i][1].getToken() == token && cell[i][2].getToken() == token) {
				return true;
			}
		
		for(int j = 0; j < 3; j++)
			if(cell[0][j].getToken() == token && cell[1][j].getToken() == token && cell[2][j].getToken() == token) {
				return true;
			}
		
		if(cell[0][0].getToken() == token && cell[1][1].getToken() == token && cell[2][2].getToken() == token) {
			return true;
		}
		
		if(cell[0][2].getToken() == token && cell[1][1].getToken() == token && cell[2][0].getToken() == token) {
			return true;
		}
		
		return false;
	}
	
	// An inner class for a cell
	public class Cell extends Pane{
		// Token used for this cell
		private char token = ' ';
		
		public Cell() {
			setStyle("-fx-border-color: black");
			this.setPrefSize(2000, 2000);
			this.setOnMouseClicked(e -> handleMouseClick());
		}
		
		/** Return token */
		public char getToken() {
			return token;
		}
		
		/** Set a new token */
		public void setToken(char c) {
			token = c;
			
			if(token == 'X') {
				Line line1 = new Line(10, 10, this.getWidth() - 10, this.getHeight() - 10);
				line1.endXProperty().bind(this.widthProperty().subtract(10));
				line1.endYProperty().bind(this.heightProperty().subtract(10));
				Line line2 = new Line(10, this.getHeight() - 10, this.getWidth() - 10, 10);
				line2.startYProperty().bind(this.heightProperty().subtract(10));
				line2.endXProperty().bind(this.widthProperty().subtract(10));
				
				// Add the lines to the pane
				this.getChildren().addAll(line1, line2);
			}
			else if(token == 'O') {
				Ellipse ellipse = new Ellipse(this.getWidth() / 2, this.getHeight() / 2, this.getWidth() / 2 - 10, this.getHeight() / 2 - 10);
				ellipse.centerXProperty().bind(this.widthProperty().divide(2));
				ellipse.centerYProperty().bind(this.heightProperty().divide(2));
				ellipse.radiusXProperty().bind(this.widthProperty().divide(2).subtract(10));
				ellipse.radiusYProperty().bind(this.heightProperty().divide(2).subtract(10));
				ellipse.setStroke(Color.BLACK);
				ellipse.setFill(Color.WHITE);
				
				getChildren().add(ellipse); // Add the ellipse to the pane
			}
		}
		
		/** Handle a mouse click event */
		private void handleMouseClick() {
			// If cell is empty and game is not over
			if(token == ' ' && whoseTurn != ' ') {
				setToken(whoseTurn); // Set token in the cell
				
				// Check game status
				if(isWon(whoseTurn)) {
					lblStatus.setText(whoseTurn + " won! The game is over");
					whoseTurn = ' '; // Game is over
				}
				else if(isFull()) {
					lblStatus.setText("Draw! he game is over");
					whoseTurn = ' '; // Game is over
				}
				else {
					// Change the turn
					whoseTurn = (whoseTurn == 'X') ? 'O' : 'X';
					// Display whose turn
					lblStatus.setText(whoseTurn + "'s turn");
				}
			}
		}
	}
}

```

The **TicTacToe** class initializes the user interface with nine cells placed in a grid pane (lines 26–29). A label named **lblStatus** is used to show the status of the game (line 21). The variable **whoseTurn** (line 15) is used to track the next type of token to be placed in a cell. The methods **isFull** (lines 47–54) and **isWon** (lines 57–77) 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 defined 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**.

The listener for the mouse-click action is registered for the cell (line 87). If an empty cell is clicked and the game is not over, a token is set in the cell (line 126). If the game is over, **whoseTurn** is set to **' '** (lines 132, 136). Otherwise, **whoseTurn** is alternated to a new turn (line 140).