jPanel doesn't redraw, waiting for server messages - java

I'm creating a connect four game with sockets: 1 server running and two clients that should connect to play the game. I'm having some troubles with the jPanels that are not updating because of the sockets (I think).
When the game (client) starts, you get a jPanel with input box for the IP address and a submit button. If the submit button is pressed, I want to display a message that the game is being prepared and it's waiting for a second player to join.
submitButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!input.getText().equals("")) try {
prefs.put("SavedIp", input.getText());
errorLabel.setText("Preparing game and waiting for other players...");
parentLayout.startGame(input.getText());
} catch (SocketTimeoutException timeout) {
errorLabel.setText("Server timed out.");
} catch (IOException ioeException) {
errorLabel.setText("Server not found!");
} finally {
setTheCursor(Cursor.getDefaultCursor());
}
else errorLabel.setText("Enter an IP adress");
}
});
However in the startGame method, a new GamePanel is created, and in that constructor a new PlayField is created and in that PlayField a new connection is created with sockets.
Once it's connected, it runs a method checkForNextAction to see if it receives a message from the server. It keeps running that method until the server sends a message.
Problem: I don't see the message appear when I press submit. Instead it hangs until a second player joins, and then the server sends a message that the game can start and the gameboard is shown. Can't I just make the message change instantly to the text that I've set? The jPanel probably doesn't update because it's running a method and waiting for a message to come.
I have a similar problem in-game. When a player puts a coin in a column, this move is sent to the server but the coin isn't drawn on the jPanel. Instead it hangs/freezes. The other client makes his move and only then the jPanel of the first client gets updated with the coin from the first move ànd the coin from the other player in the second move. So it seems the move only gets drawn after the other player makes a move.
Does anyone have an idea? I tried to use this.revalidate() and such things but I can't seem to force the jPanel to update before he executes the startGame method.

Your problem is a Swing threading problem, since your code that waits, pauses the Swing event thread, freezing all GUI painting and user interaction. The solution: use a background thread such as a SwingWorker to do the waiting for you. Please check out Concurrency in Swing.
Something like,
submitButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!input.getText().equals("")) {
prefs.put("SavedIp", input.getText());
errorLabel.setText("Preparing game and waiting for other players...");
final ParentWorker parentWorker = new ParentWorker(input.getText());
parentWorker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
setTheCursor(Cursor.getDefaultCursor());
try {
parentWorker.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
// here's where you check for your socket or io exceptions
}
}
}
});
parentWorker.execute();
// parentLayout.startGame(input.getText());
} else {
errorLabel.setText("Enter an IP adress");
}
}
});
and...
class ParentWorker extends SwingWorker<Void, Void> {
private String text;
public ParentWorker(String text) {
this.text = text;
}
#Override
protected Void doInBackground() throws Exception {
parentLayout.startGame(text);
return null;
}
}

Related

Changing owner for ReentrantLock

So I wrote a little program which will move circles around and when they collide they will move opposite direction, however when I'm trying to delay the execution so they won't move around stupidly fast I get java.lang.IllegalMonitorStateException
Lock in canvasRender.java, creating an instance:
ReentrantLock renderLock = new ReentrantLock();
Method which will pause execution for a moment, so circles won't move around super fast.
publlic void delay(){
renderLock.unlock();
try { Thread.sleep(10); } catch (Exception e) {} ;
renderLock.lock();
}
then from another class where I create a window and add actionListener
public static void main(String[] args){
//Buttons and other elements
// ...
JButton start = new JButton("Start!");
createAndShowGUI();
}
In createAndShowGUI():
static void createAndShowGUI(){
//adding elements to panels
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start(); //this will set gameIsRunning variable to true and create models
while (gameIsRunning) {
//update(); //which has delay(); at the end of frame drawing
//but even if just put delay()
delay(); //still says exception
start.setEnabled(false); //while game is running button is unavailable
}
start.setEnabled(true);
}
});
}
In this case my lock is owned by the Thread main, but at the time when I click button 'Start!' current is Thread AWT-EventQueue-0, and so the program crashes. How to fix this issue? (or where am I silly?)
The problem is that you're calling renderLock.unlock() from AWT-EventQueue-0 after renderLock.lock() was called by main. The thread AWT-EventQueue-0 isn't allowed to call unlock() it, since it's not the thread that called lock() it in the first place.
You could probably simplify things by dropping ReentrantLock and just using synchronized.
I don't know the design of the rest of your program, but it seems to me that the contents of the while loop belong in a separate thread. You generally don't want to loop in a UI listener method (such as actionPerformed() in ActionListener) because it will freeze up the GUI.
One thing you could do is add an Object to synchronize on:
private static final Object LOCK = new Object()
Then move the game-updating logic to its own thread — something like this:
private static class GameThread extends Thread {
public GameThread() {
super("GameThread");
}
public void run() {
synchronized (LOCK) {
start();
while (gameIsRunning) {
update();
try {
// Try to sleep for 10 millis:
LOCK.wait(10);
} catch (InterruptedException ignored) { }
}
}
// Re-enable the button:
javax.swing.SwingUtilities.invokeLater(() -> start.setEnabled(true));
}
}
And you can change your ActionListener to simply disable the button and start a GameThread:
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
synchronized(LOCK) {
if(!gameIsRunning) {
new GameThread().start();
}
}
}
});
Any other code that checks or modifies the state of the game should also be enclosed within a synchronized (LOCK) block. And if update() modifies the GUI as well as the game state, then it probably needs to do so with SwingUtilities.invokeLater().
It might also make things more clear to rename start() to setupGame() and JButton start to JButton startButton.

