I have a game loop and it basically is this: function game(){init();setInterval(draw, 30);} but when a player wins I want to call Win() which clears the interval and restart. What's the best way to tackle this in javascript? Since the setInterval() is asynch I've already fallen out of game() by this point. So do I add a busy loop: function game(){while(1){init();setInterval(draw, 30);while(!Win);}}? There's no good way to sleep() currently is there? Is there a way to call game() inside Win() without making the call stack of arbitrary size? What's the best way to handle this situation?
1 Answer
You can use named interval:
var time;
function game(){
init();
time = setInterval(function (){
draw();
}, 30);
}
win(){
if(time){
clearInterval(time); // clear the interval
game(); // start the game again.
}
}
Now you have to call this win function, whenever user wins and start the game.
14 Comments
LetterEh
@Black ensure that you're only calling
win when appropriate; you're keeping a closure reference to win within draw, and calling win with a predicate. Your stack will increase by whatever win( ) will increase it by, but your actual callstack is going to be drained at the bottom of draw, and a new one will take over, in the new interval period. JS game-engine or otherwise polling loops are'nt easy to initially grasp. But think of it like an implicit sleep at the bottom of draw, where the callstack is gone and the browser gets the thread for render/IO work, then gives you a new stack.LetterEh
@Black your busy loop will actually lock the thread, and thus never give it back to the browser, and thus never allow you to render (or enter input, or even allow you to refresh the page in certain browsers). Rely on the implicit sleep and the callback. Ideally with
requestAnimationFrame rather than setTimeout || setInterval, but let the stack drain, quickly and without trying to keep the thread locked, nonetheless.LetterEh
@Black theoretically, yes... ...you may have several frames that go by, in that case, where the game should already be over. And you'll need a global
shouldReboot that gets set to true or whathaveyou, if you're relying on the checkReboot. A better solution might be to do something like var pid = setInterval(tick, 1000/60); function tick () { var isOver = draw(); if (isOver) { win(pid); } } And not each interval. That'd be a wrong way of thinking about it. The call-stack exists until there are no more synchronous calls on the stack. set(Interval|Timeout), AJAX, Promises... asyncLetterEh
@Black when the host environment decides it (the browser's tab-manager, or whatever is hosting the JS machine) it starts up the execution context for whatever stack of synchronous calls has been scheduled next. If you had previously fired a timeout, then it will call the function that was registered in that timeout, and it will run until the stack has been exhausted; at that point, the thread reverts back to the host for all of the browser/server/app-container stuff. The host will then set off the next registered stack in JS, et cetera.
LetterEh
@Black assume
next is the name of the function in my setInterval example (it’s been taken out given a name, and passed as a reference): game->pid=setInterval(next) ... ... next()->over=draw() === false ... ... next()->over=draw() === false ... ... next()->over=draw() === true win()->clearInterval(pid); restart()->game() Each ... ... is the exhaustion of synchronous calls, and the eventual rescheduling of another entry, into next |
var timer = setInterval(...)then clear the timer by callingclearInterval(timer). However, in my opinion, anytime you're using a timer you should consider the possibility that there's another way to accomplish your goal.win()from wherever you handle your game's logic?draw()looks like the place, but the name doesn't imply itdelay(milliseconds)