alt
October 28th, 2012

Written by Stoyan Rachev

I recently created Hanoier, a Tower of Hanoi implementation in JavaScript using jQuery. This is my next entry for the “Geeky Quickies” contest at SAP, after Feeder and Todor.

The task was somewhat simpler than the previous ones, and so there were not too many best practices to explore and showcase. So I focused on making it as simple as possible by leveraging the power of the used technologies, while maintaining the code as clean as possible by using object-oriented design principles and patterns.

You can play Hanoier live as it is published on GitHub Pages. As usual, you can also find the source code on GitHub. Feedback, comments, and contributions are welcome!

Main View

Game Features

  • Drag and drop disks to move them between the towers. If the move is not legitimate, the disk returns to the original tower in an animated way.
  • When all disks have been moved to a different tower, the game displays a victory message and restarts.
  • Choose the number of disks to play with, from 3 to 7.
  • Track the number of moves performed.
  • Start the game over by clicking a button.
  • Play on all modern browsers and mobile devices.

Programming Highlights

  • Uses the following jQuery features: selectors, events, HTML DOM manipulation for getting, adding, and removing elements, CSS manipulation for positioning elements, as well as jQuery UI draggable and droppable interactions for managing drag and drop.
  • Uses new HTML5 features such as canvas for drawing graphics.
  • A simple JavaScript object prototyping pattern is used to achieve object orientation while ensuring that “this is always this”.
  • The code is both clean and minimal. The entire game is written in less than 300 lines of JavaScript code (according to GitHub sloc) and very few HTML and CSS, courtesy of using jQuery rather than plain JavaScript.

Code Structure and Design

As mentioned above, Hanoier uses a simple pattern based on JavaScript prototypes to achieve object orientation. This technique is usually called methods added externally to the prototype to distinguish it from the more “advanced” prototype pattern or the revealing prototype pattern. The chosen technique is somewhat limited in terms of encapsulation and inheritance compared to its more advanced counterparts, however it has one major advantage: this always refers to the current object (except in event handlers). For more detailed comparison of the different JavaScript patterns, see the
Some JavaScript Object Prototyping Patterns article, the Techniques, Strategies and Patterns for Structuring JavaScript Code blog series, or the Learning JavaScript Design Patterns book.

In Hanoier, there are three classes:

  • The Tower class represents a tower. A tower has a number, dimensions, and an array of disks representing all disks that are currently on the tower. It provides methods for creating the HTML elements related to the tower, initialization, drawing, checking if a disk can be moved and actually moving a disk to the tower, manipulating the droppable property, and calculating disk coordinates for positioning.
  • The Disk class represents a disk. A disk has a number, dimensions, font, and a reference to the tower on which the disk is currently on. It provides methods for creating the HTML elements related to the disk, initialization, drawing, manipulating the draggable property, and positioning.
  • The Game class represents a game. A game has two arrays containing all towers and disks. It provides methods for initialization, as well as the actual event handlers for the drag and drop events that drive the game.

Each tower and disk is represented in HTML by a canvas element and an associated image which is drawn on the canvas. These elements are not part of the static index.html, instead they are created dynamically upon game initialization. All such elements have unique ids based on their numbers, which allows efficient selection by id, performed by the getElement() methods which expose the associated jQuery objects on which various jQuery methods are called.

There is very little code outside these three classes. Most notably this is the jQuery document ready event, which simply creates a new game and initializes it, and then sets-up a handler for the “Start Over” button which does the same:

$(function() {
	new Game().init();
	$("#startOver").click(function() {
		new Game().init();;
	});
});

Graphics

As already mentioned, the visualization of towers and disks is achieved by drawing images upon canvases. I am not good at creating graphics myself, so I decided to use simple grayscale images for towers and disks available freely on the Web (from Coolmath-Games.com). For the background, I use a Chinese landscape converted to grayscale. Since all HTML elements that refer to the actual images are created dynamically, one could easily extend the game with different “skins” consisting of images with identical names placed in different directories.

Drawing an image upon a canvas is achieved simply by calling the HTML5 canvas drawImage() function. However, this function expects a pre-loaded image, so when I tried to call it inside the draw() method, which is invoked by the document ready event, nothing was drawn, because the images hadn’t been loaded yet. Eventually I resorted to setting up an event handler for the image load event and doing the actual drawing inside the handler:

Disk.prototype.draw = function() {
	this.getImageElement().load($.proxy(this.loadImage, this));
}

Disk.prototype.loadImage = function(event) {
	var elem = this.getElement();
	var ctx = elem.get(0).getContext("2d");
	var img = $(event.target);
	ctx.drawImage(img.get(0), 0, 0, this.width, this.height);
	...
}

Note the use of $.proxy() in the above code snippet – this seems to be the most portable way available to ensure that inside the event handler, this refers to the current object. Without it, the above code would fail. The standard JavaScript bind() function could be used as well, but it is not supported in IE 9.

Drag and Drop

When thinking about how to implement drag and drop, I wondered whether to use HTML5 drag and drop, or the same functionality available in jQuery UI. I found working “Tower of Hanoi” implementations based on both approaches (see hanoi-towers and towers-of-hanoi-in-HTML5), as well as some stackoverflow questions on this topic. Eventually, I decided to stick with jQuery UI for the following reasons:

  • The contest rules explicitly called for using “jQuery features”.
  • Currently, jQuery UI still has better cross-browser compatibility.
  • The jQuery UI API is very simple and convenient to use.

In the future, using the standards-based HTML5 approach would probably become a better choice than the outdated jQuery UI implementation, unless it is also ported to HTML5

Positioning Disks

Initially, I used the jQuery UI position utility to position the disks relative to their towers. This basically worked, but it had various issues with window resizing and zooming. Eventually I found using CSS positioning to be both simpler and more robust. The positioning code now looks like this:

Disk.prototype.position = function() {
	var elem = this.getElement();
	var top = this.tower.calcDiskTop(this.num, this.height);
	var left = this.tower.calcDiskLeft(this.width);
	elem.css({ position:"absolute", top:top, left:left });
}

In order for the above code to work, the disk parent element, in this case a div called game, must have the position: relative; style.

Browser Compatibility

I tested that Hanoier works smoothly on the following browsers:

  • Chrome 22
  • Firefox 13
  • IE 9
  • Safari 5.1.7
  • Opera 12
  • Chrome for Android 18
  • Android Browser 4.1
  • Blackberry Browser 6.0
  • Opera Mobile 12.1

Support for such a wide array of browsers without a single line of browser-specific code is possible since jQuery and jQuery UI ensure cross-browser portability for browser versions much lower than the listed above. The only advanced HTML5 feature that requires a newer browser is the canvas. Touch screen devices are supported via the jQuery UI Touch Punch plugin.

Leave a Reply

CAPTCHA Image
*


8 + = fourteen