Pirate Pennywise is a simple browser hosted game (tested in: Safari, Firefox, Chrome) that I made in my spare time. It is a personal side project, that has taken approximately 3 months (of occasional evenings) to complete. In this post I’d like to reflect on the development process, and what I have learnt. Do not expect this to be a masterclass in web development, my goal was as much about learning as it was about making a new game.
Some Background
Pennywise (originally called “FIGHT!”) is a multiplayer coin game, designed by Cheapass Games, released under a creative commons licence, and distributed at board game conventions as a promotional device. It is a game that has interested me for a long time as it’s fun to play, and presents some interesting strategic challenges.
I created an AI & tournament framework for it called “AlphaBeta, and heuristic tuning with genetic algorithms.
I had briefly experimented with implementing it as a playable game in Java about 8 years ago, but lost interest in Java as a language and never got further than sprites that could be moved around with the mouse.
So when, after finishing Syd Sym, I decided that I wanted to try making a browser game (but not with flash) as my next hobby project, it seemed like the natural thing to pick up unfinished business and finally give the AI I’d made for Pennywise some human opponents to play against. The realisation that the art requirements would be minimal was the icing on the cake.
The First Month
Initially the progress was slow. I spent long evenings reading poorly written HTML, CSS and Javascript tutorials, and figuring out very simple things, like how to implement drag and drop. I made a deliberate choice not to use <canvas> but to work within the HTML document structure (I figured I’d learn more that way, and I think I was right). So I created a simple document structure that mirrored the games conceptual structure, a <div> for the game “table” containing a <div> for each player, and a <div> for the pool of change in the middle. I set up the HTML so they basically stacked on top of each other, and I placed a <div> for each type of coin in each of the player <divs>.
I then deleted huge chunks of it, wrote a javascript port of the game state objects from cppFIGHT and generated the HTML in the onload event handler for the page.
After which I researched how web apps normally implement drag and drop and used what I had learnt about onmousedown, onmousemove and onmouseup to implement my own drag drop system for picking up, moving coins and detecting which container div they had been dropped onto.
Each time a coin was dropped, I deduced how that related to a move in the game, applied an appropriate change to the game state, and regenerated the innerHTML from that.
At that stage I was becoming confident I’d be able to turn out a working game in a reasonable amount of time, so I roped an artist friend of mine into creating some original art for the game.
Enthused by the promise of real professional art for the game, I finally got around to creating a mercurial repository for it on my laptop.
The Second Month
With the basic framework for interacting in place and the promise of art in the not too distant future, I dived into the next major piece of the puzzle: the opponent player. As previously mentioned, I had already written some simple AIs for this game in C++, but a major unknown for me was Javascript performance. Would I be able to do a fully fledged AI, or would that result in the dreaded “A script on this page may be busy, or it may have stopped responding” message? I started by implementing a very simple non-searching opponent player strategy from the collection I had on the cppFIGHT repository, which worked, but with the game state updating the HTML immediately, tracking what was happening in the game was hard.
I spent about a week implementing a system for moving the coins (liner interpolation) to show the opponents moves, tidying up the code, testing and bug fixing before implementing any other AIs.
I then implemented 2 more rule-based AIs, the ones that eventually became the “Shipman” (easy) and “Ensign” (Normal) difficulties, along with support for AI vs AI matches and support for the human player to play 1st or 2nd, at either top or bottom position on the table.
To implement the scaling, the coin <div> lost it’s background style and gained an <img> tag, the click detection had to be modified to traverse the object graph and the new width (scale) had to be applied to the coin as it moved. Determining the correct scale to create a perspective illusion was simply a matter mapping the coins position on the trapezoid of the table:
1 2 3 4 5 6 7 8 | function GetScaleOnTableAt( height ) { // where height = 0, result = TABLE_VERTICAL_RATIO // and where height = TABLE_HEIGHT, result = 1 var v = 1.0 - TABLE_VERTICAL_RATIO; var f = height / TABLE_HEIGHT; return TABLE_VERTICAL_RATIO + f*v; } |
With the perspective effect proven, I decided that rather than rebuild the innerHTML for each game-state changing action, it would be better to manipulate the DOM to keep the HTML and game-state in sync. This gave a consistency of layout across turns that I think makes the game feel more “solid” and “physical” but bugs where the DOM didn’t match the game state were a major source of headaches for the remainder of the project.
Up to this point I’d done all of the development work using Safari as my test browser, so it was a huge surprise and a great relief to discover that I only had 2 minor issues to fix when I tested it in Firefox. One extra null check in the mouse event hander and a minor change to the CSS to anchor the img style with {top:0;left:0};.
Once the game worked in both Safari and Firefox it became clear that differences in browser rendering and Javascript performance meant a slightly more sophisticated approach to animation timing would be required to keep the movement speed of objects consistent. Changing the movement offset code (simplified here for brevity) from this:
1 2 3 4 5 6 | var l = Math.sqrt(dx*dx + dy*dy) var ox = dx / l; var oy = dy / l; element.style.left = (left-ox) + 'px'; element.style.top = (top-oy) + 'px'; |
To this:
1 2 3 4 5 6 7 8 9 10 | var t = new Date().getTime(); var elapsed = (t - oldTime) / 10; oldTime = t; var l = Math.sqrt(dx*dx + dy*dy); var ox = elapsed*dx / l; var oy = elapsed*dy / l; element.style.left = (left-ox) + 'px'; element.style.top = (top-oy) + 'px'; |
Over the next week I tested a lot and fixed minor bugs as I found them, but my activity on the project was tailing off as I waited for the art to arrive.
Around halfway through the second month Roger sent me his first style-concept sketch:
Around the same time I decided to implement support for the player to move back to the select game menu using the history.pushState / window.onpopstate API. This didn’t go well and ended up being replaced with the argument passing via the URL you see in the game now.
In the last week of the second month I split the opponent player code off into it’s own ai.js file, and implemented an AI based on the MiniMax with AlphaBeta prune that I had implemented for cppFIGHT. I first implemented the MiniMax with game state copy-and-modify (without a prune), and timed it in each of Safari, Chrome, and Firefox.
I then tested a speculative optimisation where the game state is not copied, but after each recursive step the move (a modification to the game state) that was begin evaluated was un-done. This proved to be more efficient in all 3 browsers, so I kept the change.
I then implemented the alpha beta prune, which gave an order-of-magnitude performance improvement and allowed the search to look enough moves ahead to provide an interesting challenge to most players.
The Third (And Final) Month
It was at this stage I received the finished art and discovered I was on the receiving end of Roger’s wicked sense of humour. I was the pirate. Nice. I swallowed my pride, accepted looking silly on the internet is part of life now and got on with finishing the game.
Sorting out the layout to fit the new art, CSS changes and testing on multiple browsers took the best part of a week. At this stage of the project I was desperate to finish before I went on holiday and worked every evening. The biggest chunk of work came from the different sized coins in the final art, up to this point I had assumed all the coin types would be the same size on screen and had written most of the code with that assumption.
The following week was spent on fine tuning appearance and AI difficulty. Discovering the use of CSS and tiny divs to give non-rectangular text flow (for the scroll) was a great moment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <style> .lW { float: left; clear: left; height: 16px; } </style> <script> function InjectScrollFlowElements() { var scrollDiv = document.getElementById('scroll'); var innerHTML = scrollDiv.innerHTML; var prefix = ""; var th = 280; for(h=0;h<th;h+=16) { prefix += "<div class='lW' style='width:" + Math.floor(21*Math.sin(h/th*Math.PI)) + "px;'></div>"; } scrollDiv.innerHTML = prefix + innerHTML; } </style> |
The last week before the “public release” of the game was mostly spent testing it and asking friends to try it out. The final code fix was a redesign and rewrite of how placing coins on top of each other, so that the hidden coin cannot be picked up, would be prevented by the game. The final Hg commit was a set of single pixel CSS layout adjustments.
After announcing the game on twitter I went on holiday for 10 days.
It being an HTML/CSS/JS game, the full source code is of course available if you are interested. I have left it uncompressed and unobsficated. The whole thing comes in at less than 2000 lines of (poorly written) code, including white space.
Postscript
Since returning from my holiday I’ve added a “Tweet” button and a “Like” button, I also centred the whole game game in the browser window.
Using the “HTML5″ toolset was an interesting learning experience. Javascript is a more powerful language than I was expecting, though I did miss certain C++ features, especially compile time diagnostics and strong typing!
I hope you’ve enjoyed reading this and I hope you enjoy the game. If you have any feedback on either, please feel free to comment below.