Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 23, 2022 07:45 am

Create an HTML5 Canvas Tile-Swapping Puzzle

Create a dynamic tile swapping game in JavaScript. The result will work with any image and will have adjustable difficulty levels.

The Complete HTML5 Canvas Puzzle

Here is a demo of the puzzle we will be building:

A couple of notes:

  • Cross-browser compatibility: This puzzle was tested and works in all versions of Safari, Firefox, and Chrome that support the canvas element.

  • Adjustable Difficulty: We will build a slider that allows you to change the difficulty before each game.

Getting Started

To get started, create a directory for the project. Place an image in the directory that you want to use as your puzzle. Any web-friendly image will do, and it can be any size your heart desires—just make sure it fits within the fold of your browser's window.

1. Creating the HTML Template

Open a new file using your favorite text editor and save it inside your project directory, next to your image. Next, fill out this basic HTML template.

All we need to do here is create a standard HTML5 template containing one canvas tag with the id of “canvas”, along with the difficulty slider. We will work on making the difficulty slider work later.

Now start by placing your cursor inside the script tag. From here on out, all JavaScript is within that tag. With the exception of the initial variables, I'll be organizing the sections by function, first showing you the code and then explaining the logic.

Ready? Let's get right to it!

2. Setting Up Our Variables

Let’s set up our variables and take a look at each one.

First, we have the PUZZLE_HOVER_TINT constant. The PUZZLE_HOVER_TINT constant defines what the tint color should be in the hover image pieces.

Next we have a series of variables:

  • CANVAS and STAGE will hold a reference to the canvas and to its drawing context respectively. We do this so we don’t have to write out the entire query each time we use them. And we’ll be using them a lot!

  • difficulty holds the current difficulty. Later we will set this using a slider, but for now just keep it at 4.

  • img will be a reference to the loaded image, which we will be copying pixels from throughout the application.

  • puzzleWidth, puzzleHeight, pieceWidth, and pieceHeight will be used to store the dimensions of both the entire puzzle and each individual puzzle piece. We set these once to avoid calculating them over and over again each time we need them.

  • currentPiece holds a reference to the piece currently being dragged.

  • currentDropPiece holds a reference to the piece currently in position to be dropped on. (In the demo, this piece is highlighted green.)

  • mouse is a reference that will hold the mouse's current x and y position. This gets updated when the puzzle is clicked to determine which piece is touched, and when a piece is being dragged to determine what piece it's hovering over.

Now, on to our functions.

3. Initialize the Image

The first thing we want to do in our application is to load the image for the puzzle. The image object is first instantiated and set to our img variable. Next, we listen for the load event, which will then fire our onImage() function when the image has finished loading. Last, we set the source of the image, which triggers the load.

4. The onImage() Function

Now that the image is successfully loaded, we can set the majority of the variables declared earlier. We do this here because we now have information about the image and can set our values appropriately.

The first thing we do is calculate the size of each puzzle piece. We do this by dividing the difficulty value by the width and height of the loaded image. We also trim the fat off the edges to give us some nice even numbers to work with and ensure that each piece can appropriately swap ‘slots’ with others.

Next, we use our new puzzle piece values to determine the total size of the puzzle and set these values to puzzleWidth and puzzleHeight.

Lastly, we call off a few functions: setCanvas() and initPuzzle().

5. The setCanvas() Function

Now that our puzzle values are complete, we want to set up our canvas element. First, we set our canvas variable to reference our canvas element, and stage to reference its context.

Now we set the width and height of our canvas to match the size of our trimmed image, followed by applying some simple styles to create a black border around our canvas to display the bounds of our puzzle.

6. The initPuzzle() Function

Here, we initialize the puzzle. We set this function up in such a way that we can call it again later when we want to replay the puzzle. Anything else that needed to be set prior to playing will not need to be set again.

First, we set pieces as an empty array and create the mouse object, which will hold our mouse position throughout the application. Next we set the currentPiece and currentPieceDrop to null. (On the first play these values would already be null, but we want to make sure they get reset when replaying the puzzle.)

Finally, it’s time to draw! First, we draw the entire image to display to the player what they will be creating. After that, we create some simple instructions by calling our createTitle() function.

7. The createTitle() Function

Here, we create a fairly simple message that instructs the user to click the puzzle to begin. Our message will be a semi-transparent rectangle that will serve as the background of our text. This allows the user to see the image behind it and also ensures our white text will be legible on any image.

We simply set fillStyle to black and globalAlpha to 0.4, before filling in a short black rectangle at the bottom of the image.

