質問

I have written a Minesweeper game in Java. However, after I click on a few buttons, it throws a NullPointerException. Here is the complete program (I'm afraid it is huge, and my comments won't help much, I suppose!)

import java.util.Random;
import javax.swing.JToggleButton;

    /**
     * This class represents a button in a {@code MineSweeperGrid}.
     */
    @SuppressWarnings("serial") public class MineButton extends JToggleButton {
        public final boolean safe; // Whether this button hides a mine or not (true if not).
        private boolean clicked; // Whether this button has been clicked or not.
        public final int id; // The id number of this button. This can be used to determine the position of the button on
                                // the grid.

        /**
         * Constructs a new {@code MineButton} with a randomly generated safety status, and a user defined id.
         * 
         * @param id
         *            The identity number of this button, which can be used to determine its position on a grid of buttons.
         */
        public MineButton(int id) {
            super();
            safe = new Random().nextBoolean();
            clicked = false;
            this.id = id;
        }

        public boolean hasBeenClicked() {
            return clicked;
        }

    }


import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

@SuppressWarnings("serial") public class MineSweeperGrid extends JPanel {
    public final int size; // The size of this grid.
    private int noOfMines; // The number of mines in the grid.
    private int btnsLeft; // The buttons left to clear.
    private MineButton[][] grid; // The grid itself, which is represented as an array of MineButtons.

    /**
     * Creates a new MineSweeperGrid object according to the size specified as the argument.
     * @param size
     *            The length or breadth of the grid. The number of buttons in the grid will be size*size.
     */
    public MineSweeperGrid(int size) {
        super(); // Call the superclass constructor.
        if(size < 3) // This is minimum limit of the size, because our code won't work properly with a size lesser than
                        // this.
            throw new IllegalArgumentException("The size of the grid cannot be less than three.");
        setLayout(new GridLayout(10, 10)); // Set the layout of this panel.
        this.size = size;
        grid = new MineButton[size][size];
        int id = noOfMines = btnsLeft = 0; // Initialize id, noOfMines & btnsLeft to 0.

        // Initialize each button of the grid.
        for(int i = 0; i < (size - 1); i++)
            for(int j = 0; j < size; j++) {
                grid[i][j] = new MineButton(id); // Construct the button.
                if(!grid[i][j].safe)
                    noOfMines++;
                grid[i][j].addActionListener(new MineListener()); // Add a listener to it (which does not work
                                                                    // properly).
                grid[i][j].setEnabled(true); // Disable this button.
                add(grid[i][j]); // Add it to the grid.
                id++;
            }
        btnsLeft = (size * size) - noOfMines; // Set the number of buttons to be cleared .
    }

    /**
     * 
     * The ActionListener for the MineButtons in our grid.
     */
    private class MineListener implements ActionListener {
        /*
         * This is test code. Not working currently!
         */
        @Override public void actionPerformed(ActionEvent evt) {
            MineButton btn = (MineButton)evt.getSource(); // Get the button which generated this event.
            btn.setSelected(false);
            if(!(btn.safe)) {
                btn.setText("M");
                return;
            }
            btn.setEnabled(false); // Since now we know he's safe, disable this button.
            MineButton[] btns = getNeighbours(btn); // Get this button's neighbours, i.e., the
                                                    // buttons that surround it.
            int count = 0; // Initialize count to 0.
            for(int i = 0; i < btns.length; i++)
                if(!btns[i].safe)
                    count++;
            btn.setText("" + count + "");
            btnsLeft--;
            if(btnsLeft == 0) {
                JOptionPane.showMessageDialog(null, "You have cleared all the mines! You win!");
                System.exit(0);

            }
        }

