> Build and publish an HTML5 game for iPad – Part 2

The HTML5 game development holds steady, last week I was experimenting with web sql, localStorage and the PhoneGap APIs. I admit that it is slightly more complicated than I initially estimated, but it wouldn’t be fun if it weren’t challenging.

Japanese translation (thanks to Yuji Ichikawa)

PhoneGap Environment

I’m not actually developing on PhoneGap in this first stage, but I want to be sure that the code is going to work on both the browser and the PG WebView. Developing for PG is almost like developing for the browser, with some notably exceptions.

Sometimes (especially for games) it’s important to be able to lock the device orientation. Fortunately PG supports a (undocumented) function called shouldRotateToOrientation that does just that.

Adding the following to any globally accessible part of code is all you need to lock the screen into portrait mode:

function shouldRotateToOrientation (rotation) {
    return !(rotation%180);
}

Remove the ! for landscape. I’m adding the function definition as the very first thing in my code (even before including the phonegap.js file) so I’m totaly sure that the function is defined when called by PG.

Alternatively you can hardcode the application orientation directly from Xcode. Select your project (the first entry in the left column), then under TARGETS choose the “Supported Device Orientations”.

Another difference between browser and PG is document ready detection.

If you want to know when the browser loaded all its stuff and is ready to rock your code you’d usually listen to DOMContentLoaded or load events. PG is split between native and JS code and both need to be up and running for your application to kick start.

For this purpose PG offers the deviceready event. For some reasons this event seems to be called after the document load event. After spending more time than I wished on the documentation I ended up with the following configuration that seems to be pretty stable.

<head>
<script type="text/javascript">
function loaded () {
	window.PhoneGap ? document.addEventListener("deviceready", ready, false) : ready();
}

function ready () {
	setTimeout(function () {
		Game.init();
	}, 200);
}

</script>
</head>

<body onload="loaded()">
...
</body>

On document load if PhoneGap is defined (ie: if I’m not on the standard browser) we start listening to the deviceready event, otherwise we can fire the application right away.

You’ll notice that I execute the ready() function with 200ms delay. While not strictly needed, I believe that letting the engine rest for few ms helps to avoid unpredictable and weird DOM errors.

This is particularly true when dealing with many complex CSS animations. I often need to concatenate animations or transitions and execute some code at the end of each step.

Eg: fade out element > update content > fade in element > move element to x,y > set the “finished” variable. When the project becomes complex and many animations occur at the same time, letting the engine rest for few ms improves stability and helps keeping the animations within the given timeframe (you know, 200ms is more like “anything between 100ms and 2 seconds”).

Another important aspect is localization. I’m building a word game, so being able to change language is crucial. I found that the easiest way to internationalize the application is by reading the navigator.language property. It holds the device locale such as en-US or it-IT. All left to do is to load a JSON file with the localized texts based on the language variable. A piece of cake.

AJAX calls

I prefer to load data files with XMLHttpRequests instead of selectively including javascript files with the <scripts> tag. The problem here is that files on PG are all local, they are not served by a web server. This means that you do not get a 200 (OK) or 304 (cached) status codes when the file is loaded. The returned status is instead always 0. If you use an external ajax library it may or may not be aware of this difference.

A very lame example of ajax call is like so:

var req = new XMLHttpRequest();
req.open('GET', 'filename', true);
req.onreadystatechange = function () {
	if (req.readyState != 4) return;
			
	if (req.status !== 0 && req.status != 200 && req.status != 304) {
		// Error
		return;
	}

	// Success! Do something here.
}

req.send(null);

You’ll notice that I’m checking req.status for 0 together with 200 and 304. This way we can use http requests in both the browser and PhoneGap. If your ajax library doesn’t work on PG you now know why, and you can easily tweak it with a small change. As a side story… yes, those 9 lines of code are pretty much all you need to ajax.

At this point I should have a PhoneGap ready environment that I can also debug on the browser.

Web sql vs. localStorage vs. JS Object

I’m developing a word game that of course is based on a dictionary. Working with huge amount of data is never easy on JS, especially if your application is offline.

I had mainly three choices: web sql, localStorage and a JS Object. The following are the pros and cons of each technique.

Web sql

Everything is held into a sqlite database. The size limit is 5mb but indexed tables take a lot of space, so you can actually store around 2mb of data.

PROS:

  • Extremely flexible, you can perform very complex SQL queries.
  • It somehow feels “natural” to store data into a database.

CONS:

  • DB must be maintained, you have to take care of db differences between revisions.
  • Queries on not indexed data are slow.
  • APIs are more complicated than the other methods.

