-1

I am quiet new to Java and I try to make a simple turn-based game just to gain some experience. I have encounter a problem which I cannot solve on my own, despite I did quiet a lot of google research... Probably I cannot solve it because I don't understand it well enough.

The problem is I don't know how to make a program making turns for player. I mean I would like to make a few Jbuttons active till one of them is pressed and then do the same for another player. Since program is quiet complicated, I deleted 80% of code which is unrelated to problem and I post the rest of it below.

The code is divided into classes and I would like to keep it this way due to complicity of a problem (remaining 80% of code). In order to see better what programs do, I made buttons to change colours each time when activated. Unfortunatelly program stops working as soon as it hits notify() method.

Can anyone help? Thank you in advance.

Tester:

package grafika;

import javax.swing.SwingUtilities;


public class OknoTester {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Okno main = new Okno();
            }
            
            // ctr + spacja - podpowiedzi w Eclipsie
        });

    }

}

Main class

 package grafika;

import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JSplitPane;


public class Okno {
    private JFrame frame;
            
    public Okno()
    {
        initialize();
    }

    
    private void initialize()
    {
        // NEW WINDOW
        frame = new JFrame();
        frame.setTitle("Gra w statki");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(new Dimension(600,100));
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);

        
        // DIVIDE WINDOW
        JSplitPane splitPaneH = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
        frame.getContentPane().add(splitPaneH);
        splitPaneH.setDividerSize(0);
        
        
        // INITIALIZATION OF SITES
        Komputer left = new Komputer();
        Komputer right = new Komputer();
        

        // SHOWPANELS
        splitPaneH.setLeftComponent(left.Pokaz());
        splitPaneH.setRightComponent(right.Pokaz());
        splitPaneH.setResizeWeight(0.5);
        splitPaneH.setEnabled(false);
        
        // ALLOW TO USE ONE OF LEFT OR RIGHT SET OF BUTTONS
        while(true)
        {
        left.CzytajRuch();
        right.CzytajRuch();
        }
        
        
    }

}

Class with buttons

package grafika;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;

public class Komputer implements ActionListener{

    private JButton b1 = new JButton("1");
    private JButton b2 = new JButton("2");
    private JPanel panel = new JPanel();
    private Boolean aktywny = false;
    
    
    public void CzytajRuch()
    {
        aktywny = true;
        try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }

    public JPanel Pokaz()
    {
        return panel;
    }
    

    public Komputer() {
        
        GridLayout gl = new GridLayout(1,2);
        panel.setLayout(gl);
        panel.add(b1);
        b1.addActionListener(this);
        b2.addActionListener(this);
        panel.add(b2);

    }


    public void actionPerformed(ActionEvent ae) {
        if(aktywny == true)
        {
        JButton pb = (JButton) ae.getSource();
        if (pb == b1)
            b1.setBackground(new Color((int)(Math.random() * 0x1000000)));
        if (pb == b2)
            b2.setBackground(new Color((int)(Math.random() * 0x1000000)));
        aktywny = false;
        notify();
        }
    }
    

}
 

Thank you for your time reading this. I would apprieciate any help.

Marcin K
  • 9
  • 1
  • 3
    Your code defies Swing threading rules. Google "concurrency in Swing" to see why. – DontKnowMuchBut Getting Better Jun 12 '22 at 22:16
  • 2
    Also, the way to have the GUI take turns is not to use a while true game loop or to use notify and wait but rather change key state fields as the program runs, such as the whoseTurn state and then to write your listeners to vary their response depending on state – DontKnowMuchBut Getting Better Jun 12 '22 at 22:19
  • 3
    [Concurrency in Swing](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) - Swing is single threaded, you can't use things like "wait"/"notify" because you'd be blocking the thread, so you could never wake it. As stated, maintain some kind of "state" which determines which player is active – MadProgrammer Jun 12 '22 at 22:43
  • 1
    Right, calling `wait()` from the GUI will block it completely. Basically if you're new you should stay away from `wait()` and `notify()` they are very low level and easy to get wrong. Look at classes from `java.util.concurrent` like `Semaphore` and `ConcurrentLinkedQueue` – markspace Jun 13 '22 at 00:20

