CSCI 151 Lab 3

Solving a maze with stacks and queues

This is due at 6PM on Tuesday, March 22

In this lab you will implement an algorithm for finding your way through a maze. The algorithm needs a structure for temporary storage and you will implement it twice -- once with stacks and once with queues. For even more variety you will use ArrayLists to implement stacks and a dynamic node structure to implement queues.

This lab is a little larger than past labs; it has many different pieces that all need to work together. Most pieces are small, but it may take some additional patience on your part.

You may work with a partner on this assignment, and on all subsequent labs,  both during and after the lab session.

Getting Started

I have supplied starter code, including a folder of mazes here: Lab3.zip. You should expand this into your CS151 folder. It will create a new, partially implemented Eclipse project named "Lab3". Change the name of the project folder to Lab3<your last name> Inside this folder you should see a folder named "mazes" and a "src" folder that contains a package "lab3" with 10 .java files. There are numerous errors in those files due to unimplemented code, but we will fix those as we go along. Open a new project in Eclipse using this folder as the project folder.

Here are the 10 files you should find in this project:

Your work in this lab will be:

  1. Implement (and test) stacks and queues.
  2. Complete the MazeSolver abstract class.
  3. Implement the MazeSolverQueue class as a variation of MazeSolverStack.'
  4. Run the MazeApp programs to test your solution.

Part 1 - Stacks and Queues

We've been talking about stacks and queues in class, and now it is your time to put that theory to good use. Write two classes MyStack<T> and MyQueue<T> that implement the supplied interfaces StackADT and QueueADT, respectively.

MyStack
An implementation of the provided StackADT interface that is capable of storing an arbitrarily large amount of data. Use ArrayList storage (so a Push for the stack becomes an add for the ArrayList, and a pop becomes a remove).
MyQueue
An implementation of the provided QueueADT interface that is capable of storing an arbitrarily large amount of data. (This should use a dynamic node-and-links implementation.)

Methods are specified in the interface files supplied in the starting point code. Be sure to throw the correct exceptions. For the linked Queue implementation, you should make a nested class to handle the nodes. You don't need to create a separate file to do this, instead just have the class description in the same file as MyQueue but without the "public" modifier.

Before continuing, you should add JUnit tests for MyStack and MyQueue that performs testing on your data structures.

Part 2 - Representing the Maze

The Maze implementation is complete but you need to understand it to write the MazeSolver. We represent a maze as a rectangular array of Squares, each Square being empty, a wall, the start, or the exit.

Below is a graphical representation of the maze found in the file maze-2. The green box represents the start, the red box the exit, and the black squares the walls.


We describe 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

Make sure you understand how this array of numbers corresponds to the pictured maze.

The Square Class

One of the Lab3 files is class Square, which represents one entry in the maze. A Square has an instance variable that represents its type (space, wall, start, or exit). Class Square defines constants that represent the four types of Squares. You should always refer to them by name (for example, inside the Square class use WALL; outside this class use Square.WALL), rather than specific values (such as 1). A leprechaun should be able to modify all of your constant values (perhaps changing WALL to 101) without affecting your program at all. One of the major goals of this course is learning to make your programs leprechaun-proof.

A Square needs to know where it is on the grid so it can find its neighbors. Each Square has private variables of type int named row and col to represent it's location within the maze.

Class Square also has the following methods:

public int getRow()
public int getCol()
public int getType()
Accessor methods to get the values of the various class variables. Don't just make all the variables public.
public void setPrevious(Squapre sq)< and public Square getPrevious()
If this square is on the path from the start to the exit these indicate the previous square on the path.
public void setOnPath() and public boolean IsOnPath()
These allow you to specify that the current node is on the path from the start to the exit
public void mark() and public boolean isMarked()
These allow you to mark any square you have visited so you can avoid re-visiting it in the future

The Maze Class

To save you some time, we have supplied a class Maze to represent the logical layout of a maze. This contains a class variable maze that is a 2D array of Squares. It also has integer variables numRow and numCols that give the size of this array. Initially, this array will be empty and numRows and numCols will be set to 0. We will use a loadMaze() method to populate it.

Class Maze includes the following methods:

public Maze()
A constructor that takes no arguments and just sets numRows and numCols to 0. The real work will be done with the loadMaze() method described next.
boolean loadMaze(String fname)
loads the maze that is contained in the file named fname. The format of the file is described above.
If a problem is encountered while reading in the file, loadMaze() returns false to indicate that it failed. Returning true indicates that you have now loaded the file successfully.
loadMaze() catches the exception that is raised if the user specifies an incorrect file, prints out an appropriate error message when this occurs and returns false.
ArrayList<Square> getNeighbors(Square sq) returns an ArrayList of the Square neighbors of sq. The list contains all neighbors; you will have to filter out the ones that are walls on your own. There will be at most four of these (to the North, East, South, and West) and they are listed in that order.If the square is on a border, getNeighbors() skips over the directions that are out of bounds of the maze.
Square getStart()
Square getExit()
Accessor methods that return the saved start/finish locations.

