So how do you go about making a puzzle? You can see the end result at HTML5 Canvas Puzzle and the code is online at https://github.com/Setfive/setfive.github.com/tree/master/canvas_puzzle.
As it turns out generating an arbitrary puzzle programatically is reasonably complicated. The best explanation I could find on how to accomplish this is at https://www.allegro.cc/forums/thread/586750/603411#target. Conceptually, the process looks straightforward enough and you could probably manually do it on a whiteboard. Unfortunately, the issue I ran into with this approach is that drawing bezier curves and splines programmatically on a Canvas is a bit involved. I also don’t have a background in vector graphics so I was getting stuck in the weeds drawing lines.
Discounting generating the puzzle entirely on the fly, an alternative approach would be to use a fixed set of available pieces and then “fill in” a grid depending on how large the image area is. Conceptually, the idea is to construct a closed grid of pieces where some number of the pieces can be repeated and then repeat those pieces as needed to cover the target image. The templated pieces I used are in /puzzle_pieces/.
Technically, I decided to use fabric.js to facilitate Canvas interaction along with lodash.js and of course the ubituqous jQuery.
Walking through the code, the steps to build a puzzle are fairly straightforward:
- Load images: The first step is to load all the template images and target image so that they’re available to use on a Canvas. Since jQuery is available, one approach is to create a deferred for each image, resolve it as the image loads, and use $.when to wait for all of the images to load. See here for example.
- Build pieces grid: Next you’ll need to figure out how many repeated pieces you need to fill into the grid. One issue here is that since the puzzles need to fit snuggly the image dimensions of a given piece won’t be what you need to use to calculate the grid. Because of this, I ended up with a bit of goofy code for this.
- Create image masks: Once you have the number of pieces to create you’ll need to cut masks for each piece out of the source image and create fabric.js objects for them. See copyImageChunk.
- Place masks: Placing the “pieces” is also complicated because of the dimension issue above. See kludgy code.
- Shuffle and track movements: Finally, you just need to shuffle the positions of the images and then track their movement to report a “correct” position.
And that’s about it. One other “trick” is that you can use Window.requestAnimationFrame to avoid locking the UI when you’re creating the masked images since it’s a compute intensive task.
Anyway, as always questions and comments welcome.