localStorage

The word list is injected directly into a localStorage and accessed with regular expressions or on-the-fly string to object conversion. The limit is again 5mb but data is saved in UTF-16, so each character takes twice the space, bringing the total storable size to 2.5mb.

PROS:

  • Simple API, very easy to manage.
  • Very fast with small chunks of data.

CONS:

  • You can store only strings (objects and arrays are not supported at this time).
  • Slow with lots of data.

JS Object

Load the whole database into a JS array/object.

PROS:

  • Faaaaaaaaaast!
  • Super easy to manage and search for words

CONS:

  • It sucks up all the device memory

The only reason to go the JS Object path is to offer a “versus CPU” mode. The other methods need 10 to 30 seconds per round on iPad 1, that is not acceptable. The problem is that you never know how much memory you can allocate on the browser. I’ve made countless tests and I don’t really feel comfortable putting so much data into the device limited memory. With some profiling a Trie compressed word list took up to 30mb of RAM. Call me coward, but I don’t have all that faith in the WebView, and I’m not going to leech so many resources for my stupid game.

I could have probably carried a small portion of the database into memory while keeping the rest on disk, but that would have added complexity to the code and I’m trying to keep it simple and fast. So I decided to go localStorage. 90% of the times the user will be dealing with less than 6 letters words, therefore I split the database into two chunks: less (or equal) than 6 letters and more than 6 letters (the max letter count per word is 8).

I’m not going to offer a versus CPU mode (you’ll be fighting against time), so speed is not an issue, but the 6 letters words file takes only 100kb and regexing it is rather fast.

Of course the words list is slightly compressed, but it’s still easy to query with RegExp. I made quite a few tests to find a good compromise between compression and regexp speed. The file looks like this:

...;PAL,ACE,AIS,APA,ATE,E,EA,EAE,EAL,ED,ELY,ER,ES,EST,ET,ETS,IER,ING,ISH,L,LED,LET,LIA,LID,LOR,LS,LY,M,MAR,MED,MER,MS,MY,P,PAL,PED,PI,PS,PUS,S,SY,TER,TRY,Y;...

Since the game shortest word is 3 letters long, I take a triplet as root and remove that root from subsequent words with the same prefix. The above list translates into:

PAL
PALACE
PALAIS
PALAPA
PALATE
PALE
...

This syntax keeps the file easily searchable with a compress ratio close to the 40%.

What’s next

With hindsight I wouldn’t have chosen a word game as the first HTML5 experiment. Dealing with lots of data in the offline browser is no easy task, but I’m glad I decided to post about this experience, it helps me to stay on track and motivated.

Next time I’ll probably show you a demo or at least a screencast of the game. So stay tuned.

/Share the joy

/Reactions

    • Author: ICHIKAWA, Yuji
    • Posted on: 2011/11/22
    • At: 03:14

    Thank you for your interesting article.

    Here is a Japanese translation.
    http://d.hatena.ne.jp/nextliteracy/20111122/1321927892

    • great job again. thanks. I’ve added a link to your translation.

  • Bello!
    Let me know if you need help with PhoneGap best practices!

    • Author: Cokegod
    • Posted on: 2011/11/24
    • At: 09:49

    Another con of WebSQL is that it is deprecated, so if you want your game to be supported in future browsers you should not use WebSQL.

    • Author: Horst
    • Posted on: 2011/11/28
    • At: 11:05

    Very cool article. I use PhoneGAP and work on a game, too. Android version is still available, but on iOS i have added much css animations and i still working on details. ;-)
    I use 2 canvas. One static for the background and one for the sprites. I have not many moving objects, but it could be smoother. On iPad2 it’s no problem. But i use also an iPod Touch 8GB for testing.

    BTW: Have you checked sprite.js?
    https://github.com/batiste/sprite.js/

    BTW2: You can’t use navigator.language on android. You always get english.
    Some solution is here: http://groups.google.com/group/phonegap/browse_thread/thread/1240b21013bc0e3e

    • Author: Horst
    • Posted on: 2011/12/02
    • At: 15:14

    One additional comment: It seems, that phonegap can’t play ingame audio on iOS. I have some troubles with it: http://groups.google.com/group/phonegap/browse_thread/thread/1f4880ae4bce4671

    • I’m working on audio right now. I’ll keep you updated. In the weekend I’ll post the third episode

  • nice post…

    • Author: Smulow
    • Posted on: 2011/12/10
    • At: 23:35

    Good thanks