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: