My program is a memory game. Whenever a tile is clicked, the timer would start. I am trying to create a menu for pause and resume of the Swing Timer. The pause works just fine; however, the problem I am experiencing is whenever I resume the timer after the pause, it would skip four seconds instead of continuing the timer The specific methods for the timer is posted below the whole program code. Here's what I currently have:
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.*;
import java.util.Collections;
import java.util.Calendar;
import java.time.*;
public class MemoryGame implements ActionListener {
private Timer cdTimer; //Count down timer of 1.5 secs for unmatched pairs
private Timer swTimer; //Main timer for the game
private int count = 0;
private long start = Calendar.getInstance().getTimeInMillis();
private long now;
private long elapsed;
boolean match = false; //Determine if the cdTimer is running or not
private JToggleButton[] buttons;
private JToggleButton first; //Variable for the first button to match
private JLabel time; //Label to hold the
private JMenuItem pause;
private JMenuItem resume;
ArrayList<ImageIcon> iconList = new ArrayList();
ArrayList<JToggleButton> retireButton = new ArrayList();
ImageIcon icon = new ImageIcon("MemoryGame.png");
public MemoryGame() {
JFrame jfrm = new JFrame("Memory Game");
jfrm.setSize(1000, 1000);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfrm.setIconImage(icon.getImage());
time = new JLabel("Elapsed time is 00:00:00");
GridLayout layout = new GridLayout(3,4);
JPanel gamePanel = new JPanel();
gamePanel.setLayout(layout);
createIcons(); //set icons for the tiles
buttons = new JToggleButton[12];
for(int i = 0; i < buttons.length; i++) {
JToggleButton btn = new JToggleButton(icon);
buttons[i] = btn;
buttons[i].addActionListener(this);
gamePanel.add(buttons[i]);
}
Collections.shuffle(Arrays.asList(buttons));
Collections.shuffle(iconList);
jfrm.add(gamePanel, BorderLayout.CENTER);
time.setHorizontalAlignment(JLabel.CENTER);
time.setVerticalAlignment(JLabel.CENTER);
jfrm.add(time, BorderLayout.NORTH);
//Create menus
JMenuBar jm = new JMenuBar();
JMenu action = new JMenu("Action");
action.setMnemonic(KeyEvent.VK_A);
JMenu gameTimer = new JMenu("Game Timer");
gameTimer.setMnemonic(KeyEvent.VK_T);
pause = new JMenuItem("Pause", KeyEvent.VK_P);
pause.setAccelerator(KeyStroke.getKeyStroke("control P"));
pause.setEnabled(false);
pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
elapsed += now;
swTimer.stop();
}
});
resume = new JMenuItem("Resume", KeyEvent.VK_R);
resume.setAccelerator(KeyStroke.getKeyStroke("control R"));
resume.setEnabled(false);
resume.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
timerContinue();
}
});
gameTimer.add(pause);
gameTimer.add(resume);
action.add(gameTimer);
JMenuItem reveal = new JMenuItem("Reveal", KeyEvent.VK_R);
reveal.addActionListener(this);
action.add(reveal);
action.addSeparator();
JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
exit.addActionListener(this);
action.add(exit);
JMenu help = new JMenu("Help");
help.setMnemonic(KeyEvent.VK_H);
JMenuItem viewHelp = new JMenuItem("View Help...",
KeyEvent.VK_H);
viewHelp.addActionListener(this);
help.add(viewHelp);
help.addSeparator();
JMenuItem about = new JMenuItem("About", KeyEvent.VK_A);
about.addActionListener(this);
help.add(about);
jm.add(action);
jm.add(help);
jfrm.setJMenuBar(jm);
jfrm.setLocationRelativeTo(null);
jfrm.setVisible(true);
}
public void actionPerformed(ActionEvent e){
//this if makes sure the timer does not restart everytime a button is clicked
if(retireButton.size() == 0){
timerStart();
}
if(swTimer.isRunning()){
pause.setEnabled(true);
resume.setEnabled(true);
}
//this if makes sure no button will be input during the 1.5 secs delay
if(match == false){
JToggleButton btn = (JToggleButton)e.getSource(); //take in button
setIcon(btn);
resetIcon(btn);
//this if makes btn equals the first button if it is null
if(first == null){
first = btn;
return;
}
matching(first, btn);
first = null;
}
}
public void updateTime(){
long temp = Calendar.getInstance().getTimeInMillis();
time.setText("Elapsed time is " + formatTime((long) (temp - start)));
now = temp - start;
}
public void continueTime(){
long temp = Calendar.getInstance().getTimeInMillis();
time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}
private void timerContinue(){
ActionListener timerAL = new ActionListener(){
public void actionPerformed(ActionEvent e){
continueTime();
}
};
//stop the timer if it is still running
if (swTimer != null && swTimer.isRunning()) {
swTimer.stop();
}
swTimer = new Timer(1000, timerAL);
swTimer.setInitialDelay(0);
swTimer.start();
}
public static String formatTime(long ms){
long millis = ms % 1000;
long x = ms / 1000;
long seconds = x % 60;
x /= 60;
long minutes = x % 60;
x /= 60;
long hours = x % 24;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
//Method to reset the button to game image when it is clicked for a second time
private void resetIcon(JToggleButton btn){
if(!btn.isSelected()){
btn.setIcon(icon);
}
}
private void timerStart(){
ActionListener timerAL = new ActionListener(){
public void actionPerformed(ActionEvent e){
updateTime();
}
};
//stop the timer if it is still running
if (swTimer != null && swTimer.isRunning()) {
swTimer.stop();
}
swTimer = new Timer(1000, timerAL);
swTimer.setInitialDelay(0);
swTimer.start();
}
private void timerStop(){
//if all 12 buttons are matched, then stop the timer
if(retireButton.size() == 12){
long stop = Calendar.getInstance().getTimeInMillis();
time.setText("You finished in " + formatTime((long)(stop-start)));
swTimer.stop();
}
}
//set the icons for the tiles
private void setIcon(JToggleButton btn) {
if(btn == buttons[0] || btn == buttons[1])
btn.setIcon(iconList.get(0));
else if(btn == buttons[2] || btn == buttons[3])
btn.setIcon(iconList.get(1));
else if(btn == buttons[4] || btn == buttons[5])
btn.setIcon(iconList.get(2));
else if(btn == buttons[6] || btn == buttons[7])
btn.setIcon(iconList.get(3));
else if(btn == buttons[8] || btn == buttons[9])
btn.setIcon(iconList.get(4));
else if(btn == buttons[10] || btn == buttons[11])
btn.setIcon(iconList.get(5));
}
//match the two input buttons
private void matching(JToggleButton btn, JToggleButton btn2){
if(btn.isSelected()){
if(btn2.isSelected()){
buttonDisable(btn, btn2); //disable all buttons besides btn, and btn2
if(!btn.getIcon().toString().equals(btn2.getIcon().toString())){
startTime(1, btn, btn2); //start the 1.5 secs countdown
}
else {
retirePair(btn, btn2);
timerStop();
buttonEnable(btn, btn2);
}
}
}
}
private void startTime(int countPassed, JToggleButton btn, JToggleButton btn2){
ActionListener action = new ActionListener(){
public void actionPerformed(ActionEvent e){
if(count == 0){
cdTimer.stop();
match = false; //resets match
unflipPair(btn, btn2); //reset tile to game image again
buttonEnable(btn, btn2);
}
else
count--;
}
};
cdTimer = new Timer(500, action);
cdTimer.start();
match = true;
count = countPassed;
}
//enable buttons other than btn and btn2
private void buttonEnable(JToggleButton btn, JToggleButton btn2){
for(int i = 0; i < buttons.length; i++){
if(buttons[i] != btn && buttons[i] != btn2)
buttons[i].setEnabled(true);
}
}
//disable buttons other than btn and btn2
private void buttonDisable(JToggleButton btn, JToggleButton btn2){
for(int i = 0; i < buttons.length; i++){
if(buttons[i] != btn && buttons[i] != btn2)
buttons[i].setEnabled(false);
}
}
private void unflipPair(JToggleButton btn, JToggleButton btn2){
btn.setIcon(icon);
btn2.setIcon(icon);
btn.setEnabled(true);
btn2.setEnabled(true);
btn.setSelected(false);
btn2.setSelected(false);
}
private void retirePair(JToggleButton btn, JToggleButton btn2){
btn.setSelected(true);
btn2.setSelected(true);
btn.removeActionListener(this);
btn2.removeActionListener(this);
retireButton.add(btn);
retireButton.add(btn2);
}
private void createIcons(){
ImageIcon icon1 = new ImageIcon("1.png");
ImageIcon icon2 = new ImageIcon("2.png");
ImageIcon icon3 = new ImageIcon("3.png");
ImageIcon icon4 = new ImageIcon("4.png");
ImageIcon icon5 = new ImageIcon("5.png");
ImageIcon icon6 = new ImageIcon("6.png");
iconList.add(icon1);
iconList.add(icon2);
iconList.add(icon3);
iconList.add(icon4);
iconList.add(icon5);
iconList.add(icon6);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if(args.length == 0) //java MemoryGame debug increases the string length to 1
new MemoryGame();
else
new MemoryGame(2);
}
});
}
}
These are the addActionListener:
pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
elapsed += now;
swTimer.stop();
}
});
resume.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
timerContinue();
}
});
This is timerContinue method:
public void continueTime(){
long temp = Calendar.getInstance().getTimeInMillis();
time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}
private void timerContinue(){
ActionListener timerAL = new ActionListener(){
public void actionPerformed(ActionEvent e){
continueTime();
}
};
//stop the timer if it is still running
if (swTimer != null && swTimer.isRunning()) {
swTimer.stop();
}
swTimer = new Timer(1000, timerAL);
swTimer.setInitialDelay(0);
swTimer.start();
}
This is timeStart() method:
public void updateTime(){
long temp = Calendar.getInstance().getTimeInMillis();
time.setText("Elapsed time is " + formatTime((long) (temp - start)));
now = temp - start;
}
private void timerStart(){
ActionListener timerAL = new ActionListener(){
public void actionPerformed(ActionEvent e){
updateTime();
}
};
//stop the timer if it is still running
if (swTimer != null && swTimer.isRunning()) {
swTimer.stop();
}
swTimer = new Timer(1000, timerAL);
swTimer.setInitialDelay(0);
swTimer.start();
}
So, you capture start
private long start = Calendar.getInstance().getTimeInMillis();
when the class is first instantiated ... not sure this is a good idea, but lets flow with it...
When you pause the timer...
pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
elapsed += now;
swTimer.stop();
}
});
you capture the elapsed
time
When you resume the clock...
public void continueTime(){
long temp = Calendar.getInstance().getTimeInMillis();
time.setText("Elapsed time is " + formatTime((long) (temp - start)));
}
private void timerContinue(){
ActionListener timerAL = new ActionListener(){
public void actionPerformed(ActionEvent e){
continueTime();
}
};
//stop the timer if it is still running
if (swTimer != null && swTimer.isRunning()) {
swTimer.stop();
}
swTimer = new Timer(1000, timerAL);
swTimer.setInitialDelay(0);
swTimer.start();
}
You're using the current time and subtracting start
, but start
has never been reset, so it's still calculating from the time it was first initialised.
Conceptually, a "pause-able" clock is one which has two important states.
So. When you pause the clock, you need to do a couple of things...
null
, so it's obvious)When resumed, you need to...
start
time.start
time and now (when the clock ticks)totalRunningTime
to it, which creates the overall running time for the clock.Simple 😁
Firstly, the question itself is not that uncommon, and a number solutions are available if you spend some time researching the problem.
Having said that, since it's now 2018 and Java 11 is visible on the horizon, you really should be making use of Java's newer date/time API, which provides a number of really useful APIs, including Duration
and the ability to treat time independently of the underlying calendar system ... so we don't get daylight savings oddities 😝
The following is an example of some library code I use, which implements the functionality from above.
The "strange" thing about it is, it's independent of a timing system. That is, it doesn't "tick". Instead, you'd use a Swing Timer
(in your case) to call getDuration
and update the UI as required.
public class StopWatch {
private Instant startTime;
private Duration totalRunTime = Duration.ZERO;
public StopWatch start() {
startTime = Instant.now();
return this;
}
public StopWatch stop() {
Duration runTime = Duration.between(startTime, Instant.now());
totalRunTime = totalRunTime.plus(runTime);
startTime = null;
return this;
}
public StopWatch pause() {
return stop();
}
public StopWatch resume() {
return start();
}
public StopWatch reset() {
stop();
totalRunTime = Duration.ZERO;
return this;
}
public boolean isRunning() {
return startTime != null;
}
public Duration getDuration() {
Duration currentDuration = Duration.ZERO;
currentDuration = currentDuration.plus(totalRunTime);
if (isRunning()) {
Duration runTime = Duration.between(startTime, LocalDateTime.now());
currentDuration = currentDuration.plus(runTime);
}
return currentDuration;
}
}
And if you want to see the concept in action, you could have a look at Adding resume function to stopwatch