Java井字棋获胜判定代码去重优化咨询(新手求助)
Hey there! Great job getting your Tic-Tac-Toe project up and running on Hyperskill—handling the win condition is one of the trickier parts when you're starting out, so it's smart to look for ways to clean up that repetitive code. Let's break down how to optimize this and go over some Java best practices along the way.
1. Extract Repeated Logic into a Helper Method
The biggest issue with your current code is that you're repeating the same "count X/O and check for 3 in a row" logic four separate times (horizontal, vertical, two diagonals). The fix here is to pull that common logic into a reusable helper method.
Here's a simple helper that checks if three specific board positions are all the same non-empty player:
// Helper method to check if three positions form a winning line private String checkWinningLine(int x1, int y1, int x2, int y2, int x3, int y3) { String spot1 = this.field[x1][y1]; String spot2 = this.field[x2][y2]; String spot3 = this.field[x3][y3]; // Check if all three spots match and aren't empty if (spot1.equals(spot2) && spot2.equals(spot3) && !spot1.isEmpty()) { return spot1 + " wins"; } return null; // No winner here }
Now you can rewrite your main determineWinner method to use this helper instead of repeating code:
public String determineWinner() { // Check all horizontal lines for (int y = 0; y <= 2; y++) { String result = checkWinningLine(0, y, 1, y, 2, y); if (result != null) { return result; } } // Check all vertical lines for (int x = 0; x <= 2; x++) { String result = checkWinningLine(x, 0, x, 1, x, 2); if (result != null) { return result; } } // Check both diagonals String diagonal1 = checkWinningLine(0, 0, 1, 1, 2, 2); if (diagonal1 != null) { return diagonal1; } String diagonal2 = checkWinningLine(0, 2, 1, 1, 2, 0); if (diagonal2 != null) { return diagonal2; } // Check for a draw if (this.getNumberOfMoves() == 9) { return "draw"; } return "game not finished"; }
This cuts out all the redundant counting and condition checks, making your code shorter and easier to update later (if you ever wanted to adjust win logic, you only change the helper method).
2. Even Cleaner: Batch Check All Winning Lines
If you want to make the code even more concise, you can define all possible winning lines as a list of coordinate sets, then loop through that list to check each line:
import java.util.Arrays; import java.util.List; public String determineWinner() { // Define every possible winning line (each line is 3 (x,y) pairs) List<int[][]> winningLines = Arrays.asList( // Horizontal lines new int[][]{{0,0}, {1,0}, {2,0}}, new int[][]{{0,1}, {1,1}, {2,1}}, new int[][]{{0,2}, {1,2}, {2,2}}, // Vertical lines new int[][]{{0,0}, {0,1}, {0,2}}, new int[][]{{1,0}, {1,1}, {1,2}}, new int[][]{{2,0}, {2,1}, {2,2}}, // Diagonals new int[][]{{0,0}, {1,1}, {2,2}}, new int[][]{{0,2}, {1,1}, {2,0}} ); // Check each line for a winner for (int[][] line : winningLines) { String spot1 = this.field[line[0][0]][line[0][1]]; String spot2 = this.field[line[1][0]][line[1][1]]; String spot3 = this.field[line[2][0]][line[2][1]]; if (spot1.equals(spot2) && spot2.equals(spot3) && !spot1.isEmpty()) { return spot1 + " wins"; } } // Draw or unfinished game logic if (this.getNumberOfMoves() == 9) { return "draw"; } return "game not finished"; }
This approach puts all winning lines in one place, making it super clear what you're checking, and keeps the rest of the logic tight.
3. Java Coding Best Practices to Apply
Beyond the win check, here are some general Java habits that will make your code cleaner and more maintainable:
Use constants for magic strings: Instead of typing
"X"or"O"everywhere, define static constants. This prevents typos and makes it easy to update values later:private static final String PLAYER_X = "X"; private static final String PLAYER_O = "O"; private static final String RESULT_DRAW = "draw"; private static final String RESULT_NOT_FINISHED = "game not finished";Then replace all direct string uses with these constants.
Stick to camelCase naming: Variables like
countXshould bexCount—this follows Java's standard naming convention (lowercase start, capitalized words for multi-names).Limit variable scope: In your original code, you declared
countXandcountOat the top of the method, then reinitialized them in loops. It's better to declare variables inside the loop where they're used (like we did withspot1/spot2/spot3in the helper) to avoid confusion about their values.Add null checks (if needed): If your board can have
nullvalues (instead of empty strings), add a null check before comparing:spot1 != null && spot1.equals(spot2). This preventsNullPointerExceptions.Follow the Single Responsibility Principle: Your
determineWinnermethod already does one job (check the game state), which is perfect. As your project grows, keep splitting code into small, focused methods instead of making one giant method do everything.
4. Bonus: Scalable Board Idea
If you ever wanted to expand to a larger board (like 4x4 Tic-Tac-Toe), you could define the board size as a constant and generate winning lines dynamically instead of hardcoding them. For a 3x3 game, this isn't necessary, but it's a fun way to think about making your code flexible!
内容的提问来源于stack exchange,提问作者ReefG