Java Swing and Thread sleeping

I decided to make something fun as a break from reading programming books, and I have hit a snag. This is my first swing program, and am ready to wrap it up!
The problem: I clearly don't understand how threads work with Swing. I am writing a GUI for a black jack game, and I did all of the functionality first e.g. drawing a new card to the screen when the player hits, showing the dealer hit after the player decides to stay, etc. This all works.
When I added in the logic to check for a user bust when hitting, or who wins when the user decides to stay, the game instantly goes to the win/loss screen before drawing either: the card the user got that caused a bust, or; the cards that the dealer drew when hitting (if any).
I tried inserting Thread.sleep in various places, to no avail. The program would sleep before drawing the card, then end instantly as above (even though it was placed logically after the call to draw, and before the call to calculate a winner).
also I tried to follow the MVC paradigm here, just fyi.
P.S. my program runs on one thread, I do not explicitly instantiate another, but I vaguely remember reading that Swing spawns it's own thread for graphics stuff
Sorry for that long intro! Here is some code:
The Model class' pertinent methods
void hit() {
//run when button is clicked
player.hand.add(deck.deck.get(0));
deck.deck.remove(0);
}
boolean isBust() {
if (player.getScore() > 21)
return true;
return false;
}
void dealerHit() {
while (dealer.getScore() < 17) { //could implement soft 17 rules for more difficulty
dealer.hand.add(deck.deck.get(0));
setChanged();
notifyObservers();
deck.deck.remove(0);
//Here was one attempt
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
boolean isWin() {
//and another
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((player.getScore() > dealer.getScore() && player.getScore() <= 21) || dealer.getScore() > 21)
return true;
return false;
}
void stay() {
dealerHit();
isWin();
}
View Class
void addHitListener(ActionListener HitListener) {
hit.addActionListener(HitListener);
}
void addStartListener(ActionListener StartListener) {
start.addActionListener(StartListener);
}
void addStayListener(ActionListener StayListener) {
stay.addActionListener(StayListener);
}
void display() {
JFrame myFrame = new JFrame("BlackJack");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setContentPane(this);
myFrame.setPreferredSize(new Dimension(700,550));
myFrame.pack();
myFrame.setVisible(true);
}
void addCards(Player p, Dealer d) {
topPanel.remove(start);
pcardPanel.add(playerlabel);
dcardPanel.add(dealerlabel);
for (Card c : p.hand) {
ImageIcon cc = new ImageIcon(c.img);
JLabel cC = new JLabel(cc);
//cC.setAlignmentX(alignmentX); use to get X alignment of card 1 & 2 for splits
//cC.setAlignmentY(alignmentY); same for Y, then increment by .3f
pcardPanel.add(cC);
}
for (Card c : d.hand)
dcardPanel.add(new JLabel(new ImageIcon(c.img)));
topPanel.add(new JLabel("Options: "));
topPanel.add(hit);
topPanel.add(stay);
validate();
repaint();
}
void endGame(boolean isWin) {
//I think I tried here, too
removeAll();
setBackground(new Color(0, 122, 0));
if (isWin == true)
add(new JLabel("You won!"));
else
add(new JLabel("You Lost"));
validate();
repaint();
}
public void hitPlayer(Player p) {
JLabel hits = new JLabel(new ImageIcon(p.hand.get(p.hand.size()-1).img));
//hits.setAlignmentY(alignmentY);
pcardPanel.add(hits);
validate();
repaint();
}
public void hitDealer(Dealer d) {
dcardPanel.add(new JLabel(new ImageIcon(d.hand.get(d.hand.size()-1).img)));
validate();
repaint();
}
Controller class:
public class Controller implements Observer {
BlackJack game;
Table t;
Controller(BlackJack game, Table t) {
this.game = game;
this.t = t;
this.game.addObserver(this);
this.t.addHitListener(new HitListener());
this.t.addStartListener(new StartListener());
this.t.addStayListener(new StayListener());
}
public void go() {
t.display();
}
public void update(Observable obj, Object observed) {
t.hitDealer(game.getDealer());
}
class HitListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
game.hit();
t.hitPlayer(game.getPlayer());
if (game.isBust() == true)
t.endGame(false);
}
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.addCards(game.getPlayer(), game.getDealer());
}
}
class StayListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
game.stay();
//doStay();
if (game.isWin() == true)
t.endGame(true);
else
t.endGame(false);
}
}
I just had a thought, since I'm doing this in the actionPerformed methods, could that be why sleep seemed to affect the GUI thread, and not draw the card(s) then sleep. I bet that is it. But I'm going to eat dinner, hopefully someone smarter than myself can lend a hand! Thanks in advance
P.P.S. if there are any typos (I don't think there are) just know that it all compiles and works! And no warnings, if that helps
Swing is indeed a single-thread library, like most UIs. There are also many optimizations to make it work fast. Case in point - most paintings are cached and displayed together. Even if this was not the case, you'd be relying on the speed of the system, which is not a good idea.
If you want a delayed action, you need to use swing's timer (not to be confused with the other timer class). That class has an action listener that goes off when the timer expires. In your case, you'd detect the win/bust condition, start the timer (e.g to fire in 2 seconds) and continue the drawing as usual.
This is a little too long for me to read and understand... But I guess your problem is because you are processing everything in the Event Dispatching Thread.
When you paint something on the GUI and then have more processing, the paint will actually reflect on the GUI only when the whole of the thread has finished processing. That's why you are not seeing your drawing before your Thread.sleep method.
Instead you should use SwingWorker to split your processing and GUI updating to different threads.
Look at this https://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

How do I run a terminate command on JFrame exit?

I know the basics behind trying to write code that terminates a thread, but I've run into a bit of a problem.
I've got a JButton in a JFrame GUI that launches the simulation I'm trying to animate. It is called in the ActionPerformed code of the JButton by
new AnswerWorker().execute();
The AnswerWorker class, in turn, extends SwingWorker so that the animation frame can be drawn while the GUI is still active.
public class AnswerWorker extends SwingWorker<String, Integer> {
protected String doInBackground() throws Exception
{
Threading threading = new Threading();
return null;
}
protected void done()
{
try {
JOptionPane.showMessageDialog(InputGUI.this, AMEC.unsuccesfulpercentage + "% of iterations had trucks that had to sleep over");
AMEC.unsuccesfulpercentage = 0;
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
To create a way of stopping the simulation thread, I created the class Threading, that calls the function that runs the simulation.
public class Threading extends Thread {
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
AMEC.runsimulation();
} catch (IOException ex) {
Logger.getLogger(InputGUI.class.getName()).log(Level.SEVERE, null, ex);
} catch (InterruptedException ex) {
Logger.getLogger(InputGUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
return;
}
}
Now, in the runsimulation() function, I initialize a JFrame, and I want to terminate the thread running the simulation if the JFrame is closed by clicking on its close button. How do I do this?
EDIT: all of the above code is called in a file InputGUI.java that contains all my GUI elements. The runsimulation function is in my main project file, AMEC.java
You could override the dispose() method on the JFrame to include a call to stop the thread
#Override
dispose(){
stopThread();
super.dispose();
}
How do I run a terminate command on JFrame exit?
add WindowListener to JFrame, from windowClosing event you have to call JFrame.setVisible(false) and then to call SwingWorker
after SwingWorker ended to terminate current JVM, to show a JOptionPane in the case that any exception raised, or to show JFrame again back to screen, required to change DefaultCloseOperation, to HIDE_ON_CLOSE

Updating the JPanel in a JFrame

I have a JFrame with a CardLayout component. I am using the CardLayout to switch between different JPanel's at different moments of the application execution. At some point I am using a SwingWorker Object to generate some XML files. In this time I want to display another JPanel in my window to tell the user to wait. On this JPanel I want to switch between 3 labels.
JLabel 1 would be : "Please wait."
JLabel 2 would be : "Please wait.."
JLabel 3 would be : "Please wait..."
Right now the code looks like this:
ConvertersWorker.execute();
CLayout.show(Cards, WAIT_PANEL);
Timer t =new Timer(500, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e) {
WaitPanel.SwitchLabels();
}
});
t.start();
while (!this.finishedConverting)
{
}
//After this im am executing other methods based on the generated XML files
The SwingWorker code:
SwingWorker<Boolean, Void> ConvertersWorker = new SwingWorker<Boolean, Void>() {
public Boolean doInBackground() {
Boolean result = RunConverters();
return result;
}
public void done() {
try {
finishedConverting = get();
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
}
} ;
The second JPanel is not even displayed because the JFrame blocks. It blocks because of the while loop but I don't know how to implement it differently. And also the method done() from the SwingWorker is never executed. If it were executed then the finishedConverting variable would have been set to true and the while loop would have stopped. Can anyone help me to find a better solution?
I know you solved this but that's happening because you are using just one thread, and it blocked because of the While, so you need to create a new thread to handle this
new Thread(){
public void run() {
//code here
}
}.start();
and to refresh the content of a JPanel you can use
myJpanel.doLayout();
or
myJpanel.repaint();
I removed the while loop and moved the code which was after the loop in another method which is executed in the done() method of the SwingWorker so now it works.

Java: Can't get wait/notify to work properly - Concurrency issue

It's been a long time since I had to deal with concurrent programming in Java (it was in concurrent programming college classes, actually) and I'm having what to seem some pretty basic problem. The sample code below might seem kinda weird since I'm not using standard JDK for UI controls, but it goes like this:
//class Screen
public class Screen{
private Frame rootContainer;
public Screen(Frame rootContainer){
this.rootContainer = rootContainer;
this.createGui();
}
private void createGui(){
Button btn = new Button("Call");
btn.setBounds(20, 20, 100, 20);
rootContainer.add(btn);
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ConcurrentDialog cr = createDialog();
cr.start();
//Suposedly only to be called after the Exit button in the dialog is clicked
((Button)e.getSource()).setLabel("Called");
((Button)e.getSource()).repaint();
}
});
}
private ConcurrentDialog createDialog(){
return new ConcurrentDialog(rootContainer, this);
}
}
//Class ConcurrentDialog
public class ConcurrentDialog extends Thread {
private Frame rootContainer;
private Screen screen;
public ConcurrentDialog(Frame rootContainer, Screen screen){
this.rootContainer = rootContainer;
this.screen = screen;
}
public void run(){
createDialog();
synchronized(screen){
try {
screen.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
private void createDialog(){
Dialog dialog = new Dialog(rootContainer, true);
dialog.setBounds(20, 20, 110, 35);
Button btn = new Button("Exit");
btn.setBounds(5, 5, 100, 20);
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Button source = (Button)e.getSource();
Dialog dialog = (Dialog)source.getParent();
synchronized(screen){
screen.notify();
}
dialog.dispose();
dialog.getOwner().remove(dialog);
dialog = null;
}
});
dialog.add(btn);
dialog.show();
}
}
Before anyone asks, yes, I'm trying to implement a modal dialog (in fact, I should rename ConcurrentDialog to ModalDialog). As I said before, I'm not using swing (just because I CAN'T... Embeded VM's are usually platform-specific when it comes to UI and that's my case) and this particular library doesn't have a native Modal Dialog (meaning no JOptionPane.showMessageDialog love for me) so I'm building one from scratch.
Anyway, here's the problem: It seems that the wait() method is executed much later than ((Button)e.getSource()).setLabel("Called"). A workaround that I found is setting btn as a global attribute with public access and refactor the run() method to this:
public void run(){
createDialog();
synchronized(screen){
try {
screen.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
screen.getBtn().setLabel("Called");
screen.getBtn().repaint();
}
It works this way, but in my "real world" scenario, this implementation would cause quite a mess when it comes to coupling. Any pointers?
your cr.start() returns immediately.
What you want is to put the wait(screen) instead of after cr.start(), and remove wait(screen) from run() method.
This way the thread will show a dialog and exit. Once the dialog is closed, screen will be notified, and your createGui().actionPerformed() will wake up.
you can't use multiple threads with swing/awt, and you can't "wait" on the thread which is running the gui, or it will stop working. the way to do asynchronous work which involves the gui is to spawn a separate thread (as you are doing), and then have that thread use SwingUtilities.invokeLater() whenever it needs to "update" the gui.

Categories