jQuery HOTSHOT

By Dan Wellman
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Sliding Puzzle

About this book

jQuery is used by millions of people to write JavaScript more easily and more quickly. It has become the standard tool for web developers and designers to add dynamic, interactive elements to their sites, smoothing out browser inconsistencies and reducing costly development time.

jQuery Hotshot walks you step by step through 10 projects designed to familiarise you with the jQuery library and related technologies. Each project focuses on a particular subject or section of the API, but also looks at something related, like jQuery's official templates, or an HTML5 feature like localStorage. Build your knowledge of jQuery and related technologies.

Learn a large swathe of the API, up to and including jQuery 1.9, by completing the ten individual projects covered in the book.

Some of the projects that we'll work through over the course of this book include a drag-and-drop puzzle game, a browser extension, a multi-file drag-and-drop uploader, an infinite scroller, a sortable table, and a heat map.

Learn which jQuery methods and techniques to use in which situations with jQuery Hotshots.

Publication date:
March 2013
Publisher
Packt
Pages
296
ISBN
9781849519106

 

Chapter 1. Sliding Puzzle

In our first project we'll get to see a variety of techniques in action, in a fun and relaxed setting. Consider this a gentle warm up for the rest of the book.

We'll see how to make elements draggable using jQuery UI and how we can configure the behavior of the draggable elements. We'll also look at other subjects including sorting algorithms, and client-side storage using the localStorage API.

 

Mission Briefing


In this project we'll be building a simple but fun puzzle game in which a picture is scrambled and has to be unscrambled back to the original picture by sliding the different pieces around the board – a modern web-based take on a classic game from yesteryear.

Typically there is one blank space on the board and pieces can only be moved into this blank space so we will need to build a system that keeps track of where the blank space is, and only allows pieces directly adjacent to it to be dragged.

To give the player an incentive, we can also look at keeping track of how long it takes the player to solve the puzzle so that the player's best time can be recorded. The following is a screenshot that shows the final result of this project:

Why Is It Awesome?

Games are fun and they can keep people coming back to your site, especially a younger audience. Non-flash browser-based games are taking off in a big way, but getting into the action at the top end of the scale can have a steep learning curve.

A simple drag-based game like this is the perfect way to ease yourself into the gaming market without jumping straight in at the deep end, allowing you to hone your skills with some of the simpler concepts of game development.

This is also a great way to learn how to build a draggable interface in a precise and engaging format that is well suited to its intended purpose and intuitive to use. We can also look at some more advanced draggable concepts such as collision avoidance and precise positioning. We will also be learning how to interact with the localStorage API in order to store and retrieve data between sessions.

Your Hotshot Objectives

This project will be broken down into the following tasks, which we'll work through sequentially in order to produce a working end result:

  • Laying down the underlying HTML

  • Creating a code wrapper and defining variables

  • Splitting an image into pieces

  • Shuffling the puzzle pieces

  • Making the puzzle pieces draggable

  • Starting and stopping the timer

  • Determining if the puzzle has been solved

  • Remembering best times and adding some final styling

Mission Checklist

As well as jQuery, we'll also be using jQuery UI in this project, so now is the time to grab these libraries and put them in place. We can also take a moment to set up our project folder, which is where we can store all of the files that we'll create over the course of the book.

Create a new folder somewhere called jquery-hotshots. Within this folder create three new folders called js, css, and img. All the HTML pages we create will go into the root jquery-hotshots folder, while the other files we use will be distributed amongst the subfolders according to their type.

For the projects covered throughout the book we'll use a local copy of the latest version of jQuery, which at the time of writing is the brand new 1.9.0. Download a copy of the minified version from http://code.jquery.com/jquery-1.9.0.min.js and save it in the js folder.

Tip

It's considered best practice to use Google's content delivery network (CDN) to load jQuery and to link to the file without specifying a protocol. Using a CDN means the file is more likely to be in the visitor's browser cache, making the library much quicker to load.

It is also advisable to provide a fallback in the event that the CDN is not accessible for some reason. We can very easily use the excellent yepnope to load a local version of the script if the CDN version is not found. See the yepnope site, http://yepnopejs.com/, for more information on this and other resource-loading tips and tricks.

To download the jQuery UI components we'll require, visit the download builder at http://jqueryui.com/. We'll be using various other components in later projects, so for simplicity we can just download the complete library using the Stable button. The current version at the time of writing is 1.10.0.

Once the build has been downloaded, you'll need to grab a copy of the jquery-ui-x.x.x.custom.min.js file (where x.x.x is the version number) from the js directory inside the archive, and paste it into your js folder.

Tip

Recent versions of jQuery UI, as well as some of the more popular pre-built themes generated with Themeroller, are also available via Google's CDN.

 

Laying down the underlying HTML


First of all we need to build out the page that'll contain our sliding puzzle. The initial page will be a shell with mostly just a few containers; the draggable elements that will make up the individual pieces of the puzzle can all be created dynamically when required.

Prepare for Lift Off

We'll use a standard starting point for all of the different projects throughout this book, so we'll look at this briefly now to save showing it in every project:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <link rel="stylesheet" href="css/common.css" />
    </head>
    <body>
        <script src="js/jquery-1.9.0.min.js"></script>
    </body>
