Reputation: 3965
I have put a delay in my loop by awaiting a function that returns a Promise
with setTimeout()
. I am able to start and reset the loop but would also like to be able to pause the loop somehow. After clicking the start button again, I would like to continue looping from the last looped element in the array.
$(document).ready(() => {
$('#btn-start').click(() => {
loopMoves();
});
$('#btn-pause').click(() => {
// ...
});
$('#btn-reset').click(() => {
clearTimeout(timer);
});
})
var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
async function loopMoves() {
for(let i = 0; i < moves.length; i++){
console.log(moves[i]);
await delay(2000);
}
}
var timer;
function delay(ms) {
return new Promise((x) => {
timer = setTimeout(x, ms);
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn-start">start</button>
<button id="btn-pause">pause</button>
<button id="btn-reset">reset</button>
Upvotes: 1
Views: 587
Reputation: 3965
I have tried out these answers and both of them seem to work, thank you. I return to this question because I previously thought of something and would like to get some feedback on it. What if I would store the looped items in a new array and restart the loop from the position of the last stored item setting i = done.length
(done
being the array of looped items). When clicking the pause button clearTimeout()
is called and when clicking the reset button, I clear out the done
array after calling clearTimeout()
, start still runs the function containing the loop.
I understand that I am not really pausing the timer but rather stopping it and restarting it from the last position in the array. Would this be considered 'bad practice' or could this also be an acceptable solution?
$(document).ready(() => {
$('#btn-start').click(() => {
loopMoves();
});
$('#btn-pause').click(() => {
clearTimeout(timer);
});
$('#btn-reset').click(() => {
clearTimeout(timer);
done = [];
});
})
var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
var done = [];
async function loopMoves() {
for(let i = done.length; i < moves.length; i++){
console.log(moves[i]);
done.push(moves[i]);
await delay(2000);
}
}
var timer;
function delay(ms) {
return new Promise((x) => {
timer = setTimeout(x, ms);
});
}
Upvotes: 0
Reputation: 1012
You might consider flipping things around into more of a time based "state machine".
When play is clicked, you start your timmer ("playing" state), at the end of the timmer you check the current state. If the state is still "playing" you grab the next move and display that and start the timmer again. If no interaction happens this repeats until all moves are done and the state goes to "stopped". If someone clicks pause you go to "paused" state but leave the rest as is. When play is clicked again (going from "paused" to "playing") you just pick up where you were. When stop is clicked you reset everything. When play is clicked from the "stopped" state you set the move index to 0 and do the first move, start the timmer.
This does mean each update only happens every 2 seconds but that might be acceptable and it's a whole lot easier than the alternative of pausing the timmer etc.
const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4']; // These can be loaded however
let state = "stopped"; // The starting state
let currentMoveIndex = 0; // Used to track which move to grab for the next tick
$(document).ready(() => {
$('#btn-start').click(() => {
state = "playing";
tick();
});
$('#btn-pause').click(() => {
state = "paused";
});
$('#btn-reset').click(() => {
state = "stopped";
tick(); // Since we put the clean up inside the tick function we run it again to be sure it's executed in case it was paused
});
})
function tick() {
switch(state) {
case "playing":
if (currentMoveIndex + 1 === moves.length) {
break;
}
const move = moves[currentMoveIndex];
console.log(move); // Or whatever you wish to do with the move
currentMoveIndex += 1;
setTimeout(tick, 2000);
break;
case "paused":
// Do whatever you want based on entering the paused state,
// Maybe show some message saying "Paused"?
break;
case "stopped":
currentMoveIndex = 0;
break;
}
}
If you're using something like React or Angular you can wrap this into a Component/Controller to keep things together, or wrap it all into a Game function if you're using plain JavaScript
Upvotes: 1
Reputation: 8239
Instead of using for loop this could be achieved by using setInterval() and a generator function.
Please refer below snippet for the working example:
$(document).ready(() => {
let loop = loopMoves();
$('#btn-start').click(() => {
console.log("****STARTED*****");
loop("PLAY");
});
$('#btn-pause').click(() => {
console.log("****PAUSED*****");
loop("PAUSE");
});
$('#btn-reset').click(() => {
console.log("****RESET*****");
loop("RESET");
});
})
const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
function loopMoves() {
let moves = generateMoves();
let intervalId;
function startInterval(){
intervalId = setInterval(()=>{
const move = moves.next();
if(move.done)
clearInterval(intervalId);
else
console.log(move.value);
}, 2000);
}
return (state) => {
switch(state){
case "PLAY": startInterval(); break;
case "PAUSE": clearInterval(intervalId); break;
case "RESET": moves = generateMoves();
clearInterval(intervalId); break;
}
}
}
function* generateMoves(){
for(let i = 0; i < moves.length; i++){
yield moves[i];
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn-start">start</button>
<button id="btn-pause">pause</button>
<button id="btn-reset">reset</button>
Upvotes: 1