Project 1: Two-dimensional Maze Solver
Due Friday, 2019/02/01, 11:59:59PM.
Write a program that searches through a maze to find a path from the beginning to the end. The maze is a two-dimensional grid of locations where you may visit, numbered by row and column. We could draw out a maze like this:
0 1 2 3 0 X E X X 1 X X 2 X X S X 3 X X X(You can see how this maze would be given as input in sample-1-1.)
A maze is described by 3 things:
- the valid locations (places where it's okay to walk, indicated as an X in the figure above),
- the start location (indicated by S), which is one of the valid locations, and
- the end location (indicated by E), which is also one of the valid locations.
Though there may be many ways to solve a particular maze, for this problem we prefer to find a unique solution. In particular, the one which chooses the preferred directions as early as possible. That is, go as far as you can in the most preferred direction, keeping track of where you've been (using a stack). Then if you reach a point where you cannot proceed, back up (by popping the stack) and try a different direction. Again, go in the order of preferred directions. So if you can go right, do that; otherwise go down, then left, then up. This is commonly known as a depth-first search strategy.
Continuing the example above, even though there are several ways to solve the maze above, the one that our preferences give is shown here using the letters 'a' through 'f':
0 1 2 3 0 f E X X 1 e X 2 d X S X 3 c b aThis is preferred because it is the path which goes right as early as possible, then down as early as possible, then left as early as possible, then up.
Input specificationInput begins with a line containing a single integer 1 ≤ N ≤ 10,000, which is the number of valid locations in the maze. This is followed by N lines, each of which contains two integers r and c indicating the row and column of a valid location. All N locations are unique. Following the last valid location are two more lines containing locations that indicate the start and end locations of the maze. Each of these is a valid location and formatted in the same way as valid locations. The limits on each location are -10,000 ≤ r, c ≤ 10,000.
Output specificationIf a solution can be found, output Solution found followed by the sequence of valid locations visited from start to end, one per line. (If multiple solutions are possible, report only the one that best matches our preference of right, down, left, up as early as possible.) If no solution is possible, output No solution.
Details on the solution strategy
Your program should be iterative, not recursive. Use a stack to simulate recursion. The stack forms a natural data structure for storing what has been visited in the past, and the stack makes it easy to go back to a previous decision and try a different location. This type of search is common in computer science, and is called depth-first search.
The driver contains the logic to solve the puzzle. It should start at the starting location, and is only allowed to visit locations that are valid (according to the input). At each stage, the solver should either move forward in some direction (right, down, left, or up) and push that new location on the stack, or it should back up one location by popping the current location off the stack. This continues until the solver has either reached the end location or it has become empty (indicating that there is no path from the start to the end).
The stack should keep track of the locations that have been visited from the start until the current location (the current location should be the one on the top of the stack). Each step should look at the current location and use that location to produce a "neighbor" location. A neighbor location is a location (possibly invalid) which is one step away from the original location. For example, 2 2 is a neighbor of 1 2. If the neighbor is both a valid location, and is not currently on the path that has been traversed (on the stack), then it is added to the stack for further exploration. If all the neighbors of the current location (on the top of the stack) have been visited, then that location is removed from the stack (this is called backtracking).
The Location class contains both the coordinates of the location and the functionality for an iterator. A Location object is able to iterate over all of its neighbor locations. Please read the comments in the location-proj1.h file for more details of how the iteration should work. Because the stack contains Location objects, and each Location object is an iterator over its own neighbors, each Location object on the stack knows which of its neighbors is next.
Here is a pattern you should consider using in your driver when searching the maze. The top of the stack represents the current location. To get its next neighbor, you need to call iterationCurrent() on that object, and then call iterationAdvance() on that object. However, the top of the stack is required to be constant (by the stack) and iterationAdvance() is a non-const method (it should modify its own nextDirection -- only). Therefore to get the next neighbor and also update the nextDirection of top of the stack, you should do the following:
- make a modifiable copy (call it m) of the top of the stack
- call m.iterationCurrent() and store its result in another Location (this is the neighbor)
- pop the top of the stack
- call m.iterationAdvance() (changing the nextDirection of m)
- push m back on the stack (now the top of the stack has the same current location, but pointing in another direction)
Note that the program should report only the first solution it finds, not every solution there may be. The solution the program finds is heavily dependent upon whether the Location class iterates properly over its neighbors.
Sample input files
Use these sample input files to compare the output of your solution to the correct output. You can get the correct output by running the input through one of the sample executables. Download the files (don't cut and paste), use file redirection, etc.
Here are sample executables for you which correctly solve this problem. When you design test cases, you can judge your output against the output from the correct solution. These are the same correct solution compiled for various operating systems:
If you give a command-line argument to these executables, they will print extra information about how it is solving the problem. For example, this will execute the program like normal, redirecting input from a file called my_input.txt:
% project1_solution_dos.exe < my_input.txt
But here is the mode of operation that will cause the program to print out what it is doing in more detail:
% project1_solution_dos.exe printMore < my_input.txtThe command-line argument doesn't have to be the word "printMore," it can be anything.
You must use the .h files provided here:
Do not alter these files; use them as I have provided them. The files have comments describing the purpose of each class and member function. We'll also talk about these in class.
Project testing and milestones
This is a larger project, so it's best to write your code in stages and test each stage as you go. This is a generally good strategy for solving problems: break them down into smaller parts, and solve each part, testing each solution as you go. This means testing each method individually with driver programs written just for testing.
Since this is a large project, it helps to have a plan of attack. The following milestones should be turned in via the upload site by noon on the due date. I will look at your code and send you an email with notes. For each milestone you should create and upload a test driver that tests what you have written; in general this should be submitted as the "driver-projX.cpp" file for project numbered X (even though the test driver is not what you will ultimately use for the project). You need not upload things that are not part of the current milestone. Milestones are graded.
Milestones represent the minimum progress required to finishing the project on time. However, if at each milestone you completely test and debug your code, you should have no problems finishing the project in time.
|1.||Thursday, January 24 (at noon)||WRITE and TEST all the methods of the Location class. Also develop 10 test mazes that you will use later to test your project. Make them high quality! Include these in your milestone (in the comments).|
|2.||Tuesday, January 29 (at noon)||WRITE and TEST all the Stack and Maze methods.|
|3.||Thursday, January 31 (at noon)||WRITE and TEST the driver. Use your own test inputs and make sure your program produces correct outputs.|
Remember that one of the most important concepts in this exercise is data abstraction. Note, for instance, that the public interface for the Location class does not mention rows/columns, or any of the data members required for iteration. The Maze class also makes no mention of rows/columns.
There is no limit on the number of valid Locations. Note that if you use a very large number of valid Locations, your program may run for a very, very long time. The upload site will test with reasonably-sized inputs that your program should be able to solve quickly.