Good day to everyone.

I need help. Have to make more of these and need someone to show me how.

My code doesn't work the way it should.
It would have to

  • wait for you to click jbutton and then:
  • disable jbutton
  • roll die, then start timer loop to move jlabel, then stop timer and chek if die number was six, then
  • roll die again, start timer loop to move jlabel, stop timer, check if it was 6,
  • if not then
  • re-enable the jbutton, waiting for next click.

(Originally there was FOR loop instead of timer loop.)

I tried several things to change in the code, nothing works.
Button is either permanently disabled or not at all.
If 6 is rolled second timer starts over the first one without waiting to finish, messing with jlabel moving,
if you click jbutton during that, third is overlapping them and make jlabel moves even messier

I would be grateful if someone could paste the code in their IDE, see the problem, and show me what to change.

Thanks in advance.

Here's the code:

    import java.awt.EventQueue;
    import java.awt.Toolkit;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import static java.lang.Math.random;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JButton;
    import javax.swing.Timer;

    public class RunPhysics extends JFrame {
        private final int waits = 200;
        private JLabel blackBoard = new JLabel();
        private JLabel label = new JLabel("F=m*a -> O");
        private JButton roll = new JButton("Roll");
        private int labelX = 10;
        private int labelY = 60;
        private int die;
        private Timer timer;

        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable () {
                @Override
                public void run() {
                    new RunPhysics();
                }
            });
        }

        public RunPhysics() {
            setSize(1000, 200);
            setTitle("Running Physics");
            setLayout(null);
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            setVisible(true);
            getContentPane().add(blackBoard);
            blackBoard.setBounds(10, 10, 980, 280);
            blackBoard.add(label);
            blackBoard.add(roll);
            label.setBounds(30, 50, 100, 20);
            roll.setBounds(10, 10, 60, 30);

            roll.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent roller) {
                    do {labelX = 10;
                        die = (int)(random()*6+1);
                        roll.setEnabled(false);
        System.out.println(die + " was rolled.");
                        timer = new Timer( waits, new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent mover) {
                                Toolkit.getDefaultToolkit().beep();
                                label.setLocation(labelX, labelY);    
                                if (labelX >= 900) {((Timer)mover.getSource()).stop();}
                                else { labelX+=36; }
                            }
                        });
                        timer.start();
                        roll.setEnabled(true);
                    } while (die == 6); // “Dice” is the plural form of the singular noun “Die”.
                        roll.setEnabled(true);
        System.out.println("~~~~~~~~~~~~~~~~~~~~");
                }
            });
        }
    }

Recommended Answers

All 16 Replies

The reason this is confusing is that the code lines 52-57 are coded inside the loop 40-61 but they are not executed in that loop. They are executed only after the Timer has expired. It runs like:

     do {labelX = 10;
         die = (int)(random()*6+1);
         roll.setEnabled(false);
         timer = new Timer( waits, etc).start();
     } while (die == 6); 
     roll.setEnabled(true);

     ... later...

     do a few times after a time delay {
            Toolkit.getDefaultToolkit().beep();
            label.setLocation(labelX, labelY);    
            if (labelX >= 900) {((Timer)mover.getSource()).stop();}
                    else { labelX+=36; }
     }

You can see there that you are executing code immediately upon button press that should really be executed after the Timer fires. Some thing like:

button pressed:
    disable button
    start timer 

timer fired
    update label position
    if label reached limit {
          reset label position
          if a new random roll is not 6 {
              stop timer
              re-enable button
          }
     }

In other words it's your outer loop that is causing your problems. Insead of starting a loop, start a repeating timer to keep rolling and updating the label; instead of exiting the loop stop the timer.

That is exactly what I don't know how, and my deadline is approaching fast.
It is easy to change structure this ot that way, but how to preserve desired functionality?

I replaced original FOR with timer, and now have to pack outer loop there too.
I tried several ways, but didn't work.

"update label position" has to be done step by step,
with steps delayed so they can be seen,
not immediately from start to end,
which was happenig in my attempts.
So, it needs loop, which was originally FOR.

I was afraid of using FOR inside Timer because of EDT choking,
and I don't know how to slow down steps of the inner FOR loop.
Slowing it with

try {   Thread.sleep(200);     }
catch (InterruptedException e){   }

didn't work well. Gave messy out too.

Die has to be rolled in every new repetition, and checking has to be done against new roll.

Maybe to nest timer inside timer to avoid both FOR and WHILE ???
But that is another thing I don't know how. How to separate their calls ?

Thanks for your kind answer.
I wish you could clarify it some more.
All the best.

You are right about EDT choking. That's why all your "looping" has to be done by a repeating Timer, not a for/while/do loop. I'll say it again. You cannot use a loop for this exercise - it won't work. No loops, OK?