1 Answers1

1

Swing is single threaded. This means you can't perform long running or blocking operations on the Event Dispatching Thread.

If you try and use wait within the context of the EDT, not only will it block the EDT and prevent any further updates or events from been processed, you will never be able to use notify (from within the EDT) to wake it.

wait and notify are meant to be used from different thread contexts (not from within the same thread context)

See Concurrency in Swing for some more details.

What you need to do, is monitor some kind of "token". Only the player with who holds the "token" can do anything.

You can distill this down to a simple flag is maintained by some kind "controller". Each time a player does something, the controller checks to see if the player is currently active or not, processes the action and the switches the active player to a different player, for example...

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public final class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    enum Player {
        LEFT, RIGHT;
    }

    public class MainPane extends JPanel {

        private Player activePlayer = Player.LEFT;
        private ControlPane leftControlPane;
        private ControlPane rightControlPane;

        private JTextArea logTextArea;

        public MainPane() {
            ControlPane.Observer observer = new ControlPane.Observer() {
                @Override
                public void playerDidPerformAction(ControlPane source, Player player) {
                    if (player != getActivePlayer()) {
                        logTextArea.append((player == Player.LEFT ? "Player 1" : "Player 2") + " tried to perform an illegal action\n");
                    } else {

                        logTextArea.append((player == Player.LEFT ? "Player 1" : "Player 2") + " did perform action\n");
                        switch (player) {
                            case LEFT:
                                activePlayer = Player.RIGHT;
                                break;
                            case RIGHT:
                                activePlayer = Player.LEFT;
                                break;
                        }
                        activePlayerDidChange();
                    }
                }
            };
            leftControlPane = new ControlPane(Player.LEFT, observer);
            rightControlPane = new ControlPane(Player.RIGHT, observer);

            logTextArea = new JTextArea(20, 20);

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();

            gbc.gridheight = GridBagConstraints.REMAINDER;
            gbc.weightx = 1;
            gbc.weighty = 1;
            gbc.fill = gbc.BOTH;
            add(leftControlPane, gbc);

            gbc.weightx = 0;
            gbc.weighty = 0;
            add(new JScrollPane(logTextArea), gbc);

            gbc.weightx = 1;
            gbc.weighty = 1;
            add(rightControlPane, gbc);

            activePlayerDidChange();
        }

        public Player getActivePlayer() {
            return activePlayer;
        }

        protected void activePlayerDidChange() {
            switch (getActivePlayer()) {
                case LEFT:
                    rightControlPane.setActive(false);
                    leftControlPane.setActive(true);
                    break;
                case RIGHT:
                    rightControlPane.setActive(true);
                    leftControlPane.setActive(false);
                    break;
            }
        }

    }

    public class ControlPane extends JPanel {
        public interface Observer {
            public void playerDidPerformAction(ControlPane source, Player player);
        }

        private Player player;
        private JButton actionButton;
        private Observer observer;

        public ControlPane(Player player, Observer observer) {
            this.player = player;
            this.observer = observer;
            actionButton = new JButton("Do stuff!");

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.insets = new Insets(8, 8, 8, 8);

            add(new JLabel(player == Player.LEFT ? "Player 1" : "Player 2"), gbc);

            gbc.weightx = 1;
            gbc.weighty = 1;
            add(actionButton, gbc);

            actionButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.playerDidPerformAction(ControlPane.this, player);
                }
            });
        }

        public void setActive(boolean active) {
            actionButton.setEnabled(active);
        }

    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you very much for valuable answer. Your code works perfectly. Now I need a few days to understand how it does work :-) Thank you once more :-) – Marcin K Jun 13 '22 at 12:06