如何在一个 ActionListener 中有两个依赖的动作?

How to have two dependent actions within an ActionListener?

我正在尝试在 java 中编写带有 GUI 的西洋跳棋 AI 程序。到目前为止,我已经设法初始化并填充了板(现在写为“B”和“W”)。我已经为开发板创建了一个 2D JButton 面板。

搬棋子的时候不知道怎么操作。我当前的问题是我需要玩家 select 一个预先存在的棋子(动作 1),然后一个空的 space 来放置 selected 棋子(动作 2)。当然,这些操作是相互依赖的,我需要先执行操作 1。

这是我目前得到的:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

public class Checkers implements ActionListener{

        private static final int BOARD_SIZE = 8;

        Random random = new Random();
        JFrame frame = new JFrame();
        JPanel title_panel = new JPanel();
        JPanel button_panel = new JPanel();
        JLabel textfield = new JLabel();
        JButton[][] buttons = new JButton[BOARD_SIZE][BOARD_SIZE];

        Checkers(){
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(800,800);
            frame.getContentPane().setBackground(new Color(50, 50, 50));
            frame.setLayout(new BorderLayout());
            frame.setVisible(true);

            textfield.setBackground(new Color(25,25,25));
            textfield.setForeground(new Color(25, 255, 0));
            textfield.setFont(new Font("Arial", Font.BOLD, 75));
            textfield.setHorizontalAlignment(JLabel.CENTER);
            textfield.setText("Checkers");
            textfield.setOpaque(true);

            title_panel.setLayout(new BorderLayout());
            title_panel.setBounds(0,0,800, 100);
            button_panel.setLayout(new GridLayout(BOARD_SIZE,BOARD_SIZE));
            button_panel.setBackground(new Color(150,150,150));

            for (int i=0; i<BOARD_SIZE; i++){
                for (int j=0; j<BOARD_SIZE; j++){
                    buttons[i][j] = new JButton();
                    button_panel.add(buttons[i][j]);
                    buttons[i][j].setFont(new Font("Arial",Font.BOLD, 30));
                    buttons[i][j].setFocusable(false);
                    buttons[i][j].addActionListener(this);
                }
            }

            title_panel.add(textfield);
            frame.add(title_panel, BorderLayout.NORTH);
            frame.add(button_panel);
            initialiseBoard(buttons);
        }

    @Override
    public void actionPerformed(ActionEvent e) {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (e.getSource() == buttons[i][j]) {
                    String moving_piece = buttons[i][j].getText();
                }
                }
            }
        }

    // method that fills the initial board.
    public void initialiseBoard(JButton[][] buttons){
        for (int i=0; i<BOARD_SIZE; i++){
            for (int j=0; j<BOARD_SIZE; j++){

                if (i<3){
                    if (i%2==0){
                        if (j%2 != 0){
                            buttons[i][j].setText("W");
                        }
                    }
                    else {
                        if (j%2==0){
                            buttons[i][j].setText("W");
                        }
                    }
                }
                if (i>=BOARD_SIZE-3){
                    if (i%2 == 0){
                        if (j%2 != 0){
                            buttons[i][j].setText("B");
                        }
                    }
                    else {
                        if (j%2==0){
                            buttons[i][j].setText("B");
                        }
                    }
                }
            }
        }
    }
}

你需要记住和管理一个片段的选择。您可以通过以下方式完成:

//Introduce a field to store selected button
private JButton selected = null ;

并修改ActionListener

@Override
public void actionPerformed(ActionEvent e) {

    JButton clicked = (JButton) e.getSource();
    if(selected != null ){
        clicked.setText(selected.getText());
        selected.setText("");
        selected = null;
    }else{
        if( ! clicked.getText().isEmpty()) {
            selected = clicked;
        }
    }
}

考虑使用 JToggleButtons 而不是 JButtons 。

正如我在评论中提到的,解决方案的关键是让程序的侦听器基于“state-dependent 行为”,这意味着侦听器的行为将取决于程序的状态。

例如,如果您为您的程序提供一个实例字段,例如 selectedButton,它包含对最后选择的按钮的引用,那么任何 ActionListener 的响应都可能因该字段包含的内容而异。第一次按下任何 JButton 时,此字段将保持 null,因为之前没有按下 JButton,因此 ActionListener 会将此按钮分配给该字段。第二次调用 ActionListener(第二次按下 JButton)时,该字段将保存一个值,因此,使用 if / else 块检查该字段的状态时,侦听器的行为可能会有所不同:

