Assignment: Sliding Tiles

I hope this will be fun.

Goal

You'll build a web application of a sliding tile puzzle. I've seen them in a 15-piece version (and played them in the back of the car on long trips in my childhood). You'll build an 8-piece puzzle. (And, before you think it's just silliness, I found this article on magentism that suggests it may be important in solid-state physics.)

Basics:

  • There are four moves: up, down, left and right.
  • We will interpret "up" to mean that a tile is moving "up" into the blank spot. Similarly for the other moves.
  • If the blank is on the last row, "up" isn't possible. Similarly for other locations for the blank. In fact, the only time all four moves are possible is when the blank is in the center.
  • We'll use WASD for the movement keys. Feel free to add the arrow keys as well, or IJKL or whatever other keys you want. 'W' maps to "up" and so forth.
  • If a move isn't possible, that key should do nothing.

Here are the things you'll need to figure out

  • What picture is in each grid square? Since they'll be moving around, this will have to be a dynamic data structure. I recommend a 2D array of 3 rows of 3 elements each.
  • Where the blank is. You'll maintain a global variable or two that keeps track of the coordinates of the blank.
  • What moves are possible.
  • Given a move, figure out what tile needs to move and slide it the correct way. If it's not a possible move, just do nothing.

Obfuscated Solution

Here is my solution

My solution also supports the arrow keys and clickable buttons, but you don't have to do either feature (though neither is hard).

Here is a version with a scramble button

You do not need to do the scrambling behavior in your solution. But I couldn't resist implementing it, and so I wanted to share it. If you want to try solving a puzzle, click the "scramble" button and close your eyes for a minute.

Demo

2D arrays

One of the key parts of this is how to represent the current occupant of each grid square. I recommend a 2D array.

That's done just by having a 1D array, each of whose elements is a 1D array. Here's an example that shows the layout of 9 keys near the left side of your computer keyboard. (You should not use this variable name in your program. Instead, learn the idea of 2D arrays, and then adapt it for your assignment.)

var keyboard = [['q','w','e'],
                ['a','s','d'],
                ['z','x','c']];
console.log(keyboard[0][1]); // prints 'w'
console.log(keyboard[1][0]); // prints 'a'
console.log(keyboard[2][2]); // prints 'c'

Note that the [0][1] in the first console.log for 'w' means the zeroth (top) row and element 1. Similarly, in the console.log for 'a', the [1] is for row 1, and the [0] is for the zeroth column in that row.

You can assign to an element using the same access syntax:

keyboard[1][0] = 'A'; // capital A

Maintaining Data Structures

The hard part of this assignment is maintaining a data structure. What does that even mean? It means that your program is going to know what is going on in the game, where the tiles are, where the blank is, and what moves are possible by having a data structure that records the current state of the game. As the player moves the tiles around, your program needs to update the data structure so that the data structure continues to be accurate.

Overall, this means that your program is like an expert chess player playing "blind chess": your program has to know where all the pieces are, keeping the information "in its head."

I advise you against trying to infer the state of the game by poking around the DOM figuring out where all the tiles are. First, that's relatively hard to do. Second, it doesn't allow for various hypothetical advanced features we could add, such as a "hint" feature which would solve the game (without moving any pieces) and then tell the user what their next move should be. Far better to have an internal representation and then just display that representation on the screen.

Here's a screenshot from a JS console open in my solution. It shows the tile array before and after a "down" move from the initial position. When the "down" move happens, the game prints the newly updated position of the blank. Knowing where the blank is avoids having to search for it in the tile array.

before and after the first move

Let me say that last part again: knowing where the blank is avoids having to search for it. You should have global variable(s) that keep track of where the blank tile is (its row and its column), so that you never have to search for it. Of course, you'll have to update those variables whenever the blank moves, but again, you know how it's moving, so you don't have to search for its new location.

Checking that the data structure is correctly updated is key. I've had many students in the past where the update was partly correct and the app worked fine for 3-4 steps, but then went wrong after that.

Sliding a Tile

To move a tile, you'll modify either its top or left to either increase or decrease it by the width/height of a tile. Since the tiles are 200px square, the distance will always be 200px.

I suggest you use the modifying a value technique described in the reading on animate and keyup.

One important piece is to realize that the 8 pieces are all positioned in an area of the browser set aside for them. The playing area (grid) should be position:relative and all the pieces are position:absolute and positioned appropriately with the grid.

You can let each piece keep track of its location and use jQuery's css method to read those values off when you need them.

