Tutorial: Puzzle Game Basics with HTML5 and Javascript

If you've played many online games, you've probably seen the puzzle simulations where an image is cut up into pieces and randomly arranged. The goal is to click pieces and move them to reform the original image. In this tutorial you will learn the basics of how to create this type of game. If you want to take a sneak peek at the finished product, you can do so here.

UPDATE
After receiving many requests asking how to detect puzzle completion, I am happy to say I've put up a follow up tutorial for doing just that. After completing this tutorial, check out the Puzzle Game Completion tutorial to learn more!

Let's begin by first laying out our html page. The page will be pretty basic. We'll have a single div element, inside of which we'll place an image and our canvas tag.

<!DOCTYPE HTML>

<html>

<head>

    <title>HTML5 Puzzle Game</title>

    <script type="text/javascript">

    </script>

</head>

<body style="background-color:#fbfbfb;" >

    <div style="margin:0 auto; width:640px; height:480px; border: 4px solid #cc6699;">
        
        <img id="sorry" src="sorry.jpg" style="display:none;"/>
        
       <canvas id="myCanvas" width="640" height="480" onclick="onCanvasClick(event);">
       </canvas>
    
    </div>

</body>

</html>

No big deal, right? We set up a basic page and added some inline styles to make it look nice...ish. Note that we set up a script block in our head tag, but we haven't added any code there yet. Don't worry, we'll get to that soon enough; but first, let's go over the page layout. As mentioned above, we have just a single div element. This element is centered and given a width of 640 pixels and a height of 480. We also add a border to make the puzzle feel more contained.

Inside the div we first define an image. This image will display if the user's browser doesn't support HTML5, but we set its display property to none by default. Finally we have our canvas. We give it an id of myCanvas so that we will be able to manipulate it in our javascript. We also set its width and height to match the containing div and also supply an onclick callback function. Note that we have not yet defined this function, but we'll get to that a bit later.

If we're going to build a picture puzzle, it might be nice to have a picture to use. I'm a pretty big cat guy, so I went with a kitty picture. You can go use whatever image you want, but I'd recommend using one that is 640x480 for this tutorial because that's what I'll be using. If you want, you can just use the same image I'm using; it's nothing too special, I just did a google image search for cat or something. Get it here. Also, you can use the same sorry image if you don't feel like making your own. Your directory should look like this:

With page setup out of the way, it's time to get coding! We'll begin by setting up a few variables and adding an onload function to run once the page is loaded. After your opening script tag, add the following code.

    window.onload = onReady;
		
    var can;
    var ctx;		
    var img;
    var clickX;
    var clickY;
    var selected1;
    var selected2;		
    var blockSize   = 160;
    var piecesArray = new Array();
		
    function onReady()
    {
        can = document.getElementById('myCanvas');
			
        if(navigator.userAgent.toLowerCase().indexOf('firefox') >= 0 || !can.getContext)
        {
            can.style.display = 'none';
            document.getElementById('sorry').style.display = 'inline';
            return;
        }

        ctx        = can.getContext('2d');			
        img        = new Image();
        img.onload = onImage1Load;
        img.src    = "cat.jpg";			
    }

The first thing we do here is add the onload handler. After that we declare several variables that we'll be using. Most of these variables will be set later on in code, but blockSize and piecesArray are both assigned values here. We set blockSize to 160; this represents the size of our puzzle pieces and 160 divides nicely into both 640 and 480 giving us a total of twelve puzzle pieces. The piecesArray is simply set to a new Array instance. It will later contain our puzzle pieces.

Next we define our onReady function. The first thing we do is grab a reference to our canvas element. The if statement is used to check for proper canvas support in the browser. I've also excluded the Firefox browser because it doesn't support some of the code we will use later on. If the user is browsing with Firefox or using a browser that doesn't support canvas, we hide our canvas display and show them the sorry image.

After checking for proper browser support, we grab a reference to a 2d rendering context from our canvas element. Lastly, we set up our puzzle image, give it an onload handler function and set the src property. Our next step will be writing the onImage1Load function so after the onReady function, add the code below.

function onImage1Load()
{
    var r;
    for(var i = 0; i < 4; i++)
    {
        for(var j = 0; j < 3; j++)
        {
            r = new Rectangle(i * blockSize, j * blockSize, i*blockSize + blockSize, j * blockSize + blockSize);
            piecesArray.push(r);
        }				
    }
			
    scrambleArray(piecesArray, 30);
    drawImage();
}

