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.
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.
Final Checklist¶
- Your name in all files that you wrote
- The
'use strict';
directive in all.js
files - All functions and top-level variables properly documented
- All code neatly laid out, and lines not too long (80 characters is a good limit)
- Comments as needed
- Folder and files properly named
- Submit to Gradescope
Tutors/Graders¶
You can use the password to view the Sliding Tiles or the version with a scramble button
Time and Work
Finally, when you have completed the assignment, make sure you fill out the Time and Work report. That report is required.