在每次 while 循环迭代中等待用户鼠标单击

nor*_*n32 0 java swing chess

我正在尝试使用 Java 的 Swing 库构建一个带有 GUI 界面的国际象棋游戏。当我开始游戏时,会打开一个带有棋盘的窗口。

我所坚持的是,每次我都想等待当前玩家采取行动,而不是无限地运行 while 循环,因为这不是很有效。

这是代码和之后的解释:

private void startGame()
{
    while (!board.terminalBoard())
    {
        Move movePlayed = null;

        // whilist making the move returns false, meaning we cant do it...
        while (canMakeMove(movePlayed))
        {
            // TODO: Implement the getting move here
        }
        
        // If move is okay, make it
        board.movePiece(movePlayed.getStart(), movePlayed.getEnd());
    
        // Repaint the chessboard
        chessboard.loadImages(board);
        chessboard.repaintBoard();

        currColor ^= Piece.BLACK;
    }
}
Run Code Online (Sandbox Code Playgroud)

在内部 while 循环中,当用户所下的棋步无效时,它会再次迭代并等待下一个棋步。我不明白的是我如何等待玩家采取行动。

就我而言,玩家可以通过在棋盘上单击两次(源方格和目标方格​​)来进行移动。然后我的函数检查它是否有效。

如何等待用户在 Java Swing 窗口上单击两次?


编辑代码:问题是,如果我像你说的那样删除 thread.sleep,应用程序不会响应我的点击。

private void startGame()
    {
        while (!board.terminalBoard())
        {
            Move movePlayed = chessboard.getMove(board);

            // whilist making the move returns false, meaning we cant do it...
            if(canMakeMove(movePlayed))
            {
                // If move is okay, make it
                board.movePiece(movePlayed.getStart(), movePlayed.getEnd());
            
                // Repaint the chessboard
                chessboard.loadImages(board);
                chessboard.repaintBoard();

                currColor ^= Piece.BLACK;
            }

            /* try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } */
        }
    }
Run Code Online (Sandbox Code Playgroud)

Hov*_*els 5

对于刚开始创建事件驱动程序的程序员来说,您陷入了一个常见陷阱:您将其编码为线性控制台程序,例如使用基于 System.in 和 println 的 Scanner 的程序,使用while 循环允许代码执行重复操作,而这在 GUI 编码中根本不起作用。相反,您应该通过侦听用户事件来等待用户输入,例如 ActionListeners 侦听按钮按下操作,或 MouseListeners 侦听鼠标操作,并且应该删除 while 循环。程序对用户输入的响应基于程序的状态(关键状态字段保存的值),而不是 while 循环。要使用的关键范例是:

  • 事件驱动编程,以及
  • 状态设计模式

所以在这里,这些字段可能类似于:

  • private boolean whitesTurn;——如果是真的,听白棋走棋。如果为 false,则忽略白色的输入
  • private ChessCell firstClick;这可以保存获得第一次点击的单元格。如果为空,则玩家尚未进行第一次单击,但如果它不为空并且拥有一个可行的棋盘单元(无论您如何定义),则已进行第一次单击,下一次单击可能会在第二个位置

例如,请参阅下面的代码。但请注意,为了简洁起见,此示例没有使用 MVC——模型-视图-控制器,但我建议您使用它。

import java.awt.*;
import java.awt.event.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;

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

            private void createAndShowGUI() {
                JFrame frame = new JFrame("Simple Chess");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new GameBoardPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)
class GameBoardPanel extends JPanel {
    public static final String WHITE = "WHITE";
    public static final String BLACK = "BLACK";
    public static final int ROWS = 8;
    public static final int COLS = 8;
    private JPanel[][] board = new JPanel[ROWS][COLS];
    private JPanel boardPanel = new JPanel(new GridLayout(ROWS, COLS));
    private JLabel whitePiece;
    private JLabel blackPiece;
    private JLabel statusLabel = new JLabel(WHITE);
    private boolean isWhiteTurn = true;
    private ChessCellPanel firstSelectedCell = null;

