In this prelab, you will familiarize yourself with some of the design and implementation issues in the upcoming lab 3. Please write or type up your solutions, and hand in a paper copy before 10am on Monday. Remember, late prelabs receive zero credit!
I'm sure many of you have at one point heard the old Greek myth of Theseus and the Minotaur. The important plot points are these:
A much more humorous telling of the story can be found here.
Lab 3 will not only give you the skillz to escape any escapable maze without the need for string, it will give you *two* different ways of doing so! If only Ariadne or Theseus had taken CS 151...
First let's talk about your basic maze. It has walls and pathways, and it has one (or more) starting point(s) (where the minotaur resides) and one (or more) exit point(s) (where one can escape). (To keep things simple, let's just assume it has no more than one of each.) Furthermore, one wall is just like another, and any open space (not including start and finish) is also identical. So, we can think of a maze as being made up of individual squares, each square either empty, a wall, the start, or the exit.
Below is a visual representation of a maze. The green box represents the start, the red box the exit, and the black squares the walls.
We can represent such a maze with a text file of the following format. The first line of the file contains two integers. The first indicates the number of rows (R), the second, the number of columns (C).
The rest of the file will be R rows of C integers. The value of the integers will be as follows:
0 - an empty space 1 - a wall 2 - the start 3 - the exit
In terms of coordinates, consider the upper left corner to be position [0,0] and the lower right to be [R-1,C-1].
For example, this is the text version of the maze above (start is at [6,4] and exit at [6,11]).
7 13 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 1 1 1 1 1 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 1 0 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 0 1 0 1 0 1 0 0 0 0 0 2 0 0 1 0 0 1 3 0
_ - empty space # - wall S - Start E - ExitWrite a toString method that returns the appropriate character (in this case, write actual code rather than pseudocode).
Once we have Squares, we should be able to set up the maze itself.
The maze above would be returned by toString as follows.
_ _ _ _ _ _ # _ _ _ _ _ _ # # _ # # # # # # # _ # _ _ # _ _ _ _ _ _ _ _ _ # _ _ # _ # # _ _ # # # _ # _ _ _ _ # _ _ _ _ _ # _ # _ _ # # # # # _ # _ # _ # _ _ _ _ _ S _ _ # _ _ # E _
In this section of the lab you will implement your own stack and queue. For now, we'll just get some practice with them.
Now that you have a maze, and you have stacks and queues, we can get down to business! The real meat of this lab will be to write a MazeSolver class (and associated classes), which will bundle up the functionality of determining whether a maze has a solution---that is, whether you can get from the start to the finish (without jumping over any walls). The algorithm one usually follows goes something like this: start at the start location and trace along all possible paths to (eventually) all reachable open squares. If at some point you run across the finish, it was reachable. If not, it wasn't.
Boiling this down into pseudocode, we have the following:
At the start
Each step thereafter
Note that this pseudocode is entirely agnostic as to what kind of worklist you use (namely, a stack or a queue). You'll need to pick one when you create the worklist, but subsequently everything should work abstractly in terms of the worklist operations.
The coordinates are in (row, col) order. Be careful to stick to that order
|at the start||after step 1||after step 2||after step 3||after step 4||after step 5||after step 6||after step 7||after step 8||worklist as a stack||
|newly explored square||N/A||(6,4)||(6,3)||
|newly marked square(s)||N/A||(6,3)
|worklist as a queue||newly explored square||newly marked square(s)|
|at the start||(6,4)||N/A||N/A|
|after step 1||(6,5) (6,3)||(6,4)||(6,5) (6,3)|
|after step 2||(6,3) (6,6)||(6,5)||(6,6)|
|after step 3|
|after step 4|
|after step 5||(6,1)|
|after step 6|
|after step 7|
|after step 8||(6,0) (3,6) (4,7) (4,5)|
The next step in the lab will be to create an abstract class MazeSolver that implements the above algorithm, with a general worklist. Its abstract methods will be implemented differently depending on whether the worklist is a stack or a queue, but the main algorithm will remain the same. The MazeSolver class will have a private class member of type Maze, and the following methods:
Now we will have to create two different implementations of the MazeSolver class---a MazeSolverStack and a MazeSolverQueue, both extending the MazeSolver class. These will not be abstract classes, and so you will have to implement the MazeSolver's abstract methods. Each class will contain as a class variable a worklist of the appropriate type (so, MazeSolverStack should have a class member of type MyStack<Square> and MazeSolverQueue should have one of type MyQueue<Square>). All you'll have to do to implement the abstract methods is perform the appropriate operations on the stack or queue class member.
In order to output the solution to the maze when you find the exit, you will need to keep track of the path that was followed in your algorithm. This seems to be a difficult proposition; however, there is a simple technique to solve the problem.
In order to keep from wandering in a circle, you should avoid exploring the same location twice. You only ever explore locations that are placed on your worklist, so you can guarantee that each location is explored at most once by making sure that each location goes on the worklist at most once. Imagine you were to "mark" a square by placing an arrow in it that points back to the square from which you added it to the worklist. Now, when you are at the exit, you can just follow the arrows back to the start.
Of course, following the arrows gives you the path in reverse order. If only you had a way to keep track of items such that the Last item In was the First item Out, then you could read all the arrows in one pass and write them back out in the correct order...
Finally, the last part of the lab will be to add in an animated visual component, so that you can watch your maze solvers do their thing.
This part of the lab is going to have some GUI action. If you're interested in reading a bit about how GUIs work, I recommend that you take a gander at Appendix B of Weiss.