Fun With jQuery: Create A Puzzle
jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers.
Learning a new technology outside of the context of paid work can be challenging after writing code for business applications all day, so I like to frame the acclimation of new knowledge inside of something a little more light-hearted and fun.
Remember as a kid, the cheap, plastic sliding puzzles? They had a grid of sliding squares (usually imprinted with numbers on each square). One square was missing, so that all the others could be moved around, one square at a time. The goal was to put the squares back in numerical order.
This type of puzzle can be emulated in a web browser with a simple use of jQuery and jQueryUI.
Basic Set Up
Let’s get the basic set up out of the way. You will need to download the latest versions of jQuery and jQuery UI at:
https://jquery.com/download/
http://jqueryui.com/download/
Then, set up a folder structure like this:
Project folder
css
sliding-puzzle.css
img
keyhole-300×240.png
js
sliding-puzzle.js
jquery-2.1.4.min.js
jquery-ui.min.js
sliding-puzzle.html
Of course, the version number on the jquery-2.1.4.min.js file may be different in your case, as well as the keyhole-300×240.png, should you decide to go with a different image.
Setting up the html for our page is pretty straightforward, as most of the work is done in the associated .js and .css files.
<html lang="en"> <head> <meta charset="utf-8" /> <title>Keyhole jQuery Sliding Puzzle</title> <link rel="stylesheet" href="css/common.css" /> <link rel="stylesheet" href="css/sliding-puzzle.css" /> </head> <body> <div id="puzzle" class="clearfix"> <figure> <img src="img/keyhole-300x240.png" /> </figure> <div id="ui"> <button id="start">Start!</button> </div> </div> <script src="js/jquery-2.1.4.min.js"></script> <script src="js/jquery-ui.min.js"></script> <script src="js/sliding-puzzle.js"></script> </body> </html>
Some important items of note for this file:
- The inclusion of the required jquery .js files, as well as the project-specific sliding-puzzle.css and sliding-puzzle.js files, where our game logic will be implemented.
- The inclusion of the image for the puzzle.
- The inclusion of a “Start!” UI element, which will scramble the image.
- Note also the id values of “puzzle” and “ui” on the div elements, are these are used to apply the appropriate css styling defined in sliding-puzzle.css.
Now let’s look at the .css file. Again, not much of import here, logic-wise. Just basic boilerplate stuff, which you can adjust to your own liking.
#puzzle { padding:5px; margin:auto; border:1px solid #aaa; border-radius:5px; background-color:#eee; } #puzzle figure { width:300px; height:240px; border:1px solid #aaa; position:relative; float:left; background-color:#fff; } #ui { width:200px; padding:10px 0 0 10px; float:left; } #ui button { margin-bottom:2em; } #ui p { font-size:1.7em; } #start { width:204px; height:50px; font-size:1.75em; }
The Puzzle Content
Now, the real meat and potatoes; the content of sliding-puzzle.js. Start with the following code, which defines a function that gets called when the page loads.
$(function () { //code goes here });
This defines a jQuery method that gets called when the page loads. The “$” is a short-cut to calling jQuery code; using “jQuery” instead of “$” would yield the same results. All of the JavaScript/jQuery code for this project will be implemented within this function. Next, we need to define some variables that will be used throughout the code.
var aspect = "3:4", aspect_width = parseInt(aspect.split(":")[0]), aspect_height = parseInt(aspect.split(":")[1]), container = $("#puzzle"), imgContainer = container.find("figure"), img = imgContainer.find("img"), path = img.attr("src"), piece = $("<div/>"), piece_width = Math.floor(img.width() / aspect_width), piece_height = Math.floor(img.height() / aspect_height), idCounter = 0, positions = [], empty = { top: 0, left: 0, bottom: piece_height, right: piece_width };
The “aspect” variable defines the number of rows and columns that the image will be split into, and “aspect_width” and “aspect_height” assigns each of these values to their own variable for ease of use. It is important to note, that should you decide to use an image of a different size than I am using, you will need to ensure that its dimensions are such that they can be evenly divided by the defined aspect.
A useful feature of jQuery is being able to easily find elements in the source HTML and assign them to JavaScript variables. This makes it much easier to manipulate these elements through other jQuery functions, or even through JavaScript. Note in the HTML we defined a “div” element and assigned it an id attribute of “puzzle.” We can use jQuery to find this element and assign it to a variable with the following line.
container = $("#puzzle")
The “$”, as stated above, designates that we are calling a jQuery function. The parameter passed to the function, “#puzzle” indicates that jQuery needs to find the first element that has an id attribute with that value and assign it to the “container” variable. Now that we have a variable that references our puzzle element, we can use that reference to define the next two variables, “imgContainer” and “img”. We call the jQuery method “find” on the container object, passing in “figure” to return the HTML “figure” element that is contained by the puzzle “div” element.
Then, we do the same to find the “img” element by calling the “find” method on the imgContainer element. The next line calls another useful jQuery function, the “attr” method, which returns the value of the attribute on the calling element that has the name “src”. This returns the path to the image. The next line calls yet another jQuery function that creates a new “div” element and assigns it to the variable.
Note that calling this method does net yet assign the new element to the existing HTML. We will do that later, once the attributes of the element are more robustly defined.
piece = $("<div/>")
The variables “piece_width” and “piece_height” define the width and height for each block that the original image will be split into, by dividing the total width and height of the image by the aspect values.
Next, we want to split the image into the individual squares designated by the aspect value we defined earlier. The loop logic is fairly straightforward. We are iterating over the aspect values we defined earlier and calculating the top and left position values for each piece. Then, we get to use another fun jQuery method.
For each set of these values we call “clone” on the empty “div” element we created earlier. We also get to see the practice of chaining method calls in jQuery.
Since each method call returns an object (the cloned element), we don’t necessarily need to assign that return value to a variable before calling another method on it.
In our example, we call clone(), then immediately call attr(), followed by css(), and finally appendTo(). We assign the incrementing counter as the id for each piece by calling the attr() function, then assign the appropriate values to the css style for the piece. Lastly, we call appendTo(), passing in the imgContainer variable, which adds the cloned “div” element as a child of the “figure” element. We also then store the top and left position values of the piece into the “positions” array for later use.
or (var x = 0, y = aspect_height; x < y; x++) { for (var a = 0, b = aspect_width; a < b; a++) { var top = piece_height * x, left = piece_width * a; piece.clone().attr("id", idCounter++).css({ width: piece_width, height: piece_height, position: "absolute", top: top, left: left, backgroundImage: ["url(", path, ")"].join(""), backgroundPosition: ["-", piece_width * a, "px ", "-", piece_height * x, "px"].join("") }).appendTo(imgContainer); positions.push({ top: top, left: left }); } }
Clean Up
Now that we have our image split up correctly, a little clean up is necessary. The original image is still being displayed, so we need to remove it.
img.remove();
In order to allow room for the pieces to be moved around, we need to remove one of the squares. Any piece would probably do, but upper left is probably easiest. Recall that when we split the image up, we assigned the incrementing counter as the id attribute for each of the pieces, so we ask the container to find the piece with the id “0” and call remove on it.
container.find("#0").remove();
Do not forget that we also added its top and left values to the positions array, so that needs to be removed as well. We can do this by calling the JavaScript shift method on the array.
positions.shift();
The Shuffle
Now, we need to shuffle the pieces whenever the user clicks on the start button. We again call the jQuery method to find the element with the id value of “start”, which returns our button. We now see another handy jQuery function, on(). This assigns a method to the “on” event handler for the passed in event. In this case, we are defining a function to be called when the “onClick” event handler is called for the start button. The “shuffle” method we define performs a Fisher-Yates shuffle, where, for each item in the array, we generate a random number between zero and the total size of the array.
Then we switch the current item in the iteration with the one located at the position defined by the random number.
Even though the elements in the array have been shuffled, they each still have the same top and left position values as before the shuffle, so they would still appear visually unshuffled.
The next bit of code shows how easily jQuery operates on a collection of elements.
By calling $.each(…), we are telling jQuery to perform the same action on each element in the defined array. So, in this case, for each “piece”, we call the code defined in the anonymous function, which uses the positions array we defined earlier to reassign the top and left positions to each element so that now the positions of each piece are “shuffled” as well. We then append this new array to the imgContainer so that the HTML updates and the pieces become visually shuffled. Lastly, we ensure that the empty piece is in the upper left.
$("#start").on("click", function (e) { var pieces = imgContainer.children(); function shuffle(array) { var i = array.length; if (i === 0) { return false; } while (--i) { var j = Math.floor(Math.random() * (i + 1)), tempi = array[i], tempj = array[j]; array[i] = tempj; array[j] = tempi; } } shuffle(pieces); $.each(pieces, function (i) { pieces.eq(i).css(positions[i]); }); pieces.appendTo(imgContainer); empty.top = 0; empty.left = 0; });
Until Next Time..
I had hoped to complete this tutorial in the space of a single blog, but this appears to be running long. In my next blog entry I will implement starting the timer, making each piece draggable, and defining the win conditions.
Reference: | Fun With jQuery: Create A Puzzle from our WCG partner Keyhole Software at the Keyhole Software blog. |