    public GameBoardPanel() {
        whitePiece = new JLabel(createIcon(new Color(240, 240, 240)));
        blackPiece = new JLabel(createIcon(new Color(20, 20, 20)));

        setLayout(new GridLayout(ROWS, COLS));
        MyMouse myMouse = new MyMouse();
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                ChessCellPanel cellPanel = new ChessCellPanel(row, col);
                cellPanel.addMouseListener(myMouse);
                boardPanel.add(cellPanel);
                board[row][col] = cellPanel;
            }
        }

        addPiece(ROWS - 1, COLS - 1, whitePiece);
        addPiece(0, 0, blackPiece);

        JPanel statusPanel = new JPanel();
        statusPanel.add(new JLabel("Turn: "));
        statusPanel.add(statusLabel);

        setLayout(new BorderLayout());
        add(boardPanel);
        add(statusPanel, BorderLayout.PAGE_END);
    }

    private class MyMouse extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            ChessCellPanel cellPanel = (ChessCellPanel) e.getSource();
            if (cellPanel.getComponents().length > 0 && firstSelectedCell == null) {
                JLabel piece = (JLabel) cellPanel.getComponent(0);
                if (piece == (isWhiteTurn ? whitePiece : blackPiece)) {
                    firstSelectedCell = cellPanel;
                    cellPanel.setBackground(Color.YELLOW);
                }
            } else if (firstSelectedCell != null) {
                if (cellPanel == firstSelectedCell) {
                    firstSelectedCell.setBackground(cellPanel.getCellColor());
                    firstSelectedCell = null;
                } else {
                    cellPanel.removeAll();
                    cellPanel.add(firstSelectedCell.getComponent(0));
                    firstSelectedCell.removeAll();
                    firstSelectedCell.setBackground(firstSelectedCell.getCellColor());
                    firstSelectedCell = null;
                    isWhiteTurn = !isWhiteTurn;
                    statusLabel.setText(isWhiteTurn ? WHITE : BLACK);
                }
                boardPanel.revalidate();
                boardPanel.repaint();
            }
        }
    }

    private void addPiece(int i, int j, JLabel piece) {
        ChessCellPanel cellPanel = (ChessCellPanel) board[i][j];
        cellPanel.add(piece);
    }

    private Icon createIcon(Color color) {
        int width = ChessCellPanel.CELL_SIZE - 20;
        int height = ChessCellPanel.CELL_SIZE - 20;
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(color);
        g2d.fillOval(4, 4, width - 8, height - 8);
        g2d.setStroke(new BasicStroke(3f));
        g2d.setColor(Color.BLACK);
        g2d.drawOval(4, 4, width - 8, height - 8);
        g2d.dispose();
        return new ImageIcon(img);
    }

}
Run Code Online (Sandbox Code Playgroud)
class ChessCellPanel extends JPanel {
    public static final int CELL_SIZE = 100;
    private static final Color LIGHT_COLOR = new Color(240, 217, 181);
    private static final Color DARK_COLOR = new Color(181, 136, 99);
    private int row;
    private int col;
    private Color cellColor;

    public ChessCellPanel(int row, int col) {
        this.row = row;
        this.col = col;
        this.cellColor = (row + col) % 2 == 0 ? LIGHT_COLOR : DARK_COLOR;
        setBackground(cellColor);
        setLayout(new GridBagLayout());
    }

    public int getRow() {
        return row;
    }

    public int getCol() {
        return col;
    }

    public Color getCellColor() {
        return cellColor;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(CELL_SIZE, CELL_SIZE);
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例的关键在于 MouseListener(实际上是 MouseAdapter),它执行以下操作:

  1. 获取被点击的JPanel
  2. 支票
    • 如果 JPanel 包含一个组件,则为 JLabel
    • firstSelectedCell另外,如果没有选择棋子,则关键状态变量为空
  3. 如果单元格包含一块并且第一个选定的单元格为空,则
    • 检查棋子的“颜色”以及轮到谁了。
    • 如果颜色和转弯匹配,则firstSelectedCell使用该单元设置状态字段
    • 显示单元格已被选中(此处设置其背景颜色)
  4. 否则,如果firstSelectedCell不为空,那么,
    • 棋子已经选定
    • 这是第二次按下鼠标
  5. 如果第二次按下的单元格与第一次按下的单元格相同,哎呀
  6. 否则我们很好:
    • 从第一个单元格中取出棋子
    • 将其移动到第二个单元格
    • 将firstSelectedCell重置为null
    • 切换isWhiteTurn
    • 更改状态标签以显示轮到白棋了
  7. 告诉布局管理器重新布局组件,并要求重新绘制棋盘
@Override
public void mousePressed(MouseEvent e) {
    ChessCellPanel cellPanel = (ChessCellPanel) e.getSource(); //1
    
    // 2
    if (cellPanel.getComponents().length > 0 && firstSelectedCell == null) {
        // 3
        JLabel piece = (JLabel) cellPanel.getComponent(0);
        if (piece == (isWhiteTurn ? whitePiece : blackPiece)) {
            firstSelectedCell = cellPanel;
            cellPanel.setBackground(Color.YELLOW);
        }
    } else if (firstSelectedCell != null) {
        // 4
        if (cellPanel == firstSelectedCell) {
            // 5
            firstSelectedCell.setBackground(cellPanel.getCellColor());
            firstSelectedCell = null;
        } else {
            // 6
            cellPanel.removeAll();
            cellPanel.add(firstSelectedCell.getComponent(0));
            firstSelectedCell.removeAll();
            firstSelectedCell.setBackground(firstSelectedCell.getCellColor());
            firstSelectedCell = null;
            isWhiteTurn = !isWhiteTurn;
            statusLabel.setText(isWhiteTurn ? WHITE : BLACK);
        }
        // 7
        boardPanel.revalidate();
        boardPanel.repaint();
    }
}
Run Code Online (Sandbox Code Playgroud)