As you may have noticed, this function utilizes several other methods that we haven't created yet: Rectangle, scrambleArray and drawImage. We'll get to those in just a bit, but first let's run through what's happening in this method. The purpose of this function is create our puzzle pieces. Remember our blockSize variable that was set to 160? If you divide our image width, 640, by 160 you get four. That's how many columns our puzzle will have. If you do the same for the height, 480, you get three. In this function we are looping through all the columns and rows in our image and creating a rectangular boundary for each piece and then adding that piece to our piecesArray variable.

We haven't created our Rectangle function yet, but it will require four parameters: left, top, right and bottom. When we first enter our double for loop, both i and j will be zero so the first rectangle we create will have a left and top position of zero. For the right and bottom sides, we are passing i * blockSize + blockSize and j * blockSize + blockSize, respectively. We know that blockSize is 160 so our first rectangle's right and bottom properties will both be 160 (0 * 160 + 160). As i and j increase, the bounds of the rectangle created change accordingly.

After creating the pieces we call two more function that haven't been made yet: scrambleArray and drawImage. Before we get to those, let's go ahead and define our Rectangle function.

function Rectangle(left, top, right, bottom)
{
    this.left = left;
    this.top  = top;
    this.right = right;
    this.bottom = bottom;
			
    this.width = right - left;
    this.height = bottom - top;
}

Simple enough, right? All we do is define our left, top, right and bottom properties according to the parameters and then we compute the width and height based on those values. With that out of the way, let's look at the scrambleArray function we came across earlier and see how it is implemented.

function scrambleArray(ar, times)
{
    var count = 0;
    var temp;
    var index1;
    var index2;

    while(count < times)
    {
        index1 = Math.floor(Math.random()*piecesArray.length);
        index2 = Math.floor(Math.random()*piecesArray.length);
				
        temp = piecesArray[index1];
        piecesArray[index1] = piecesArray[index2];
        piecesArray[index2] = temp;
				
        count++;
    }
}

This function isn't too complicated either. It takes two parameters: the first is the array we want to scramble and the second is how many times we want to run the scramble loop. We begin by setting up a few variables, including a count that is initialized to zero. Next we start a while loop. In this loop we grab two random indices from our array of puzzle pieces and we swap them. In our case, the loop will perform 30 swaps because I passed in 30 when I called scrambleArray from the onImage1Load function.

Next up is our drawImage function. This method is responsible for actually drawing our pieces to the canvas rendering context.

function drawImage()
{
    var r;
    for(var i = 0; i < 4; i++)
    {
        for(var j = 0; j < 3; j++)
        {
            r = piecesArray[i*3+j];					
            ctx.drawImage(img, r.left, r.top, r.width, r.height, i*blockSize, j*blockSize, blockSize, blockSize);
        }
    }
}

Just as we did when we created our puzzle pieces, we are again looping through all the columns and rows in our image. We grab the corresponding rectangle piece from our piecesArray variable using the formula: column * numRows + row. Then we tell our rendering context, ctx, to draw. The context's drawImage function takes in several parameters. The first is the image we're drawing from. The next two are the top x and y coordinates of the source image; these are followed by the width and height of the drawing you want to do. The next four arguments refer to the same properties, but this time with reference to where you want to draw on the canvas element. If this is confusing, you can get a more in-depth look at the function at http://www.w3schools.com.

Ok, we have three more functions to cover before we're done. Two of them are smaller helper methods and one is the big one which handles most of the logic in our program. We'll begin with the helper methods first. Add the following two functions to your script.

function highlightRect(drawX, drawY)
{
    ctx.beginPath();
    ctx.moveTo(drawX, drawY);
    ctx.lineTo(drawX + blockSize, drawY);
    ctx.lineTo(drawX + blockSize, drawY + blockSize);
    ctx.lineTo(drawX, drawY + blockSize);
    ctx.lineTo(drawX, drawY);
    ctx.lineWidth = 2;

    // set line color
    ctx.strokeStyle = "#ff0000";
    ctx.stroke();
}
		
function swapRects(r1, r2)
{
    var index1;
    var index2;
    var temp = r1;
			
    index1 = piecesArray.indexOf(r1);
    index2 = piecesArray.indexOf(r2);
			
    piecesArray[index1] = r2;
    piecesArray[index2] = temp;			
}

The first function, highlightRect, will draw a highlighted border around the piece the user has currently selected. It takes in two parameters: drawX and drawY. It uses these coordinates as a starting point for drawing the highlight. All we do is use the built-in functions moveTo and lineTo to draw a square. We set the line thickness and color as well to make it more obvious.

The other function, swapRects, is also fairly simple. It takes in two rectangles (or pieces, if you will) and swaps their position in the array. This is key to our game because of the way it works. The user will first click one piece and then they will click another to swap with. The two selected pieces will trade positions. The user's goal is to move the pieces into the correct order to display the proper image.

