Java开发井字棋:Oval被Rectangle遮挡及对象颜色修改问题
Hey Carter, let's fix those two frustrating issues you're facing with your Tic Tac Toe game—we'll break them down one by one:
1. Fixing the Oval Being Covered by Rectangles
The core problem here is how Swing handles component rendering order when you add multiple elements to a container. If you stick with your current component-based setup, the quick fix is to manually adjust the z-order of your Oval components after adding them to the panel. Swing draws components from the bottom of the stack to the top, so placing your Ovals at the front ensures they render above the Rectangles.
If you want a cleaner, more maintainable solution though, it's better to use a single custom panel to draw both the board and markers. This way you fully control the drawing order: draw the board first, then the markers on top—no more hiding issues.
2. Simplifying Color Modification for Shapes
To easily update the color of your Rectangle and Oval components, add a color setter method to each custom class, paired with an instance variable to store the current color. This lets you change colors on the fly and triggers a repaint to update the visuals immediately.
Quick Fix for Your Existing Code
Adjust Z-Order for Ovals
Update your userInput method to force Ovals to render on top:
static void userInput(){ int uImpCalc = 1; Scanner s = new Scanner(System.in); while(uImpCalc <= 9){ int x = s.nextInt(); int y = s.nextInt(); if(x <= 3 && x > 0){ if(y <= 3 && y >0){ int rX = (x-1)*80; // Fixed to 0-based index for correct positioning int rY = (y-1)*80; if(uImpCalc%2 == 0){ player1[p1Count][p1Count] = new Oval(rX, rY, 80, 80, Color.BLUE); win.add(player1[p1Count][p1Count]); // Move oval to the front of the rendering stack win.setComponentZOrder(player1[p1Count][p1Count], 0); p1Count++; }else{ player2[p2Count][p2Count] = new Oval(rX, rY, 80, 80, Color.RED); win.add(player2[p2Count][p2Count]); win.setComponentZOrder(player2[p2Count][p2Count], 0); p2Count++; } win.repaint(); System.out.println("Entering '"+x+", "+y+"'"); uImpCalc++; }else{ System.out.println("Invalid Y: "+y); } }else{ System.out.println("Invalid X: "+x); } } System.out.println("Game Over"); System.exit(0); }
Add Color Setters to Your Shape Classes
Modify your Rectangle and Oval classes to include these methods (example for Oval):
import javax.swing.*; import java.awt.*; public class Oval extends JComponent { private Color shapeColor; private int x, y, width, height; public Oval(int x, int y, int width, int height, Color color) { this.x = x; this.y = y; this.width = width; this.height = height; this.shapeColor = color; setBounds(x, y, width, height); } // Setter for easy color changes public void setShapeColor(Color newColor) { this.shapeColor = newColor; repaint(); // Refresh the component with the new color } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(shapeColor); g.drawOval(5, 5, getWidth()-10, getHeight()-10); // Add padding for better visuals } }
Recommended: Refactored Code (Cleaner & More Maintainable)
Using a single custom panel to draw everything eliminates z-order issues entirely and simplifies state management. Here's a polished version:
import javax.swing.*; import java.awt.*; import java.util.Scanner; public class GameBoard { static JFrame f = new JFrame("Tic Tac Toe"); static GamePanel gamePanel = new GamePanel(); // Track game state: 0 = empty, 1 = Player 1 (X), 2 = Player 2 (O) static int[][] boardState = new int[3][3]; public static void main(String args[]) { f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(gamePanel); f.pack(); f.setLocationRelativeTo(null); // Center the window f.setVisible(true); userInput(); } static void userInput() { int moveCount = 0; Scanner s = new Scanner(System.in); while (moveCount < 9) { System.out.println("Enter X (1-3) and Y (1-3):"); int x = s.nextInt() - 1; // Convert to 0-based index int y = s.nextInt() - 1; if (x >= 0 && x < 3 && y >= 0 && y < 3) { if (boardState[y][x] == 0) { boardState[y][x] = (moveCount % 2 == 0) ? 1 : 2; gamePanel.repaint(); // Refresh the board moveCount++; System.out.println("Placed marker at (" + (x+1) + ", " + (y+1) + ")"); } else { System.out.println("That cell is already taken!"); } } else { System.out.println("Invalid coordinates—use 1-3 for both X and Y."); } } System.out.println("Game Over!"); System.exit(0); } // Custom panel to draw the entire game static class GamePanel extends JPanel { private final int CELL_SIZE = 80; // Easily modify colors here private final Color CELL_COLOR_1 = Color.LIGHT_GRAY; private final Color CELL_COLOR_2 = Color.WHITE; private final Color PLAYER1_COLOR = Color.BLUE; private final Color PLAYER2_COLOR = Color.RED; @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // Draw the board first for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { // Alternate cell colors g.setColor((row + col) % 2 == 0 ? CELL_COLOR_1 : CELL_COLOR_2); g.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE); // Draw cell borders g.setColor(Color.BLACK); g.drawRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1); } } // Draw player markers on top for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { int player = boardState[row][col]; if (player == 1) { // Draw X for Player 1 g.setColor(PLAYER1_COLOR); int offset = 10; g.drawLine(col * CELL_SIZE + offset, row * CELL_SIZE + offset, col * CELL_SIZE + CELL_SIZE - offset, row * CELL_SIZE + CELL_SIZE - offset); g.drawLine(col * CELL_SIZE + CELL_SIZE - offset, row * CELL_SIZE + offset, col * CELL_SIZE + offset, row * CELL_SIZE + CELL_SIZE - offset); } else if (player == 2) { // Draw O for Player 2 g.setColor(PLAYER2_COLOR); int offset = 10; g.drawOval(col * CELL_SIZE + offset, row * CELL_SIZE + offset, CELL_SIZE - 2 * offset, CELL_SIZE - 2 * offset); } } } } @Override public Dimension getPreferredSize() { return new Dimension(CELL_SIZE * 3, CELL_SIZE * 3); } } }
Key Improvements:
- No more z-order issues: We explicitly draw the board first, then markers on top.
- Easy color changes: Just update the color constants in
GamePanelor add setter methods for dynamic color shifts. - Better state management: Uses a simple 2D array to track moves instead of multiple component arrays.
- Swing-friendly layout: Avoids
nulllayout by usinggetPreferredSize()for proper window sizing.
内容的提问来源于stack exchange,提问作者Carter Smith