</html>

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Each project we cover will be contained in a page that starts out exactly like this. Save a copy of the previous file now in your local project folder and call it template.html. At the start of each project I'll say something like "save a copy of the template file as project-name.html". This is the file I'll be referring to.

So in this case, save a copy of the previous HTML (or template.html if you wish) in the main project directory (jquery-hotshots) and call it sliding-puzzle.html.

We'll also be using a common style sheet for basic styling that each project will utilize. It contains things such as an HTML5 reset, clearfix, and other utilities, as well as some basic typographical fixtures and theming for consistency between projects. While I won't go into detail on that here, you can take a look at the common.css source file in the accompanying download of this book for more information.

Each project will also need its own style sheet. These will be covered where applicable and will be discussed on a per-project basis as and when required. We can create the custom style sheet we'll be using in this project now.

Create a new file and call it sliding-puzzle.css, then save it in the css folder. We can link to this file in the <head> of the page using the following code:

<link rel="stylesheet" href="css/sliding-puzzle.css" />

This should appear directly after the common.css style sheet reference.

We can also link to the script files that we'll be using in this project. First, the jQuery UI file we downloaded and copied into the js folder can be linked to using the following code:

<script src="js/jquery-ui-1.10.0.custom.min.js"></script>

Remember to always add the script for jQuery UI on the next line after the script for jQuery itself.

Lastly we can add the script file we'll use for this project. Create a new file and save it as sliding-puzzle.js in the js folder. We can link to it by adding the following <script> element directly after the jQuery UI reference:

<script src="js/sliding-puzzle.js"></script>

Engage Thrusters

Save a copy of the template file as sliding-puzzle.html in the root project folder and then add the following mark-up to the <body> element (before the jQuery <script> element):

<div id="puzzle" class="clearfix">
    <figure>
        <img src="img/Flower.png" />
    </figure>
    <div id="ui">
        <p id="time">Current time: <span>00:00:00</span></p>
        <button id="start">Start!</button>
    </div>
</div>

Objective Complete - Mini Debriefing

This simple HTML is all that's required to start with. As this is a book about JavaScript, I won't cover the HTML in much detail unless absolutely critical to the project at hand. In this case most of the elements themselves aren't significant.

The main thing is that we have a series of containers with id attributes that make selecting them fast and easy. The only really important element is the <img>, which displays the original image that we'll be turning into the puzzle.

Note

The awesome image used in this example was created by the extremely talented Mr. Eamon O'Donoghue. More examples of his fine work can be seen at http://eamonart.com/. The image used in this project can be found at http://eamonart.com/IMAGES/PINUPSLINKS/Space%20Girl%20Vera.html.

 

Creating a code wrapper and defining variables


All of our code will need to be contained within a wrapper function that is executed once the page has finished loading.

Prepare for Lift Off

The steps that we'll complete in this part of the project are as follows:

  • Add a wrapper function for our code that will execute as soon as the page has finished loading

  • Define the variables that we'll use throughout the script

Engage Thrusters

The first step is to create a wrapper function for our code that will be executed as soon as the page has loaded. Add the following code to a new script file called sliding-puzzle.js, which should be saved in the js directory we created earlier:

$(function () {

    //all our code will be in here...

});

Most jQuery code that we see in the wild resides within some kind of wrapper like this. Using $(function(){}); is a shortcut to jQuery's document.ready function, which is fired once the DOM for the page has loaded.

Tip

Using $

We wouldn't normally use $ in the global scope like this if we were sharing our code with other developers, as there may be other libraries on the page also using it. Best practice is to alias the $ character within an automatically invoked anonymous function, or an immediately invoked function expression if you prefer. This can be done using the syntax (function($) { … }(jQuery));.

Next we can set some variables near the top of the script file. This is so that we don't have lots of values that we may want to change later distributed throughout the file. Organization is one of the keys to writing maintainable code, and we should always strive to make our code, as well as our intentions, as clear as possible.

Next add the following code inside the function we just defined, replacing the comment shown in the previous code sample:

var numberOfPieces = 12,
    aspect = "3:4",
    aspectW = parseInt(aspect.split(":")[0]),
    aspectH = parseInt(aspect.split(":")[1]),
    container = $("#puzzle"),
    imgContainer = container.find("figure"),
    img = imgContainer.find("img"),
    path = img.attr("src"),
    piece = $("<div/>"),
    pieceW = Math.floor(img.width() / aspectW),
    pieceH = Math.floor(img.height() / aspectH),
    idCounter = 0,
    positions = [],
    empty = {
        top: 0, 
        left: 0,
        bottom: pieceH, 
        right: pieceW
    },
    previous = {},
    timer,
    currentTime = {},
    timerDisplay = container.find("#time").find("span");

These aren't all of the variables that we'll use, just the majority of them. The list also includes any variables that we'll need to use inside callback functions so that we don't run into scope issues.

Objective Complete - Mini Debriefing

The variables we defined first are a combination of simple (primitive) values and objects or arrays that we'll use throughout the code, and cached jQuery elements. For best performance when using jQuery, it's best to select elements from the page and store them in variables instead of repeatedly selecting them from the page.

Although none of our variables are directly assigned to window, and are therefore not actually global, because we are defining them right at the top of our outermost function, they will be visible throughout our code and we can consider them as global. This gives us the visibility of globals, without actually cluttering the global namespace.

Note

