Search code examples
javaswingjpanelgrid-layoutpaintcomponent

Grid of jpanels (for connect four game) - paintComponent only works for top left panel


I'm trying to make a simple connect four GUI by using a grid of JPanels each of which paints a colored disk when the button below them is pressed and the panels under it are full. Before adding the game rules I'm trying to just make sure the buttons and display work properly. But it is not working - only the top left panel displays a disk (after pressing button 1 6 times). here is my code:

public class ConnectFourFrame extends JFrame {

private final JPanel gamePanelsPanel; // panel to hold the game panels
private final GamePanel[][] gamePanels; // a 2D array to hold the grid of panels to display the game disks
private final JPanel buttonsPanel; // panel to hold the buttons panels
private final JPanel gameButtonsPanel; // panel to hold the game buttons to add disk to a column
private final JButton[] gameButtons; // an array to hold the game buttons
private final JPanel clearButtonPanel; // panel to hold the clear button
private final JButton clearButton; // button to clear the game grid from disks

private enum Turn {RED_PLAYER, BLUE_PLAYER}; // keeps track of which players turn it is 
private Turn turn;


// no argument constructor
public ConnectFourFrame() {

    super("Connect Four");
    this.setLayout(new BorderLayout());

    //add panels to hold the game panel and the buttons
    gamePanelsPanel = new JPanel();
    add(gamePanelsPanel, BorderLayout.CENTER);  
    buttonsPanel = new JPanel();
    buttonsPanel.setLayout(new BorderLayout());
    add(buttonsPanel, BorderLayout.SOUTH);

    //set up game panels
    gamePanelsPanel.setLayout(new GridLayout(6,7,3,3));
    gamePanelsPanel.setBackground(Color.BLACK);
    gamePanels = new GamePanel[6][7];
    for (int i = 0; i < 6; i++) {
        for (int j = 0; j < 7; j++) {
            gamePanels[i][j] = new GamePanel(false, Color.WHITE);
            gamePanelsPanel.add(gamePanels[i][j]);
        }
    }


    //set up game and clear buttons
    gameButtonsPanel = new JPanel();
    gameButtonsPanel.setLayout(new GridLayout(1,7));
    clearButtonPanel = new JPanel();

    gameButtons = new JButton[7];
    for (int i = 0; i < 7; i++) {
        gameButtons[i] = new JButton("" + (i+1));
        gameButtonsPanel.add(gameButtons[i]);
    }

    clearButton = new JButton("CLEAR");
    clearButtonPanel.add(clearButton);

    buttonsPanel.add(gameButtonsPanel, BorderLayout.NORTH);
    buttonsPanel.add(clearButtonPanel, BorderLayout.SOUTH);
    add(buttonsPanel, BorderLayout.SOUTH);

    // register event handlers
    ClearButtonHandler clearButtonHandler = new ClearButtonHandler();
    clearButton.addActionListener(clearButtonHandler);
    GameButtonHandler gameButtonHandler = new GameButtonHandler();
    for (int i = 0; i < 7; i++) {
        gameButtons[i].addActionListener(gameButtonHandler);
    }

    turn = Turn.RED_PLAYER; //set first turn to player1

}

// inner class for game button event handling
private class GameButtonHandler implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        // get the number of the pressed button
        int pressedButtonNum = Integer.parseInt(((JButton) e.getSource()).getActionCommand());
        // display disk in top empty panel of the column 
        for (int i = 5; i >= 0; i--) {
            if (!gamePanels[i][pressedButtonNum - 1].isFull()) {
                if (turn == Turn.RED_PLAYER) {
                    gamePanels[i][pressedButtonNum - 1].setDiskColor(Color.RED);
                    turn = Turn.BLUE_PLAYER;
                }
                else {
                    gamePanels[i][pressedButtonNum - 1].setDiskColor(Color.BLUE);
                    turn = Turn.RED_PLAYER;
                }
                gamePanels[i][pressedButtonNum - 1].setFull(true);
                gamePanels[i][pressedButtonNum - 1].repaint();
                return;
            }
        }
            // if column is full display message to try again
            JOptionPane.showMessageDialog(gamePanelsPanel, "Column " + pressedButtonNum + " is full. Try again.");
    }

}

public class GamePanel extends JPanel{

private boolean isFull; // true if the panel has a disk in it. default is empty (false).
private Color diskColor; //color of disks. default is white (same as background)


public GamePanel(boolean isFull, Color diskColor) {
    super();
    this.isFull = isFull;
    this.diskColor = diskColor;
}

public Color getDiskColor() {
    return diskColor;
}

public void setDiskColor(Color diskColor) {
    this.diskColor = diskColor;
}

public boolean isFull() {
    return isFull;
}

public void setFull(boolean isFull) {
    this.isFull = isFull;
}


@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    this.setBackground(Color.WHITE);

    g.setColor(diskColor);
    g.fillOval(this.getX() + this.getWidth()/4 , this.getY() + this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
}

}


Solution

  • The problem is right here...

    g.fillOval(this.getX() + this.getWidth()/4 , this.getY() + this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
    

    The Graphics context passed to your paintComponent method has already been translated by the components x/y position, meaning that the top/left corner of the component is always 0x0

    g.fillOval(this.getWidth()/4 , this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
    

    will probably work better

    Also, calling this.setBackground(Color.WHITE); inside paintComponent is unadvisable, as it will setup a situation where by a new paint cycle will be scheduled, over and over again. Don't change the state of the UI from within a paint method