In this project, you will write a program to open a text file containing a description of a maze, use a recursive algorithm to have Harry find his way through the maze to the Triwizard Cup, and print out the solution at the end, along with some other statistics.
Here is an example file:
.# .C H#This file describes a 3-row, 2-column maze, though a maze may have any number of rows and any number of columns. The hash marks (#) specify hedges in the maze that Harry cannot go through; there are also implicit hedges surrounding the boundary of the maze (so Harry cannot leave the boundaries of the maze). Harry's starting position in the maze is specified by the letter H, and the Triwizard Cup that Harry is seeking is marked by the letter C. Periods specify open sections of the maze where Harry is allowed to walk as he searches for the cup.
I suggest you read the lines of a maze file into a vector of strings, i.e., a vector<string>. If you do this, you can treat this vector as a 2-d grid of characters. In other words, say you read the lines of the file into a vector<string> called maze. You can then use maze[row][col] to access any square of the grid. For instance, in the maze above, maze[2][0] = 'H' and maze[1][1] = 'C'. Note that we use single quotes because R and C are individual characters.
To solve this problem, Harry asks himself, "Am I currently standing where the cup is located?" (the base case). If so, then Harry is done (success!). If Harry is not standing where the cup is, Harry will try to take one step north, and try to solve the maze from that new location. If that didn't work, Harry will try to take one step south, and try to solve the maze recursively from that location. He will do the same thing for east and west. If one of those four recursive cases succeeds in solving the maze, then Harry is done (also success!). If none of the recursive cases solves the maze, then Harry is done (but with failure).
[ See an example of how the recursive formulation works on the sample maze. ]
To remedy this, let's change our recursive formulation to return strings rather than success or failure. Each string will represent the path through the maze from the starting location to the cup: we'll use "N", "S", "E", and "W" for the four cardinal directions, and "C" for the cup. If a path can't be found, we'll use "X" for failure.
[ See an example of how the recursive formulation works on the sample maze. ]
Your program will implement the second recursive formulation, so that we can see the path at the end.
string solve(maze, row, col, numcalls): // maze represents the maze we are trying to solve // row and col represent Harry's current position as he moves // through the maze, searching for the cup. // numcalls stores the number of calls to solve we have made so far. // The return value (string) gives the path from (row, col) to the cup, assuming // the cup can be reached from Update numcalls. Print the current (row, col). // This printing step is not needed in the algorithm, but I want you to include it // so I can check that your recursion is correct. Is our current (row, col) where the cup is? If yes, return the string "C" (indicating the cup is here). // If no, keep going below. Drop a breadcrumb at our current (row, col) position. Can Harry move NORTH from his current position? If yes, try to solve the maze from one step north. Examine the string that comes back from the recursive call. If this string is not "X", then that means the recursive call found a solution and this string is the path to that solution. Update this string to reflect that we moved NORTH to find the solution. Return the string. Can Harry move SOUTH from his current position? If yes, try to solve the maze from one step south. Examine the string ... (see above) Can Harry move EAST from his current position? Repeat for EAST. Can Harry move WEST from his current position? Repeat for WEST. // If we get here, it means the maze cannot be solved from our current position, // since trying all four possible directions failed (either returned "X" or Harry // couldn't move in that direction). We must be in a dead end. Pick up the breadcrumb from our current position because breadcrumbs only mark the solution, and we didn't find one from here. Return "X" (indicating a dead end)
A breadcrumb is placed in a square at the beginning of the function. It is only removed if we reach a dead end, therefore, when the algorithm finds the cup, we can be guaranteed that there will be a single trail of breadcrumbs from start to finish. In your code, you will represent breadcrumbs by a lowercase letter 'o'. When the code asks you to drop a breadcrumb, you will literally alter a character in maze[row][col] from a period to a lowercase 'o'. If you ever pick up the breadcrumb, you will change the 'o' back to a period.
Note that the 'H' symbol never moves in the maze; it is used just to signify where Harry begins. All the "moving" in the maze is accomplished with the row and col arguments that are passed (recursively) to solve. In other words, the current location of Harry is the row/col of the current call to solve. Because of the recursion, Harry will automatically backtrack if he finds himself in a dead end. A nice explanation of this is here.
Note: This algorithm is guaranteed to find a solution to the maze if it exists. For some mazes, it will not necessarily find the shortest path if there are multiple paths Harry could take.
[ maze0.txt ] [ maze0.txt output ]
[ maze1.txt ] [ maze1.txt output ]
[ maze2.txt ] [ maze2.txt output ]
[ maze3.txt ] [ maze3.txt output ]
[ maze4.txt ] [ maze4.txt output ]
Try your solution on this maze: maze5.txt. Using the same N/S/E/W order of checking directions gives an extremely poor solution. If you do this challenge right, Harry should go straight to the cup.