It is best practice to define variables at the top of the function they are scoped to because of a phenomenon known as hoisting, in which variables defined deep inside a function, inside a for loop for example, are "hoisted" to the top of the function in some situations, potentially causing errors that are hard to track down.

Defining variables at the top of the function where possible is a simple way to avoid this occurring and is considered a good practice when writing jQuery, or JavaScript in general.

Most of the variables are quite straightforward. We store the number of puzzle pieces we'd like to use and the aspect ratio of the image being used. It's important that the number of pieces can be equally divided by both the width and height components of the ratio.

We split the aspect ratio into its component parts using JavaScript's split() function and specifying the colon as the character to split on. We also use the JavaScript parseInt() function to ensure we end up with actual numbers and not strings in the aspectW and aspectH variables.

The next three variables are all different elements selected from the page that we need to manipulate. Following this is a new element that we create using jQuery.

Next we calculate the width and height each piece of the puzzle will need to be sized to, based on the width and height of the original image and the aspect ratio, and we initialize a counter variable that we'll use to add a unique, ordered id attribute to each puzzle piece. We also add an empty array called positions, which we'll use to store the top and left positions of each new piece.

We'll need a way of keeping track of the empty space as the pieces are moved around the board, so we create an object called empty and give it top, left, bottom, and right properties so that we'll know exactly where the blank is space at any given moment. We'll also want to keep track of the previous location of any given piece so we create an empty object called previous that we'll populate with properties when required.

The remaining three variables are all concerned with keeping track of the time it takes to solve the puzzle. We defined, but didn't initialize the timer variable that we'll use to store a reference to a JavaScript setInterval()-based timer later in the script. We also created an empty object called currentTime, which again we'll populate when required, and cached a reference to the element that we'll use to display the current time.

 

Splitting an image into pieces


Our next task is to divide the image into a specified number of squares to represent the individual pieces of the puzzle. To do this we'll create a series of smaller elements which each show a different part of the image and which can be manipulated individually.

Prepare for Lift Off

The single step required to complete this task is to create a specified number of puzzle pieces and give each a unique background-position and position in order to recreate the image.

Engage Thrusters

We now want to generate the different pieces that make up the puzzle. We can do this with the following code, which should be added directly after the variables we just defined in sliding-puzzle.js:

for (var x = 0, y = aspectH; x < y; x++) {
    for (var a = 0, b = aspectW; a < b; a++) {
        var top = pieceH * x,
            left = pieceW * a;

        piece.clone()
             .attr("id", idCounter++)
             .css({
                 width: pieceW,
                 height: pieceH,
                 position: "absolute",
                 top: top,
                 left: left,
                 backgroundImage: ["url(", path, ")"].join(""),
                 backgroundPosition: [
                     "-", pieceW * a, "px ", 
                     "-", pieceH * x, "px"
                 ].join("")
        }).appendTo(imgContainer);

        positions.push({ top: top, left: left });
    }
}

Objective Complete - Mini Debriefing

We used a nested set of for loops to create the new puzzle pieces in a grid pattern. The first loop will run for as many rows as required; with a 3:4 aspect-ratio image such as that used in this example, we will need four rows of squares. The inner loop will for run for as many columns as required, which in this case is three.

Within the inner loop we first create two new variables top and left. We need to use these values in a couple of places so it makes sense to create them once and reuse them each time they're required.

The top position is equal to the height of the piece multiplied by the current value of the outer loop's counter variable (x), while the left position is equal to the width of the piece multiplied by the current value of the inner loop's counter variable (a). These variables are used to make the puzzle pieces line up in a grid.

We then copy our stored <div> element using jQuery's clone() method and use the attr() method to set a unique id attribute using the idCounter variable that we initialized in the first part of the project. Notice that we increment the variable at the same time as setting it directly within the attr() method.

We could increment the variable either inside the method as we have done here, or outside of the method; there's no real difference in performance or anything else. I just feel that it's more succinct to update it in situ.

Next we use the css() method to set a style attribute on the new element. We set the width and height of the puzzle piece and position it using our top and left variables, as well as set its backgroundImage and backgroundPosition style properties.

Note

Any style properties that are usually defined using hyphenated words, such as background-image, should be camel-cased when used with jQuery's css() method in conjunction with an object.

The backgroundImage property can be set using our path variable and the rest of the string components of the style, but the backgroundPosition property will need to be calculated individually for each puzzle piece.

The horizontal component of the backgroundPosition style property is equal to the width of the piece multiplied by the value of the inner loop's counter variable (a), while the vertical component is equal to the height of the piece multiplied by the value of the outer loop's counter variable (x).

Once the new element has been created we can add its position to our positions array using JavaScript's push() method, passing in an object containing the top and left positional properties of the element for later use.

Classified Intel

Instead of using standard string concatenation to construct the backgroundImage and backgroundPosition strings, we put the values into an array literal and then joined the array using JavaScript's join() method. By specifying an empty string as the value to use to join the string, we ensure that no additional characters are added to the string.

Joining an array of substrings to form a single string is much faster than building a string using the + operator on substrings, and as we're working repetitively inside a loop, we should optimize the code within the loop as much as possible.

 

Shuffling the puzzle pieces


In this step we need to randomly shuffle the pieces to make it a puzzle so that the visitor can unscramble them. We can also remove the original image as it's no longer required, and remove the first piece to create an empty space so that the other pieces can be moved around.

Prepare for Lift Off

