首页 > 解决方案 > 游戏面板实例的 MouseListener

问题描述

我正在创建一个井字游戏,我正在使用 MouseListener 向我的游戏面板添加功能。当用户单击其中一个单元格时,它假设根据轮到谁生成一个 X 或 O 图形。我尝试将 MouseListener 添加到窗格中,但是当我运行它时,单击时没有任何反应。关于如何解决这个问题的任何想法?

这是我的游戏面板:

public GameMain() {
        Handler handler = new Handler();
        this.addMouseListener(handler);

        // setup JLabel
        label = new JLabel("         ");
        label.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
        label.setOpaque(true);
        label.setBackground(Color.LIGHT_GRAY);

        setLayout(new BorderLayout());
        add(label, BorderLayout.SOUTH);
        setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
        board = new Board();
        initGame();

    }   
}

这是我的 Handler 类,它带有应该运行的 mouseClick() 方法:

public class Handler extends MouseAdapter {
        public void mouseClick(MouseEvent e) {
            int mouseX = e.getX();
            int mouseY = e.getY();

            // Get the row and column clicked
            int rowSelected = mouseY / CELL_SIZE;
            int colSelected = mouseX / CELL_SIZE;

            if (currentState == GameState.PLAYING) {
                if (rowSelected >= 0 && rowSelected < board.cells.length && colSelected >= 0 && colSelected < board.cells.length &&  
                    board.cells[rowSelected][colSelected].content == Seed.EMPTY) {
                    board.cells[rowSelected][colSelected].content = currentPlayer; // move
                    updateGame(currentPlayer, rowSelected, colSelected); // update currentState
                    currentPlayer = (currentPlayer == Seed.X) ? Seed.O : Seed.X;
                }
            } else {
                initGame();
            }

            repaint();
        }

        public void handleButtonPress(Object o) {
            if (o == singlePlayer) {
                singlePlayerGame();
            }
            if (o == multiPlayer) {
                multiPlayerGame();
            }
        }
    }

标签: javaswinguser-interfacemouselistener

解决方案


您的问题启发了我接受编写井字游戏的挑战,因为我在处理自定义绘画方面没有太多经验。下面的代码注释很多,所以我希望这些注释可以很好地解释代码。

我假设比我更有经验的人会在下面的代码中发现缺陷,这是意料之中的,因为正如我已经提到的,我在这种编程方面没有很多经验。尽管如此,我希望它足够好,以便对您有所帮助。

请注意,以下代码使用了 JDK 14 引入的新 Java 功能,即Records。因此,作为如何将 java 记录集成到他们的代码中的简单示例,它也可能对人们有所帮助。如果您没有使用 JDK 14 或者如果您使用但没有启用预览功能,您可以简单地将记录定义替换为一个简单的类。只需将单词替换为recordclass在代码中添加构造函数和“getter”方法即可。默认情况下,a 中的“getter”方法record的名称只是字段的名称,例如对于minX记录中的成员Square(在下面的代码中),“getter”方法是minX().

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

/**
 * A simple tic-tac-toe (a.k.a. noughts and crosses) game. User clicks mouse on an empty square of
 * the game board and either a nought or a cross is drawn in that square, depending on whose turn
 * it is. Each square has an index as shown below.
 * <pre>
 * 0    1   2
 * 
 * 3    4   5
 * 
 * 6    7   8
 * </pre>
 * See <a href="https://en.wikipedia.org/wiki/Tic-tac-toe">Tic-tac-toe</a> on <i>Wikipedia</i>.
 */
public class GameMain extends JPanel implements ActionListener, MouseListener, Runnable {

    /** For serializing instances of this class. */
    private static final long serialVersionUID = 7014608855599083001L;

    /** A cross. */
    private static final char  CROSS = 'X';

    /** A nought. */
    private static final char  NOUGHT = 'O';

    /** Dimension of each square in tic-tac-toe board. */
    private static final int  SQUARE_DIMENSION = 80;

    /** Number of consecutive squares required for a win. */
    private static final int  REQUIRED_SQUARES = 3;

    /** Total number of squares in tic-tac-toe board. */
    private static final int  TOTAL_SQUARES = 9;

    /** Each of the 9 squares in tic-tac-toe board. */
    private final Square[]  SQUARES = new Square[]{new Square(1, 1, 99, 99),
                                                   new Square(101, 1, 199, 99),
                                                   new Square(201, 1, 299, 99),

                                                   new Square(1, 101, 99, 199),
                                                   new Square(101, 101, 199, 199),
                                                   new Square(201, 101, 299, 199),

                                                   new Square(1, 201, 99, 299),
                                                   new Square(101, 201, 199, 299),
                                                   new Square(201, 201, 299, 299)};