        /**
         * Method which computes the buttons surrounding the button passed as parameter.
         * 
         * @param btn
         *            The button passed as parameter.
         * @return The buttons surrounding this button as an array of buttons.
         * @throws IllegalArgumentException
         *             If the parameter passed is null.
         */
        private MineButton[] getNeighbours(MineButton btn) {
            int x = btn.id / size, y = btn.id % size;
            System.out.println(x + ", " + y);
            if((x == 0) && (y == 0)) // This is the first button in the grid, in the top left corner.
                return new MineButton[] {grid[x][y + 1], grid[x + 1][y], grid[x + 1][y + 1]}; // This is OK.

            else if((x == (size - 1)) && (y == (size - 1))) // This is the last button in the grid, in the lower right
                                                            // corner.
                return new MineButton[] {grid[x - 1][y - 1], grid[x - 1][y], grid[x][y - 1]}; // This is OK.

            else if((x == 0) && (y == (size - 1))) // This is the button in the top right corner.
                return new MineButton[] {grid[x][y - 1], grid[x + 1][y], grid[x + 1][y - 1]};

            else if((x == (size - 1)) && (y == 0)) // This is the button in the lower left corner.
                return new MineButton[] {grid[x][y + 1], grid[x - 1][y], grid[x - 1][y + 1]}; // This is OK.

            else if(x == 0) // This is a button belonging to the first row.
                return new MineButton[] {grid[x][y + 1], grid[x + 1][y], grid[x + 1][y + 1], grid[x + 1][y - 1],
                        grid[x][y - 1]}; // This is OK.

            else if(y == 0) // This is a button belonging to the first column.
                return new MineButton[] {grid[x][y + 1], grid[x + 1][y], grid[x + 1][y + 1], grid[x - 1][y],
                        grid[x - 1][y + 1]}; // This is OK.

            else if(x == (size - 1)) // This is a button in the last row.
                return new MineButton[] {grid[x][y - 1], grid[x - 1][y], grid[x - 1][y - 1], grid[x - 1][y + 1],
                        grid[x][y + 1]}; // This is OK.;

            else if(y == (size - 1)) // This is a button in the last column.
                return new MineButton[] {grid[x][y - 1], grid[x - 1][y], grid[x - 1][y - 1], grid[x + 1][y],
                        grid[x + 1][y - 1]}; // This is OK.

            // For all other buttons.
            return new MineButton[] {grid[x + 1][y + 1], grid[x + 1][y], grid[x][y + 1], grid[x - 1][y - 1],
                    grid[x - 1][y], grid[x][y - 1], grid[x - 1][y + 1], grid[x + 1][y - 1]}; // This is OK.

        }
    }
}


import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;

import org.mandal.data.MineSweeperGrid;

public class MineSweeperUI {

    private JFrame frame;
    private JPanel content;
    private ButtonHandler listener;
    private MineSweeperGrid grid;
    private JMenuBar menuBar;
    private JMenu file;
    private JMenuItem newGame;
    private JMenuItem options;
    private JMenuItem close;
    private JMenu help;
    private JMenuItem instructions;
    private JMenuItem about;

    /**
     * 
     */
    private class ButtonHandler implements ActionListener {
        @Override public void actionPerformed(ActionEvent evt) {
        }
    }

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override public void run() {
                try {
                    MineSweeperUI window = new MineSweeperUI();
                    window.frame.setVisible(true);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public MineSweeperUI() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame("MineSweeper v1.00");
        frame.setResizable(false);
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        content = new JPanel();
        content.setLayout(null);

        grid = new MineSweeperGrid(10);
        grid.setBounds(0, 0, 444, 251);
        content.add(grid);

        menuBar = new JMenuBar();
        frame.setJMenuBar(menuBar);

        file = new JMenu("File");
        menuBar.add(file);

        newGame = new JMenuItem("New Game");
        file.add(newGame);

        options = new JMenuItem("Options...");
        file.add(options);

        close = new JMenuItem("Close");
        file.add(close);

        help = new JMenu("Help");
        menuBar.add(help);

        instructions = new JMenuItem("How to play?");
        help.add(instructions);

        about = new JMenuItem("About...");
        help.add(about);

        frame.setContentPane(content);
    }
}

As you can see, the program is not yet complete, and some parts have been simplified (such as displaying "M" when you hit a mine, instead of handling the loss) for debugging purposes. This is the stack trace :

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at org.mandal.data.MineSweeperGrid$MineListener.actionPerformed(MineSweeperGrid.java:68)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.JToggleButton$ToggleButtonModel.setPressed(Unknown Source)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$200(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)
役に立ちましたか?

解決

It seems that:

for(int i = 0; i < (size - 1); i++)

should be:

for(int i = 0; i < size; i++)

otherwise you would not fill the last array grid[size - 1] in the last iteration. grid[size - 1] refers to an empty array:

{null, null, null, ...} // its length = size

or in other words:

grid[size - 1][0] == null
grid[size - 1][1] == null
grid[size - 1][2] == null
grid[size - 1][3] == null
...
grid[size - 1][size - 1] == null
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top