A crude JavaScript implementation of the first stage of Super Mario Brothers has been making the rounds today. It’s roughly playable but misses many key aspects (no mushrooms, no flag, no one-ups, etc.). However, that’s not what’s particularly interesting about the game.
Perhaps the most interesting part of the application is not within the game mechanics itself but in the fact that all of the game contents are embedded within the game script: This includes all game sprites and music.
This is an interesting feat and one that’s possible to accomplish within your own code. Let’s take a step through how this was done to understand how we can better use this within our own sites.
Music Embedding (base64)
Surprisingly, how the music is embedded within the application is, perhaps, the easiest to completely understand. The application makes use of data: URLs, encoding the Mario music MIDI files using base64.
We can see the result within the source file:
aSounds = [ // very small, very simple mario theme. Sequenced by Mike Martel. "data:audio/mid;base64,TVRoZAAAAAYAAQAEAMBNVH...", // game over. Sequenced by John N. Engelmann. "data:audio/mid;base64,TVRoZAAAAAYAAQADAHhNVH..." ],
data: URLs work by encoding the entire contents of the specified files within the URL to the file itself. This can even be used for images (embedding PNGs, JPGs, GIFs, etc.). In this case it’s being sent to an <embed/> element, which is capable of playing the midi file. We can spot the resulting injection code here:
playMusic = function(iSoundID, bLoop) { if (!bMusic) return; var oEmbed = dc("embed"); oEmbed.src = aSounds[iSoundID]; oEmbed.id = "sound_" + iSoundID; if (bLoop) oEmbed.setAttribute("loop", "true"); oEmbed.setAttribute("autostart", "true"); oEmbed.style.position = "absolute"; oEmbed.style.left = -1000; appChild(document.body, oEmbed); },
Which simply creates an embed element, sets it to automatically start, and injects the data: url as the src. The result is an auto-playing MIDI file (assumedly a similar result could be achieved with another universal file format, such as WAV).
data: URLs are capable of playing in all browsers but Internet Explorer (they’re being introduced in Internet Explorer 8). In the case of this application Internet Explorer users simply don’t receive audio for the game.
Image Embedding (binary and ascii)
The second, interesting, portion of the application is the embedding of the various sprites used in the game. This includes the mario character, the blocks, pipes, and even the background images.
The application uses a number of interesting techniques in order to compress the size of the resulting images. Rather than using the data: url technique (which is completely applicable, in this case, to applications that can use them) the game, instead, uses the <canvas/> element (and regular div/spans for Internet Explorer). Because of this dual platform target the individual pixels of the sprites must be saved and retrieved.
In order to meet this target all of the individual pixels must be stored in a way that is space efficient, and yet, easy to access.
To start, all available colors must be mapped out and reduced. All sprites within the game have, at most, 4 colors (some have less). We can see this reduction process in the following graphic:
This reduction, and mapping into a binary grid, is a useful way to create a portable sprite (each sprite is reduced to the binary and associated colors). The next step is to convert the binary grid into an easily-transportable string, like the following:
The end result is a string representation of the entire sprite – which is quite small (byte-wise) while still being translatable back to its original representation.
Both of these encoding techniques can prove to be quite useful in your JavaScript code – especially if you’re striving to encapsulate as much within the base code, itself, rather than in external files.
Daniel Stockman (April 9, 2008 at 3:54 pm)
Awesome visuals, great exploration of dense code.
I wonder why Webkit nightlies are puking all over it…
(First new thought since reading this: String.charCodeAt() discrepancies)
Daniel Stockman (April 9, 2008 at 3:56 pm)
Nevermind! Latest nightly works just fine (no sound, though)
Francisco Brito (April 9, 2008 at 5:19 pm)
This is one of those events that becomes a milestone and changes the landscape: from a 14k curiosity to a simple-to-understand analysis to maybe a trend, where ultimately we all end up embedding images and data this way.
Let’s see how soon someone comes up with a jQuery plugin and a Firebug extension.
Cris (April 9, 2008 at 5:21 pm)
What’s the rationale for starting the sprite encoding at ASCII 161? My guess is that it’s the start of the first contiguous range of 128 non-space characters.
Tim McCormack (April 9, 2008 at 5:27 pm)
You can also generate images on the fly. I wrote a little Javascript app to generate BMP v3 files. (That one just generates a semi-random raster.)
John Resig (April 9, 2008 at 5:28 pm)
@Cris: The actual code used in the game uses a mixture of ASCII characters (both low and high). For simplicity in explanation I started at 161 for the contiguous range, as you pointed out.
jmdesp (April 9, 2008 at 6:03 pm)
Can you give a little more details about the div/spans technic for Internet Explorer ? I was thinking a while ago about creating a page with graphic elements embedded, but I thought the lack of data url on IE6 meant it was a “no go” on it.
Sander Aarts (April 9, 2008 at 6:23 pm)
Very nice, no sound in Opera (9.27) though.
Antonio (April 9, 2008 at 8:18 pm)
John,
Great dissection— well done! And as you say, this is super helpful as a guide for ppl building JS applications.
AR
Jacob Seidelin (April 10, 2008 at 1:46 am)
John, thanks for the dissection. The sprite thing was indeed the most interesting part of it, I think. You explained better than I probably could.
JINN NGUYEN (April 10, 2008 at 8:11 am)
Are you making deal with devil or what …
That’s cool man!
aaron (April 10, 2008 at 8:26 am)
thanks for the explaination
Dan Hiester (April 10, 2008 at 8:57 am)
I’m not a programmer, but what’s amusing to me about this is, having looked at the source code to another NES game (Metroid), I can’t help but wonder if this technique merely implements the same type of programming technique it took to write the original game.
Mega69 (April 10, 2008 at 1:44 pm)
This is very cool.
Ellisgl (April 11, 2008 at 1:15 pm)
I was thinking about the sprite and how deal with the colors when I first played the JS Mario.
I was thinking of doing an array for the color pallet – say 16 color max.
//php like code
$colors[‘0’] = ‘#000000’;
…
$colors[‘F’] = ‘#FFFFFF’;
For the sprite I would set up an array like this
$sprite[‘spritename’][‘size’][‘X’] = ‘8’; // PX
$sprite[‘spritename’][‘size’][‘Y’] = ‘8’; // PX
$sprite[‘spritename’][‘data’][‘0’][‘0’] = ‘F’; // $color[‘f’]
$sprite[‘spritename’][‘data’][‘0’][‘1’] = ‘F’; // $color[‘f’]
If there was a pixel I wanted to be clear – I wouldn’t even create the element for that X, Y position.
Mike Branski (April 11, 2008 at 1:17 pm)
The first thing that came to mind that one could do with this is to create a self-contained Lightbox plug-in so the default graphics are rendered in a similar fashion, but would still be overridable by the user if they so chose.
Breton (April 13, 2008 at 5:12 pm)
Last time I looked, IE6 *does* support Data: uri, and it was something that was explicitly removed from IE7 with much disappointment, only to be reintroduced in IE8. The main limitation is that IE6 has such a low limit on URL length that makes some applications of the technology impractical.
In reply to Dan, as someone who has dabbled in some gameboy programming, I can say that in the world of assembler language, the line between data and program is very blurred. It’s common practice to embed graphics directly in source code in a manner very similar to the way demonstrated above. THe main difference is that in assembler, it isn’t a dirty hack, and is quite a natural sort of thing to be doing. The ability to encode patterns of bits directly into binary data, and decode it again is built right in.
p01 (April 14, 2008 at 1:51 am)
Since Jacob Seidelin obviously had compression in mind, I wonder why he did this base128 encoding at all. Surely it is fun and worth a few extra geek points but the increased entropy render any JS packer useless ( minification pre-pass aside ). Had he stored the sprites in a more basic format it would have been a heck of lot smaller once packed.
¤ (May 4, 2008 at 9:03 am)
There’s a new version up over at nihilogic that uses even more interesting techniques.
http://blog.nihilogic.dk/2008/05/8-kilobytes-of-mario.html