public class Checkers2 {
    private static final int BOARD_SIZE = 8;
    // .....
    private JButton[][] buttons = new JButton[BOARD_SIZE][BOARD_SIZE];
    private JButton selectedButton = null;
    
    public Checkers2() {
        buttonPanel.setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE));
        for (int i = 0; i < buttons.length; i++) {
            for (int j = 0; j < buttons[i].length; j++) {
                buttons[i][j] = new JButton(" ");
                buttonPanel.add(buttons[i][j]);
                buttons[i][j].setFont(BTN_FONT);
                buttons[i][j].setPreferredSize(BTN_SIZE);
                buttons[i][j].addActionListener(e -> btnsActionPerormed(e));
            }
        }
        
        // ...
    }
    
    private void btnsActionPerormed(ActionEvent e) {
        JButton source = (JButton) e.getSource();
        
        // if selectedButton is null, then this is the first press      
        if (selectedButton == null) {
            if (source.getText().trim().isEmpty()) {
                return; // no button text means that there is no piece here. Exit method
            } else {
                // first set the selectedButton's value
                selectedButton = source;
                
                // change its appearance so we know that it has been pressed
                source.setForeground(Color.RED);
                source.setBackground(Color.LIGHT_GRAY);
            }
        } else {
            
            // else, if selectedButton is NOT null, then this is the second press
            // only do things if this current button is empty, if it has no text
            if (source.getText().trim().isEmpty()) {
                selectedButton.setForeground(Color.BLACK);
                selectedButton.setBackground(null);
                source.setText(selectedButton.getText());
                selectedButton.setText("");
                
                // the code below is of key importance
                selectedButton = null;
            }
        }
    }

显示的最后一位代码也是关键——在第二次按下 JButton 时,我们 re-set selectedButton 返回到 null,这样程序的状态就被设置回它的初始状态,并且监听器的初始行为可以重复。

演示这一点的简单示例程序如下所示。该程序仍未合并真正的 Checker 游戏逻辑,程序在该逻辑中确定移动是否真正合法:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;

public class Checkers2 {
    private static final int BOARD_SIZE = 8;
    private static final Font BTN_FONT = new Font("Arial", Font.BOLD, 30);
    private static final Dimension BTN_SIZE = new Dimension(100, 100);
    private JPanel buttonPanel = new JPanel();
    private JButton[][] buttons = new JButton[BOARD_SIZE][BOARD_SIZE];
    private JButton selectedButton = null;

    public Checkers2() {
        buttonPanel.setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE));
        for (int i = 0; i < buttons.length; i++) {
            for (int j = 0; j < buttons[i].length; j++) {
                buttons[i][j] = new JButton(" ");
                buttonPanel.add(buttons[i][j]);
                buttons[i][j].setFont(BTN_FONT);
                buttons[i][j].setPreferredSize(BTN_SIZE);
                buttons[i][j].addActionListener(e -> btnsActionPerormed(e));
            }
        }

        // Place "W" and "B" text in appropriate locations:
        for (int i = 0; i < 12; i++) {
            int wI = (2 * i) / BOARD_SIZE;
            int wJ = (2 * i) % BOARD_SIZE + ((wI % 2 == 1) ? 0 : 1);

            buttons[wI][wJ].setText("W");
            buttons[BOARD_SIZE - wI - 1][BOARD_SIZE - wJ - 1].setText("B");
        }
    }

    private void btnsActionPerormed(ActionEvent e) {
        JButton source = (JButton) e.getSource();

        // if selectedButton is null, then this is the first press
        if (selectedButton == null) {
            if (source.getText().trim().isEmpty()) {
                return; // no button text means that there is no piece here. Exit method
            } else {
                // first set the selectedButton's value
                selectedButton = source;

                // change its appearance so we know that it has been pressed
                source.setForeground(Color.RED);
                source.setBackground(Color.LIGHT_GRAY);
            }
        } else {

            // else, if selectedButton is NOT null, then this is the second press
            // only do things if this current button is empty, if it has no text
            if (source.getText().trim().isEmpty()) {
                selectedButton.setForeground(Color.BLACK);
                selectedButton.setBackground(null);
                source.setText(selectedButton.getText());
                selectedButton.setText("");

                // the code below is of key importance
                selectedButton = null;
            }
        }
    }

    public JPanel getButtonPanel() {
        return buttonPanel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            Checkers2 checkers = new Checkers2();

            JFrame frame = new JFrame("Checkers");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(checkers.getButtonPanel());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

}

并且我希望展示如何在编辑中合并程序逻辑。为此,我建议使用“Model-View-Controller”或 MVC 程序结构。