"Timer inside Timer" can be made to work, but i's messy. One timer is enough...

Just play this through in your head (yes, really, do it)...

The use has clicked to button, and you've disabled it temporarily so he can't click it again too soon.

Now you timer is going tick - tick - tick (play each tick in your head) and at each tick you:

... move the label one step (eg 36 pixels)
... if it's not at the end yet then there's nothing else to do on this tick, so return
... (the label has moved all the way across, so..)
... roll the dice to see if it's a six
... if it's not a six you have finished, so stop the timer, re-enable the button, and return
... (the dice roll was a six, so...)
... move the label back to the beginning and return
... NB: There are no loops in this code !

The pseudo-code I posted illustrates a detailed structure you could be using.

 ~~~~~~~~
... move the label
... if it's not at the end yet then there's nothing else to do on this tick, so return   <------------- THESE TWO LINES +)
... (the label has moved all the way across, so..)    <----------------------------------------------------------/
... roll the dice to see if it's a six
... if it's not a six you have finished, so stop the timer, re-enable the button, and return
... (the dice roll was a six, so...)
... move the label back to the beginning and return   <------- how to MOVE THE LABES STEP BY STEP, SEVERAL STEPS till the "end of road" without loop, before next die throw?
... NB: There are no loops in this code !
~~~~~~~~ 

+) How do you repeat movement of the label STEP BY STEP without loop before the next die throw?

That is the main part of my question from the beginning.
Sorry for not being able to find right words before.

The timer is a repeating timer that fires every 200ms
Every time it fires you move the label 36 pixels right
So it moves at a speed of 180 pixels every second (5 steps/sec)
No loop

When it reaches the end that's when you roll the die and reset the label- still part of the same timer firing.

(Line 4 is a comment - because of the if test on line 3 you will only get to line 4 if the label has reached the end of its journey)

So it will go like this (every ... is one tick):
... timer fires and disables button
... timer fires and rolls the die
... moves label (for those 36 pixels)
... check if it is end, if not, moves 36 pixels AGAIN
... (and again, if not the end of road)
... (and again, if not the end of)
... (and again, if not the end)
... (and again, if not)
... (and again, till reaches end of road)
... check again, if it is, check if previous roll was 6
... if not, then re-enable button and returns
... if yes, goes (loops?) to the beginning and repeats (rolls again and goes with moving steps)

Would it be 300 lines with "(and again)" if I want smoother movement, say for 3 pixels every 18 millisecons?
Or there is mechanism to repeat those "(and again)" inside timer's repetitions?

That has absolutely no resemblance to the pseudo-code I posted earlier (did you read it?), and definitely will not work. Maybe I confused things by talking about "ticks". In animation like this a "tick" is the same as one timer event being fired. Ie if the timer delay is 40 mSec you will get 25 ticks (timer events) per second.

Here's the pseudo code again, this time formatted more like Java to make it absolutely clear.

// this is the action listener for the button...
// it's executed once each time the (enabled) button is pressed 

       disable the button;
       start the timer;
      // that's all

// end of action listener for the button

// this is the action listener for the timer events...
// the timer will executed it once every 200 mSec

       move the label one step (36 pixels)

       if (label has not reached the end) 
           return;
           // that's all that we do for this timer event
           // nothing else happens until the next timer event
       }

       // if we get here the label has reached the end

       roll the die;
       reset the label's position;

       if (roll == 6) {
             // we go round again
             // (the label will continue moving again on the next timer event)
       } else {
            // we have finished, so...
           stop the timer;
           re-enable the button;
       }

// end of action listener for the timer events

That's the whole structure. Nothing is repeated. and there are no loops. It's really very simple, but it can take a bit of time to wrap your head round how it executes.

After that I really don't know how to make it any clearer, sorry.

ps: smoother movement. Assuming you get that working then you can play with the timer millisecs and the distance moved on each event. Shorter delays with smaller movements will look smoother. Cinema films and TVs update their images 24, 25 or 30 times a second, and that gives sufficiently smooth movement, so there's no point setting your timer delay below about 30 mSec.

This solution works just OPPOSITE of desired.

It moves label only ONCE for 36 pixels before rolling.
(Desired was to go all steps till the end before new roll).

It rolls die for every STEP, instead of at only the end of road.

Thanks for your time.
It was nice chatting with you.

No, that's not right. It does exactly what you asked. Maybe you're not reading the pseudo code correctly?

On the first move (timer event) the label will not be at the end, so you return from the actonPerformed immediatley. You don't go on to roll unless the label is at the end. (see lines 15,16 above).

Don't get frustrated or give up on this now. I've been writing code for animations like this in Java since the 1990's, and I promise you that the pseudo code above does do what you asked.

