Home > Net >  How to have two dependent actions within an ActionListener?
How to have two dependent actions within an ActionListener?

Time:02-15

I'm trying to write a checkers AI program with a GUI in java. I've managed to initialise and fill the board so far (with pieces written as "B" and "W" for now). I've created a 2D JButton panel for the board.

I don't know how to proceed when moving pieces. My current issue is I need the player to select a preexisting piece (action 1) and then an empty space to place the selected piece (action 2). Of course these actions are dependent, and I need action 1 to happen first.

Here's what I've got so far:

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");
                        }
                    }
                }
            }
        }
    }
}

CodePudding user response:

As I mentioned in my comment, the key to a solution is to base your program's listener on "state-dependent behavior", meaning the behavior of the listener will depend on the state of the program.

For instance, if you give your program an instance field, say, selectedButton that held a reference to the last selected button, then any ActionListener's response can vary depending on what this field holds. The first time any JButton is pressed, this field will hold null since no previous JButton was pressed, and so the ActionListener will assign this button to that field. The second time that the ActionListener is called (the second time that a JButton is pressed), the field will hold a value, and so, using an if / else block that checks the state of this field, the listener's behavior can vary:

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;
            }
        }
    }

The last bit of code shown is also key -- with the second JButton press, we re-set selectedButton back to null, so that the program's state is set back to its initial state and so that the listener's initial behavior can repeat.

A simple example program that demonstrates this is as shown below. The program still has not encorporated true Checker's game logic where the program determines if a move is truly legal or not:

example of program

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);
        });
    }

}

And I hope to show how to encorporate program logic in an edit. For this, I recommend using a "Model-View-Controller" or MVC program structure.

CodePudding user response:

You need to memorize and manage the selection of a piece. You can do it by:

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

And modify the 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;
        }
    }
}

Consider using JToggleButtons instead of a JButtons .

  • Related