user3369920
user3369920

Reputation: 83

JPanel creates twice

I am trying to make a GUI for a battleship game. One class is for creating the GUI itself and a second class is on top of it to manage the board in the game. My problem is that the JPanel creates twice once a mouse click happens (the mouse click is supposed to be where one is firing in the game and then marks that as a hit/miss). I'm not sure why it is creating twice. Is it because of the passing of a panel? Code below and a photo of what the code generates.

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class BattleshipApplet extends JApplet implements MouseListener {
    private final JButton playButton = new JButton("Play");
    private final JLabel msgBar = new JLabel("Click Play to start game");
    private BoardPanel panel;



    public BattleshipApplet(){
        playButton.addActionListener(this::playButtonClicked);  
        addMouseListener(this);
    }

    public void init(){
        configureGui();
    }

    private void configureGui(){
        setLayout(new BorderLayout());
        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
        buttons.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
        buttons.add(playButton);
        add(buttons, BorderLayout.NORTH);
        msgBar.setBorder(BorderFactory.createEmptyBorder(10,10,5,5));
        add(createBoardPanel(), BorderLayout.CENTER);
        add(msgBar, BorderLayout.SOUTH);
    }

    private BoardPanel createBoardPanel(){
        panel = new BoardPanel();
        return panel;
    }

    private void displayMessage(String msg){
        msgBar.setText(msg);
    }

    private void playButtonClicked(ActionEvent event){
        displayMessage("Play button clicked!");
    }

    public void mouseClicked(MouseEvent e) {
        panel.mouseClickedAt(e.getX(), e.getY());
        e.consume();
    }


    public void mouseEntered(MouseEvent e) {
    }


    public void mouseExited(MouseEvent e) {
    }  
    public void mousePressed(MouseEvent e) {
    }
    public void mouseReleased(MouseEvent e) {
    }   
}

The board class using JPanel

[![import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;


public class BoardPanel extends JPanel  {
    int mx, my;
    boolean rect1Clicked;
    //gamePlay a;
    public void init(){

        rect1Clicked = false;

    }

    /***Your applet shall show the status of the board before and after
     each shot, including the number of shots made and the status of
     each place (no shot or hit/miss shot). ***/
    public void paint(Graphics g){
        boolean miss = false;


        for (int i=0; i<11; i++){ 
            g.setColor(Color.blue);
            g.drawLine(20,20+i*28, 300, 20+i*28);   
        }
        for (int i=0; i<11; i++)   
            g.drawLine(20+i*28,20,20+i*28,300);

        //if inside board 
        if(rect1Clicked == true){
            g.setColor(Color.green);
            //aligns to square to check in computer board for hit/miss
            int bx =(my-20)/28;
            int by =(mx-20)/28;



            //check hit on board
            //if shot was a miss
            if(miss == true ){
                //update to white
                g.setColor(Color.white);
            }
            //if shot was a hit
            if(miss == false){
                //update to red
                g.setColor(Color.red);
            }
            //compare to line for fill
            int fillx = mx/2;
            int filly = my/2 ;
            if(mx<=47){
                fillx = 20;
            }
            if(mx>47 && mx<=75){
                fillx = 48;
            }
            if(mx>75 && mx<=103){
                fillx = 76;
            }
            if(mx>103 && mx <=131){
                fillx = 104;
            }
            if(mx>131 && mx<=159){
                fillx = 132;
            }
            if(mx>159 && mx<=187){
                fillx = 160;
            }
            if(mx>187 && mx <=215){
                fillx = 188;
            }
            if(mx>215 && mx <=243){
                fillx = 216;
            }
            if(mx>243 && mx <=271){
                fillx = 244;
            }
            if(mx>271 && mx<=299){
                fillx = 272;
            }
            if(mx>299){
                fillx = 300;
            }
            //y comparisons
            if(my<=47){
                filly = 20;
            }
            if(my>47 && my<=75){
                filly = 48;
            }
            if(my>75 && my<=103){
                filly = 76;
            }
            if(my>103 && my <=131){
                filly = 104;
            }
            if(my>131 && my<=159){
                filly = 132;
            }
            if(my>159 && my<=187){
                filly = 160;
            }
            if(my>187 && my <=215){
                filly = 188;
            }
            if(my>215 && my <=243){
                filly = 216;
            }
            if(my>243 && my <=271){
                filly = 244;
            }
            if(my>271 && my<=299){
                filly = 272;
            }
            if(my>299){
                filly = 300;
            }


            g.drawString("("+mx+","+my+")",mx,my);
            //25 describes size of square 
            g.fillOval(fillx, filly, 25, 25);

        }


    }

    public void game(BoardPanel p){
        //while game plays
    }


    public void mouseClickedAt(int x, int y){

        mx = x;
        my = y;

        //user clicked inside of board space
        if(mx>20 && mx<300 && my>20 && my<300){       

            //send to board in MainBattleship
            rect1Clicked = true;

        }
        //updates board
        repaint();
    }


}][1]][1]