... and just to be absolutely certain I have edited your code into the structure described above and tested it. It executes exactly as you described. It took a few minutes, so it's not a big job.
Please try it. If your version does not execute as you want then post your edited code here and I'll tell you where it's going wrong.

.. still don't believe me? Ok then, here it is structured as above and working...

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static java.lang.Math.random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.Timer;

public class RunPhysics extends JFrame {

    private final int waits = 200; // 30 is smoother
    private JLabel blackBoard = new JLabel();
    private JLabel label = new JLabel("F=m*a -> O");
    private JButton roll = new JButton("Roll");
    private int labelX = 10;
    private int labelY = 60;
    private int die;
    // private Timer timer;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new RunPhysics();
            }
        });
    }

    public RunPhysics() {
        setSize(1000, 200);
        setTitle("Running Physics");
        setLayout(null);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setVisible(true);
        getContentPane().add(blackBoard);
        blackBoard.setBounds(10, 10, 980, 280);
        blackBoard.add(label);
        blackBoard.add(roll);
        label.setBounds(30, 50, 100, 20);
        roll.setBounds(10, 10, 60, 30);
        roll.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent roller) {
                roll.setEnabled(false);
                timer.start();
            }
        });
    }

    private Timer timer = new Timer(waits, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent mover) {

            labelX += 36; // += 4 is smoother
            label.setLocation(labelX, labelY);

            if (labelX < 900) {
                return;
            }

            die = (int) (random() * 6 + 1);
            System.out.println(die + " was rolled.");
            labelX = 10;

            if (die == 6) {
                // let the label continue moving
            } else {
                timer.stop();
                roll.setEnabled(true);
            }
        }
    });

}

I knew my old single-task way of thinking was messing with my Java,
but I didn't realize how much. (Pascal and fortran for DOS, 30 years ago.)

I must apologize to you for my disbelief.
And I mean REALLY APOLOGIZE.
I'm sorry I acted like a complete arsehole.

Going now to study this.
Need to understand why and how is this working, so I can use it further.

THANKS A MILLION.
And thank you for not giving up on me.

commented: It's a smart person who sees when they're wrong, and a strong person who admits it. You're welcome. JC +14

Hey, no need to apologise - I know how frustrating it can be when you look at code and you just can't see what you're looking for.
I too started on traditional procedural programming (IBM 360 PL/!) and I still remember how hard it was to switch to event-driven GUI logic on the first Macintoshes. Once you've got it it perfectly simple, but until then...

I didn't comment on or change any other aspects of your code, but if you want to discuss them further I'd be glad to help. I see you are still using anonymous inner classes for your listeners... are you limited to pre-version-8 Java?

No, I have NetBeans 8.2, Java SE 8 , Java EE 8 (not using yet).

One guy told me to not use Lambda Expressions (yet), because "sooner or later
you will compile for Android, so learn to stay compatible with Java 1.6 or lower" (his words).
So, I will most likely soon install Android SDK and NBAndroid plugin for NetBeans.

I have to address more of my thanks to you, because what was painful to you, you made less painful to me.

You might be curious to get some feedback. Here is what I expanded your code into,
with "two concatenated timers":
(Maybe I could do as continuation in single timer, but I was experimenting.
This way is more obvious which timer moves which label, and when.)

import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static java.lang.Math.random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.Timer;

public class RunPhysics extends JFrame {

private final int waits = 200; // 30 is smoother (was 200)
private final int step = 150;  // += 4 is smoother (was 36)
private JLabel blackBoard = new JLabel();
private JLabel labelOne = new JLabel("F=m*a -> O");
private JLabel labelTwo = new JLabel("a=F/m -> X");
private JButton roll = new JButton("Roll");
private int cntOne;
private int labelOneX = 10;
private int labelOneY = 60;
private int cntTwo;
private int labelTwoX = 10;
private int labelTwoY = 100;
private int die;
private int dieIN;

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            new RunPhysics();
        }
    });
}

public RunPhysics() {
    setSize(1000, 200);
    setTitle("Running Physics");
    setLayout(null);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setVisible(true);
    getContentPane().add(blackBoard);
    blackBoard.setBounds(10, 10, 980, 280);
    blackBoard.add(labelOne);
    blackBoard.add(labelTwo);
    blackBoard.add(roll);
    labelOne.setBounds(30, 60, 111, 22);
    labelTwo.setBounds(30, 100, 111, 22);
    roll.setBounds(10, 10, 80, 33);
    roll.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent roller) {
            roll.setEnabled(false);
            die = (int) (random() * 6 + 1);
            timer.start();
            roll.setEnabled(true);
        }
    });
}

