Skip to Content
Course content

225: Building a Tic-Tac-Toe Game with JavaFX

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to almost everyone the first time they build a grid-based game in JavaFX. You get your layout working, your event handlers are firing, and you're feeling great—until you actually play the game and realize your "X" just got overwritten by an "O".

// A typical mistake in the button handler
button.setOnAction(e -> {
    button.setText(currentPlayer); 
    currentPlayer = (currentPlayer.equals("X")) ? "O" : "X";
    checkWin();
});

The Case of the Shifting Symbols

The problem here is that the button remains active. In a real game of Tic-Tac-Toe, once a square is claimed, it's locked. In the snippet above, if I'm player "O" and I'm feeling mischievous, I can just click on player "X"'s square, and because the setOnAction handler is still listening, it will simply change the text to "O". It's a classic case of forgetting that the UI state needs to mirror the game state.

Locking the Board State

The fix is simple, but it's a habit you need to build: disable the component the moment it's used. By calling setDisable(true), you tell JavaFX to ignore all further mouse events for that specific button and visually grey it out (though we'll likely override the styling later).

button.setOnAction(e -> {
    button.setText(currentPlayer); 
    button.setDisable(true); // This is the missing piece
    currentPlayer = (currentPlayer.equals("X")) ? "O" : "X";
    checkWin();
});

Organizing the Grid with GridPane

For this game, you don't want to manually position nine different buttons using absolute coordinates. That's a nightmare to maintain. Instead, use a GridPane. I usually initialize the board using a nested loop to keep the code clean. If you're hard-coding nine buttons, you're doing it the hard way.

I recommend storing your buttons in a 2D array (Button[][] board = new Button[3][3]). This makes your win-checking logic much easier because you can iterate through rows and columns using indices rather than trying to find a specific button object in the scene graph.

Evaluating the Win Condition

The logic for checking a win is where most people get bogged down in "if-statement soup." You could write a massive block of if statements for every possible combination, but I'd suggest a more algorithmic approach. Create a helper method that takes a set of three buttons and checks if they all have the same text and aren't empty.

You'll need to check three rows, three columns, and the two diagonals. Since we're using a 2D array, a simple loop can handle the rows and columns, while the diagonals are a quick manual check. Remember to check for a draw as well—if all buttons are disabled and no one has won, the game is over.

One last tip: keep your game logic separate from your UI code. While it's tempting to put the win-checking logic directly inside the button's setOnAction, it makes the code harder to read. Pass the current board state to a separate GameLogic class or method. It'll save you a lot of headaches when you decide to add a "Score Tracker" or an "AI Opponent" later.




📋 Practical Task

Implement a Game Reset Mechanism

Your current Tic-Tac-Toe game works, but once it's over, the user has to restart the entire application to play again. Your task is to add a "Reset Game" button below the GridPane.

  • Add a Button called resetButton to your layout (use a VBox to wrap the GridPane and the button).
  • Implement an event handler for the reset button that:
    • Loops through the 2D array of buttons and clears the text of every square.
    • Calls setDisable(false) on every button to make them clickable again.
    • Resets the currentPlayer variable back to "X".
  • Ensure the reset button is always enabled, even when the game is over.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.