Sunday, February 17, 2013

Boundary Conditions for Kids

‹prev | My Chain | next›

In the most recent candidate game for 3D Game Programming for Kids, the player positions ramps strategically so that the in-game player can perform sweet jumps to reach the green goal:



That works works quite well thanks to some simple Three.js and Physijs coding. It worked so well, in fact, that I was inspired to add the very excellent scoreboard, which, as of last night, seems to be in decent shape.

I was further inspired to add multiple levels to the game. If this makes it into the book, it would be a separate chapter because the game has gotten quite large—I would prefer under 150 lines of code for any game and this has now hit 300.

Anyhow, the game and the various levels are working quite well, but resetting the game does not. Thanks also to last night's work, reset works for the first level and after the game is over. But if I reset in between levels, then things break down. Obstacles from the previously active level remain on the screen in addition to the first level objects that should be there.

Since this works on the first level and after the game is over, I must have an error in my logic, but where? More importantly, how do I troubleshoot? Am I going to have to work to get to the second level each time I want to try something new?

Happily, I am able to reproduce my problem by starting the game on level 2 (well 1, but it is zero-indexed):
  var current_level = 1;
  function drawCurrentLevel() {
    scoreboard.resetCountdown(50 - current_level * 10);
    var obstacles = levels[current_level];
    obstacles.forEach(function(obstacle) {
      scene.add(obstacle);
    });      
  }
Immediately after the game loads, I can reset. Now, not only do I have the two platform obstacles from level 1, but I also have the single obstacle from level 0:



Being able to reproduce quickly will certainly help. With that, I start looking closer at my code in the hopes of figuring out where I went wrong. The most likely culprit in gameReset() would seem to be eraseOldLevel() since the old level is not actually being erased:
  function gameReset() {
    pause = false;
    eraseOldLevel();
    current_level = -1;
    scoreboard.resetTimer();
    scoreboard.setMessage('');
    levelUp();
  }
And this does turn out to be the problem area. If, as in this case of manually setting the current level, the game is at level one, then eraseOldLevel() will try to erase the previous level (zero in this case):
  function eraseOldLevel() {
    if (current_level === 0) return;
    var obstacles = levels[current_level-1];
    obstacles.forEach(function(obstacle) {
      scene.remove(obstacle);
    });      
  }
Well that's a bit of a bummer. I got myself confused by a boundary condition and I'm trying to teach this to beginners & kids?

The most obvious thing to do at this point is to avoid the topic in the book by not allowing reset. A straight progression through levels will avoid the boundary nicely. Then again, this might be a nice teaching moment. A mistake so common that we give it fancy names like boundary conditions / off-by-one / fencepost problems might give comfort to beginners. I will defer that decision until I write that chapter. The ultimate decision will likely rest on how long the chapter is without jamming this in.

For now, I solve the problem by removing the dependence on external state (current_level) and renaming the function from eraseOldLevel() to just eraseLevel():
  function eraseLevel(level) {
    if (level < 0) return;
    if (level > levels.length-1) return;
    var obstacles = levels[level];
    obstacles.forEach(function(obstacle) {
      scene.remove(obstacle);
    });      
  }
Now, it becomes the calling context's responsibility to know which level to erase. In the case of gameReset(), this is the current_level:
  function gameReset() {
    pause = false;
    eraseLevel(current_level);
    scoreboard.resetTimer();
    scoreboard.setMessage('');
    current_level = 0;
    drawCurrentLevel();
    moveGoal();
  }
I can then reset the current_level back to the starting point and allow the game to proceed.

With that, I believe that I am finally done with this game. This leaves me free to pursue any of the myriad of other obstacles in my way before I can get the book out of beta. Tomorrow.


Day #665

No comments:

Post a Comment