2

I'm writing a game using HTML5 canvas and Javascript. I'm using setInterval to animate the game and check for events at regular intervals. I've written multiple levels for the game, but I can't seem to escape the setInterval when a given level ends in order to start the next one. The code accurately detects when a level is won or lost and successfully clears the interval and renders the button, but the button does not fire.

Adding a button was my latest idea. I've also tried removing the canvas using jQuery and inserting a new one. I've also tried using clearRect on the canvas but it doesn't fire either.

Given that I can't return a value from setInterval, what are my options, if any? Is there another way to accomplish the same thing? Is there a separate error with my code that I've overlooked? Thanks!

Game.prototype.win = function(int) {
    clearInterval(int);
    var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
    $('#controls').append(content)
};

Game.prototype.lose = function(int) {
    clearInterval(int);
    var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
    $('#controls').append(content)
};

Game.prototype.run = funtion () {    
    $('#start').click( function () {
      $('#controls').empty();
      var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
      var game = this

      var int = setInterval( function () {
        if (waveOne.step() === "lost" ) {
          game.lose(int);
        } else if (waveOne.step() === "won") {
          game.win(int);
        }
        waveOne.draw(this.ctx)
      }, 20)
      this.bindKeyHandlers(waveOne);
   }.bind(this));

   $('#next-level').click( function () {
      $('#controls').empty();
      ...more code...
   });
};
hmt
  • 23
  • 2

1 Answers1

1

To stop a setInterval() you have to store the returned value from the original call to setInterval() in some persistent location and then call clearInterval() on that value.

Because you declared your interval with var as in var int, it was local only to that method and was not available anywhere else in the code.

There are a number of ways to do that in your code. I would probably suggest storing it as an instance variable like this:

Game.prototype.run = funtion () {    
    $('#start').click( function () {
      $('#controls').empty();
      var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
      var game = this;

      this.stop();
      this.interval = setInterval( function () {
        if (waveOne.step() === "lost" ) {
          game.lose(int);
        } else if (waveOne.step() === "won") {
          game.win(int);
        }
        waveOne.draw(this.ctx)
      }, 20)
      this.bindKeyHandlers(waveOne);
   }.bind(this));

   $('#next-level').click( function () {
      $('#controls').empty();
      ...more code...
   });
};

Then, you can make a method that will stop the interval like this:

Game.prototype.stop = function() {
      if (this.interval) {
          clearInterval(this.interval);
          this.interval = null;
      }
}

And, change your other methods like this:

Game.prototype.win = function(int) {
    this.stop();
    var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
    $('#controls').append(content)
};

Game.prototype.lose = function(int) {
    this.stop();
    var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
    $('#controls').append(content)
};

For your event handling issues, if you are destroying and recreating a button, then you will lose any event handlers that were attached to any DOM elements that got replaced.

You have two choices for how to fix it:

  1. After you create the new DOM elements, you can again set the event handlers on the new DOM elements.
  2. You can use "delegated event handling". This attaches the event handlers to a parent DOM object that is itself not replaced. The click event bubbles up to the parent and is handled there. The child can be replaced as many times as you want and the event handler will still work.

See these references for how to use delegated event handling with jQuery:

Does jQuery.on() work for elements that are added after the event handler is created?

jQuery .live() vs .on() method for adding a click event after loading dynamic html

JQuery Event Handlers - What's the "Best" method

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Hm. That method doesn't seem to make the code run any differently than with the local variable. The interval still stops, but I can't do anything outside the interval (click a button, etc). – hmt Apr 24 '15 at 17:32
  • @hmt - What do you mean "can't do anything outside the interval"? I don't understand that part of the problem. If you are destroying DOM elements that have event handlers on them with code like `$('#controls').empty();`, then those event handlers are also destroyed. You will have to either reattach the event handlers to the newly created DOM elements or use delegated event handling to attach the event handlers to higher level DOM elements that are not replaced. – jfriend00 Apr 24 '15 at 17:33
  • @hmt - see [Does jQuery.on() work for elements that are added after the event handler is created?](http://stackoverflow.com/questions/9814298/does-jquery-on-work-for-elements-that-are-added-after-the-event-handler-is-cre/9814409#9814409) for how to do delegated event handling that attaches the event handler to a parent object that is not replaced. – jfriend00 Apr 24 '15 at 17:37
  • @hmt - see the info I added to my answer about event handlers when you're recreating DOM elements. – jfriend00 Apr 24 '15 at 17:41
  • ah!! that was all i needed, to user jQuery.on instead of jQuery.click. thank you!! – hmt Apr 24 '15 at 17:45
  • @hmt - just to be clear, you have to not only use `.on()`, but you have to use it the right way (which is described in the reference posts I've linked to). – jfriend00 Apr 24 '15 at 17:59
  • got it. thanks very much for all of the resources and the help. it seems to be working now, and i'll check them again if it stops. i'd be upvoting if i had the reputation! – hmt Apr 24 '15 at 18:03
  • @hmt - you can accept the answer as the best answer that solved your problem by clicking the green checkmark to the left of the answer. This will indicate to the StackOverflow community that your question has been answered and will earn both you and the person who provided the answer some additional reputation points. – jfriend00 Apr 24 '15 at 18:05