The steps we'll cover in this task are:

  • Removing the original image from the page

  • Removing the first piece of the puzzle

  • Removing the first item in the positions array

  • Shuffling the pieces randomly

Engage Thrusters

Completing the first step requires just the following line of code, which should be added directly after the closing curly-bracket of the outer for loop we added to sliding-puzzle.js in the last task:

img.remove();

The second step is equally as simple; the following can be added directly after the previous line of code:

container.find("#0").remove();

We can also use a single line of code for the next step. Add the following directly after the previous line of code:

positions.shift();

Shuffling the pieces will be slightly more complex; you'll remember from the first part of the project when we added the underlying HTML that one of the elements was a start button. We'll use this button to trigger the shuffle. Add the following code directly after the first two lines we just added (make sure they are still within the outer function wrapper):

$("#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;

    container.find("#ui").find("p").not("#time").remove();

});

Objective Complete - Mini Debriefing

jQuery's remove() method is used to remove the original image element from the page, which we already selected when we declared our variables at the start of the script. We use the same method to remove the first puzzle piece, which we should do before the pieces are shuffled to avoid removing a key piece, such as a face. As with the image used in this example, an image where the main item of interest is not in the top-left corner is beneficial.

As we've removed the first piece from the board, we should also remove the first item in the positions array. We'll use this array when we come to check whether the puzzle has been unscrambled and as there won't be a piece at the first position, we don't need to store its position. We use JavaScript's unshift() method to do this, which simply removes the first item in the array it is called on.

Adding an event handler to the button using on()

We added a click event handler for the button by selecting it and calling the jQuery on() method. The on() method takes two arguments in this example (although it can take three when event delegation is required).

The first argument is the event to listen for and the second is the handler function to be executed each time the event is detected. We are listening for the click event in this case.

Tip

The all-encompassing on() method

jQuery's on() method, introduced in version 1.7, replaces the bind(), live(), and delegate() methods, which are now deprecated. Using on() is now the recommended way of attaching event handlers in jQuery.

Within the handler function we first define a variable which stores the children of the <figure> element. Although we need to select the pieces from the page again, we can still use our cached imgContainer variable to avoid creating a new jQuery object.

Shuffling the pieces

Next we define a function called shuffle(), which accepts the array to shuffle as an argument. This function performs a Fisher-Yates shuffle, which is an established pattern for creating a random ordering of a given set of values.

Within the function, we first get the length of the array that was passed in, and return false (exiting the function) if the array is empty. We then use a while loop to cycle through the array. A while loop in JavaScript is similar to a for loop but executes while the condition specified in brackets has a truthy value (or while it evaluates to true), instead of executing a specified number of times. A pre-decrementing loop condition is used to avoid an unnecessary iteration of the loop once the items have all been shuffled.

Note

In JavaScript, as well as the true or false Boolean values, other types of variables can be said to be truthy or falsey. The following values are all considered falsey:

  • The Boolean value false

  • The number 0

  • An empty string

  • null

  • undefined

  • NaN

All other values are considered truthy. This is so that non-Boolean values can be used as conditionals. The similarities between the terms falsey and false may lead to confusion; just remember that false is an actual value, and falsey is an aspect of a value, which values other than false also have.

For more information on this subject, see http://james.padolsey.com/javascript/truthy-falsey/.

Within the loop, which will be executed once for each item in the array except the first item, we want to pick a random item from the array and swap its position in the array with another item. To generate a random number to use as the index of the item to swap, we first generate a random number using JavaScript's Math.random() function and multiply the random number (which will be between 0 and 1) by the length of the array plus 1. This will give us a random number, between 0 and the length of the array.

We then pull the item with the current index out of the array, along with the item at the randomly generated index, and swap them. It may seem complex but this is almost universally regarded as the most efficient way to randomly shuffle the items in the array. It gives us the most random result for the least amount of processing.

Once we have defined the function, we then invoke it, passing in the pieces array as the array to shuffle.

Note

For more information on the JavaScript implementation of the Fisher-Yates shuffle, see http://sedition.com/perl/javascript-fy.html.

Positioning the pieces

Once the array of elements has been shuffled, we iterate it using jQuery's each() method. This method is passed the array to iterate over, which in this case is the pieces array we have just shuffled. The second argument is an iterator function that will be called for each item in the array.

Within this function we use our positions array to put the shuffled elements in the right place on the page. If we didn't do this, the elements would be shuffled, but would still appear in the same place on the page because of their absolute positioning. We can use the positions array that we updated when creating the new elements to get the correct top and left positions for each of the shuffled elements.

Once the collection of elements have been iterated and their positions set, we then append them back to the page using jQuery's appendTo() method. Again we can specify our imgContainer variable as the argument to appendTo() in order to avoid selecting the container from the page once more.

Positioning the empty space

Lastly we should make sure that the empty space is definitely at 0 top and 0 left, that is the top-left square of the board. If the button is clicked, some pieces are moved and then the button is clicked again, we have to ensure that the empty space is in the right place. We do this by setting both the top and left properties of the empty object to 0.