    /** Text for {@link #turnLabel} at start of a new tic-tac-toe game. */
    private static final String  FIRST_MOVE = "X goes first";

    /** Text for <i>new game</i>. button. */
    private static final String  NEW_GAME = "New Game";

    /** Indicates start of a new game. */
    private boolean  newGame;

    /** <tt>true</tt> means O turn and <tt>false</tt> means X turn. */
    private boolean  oTurn;

    /** Records occupied squares, either &apos;O&apos; or &apos;X&apos; */
    private char[]  occupied = new char[TOTAL_SQUARES];

    /** Number of unoccupied squares in tic-tac-toe board. */
    private int  freeCount;

    /** Displays whose turn it currently is. */
    private JLabel  turnLabel;

    /** Location of last mouse click. */
    private Point  clickPoint;

    /**
     * Creates and returns instance of this class.
     */
    public GameMain() {
        setPreferredSize(new Dimension(300, 300));
        addMouseListener(this);
        freeCount = TOTAL_SQUARES;
    }

    private static boolean isValidRequirement(int index) {
        return index >= 0  &&  index < REQUIRED_SQUARES;
    }

    /**
     * Determines whether <var>square</var> is a valid index of a square in tic-tac-toe board.
     * 
     * @param square - will be validated.
     * 
     * @return  <tt>true</tt> if <var>square</var> is valid.
     */
    private static boolean isValidSquare(int square) {
        return square >= 0  &&  square < TOTAL_SQUARES;
    }

    @Override // java.awt.event.ActionEvent
    public void actionPerformed(ActionEvent actnEvnt) {
        String actionCommand = actnEvnt.getActionCommand();
        switch (actionCommand) {
            case NEW_GAME:
                startNewGame();
                break;
            default:
                JOptionPane.showMessageDialog(this,
                                              actionCommand,
                                              "Unhandled",
                                              JOptionPane.WARNING_MESSAGE);
        }
    }

    @Override // java.awt.event.MouseListener
    public void mouseClicked(MouseEvent mousEvnt) {
        // Do nothing.
    }

    @Override // java.awt.event.MouseListener
    public void mouseEntered(MouseEvent mousEvnt) {
        // Do nothing.
    }

    @Override // java.awt.event.MouseListener
    public void mouseExited(MouseEvent mousEvnt) {
        // Do nothing.
    }

    @Override // java.awt.event.MouseListener
    public void mousePressed(MouseEvent mousEvnt) {
        // Do nothing.
    }

    @Override // java.awt.event.MouseListener
    public void mouseReleased(MouseEvent mousEvnt) {
        System.out.println("mouse clicked");
        clickPoint = mousEvnt.getPoint();
        repaint();
        EventQueue.invokeLater(() -> {
            String text;
            if (isWinner()) {
                text = oTurn ? "O has won!" : "X has won!";
                removeMouseListener(this);
                JOptionPane.showMessageDialog(this,
                                              text,
                                              "Winner",
                                              JOptionPane.PLAIN_MESSAGE);
            }
            else if (freeCount <= 0) {
                text = "Drawn game.";
                removeMouseListener(this);
                JOptionPane.showMessageDialog(this,
                                              text,
                                              "Draw",
                                              JOptionPane.PLAIN_MESSAGE);
            }
            else {
                oTurn = !oTurn;
                text = oTurn ? "O's turn" : "X's turn";
            }
            turnLabel.setText(text);
        });
    }

    @Override // java.lang.Runnable
    public void run() {
        createAndShowGui();
    }

