Comp 116 Programming Assignment 08:
A-maze-ing Race

Due 11:59pm Friday, December 07 2018

This is an individual assignment. You must complete this lab on your own, although you may discuss lab concepts with other students. Please keep the Academic Integrity Policy in mind---do not show your code to anyone outside of the professors and ninjas, and do not look at anyone else's code for this lab. If you need help, please post a question on Piazza, or contact the professor.

The main goal of this lab are to use data structures to implement algorithms. Concepts you will be familiar with after this lab include:

Click here to get all the files containing the starter code for this project.

Update: copy-paste the code from the file test.cpp and add it to your project. In CLion, replace main.cpp by test.cpp in CMakeLists.txt and compile and run the project to run the tests. Replace test.cpp back to main.cpp to change it back to the original project.


Main Problem: The A-Maze-ing Race

In this lab, you will implement two search algorithms—depth-first search and breadth-first search—for finding a path through a maze.

Search Algorithms

In class last week, you saw the following search algorithm:
Algorithm MazeSearch:
   mark Start as visited // add the start to the unordered_map as its own previous
   add Start to DataStructure // either stack (for DFS) or queue (for BFS) 
   while (DataStructure is not empty):
      Current = remove location from DataStructure
      if (Current == Destination): // found a path!
         build the Path found by tracing back previous pointers in the unordered_map
         return Path
      else
         get the list of neighbors of current 
         for each Neighbor of Current:
         if Neighbor not visited: // you can tell by checking if Neighbor can be found in unordered_map
            record that Neighbor has Current as its previous
            add Neighbor to DataStructure
   return no Path exists
The way this algorithm traverses through locations depends on which data structure is used to store visited locations. When we use a stack, the algorithm is called Depth-First Search (DFS). When a queue is used, it is commonly called Breadth-First Search (BFS).

In this lab you will implement both DFS and BFS, use them to find paths through a 2-D maze, and explore how the two search algorithms produce different paths.

Starting Code

Below is an overview of the files required for submitting this lab. Those highlighted in blue will require implementation on your part. Those highlighted in black are complete and should not be modified except for comments.



Programming Requirements

For each .cpp file, read the corresponding .h file very carefully. The comments that precede each function and method give you precise information about what the function or method is supposed to do.

The Maze Layout

Each maze is a rectangular grid. Each space in the grid is assumed to be connected to the adjacent spaces (left, right, up, and down, but not the diagonally adjacent spaces. The starting point of each maze is the upper left corner; the exit is in the bottom right corner.

The layout of a particular maze will be stored in a file with the extension .map; you can find several examples in your labyrinths.zip. A map file contains the following information:

For instance, the following is a valid map:
5 3
.#.#.
.#...
...#.
We have given you the code which loads files in this format; this explanation is just for reference.

The Position Class

We will represent a maze as a two-dimensional grid of pointers to Position objects. Each Position object contains relevant information for one place in the maze: the (X,Y) coordinates, where (0,0) is the upper-left and X increases as you move right, Y increases as you move down) and whether that position is a wall. The constructor of the Position class should take the X and Y coordinate values and initialize the other fields to suitable default values.

The 2-D grid that you create for your maze should take the form of a data member positions of type Position***. This is an array of arrays of Position pointers. This way, you can write e.g. positions[0][0] to get the Position* which points to the Position object in the upper-left corner. You'll have to use nested for-loops to initialize positions correctly.

The Maze Class

A maze object represents our knowledge of a particular maze. We can use this information to find a path through the maze using various search algorithms. You will write two methods—solveDepthFirstSearch and solveBreadthFirstSearch—which are very similar. These methods will return a vector<Position*>: a vector of Position pointers, which constitute a correct path through the maze from the start to the finish. If no such path exists, the method should return an empty vector.

You have been given the declaration of the maze class. You only need to implement each of the methods. The real magic of making the program work happens in these methods, which find a path through the maze using the search algorithm given above.

In the maze solver functions, you will need a map to keep track of which Positions have been visited and from which other Position they were visited. I recommend that you use an unordered_map<string,Position*> that maps strings to Position pointers. The reason why the keys in this map are strings is complicated, but long story short, it will work better this way. To map a Position to a string, you will need to program a to_string function in the Position object that will return an unambiguous string representation of the coordinates of the Position object.

Testing the Maze class

Make sure to run tests on your code as you develop. Definitely test your code once you've finished the Maze class! It will be easier to find bugs in your Maze implementation by direct testing than it will be by trying to find them by hand running the maze program.

Some of the map files in labyrinths.zip have multiple possible solutions. As a result, your output may differ from the provided solutions depending on how you explore the maze. To get the same results as the provided solutions, you should explore neighboring spaces in the following order: up, left, right, and then down.

Implementing Main

The implementation of main should be less work than the Maze class. It just involves assembling the pieces you've constructed above. The loadMap and renderAnswer functions have been written for you. loadMap will read a map file and return a Maze* if loading was successful. Otherwise, it will throw a runtime_error. renderAnswer should produce a string containing a solution, which looks like the map from the input file, but contains @ characters on the path you found. For example, here is a possible execution of the program:

$ ./maze
Welcome to CS35 lab 05: The A-Maze-ing Race.
where is your maze file? cycle.map
Which search algorithm to use (BFS or DFS)? DFS
@@###
#@@@#
#.#@#
#..@@
####@

Invalid Inputs: Your maze program should gracefully handle the following cases of invalid input:

Memory Management

Your program should run without any memory errors. It's OK if your destructor(s) do not clean everything up, but you should at least make a reasonable attempt (you should not leak most of the memory you allocated), and in any case, you should avoid dereferencing uninitialized memory.
Commenting and Coding Style Requirements
For this and future labs, graders will assign minor penalties for poor commenting and coding style. Here are some style tips for this lab:
Summary

To summarize the requirements of this lab:

Submit

Submit a .zip file containing the entire project folder for the programming assignment using the submission link on onCourse.

Acknowledgement

This lab write up is based off of the Labyrinth lab developed by the Computer Science Department of Swarthmore College.