Javascript Boulderdash
Tue, Oct 25, 2011
Still on the subject of exploring HTML5 games, the next game I chose to implement was a <canvas>
version of the c64 classic Boulderdash.
- play the game now
- view the source code
Boulderdash
Anyone who owned a c64 back in the 80’s will likely have fond memories of the Boulderdash series of games. In fact they were so popular that there are a multitude of fan sites and remakes, even the original publisher First Star is still making commercial versions for todays mobile platforms. I highly recommend you go purchase one for your favorite device.
I wanted to take a stab at making an HTML5 version, and found a huge amount of information on Martijn’s and Arno’s fan sites… including cave data and an actual specification!
This wealth of information allowed me to put together a playable version of Boulderdash in just a couple of weekends.
Room for Improvement
This is just a programming experiment of the Boulderdash game mechanics. It’s not intended to be a shippable game and therefore does not include a menu system or sound effects, but it does include all 20 levels from the original Boulderdash I game.
If you were to polish this game further you would want to add things like:
- splash screen and menu
- music and sound fx
- some modern graphical flair
- a cave editor
- support for importing caves in BDCFF format.
- touch support
Implementation Details
The game consists of a single static index.html
page, a pretty minimal boulderdash.css
stylesheet and two javascript files:
boulderdash.js
- the game logic and renderercaves.js
- the cave data
There are no 3rd party libraries or server side components needed, simply use your web server of choice to serve up the static files.
The game can be broken down into a game loop plus 3 main areas, and I will write each of those up as a separate article over the next couple of days, so check back here shortly for links to the following articles:
But to get us started lets talk about…
The Game Loop
The fundamental Boulderdash algorithm iterates through the cave, updating every object once in each game frame. The original c64 version of Boulderdash controls its difficulty by adjusting the game logic frame rate - meaning - the faster the frame rate, the more times each object is updated and therefore, the faster Rockford moves or the boulders fall.
On difficulty level 1 (the easiest) this game frame rate is approximately 10fps.
The game frame rate is independent of the rendering frame rate. In fact when we talk about rendering we will find we have a number of sprites that must be animated at different frame rates, some as slow as 8fps, others as fast as 30fps, in order to animate at the correct speed.
That sounds kind of complex, but is actually quite simple in practice, we just need to maintain a game loop whose rate is independent of our rendering loop. This is generally considered good practice for games anyway to avoid variations in hardware rendering performance affecting our underlying game physics behavior.
Lets start by assuming we have 2 objects representing our game logic and our renderer:
var game = new Game(),
var render = new Render(game);
Don’t worry! These will be examined in excruciating detail in the upcoming articles, but for now, we only need to know that they each define
an update()
method and 2 constants: fps
- the desired frame rate and step
= 1/fps
.
We will need a way to measure the current time. Until we get a high resolution javascript timer (don’t hold your breath) we must do it the old fashioned way:
function timestamp() {
return new Date().getTime();
};
We will run our main loop as fast as the browser allows (using requestAnimationFrame), but we will maintain independent game and render loops using the following 5 variables:
now
- the time at the start of this looplast
- the time at the start of the previous loopdt
- the delta betweennow
andlast
gdt
- the accumulated delta since we last rangame.update()
rdt
- the accumulated delta since we last ranrender.update()
We accumulate time until we reach the step
required to trigger an update
:
var now, last = timestamp(), dt = 0, gdt = 0, rdt = 0;
function frame() {
now = timestamp();
dt = Math.min(1, (now - last) / 1000); // (see NOTE)
gdt = gdt + dt;
while (gdt > game.step) {
gdt = gdt - game.step;
game.update();
}
rdt = rdt + dt;
if (rdt > render.step) {
rdt = rdt - render.step;
render.update();
}
last = now;
requestAnimationFrame(frame);
}
NOTE: Since requestAnimationFrame will go idle when the browser is not visible, it is possible for dt to be very large, therefore we limit the actual dt to automatically ‘pause’ the loop when this happens.
Interesting to note is that when we do have a large dt
we make sure to run game.update
in a while loop to ensure the
game ‘catches up’ without missing any updates, but we don’t bother doing this for render.update
where simply rendering
the most recent state once is enough to catch up.
Loading Assets
Once last thing we must deal with before actually starting the loop is to be sure our external image resources have loaded and are ready for rendering to the canvas.
For Boulderdash, all of our sprites are contained on a single sprites.png
image, so we need a very simple function for creating
our single image and waiting until it has finished loading:
function load(cb) {
var sprites = document.createElement('img');
sprites.addEventListener('load', function() { cb(sprites); } , false);
sprites.src = 'images/sprites.png';
};
With this in place, starting the game loop can become as simple as:
load(function(sprites) {
render.reset(sprites); // reset the canvas renderer with the loaded sprites <IMG>
game.reset(); // reset the game
frame(); // ... and start the first frame !
});
Summary
Encapsulating the above code into a single run()
function gives us our final game loop:
var game = new Game(),
render = new Render(game);
function run() {
var now, last = timestamp(), dt = 0, gdt = 0, rdt = 0;
function frame() {
now = timestamp();
dt = Math.min(1, (now - last) / 1000);
gdt = gdt + dt;
while (gdt > game.step) {
gdt = gdt - game.step;
game.update();
}
rdt = rdt + dt;
if (rdt > render.step) {
rdt = rdt - render.step;
render.update();
}
stats.update();
last = now;
requestAnimationFrame(frame, render.canvas);
}
load(function(sprites) {
render.reset(sprites); // reset the canvas renderer with the loaded sprites <IMG>
game.reset(); // reset the game
frame(); // ... and start the first frame !
});
};
Next…
Now that we have our game loop, I can go into detail about our game
object that implements
the Boulderdash game logic, followed by a description of the render
object that implements
rendering to the HTML5 <canvas>
. Then finally I’ll talk about where we get the Boulderdash
cave data from that defines each of our 20 levels.
Related Links
- play the game now
- view the source code
- read more about how it works:
More information…
- The original publishers - First Star
- Martijn’s Boulderdash Fan Site
- Arno’s Boulderdash Fan Site
- Boulderdash Common File Format BDCFF
- Boulderdash on the c64
- Boulderdash on Wikipedia
Game specs…