On this page:
1 Warmup:   filtering on Array  Lists
2 The 15 Puzzle using impworld
8.13

Recitation 10: Imperative Worlds🔗

Goals: We’re going to implement the 15 Puzzle using the Impworld library — short for imperative, in this case meaning uses mutation.

Related files:
  javalib.jar     tester.jar  

1 Warmup: filtering on ArrayLists🔗

Start a new project, called FifteenGame, and add to it the two libraries above. Add a new file to the project, and add the following include directives:
import java.util.ArrayList;
import tester.*;
import javalib.impworld.*;
import javalib.worldimages.*;
import java.awt.Color;

Define a utilities class, and in it define a method with the signature
<T> ArrayList<T> filter(ArrayList<T> arr, IPred<T> pred)
with our usual definition of the IPred<T> interface. This method should produce a new ArrayList<T> containing all the items of the given list that pass the predicate. Use a for-each loop or a counted-for loop, whichever seems easier. (Try both!)

Harder: Try to define a method with the signature
<T> void removeExcept(ArrayList<T> arr, IPred<T> pred)
that modifies the given list to remove everything that fails the predicate. The two obvious ways to do this, using for-each loops or using counted-for loops, will fail. The first one will give an exception, and the second will either (likely) compute incorrect results or crash with a different exception, depending on how you’ve implemented it. (Try removing the odd numbers from the list [1, 3, 4, 5, 7, 8], and see whether your code works properly...)

You cannot modify an ArrayList while you are iterating over it — you get garbage answers like these. You’ll need to use a temporary ArrayList, then update the original list after you’ve finished constructing the temporary...

2 The 15 Puzzle using impworld🔗

The funworld library used methods (like onTick or onKeyEvent) that returned new World objects, which made testing easier: you could compare the old World to the new one.

You could also return objects that were instances of different subclasses of World, as you might have done on Assignment 5: The Aliens Attack Again, to represent different “phases” of your gameplay. That’s not quite so easy to do with impworld...

The impworld library uses methods that return void, and you must use side effects to change the current world to update it between ticks and on key events...but you still need to figure out how to test it!

The 15 puzzle consists of four rows of four tiles, with one tile missing. Each tile has a number, and tiles can move into the hole if they are adjacent to it. Represent this information as follows:

// Represents an individual tile class Tile {
// The number on the tile. Use 0 to represent the hole int value;
...
// Draws this tile onto the background at the specified logical coordinates WorldImage drawAt(int col, int row, WorldImage background) { ... }
}
 
class FifteenGame extends World {
// represents the rows of tiles ArrayList<ArrayList<Tile>> tiles;
...
// draws the game public WorldScene makeScene() { ... }
// handles keystrokes public void onKeyEvent(String k) {
// needs to handle up, down, left, right to move the hole // extra: handle "u" to undo moves ...
}
}

To construct the tiles, you’ll need to construct 4 rows of 4 tiles each. Which loop structure (for-each or counted-for) do you think will be most appropriate here? (Hint: you’ll need two loops, one nested inside the other.)

Implement swapping two tiles by their indices — how will this have to change from the swap method we did in class?

To handle moving the tiles, you’ll need to determine whether a given move direction is currently possible: for example, if the hole is in the top-left corner, then you cannot move the hole any further up or left. It is up to you to interpret a keystroke of e.g. "left" as either “move the hole 1 cell left” or “move the tile to the right of the hole left to fill the hole”, and similarly for the other keys. You may find that one interpretation is more intuitive than the other, but it is a rather subjective choice. Be sure to document in you code which interpretation you chose!

To start the game, create a Run configuration as usual, set the main class to tester.Main, and set the arguments to ExampleFifteenGame. Then create the ExampleFifteenGame class with at least this method:

class ExampleFifteenGame {
void testGame(Tester t) {
FifteenGame g = new FifteenGame();
g.bigBang(120, 120);
}
}
Obviously, you’ll need to write tests, too!

To handle undoing moves: create another field in the FifteenGame class that is an ArrayList<String>, which you’ll update on each keystroke by adding the key that was just pressed to the front of the list. When the "u" key is pressed, the most recent move (if any) will then be stored at the front of the list — you just have to figure out how to undo it!

Reminder: read the The Image Library for documentation on how the image library works.