Class Maze is complete; you shouldn't need to change this file. Before you continue, you should test that the Maze class integrates correctly with your Square class. You can do this by creating a JUnit test suite. Among other things, this test should load a maze from one of the supplied files, get the neighbours of some specific square (the start square, for example), and assert that (1) there are the correct number of neighbours, and (2) the neighbours are in the correct locations. You probably should do this for the corners and border cases, at least. There should also be tests to print out the maze, and to confirm your getStart() and getFinish() methods return the correct squares. Note that you can refer to the test mazes from inside Eclipe through names such as "mazes/maze-2." There is no ".txt" extension in these file names.

 

Part 3 - Solving the Maze

Now that you have a maze and working stack and queue data structures, we can use them to solve mazes. You will next implement the application portion of this lab, writing the MazeSolver, MazeSolverStack, and MazeSolverQueue classes which will find the solution to any maze file. You should pay attention to the structure here. We are producing two programs that solve mazes: one uses stacks and the other uses queues. We will make an abstract class MazeSolver with all of the behavior that is the same for both versions, then concrete subclasses MazeSolverStack and MazeSolverQueue with the parts that are unique to just one of the versions.

Our maze solving algorithm goes like this: begin at the start location, and trace along all possible paths to (eventually) visit every reachable square. If at some point you visit the finish Square, it was reachable. If you run out of squares to check, it isn't reachable.

Boiling this down into pseudocode, we have the following:

At the start

  1. Create an (empty) worklist (stack/queue) of locations to explore.
  2. Add the start location to it.

Each step thereafter

  1. Is the worklist empty? If so, the exit is unreachable; terminate the algorithm.
  2. Otherwise, grab the "next" location to explore from the worklist.
  3. Does the location correspond to the exit square? If so, the finish was reachable; terminate the algorithm and output the path you found.
  4. Otherwise, it is a reachable non-finish location that we haven't explored yet. So, explore it as follows:
    • compute all the adjacent locations that are inside the maze and aren't walls, (the Maze class has a method getNeighbors( ) for this) and
    • add them to the worklist for later exploration provided they have not previously been added to the worklist. Each time we add a Square to the worklist we will mark it (by setting a boolean flag in the Square). When we come across a Square that has been marked there is no reason to add it to the worklist a second time so we just ignore it. This prevents us from getting caught in a loop between two adjacent open Squares.

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

Create an abstract class MazeSolver that will implement 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. The MazeSolver class should have a non-public class member of type Maze, and should have the following methods:

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
MazeSolver(Maze maze)
A (non-abstract) constructor that takes a Maze as a parameter and stores it in a protected variable that the subclasses can access. Don't try to do anything else here. The MazeSolver constructor will be run before the worklist is created, so it can't refer to any of the worklist methods.
boolean isFinished()
This returns the value of a variable finished, which your step( ) method should set to true if either the worklist is empty (so there is no solution) or the path is found.
boolean pathFound()
This returns a variable pathFound, which your step( ) method should set to true if you reach the exit square.
void step()
This performs one iteration of the algorithm above (i.e., steps 1 through 4). Note that this is not an abstract method, but you should implement it 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. 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).
To find the path from the entrance to the exit of the maze it will help to have each Square keep track of which Square added it to the worklist (i.e., "Which Square was being explored when this Square was added to the worklist?"). The Square class has a method setPrevious(Square sq) that sets the currrent square's previous square to sq.

String getPath()
This returns an ArrayList of squares from the start square to the exit. From the exit it is easy to follow the previous links back to the start, but that gives the path in the reverse order. Here is a trick to reverse a list. As you follow the previous links, push each of the Squares you visit onto a stack. When you get to the start node (its previous link will be null), go through a loop that pops the stack, adding each Square onto a list. When the stack is empty, return the list. The second half of this algorithm looks like this:
      ArrayList<Square>path = new ArrayList<Square>( );
		while (!stack.isEmpty()) {
			Square t = stack.pop();
			path.add(t);
		}
		return path;

The MazeSolverStack and MazeSolverQueue Classes

Now we just need to create the two implementations of the MazeSolver class. MazeSolverStack is complete. Observe the way it creates a MyStack object and uses it to implement the abstract worklist methods from the MazeSolver class. You need to do something similar for MazeSolverQueue.

You should now be able to run the MazeApp application and watch your algorithms solve mazes.

 

Handing in your work

You should hand in a zipped copy of the project folder for this lab. That should contain all of the files needed to allow us to run MazeSolverStack and MazeSolverQueue. Remember that the name of everyone who worked on the program should be in a comment on every code file. If you adhered to the honor code in this assignment, add a README text file to your project folder with the text

I have adhered to the Honor Code in this assignment.

You want to keep the graders happy. If there are portions of these programs that you know are not working correctly, it would be helful if you included information about what does and what doesn't work in your README file.

Go to your csci151 folder and make a compressed version of your project folder. On a Mac select this folder and select Compress from the File menu. On Windows right-click on the project folder and select Send to -> Compressed (zipped) folder. In either case you should get a zip file. Open the Blackboard course site.. Click onthe Course Material link, then on the Lab 3 link. In the Assignment Submission section click on the Browse My Computer button. this will bring up the usual navigation controls that should allow you to select your zipped project file for this assignment. If you worked with a partner only one of you should hand in your work.VI Powered