2

I'm writing a "Who Wants to Be a Millionare" game and I have everything set up, it's just a problem with the timer.

The game works like this: if the user gets the question right he/she moves on. If not, the game is over and they can opt to play again.

When the game first runs it's fine and the timer does what it should -- start at 30 seconds and count down, displaying the seconds.

However, when the user clicks the "play again" button, the previous timer continues with the new timer. Like this:

-timerA had 20 seconds left before the user lost (indicated by a).

-timerB begins the next time the game is played (indicated by b).

output: 20a 29b 19a 28b 18a 27b 17a 26b ....... 2a 11b 1a 10b 9b 8b 7b 6b 5b 4b 3b 2b 1b

So here is my timer class called CountDown:

  import java.util.Timer;
  import java.util.TimerTask;

  public class CountDown {

      static Timer timer;
      public static int seconds = 30;

      public CountDown() {
          timer = new Timer();
          timer.schedule(new DisplayCountdown(), 0, 1000);
      }

      class DisplayCountdown extends TimerTask {

          int seconds = 30;

          public void run() {

                  if (seconds == 0) {
                      timer.cancel();
                      timer.purge();
                      return;
                  }

              if (seconds > 0) {
                  PlayFrame.timer.setText("" + seconds); //jLabel used to display seconds
                  seconds--;
                  if (seconds < 30) {
                      if (PlayFrame.right == true) { //check if question was answered correctly
                          System.out.print("true"); //testing purposes
                          PlayFrame.right = false;
                          PlayFrame.showQuestion();
                          PlayFrame.startTimer();
                          seconds = 0;
                          //break;
                      }
                      else if (PlayFrame.right == false) {
                          //break;
                      }
                  }      

                  else if (seconds == 0) { //if time runs out its automatic wrong answer
                      PlayFrame.wrong();
                      //break;
                  }      

                  else {
                      PlayFrame.wrong();
                      PlayFrame.timer.setText(null);
                      timer = new Timer();
                      //break;
                  }
              }
              System.out.println(seconds); // for testing purposes only
          }
      }
  }

