Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
225: Building a Tic-Tac-Toe Game with JavaFX
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
ButtoncalledresetButtonto your layout (use aVBoxto wrap theGridPaneand 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
currentPlayervariable back to "X".
- Ensure the reset button is always enabled, even when the game is over.
There are no comments for now.