Prelab 3

Trapped in a Maze!
Due by 10am, Monday 24 Sep 2012

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!


Theseus and the Minotaur

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...



Part 1 - Representing the Maze

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

The Square Class

  1. Design a Square class that represents a single square in the maze. In particular, what class members should you have (including type and visibility)? What methods should the class have? Is there anything else that should be included (e.g., class constants)?



  2. Suppose you want to print out a representation of a Square using the following substitutions:
        _ - empty space
        # - wall
        S - Start
        E - Exit
    
    Write a toString method that returns the appropriate character (in this case, write actual code rather than pseudocode).




The Maze Class

Once we have Squares, we should be able to set up the maze itself.

  1. What class variables (including type, visibility, static or not) and what methods would be useful for a Maze class? Give this some thought, but don't fret too much about being 100% complete.


The maze above would be returned by toString as follows.

    _ _ _ _ _ _ # _ _ _ _ _ _ 
    # # _ # # # # # # # _ # _ 
    _ # _ _ _ _ _ _ _ _ _ # _ 
    _ # _ # # _ _ # # # _ # _ 
    _ _ _ # _ _ _ _ _ # _ # _ 
    _ # # # # # _ # _ # _ # _ 
    _ _ _ _ S _ _ # _ _ # E _ 

Part 2 - Stacks and Queues

In this section of the lab you will implement your own stack and queue. For now, we'll just get some practice with them.

  1. (Weiss) Draw the stack and queue data structures (for the array implementations) for each step in the following sequence: add(1), add(2), remove, add(3), add(4), remove, remove, add(5). Assume both structures were implemented using an array of length 3 for storage and are initially empty.

     Queue  add(1)  
     add(2)  
     remove  
     add(3)  
     add(4)  
     remove  
     remove  
     add(5)  




     Stack
    add(1) add(2) remove add(3) add(4) remove remove add(5)
     








     












Part 3 - Solving the Maze

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.

  1. Run this algorithm on the example given at the top of this prelab, where your worklist is a stack. Run it for the first 8 steps; show your worklist after each step, as well as list (at each step) which squares have been explored, and which have been marked (as in, which squares have at one point in the algorithm been added to the worklist). To make Alexa's grading life easier, push adjacent squares onto the stack in the clockwise order given, namely, up, right, down, then left. Some of the entries are already filled in, so that you can tell if you are on the right track.

    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  






    (6,4)
     





    (6,3)
    (6,5)
     





    (6,2)
    (6,5)
             




    (4,2)
    (3,0)
    (6,5)
    newly explored square N/A (6,4) (6,3)    



           
    newly marked square(s) N/A (6,3)
    (6,5)
    (6,2)    



    (5,0)      


  2. Now run the algorithm on the same example, where your worklist is a queue. Show the same work. Did things work differently? Do you think one worklist is better than the other (why, or why not?)?

      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 MazeSolver Abstract Class

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:

abstract void makeEmpty()
create an empty worklist
abstract boolean isEmpty()
return true if the worklist is empty
abstract void add(Square sq)
add the given Square to the worklist
abstract Square next()
return the "next" item from the worklist
Square step()
perform one iteration of the algorithm above and return the Square that was just explored (and null if no such Square exists). Note that this is not an abstract method, that is, it must be implemented in the MazeSolver class by calling the abstract methods listed above.
In order to keep track of which squares have previously been added to the worklist, you will "mark" each square that you place in the worklist when you place it in the worklist. Then, before you add a square to the worklist, you should first check that it is not marked (and if it is, refrain from adding it).
  1. What should the MazeSolver constructor do? (Hint: look at the algorithm, and use the abstract methods above.) Think about what information it needs to do this.



The MazeSolverStack and MazeSolverQueue Classes

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.

  1. What are the benefits of having the MazeSolver as an abstract class? Why not just make two different versions (one for the stack and one for the queue). Are we really saving anything by using the single superclass with two subclasses?





  2. Suppose the MazeSolverStack class' stack is called stack. What will the void add(Square sq) method look like? (Hint: it's really quite short.)





Tracing the path

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...

  1. Write pseudocode for a method retracePath(Square sq) that prints out the path you found from the start to the Square sq.






Part 4 - Animatronics!

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.