    @Override // javax.swing.JComponent
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.drawLine(100, 0, 100, 300);
        g2d.drawLine(200, 0, 200, 300);
        g2d.drawLine(0, 100, 300, 100);
        g2d.drawLine(0, 200, 300, 200);
        if (!newGame) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setStroke(new BasicStroke(3));
            for (int i = 0; i < TOTAL_SQUARES; i++) {
                if (occupied[i] == NOUGHT) {
                    drawNought(i, g2d);
                }
                else if (occupied[i] == CROSS) {
                    drawCross(i, g2d);
                }
            }
            int square = getSquare(clickPoint);
            if (isFreeSquare(square)) {
                if (oTurn) {
                    drawNought(square, g2d);
                    occupied[square] = NOUGHT;
                }
                else {
                    drawCross(square, g2d);
                    occupied[square] = CROSS;
                }
                freeCount--;
            }
        }
        else {
            newGame = false;
        }
    }

    private void createAndShowGui() {
        JFrame frame = new JFrame("O & X");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(createTurnPanel(), BorderLayout.PAGE_START);
        frame.add(this, BorderLayout.CENTER);
        frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JButton createButton(String text, int mnemonic, String tooltip) {
        JButton button = new JButton(text);
        if (mnemonic > 0) {
            button.setMnemonic(mnemonic);
        }
        if (tooltip != null  &&  !tooltip.isBlank()) {
            button.setToolTipText(tooltip);
        }
        button.addActionListener(this);
        return button;
    }

    private JPanel createButtonsPanel() {
        JPanel buttonsPanel = new JPanel();
        buttonsPanel.setBorder(BorderFactory.createMatteBorder(2, 0, 0, 0, Color.GRAY));
        buttonsPanel.add(createButton(NEW_GAME, KeyEvent.VK_N, "Start a new game."));
        return buttonsPanel;
    }

    private JPanel createTurnPanel() {
        JPanel turnPanel = new JPanel();
        turnPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 2, 0, Color.GRAY));
        turnLabel = new JLabel(FIRST_MOVE);
        turnPanel.add(turnLabel);
        return turnPanel;
    }

    /**
     * Draws a {@link #CROSS} in <var>square</var> of tic-tac-toe board.
     * 
     * @param square - index of square in tic-tac-toe board.
     * @param g2d    - facilitates drawing.
     */
    private void drawCross(int square, Graphics2D g2d) {
        if (isValidSquare(square)  &&  g2d != null) {
            g2d.setColor(Color.BLUE);
            g2d.drawLine(SQUARES[square].minX() + 10,
                         SQUARES[square].minY() + 10,
                         SQUARES[square].maxX() - 10,
                         SQUARES[square].maxY() - 10);
            g2d.drawLine(SQUARES[square].maxX() - 10,
                         SQUARES[square].minY() + 10,
                         SQUARES[square].minX() + 10,
                         SQUARES[square].maxY() - 10);
        }
    }

    /**
     * Draws a {@link #NOUGHT} in <var>square</var> of tic-tac-toe board.
     * 
     * @param square - index of square in tic-tac-toe board.
     * @param g2d    - facilitates drawing.
     */
    private void drawNought(int square, Graphics2D g2d) {
        if (isValidSquare(square)  &&  g2d != null) {
            g2d.setColor(Color.RED);
            g2d.drawOval(SQUARES[square].minX() + 10,
                         SQUARES[square].minY() + 10,
                         SQUARE_DIMENSION,
                         SQUARE_DIMENSION);
        }
    }

    /**
     * Returns the square of the tic-tac-toe board that contains <var>pt</var>.
     * 
     * @param pt - point on tic-tac-toe board.
     * 
     * @return  index of square in tic-tac-toe board containing <var>pt</var> or -1 (negative one)
     *          if <var>pt</var> not in tic-tac-toe board.
     * 
     * @see Square#contains(int, int)
     */
    private int getSquare(Point pt) {
        int ndx = -1;
        if (pt != null) {
            for (int i = 0; i < 9; i++) {
                if (SQUARES[i].contains(pt.x, pt.y)) {
                    ndx = i;
                    break;
                }
            }
        }
        return ndx;
    }

    /**
     * Determines whether <var>column</var> of tic-tac-toe board contains all {@link #CROSS} or all
     * {@link #NOUGHT}.
     * 
     * @param column - index of column in tic-tac-toe board.
     * 
     * @return  <tt>true</tt> if <var>column</var> contains all {@link #CROSS} or all {@link #NOUGHT}.
     * 
     * @see #isValidRequirement(int)
     */
    private boolean isColumnWin(int column) {
        boolean isWin = false;
        if (isValidRequirement(column)) {
            isWin = isOccupied(column)  &&
                    occupied[column] == occupied[column + REQUIRED_SQUARES]  &&
                    occupied[column] == occupied[column + (REQUIRED_SQUARES * 2)];
        }
        return isWin;
    }

    /**
     * Determines whether diagonal of tic-tac-toe board contains all {@link #CROSS} or all
     * {@link #NOUGHT}. The board contains precisely two diagonals where each one includes the
     * board&apos;s center square, i.e. the square with index 4. The other squares that constitute
     * diagonals are the corner squares, including the indexes 0 (zero), 2, 6 and 8.
     * 
     * @return  <tt>true</tt> if one of the tic-tac-toe board diagonals contains all {@link #CROSS}
     *          or all {@link #NOUGHT}.
     * 
     * @see #isValidRequirement(int)
     */
    private boolean isDiagonalWin() {
        boolean isWin = false;
        isWin = (isOccupied(0)  &&
                occupied[0] == occupied[4]  &&
                occupied[0] == occupied[8])
                ||
                (isOccupied(2)  &&
                 occupied[2] == occupied[4]  &&
                 occupied[2] == occupied[6]);
        return isWin;
    }

    /**
     * Determines whether <var>square</var> in tic-tac-toe board does not contain a {@link #CROSS}
     * nor a {@link #NOUGHT}.
     * 
     * @param square - index of square in tic-tac-toe board.
     * 
     * @return  <tt>true</tt> if <var>square</var> does not contain a {@link #CROSS} nor a {@link
     *          #NOUGHT}.
     */
    private boolean isFreeSquare(int square) {
        boolean freeSquare = false;
        if (isValidSquare(square)) {
            freeSquare = occupied[square] != CROSS  &&  occupied[square] != NOUGHT;
        }
        return freeSquare;
    }

    /**
     * Determines whether <var>row</var> of tic-tac-toe board contains all {@link #CROSS} or all
     * {@link #NOUGHT}.
     * 
     * @param row - index of row in tic-tac-toe board.
     * 
     * @return  <tt>true</tt> if <var>row</var> contains all {@link #CROSS} or all {@link #NOUGHT}.
     * 
     * @see #isValidRequirement(int)
     */
    private boolean isLineWin(int row) {
        boolean isWin = false;
        if (isValidRequirement(row)) {
            int index = row * REQUIRED_SQUARES;
            isWin = isOccupied(index)  &&
                    occupied[index] == occupied[index + 1]  &&
                    occupied[index] == occupied[index + 2];
        }
        return isWin;
    }

    /**
     * Determines whether square at <var>index</var> in tic-tac-toe board contains either a {@link
     * #CROSS} or a {@link #NOUGHT}.
     * 
     * @param index - index of square in tic-tac-toe board.
     * 
     * @return  <tt>true</tt> if square at <var>index</var> in tic-tac-toe board contains either a
     *          {@link #CROSS} or a {@link #NOUGHT}.
     * 
     * @see #isValidSquare(int)
     */
    private boolean isOccupied(int index) {
        boolean occupied = false;
        if (isValidSquare(index)) {
            occupied = this.occupied[index] == CROSS  ||  this.occupied[index] == NOUGHT;
        }
        return occupied;
    }

    /**
     * Determines if there is a winner.
     * 
     * @return  <tt>true</tt> if someone has won the game.
     * 
     * @see #isColumnWin(int)
     * @see #isDiagonalWin()
     * @see #isLineWin(int)
     */
    private boolean isWinner() {
        return isLineWin(0)   ||
               isLineWin(1)   ||
               isLineWin(2)   ||
               isColumnWin(0) ||
               isColumnWin(1) ||
               isColumnWin(2) ||
               isDiagonalWin();
    }

    /**
     * Initializes the GUI in order to start a new game.
     */
    private void startNewGame() {
        freeCount = TOTAL_SQUARES;
        newGame = true;
        oTurn = false;
        occupied = new char[TOTAL_SQUARES];
        repaint();
        EventQueue.invokeLater(() -> {
            removeMouseListener(this);
            addMouseListener(this);
            turnLabel.setText(FIRST_MOVE);
        });
    }

    /**
     * This method is the first one called when this class is launched via the <tt>java</tt>
     * command. It ignores the method parameter <var>args</var>.
     * 
     * @param args - <tt>java</tt> command arguments.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new GameMain());
    }
}

/**
 * Represents the geometrical shape known as a square. For the purposes of this {@code record}, a
 * square is defined by two points that indicate its top, left corner and its bottom, right corner.
 */
record Square (int minX, int minY, int maxX, int maxY) {

    /**
     * Determines whether the supplied point lies within this square.
     * 
     * @param x - x coordinate of a point.
     * @param y - y coordinate of same point (as x).
     * 
     * @return  <tt>true</tt> if supplied point lies within this square.
     */
    public boolean contains(int x, int y) {
        return minX <= x  &&  x <= maxX  &&  minY <= y  &&  y <= maxY;
    }
}

推荐阅读