Reputation: 2088
First of all, you can find an example of my code in JS Fiddle and also below the question.
I'm working on a personal training webapp and basically you can hit play and then you get five minutes to do a series of tasks in a random order. The program creates the sessionTasks
array in which are put in a random order tasks for the tasks
array in order to fit the five minute limit. Right now the tasks
array is just one I created with four tasks and the respective times just for testing.
The problem I ran into is the following: When you click the task so you can move forward to the next task, the next time you play seconds will move faster. The way I found to replicate is:
Now the seconds should be moving quicker. If not, repeat what you just did. It is irregular but it usually does it in the second try.
I cannot for the life of me understand why it behaves like this. I thought that maybe it was creating more Timers that all used #taskTimer
to run but that didn't make sense to me. Is it something wrong with the Timer
function? What is wrong in my code?
mainMenu();
var totalSessionTasks, taskIterator, selectedTimeInSecs = 300;
var taskTimer = new Timer("#taskTimer", nextTask);
var globalTimer = new Timer("#globalTimer", function() {
});
var tasks = [
["First task", 0, 30],
["Second task", 0, 15],
["Third task", 0, 10],
["Fourth task", 3, 0]
];
var sessionTasks = [
]
function setUpSession() {
sessionTasks = []
if (tasks.length != 0) {
var sessionTasksSeconds = 0; //the seconds of the session being filled
var sessionTasksSecondsToFill = selectedTimeInSecs; //seconds left in the session to fill
var newTaskSeconds = 0; //seconds of the next task being added to the session
var sessionFull = false;
console.log('Session Empty');
while (sessionFull === false) {
var areThereAnyTaskThatFitInTheSession =
tasks.some(function(item) {
return ((item[1] * 60 + item[2]) <= sessionTasksSecondsToFill) && (item != sessionTasks[sessionTasks.length - 1]);
});
console.log(areThereAnyTaskThatFitInTheSession);
if (areThereAnyTaskThatFitInTheSession) {
do {
var randTaskNum = Math.floor(Math.random() * tasks.length);
} while (((tasks[randTaskNum][1] * 60 + tasks[randTaskNum][2]) > sessionTasksSecondsToFill) || (tasks[randTaskNum] == sessionTasks[sessionTasks.length - 1]))
sessionTasks.push(tasks[randTaskNum]);
newTaskSeconds = (tasks[randTaskNum][1]) * 60 + tasks[randTaskNum][2];
sessionTasksSecondsToFill -= newTaskSeconds;
sessionTasksSeconds += newTaskSeconds;
console.log(tasks[randTaskNum][0] + ": " + newTaskSeconds + "s");
console.log(sessionTasksSeconds)
} else if (sessionTasks.length == 0) {
note("All your tasks are too big for a game of " + selectedTimeInSecs / 60 + " minutes!");
break;
} else {
console.log('Session full');
sessionFull = true;
taskIterator = -1;
totalSessionTasks = sessionTasks.length;
console.log(totalSessionTasks);
globalTimer.set(0, sessionTasksSeconds);
nextTask();
globalTimer.run();
taskTimer.run();
}
}
} else {
note("You don't have have any tasks in your playlists!");
}
}
function nextTask() {
if (taskIterator + 1 < totalSessionTasks) {
taskIterator++;
$("#taskText").text(sessionTasks[taskIterator][0]);
globalTimer.subtract(0, taskTimer.getTotalTimeInSeconds())
taskTimer.set(sessionTasks[taskIterator][1], sessionTasks[taskIterator][2]);
$("#taskCounter").text(taskIterator + 1 + " of " + totalSessionTasks + " tasks");
} else {
mainMenu();
taskTimer.stop();
globalTimer.stop();
note("Thanks for playing!");
}
}
//timer object function
function Timer(element, callback) {
var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, interval = 1000,
self = this,
timeLeftToNextSecond = 1000;
this.running = false;
this.set = function(inputMinutes, inputSeconds) {
finalTimeInSeconds = inputMinutes * 60 + inputSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.add = function(inputMinutes, inputSeconds) {
finalTimeInSeconds += inputMinutes * 60 + inputSeconds;
finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.subtract = function(inputMinutes, inputSeconds) {
finalTimeInSeconds -= inputMinutes * 60 + inputSeconds;
if (finalTimeInSeconds <= 0) {
callback()
}
finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.reset = function() {
this.set(0, 0);
}
this.print = function() {
displayMinutes = (minutes.toString().length == 1) ? "0" + minutes : minutes; //ternary operator: adds a zero to the beggining
displaySeconds = (seconds.toString().length == 1) ? "0" + seconds : seconds; //of the number if it has only one caracter.
$(element).text(displayMinutes + ":" + displaySeconds);
}
this.run = function() {
if (this.running == false) {
this.running = true;
var _f = function() {
secondStarted = new Date;
self.subtract(0, 1);
interval = 1000;
}
ac = setInterval(_f, interval);
}
}
this.stop = function() {
if (this.running == true) {
this.running = false;
console.log(this + "(" + element + ") was stopped");
stopped = new Date;
interval = 1000 - (stopped - secondStarted);
clearInterval(ac);
}
}
this.getTotalTimeInSeconds = function() {
return finalTimeInSeconds;
}
this.reset();
}
function note(string) {
alert(string);
}
function mainMenu() {
//EMPTY BODY
$("body").empty();
$("body").append(
//BUTTONS
"<div id='playButton' class='mainButton'><div class='buttonText mainButtonText'>PLAY</div></div>"
);
//BINDS
$("#playButton").bind("click", function(){
playMain();
setUpSession();
});
}
function playMain() {
//EMPTY BODY
$("body").empty();
$("body").append(
//TASK TEXT
"<p class='text' id='taskText'>Lorem ipsum dolor sit amet.</p>",
//TIMERS
"<div id='taskTimerWrap'><p class='text timer' id='taskTimer'>00:00</p><p class='text' id='taskTimerText'>Task Time</p></div>",
"<div id='globalTimerWrap'><p class='text timer' id='globalTimer'>00:00</p><p class='text' id='globalTimerText'>Global Time</p></div>",
//TASK COUNTER
"<div class='text' id='taskCounter'>0/0 tasks completed</div>"
);
//BINDS
$("#taskText").bind("click", nextTask);
}
#taskText {
text-align: center;
display: table;
vertical-align: middle;
height: auto;
width: 100%;
top: 50px;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: auto;
font-size: 65px;
cursor: pointer;
}
#taskTimerWrap {
text-align: center;
top: 0;
right: 0;
left: 170px;
margin: 5px;
position: absolute;
-webkit-transition: all 0.5s ease;
}
.timer {
font-size: 64px;
margin: 0;
line-height: 0.88;
}
#taskTimerText {
font-size: 34.4px;
margin: 0;
line-height: 0.65;
}
#globalTimerWrap {
text-align: center;
top: 0;
left: 0;
right: 170px;
margin: 5px;
position: absolute;
}
#globalTimerText {
font-size: 28.5px;
margin: 0;
line-height: 0.78;
transform: scale(1, 1.2);
}
#taskCounter {
text-align: center;
bottom: 0;
right: 0;
left: 0;
width: auto;
position: absolute;
font-size: 30px;
color: #98D8D9;
-webkit-transition: all 0.5s ease;
}
#taskCounter:hover {
color: #F1F2F0
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Upvotes: 14
Views: 697
Reputation: 491
Here is an update in jsfiddle
The problem is on the variable interval
of Timer
This line creates unpredictable interval under Timer.stop()
:
interval = 1000 - (stopped - secondStarted);
If you would like to shorten the time interval, you can add a play_count
property:
...
//timer object function
function Timer(element, callback) {
var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, interval = 1000,
self = this,
timeLeftToNextSecond = 1000;
this.running = false;
play_count = 0; // play count property
...
this.run = function() {
if (this.running == false) {
this.running = true;
var _f = function() {
secondStarted = new Date;
self.subtract(0, 1);
interval = Math.max(1000 - play_count * 100, 500); // ** <-- shorten time interval
}
ac = setInterval(_f, interval);
}
}
this.stop = function() {
if (this.running == true) {
this.running = false;
console.log(this + "(" + element + ") was stopped");
// stopped = new Date;
// interval = 1000 - (stopped - secondStarted);
play_count++;
clearInterval(ac);
}
}
this.getTotalTimeInSeconds = function() {
return finalTimeInSeconds;
}
this.reset();
}
...
Upvotes: 1
Reputation: 4216
As I see, you are trying to set the value for variable interval
from inside the interval callback function.
var _f = function() {
secondStarted = new Date;
self.subtract(0, 1);
//interval = 1000; REMOVE THIS LINE
}
interval = 1000; //ADD IT HERE
ac = setInterval(_f, interval);
Actually when setInterval
is executed, the value for interval
is not 1000. It's not updated by _f
yet, because that function will run after the setInterval()
is executed. And once setInterval()
is called with the existing value of interval
, changing it later has no impact on the created interval, because the setInterval()
function has already set the delay time for the interval created. And the value for interval
changes from it's initial value 1000 because of this line :
interval = 1000 - (stopped - secondStarted); //I'm not sure what you are trying to do with this, possibly removing this line will also fix your problem.)
And here is the JS Fiddle.
mainMenu();
var totalSessionTasks, taskIterator, selectedTimeInSecs = 300;
var taskTimer = new Timer("#taskTimer", nextTask);
var globalTimer = new Timer("#globalTimer", function() {
});
var tasks = [
["First task", 0, 30],
["Second task", 0, 15],
["Third task", 0, 10],
["Fourth task", 3, 0]
];
var sessionTasks = [
]
function setUpSession() {
sessionTasks = []
if (tasks.length != 0) {
var sessionTasksSeconds = 0; //the seconds of the session being filled
var sessionTasksSecondsToFill = selectedTimeInSecs; //seconds left in the session to fill
var newTaskSeconds = 0; //seconds of the next task being added to the session
var sessionFull = false;
console.log('Session Empty');
while (sessionFull === false) {
var areThereAnyTaskThatFitInTheSession =
tasks.some(function(item) {
return ((item[1] * 60 + item[2]) <= sessionTasksSecondsToFill) && (item != sessionTasks[sessionTasks.length - 1]);
});
console.log(areThereAnyTaskThatFitInTheSession);
if (areThereAnyTaskThatFitInTheSession) {
do {
var randTaskNum = Math.floor(Math.random() * tasks.length);
} while (((tasks[randTaskNum][1] * 60 + tasks[randTaskNum][2]) > sessionTasksSecondsToFill) || (tasks[randTaskNum] == sessionTasks[sessionTasks.length - 1]))
sessionTasks.push(tasks[randTaskNum]);
newTaskSeconds = (tasks[randTaskNum][1]) * 60 + tasks[randTaskNum][2];
sessionTasksSecondsToFill -= newTaskSeconds;
sessionTasksSeconds += newTaskSeconds;
console.log(tasks[randTaskNum][0] + ": " + newTaskSeconds + "s");
console.log(sessionTasksSeconds)
} else if (sessionTasks.length == 0) {
note("All your tasks are too big for a game of " + selectedTimeInSecs / 60 + " minutes!");
break;
} else {
console.log('Session full');
sessionFull = true;
taskIterator = -1;
totalSessionTasks = sessionTasks.length;
console.log(totalSessionTasks);
globalTimer.set(0, sessionTasksSeconds);
nextTask();
globalTimer.run();
taskTimer.run();
}
}
} else {
note("You don't have have any tasks in your playlists!");
}
}
function nextTask() {
if (taskIterator + 1 < totalSessionTasks) {
taskIterator++;
$("#taskText").text(sessionTasks[taskIterator][0]);
globalTimer.subtract(0, taskTimer.getTotalTimeInSeconds())
taskTimer.set(sessionTasks[taskIterator][1], sessionTasks[taskIterator][2]);
$("#taskCounter").text(taskIterator + 1 + " of " + totalSessionTasks + " tasks");
} else {
mainMenu();
taskTimer.stop();
globalTimer.stop();
note("Thanks for playing!");
}
}
//timer object function
function Timer(element, callback) {
var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, interval = 1000,
self = this,
timeLeftToNextSecond = 1000;
this.running = false;
this.set = function(inputMinutes, inputSeconds) {
finalTimeInSeconds = inputMinutes * 60 + inputSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.add = function(inputMinutes, inputSeconds) {
finalTimeInSeconds += inputMinutes * 60 + inputSeconds;
finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.subtract = function(inputMinutes, inputSeconds) {
finalTimeInSeconds -= inputMinutes * 60 + inputSeconds;
if (finalTimeInSeconds <= 0) {
callback()
}
finalTimeInSeconds = (finalTimeInSeconds < 0) ? 0 : finalTimeInSeconds;
minutes = (Math.floor(finalTimeInSeconds / 60));
seconds = finalTimeInSeconds % 60;
this.print();
}
this.reset = function() {
this.set(0, 0);
}
this.print = function() {
displayMinutes = (minutes.toString().length == 1) ? "0" + minutes : minutes; //ternary operator: adds a zero to the beggining
displaySeconds = (seconds.toString().length == 1) ? "0" + seconds : seconds; //of the number if it has only one caracter.
$(element).text(displayMinutes + ":" + displaySeconds);
}
this.run = function() {
if (this.running == false) {
this.running = true;
var _f = function() {
secondStarted = new Date;
self.subtract(0, 1);
//interval = 1000; REMOVE THIS LINE
}
interval = 1000; //ADD IT HERE
ac = setInterval(_f, interval);
}
}
this.stop = function() {
if (this.running == true) {
this.running = false;
console.log(this + "(" + element + ") was stopped");
stopped = new Date;
interval = 1000 - (stopped - secondStarted);
clearInterval(ac);
}
}
this.getTotalTimeInSeconds = function() {
return finalTimeInSeconds;
}
this.reset();
}
function note(string) {
alert(string);
}
function mainMenu() {
//EMPTY BODY
$("body").empty();
$("body").append(
//BUTTONS
"<div id='playButton' class='mainButton'><div class='buttonText mainButtonText'>PLAY</div></div>"
);
//BINDS
$("#playButton").bind("click", function(){
playMain();
setUpSession();
});
}
function playMain() {
//EMPTY BODY
$("body").empty();
$("body").append(
//TASK TEXT
"<p class='text' id='taskText'>Lorem ipsum dolor sit amet.</p>",
//TIMERS
"<div id='taskTimerWrap'><p class='text timer' id='taskTimer'>00:00</p><p class='text' id='taskTimerText'>Task Time</p></div>",
"<div id='globalTimerWrap'><p class='text timer' id='globalTimer'>00:00</p><p class='text' id='globalTimerText'>Global Time</p></div>",
//TASK COUNTER
"<div class='text' id='taskCounter'>0/0 tasks completed</div>"
);
//BINDS
$("#taskText").bind("click", nextTask);
}
#taskText {
text-align: center;
display: table;
vertical-align: middle;
height: auto;
width: 100%;
top: 50px;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: auto;
font-size: 65px;
cursor: pointer;
}
#taskTimerWrap {
text-align: center;
top: 0;
right: 0;
left: 170px;
margin: 5px;
position: absolute;
-webkit-transition: all 0.5s ease;
}
.timer {
font-size: 64px;
margin: 0;
line-height: 0.88;
}
#taskTimerText {
font-size: 34.4px;
margin: 0;
line-height: 0.65;
}
#globalTimerWrap {
text-align: center;
top: 0;
left: 0;
right: 170px;
margin: 5px;
position: absolute;
}
#globalTimerText {
font-size: 28.5px;
margin: 0;
line-height: 0.78;
transform: scale(1, 1.2);
}
#taskCounter {
text-align: center;
bottom: 0;
right: 0;
left: 0;
width: auto;
position: absolute;
font-size: 30px;
color: #98D8D9;
-webkit-transition: all 0.5s ease;
}
#taskCounter:hover {
color: #F1F2F0
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Upvotes: 1
Reputation: 6476
In Timer.stop()
you change the interval that's used for the next run. Changing the interval variable in _f()
isn't going to change the interval used by setInterval()
.
In this case you will have to use setTimeout()
instead:
function Timer(element, callback) {
var ac, minutes, seconds, finalTimeInSeconds, displayMinutes, displaySeconds, timeout = 1000,
self = this,
timeLeftToNextSecond = 1000;
this.running = false;
/* ... */
this.run = function() {
if (this.running == false) {
this.running = true;
var _f = function() {
secondStarted = new Date;
self.subtract(0, 1);
ac = setTimeout(_f, 1000);
}
ac = setTimeout(_f, timeout);
}
}
this.stop = function() {
if (this.running == true) {
this.running = false;
console.log(this + "(" + element + ") was stopped");
stopped = new Date;
timeout = 1000 - (stopped - secondStarted);
clearTimeout(ac);
}
}
/* ... */
}
Upvotes: 2