I am so lost, thank you for any help!

Upvotes: 1

Views: 886

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

Suggestions:

  1. Don't override a JPanel's paint method but rather its paintComponent method as this is safer, and later when you want to do animation, will result in smoother animation.
  2. Most important you almost always need to call the super's painting method within your own, else the JPanel will not remove previous image artifacts that need to be cleaned up. So if you continue to override paint (although I recommend against, this) the first line of your override should be super.paint(g);, or if you override paintComponent then the first line should be super.paintComponent(g);, of course assuming that you're methods use a Graphics parameter named g.
  3. Also, add the MouseListener to the JPanel, not to the applet, since it is the mouse click location on the panel that matters to you.
  4. Also, use a grid of components or some math to greatly simplify your code -- that ugly list of if blocks should be replaced by a much simpler for loop, one using basic math.
  5. Consider extracting that logic discussed in the point above out of your painting method and into a model of some kind, perhaps a 2D array of boolean.
  6. You're using a lot of "magic" numbers in your code, numbers that should be changed to a combination of constants and mathematically derived numbers.
  7. Notice what happens if you click on your GUI, and then resize it, or if you minimize and then restore it -- you lose all red circles except for the last one pressed. This is another reason to use a grid of boolean or other model to hold state of the game, and then use this model when drawing your GUI.
  8. On further thinking, you might want a 2D array of an enum or an int array, since the grid cell state will likely be more than 2 values (true or false), but rather will be three values -- untested, hit, and miss, and you'll likely want to fill your oval with red if a hit or white if a miss.

For example:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import javax.swing.*;

public class GridExample {
    private static void createAndShowGui() {
        final GridPanel gridPanel = new GridPanel();

        JButton resetBtn = new JButton(new AbstractAction("Reset") {

            @Override
            public void actionPerformed(ActionEvent e) {
                gridPanel.reset();
            }
        });
        JPanel btnPanel = new JPanel();
        btnPanel.add(resetBtn);

        JFrame frame = new JFrame("GridExample");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(gridPanel);
        frame.getContentPane().add(btnPanel, BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

@SuppressWarnings("serial")
class GridPanel extends JPanel {
    private static final int ROWS = 10;
    private static final int CELL_WIDTH = 28;
    private static final int PAD = 20;
    private static final int PREF_W = ROWS * CELL_WIDTH + 2 * PAD;
    private static final int PREF_H = PREF_W;
    private static final Color GRID_COLOR = Color.blue;
    private static final Color CIRCLE_COLOR = Color.red;
    private static final int SML_GAP = 2;
    private boolean[][] grid = new boolean[ROWS][ROWS];

    public GridPanel() {
        addMouseListener(new MyMouse());
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    public void reset() {
        grid = new boolean[ROWS][ROWS]; // fills grid with false
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // draw grid:
        g2.setColor(GRID_COLOR);
        for (int i = 0; i <= ROWS; i++) {
            int x1 = PAD + i * CELL_WIDTH;
            int y1 = PAD;
            int x2 = x1;
            int y2 = PAD + CELL_WIDTH * ROWS;
            g2.drawLine(x1, y1, x2, y2);
            g2.drawLine(y1, x1, y2, x2);
        }

        // iterate through the grid boolean array
        // draw red circles if the grid value is true.
        g2.setColor(CIRCLE_COLOR);
        int w = CELL_WIDTH - 2 * SML_GAP; // width of the circle to draw
        int h = w;
        // nested for loop to go through the grid array
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {
                if (grid[r][c]) {
                    int x = PAD + c * CELL_WIDTH + SML_GAP;
                    int y = PAD + r * CELL_WIDTH + SML_GAP;
                    g2.fillOval(x, y, w, h);
                }
            }
        }        
    }

    private class MyMouse extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            int x = e.getPoint().x;
            int y = e.getPoint().y;

            if (x < PAD || y < PAD) {
                // clicked above or to right of grid
                return;
            }

            int r = (y - PAD) / CELL_WIDTH;
            int c = (x - PAD) / CELL_WIDTH;

            // if clicked to right or below grid.
            // the < 0 part is likely unnecessary, but why not be extra safe?
            if (r >= ROWS || c >= ROWS || r < 0 || c < 0) {
                return;
            }
            grid[r][c] = true;
            repaint();
        }
    }
}

Upvotes: 5

Related Questions