2012-02-13

Post Mortem: robotfindskitten.com

It's Been a While

I realize it has been a while since I last posted. Mostly that is because of my little one, work, and working on personal projects as opposed to writing articles for the blog. Today I will share with you my experiences with developing one of those personal projects: robotfindskitten.com.

robotfindskitten

If you're not familiar with robotfindskitten (which 99% of you probably are not), then as a brief introduction robotfindskitten is the name of a terminal-based (i.e. text) game (nee, "zen simulation"). The object is to move the "robot" character on the screen and touch other unidentified items until you find the one that represents "kitten." The game has been ported to nearly every platform known to man. For more history on the subject, consult the official website.

The Desire

I originally stumbled upon robotfindskitten when I was in college over half a decade ago. Back then I added creating a port of the game to my bucket list. I even registered the robotfindskitten.com domain name with the intention of posting a Java applet version of the game on the web. Sadly, life got in the way, and even though I started that version it was never completed.

Fast forward to sometime in the middle of last year. There were a variety of new technologies that I have been eager to learn, but I needed a project that would allow me to try them out. Here is a short list of the technologies I wanted to learn:
  • JavaScript
  • HTML5 Canvas
  • jQuery
  • Git
  • node.js
Finally it dawned on me that there was no port of robotfindskitten using HTML5 canvas (there are, however, JavaScript and jQuery ports). This was my opportunity to learn those technologies and contribute my personal port of robotfindskitten to the world.

The Goals

On top of learning those technologies, I set a few additional goals for myself for developing my JavaScript/Canvas robotfindskitten port:
  • The implementation would be completely open source and hosted on GitHub.
  • The implementation would be created using only free tools.
  • The implementation would be created using only online development environments. See my article on Cloud IDEs for more information.
  • The implementation would be deployed to a free hosting service.
  • The main code for the robotfindskitten game itself would be pure JavaScript without any dependencies.
  • The main JavaScript code would use a module approach.
  • The game would be playable with touch controls on tablet devices.
Learning the Technologies

As I mentioned in my article on language bigotry, JavaScript was a language I have looked down on in the past. With all of the buzz on HTML5 and server technologies such as node.js, I have quickly changed my stance on that and determined that JavaScript is something I REALLY need to learn. To further that goal, I read Douglas Crockford's wonderful JavaScript: The Good Parts and Stoyan Stephanof's JavaScript Patterns. I highly recommend both books for anyone looking to quickly get up to speed with JavaScript.

For HTML5 Canvas, Git, and node.js I read a variety of tutorials on the web; sadly I do not remember which ones otherwise I would link to them.

Choosing the Right IDE

In my article on Cloud IDEs I played with a lot of different options. My needs for this project was an online IDE that could interface with Git and had good support for HTML and JavaScript. In the end, I settled on using both Cloud9 IDE and eXo Cloud IDE. Most of the work was done in Cloud9 IDE, since I preferred the command line interface to Git, the GitHub integration, its text editors and testing environment, and its simple integration with Heroku. All of the code was written in one of those two editors, and never once have the files on the robotfindskitten.com website been on my local machine.

Choosing the Right Hosting Service

The hosting needs for the website are very simple. It is pretty much a single HTML page with some JavaScript dependencies. I wanted to deploy to a cloud hosting provider in case I wanted more services and I looked mostly into Heroku. Ruby and Rails would be massive overkill for a single page application, but I had been looking into node.js and it was perfectly lightweight enough to meet my needs and give me a new tool to play with. In the end I chose to use node for my application and deploy to Heroku (which recently started hosting node.js applications).

What I Learned

I learned a lot about JavaScript. The main source code file for the application went though a number of iterations. I had the game working pretty quickly, but there were no real objects and the code was a spaghetti mess. I had decided I wanted to use the module system described in JavaScript: The Good Parts and JavaScript Patterns, so I refactored out classes using the module approach until the game was very modular. I used jQuery on the HTML page itself, but I excluded it from the robotfindskitten code because I didn't want a dependency. In fact, to create a new game all you need is rfk.js and an HTML page with a canvas and a div for displaying messages.
var canvas = document.getElementById("yourcanvasid");
var messageDiv = document.getElementById("yourmessagediv");

var rfkGame = new com.robotfindskitten.Game(canvas, messageDiv);
rfkGame.startGame();

jQuery was pretty easy to pick up from a few examples, at least as much as I needed to use jQuery (basic references to existing tags on the page). I learned quite a bit about how to manage code with Git, and even how to do a merge.

What Went Right

Mostly everything. I was able to pick up JavaScript quite easily. Both web based IDEs worked quite well. GitHub was easy to use, as was deploying to Heroku. node.js is very simple to start up a quick application using the "Express" plugin. I learned to really like Git, except for at one point, when I really did not like Git (see below).

What Went Wrong

Most of my difficulties fell into three categories: not understanding Git, working with fonts on a Canvas, iOS quirks for touch controls.

Git