private Timer timer = new Timer(waits, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent mover) {
        cntOne++;
        labelOneX = cntOne * 150;
        labelOne.setLocation(labelOneX, labelOneY);
        Toolkit.getDefaultToolkit().beep();

        if (labelOneX < die * 150) {
            return;
        }
        dieIN = (int) (random() * 6 + 1);
        System.out.println("Timer: " + + die + " was rolled.");
        cntOne = 0;
        labelOneX = 10;
        if (die == 6) { die = dieIN; } // let the labelOne continue moving

        else {
            timer.stop();
    System.out.println("----------");
            die = (int) (random() * 6 + 1);
            try { Thread.sleep(200);} catch(InterruptedException e){ }
            looper.start();
        }
    }
});

private Timer looper = new Timer(waits, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent stepper) {
        cntTwo++;
        labelTwoX = cntTwo * 150;
        labelTwo.setLocation(labelTwoX, labelTwoY);
        Toolkit.getDefaultToolkit().beep();

        if (labelTwoX < die * 150) {
            return;
        }

        dieIN = (int) (random() * 6 + 1);
        System.out.println("Looper: " + die + " was rolled.");
        cntTwo = 0;
        labelTwoX = 10;
        if (die == 6) { die = dieIN; } // let the labelTwo continue moving

        else {
            looper.stop();
    System.out.println("~~~~~~~~~~~~~~~~~~~~~");
//                roll.setEnabled(true);
            }
        }
    });
}

Thank you for your thank you. The world is such a scary place that there's no such thing as being too kind or too nice. And yes, after putting effort into trying to help, feedback is very very welcome.

The one thing in that code that made me cringe was the Thread.sleep in a swing callback. Sleeping the whole of Swing is a real no-no. I couldn't guess why you needed a 1/5 sec delay there, but of you really did then set it as an initial delay on the second timer.

Java 1.6 for Android sounds very conservative to me. Here's the official position. So yes, you should keep aware of Java 8 features and be ready for when they are standard in Android. But foir now, I guess you're stuck with anonymous inner classes and no streams.

If you have 2 labels moving then two timers seems a perfectly sensible choice. As you say, it makes the code more obvious, which for me is a massive benefit. You could think about packaging a "moving label" as a class that hides its timer as part of its internal implementation. In terms of code that would scale beautifully. Mutiple instances of javax.swing.Timer all share the same single underlying thread, so having lots of them isn't a problem.

The alternative is to have eveything moving to the same timer. The benefit of that is that you only refresh the display once per tick, regardless of the number of moving objects. You may find that that scales far better in terms of performance, because the overheads of updating the actual window on the real screen via the operating system tend to swamp whatever you are doing in your own code. In terms of code you could have a master clock and each moving object would register itself with that clock as a call-back, just like a swing listener.

If you are interested we could discuss how those either or both two approaches could look in terms of real code. For what it's worth, I've implemented apps with dozens or hundreds of objects whizzing and bouncing about the window in real time, and found the second alternative to give excellent perfomance and a good clean code structure.

Last thought (for now): if you want to develop into animated displays of physics simulations then calculating and moving JLabels inside other JLabels is going to become both messy and limiting. The standard solution is to subclass JPanel with your own painting method(s) that draw your moving objects directly in the panel. You will also separate the actual physics modelling (gravity, friction, elastic collisions etc) from the screen painting code. Once again, there's a lot more to be said about that if you're interested.

JC

Well, for each step of labels I added "beep();".
And that "sleep" marks transit from series of dings for first label, to second.
You can clearly hear the little longer pause between "beeps".
It was the first thing that came to mind when I needed independent "pause maker".

As for scaling better in single timer, in general it is both "correct" and "neat".
Only, in this case first timer ends before second begins. Labels don't move
simultaneously, so it is not critical.

~ * ~

I am interested, and I believe I will have time for it within couple of weeks.
This series is for low level audience, and when the time comes I will need every
single bit of every such knowledge.
I'm 56 y/o and after couple of decades I have to be very quick in getting back
on track, while I still have time.

Meanwhile I also have to have job again, for after my surgery I lost the old one.
Problem is, "on foot" jobs are easy to find, but with this leg I can't accept them.
And desk job is "nowhere in sight" for me, since end of April.
Being unable to find job, I will have to create my own somehow, if I can...

Java looked like good idea, and since that man mentioned Android,
I'm thinking of learning that as well.

My address is sinniman (at) hot mail. It is clogged with spam, but every
now and then (2-3 days or 2-3 weeks) I still go there and try to find
something other than just tons of junk.

P.S.: Meanwhile I've opened the link you gave me, and saw the "Moving forward,
Java 8 language features will be natively supported by the Android build system."
I hope it means what I think it does.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.