HTML5 game control flow
Because Boulder Bop is somewhat complex, it can be helpful to walk through a typical flow of control scenario for the game, which is just what this topic does.
Now, consider the typical scenario where a user loads the page, clicks the game's Start button, and shoots a missile toward a descending boulder. Flow of control for this scenario looks like this:
- Page loads.
- Bunkers are drawn.
- Start button is clicked.
- Boulders descend toward bunkers.
- User fires missile toward descending boulder.
- Missile explodes at click point.
- Boulder is destroyed by missile explosion.
1. Page loads
The markup is parsed, drawing the game's linear gradient background in the process.
Next, the script block is parsed, setting up globals.helpers
, globals.game
, and running the anonymous event registering function (see HTML5 game skeleton code for Boulder Bop).
Finally, globals.game.init()
is invoked, as discussed next.
2. Bunkers are dawn
As suggested by it's name, globals.game.init()
initializes the game:
var init = function() { // Public.
// Note that one could, if so inclined, cache (preload) the game's images here.
var rightBunker = 86.5; // Given the image's width, 86.5% places the image on the far right of the viewport.
var middleBunker = rightBunker / 2; // Center the image within the viewport.
var leftBunker = 0; // 0% places the image on the left of the viewport.
var offset = 10 // Offset the left and right images by this percentage value.
for (var i = 0; i < _objects.length; i++) { // Remove all lingering game objects from the last game, if present (and they usually are).
if (_objects[i]) {
_objects[i].markActive(false);
if (_objects[i].audio) { // As a saftey precaution, mute any potentially playing sound effects (defensive programming).
_objects[i].audio.muted = true;
} // if
} // if
} // for
setState("over", false);
setState("paused", true);
setState("score", 0);
setState("level", 1);
rightBunker = rightBunker - offset + "%";
middleBunker += "%";
leftBunker = leftBunker + offset + "%";
var bunkerSpec = {};
bunkerSpec.type = 'bunker';
bunkerSpec.image = BUNKER_IMAGE; // Ignored by GameObject(spec).
bunkerSpec.movable = false; // Bunkers are not movable.
bunkerSpec.y = "82%";
bunkerSpec.width = 142;
bunkerSpec.height = 64;
bunkerSpec.id = 'rightBunker';
bunkerSpec.x = rightBunker;
_objects.push(Bunker(bunkerSpec));
bunkerSpec.id = 'middleBunker';
bunkerSpec.x = middleBunker;
_objects.push(Bunker(bunkerSpec));
bunkerSpec.id = 'leftBunker';
bunkerSpec.x = leftBunker;
_objects.push(Bunker(bunkerSpec));
createBoulders(spec.level); // The total number of boulders (of death) that fall from the sky is a function of the game's level.
}; // Game.init()
that.init = init;
First, some local variables are set up to ease the positioning of the bunkers, then the for
loop removes any lingering game objects from the last game (if applicable) and turns off any playing audio objects (if applicable):
for (var i = 0; i < _objects.length; i++) { // Remove all lingering game objects from the last game, if present (and they usually are).
if (_objects[i]) {
_objects[i].markActive(false);
if (_objects[i].audio) { // As a saftey precaution, mute any potentially playing sound effects (defensive programming).
_objects[i].audio.muted = true;
} // if
} // if
} // for
The markActive
method simply sets the active
property of the game object to false
(the active
property is used throughout the codebase). The custom audio
property contains an HTML5 audio object, which exposes the native muted property, allowing us to mute the game object's sound.
The setState
function is used to initialized the game's state to not-over, paused, on level 1, with a score of 0:
setState("over", false);
setState("paused", true);
setState("score", 0);
setState("level", 1);
Based on the value of it's two parameters, setState
sets various spec
properties and can update the game's score and level displays:
var setState = function(stateItem, value) { // Public.
switch (stateItem) {
case "fps":
spec.fps = value;
break;
case "score":
spec.score = value;
_scoreBox.textContent = "Score: " + spec.score;
break;
case "level":
spec.level = value;
_levelBox.textContent = "Level: " + spec.level;
break;
case "paused":
spec.paused = value; // A Boolean value.
break;
case "over":
spec.over = value; // A Boolean value indicating if the game is over or not.
break;
case "sound":
spec.sound = value; // A Boolean value indicating if the game should have sound or not.
break;
default:
console.log("Error in switch of setState()");
} // switch
}; // Game.setState()
that.setState = setState;
Be aware that the spec
seen in the switch
statement above is the parameter passed into the anonymous constructor that returns its constructed object to globals.game
:
globals.game = (function(spec) {
.
.
.
})({fps: FPS, sound: true});
Because of this, spec
acts as a static local variable and is within scope to all of this anonymous constructor's internal functions.
Next, init
creates three bunker objects using the Bunker
constructor and pushes them into the _objects[]
array:
var bunkerSpec = {};
bunkerSpec.type = 'bunker';
bunkerSpec.image = BUNKER_IMAGE; // Ignored by GameObject(spec).
bunkerSpec.movable = false; // Bunkers are not movable.
bunkerSpec.y = "82%";
bunkerSpec.width = 142;
bunkerSpec.height = 64;
bunkerSpec.id = 'rightBunker';
bunkerSpec.x = rightBunker;
_objects.push(Bunker(bunkerSpec));
bunkerSpec.id = 'middleBunker';
bunkerSpec.x = middleBunker;
_objects.push(Bunker(bunkerSpec));
bunkerSpec.id = 'leftBunker';
bunkerSpec.x = leftBunker;
_objects.push(Bunker(bunkerSpec));
The Bunker
constructor follows:
var Bunker = function(spec) { // Private.
var that = GameObject(spec); // Get all the methods and properties exposed by the GameObject constructor.
that.core = globals.helpers.createImage(spec); // Note that spec.x and spec.y could be specified in terms of percentages.
if (that.core.width.baseVal.value > _maxBunkerWidth) {
_maxBunkerWidth = that.core.width.baseVal.value; // _maxBunkerWidth is global to the Game namespace.
}
var bunkerCenterX = that.core.x.baseVal.value + (that.core.width.baseVal.value / 2);
var bunkerCenterY = that.core.y.baseVal.value + (that.core.height.baseVal.value / 2);
that.setPosition(bunkerCenterX, bunkerCenterY); // Record the center of the bunker's image as the position of the bunker.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var collisionDetection = function() { // Public.
for (var i = 0; i < _objects.length; i++) {
if (_objects[i].type == "boulder") {
var boulderX = _objects[i].core.cx.baseVal.value;
var boulderY = _objects[i].core.cy.baseVal.value;
if ( Math.abs(boulderX - bunkerCenterX) < TARGET_SENSITIVITY && Math.abs(boulderY - bunkerCenterY) < TARGET_SENSITIVITY) {
_objects[i].markActive(false); // Mark the boulder object as inactive so that exited() returns true on it in the main game loop (i.e., in update()).
var bunkerExplosionSpec = {};
bunkerExplosionSpec.type = "bunkerExplosion";
bunkerExplosionSpec.fill = "red";
bunkerExplosionSpec.stroke = "white";
bunkerExplosionSpec.strokeWidth = 3; // In SVG user units.
bunkerExplosionSpec.radius = 0; // Start a bunker explosion with a radius of zero, since this radius sinusoidally grows from a radius equal to 0.
bunkerExplosionSpec.movable = false; // So that the bunker explosions are drawn on top of the bunker images.
bunkerExplosionSpec.x = bunkerCenterX;
bunkerExplosionSpec.y = bunkerCenterY;
bunkerExplosionSpec.targetBunker = that;
bunkerExplosionSpec.soundPath = BUNKER_SOUND;
_objects.push(BunkerExplosion(bunkerExplosionSpec));
} // if
} // if
} // for
}; // Game.Bunker.collisionDetection()
that.collisionDetection = collisionDetection;
The first thing the Bunker
constructor does is pass its spec
parameter (not to be confused with the static local spec
object described above) to the GameObject
constructor in order to inherit all the GameObject
constructor's properties and methods into its local that
variable. In this way, the Bunker
constructor only needs to redefine its collisionDetection
method. This collisionDetection
method checks to see if any boulder object (if (_objects[i].type == "boulder")
) is close enough to the bunker's center and if so, marks the bunker for destruction (_objects[i].markActive(false)
), specifies the characteristics of a bunker explosion object, and pushes this object into the list of game objects via the BunkerExplosion
constructor (_objects.push(BunkerExplosion(bunkerExplosionSpec))
).
Here's the BunkerExplosion
constructor:
var BunkerExplosion = function(spec) { // Private.
var that = MissileExplosion(spec);
var speed = 3.5 / FPS * GAME_SPEED; // Increase this numeric value to increase the rate at which the missile explosion expands.
var maxBunkerExplosionRadius = _maxBunkerWidth / 1.8; // Only make this calculation once. Decrease the numeric value to make the bunker explosion radius larger. Note that _maxBunkerWidth is a Game namepspace global.
var t = 0;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
t += speed;
var currentRadius = that.core.r.baseVal.value = maxBunkerExplosionRadius * Math.sin(t);
if (Math.abs(that.radius - currentRadius) < 5) { // Returns true when the radius of the bunker explosion is close to its maximum value.
spec.targetBunker.markActive(false); // This code is invoked multiple times while "Math.abs(that.radius - currentRadius) < 5" is true. And it's safe to mark the target bunker object as inactive (i.e., destroyed, so that it will be removed) multiple times.
}
if (currentRadius <= 0) {
that.markActive(false); // Mark the bunker explosion object as inactive so that _objects[i].exited() returns true on it.
}
}; // Game.bunkerExplosion.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var collisionDetection = function() { // Public. Don't use MissileExplosion's inherited collisionDetection() method.
// NO-OP.
}; // Game.bunkerExplosion.collisionDetection()
that.collisionDetection = collisionDetection;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that;
}; // Game.bunkerExplosion()
Because a bunker explosion is quite similar to a missile explosion, the BunkerExplosion
constructor calls the MissileExplosion
constructor and redefines its inherited move
and collisionDetection
methods.
The BunkerExplosion
constructor's NOPcollisionDetection
method points out one of the potential problems of the functional inheritance pattern - it's occasionally necessary to disable inherited functionality. In this case, collision detection functionality for a bunker explosion makes no sense - a bunker explosion exists to gracefully remove a bunker, as described next.
The BunkerExplosion
constructor's move
method causes an SVG circle (centered on the middle of the bunker) to expand close to its maximum radius (i.e., Math.abs(that.radius - currentRadius) < 5
), at which point it removes the bunker (by calling spec.targetBunker.markActive(false)
) and then removes itself when its radius reaches 0 (by calling that.markActive(false)
). Because the bunker explosion circle is on top of the bunker image, it appears as if the bunker was consumed by the explosion.
Note that the rate at which a bunker explosion circle expands and contracts (based on sine) remains constant regardless of the specified frames per second used in the game, due to the line var speed = 3.5 / FPS * GAME_SPEED
(GAME_SPEED
is currently set to 1).
Returning to the first line of BunkerExplosion
, the called MissileExplosion
constructor looks like this:
var MissileExplosion = function(spec) { // Private.
var that = GameObject(spec); // Get all the methods and properties currently exposed by the GameObject constructor.
var speed = 3 / FPS * GAME_SPEED; // Increase this numeric value to increase the rate at which the missile explosion expands.
var t = 0; // A local static variable.
that.core = globals.helpers.createCircle(spec);
that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
t += speed;
that.core.r.baseVal.value = MAX_MISSILE_EXPLOSION_RADIUS * Math.sin(t);
if (that.core.r.baseVal.value <= 0) {
that.markActive(false); // Mark this game object as inactive so that objects[i].exited() returns true.
} // if
}; // Game.MissileExplosion.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var collisionDetection = function() { // Public. Note that this method is called from a FOR loop (so this is actually a doubly nested FOR loop).
for (var i = 0; i < _objects.length; i++) {
if (_objects[i].type == "boulder" && globals.helpers.circlesOverlap(that.core, _objects[i].core)) {
var boomSpec = {};
boomSpec.type = "boom";
boomSpec.image = BOOM_IMAGE;
boomSpec.width = 202; // Actual size of image in pixels - reduce if necessary.
boomSpec.height = 156;
boomSpec.x = _objects[i].core.cx.baseVal.value - (boomSpec.width / 2);
boomSpec.y = _objects[i].core.cy.baseVal.value - (boomSpec.height / 2);
boomSpec.soundPath = BOOM_SOUND;
_objects.push(Boom(boomSpec));
_objects[i].markActive(false); // Mark the boulder object as inactive so that it'll be removed.
updateScore(BOULDER_POINTS); // Credit the score since the user destroyed a boulder with a missile.
} // if
} // for
}; // Game.MissileExplosion.collisionDetection()
that.collisionDetection = collisionDetection;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that;
}; // Game.MissileExplosion()
Because BunkerExplosion
redefines MissileExplosion
's move
and collisionDetection
methods, the only items of value inherited from MissileExplosion
are, of course, the properties and methods inherited from GameObject
and the returned bunker-explosion-SVG-circle:
var MissileExplosion = function(spec) { // Private.
var that = GameObject(spec); // Get all the methods and properties currently exposed by the GameObject constructor.
.
.
.
that.core = globals.helpers.createCircle(spec);
that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value);
.
.
.
return that;
}; // Game.MissileExplosion()
This leads us to the GameObject
constructor itself:
var GameObject = function(spec) { // Private constructor. The base game object constructor - all game objects stem from this constructor.
var that = {};
that.active = spec.active;
if (typeof(that.active) == "undefined" || that.active == null) {
that.active = true;
}
that.movable = spec.movable;
if (typeof(that.movable) == "undefined" || that.movable == null) {
that.movable = true;
}
that.radius = spec.radius || 0; // It's possible that spec.radius is purposely set to zero, so honor this possibility.
that.type = spec.type || 'unknown';
that.core = spec.core || null;
that.x = spec.x; // The initial position of the game object.
that.y = spec.y;
that.targetX = spec.targetX; // The target of the game object (like where a missile should explode).
that.targetY = spec.targetY;
var deltaX = that.targetX - that.x; // Used to calculate a unit vector (i.e., direction vector).
var deltaY = that.targetY - that.y;
var magnitude = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); // Distance, as the crow flies, between the two points.
that.unitX = (deltaX / magnitude); // The x-component of the unit vector from the point (that.x, that.y) to the point (that.targetX, that.targetY).
that.unitY = (deltaY / magnitude); // The y-component of the unit vector from the point (that.x, that.y) to the point (that.targetX, that.targetY).
if (getState().sound) { // True if the user wants sound to be played. False otherwise.
that.audio = new Audio(spec.soundPath); // spec.soundPath is a path to an MP3 file.
if (that.audio) { // If a valid spec.soundPath path was supplied, play the sound effect for the game object (otherwise don't).
that.audio.preload = "auto"; // Attempt to reduce audio latency.
that.audio.play();
} // if
} // if
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var setPosition = function(x, y) { // Public method.
that.x = x || 0;
that.y = y || 0;
}; // Game.GameObject.setPosition()
that.setPosition = setPosition;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var getPosition = function() { // Public.
return globals.helpers.createPoint(that.x, that.y); // In the form of an SVG point object, returns the position of this game object's position.
}; // Game.GameObject.getPosition()
that.getPosition = getPosition; // Make the method public.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public. The default move() method is a NOP.
}; // Game.GameObject.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var collisionDetection = function() { // Public. The default collisionDetection() method is a NOP in that some of the objects created using GameObject() do not move.
}; // Game.GameObject.collisionDetection()
that.collisionDetection = collisionDetection;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var exited = function() { // Public.
return (!that.active); // It's definitely true that a non-active game object has logically exited the game.
}; // Game.GameObject.exited()
that.exited = exited;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var remove = function() { // Public.
var arrayIndex = _objects.indexOf(that); // Return the array index associated with this game object.
var core = that.core;
if (arrayIndex == -1) {
console.log('Error in GameObject.remove(), "that" = ' + that + ' object not found in _objects[], arrayIndex = ' + arrayIndex); // Defensive programming good!
return;
}
if (that.type == "bunker") {
updateScore(BUNKER_POINTS); // Majorly debit the user when a bunker is destroyed.
}
var removeChild = core.parentNode.removeChild(core); // Remove the game object from the DOM, and thus from the screen.
if (!removeChild) {
console.log('Error in GameObject.remove(), "that" = ' + that + ' object was not removed from DOM'); // Defensive programming good!
return;
} // if
_objects.splice(arrayIndex, 1); // Remove the game object from the array of game objects.
}; // Game.GameObject.remove()
that.remove = remove;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var markActive = function(Boolean) { // Public.
that.active = Boolean; // In honor of George Boole, "Boolean" is capitalized (and also because "boolean" is a reserved word).
}; // Game.GameObject.markActive()
that.markActive = markActive;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that; // Return the result of calling the GameObject constructor.
} // Game.GameObject()
Because the GameObject
constructor is the progenitor of all game objects, it's worth reviewing the details. We start by looking at the properties and then each of the methods:
The
spec
parameter can contain a myriad of properties (i.e., game object specifications) that might have special meaning to particular constructors, but theGameObject
constructor is only concerned with the most general ones:var that = {}; that.active = spec.active; if (typeof(that.active) == "undefined" || that.active == null) { that.active = true; } that.movable = spec.movable; if (typeof(that.movable) == "undefined" || that.movable == null) { that.movable = true; } that.radius = spec.radius || 0; // It's possible that spec.radius is purposely set to zero, so honor this possibility. that.type = spec.type || 'unknown'; that.core = spec.core || null; that.x = spec.x; // The initial position of the game object. that.y = spec.y; that.targetX = spec.targetX; // The target of the game object (like where a missile should explode). that.targetY = spec.targetY; var deltaX = that.targetX - that.x; // Used to calculate a unit vector (i.e., direction vector). var deltaY = that.targetY - that.y; var magnitude = Math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) ); // Distance, as the crow flies, between the two points. that.unitX = (deltaX / magnitude); // The x-component of the unit vector from the point (that.x, that.y) to the point (that.targetX, that.targetY). that.unitY = (deltaY / magnitude); // The y-component of the unit vector from the point (that.x, that.y) to the point (that.targetX, that.targetY). if (getState().sound) { // True if the user wants sound to be played. False otherwise. that.audio = new Audio(spec.soundPath); // spec.soundPath is a path to an MP3 file. if (that.audio) { // If a valid spec.soundPath path was supplied, play the sound effect for the game object (otherwise don't). that.audio.preload = "auto"; // Attempt to reduce audio latency. that.audio.play(); } // if } // if
that.active
is used throughout the codebase but in particular, it's used by theexited
method (i.e.,return (!that.active)
). In turn,exited
is used byupdate
to remove game objects that have been destroyed or have left the game's playing field:var update = function() { for (var i = 0; i < _objects.length; i++) { _objects[i].move(); _objects[i].collisionDetection(); if (_objects[i].exited()) { // INDICATES THE GAME OBJECT SHOULD BE REMOVED _objects[i].remove(); // REMOVES THE GAME OBJECT FROM _objects[] } // if } // for . . . }; // Game.update()
The next item in
GameObject
isthat.movable
. This property is used to determine if one object should be drawn on top of another. For example, in thecreateCircle
helper function, we have:if (spec.movable) { _activeGameObjectsContainer.appendChild(circle); // Place the circle in the active DOM container. } else { _staticGameObjectsContainer.appendChild(circle); // Place the circle in the static DOM container. }
Because
<g id="staticGameObjectsContainer">
is physically below<g id="activeGameObjectsContainer">
, objects inserted into thestaticGameObjectsContainer
container appear on top of objects inserted into theactiveGameObjectsContainer
container. For example, inBunker
'scollisionDetection
method, we havebunkerExplosionSpec.movable = false
. This ensures that the bunker-explosion-SVG-circle is appended to thestaticGameObjectsContainer
container thereby drawing it on top of any objects drawn in theactiveGameObjectsContainer
container (as well as the previously drawn bunker image itself).that.radius
andthat.type
are self explanatory.that.core
is used to store the core SVG object associated with the "abstracted" game object. This allows the game object to move itself, as inBoulder
'smove
method:var move = function() { // Public. that.core.cx.baseVal.value += that.unitX * BOULDER_SPEED; that.core.cy.baseVal.value += that.unitY * BOULDER_SPEED; if (that.exited()) { // Use the Missile constructor's exited() method here. that.markActive(false); // Mark the boulder object as inactive so as to get it out of _objects[] (and thus out of the game). } }; // Game.Boulder.move() that.move = move;
Here, the
that.core
property is used to move the SVG circle (i.e., boulder) based on its normalized direction vector (that is, a unit vector):The red arrow is the direction vector and the green arrow its unit vector.
unitX
andunitY
(calculated from thethat.x
,that.y
,that.targetX
, andthat.targetY
values) are the x- and y-components of the (green) unit vector, respectively.The last
GameObject
property to discuss isthat.audio
. In order for multiple sound effects to play currently, each object that specifies an MP3 file path is given an HTML5 audio object:if (getState().sound) { // True if the user wants sound to be played. False otherwise. that.audio = new Audio(spec.soundPath); // spec.soundPath is a path to an MP3 file. if (that.audio) { // If a valid spec.soundPath path was supplied, play the sound effect for the game object (otherwise don't). that.audio.preload = "auto"; // Attempt to reduce audio latency. that.audio.play(); } // if } // if
Note that if
spec.soundPath
is undefined (for example, thespec
parameter for a bunker object doesn't have a sound effect, by design),that.audio
is undefined, andthat.audio.play()
is never called.We next move onto the methods of the
GameObject
constructor.The public
setPosition
andgetPosition
methods were created for convenience.A generic game object (i.e., an object created via
GameObject
) cannot know how to move itself or detect a collision, so both itsmove
andcollisionDetection
methods are NOPs.The
GameObject
'sexited
method is defined next, in that all game objects should remove themselves if theiractive
property is false:var exited = function() { // Public. return (!that.active); // It's definitely true that a non-active game object has logically exited the game. }; // Game.GameObject.exited() that.exited = exited;
We originally used the functional inheritance pattern because all game objects share the same
_objects[]
removal code, specifically:var remove = function() { // Public. var arrayIndex = _objects.indexOf(that); // Return the array index associated with this game object. var core = that.core; if (arrayIndex == -1) { console.log('Error in GameObject.remove(), "that" = ' + that + ' object not found in _objects[], arrayIndex = ' + arrayIndex); // Defensive programming good! return; } if (that.type == "bunker") { updateScore(BUNKER_POINTS); // Majorly debit the user when a bunker is destroyed. } var removeChild = core.parentNode.removeChild(core); // Remove the game object from the DOM, and thus from the screen. if (!removeChild) { console.log('Error in GameObject.remove(), "that" = ' + that + ' object was not removed from DOM'); // Defensive programming is good! return; } // if _objects.splice(arrayIndex, 1); // Remove the game object from the array of game objects. }; // Game.GameObject.remove()
In most web browsers,
indexOf()
is capable of finding a generic object within an array of objects. We use this capability to quickly find the index of the game object to be removed. Then, the game object's associated core object (i.e.,that.core
) is removed from the DOM (i.e.,core.parentNode.removeChild(core)
), which removes it from the screen. Lastly, the game object itself is removed from the array of game objects using the found index value (i.e.,_objects.splice(arrayIndex, 1)
).Take a look at this defensive programming code:
if (arrayIndex == -1) { console.log('Error in GameObject.remove(), "that" = ' + that + ' object not found in _objects[], arrayIndex = ' + arrayIndex); // Defensive programming good! return; }
Proactive but simple techniques like this can be helpful finding and fixing bugs (recall that
indexOf()
returns -1 if it can't find the given object in its array). Another example is the simple act of placing an "unnecessary"default
clause withinswitch
statements, as we see in thesetState
function:default: console.log("Error in switch of setState()");
GameObject
's last method,markActive
, was created for convenience:var markActive = function(Boolean) { // Public. that.active = Boolean; // In honor of George Boole, "Boolean" is capitalized (and also because "boolean" is a reserved word). }; // Game.GameObject.markActive() that.markActive = markActive;
Finally, the last line of the GameObject
constructor returns the object it constructed (via return that
), which is passed to its children - the various game object constructors that call it directly or indirectly through another constructor.
3. Start button is clicked
With the page fully loaded, the user clicks the Start button which, thanks to startButton.addEventListener('click', handleStartButton, false)
, invokes the handleStartButton
event handler:
function handleStartButton() {
var game = globals.game;
if (game.getState().over) {
game.init(); // Note that this sets spec.paused to true.
}
if (game.getState().paused) {
game.run();
document.querySelector('#startButton text').style.textDecoration = "line-through";
}
else {
game.pause();
document.querySelector('#startButton text').style.textDecoration = "none";
}
} // handleStartButton
Because globals.game.init()``spec.paused
to true, the game.getState().paused
clause is executed, invoking game.run()
:
var run = function() { // Public. Invoked when the Start button is clicked.
setSound('play'); // Play any previously paused sound effects.
var msPerFrame = 1000 / spec.fps; // (1000 ms / 1 s ) / (spec.fps frames / 1 s) = the number of milliseconds per frame.
_updateID = window.setInterval(update, msPerFrame); // Using setInterval (as opposed to requestAnimationFrame) to ensure precise frames per second rate for the game.
spec.paused = false; // This is the spec parameter that is passed into the "Game constructor".
globals.helpers.removeText({id: "gameOver"}); // If "Game Over" text is on the screen, this removes it.
globals.helpers.opacify(false); // Un-opacify the screen (whether it needs it or not).
}; // Game.run()
that.run = run; // Make run() a public method.
As described in HTML5 game skeleton code for Boulder Bop, the run
function invokes update
about every 16.67 milliseconds (assuming FPS
= spec.fps
= 60) and update
causes the boulders to descend toward the bunkers, as discussed next.
4. Boulders descend toward bunkers
As shown in step 3, the last line of the init
function is createBoulders(spec.level)
, and here's its code:
var createBoulders = function(level) { // Private.
var boulders = level; // Determines how many boulders fall from the sky per level. Note that Math.floor(1.125*level) + 1 is a bit more aggressive.
var gameBackground = document.getElementById('gameBackground');
while (boulders) {
var boulderSpec = {};
boulderSpec.radius = Math.floor( Math.random() * (MAX_BOULDER_RADIUS - MIN_BOULDER_RADIUS + 1) ) + MIN_BOULDER_RADIUS; // Returns an integer between the min and max values.
boulderSpec.x = Math.floor( Math.random() * (gameBackground.width.baseVal.value + 1) ); // Returns an integer between 0 and the SVG viewport's width.
boulderSpec.y = -boulderSpec.radius; // Position the boulder just off screen.
boulderSpec.targetBunker = getRandomBunker();
boulderSpec.targetX = boulderSpec.targetBunker.x;
boulderSpec.targetY = boulderSpec.targetBunker.y;
boulderSpec.type = "boulder";
boulderSpec.fill = "#963";
boulderSpec.stroke = "white";
boulderSpec.strokeWidth = 2; // In SVG user units.
boulderSpec.movable = true; // Allow the user to click on top of a boulder to send another missile, if they're so inclined.
_objects.push(Boulder(boulderSpec));
boulders--;
} // while
}; // Game.createBoulders()
Because this is the beginning of the game, the level
parameter is equal to 1, hence one boulder object gets pushed into the _objects[]
array via the Boulder
constructor. Before we describe this constructor, let's look at the initialization of boulderSpec
.
boulderSpec
's initialization code randomly chooses a boulder radius (between MAX_BOULDER_RADIUS and MIN_BOULDER_RADIUS), randomly chooses a starting location for the boulder just above the playing field (boulderSpec.x
and boulderSpec.x
), and randomly chooses one of the bunkers as the target for the boulder:
boulderSpec.targetBunker = getRandomBunker();
boulderSpec.targetX = boulderSpec.targetBunker.x;
boulderSpec.targetY = boulderSpec.targetBunker.y;
Here, getRandomBunker()
returns a bunker object (whose location is specified via its x
and y
properties). The rest of boulderSpec
's initialization code is straightforward.
With boulderSpec
initialized, we move onto the constructor that consumes it:
var Boulder = function(spec) { // Private.
var that = Missile(spec); // A boulder is a lot like a missile, so use it's constructor.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
that.core.cx.baseVal.value += that.unitX * BOULDER_SPEED;
that.core.cy.baseVal.value += that.unitY * BOULDER_SPEED;
if (that.exited()) { // Use the Missile constructor's exited() method here.
that.markActive(false); // Mark the boulder object as inactive so as to get it out of _objects[] (and thus out of the game).
}
}; // Game.Boulder.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that; // Return the object created by the Boulder constructor.
}; // Game.Boulder()
Because a boulder is essentially a missile (i.e., they're both SVG circles), the Boulder
constructor calls the Missile
constructor. The only difference between the two is the rate at which their associated objects move across the screen (i.e., MISSILE_SPEED vs. BOULDER_SPEED), hence Boulder
's redefinition of its inherited move
method.
This, then, leads us to the Missile
constructor:
var Missile = function(spec) { // Private. The Boulder() constructor inherits from this constructor.
var that = GameObject(spec);
that.core = globals.helpers.createCircle(spec);
that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value); // Set the initial position of the missile.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var exited = function() { // Public.
var offset = -(MAX_BOULDER_RADIUS + 1); // In SVG user units. This ensures that the boulders initially positioned just off screen won't be tagged as having exited the playing field.
if (!that.active) { // Any game object that is marked as inactive has logically exited the game.
return true;
}
if (that.core.cx.baseVal.value < 0 || that.core.cy.baseVal.value < offset) { // Left and top part of the playing field.
return true;
} // if
if (that.core.cx.baseVal.value > _gameClickPlaneWidth || that.core.cy.baseVal.value > _gameClickPlaneHeight) { // Right and bottom part of the playing field.
return true;
} // if
return false;
}; // Game.Missile.exited()
that.exited = exited;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
var positionX = (that.core.cx.baseVal.value += that.unitX * MISSILE_SPEED);
var positionY = (that.core.cy.baseVal.value += that.unitY * MISSILE_SPEED);
var colorString = 'fill: rgb(';
colorString = colorString + parseInt(Math.abs(positionX) % 256) + ', '; // Constrain the values to be an integer inclusively between 0 and 255.
colorString = colorString + parseInt(Math.abs(positionY) % 256) + ', ';
colorString = colorString + parseInt(Math.abs(positionX + positionY) % 256) + ')';
that.core.setAttribute('style', colorString);
if ( Math.abs(positionX - that.targetX) < TARGET_SENSITIVITY && Math.abs(positionY - that.targetY) < TARGET_SENSITIVITY) {
that.markActive(false); // Mark the missile object as inactive so that _objects[i].exited() returns true on it (in the main game loop in update()).
var missileExplosionSpec = {};
missileExplosionSpec.type = "missileExplosion";
missileExplosionSpec.fill = "gold";
missileExplosionSpec.stroke = "white";
missileExplosionSpec.strokeWidth = 2; // In SVG user units.
missileExplosionSpec.x = that.targetX;
missileExplosionSpec.y = that.targetY;
missileExplosionSpec.radius = 0; // Start a missile explosion with a radius of zero, since this radius sinusoidally grows from a radius equal to 0.
missileExplosionSpec.movable = true; // Allow the user to click on top of an explosion to send another missile.
_objects.push(MissileExplosion(missileExplosionSpec));
} // if
}; // Game.Missile.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that; // Return the object created by the Missile constructor.
}; // Game.Missile()
The Missile
constructor inherits from GameObject
, creates an SVG circle (based on its spec
parameter), and redefines its inherited exited
and move
methods.
The redefined exited
method returns true if the missile is not active (i.e., !that.active
) or it has left the playing field. The one caveat is the that.core.cy.baseVal.value < offset
check. If this check was that.core.cy.baseVal.value < 0
, then a boulder's exited
method would immediately return true as soon as the boulder was positioned above the playing field via createBoulders
. By setting offset
to -(MAX_BOULDER_RADIUS + 1)
, we let the boulders be positioned just above the sky so they appear to descend from the heavens toward their target bunkers.
Because Boulder
redefines Missile
's move
method, it is not discussed here but covered next.
5. User fires missile toward descending boulder
With a boulder descending towards a bunker from the top of the playing field, the user tries to destroy it by clicking (or tapping) near it. Thanks to gameClickPlane.addEventListener('click', handleGameClickPlane, false)
, this click event is handled by the handleGameClickPlane
event handler:
function handleGameClickPlane(evt) {
var helpers = globals.helpers;
var game = globals.game;
var point = null;
if (!game.getState().paused) { // If the game is not paused, then it's running.
point = helpers.createPoint(evt.pageX, evt.pageY);
point = helpers.coordinateTransform(point, evt.target); // Using the same point object, convert screen coordinates to playing field coordinates.
game.createMissile(point); // Create a new missile object and add it to the list of core game objects.
} // if
} // handleGameClickPlane
Here, the click position (evt.pageX
, evt.pageY
) is converted from screen coordinates to game playing field coordinates (as described in SVG Coordinate Transformations (Windows)) and used to fire a missile via game.createMissile(point)
:
var createMissile = function(target) { // Public. The target parameter is a point object representing the location (center point) of where the missile should explode.
var missileSpec = getClosestBunker(target).getPosition(); // Get the position of the bunker that is closest to target.
missileSpec.targetX = target.x;
missileSpec.targetY = target.y;
missileSpec.type = "missile";
missileSpec.radius = MISSILE_RADIUS; // In SVG user units.
missileSpec.stroke = "white";
_objects.push(Missile(missileSpec));
updateScore(MISSILE_POINTS); // Debit the user for firing a missile.
}; // Game.createMissile()
that.createMissile = createMissile; // A public method for one or more event handers.
To decide which bunker the missile should be fired from, we need to know which bunker is closest to the user's click point (i.e., the target
parameter). This is handled by the getClosestBunker
function (which uses the globals.helpers.euclideanDistance
function).
With this info and some other details safely ensconced in missileSpec
, we push a missile object into _objects[]
via the Missile
constructor. Except for its move
method, the Missile
constructor was described in step 4. We now describe this move
method (copied from step 4):
var Missile = function(spec) { // Private. The Boulder() constructor inherits from this constructor.
var that = GameObject(spec);
that.core = globals.helpers.createCircle(spec);
that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value); // Set the initial position of the missile.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var exited = function() { // Public.
var offset = -(MAX_BOULDER_RADIUS + 1); // In SVG user units. This ensures that the boulders initially positioned just off screen won't be tagged as having exited the playing field.
if (!that.active) { // Any game object that is marked as inactive has logically exited the game.
return true;
}
if (that.core.cx.baseVal.value < 0 || that.core.cy.baseVal.value < offset) { // Left and top part of the playing field.
return true;
} // if
if (that.core.cx.baseVal.value > _gameClickPlaneWidth || that.core.cy.baseVal.value > _gameClickPlaneHeight) { // Right and bottom part of the playing field.
return true;
} // if
return false;
}; // Game.Missile.exited()
that.exited = exited;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
var positionX = (that.core.cx.baseVal.value += that.unitX * MISSILE_SPEED);
var positionY = (that.core.cy.baseVal.value += that.unitY * MISSILE_SPEED);
var colorString = 'fill: rgb(';
colorString = colorString + parseInt(Math.abs(positionX) % 256) + ', '; // Constrain the values to be an integer inclusively between 0 and 255.
colorString = colorString + parseInt(Math.abs(positionY) % 256) + ', ';
colorString = colorString + parseInt(Math.abs(positionX + positionY) % 256) + ')';
that.core.setAttribute('style', colorString);
if ( Math.abs(positionX - that.targetX) < TARGET_SENSITIVITY && Math.abs(positionY - that.targetY) < TARGET_SENSITIVITY) {
that.markActive(false); // Mark the missile object as inactive so that _objects[i].exited() returns true on it (in the main game loop in update()).
var missileExplosionSpec = {};
missileExplosionSpec.type = "missileExplosion";
missileExplosionSpec.fill = "gold";
missileExplosionSpec.stroke = "white";
missileExplosionSpec.strokeWidth = 2; // In SVG user units.
missileExplosionSpec.x = that.targetX;
missileExplosionSpec.y = that.targetY;
missileExplosionSpec.radius = 0; // Start a missile explosion with a radius of zero, since this radius sinusoidally grows from a radius equal to 0.
missileExplosionSpec.movable = true; // Allow the user to click on top of an explosion to send another missile.
_objects.push(MissileExplosion(missileExplosionSpec));
} // if
}; // Game.Missile.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that; // Return the object created by the Missile constructor.
}; // Game.Missile()
The first 2 lines move the missile across the screen:
var positionX = (that.core.cx.baseVal.value += that.unitX * MISSILE_SPEED);
var positionY = (that.core.cy.baseVal.value += that.unitY * MISSILE_SPEED);
The next 5 lines change the color of the missile based on its current position:
var colorString = 'fill: rgb(';
colorString = colorString + parseInt(Math.abs(positionX) % 256) + ', '; // Constrain the values to be an integer inclusively between 0 and 255.
colorString = colorString + parseInt(Math.abs(positionY) % 256) + ', ';
colorString = colorString + parseInt(Math.abs(positionX + positionY) % 256) + ')';
that.core.setAttribute('style', colorString);
Now, when the missile is close enough to its intended target (i.e., where the user clicked/tapped), it explodes:
if ( Math.abs(positionX - that.targetX) < TARGET_SENSITIVITY && Math.abs(positionY - that.targetY) < TARGET_SENSITIVITY) {
that.markActive(false); // Mark the missile object as inactive so that _objects[i].exited() returns true on it (in the main game loop in update()).
var missileExplosionSpec = {};
missileExplosionSpec.type = "missileExplosion";
missileExplosionSpec.fill = "gold";
missileExplosionSpec.stroke = "white";
missileExplosionSpec.strokeWidth = 2; // In SVG user units.
missileExplosionSpec.x = that.targetX;
missileExplosionSpec.y = that.targetY;
missileExplosionSpec.radius = 0; // Start a missile explosion with a radius of zero, since this radius sinusoidally grows from a radius equal to 0.
missileExplosionSpec.movable = true; // Allow the user to click on top of an explosion to send another missile.
_objects.push(MissileExplosion(missileExplosionSpec));
} // if
This missile explosion is covered next.
6. Missile explodes at click point
From the code in step 5, we see that the missile is close enough to its intended target when the following is true:
Math.abs(positionX - that.targetX) < TARGET_SENSITIVITY &&
Math.abs(positionY - that.targetY) < TARGET_SENSITIVITY
That is, the position of the missile (positionX
, positionY
) is "close enough" to where the user clicked (that.targetX
, that.targetY
) when their absolute difference is less than TARGET_SENSITIVITY
(which has an empirically derived value of 15).
When this condition is true, we mark the missile object for destruction (that.markActive(false)
), initialize missileExplosionSpec
, and push a missile explosion object into _objects[]
via MissileExplosion
(copied from step 5):
var MissileExplosion = function(spec) { // Private.
var that = GameObject(spec); // Get all the methods and properties currently exposed by the GameObject constructor.
var speed = 3 / FPS * GAME_SPEED; // Increase this numeric value to increase the rate at which the missile explosion expands.
var t = 0; // A local static variable.
that.core = globals.helpers.createCircle(spec);
that.setPosition(that.core.cx.baseVal.value, that.core.cy.baseVal.value);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var move = function() { // Public.
t += speed;
that.core.r.baseVal.value = MAX_MISSILE_EXPLOSION_RADIUS * Math.sin(t);
if (that.core.r.baseVal.value <= 0) {
that.markActive(false); // Mark this game object as inactive so that objects[i].exited() returns true.
} // if
}; // Game.MissileExplosion.move()
that.move = move;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var collisionDetection = function() { // Public. Note that this method is called from a FOR loop (so this is actually a doubly nested FOR loop).
for (var i = 0; i < _objects.length; i++) {
if (_objects[i].type == "boulder" && globals.helpers.circlesOverlap(that.core, _objects[i].core)) {
var boomSpec = {};
boomSpec.type = "boom";
boomSpec.image = BOOM_IMAGE;
boomSpec.width = 202; // Actual size of image in pixels - reduce if necessary.
boomSpec.height = 156;
boomSpec.x = _objects[i].core.cx.baseVal.value - (boomSpec.width / 2);
boomSpec.y = _objects[i].core.cy.baseVal.value - (boomSpec.height / 2);
boomSpec.soundPath = BOOM_SOUND;
_objects.push(Boom(boomSpec));
_objects[i].markActive(false); // Mark the boulder object as inactive so that it'll be removed.
updateScore(BOULDER_POINTS); // Credit the score since the user destroyed a boulder with a missile.
} // if
} // for
}; // Game.MissileExplosion.collisionDetection()
that.collisionDetection = collisionDetection;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
return that;
}; // Game.MissileExplosion()
As update
processes the _objects[]
array, it eventually executes the missile explosion object's move
method:
var move = function() { // Public.
t += speed;
that.core.r.baseVal.value = MAX_MISSILE_EXPLOSION_RADIUS * Math.sin(t);
if (that.core.r.baseVal.value <= 0) {
that.markActive(false); // Mark this game object as inactive so that objects[i].exited() returns true.
} // if
}; // Game.MissileExplosion.move()
that.move = move;
Due to Math.sin(t)
, the missile-explosion-SVG-circle expands to a maximum radius and then contracts to zero (or below). When it hits zero (or goes negative), it's marked for destruction.
Next, we have MissileExplosion
's collisionDetection
method:
var collisionDetection = function() { // Public. Note that this method is called from a FOR loop (so this is actually a doubly nested FOR loop).
for (var i = 0; i < _objects.length; i++) {
if (_objects[i].type == "boulder" && globals.helpers.circlesOverlap(that.core, _objects[i].core)) {
var boomSpec = {};
boomSpec.type = "boom";
boomSpec.image = BOOM_IMAGE;
boomSpec.width = 202; // Actual size of image in pixels - reduce if necessary.
boomSpec.height = 156;
boomSpec.x = _objects[i].core.cx.baseVal.value - (boomSpec.width / 2);
boomSpec.y = _objects[i].core.cy.baseVal.value - (boomSpec.height / 2);
boomSpec.soundPath = BOOM_SOUND;
_objects.push(Boom(boomSpec));
_objects[i].markActive(false); // Mark the boulder object as inactive so that it'll be removed.
updateScore(BOULDER_POINTS); // Credit the score since the user destroyed a boulder with a missile.
} // if
} // for
}; // Game.MissileExplosion.collisionDetection()
that.collisionDetection = collisionDetection;
Again, the update
method goes through all game objects in _objects[]
and eventually invokes the missile explosion's collisionDetection
method. The for
loop (in collisionDetection
) goes through all active boulders (SVG circles) to determine if any of them overlap with this missile explosion (another SVG circle):
if (_objects[i].type == "boulder" &&
globals.helpers.circlesOverlap(that.core, _objects[i].core)) {
.
.
.
}
If so, we prepare to destroy the boulder, as described next.
7. Boulder is destroyed by missile explosion
When a boulder (SVG circle) overlaps with a missile explosion (SVG circle), a boom object is specified (boomSpec
), created (via Boom
), pushed into _objects[]
, the struck boulder is marked for destruction, and the user's points are updated (for successfully destroying a boulder):
var boomSpec = {};
boomSpec.type = "boom";
boomSpec.image = BOOM_IMAGE;
boomSpec.width = 202; // Actual size of image in pixels - reduce if necessary.
boomSpec.height = 156;
boomSpec.x = _objects[i].core.cx.baseVal.value - (boomSpec.width / 2);
boomSpec.y = _objects[i].core.cy.baseVal.value - (boomSpec.height / 2);
boomSpec.soundPath = BOOM_SOUND;
_objects.push(Boom(boomSpec));
_objects[i].markActive(false); // Mark the boulder object as inactive so that it'll be removed.
updateScore(BOULDER_POINTS); // Credit the score since the user destroyed a boulder with a missile.
Hopefully, this flow of control walkthrough helps you understand how Boulder Bop works in all of its game playing scenarios.
In the next two sections, Debugging an HTML5 game and Replacing setInterveral with requestAnimationFrame in a HTML5 game, we cover some basic debugging techniques and show how to replace Boulder Bop's setInterval call with requestAnimationFrame.