UW Home     CSE Home   Announcements    Message Board    Contact Info 

 
 
 

CSE143 Autumn 2004 Project #3 Part B

The 15-puzzle game

Due: Monday, December 6 at 9:00 pm. No late assignments will be accepted.

For this part of the assignment, you will find a way to automatically solve a 15-puzzle game.

As always, work with your partner.

Grading: You and your partner will receive the same scores on the programming parts of the project. Programming projects will be evaluated both for how well the code works and how well it is written and tested, each on a scale of 0 to 4. Be sure to include appropriate JavaDoc and other comments in your code, use meaningful names, indent sensibly, and so forth.

Keep track of the major design decisions, algorithms, and tests you perform to verify your code is working. You will be asked to include a summary of these in the report that you will write individually at the end of the final part of this project.

Overview and Algorithm

The goal is to automatically find and display a solution for the 15-puzzle in this assignment. The solution will be displayed by showing each legal tile swap, one by one, until the puzzle is solved as described in the last assignment.

The basic algorithm is called a "Depth-First search". The idea is that, given the current state of the board, there are at most four states you could immediately move to (one state for each of the tiles that could swap with the blank). Once you've moved to one of those states, there are four states you could move to from there. Once you've moved to one of those . . . and so on. At each new state, the question is still, "can the puzzle be solved from this state"? A depth-first search just travels from state to state, taking the first legal move it can at each state, hoping to find a solution. If it reaches one, it can return reporting success, and the algorithm (i.e., the previous trial moves) as a whole returns a successful solution.

If you're in a maze, you might look for a path out by always taking the available left turn first, and, when you hit a dead end, going back and trying a right turn at the last place you went left. But what happens if you reach a point in the maze where you end up in a loop, taking the same left turns over and over again? You need a way to keep track of where you've been before. In the case of the 15-puzzle, instead of physical location in a maze, the "places" you've been are states of the game. An illustration of this is shown here:

Note that the tree of states shown above is never really built as an explicit data structure. It's purely conceptual and is meant to help you understand how to implement the search algorithm. We recommend drawing it out on paper, but if you find yourself implementing a tree structure in Java, you need to reconsider what you're doing.

To avoid getting into an endless loop, we need to keep track of all of the game states that have been visited so far. One reasonable way to do this is to represent each game state as a String that contains the numbers of the squares in the order that they appear on the board, separated by blanks or commas. (You need some sort of separator so you don't have ambiguous numbers like "111", which could mean 11 followed by 1 or 1 followed by 11. "11,1" or "11 1" removes the ambiguity.) Once you have decided on a way to represent game states or configurations, you can use a HashSet to store the configurations that you've already explored. If a proposed move would reach a state that you've already seen, then there is no point in exploring that move further.

You know there's a solution from the state you're in if

  1. Your current state is a solution state
  2. One of the states you could move to has a solution from it
How do you know if one of the states you could move to has a solution? Call the search method recursively!

If none of the states you could move to leads to a solution, then there is no possible solution from the current state and you should return reporting failure from that state. In that case, the search should continue by trying a different move from the previous state, if there is one. If you call the search method recursively and it reports success, then you've found a sequence of moves that solves the puzzle.

Implementation Requirements

  • All the requirements from part A, though you can get rid of the beep or text or other indication of an illegal move if you no longer allow the user to enter arbitrary moves.
  • Solve an arbitrary instance of the 15-puzzle. This instance can either be one generated when the user hits the "new game" button, or if the user starts a game and makes a few moves.
  • If it's not solvable, tell the user so in some way.
  • If it is solvable, show an animation of the process of solving it, and indicate how many moves it takes. Remember to make the animation visible to the user (i.e. your program should wait a little bit between each tile animation).
  • Continue to use the MVC model to separate the details of the user interface from the game engine, which decides if a move is legal, updates the state of the game, and checks for whether the moves have led to a solution.

Suggestions

Though not required, we suggest that you create a method which produces a random, but solvable puzzle. If you just place tiles randomly, in some circumstances the puzzle will not be solvable. Instead, it is possible to start from a solution state and make random legal moves to scramble the board, then test your algorithm to see if it finds the solution. In this way, you know that the board is solvable and that your algorithm should be able to solve it. More to the point, you can set the number of random moves that set up the initial state very low, and know that the board is solvable in that many moves. In combination with the bounded recursion described later, this can make testing much faster. However, you should test that your program works with random initial configurations, and that it doesn't choke on unsolvable games.

To show the solution in action, come up with a way to record the steps on the winning path. That is, once you've found a solution, record the move you just took to get there on a list of moves to get to a solution. This can be done either by updating an instance variable, or by returning a list of moves on the solution path.

Stack overflow error

Java stores information about what methods you've called, and which methods are still waiting to return, in a structure called the stack. Each call produces a stack frame. In recursive computing, every time a method calls itself, the frame for the first call is still sitting there on the stack waiting to return. If there are too many recursive calls without reaching a solution, too many stack frames build up, and the stack overflows, producing an exception. In the case of the 15-puzzle, there can be very many moves from the start to the finish, and it's possible in normal operation to overflow the stack. To prevent this, we suggest that you add a parameter to the recursive method to limit the number of recursive calls in the search (i.e., give up and report failure after some number of recursive attempts to find a solution, then back up and try other possibilities). Adding a limit like this is called bounding the recursion.

Extra credit ideas

  • There are ways to improve the algorithm to increase the likelihood of finding a solution quickly. One possibility is to compute a "score" for each board configuration that is a guess of how far it is from a solution. This could be some function of how far the different tiles need to be moved to get in their final location (number of rows + number of columns). Experiment with different scoring functions and strategies for picking the next move to try based on those scores to see if you can find a solution more quickly.
  • The requirements specify that you need to show an animation of the puzzle being solved if your search is able to find a solution. You might want to add an optional (fast) animation showing the algorithm's attempts to find a solution.
  • You might also add some pause/resume/step controls to your user interface to run the search or the final animation a step at a time to help you visualize or debug the search.
  • Display a picture instead of numbers so that when the tiles are in order a nice picture will be displayed.
  • Allow the user to select an image to be used.
  • Anything else interesting your can come up with.

What to Turn In

Use this online turnin form to turn in the files that make up your program and tests.