I am making a simple board game in java, where I want to animate a dice roll. So I flash pictures of a dice like this:
public Timer roll_dice = new Timer(50, this);
...
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
movePiece();
}else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
}
movePiece(){
//do some more painting
}
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
Does anyone have any ideas how I can call movePiece only after the final repaint has happened?
So the die is going so show random numbers for a few times, and then slowly settle on a number. After that is done I would like to call the movePiece() method. However, as it is, the the repaint occurs sporadically and screws everything up so that movePiece() gets called before the dice roll is actually finished animating.
What worries me here is why your painting is occurring sporadically -- it simply shouldn't be doing that, and perhaps that is what you need to fix. I wonder if you're reading in the images from the file each time you do the drawing or some other cause for slowing the drawing down. If you need more help regarding this issue, then you'll have to give us more information on how you do your painting. Regardless, you should avoid having program logic be dependent on painting as you don't have full control over when or even if painting will occur.
Rather than redrawing images and calling repaint(), why not simply put your rolling dice images into ImageIcons on program start up, and then in your Swing Timer, swap icons in a JLabel? Then stop your Timer when the delay gets long enough and in that if block, move your piece.
So, assuming that you have several dice, each can be displayed by a JLabel that is held in an array of JLabel called diceLabels, and the ImageIcons can be held in an array called diceIcons. Then you can do something like:
public void actionPerformed(ActionEvent e) {
if (roll_dice.getDelay() > 500) {
roll_dice.setDelay(50);
roll_dice.stop();
movePiece(); // I like this -- this shouldn't change
} else {
roll_dice.setDelay(roll_dice.getDelay() + 50);
// dice_panel.repaint(0);
for (JLabel dieLabel : diceLabels) {
int randomIndex = random.nextInt(diceIcons.length);
dieLabel.setIcon(diceIcons[randomIndex]);
}
}
}
I like your logic on when you call movePiece() and I think that this should remain unchanged.
You can call the rolling in another thread and join() the current thread to the rolling one. That way the main code will wait until the roll thread dies (finished rolling).
public void actionPerformed(ActionEvent evt) {
if(roll_dice.getDelay() > 500){
Thread rollerThread = new RollerThread();
rollerThread.start();
rollerThread.join();
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
private RollerThread extends Thread
{
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
}
However, this might not work with the EDT - because repaints should be scheduled to the queue. Maybe you can shedule the event using the SwingUtilities.invokeAndWait():
public void actionPerformed(ActionEvent evt) {
Thread thread = new Thread(){
public void run(){
if(roll_dice.getDelay() > 500){
SwingUtilities.invokeAndWait(new Runnable(){
public void run(){
roll_dice.setDelay(50);
roll_dice.stop();
}
});
movePiece();
}
else{
roll_dice.setDelay(roll_dice.getDelay() + 50);
dice_panel.repaint(0);
}
}
};
thread.start();
}
Does anything change if you put that call to movePiece(); in a SwingUtilities.invokeLater(Runnable);?
if(roll_dice.getDelay() > 500){
roll_dice.setDelay(50);
roll_dice.stop();
SwingUtilities.invokeLater(new Runnable() {
public void run() { movePiece(); }
});
}
...
Related
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
Original Question
I'm currently working on a simple application that displays a map and will later implement pathfinding logic for units. I've implemented the map and view so far and everything runs just fine until I implemented the game loop.
With the game loop enabled, the program just freezes. I can't close the window anymore and the map isn't presented, even though the game loop is executed just fine. I've used this game loop twice in the past and never had any problems until now.
Edit: The game loop continues to execute just fine while everything else freezes.
Here are the two functions involved:
public GameController() {
paused = true;
frame = new GameFrame(this);
map = new Map(500, 500);
mvm = new MapViewModel(map.getMap(), map.getWidth(), map.getHeight());
//TODO: gameLoop() currently breaks the game.
gameLoop();
}
public void gameLoop() {
double tickTime, lastTick;
for (;;) {
tickTime = System.nanoTime();
lastTick = tickTime;
//Repaints the frame
update();
while (tickTime - lastTick < NANOSECONDS_PER_UPDATE) {
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {}
tickTime = System.nanoTime();
}
}
}
edit2: I'm using Swing. The actual painting happens in the paintComponent method of my GamePanel (JPanel):
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//Paints the map
painter.paintMap(g2, controller.getMvm());
}
Obviously, if you have any further questions feel free to ask. Thanks in advance.
Solution
Here's the code I'm using now, GameController and update haven't changed.
public void gameLoop() {
timer = new Timer(MILLISECONDS_PER_UPDATE, updater);
timer.start();
}
updater is an ActionListener that I have added as a private variable to the class.
private ActionListener updater = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("test2");
update();
}
};
You could add updater locally but I prefer it this way.
You tell us nothing about what GUI library you might be using, but assuming that it might be Swing, it looks like you're running some long-running code on the Swing event thread, the main thread responsible for doing all Swing graphics and for interacting with the user. If so, then the code will prevent Swing from redrawing the GUI, freezing your application.
My suggestions are:
Don't do this. Don't run any long-running code on the main event thread.
Instead consider using a Swing Timer to do your "game loop".
Or if you must use your while loop and Thread sleep, do it off of the event thread, but then be sure that all Swing calls that mutate the state of Swing objects be done on the event thread.
For more on Swing threading, please read Concurrency in Swing.
I have a Frame that is all one color, but has some text in the corner. I set the color, actually read the color from the monitor, and then do some computations based on those measurements.
The problem is, calling repaint() causes the Frame to be painted after I do the measurements. I'm assuming this is due to repaint() delegating to the EDT, but I'm getting incorrect results due to the measurements occurring before/during the actual painting work.
My initial thought was to put a listener on paint completion, but I repaint to update the text much more frequently than I do for the color and I don't want to listen to those events. How can I wait for the actual painting task to finish before taking my measurement?
Amazing what you can find...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
PaintEvent pe = (PaintEvent) event;
String type = "";
if (pe.getID() == PaintEvent.PAINT) {
type = "PAINT";
} else if (pe.getID() == PaintEvent.PAINT_FIRST) {
type = "PAINT_FIRST";
} else if (pe.getID() == PaintEvent.PAINT_LAST) {
type = "PAINT_LAST";
} else if (pe.getID() == PaintEvent.UPDATE) {
type = "UPDATE";
}
System.out.println(type + "; pe.UpdateRec = " + pe.getUpdateRect() + "; pe.component = " + pe.getComponent());
}
}, AWTEvent.PAINT_EVENT_MASK);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
Now, because repaint requests can come think and fast, I'd be tempted to place a small "delay" in that would fired shortly after the last request has completed...
private Timer updateTimer;
// ...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
updateTimer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Update compulations here...
}
});
updateTimer.setRepeats(false);
updateTimer.setCoalesce(true);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
updateTimer.restart();
}
}, AWTEvent.PAINT_EVENT_MASK);
}
}
The idea is to allow at least 250 milliseconds between the last repaint request and the start of your compilations. You might like to play around with these values a bit and see what suits you...
UPDATE
You could also try JComponent.paintImmediately
Paints the specified region in this component and all of its
descendants that overlap the region, immediately.
It's rarely necessary to call this method. In most cases it's more
efficient to call repaint, which defers the actual painting and can
collapse redundant requests into a single paint call. This method is
useful if one needs to update the display while the current event is
being dispatched.
I know this is an old thread, but in case someone is searching for an answer to this, SwingUtilities has a method called invokeAndWait(Runnable) that runs the task on AWT dispatching thread synchronously with your current thread. Note that this cannot be done if you need it to be done inside the Event dispatching thread.
This post relates to my last one regarding a timer. I decided the easiest thing to do for immediate results was to just write a Counter thread that counts down from a certain time (in this case 5 seconds) and if the counter reaches 0, the JFrame closes and let's the user know that time has expired.
I'm running into some trouble, however. I cannot seem to make the JFrame close when the counter reaches 0. I'm not sure if I'm missing something stupid or if I am misunderstanding the way threads work and the way JFrames work. Here is the code, let me know what you think.
On a side note, I understand it would probably be most efficient to use a swing.Timer, but I just don't quite grasp the nature of them yet. I'm under self-imposed time constraints (I'm not a student or anything, I just like to stay motivated) and so I'm "jerry-rigging" this thing for now.
Anyway, on to the code!
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
public class RacerDoom extends JFrame {
boolean timesUp=false;
public RacerDoom() {
//create JFrame
super("Racer Doom Squared");
setSize(WIDTH,HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
if(timesUp==true) {
dispose();
JOptionPane.showConfirmDialog(null, "Time's Up! Click Okay to try again!");
}
Counter c1 = new Counter();
c1.start();
//Counter
private class Counter extends Thread {
public Counter() {}
public void run() {
for(int i=5;i>=0;i--) {
if(i==0) {
timesUp=true;
}
System.out.println(i);
try{
Thread.sleep(1000);
}
catch(InterruptedException e){}
}
}
}
...
EDIT: I have the timer implemented and working. It does exactly what I need it to, but I can't get the timer.stop(); command to work. I get the error "The local variable timer may not have been initialized.
Like I said, the timer works, it just never stops working until the program is terminated. Here is the constructor code for the JFrame, where the timer is located.
int counter = 0;
public RacerDoom() {
//create JFrame
super("Racer Doom Squared");
setSize(WIDTH,HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
final Timer timer=new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(counter>=10) {
timer.stop(); //the error occurs here
dispose();
JOptionPane.showConfirmDialog(null, "Time's Up!");
}
else{
counter++;
}
System.out.println(counter);
}
});
//inner thread
Move1 m1 = new Move1();
m1.start();
timer.start();
}
Thats easy to do with the help of a swing timer.. See this code sample:
final java.swing.Timer timer=new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(counter>5) {
timer.stop();
<dispose the fram here>
}else{
counter++;
}
}
});
timer.start();
I put this code in the constructor of my JFrame which will run in the Event despatch thread. If you dont want hang up your GUI, make sure that you run this timer on another thread and when you are disposing the JFrame wrap the call with SwingUtilities.invokeLater() - This ensures that the call gets queued on the event despatch thread.
I think your code is not working for the same reason, that you trying to something that does not get queued up in the event despatch thread. Here's an article that will get you going
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
I'm writing a card game. Right now I'm having problems with mouse handling. Below is the timer that handles the game flow of drawing and discarding cards.
final Timer timer = new Timer(1000, null);
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
b.players[p].drawCard();
if(p==0) // player zero is the human player
{
timer.stop();
// ...
b.players[p].discardCard(i);
timer.start();
}
else
b.players[p].discardCard(0);
p=(p+1)%4;
b.repaint();
}
});
The thing is that I want to stop the timer, wait until the user clicks the card he wants to discard, then start the timer again. b implements MouseListener in a basic way:
public void mouseClicked(MouseEvent arg0)
{
clickX = arg0.getX();
clickY = arg0.getY();
}
There's also the xYtoCardIndex() method somewhere out there.
What do I do here? I assume I have to do nothing in a nonblocking way, right?
In pseudo-code, in your MouseEventListener :
public void mouseClicked(MouseEvent arg0)
{
clickX = arg0.getX();
clickY = arg0.getY();
Card discarded = getCard(clickX,clickY);
b.players[p].discardCard(discarded);
// The card has been discarded, I can start my timer again.
timer.start();
}
In your drawCard function :
public void drawCard() {
// Stop the timer
timer.stop();
// Do the drawing.
}
This way, when the player draws a card, the timer stops until a card is discarded.
First, your code is not compiled:
b.players[p].discardCard(int i); contains a syntax error int i.
Second, I do not really understand the problem. Stop the timer when you want, implement your listener (i.e. mouse listener) that starts the timer.
Or probably I did not understand your question?
BTW I have just checked Timer API. It does not have neither start nor stop methods. You have to deal with specific tasks to control execution.