I suggest giving each piece an ID or data- attribute so that you can easily find it to move it around.

References

Some information you might need to look back on:

Directions

You'll put your solution in a tiles folder in your cs204-assignments folder:

cd ~/public_html/cs204-assignments
mkdir tiles

I'm going to leave most of the markup, CSS and coding to you. You are welcome to use mine:

cd ~/public_html/cs204-assignments
cp ~cs204/pub/assignments/tiles/solution/tile-game.html tiles/
cp ~cs204/pub/assignments/tiles/solution/tile-game.css tiles/

As in the last two assignments, I will supply a set of images that you can copy. It's in a folder called tiles which you'll copy to your tiles folder.

cd ~/public_html/cs204-assignments
cp -r ~cs204/pub/downloads/tiles/ tiles/

(This is the same procedure as for the last two assignments.)

If you want to use a different image, you can go to imagesplitter.net and split an image of your choosing.

Note that the tiles include a blank.jpeg file (just because the imagesplitter created it), but you don't need to use the blank at all. If you arrange the 8 tiles in a 3x3 grid, one grid position will naturally be empty. Your game only needs to worry about sliding a single tile into the unoccupied grid position.

Where is the Blank?

Students have often written code that searches the 2D array to find the blank, which then allows their code to determine the neighboring tile. Such code can work, but is unnecessary. Instead, just keep track of where the blank is. After all, you know where it starts, and it only moves under your control, so it's straightforward to keep track of where it currently is. My solution has no loops.

Advice

  • Build functions that you can test on their own, using the JS console, rather than trying to build everything and test it using the click handlers
  • A low-level function you might write could be getRightTile() taking no arguments and which returns the tile to the right of the blank, if any. You'll have to figure out what it should do or return if there is no such tile. Test it from the JS console. Write and test three similar functions.
  • A high-level function you might write could be doMove which takes a direction, determines the tile to slide and slides it one space in that direction. Test it from the JS console.
  • Remember that whenever you move a tile, you have to update your data structures and global variables to ensure that they correctly represent the new state of the grid.
  • When you have the functions working well, then add the keyboard event handler to do the right thing.

Final advice:

  • You can use either raw JS or jQuery to manipulate the DOM. I used jQuery.
  • Coding quality matters. Try to be concise and abstract.
  • My code, including functions you've already written, and including blank lines and comments space between functions, is still less than 100 lines. (But don't worry if yours is a bit more than that. The number of lines of code is not that important, but you should try to avoid being verbose.)
  • You don't have to use OOP in this assignment. You can, but I didn't. We will use OOP on future assignments.

Final Checklist

  • Make sure your name is in the files. If you have a partner, both names should be in the files.
  • Make sure everything works and looks nice
  • Make sure both the HTML and the CSS are valid

How to turn this in

We'll grade the work in your cs204-assignments/tiles folder; no need to do anything else. You'll need to submit a Gradescope item, as usual.

More Information

The following information is unnecessary for this assignment, but I've included it because it's connected to some of the material. Reading this is optional, for those who are interested.

Above, we learned that because animations take time, a "print" statement right afterwards doesn't show the updated values, because the value have not yet been updated. What if you do want the updated values? (You don't in the assignment). What can you do?

The jQuery .animate() method takes an optional callback function that is invoked after the animation completes. You should supply a function that takes takes no arguments.

The following example prints the correct "after" value:

function report() {
    console.log($("#white_rabbit").css("width"));
}
$("#growButton2").click(function () {
    report();
    $("#white_rabbit").animate({width: "+=200"}, 600, report);
});

Animation Callbacks

Checking that a jQuery animation worked is tricky. The following won't work. The buttons to grow/shrink the "Alice" regious work, but the console.log statements always print the same value, namely the "before" values.

$("#growButton").click(function () {
    console.log('before '+$("#alice").css("width"));
    $("#alice").animate({width: "+=200"}, 600);
    console.log('after '+$("#alice").css("width"));
});

The before value is printed twice because the animation takes time (600ms), and the second console.log happens immediately after the animation begins. So, the value that we label "after" is unchanged from the "before" value.

Don't let this confuse you!

In practice, students don't check that the animation "worked" using code; they just use their eyes.

Tutors/Graders

You can use the password to view the Sliding Tiles or the version with a scramble button

Time and Work

The following link has been updated for Fall 2023.

Finally, when you have completed the assignment, make sure you fill out the Time and Work Fall 2023 That report is required.