Since globalAlpha affects the entire canvas, we need to set it back to 1 (opaque) before drawing the text. To set up our title, we set the textAlign to "center" and the textBaseline to "middle". We can also change the font through the font property.

To draw the text, we use the fillText() method. We pass in the msg variable and place it at the horizontal center of the canvas and the vertical center of the rectangle.

8. The buildPieces() Function

Finally, it's time to build the puzzle!

We do this by building an object for each piece. These objects will not be responsible for rendering to the canvas, but rather will hold references on what to draw and where. That being said, let’s get to it.

First off, let’s declare a few variables that we’ll be reusing through the loop. We want to set up the loop to iterate through the number of puzzle pieces we need. We get this value by multiplying difficulty by itself—so in this case, we get 16.

In the Loop

Start by creating an empty piece object. Next, add the sx and sy properties to the object. In the first iteration, these values are 0 and represent the point in our image where we will begin to draw from. Now, push it to the pieces array. This object will also contain the properties xPos and yPos, which will tell us the current position in the puzzle where the piece should be drawn. We’ll be shuffling the objects before it's playable, so these values don’t need to be set quite yet.

The last thing we do in each loop is increase the local variable xPos by pieceWidth. Before continuing with the loop, we determine if we need to step down to the next row of pieces by checking whether xPos is beyond the width of the puzzle. If so, we reset xPos back to 0 and increase yPos by pieceHeight.

Now we have our puzzle pieces all stored away nicely in our pieces array. At this point, the code finally stops executing and waits for the user to interact. We set a click listener to the document to fire the shufflePuzzle() function when triggered, which will begin the game.

9. The shufflePuzzle() Function

First things first: shuffle the pieces[] array. I’m using a nice utility function here that will shuffle the indices of the array passed into it. The explanation of this function is beyond the topic of this tutorial, so we’ll move on, knowing that we have successfully shuffled our pieces. (For a basic introduction to shuffling, take a look at this tutorial.)

Let’s first clear all graphics drawn to the canvas to make way for drawing our pieces. Next, set up the array similar to how we did when first creating our piece objects.

In the Loop

First of all, use the i variable to set up our reference to the current piece object in the loop. Now we populate the xPos and yPos properties I mentioned earlier, which will be 0 in our first iteration.

Now, at long last, we draw our pieces.

The first parameter of drawImage() assigns the source of the image we want to draw from. Then use the piece objects' sx and sy properties, along with pieceWidth and pieceHeight, to populate the parameters that declare the area of the image from which to draw. The last four parameters set the area of the canvas where we want to draw. We use the xPos and yPos values that we are both building in the loop and assigning to the object.

Immediately after this, we draw a quick stroke around the piece to give it a border, which will separate it nicely from the other pieces.

Now we wait for the user to grab a piece by setting another click listener. This time, it will fire an onPuzzleClick() function.

Final HTML5 Puzzle

10. The onPuzzleClick() Function

We know that the puzzle was clicked; now we need to determine what piece was clicked on. This simple conditional will get us our mouse position on all modern desktop browsers that support canvas, using either e.layerX and e.layerY or e.offsetX and e.offsetY. Use these values to update our mouse object by assigning it an x and a y property to hold the current mouse position—in this case, the position where it was clicked.

On line 112, we then immediately set currentPiece to the returned value from our checkPieceClicked() function. We separate this code because we want to use it later when dragging the puzzle piece. I’ll explain this function in the next step.

If the value returned was null, we simply do nothing, as this implies that the user didn’t actually click on a puzzle piece. However, if we do retrieve a puzzle piece, we want to attach it to the mouse and fade it out a bit to reveal the pieces underneath. So how do we do this?

First, we clear the canvas area where the piece sat before we clicked it. We use clearRect() once again, but in this case we pass in only the area obtained from the currentPiece object. Before we redraw it, we want to save() the context of the canvas before proceeding. This will ensure that anything we draw after saving will not simply draw over anything in its way. We do this because we’ll be slightly fading the dragged piece and want to see the pieces under it. If we didn’t call save(), we’d just draw over any graphics that are in the way—faded or not.

Now, we draw the image so its center is positioned at the mouse pointer. The first five parameters of drawImage will always be the same throughout the application. When clicking, the next two parameters will be updated to center itself to the pointer of the mouse. The last two parameters, the width and height to draw, will also never change.

Lastly, we call the restore() method. This essentially means we are done using the new alpha value and want to restore all properties back to where they were. To wrap up this function, we add two more listeners: one for when we move the mouse (dragging the puzzle piece), and one for when we let go (drop the puzzle piece).

11. The checkPieceClicked()Function

Now we need to backtrack a bit. We were able to determine what piece was clicked, but how did we do it? It’s pretty simple, actually. What we need to do is loop through all of the puzzle pieces and determine if the click was within the bounds of any of our objects. If we find one, we return the matched object and end the function. If we find nothing, we return null.

12. The updatePuzzle() Function

Now back to the dragging. We call this function when the user moves the mouse. This is the biggest function of the application as it’s doing several things. Let’s begin. I'll break it down as we go.

Start by setting currentDropPiece to null. We need to reset this back to null on update because of the chance that our piece was dragged back to its home. We don’t want the previous currentDropPiece value hanging around. Next, we set the mouse object the same way we did on click.

Here, we need to clear all the graphics on the canvas. We essentially need to redraw the puzzle pieces because the object being dragged on top will affect their appearance. If we didn't do this, we’d see some very strange results following the path of our dragged puzzle piece.

Begin by setting up our usual pieces loop.

In the Loop

Create our piece reference as usual. Next, check if the piece we are currently referencing is the same as piece we are dragging. If so, continue the loop. This will keep the dragged piece's home slot empty.

Moving on, redraw the puzzle piece using its properties exactly the same way we did when we first drew them. You’ll need to draw the border as well.

Since we have a reference to each object in the loop, we can also use this opportunity to check if the dragged piece is on top of it. We do this because we want to give the user feedback on which piece it can be dropped on. Let’s dig into that code now.

First, we want to see if this loop has already produced a drop target. If so, we don’t need to bother since only one drop target can be possible for any given mouse move. If not, currentDropPiece will be null, and we can proceed to the logic. Since our mouse is in the middle of the dragged piece, all we really need to do is determine which other piece our mouse is over.

Next, we use our handy checkPieceClicked() function to determine whether the mouse is hovering over the current piece object in the loop. If so, we set the currentDropPiece variable and draw a tinted box over the puzzle piece, indicating that it is now the drop target.

Remember to save() and restore(). Otherwise you’d get the tinted box and not the image underneath.

Out of the Loop

Last but not least, we need to redraw the dragged piece. The code is the same as when we first clicked it, but the mouse has moved, so its position will be updated.

13. The pieceDropped() Function

OK, the worst is behind us. We are now successfully dragging a puzzle piece and even getting visual feedback on where it will be dropped. Now, all that is left is to drop the piece. Let’s first remove the listeners right away since nothing is being dragged.

Next, check that _currentDropPiece is not null. If it is, this means that we dragged it back to the piece's home area and not over another slot. If it’s not null, we continue with the function.

What we do now is simply swap the xPos and yPos of each piece. We make a quick temp object as a buffer to hold one of the object's values in the swapping process. At this point, the two pieces both have new xPos and yPos values, and they will snap into their new homes on the next draw. That’s what we’ll do now, simultaneously checking whether the game has been won.

14. The resetPuzzleAndCheckWin() Function

Once again, clear the canvas and set up a gameWin variable, setting it to true by default. Now proceed with our all-too-familiar pieces loop.

The code here should look familiar, so we won’t go over it. It simply draws the pieces back into their original or new slots. Within this loop, we want to see if each piece is being drawn in its winning position. This is simple: we check to see if our sx and sy properties match up with xPos and yPos. If not, we know we couldn't possibly win the puzzle and set gameWin to false. If we made it through the loop with everyone in their winning places, we set up a quick timeout to call our gameOver() method. (We set a timeout so the screen doesn’t change so drastically upon dropping the puzzle piece.)

15. The gameOver() Function

This is our last function for the main game mechanics! Here, we just remove all listeners and call initPuzzle(), which resets all the necessary values and waits for the user to play again.

16. Add a Difficulty Slider

Now, we will add the behavior to the difficulty slider. First, create an updateDifficulty() function.

Let's go through this step by step.

This part updates the difficulty so that it is set to the value in the slider.

This snippet updates the pieces to be correctly sized based on the difficulty.

Finally, this restarts the game with the new difficulty.


As you can see, you can do a lot of new creative things in HTML5 by using selected bitmap areas of loaded images and drawing. You can easily extend this application by adding scoring and perhaps even a timer to give it more gameplay.

This post has been updated with contributions from Jacob Jackson. Jacob is a web developer, technical writer, freelancer, and open-source contributor.

Original Link:

Share this article:    Share on Facebook
View Full Article

TutsPlus - Code

Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.

More About this Source Visit TutsPlus - Code