Weekend Project: Learn the JavaScript Canvas by building a jigsaw puzzle game.

First things first, here's a link to the puzzle. Give it a try - then come back to read about the main successes and failures in the process.


If you haven't already checked it out, this is the main puzzle interface.

Why a puzzle?

After seeing many inspiring JavaScript demos hosted on Github pages and personal websites (like the list assembled here by David Walsh), I wanted to make something even remotely comparable. I was particularly interested in cubic and quadratic bezier curves, and learning to draw more complex shapes than simple rectangles or circles. A jigsaw puzzle was the perfect opportunity to build something fun, simple, and intuitive that would help me play with those tools.


The puzzle itself is fairly straightforward. The first major thing to grasp with the Canvas element, compared to a real game engine like Unity, is that nothing is done for you except the rendering. The core element of an engine - the frame loop - is implemented by simply making your Draw() function call itself again at the end of each execution. After overcoming that very minor hurdle, it was surprisingly easy to implement rectangular pieces that can be snapped to a grid. Using an object-based model, the puzzle itself contains multiple piece-objects, each with its current coordinates and the correct location in the puzzle. Drawing the teeth was an interesting challenge. I used one of the many interactive JavaScript bezier curve editors to sketch out a tooth shape that I was happy with. When implementing the routine into the DrawTooth() method of the Piece class, I made a couple of small changes that allow for variable tooth shapes, including height, width, and the size of the indent at the base. The piece itself has a set of variables that control these, so it's also possible to make a puzzle with varying or even unique tooth shapes on each piece. Each piece gets its unique shape from an array of four numbers - each containing a -1, 0, or 1. Negative one is an indented tooth, zero is a flat side, and positive one is a tooth sticking out. Pieces are generated from the top left to bottom right, to ensure that the next piece can get consistent information about which sides are already decided, and which should be chosen randomly.

Room for Improvement

If you've played the game, you already know there's some areas that are still rough around the edges.

- Piece Intersection


Two neighbouring pieces. The bodies (rectangles) are drawn first, then the teeth - resulting in the overlap seen here.

The main loop draws the body of each piece first (a rectangle), followed by the teeth. The reason is pretty simple: there's no easy way to draw the body with cutouts where a neighbouring piece's teeth should go. It's not possible to "draw" nothing to the canvas, without getting to the level of a) making the body itself a complex bezier shape, or b) sampling the underlying pixels at that location and re-drawing them to the rectangle to fake transparency. There may be other ways, but the easiest workaround was to force the teeth sticking out to be drawn last, so they'd always be on top properly. However, the picture above shows what can happen when two pieces are placed beside each other with teeth overlapping.

- Unrecognized Win Cases


An example of an unrecognized win case. The puzzle on the left is technically solved, but the game expects the two left edge pieces to be swapped because that's the order they were generated in.

Essentially, the Puzzle object loops through each of its Pieces every frame to check its grid coordinates. If all of them are in the same position where they were generated, it triggers the win flag and a message is displayed to the user. But there's an obvious catch with this method: if the player finds an alternative solution to the puzzle, which is impossible on Easy difficulty but near-guaranteed on the harder settings, the puzzle will never realize it's solved.

The solution is pretty straightforward. As pieces are snapped into place and removed, the puzzle keeps track of which pieces are where. The individual pieces can access their neighbours and check if they correspond -  each piece interaction (apart from edges and corners, which are handled separately) consists of a positive and negative tooth, summing to zero. Any piece out of place raises a "not win" flag which exits the checking loop until the next frame.