Don't get me wrong, Git is a great tool, but at one point I was switching between the two cloud based editors and had changes from both, forcing a merge. Manually merging source files with just a text editor is a painful process. I'm sure it would have been easier if I had a merge tool, but there were none available in the online IDEs, so the manual merging process was very difficult.  

Fonts on a Canvas

Since the "graphics" in robotfindskitten are just characters drawn to the Canvas, I had to learn a lot about how text is rendered. One of the biggest difficulties I had was that Canvas only supports font metrics in pixels, and I wanted to use "em" as my units. The conversion was tricky in order to calculate the actual width and height of a character drawn on the canvas. Canvas natively supports the width of a character (using context.measureText(character).width), but provides no means to measure the height. In the end, the following code was used to calculate the height of the area the characters would be drawn in:

function calculateHeight() {
  var heightElement = document.createElement("span");
  heightElement.style.fontFamily = "Courier, Monospace";
  heightElement.style.fontSize = "1em";
  heightElement.style.position = "absolute";
  heightElement.style.visibility = "hidden";

  heightElement.appendChild(document.createTextNode("M"));

  var body = document.getElementsByTagName("body")[0];

  body.appendChild(heightElement);

  var height = heightElement.offsetHeight;

  body.removeChild(heightElement);

  return height;
}

The capital letter "M" was used to calculate both the width and height because based on my research it is typically the widest and tallest character.

iOS Quirks

Late in development, after I already had the keyboard controls working, I started work on the touch controls. I used the Apple developer docs as well as several other sources to learn how 'touchstart', 'touchmove', and 'touchend' events work. My approach to determining which direction to move the robot involved three steps.
  1. When 'touchstart' was fired, I saved off a reference to the touch event so that I would know where the user initially touched the screen.
  2. When 'touchmove' was fired, I saved off a reference to that touch event so that I would know the last location where the user was touching the screen.
  3. When 'touchend' was fired, I would compare the coordinates from the start and move events to determine which direction the robot should move.
The actual code looked something like this:

  function handleTouchStart(event) {
    if (event.touches.length === 1)
    {
      this.firstTouch = event.touches[0];
    }
  }

  function handleTouchMove(event) {
    if (event.changedTouches.length === 1)
    {
      this.lastTouch = event.changedTouches[0];
      event.preventDefault();
    }
  }

  function handleTouchEnd(event) {

    var diffX = this.lastTouch.pageX - this.firstTouch.pageX;
    var diffY = this.lastTouch.pageY - this.firstTouch.pageY;

    // Code to move robot 
  }


I coded up my implementation for controlling the robot and tested it in my local browser using Phantom Limb to simulate the touch events. All was well, and the touch controls were working.

Later that night I tested my implementation on my iPad, and things were not as good as I had originally hoped. The robot would not move. I found out that if I moved my finger and released it, then tapped again without moving, then the robot would move. Weird. Based on that behavior I concluded that 'touchend' was not being fired after 'touchmove' was called. I tested the game on an Android tablet and confirmed it worked there, so the issue had to be related to iOS. I spent a couple of days searching on and off for 'touchend' not firing, but came up blank.

Finally, I got smart. I realized that you can turn on the developer console on Safari on the iPad. I turned that on and started adding debugging statements. Turns out, 'touchend' was firing. After adding a few more debugging statements, I realized that the robot wasn't moving because the difference in where the user initially touched and where they moved their finger to was always 0. More debugging statements. At last I realize that the reason the robot is not moving is because "firstTouch" and "lastTouch" are the exact same event. It turns out, iOS (presumably to save space in a mobile environment) does not create an new even object for each touch event, but instead reuses references to a single event. Thus, "firstTouch" and "lastTouch" both pointed to the same event in memory, only its values had changed when the 'touchmove' event was fired. The solution was to explicitly save off the coordinates of the events and not the events themselves.

The final code looked like this to work around the events being the same event:

  function handleTouchStart(event) {
    if (event.touches.length === 1)
    {
      this.firstX = event.touches[0].pageX;
      this.firstY = event.touches[0].pageY;
    }
  }

  function handleTouchMove(event) {
    if (event.changedTouches.length === 1)
    {
      this.lastX = event.changedTouches[0].pageX;
      this.lastY = event.changedTouches[0].pageY;
      event.preventDefault();
    }
  }

  function handleTouchEnd(event) {

    var diffX = this.lastX - this.firstX;
    var diffY = this.lastY - this.firstY;

    // Code to move robot 
  }

Conclusion

Crafting robotfindskitten.com was a wonderful experience. I learned a lot about JavaScript, Git, developing in the cloud, touch controls, and HTML5 canvas. I also scratched off an item from my bucket list. I hope you enjoy it and maybe have learned a bit from my experiences.

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. The training was very great and more information to get after end of course,Then the more offers are provided on the training times.I think this is very best one. Thanks for sharing in this post.


      Software testing training in Chennai

      Delete
  2. Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot. it is really explainable very well and i got more information from your blog.
    Software Testing Training

    ReplyDelete
  3. Interesting and informative article.. very useful to me.. thanks for sharing your wonderful ideas.. please keep on updating..


    Software Testing Training in chennai

    ReplyDelete