Lab 9: Imperative Worlds
Goals: We’re going to implement the 15 Puzzle
using the Impworld library —
You should work with your assignment partner for this lab. You will need to know how to work with this library for the next assignment.
Problem 1: Imperative Worlds
9.1 Warmup: filtering on ArrayLists
import java.util.ArrayList; import tester.*; import javalib.impworld.*; import javalib.worldimages.*; import java.awt.Color;
<T> ArrayList<T> filter(ArrayList<T> arr, Predicate<T> pred)
<T> void removeExcept(ArrayList<T> arr, Predicate<T> pred)
You cannot modify an ArrayList while you are iterating over it —
9.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: A Game, to represent different “phases” of your gameplay. That’s not quite so easy to do with impworld...
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 —
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); } }
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 —
Reminder: read the The Image Library for documentation on how the image library works.