At the start of this tutorial, when we set up our html page, we created a canvas element. We also gave that canvas element an onclick callback function. It is time to write this function and finish up the tutorial!

function onCanvasClick(evt)
{
    clickX = evt.offsetX;
    clickY = evt.offsetY;
			
    var drawX         = Math.floor(clickX / blockSize);
    var drawY         = Math.floor(clickY / blockSize);			
    var index         = drawX * 3 + drawY;			
    var targetRect    = piecesArray[index];
    var drawHighlight = true;
			
    drawX *= blockSize;
    drawY *= blockSize;
			
    ctx.clearRect(0, 0, 640, 480);
			
    if(selected1 != undefined && selected2 != undefined)
    {
        selected1 = selected2 = undefined;
    }
			
    if(selected1 == undefined)
    {
        selected1 = targetRect;
    }
    else
    {
        selected2 = targetRect;
        swapRects(selected1, selected2);
        drawHighlight = false;
    }
			
    drawImage();
			
    if(drawHighlight)	
        highlightRect(drawX, drawY);
}

There's quite a bit going on here, but it really isn't too complicated. The function takes in one parameter: the event that was generated by the click. The first thing we do is figure out the click x and y position using evt.offsetX and evt.offsetY. The offset values give us the x and y coordinates of the mouse click with respect to the current element, our canvas. At the time of this writing, this functionality isn't supported by Firefox, that is why we checked the browser in the onReady function at the beginning of this tutorial.

Next, we divide the click position by the blockSize to help us calculate the index of the clicked piece. Once we've calculated the index (column * numRows + row), we grab the clicked piece and assign it to local variable targetRect. The last variable there is just a boolean that tells us whether or not to draw a highlight around the clicked piece; it defaults to true. Once we've determined the index, we want to multiply the drawX and drawY variables by blockSize to get the actual pixel coordinates of the puzzle piece that was clicked.

In the next bit of code we clear the image in preparation for drawing any changes made. We then check our selected1 and 2 variables. We declared these at the top of our script block, but until now, we hadn't used them. These are used to store the clicked pieces. If both variables are NOT undefined, then we should clear them so that the user can make a fresh pick.

Next we check if selected1 is undefined. If it is, that means this click is the user's first selection for this round so we assign the clicked puzzle piece to selected1. If selected1 is NOT undefined, that means the user already has selected one piece to move, so we know that the piece they just clicked is the one they want to swap positions with. In this case, we set the selected piece to selected2 and then call our swapRects function that we created earlier. This method will switch the position of the two clicked puzzle pieces and because the two pieces are swapping places, we don't need to draw a highlight, so the drawHighlight variable is set to false.

After we've determined which pieces were selected and made any necessary changes, we redraw the image with a call to drawImage. The final bit of code checks to see if we should draw a highlight or not. If so, we call our highlightRect function and pass in the drawX and drawY positions.

If you followed along correctly you should now have a basic, working puzzle game. In this tutorial you learned how to use HTML5, the canvas element and javascript to create a simple puzzle. It may have seemed like a lot, but if you look at everything it is broken down nicely and comes in at less than 200 lines of code. Pretty neat!

If things aren't quite working for you, feel free to check out the completed HTML5 puzzle game and view the source for yourself. Thanks for reading and let me know what you think in the comments!

Bookmark and Share