and here is some of my PlayFrame:

  import java.awt.Color;
  import java.util.Timer;

  public class PlayFrame extends javax.swing.JFrame {

      public static void wrong() {
          //determines which losing frame to show
          if (count <= 2){
              LossLevel0 L0 = new LossLevel0();
              L0.setVisible(true);
          }
          else if (count > 2 && count <= 6 && count != 6){
              LossLevel1 L1 = new LossLevel1();
              L1.setVisible(true);
          }
          else {
              LossLevel1 L1 = new LossLevel1();
              L1.setVisible(true);
          }
          //"supposed" to stop the timer
          CountDown.timer.cancel();
          CountDown.timer.purge();
      }

      public static void startTimer() {
          //creates new CountDown object and timer, also resets seconds
          CountDown countdown = new CountDown();
          CountDown.timer = new Timer();
          CountDown.seconds = 30;
      }

I think the trouble may be in when I restart the game. The only code that is in there is I reset all of my variables back to its original state before the game started. Like so:

    // Reset Everything
    PlayFrame.count = 0;
    PlayFrame.answer = new String();
    PlayFrame.count = 0;
    PlayFrame.right = false;
    PlayFrame.winnings = 0;
    CountDown.seconds = 30;
    CountDown.timer = new Timer();
    CountDown.timer.cancel();
    CountDown.timer.purge();

Please help, and if you need further information just ask!

CodeAddict
  • 194
  • 2
  • 5
  • 19

3 Answers3

5

You shouldn't be using java.util.Timer with a Swing application. For these you should use javax.swing.Timer since this will respect the Swing event thread. Thus your question is moot, and I suggest that you throw all of your timing code out and try the proper timer. You can find the tutorial here: Swing Timer Tutorial

Next you'll want to get rid of all the static methods and fields that your code is using and change them over for instance fields and methods.

An example of a Swing Timer displaying remaining time in a JLabel:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class TimerExample {

   private static void createAndShowGui() {
      Gui mainPanel = new Gui();

      JFrame frame = new JFrame("TimerExample");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

class Gui extends JPanel {
   private static final long serialVersionUID = 1L;
   private static final String PRESS_BUTTON = "Press Button ";
   private static final String PRESS_BUTTON_AGAIN = "Press Button Again Within %02d Seconds!";
   private static final String YOU_LOSE = "You Lose!";
   private static final String YOU_WIN = "You Win!";
   private static final int PREF_W = 250;
   private static final int PREF_H = 100;
   private static final int TOTAL_SECONDS = 20;
   private static final int ONE_SECOND = 1000;

   private int elapsedSeconds = 0;
   private JLabel timerLabel = new JLabel(PRESS_BUTTON);
   private JButton button = new JButton("Button");
   private Timer myTimer;

   public Gui() {
      JPanel btnPanel = new JPanel();
      btnPanel.add(button);

      setLayout(new BorderLayout());
      add(btnPanel, BorderLayout.CENTER);
      add(timerLabel, BorderLayout.SOUTH);

      button.addActionListener(new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            if (myTimer != null && myTimer.isRunning()) {
               myTimer.stop();
               myTimer = null;
               timerLabel.setText(YOU_WIN);               
            } else {
               elapsedSeconds = 0;
               myTimer = new Timer(ONE_SECOND, new TimerListener());
               myTimer.start();
               String text = String.format(PRESS_BUTTON_AGAIN, TOTAL_SECONDS); 
               timerLabel.setText(text);
            }
         }
      });
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private class TimerListener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         elapsedSeconds++;

         if (elapsedSeconds == TOTAL_SECONDS) {
            myTimer.stop();
            timerLabel.setText(YOU_LOSE);
         } else {
            String text = String.format(PRESS_BUTTON_AGAIN, TOTAL_SECONDS - elapsedSeconds);
            timerLabel.setText(text );
         }
      }
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Well that's not ideal. I have everything working it's just that the previous timer won't stop, even when I call cancel() and purge() on it. And (yes im a newbie) I'm confused by ActionListeners; because I write in Netbeans it does it automatically for me. – CodeAddict Jan 18 '13 at 02:01
  • 1
    @CodeAddict: it's not a matter of if it is *ideal* or not. It's wrong and may not work, pure and simple. If ActionListeners confuse you, the same tutorial that I've linked to above has a section on how to use ActionListeners. And of course if you get stuck, you can always come back here and show your attempt. – Hovercraft Full Of Eels Jan 18 '13 at 02:02
  • But it does work. I just want to know how to get one timer to stop. ive tried the cancel and purge methods on it but that doesnt seem to do it. also, this only happens if i click on play again, it works awesome the first time around. so I think that's it instead of the timer, maybe im just not resetting something right – CodeAddict Jan 18 '13 at 02:05
  • @CodeAddict: Simply use a Swing Timer. When it's time for a new game, create a new Swing Timer instance. Simple as that. But also you need to get rid of statics so that your objects will behave like true objects and interact well. – Hovercraft Full Of Eels Jan 18 '13 at 02:07
  • and i would do so by stating `Timer newTime = new Timer();` ? and I have other static objects in PlayFrame that won't work with these if I took it out, along with static methods – CodeAddict Jan 18 '13 at 02:09
  • @CodeAddict: `and i would do so by stating Timer newTime = new Timer();` -- No, the Swing Timer constructor doesn't look like that. Again, please look at the tutorial I've linked to above. `... and I have other static objects in PlayFrame that won't work with these if I took it out, along with static methods` -- Then you have to fix that code too. Else you're just adding on to bad code and will end up with one unholy mess. – Hovercraft Full Of Eels Jan 18 '13 at 02:11
  • @CodeAddict: is this for a class or personal education? – Hovercraft Full Of Eels Jan 18 '13 at 02:12
  • personal education, im not in a class right now. im trying to write games and i found one I thought I could write pretty well. here is the link: http://www.math-play.com/square-roots-game.html – CodeAddict Jan 18 '13 at 02:13
  • You may be better off learning about how to create classes and about basic object-oriented coding principles before trying game or GUI programming. It can help prevent a lot of frustration. – Hovercraft Full Of Eels Jan 18 '13 at 02:15
  • I took a basic Java class last year, I'm doing this purely to prove to myself that I can take a simple game and recreate it in java. im not starting over; ive gotten too far. Maybe I can find a way around this myself since all you're telling me is to start over. Thanks for your suggestions, I may come back later and try it your way if I make a new program, but I'm afraid I'm too far deep into this one. Thanks – CodeAddict Jan 18 '13 at 02:18
  • 2
    @CodeAddict I am also a pretty new programmer and I have to say that Swing Timer is the way to go. I've only used it once, but it was way easy. I would almost use it over the util Timer even I weren't using a GUI. Really you should take Hovercraft's advice. This guy gives a really great explanation http://albertattard.blogspot.com/2008/09/practical-example-of-swing-timer.html – sage88 Jan 18 '13 at 03:02
  • +1 nice example. @CodeAddict *I'm too far deep into this one* , Java is Object Orientated which should make usability and re-usability etc easier, this gives me the impression you are not taking full advantage of OOP... Implementing changes unless maybe to the logic should be easy to do in a MVC (Model-View-Controller) program. – David Kroukamp Jan 18 '13 at 21:01
  • @sage88 I do love `Swing Timer`s, but I cannot fully agree with using `Swing Timer` over `java.util.Timer`, even in Gui app sometimes `java.util.Timer` is better. We tend to forget `Swing Timer` is executed on EDT, thus, any long running task within a `Swing Timer` would freeze the Gui; where as `java.util.Timer` would allow us to wrap only necessary code in `SwingUtilities.invokeXX` block, and keep the rest off EDT. Well thats my 2 cents... – David Kroukamp Jan 18 '13 at 21:09
  • 1
    @David Kroukamp I understand that argument, but I think I might opt to use Swing Timer with a SwingWorker to prevent the long running task from ending up on the EDT. However, you're right, using util Timer would circumvent this as well. – sage88 Jan 18 '13 at 21:39
  • @sage88, this website provided exactly what I needed, thanks so much! The only thing is that some of the numbers go faster than the others but that's probably more of what I put as the numbers than a bug. – CodeAddict Jan 18 '13 at 21:42
  • 2
    @DavidKroukamp: that's true of anything that runs on the EDT. The Swing Timer is strictly for timing and that's it. If code calls a long-running process, then that needs to be on a background thread. The advantage of the Swing Timer is that its ActionListener code is run on the EDT saving you from having to queue up a Runnable. On the other hand, there's also the [ScheduledThreadExecutor](http://stackoverflow.com/questions/409932/java-timer-vs-executorservice) which is sometimes better to use than a util Timer for time critical code. – Hovercraft Full Of Eels Jan 18 '13 at 22:12
1

What I recommend doing is moving the Timer declaration into the JFrame, and then creating a new instance of your "DisplayCountdown" class for each time the timer is needed, and move the "timer.schedule(new DisplayCountdown(), 0, 1000);" line elsewhere.

If you purge a timer object, it is completely reset, so you don't need to create a new timer object, only add a new TimerTask.

EAKAE
  • 153
  • 1
  • 11
  • creating a new instance of your "DisplayCountdown" class means what syntax wise? if i moved the `timer.schedule` line into the `run` block would that help? and how would I add a new TimerTask? sorry, the Timer class confuses me with the purge() an cancel() methods – CodeAddict Jan 18 '13 at 02:26
  • so in the main application, I think you called it PlayFrame, you'd create a private Timer variable, and whenever you started the Timer, add this line instead: "timer.schedule(new DisplayCountdown(), 0, 1000);". When the Countdown finishes, if it times out, it auto clears itself, so you shouldn't need to call purge anywhere but in the run of the DisplayCountdown class and the wrong function of the PlayFrame class. – EAKAE Jan 18 '13 at 02:41
  • I get `an enclosing instance that contains CountDown.DisplayCountdown is required` (because DisplayCountdown is in the CountDown class). okay, but if the answer is answered correctly i want the timer to completely go away, never use it again, and make a new one – CodeAddict Jan 18 '13 at 02:44
  • Using the cancel and purge functions does the exact same thing as creating a new timer, but should clear the stored memory better. In multi-threading, it is always a good idea to use the provided cleanup functions, like with streams. – EAKAE Jan 18 '13 at 02:49
  • the memory isn't getting cleared as far as I can tell. It just keeps going, it won't stop until it reaches 0. Like a woman on a mission that wil not stop until she reaches the goal. I'm not familiar with that last sentence is talking about tho. – CodeAddict Jan 18 '13 at 02:54
  • Hmm, you're getting the requires CountDown.DisplayCountdown because DisplayCountdown isn't in a file of it's own. You can try moving it to it's own file and remove the CountDown class completely. This would show wherever you are using CountDown.timer instead of timer, which would cause errors, but if this doesn't work I'd need to look at the new source code. A-lot of it has changed. – EAKAE Jan 18 '13 at 03:04
-1

Solved, thanks for the link sage88! http://albertattard.blogspot.com/2008/09/practical-example-of-swing-timer.html

And for more help on swing timers (for future searches of this topic) http://www.asjava.com/swing/java-timer-tutorial/

public static void startTimer() {
    listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent event) {
            System.out.print("action");
            timerLabel.setText("" + seconds);
            seconds--;
            System.out.println(seconds);
            if (seconds < 0){
                System.out.print("zero");
                wrong();
            }
        }
    };
    displayTimer = new Timer(1000, listener);
    displayTimer.setInitialDelay(1);
    displayTimer.start();

    if (right == true){
        System.out.print("true");
        displayTimer.stop();
        right = false;
        seconds = 30;
        displayTimer = new Timer(10000, listener);
        displayTimer.setDelay(10000);
        displayTimer.setInitialDelay(1);
        displayTimer.start();
    }
    else if (right == null){
        System.out.print("null");
        displayTimer.stop();
        seconds = 30;
        displayTimer = new Timer(10000, listener);
        displayTimer.setInitialDelay(1);
        displayTimer.setDelay(10000);
        displayTimer.start();
    }
}
CodeAddict
  • 194
  • 2
  • 5
  • 19
  • 1
    You're welcome! It helped me out, so I figured I'd pass it along. He does some great coverage of SwingWorker which you might take a look at as well. Really helpful to know about. – sage88 Jan 19 '13 at 03:49