We can also remove any previous messages that may be displayed in the UI area (we'll cover adding these messages towards the end of this project). We don't want to remove the timer though, so we filter this element out of the selection using jQuery's not() method, which accepts a selector for which matching elements are discarded and therefore not removed from the page.

At this point we should be able to run the page in a browser and shuffle the pieces by clicking on the Start! button:

 

Making the puzzle pieces draggable


Now it's time to kickstart jQuery UI to make the individual pieces of the puzzle draggable.

jQuery UI is a suite of jQuery plugins used to build interactive and efficient user interfaces. It is stable, mature, and is recognized as the official, although not the only UI library for jQuery.

Prepare for Lift Off

In this task we'll cover the following steps:

  • Making the puzzle pieces draggable using jQuery UI's Draggable component

  • Configuring the draggables so that only pieces directly next to the empty space can be moved

  • Configuring the draggables so that pieces can only be moved into the empty space

Engage Thrusters

First we'll make the pieces draggable and set some of the configuration options that the component exposes. This code should be added to sliding-puzzle.js, directly after the code added in the previous task:

pieces.draggable({
    containment: "parent",
    grid: [pieceW, pieceH],
    start: function (e, ui) {

    },
    drag: function (e, ui) {

    },
    stop: function (e, ui) {

    }
});

The next few steps in this task will see additional code added to the start, drag, and stop callback functions in the previous code sample.

We also need to configure the draggability so that the pieces can only be moved into the empty space, and not over each other, and so that only pieces directly adjacent to the empty space can be moved at all.

Next add the following code in to the start callback function that we just added:

var current = getPosition(ui.helper);

if (current.left === empty.left) {
    ui.helper.draggable("option", "axis", "y");
} else if (current.top === empty.top) {
    ui.helper.draggable("option", "axis", "x");
} else {
    ui.helper.trigger("mouseup");
    return false;
}

if (current.bottom < empty.top || 
    current.top > empty.bottom ||
    current.left > empty.right || 
    current.right < empty.left) {
        ui.helper.trigger("mouseup");
        return false;
    }

    previous.top = current.top;
    previous.left = current.left;

Next, add the following code to the drag callback function:

var current = getPosition(ui.helper);

ui.helper.draggable("option", "revert", false);

if (current.top === empty.top && current.left === empty.left) {
    ui.helper.trigger("mouseup");
    return false;
}

if (current.top > empty.bottom ||
    current.bottom < empty.top || 
    current.left > empty.right || 
    current.right < empty.left) {
        ui.helper.trigger("mouseup")
                 .css({ 
                     top: previous.top, 
                     left: previous.left 
                 });
        return false;
}

Finally, we should add the following code to the stop callback function:

var current = getPosition(ui.helper);

if (current.top === empty.top && current.left === empty.left) {

    empty.top = previous.top;
    empty.left = previous.left;
    empty.bottom = previous.top + pieceH;
    empty.right = previous.left + pieceW;
}

In each of our callbacks we've used a helper function that returns the exact position of the current draggable. We should also add this function after the draggable()method:

function getPosition(el) {
    return {
        top: parseInt(el.css("top")),
        bottom: parseInt(el.css("top")) + pieceH,
        left: parseInt(el.css("left")),
        right: parseInt(el.css("left")) + pieceW
    }
}

Objective Complete - Mini Debriefing

We wrote a lot of code in that last task, so let's break it down and see what we did. We started by making the pieces draggable using the jQuery UI draggable component. We did this by calling the draggable() method, passing in an object literal that sets various options that the draggable component exposes.

First we set the containment option to parent, which stops any of the pieces being dragged out of the <figure> element that they are within. We also set the grid option, which allows us to specify a grid of points that the piece being dragged should snap to. We set an array as the value of this option.

The first item in this array sets the horizontal points on the grid and the second item sets the vertical points on the grid. Setting these options gives the movement of the pieces a more realistic and tactile experience.

The next and final three options that we set are actually callback functions that are invoked at different points in the life-cycle of a drag. We use the start, drag, and stop callbacks.

When the drag begins

The start callback will be invoked once at the very start of the drag interaction following a mousedown event on a draggable. The stop callback will be invoked once at the very end of a drag interaction, once a mouseup event has registered. The drag callback will fire almost continuously while a piece is being dragged as it is invoked for every pixel the dragged element moves.

Let's look at the start callback first. Each callback is passed two arguments by jQuery UI when it is invoked. The first of these is the event object, which we don't require in this project, while the second is an object containing useful properties about the current draggable.

At the beginning of the function we first get the exact position of the piece that dragging has started on. When we call our getPosition() function, we pass in the helper property of the ui object, which is a jQuery-wrapped reference to the underlying DOM element that has started to be dragged.

Once we have the element's position, we first check whether the element is in the same row as the empty space by comparing the left property of the current object (the object returned by getPosition()) with the left property of the empty object.

If the two properties are equal, we set the axis option of the draggable to y so that it can only move horizontally. Configuration options can be set in any jQuery UI widget or component using the option method.

If it isn't in the same row, we check whether it is in the same column instead by comparing the top properties of the current and empty objects. If these two properties are equal, we instead set the axis option to x so that the piece may only move vertically.

If neither of these conditions is true, the piece cannot be adjacent to the empty space, so we manually trigger a mouseup event to stop the drag using jQuery's trigger() method, and also return false from the function so that our stop handler is not triggered.

We need to make sure that only squares in the same row or column as the empty space are draggable, but we also need to make sure that any pieces that are not directly adjacent to the empty space cannot be dragged either.

To stop any pieces not adjacent to the empty space being dragged, we just check that:

  • The bottom of the current piece is less than the top of the empty space

  • The top of the current piece is greater than the bottom of the empty space

  • The left of the current piece is greater than the right of the empty space

  • The right of the current piece is less than the left of the empty space

If any of these conditions are true, we again stop the drag by triggering a mouseup event manually, and stop any further event handlers on the draggable being called (but only for the current drag interaction) by returning false.

If the callback function has not returned at this point, we know we are dealing with a draggable that is adjacent to the empty space, thereby constituting a valid drag object. We therefore store its current position at the start of the drag for later use by setting the top and left properties of the previous object that we initialized at the start of the project.

Tip

The position of ui.helper

The ui object passed to our callback function actually contains an object called position, which can be used to obtain the current draggable's position. However, because we are using the grid option, the values contained in this object may not be granular enough for our needs.

During the drag

Next we can walk through the drag callback, which will be called every time the position of the current draggable changes. This will occur during a mousedown event.

First of all we need to know where the piece that's being dragged is, so we call our getPosition() helper function again.

Then we want to check whether the piece being dragged is in the empty space. If it is, we can stop the drag in the same way that we did before – by manually triggering a mouseup event and returning false.

During the drag, only valid pieces will be draggable because we've already filtered out any pieces that are not directly adjacent to the empty space. However, we still need to check that the piece being dragged is not being dragged away from the empty space. We do this in the same way that we filtered out pieces not adjacent to the empty space in the start callback.

When the drag ends

The stop callback is the simplest of the three callbacks. We get the position of the piece that was dragged, and if it's definitely in the empty space, we move the empty space so that it is in the position the dragged piece was in when the drag began. Remember we stored this information in an object called previous.

 

Starting and stopping the timer


At this point, our game is fully functional and the puzzle can be unscrambled; however to make it more fun we should introduce an element of competitiveness by incorporating a timer.

Prepare for Lift Off

In this task we'll need to complete the following steps:

  • Check the timer isn't already running when the Start button is clicked

  • Start the timer from 0

  • Increment the timer every second

  • Update the display on the page so that the player can see how long the current game has taken so far

Engage Thrusters

To check whether the timer is already running when the Start button is clicked we should add the following code directly after where we appended the shuffled pieces to the page, and directly before the call to draggable():

pieces.appendTo(imgContainer).draggable("destroy");

if (timer) {
    clearInterval(timer);
    timerDisplay.text("00:00:00");
}

timer = setInterval(updateTime, 1000);
currentTime.seconds = 0;
currentTime.minutes = 0;
currentTime.hours = 0;

pieces.draggable({

Next we can add the function that increments the timer and updates the display. This code should come directly after where we update currentTime.hours in the previous code:

function updateTime() {
  
    if (currentTime.hours === 23 && currentTime.minutes === 59 &&
currentTime.seconds === 59) {
        clearInterval(timer);          
    } else if (currentTime.minutes === 59 && currentTime.seconds === 59) {

        currentTime.hours++;
        currentTime.minutes = 0;
        currentTime.seconds = 0;
    } else if (currentTime.seconds === 59) {
        currentTime.minutes++;
        currentTime.seconds = 0;
    } else {
        currentTime.seconds++;
    }

    newHours = (currentTime.hours <= 9) ? "0" + currentTime.hours :

    currentTime.hours;
    newMins = (currentTime.minutes <= 9) ? "0" + currentTime.minutes :

    currentTime.minutes;
    newSecs = (currentTime.seconds <= 9) ? "0" + currentTime.seconds : 

    currentTime.seconds;

    timerDisplay.text([
        newHours, ":", newMins, ":", newSecs
    ].join(""));


}

Objective Complete - Mini Debriefing

The first thing we have to do in this task is check whether a timer is already running. The timer will be stored in one of our "global" variables so we can check it easily. We use an if statement to check whether timer contains a truthy value (see the previous information on JavaScript's truthy and falsey values).

If it does, we know the timer is already running, so we cancel the timer using JavaScript's clearInterval() function, passing in our timer variable as the timer to clear. We can also reset the timer display if the timer is already running. We selected the timer display element from the page and cached it when we initially declared our variables at the start of this project.

Next we start the timer using JavaScript's setInterval() method and assign it to our timer variable. When the timer begins this variable will contain the ID of the timer, not the value of the timer, which is how clearInterval() knows which timer to clear.

The setInterval() function accepts a function to execute after the specified interval as the first argument, and the interval as the second argument. We specify 1000 milliseconds as the interval, which is equal to 1 second, so the function passed as the first argument will be called every second until the timer is cleared.

Once the timer has started we can also reset the values stored in the object we'll use to keep track of the timer – the currentTime object. We set the seconds, minutes, and hours properties of this object to 0. We need an object to keep track of the time because the timer variable itself just contains the ID of the timer.

Next we added the updateTime() function that will be called by our interval every second. All we do in this function is update the relevant properties of the currentTime object, and update the display. We use an if conditional to check which parts of the timer to update.

We first check that the timer has not reached 24 hours. I would hope that no one would actually spend that long playing the game, but if the browser is left open for some reason for this length of time, we don't want the time display to say, for example, 24 hours and 1 minute, because at that point, we really should update the display to say 1 day, 0 hours, and 1 minute. But we aren't bothering with days so instead we just stop the timer.

If the timer has not reached this length of time we then check whether the current minutes equal 59 and the current seconds equal 59. If they do we need to increment currentTime.hours by 1 and reset the currentTime.minutes and currentTime.seconds properties back to 0.

If this check fails we then check whether the seconds equal 59. If they do, we increment the currentTime.minutes property and then reset currentTime.seconds back to 0. If this second test also fails we know that all we have to do is increment currentTime.seconds.

Next we need to check whether we need to pad any of the time components with a leading 0. We could use another if else conditional for this, but the JavaScript ternary construct is neater and more compact so we use this instead.

First we test whether currentTime.hours is equal to or less than 9 and if so we add 0 to the start of the value. We do the same for currentTime.minutes and currentTime.seconds.

Finally, we build the string which we will use to update the timer display. Instead of using boring and slow string concatenation, we again use an awesome array comprising the various parts of the display and then join the array.

The resulting string is set as the value of the <span> element contained in the timerDisplay variable and the element on the page is updated using jQuery's text() method.

At this point we can now click on the button to shuffle the puzzle pieces, and watch as the timer starts to increment.

 

Determining if the puzzle has been solved


In this task we'll focus on determining whether the pieces have been put back into their correct locations, unscrambling and therefore solving the puzzle.

Prepare for Lift Off

The following steps will be covered in this task:

  • Checking the order of pieces to see if they match the starting order of the pieces

  • Stopping the timer

  • Displaying a congratulatory message

Engage Thrusters

First of all we need to decide when we should check whether the puzzle has been completed. A good place to do the check would be on the stop event of the drag.

First add the following new variable directly after the existing current variable at the top of the stop() callback:

var current = getPosition(ui.helper),
    correctPieces = 0;

Don't forget to add a trailing comma after the first variable, as shown in the previous code sample. Next add the following code directly after the if statement:

$.each(positions, function (i) {
    var currentPiece = $("#" + (i + 1)),
        currentPosition = getPosition(currentPiece);

    if (positions[i].top === currentPosition.top && positions[i].left === currentPosition.left) {

        correctPieces++;
    }
});

if (correctPieces === positions.length) {
    clearInterval(timer);
    $("<p/>", {
        text: "Congratulations, you solved the puzzle!"
    }).appendTo("#ui");
}

Objective Complete - Mini Debriefing

First of all we defined a new variable called correctPieces and set its value to 0. We then used jQuery's each() method to iterate the positions array that we populated much earlier in the code, when we initially shuffled the pieces.

What we need to do at this point is get each piece from the puzzle and check whether the pieces are in the correct order. However, we can't just select the elements from the page using jQuery's children() method, for example, or find(), because jQuery does not return the elements in the order that they are found in the DOM, especially as we have already dragged them all around their parent container.

What we have to do instead is select each element by its id attribute, and check to see what top and left CSS properties it has in its style attribute. The length of the positions array is the same as the number of pieces so we can iterate this array and use the index argument that jQuery automatically passes to the iterator function.

Within the iterator we first select the current element. The id attributes for each piece will start at 1 instead of 0 because we already removed the first piece from the puzzle, so we add 1 to the index value when selecting each piece. We also get the position of the current element using our existing getPosition() function, passing in the element we just selected.

Next we compare the current piece's top and left properties with the equivalent item from the positions array, and if both the top and left properties match, we increment the correctPieces variable.

Once each piece from the page and each item in the positions array have been compared and the each() method has finished iterating, we then check whether the value of the correctPieces variable is equal to the length of the positions array. If it is, we know that each piece is in the correct place.

We can stop the timer at this point in the same way that we did before – using the clearInterval() function, and then create the congratulatory message and append it to the element with an id of ui.

 

Remembering best times and adding some final styling


The game is now pretty playable as it stands. We can shuffle the pieces, only allow them to be dragged according to the rules, and the game will detect when the puzzle has been solved. Using a simple timer we can tell the player how long it took for them to solve it, but then what? What is the player supposed to do, just remember his/her best score?

Of course, we now need a way to save the player's best time. It would also be handy if we could display an additional message if they beat their stored best time. We'll use the JavaScript localStorage API to store the best time.

We can also add a little extra styling to finish the appearance of the game and lay out the different elements a little better.

Prepare for Lift Off

The steps that we'll cover in this task are as follows:

  • Checking whether a best time has been saved

  • Checking whether the current best time is better than the saved best time

  • Updating the saved best time when the current best time is better than it

  • Displaying an additional message when the saved best time is beaten

  • Tidying up the presentation of the game with CSS

Engage Thrusters

Everything we need to do in this task can be done in the if statement that is executed once the pieces are back in the correct order. Directly after where we displayed the congratulatory message in the last task add the following code:

var totalSeconds = (currentTime.hours * 60 * 60) + (currentTime.minutes * 60) + currentTime.seconds;



if (localStorage.getItem("puzzleBestTime")) {

    var bestTime = localStorage.getItem("puzzleBestTime");

    if (totalSeconds < bestTime) {
    
        localStorage.setItem("puzzleBestTime", totalSeconds);

        $("<p/>", {
            text: "You got a new best time!"
        }).appendTo("#ui");
    }
} else {
    localStorage.setItem("puzzleBestTime", totalSeconds);

    $("<p/>", {
        text: "You got a new best time!"
    }).appendTo("#ui");
}

We already created the style sheet that we'll use for this – sliding-puzzle.css, so we just need to add the following selectors and style rules to this file:

#puzzle { 
    width:730px; padding:5px; margin:auto; 
    border:1px solid #aaa; border-radius:5px; 
    background-color:#eee; 
}
#puzzle figure { 
    width:510px; height:676px; border:1px solid #aaa; 
    position:relative; float:left; background-color:#fff; 
}
#ui { 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; }

Objective Complete - Mini Debriefing

First of all we convert the current time into seconds so that we have only a single value to work with and store. The seconds are calculated using the hours, minutes, and seconds properties of the currentTime object used to update the visible timer on the page.

The hours property is multiplied by 60 to convert to minutes, and then by 60 again to convert to seconds. The minutes property is multiplied by 60 a single time, then these two values are added to the seconds remaining in the seconds property to give the final total, which we store in the totalSeconds variable.

Next we check the localStorage to see if a key exists with the name puzzleBestTime. If it does, we store the value held in localStorage in the bestTime variable. If the value of our totalSeconds variable is less than the bestTime variable, we have a new high score, which we save in localStorage with the puzzleBestTime name in order to overwrite the old best time. We then display a second congratulatory message to say a new high score has been achieved.

If localStorage doesn't contain a key with this name, this must be the first time the game has been played in this browser, so we set the name of the key and store the value of the currentTime variable as the new best time, and again display the second congratulatory message.

There's nothing really crucial in the CSS that we added; it was just a little bit of light styling to tidy up the various elements we've used and present the game in a cleaner style.

Classified Intel

The localStorage API is one of the more stable JavaScript APIs that fall within the general umbrella term of HTML5, and enjoys wide support by all of the latest versions of all common browsers.

Old browsers, which we may still need to support, such as IE7 or Firefox 2, do not support localStorage. Luckily there are plenty of polyfills and workarounds that exist to add a basic level of support in these legacy browsers.

See https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills for a wide range of polyfills and patches that add support for modern APIs to legacy browsers.

 

Mission Accomplished


We used a wide range of jQuery and plain-vanilla JavaScript over the course of this project to create this simple game. We also looked at using jQuery UI's draggable component as well as the localStorage API.

We covered a lot of code so let's briefly look back at what we did.

We first declared most of the variables that we used throughout the project right at the start of our document.ready function. It's useful to do this so that variables can be used throughout our code without making them global in scope. For performance reasons, it's also best to cache jQuery objects so that they can be manipulated frequently without having to keep selecting them from the page.

We then saw how we can easily split an image of a known aspect-ratio into a number of equally-sized pieces laid out in a grid using nothing but some nested for loops and some simple mathematics. We also saw that using an array of substrings to create a string instead of using string concatenation is a very easy optimization that can help speed up our applications when long strings need to be constructed.

We then saw how to shuffle the individual pieces into a random order using an accepted algorithm for randomizing – the Fisher-Yates shuffle. We didn't actually use jQuery at all to do this, but don't forget that the code to produce the shuffle was executed inside an event handler added using jQuery's on() method.

Next we looked at how to make the pieces of the puzzle draggable using jQuery UI. We looked at some of the configurable options exposed by the component, as well as how to react to different events generated when the pieces were dragged. Specifically, we used the start, drag, and stop callbacks to enforce the rules of the game concerning which pieces could be moved, and how they could be moved during game play.

After this we looked at using a standard JavaScript timer to keep track of how long it took to solve the puzzle, and how to keep the visible timer on the page updated so that the player could see the time that has elapsed since they started.

Detecting when the puzzle was solved was also a crucial ability of the code. Our main obstacle here was the fact that the pieces weren't selected from the page in the visible order we could see on the screen, but this was easily overcome by selecting the pieces using their numbered id attributes and then manually checking their CSS position.

Lastly we looked at how to keep a record of the player's best time in solving the puzzle. localStorage is the obvious choice here, and it was a small step to check whether a score was already stored, and then compare the current time with the stored time to see if the record had been beaten.

 

You Ready To Go Gung HO? A Hotshot Challenge


There is still much more functionality we could add to our simple game. Why not update the game so that it has different skill levels available for the player to choose from?

All we'd need to do to achieve this would be to provide some kind of interface to allow the visitor to select the skill level, and then think of a way in which the game could be made more difficult.

If we assume that the game in its current format is the easiest skill level, one very simple way to make it harder is to increase the number of pieces that the original image is split into. Have a go at doing this yourself. Those of you with a deep understanding of mathematics may realize that our game has another flaw – some random combinations of the pieces will simply not be solvable. Storing or computing all of the possible combinations that are solvable is probably beyond practical, but there is another option.

Instead of randomly shuffling the array of pieces and then writing their positions to the board, we could instead shuffle the pieces by programmatically moving them around the board. A puzzle shuffled according to the rules of the game by which the player is bound would result in a solvable puzzle every time.

About the Author

  • Dan Wellman

    Dan Wellman is an author and frontend engineer living on the South Coast of the UK and working in London. By day he works for Skype and has a blast writing application-grade JavaScript. By night he writes books and tutorials focused mainly on frontend web development. He is also a staff writer for the Tuts+ arm of the Envato network, and occasionally writes for .Net magazine. He's the proud father of four amazing children, and the grateful husband of a wonderful wife.

    Browse publications by this author
Book Title
Access this book and the full library for FREE
Access now