24 Responses to “Tutorial: Puzzle Game Basics with HTML5 and Javascript”

  1. Lauren says:

    Fell out of bed fleeing down. This has brightened my day!

  2. Besa says:

    Hello,

    I am trying this code in microsoft visual studio 2012 and when I run it I get an error in this line :

    ctx.drawImage(img, r.left, r.top, r.width, r.height, i * blockSize, j * blockSize, blockSize, blockSize);

    This is the error msg:
    Unhandled exception at line 105, column 13 in ms-appx://41f018d6-a4d9-40d0-9d0a-8ff26dd69965/js/default.js

    0x800a138f – JavaScript runtime error: Unable to get property ‘left’ of undefined or null reference

  3. Rhuno says:

    The r variable is null, according to that error. Double check what you’ve got with the source code from the sample. The line directly above the ctx.drawImage call is where the variable r gets set:

    r = piecesArray[k*3+l];

    You may want to try stepping through the code and seeing what’s at that array index. It seems that it doesn’t exist in your project. I’m not sure exactly what you’re doing, but do keep in mind that this math here is accurate only for a 4×3 puzzle such as the one in the example. You’d need to update the numbers according to your own puzzle if different.

  4. besa says:

    I try to run this game on visual studio ultimate and I get error at :

    0x800a1391 – JavaScript runtime error: ‘onCanvasClick’ is undefined

    for the part of code onclick=”onCanvasClick(event) .

    What is the prb with this? Why it does not work?

  5. Rhuno says:

    I haven’t worked with Visual Studio for web development, so I’m not really sure. However, it seems as though you haven’t defined the onCanvasClick function.

  6. AJ says:

    Absolutely wonderful tutorial, with very clear explanation of each line.
    Finally I have learnt the logic behind puzzle game.
    Thank you so much :)

  7. Roger says:

    First of all this is one of the best tutorials I have used. I am new to javascript but this was certainly the best explanation of how to do it. You can see on my site I have added bits to it but I have ended up creating lots of html and javascript pages. I hope to reduce that when I understand Javascript a bit better.
    Are there any copyright issues with using any of your scripts?

    My site is http://www.rogerswebsite.co.uk/index11.html , it is up and running but I am still working on it.
    Thanks again for a great tutorial.

  8. Eric says:

    Is there a reason why this is not supported in moz? And what can we do to get it to work?

  9. Rhuno says:

    This doesn’t work in Firefox because the onCanvasClick method uses the offsetX and offsetY properties of the event object to determine where the image was clicked. Firefox does not support those properties.

  10. Eric says:

    The solution below seems to work for me to fix the Firefox situation

    clickX = event.offsetX === undefined ? event.layerX: event.offsetX;
    clickY = event.offsetY === undefined ? event.layerY: event.offsetY;

  11. Alex says:

    What we’ve done to overcome the Firefox issue was:
    1. comment out
    //if(navigator.userAgent.toLowerCase().indexOf(‘firefox’) >= 0 || !isCanvasSupported())
    //{
    // can.style.display = ‘none’;
    // document.getElementById(‘sorry’).style.display = ‘inline’;
    // document.getElementById(‘support’).innerHTML = “Your browser is not supported. Please use one of the browsers above.”;
    //}

    2. Edit the function onCanvasClick(evt):

    if (!evt.hasOwnProperty(‘offsetX’)) {
    evt.offsetX = evt.layerX – evt.currentTarget.offsetLeft;
    evt.offsetY = evt.layerY – evt.currentTarget.offsetTop;
    }

    clickX = evt.offsetX;
    clickY = evt.offsetY;

    This works for us.

  12. David says:

    Great thoughtful tutorial! However, how would you show the user that they’ve correctly finished the puzzle?

  13. Rhuno says:

    One way would be to give each slot on the board an id and assign a correct id to each puzzle piece. When a piece is placed, just check if its in the right slot. Once all of them are, the puzzle is complete.

  14. Ambreen Altaf Hussain says:

    How a draggable event can be applied in this code?

  15. Kai says:

    Could you please help me how to show the user that s/he has correctly finished the puzzle?

  16. marco says:

    In the function function swapRects(r1, r2),I have tried to push to another array the values index1 and index 2 and then i have created a function for retive the values,but it not always work correctly.Please
    help us to show the solution

  17. Rhuno says:

    As requested, here is a follow up tutorial that shows how to detect whether or not the user has completed the puzzle: http://rhuno.com/flashblog/2014/03/26/tutorial-update-puzzle-game-completion-with-html5-and-javascript/

  18. marco says:

    Thank you very much, you are a true genius

  19. Cameron says:

    How would you go about animating the puzzle with Greensock?

  20. Laila says:

    Hey this code works perfectly for me but i wanted to have 15 pieces instead of 12 so i played around with the pixels, blocksize, and the 4 and 3 in the loops. Every time theres a problem with blank squares in the middle or the whole picture shrinks and theres a row of empty squares. How can i get it to be 3×5 with 15 pieces?

  21. Rhuno says:

    I haven’t used the js version of greensock, so I don’t know how it works.

    As for getting a 15 piece puzzle, you will have to adjust the size of the blocks and they will not be perfect squares. To get a puzzle that is 5 columns x 3 rows, you need to make the pieces 128 pixels wide and 160 pixels tall (in this case since the image is 640×480). You will need to ditch blocksize and make variables for block width and block height and set them accordingly. You will then need to replace blocksize throughout the code with the appropriate value.

  22. Peter says:

    Thank you for the wonderful tutorial

  23. sam says:

    wow !!!

    this is really helpful, amazing ….
    but some condition I don’t understand & trying to get understood, hope I will be able to make it my won way ….
    I made one very simple, but can’t upload randomly images :(

    Thank you very much

  24. James says:

    Thank you for great tutorial!
    However, if I need a blank block in the right bottom and all blocks only change position with the blank block, how can I do this with your code?
    Plz share your code for learning if you can do it. Thank you.

Leave a Reply

Subscribe to RSS feed FGS5 Badge