The MazeSolver
class is an abstract class that implements most of the functionality needed to find a solution path through a given Maze
(or determine that no solution exists). It is an abstract class because later in the lab, we will implement two children classes: one that uses a queue to explore the possible paths through the maze and another that instead uses a stack.
The general algorithm for finding a solution for a maze works by expanding possible path one step at a time from the start location until either (1) we reach the exit location, indicating we found a solution path, or (2) we explore every reachable location from the start location without reaching the exit location, indicating no solution path exists.
To explore all reachable paths, we need to keep track of three important pieces of information:
Square
s that would expand our current paths by a single step. We will call this collection our exploration collection.Square
location needs to know whether it has already been added to the exploration collection by our algorithm (so that we never repeat locations, which at best would result in longer paths than needed, and at worst could cause our algorithm to become stuck in an infinite loop).isMarked()
and mark()
methods defined in Part 3 of the lab will be helpful hereSquare
location along a possible path needs to know which Square
location preceded it in the path back to the start location. We can use these previous links (similar to the previous
variable of our DoublyLinkedNodes
of our linked list in Lab 4) to trace an entire path through the maze.getPrevious()
and setPrevious()
methods defined in Part 3 of the lab will be helpful here.When our algorithm starts, our exploration collection will contain only the start location, all Square
s other than the start location will not be marked as explored (so their isMarked
method will return false
), and no Square
has a previous Square
. The algorithm will proceed by:
Square
from the exploration collection should be retrieved from the exploration collection.Square
in Step 2 is the exit location, use its previous Square
s to build the path from the start location to the exit location. The algorithm should also record that it has finished and a solution has been found.Square
s of the Square
from Step 2 should be added to the exploration collection as long as (i) the neighbor Square
is not a wall and (ii) the neighbor Square
location has not already been added to the exploration collection.
When a neighboring Square
s location is added to the exploration collection, then (i) the Square
from Step 2 should be saved as the neighbor’s previous location back towards the start location, and (ii) the neighbor should be marked as added to the exploration collection.Below, we describe how to complete the MazeSolver
class in two parts so that it executes this algorithm.
Steps 1 and 2 of the algorithm have already been implemented for you in the public void step
method of the MazeSolver
class. Your assignment in this part of the lab is to implement Steps 3-4 in the bottom of the method under the comment:
/*
* TODO: implement one step of maze exploration algorithm
*/
Running the algorithm until it completes in Step 5 is also implemented for you by the MazeApp
class.
As part of Step 3, you should call the public void buildPath(Square sq)
method that is responsible for saving the solution path in the this.path
instance variable as a list of Square
s starting with the starting location and ending with the exit location. You should also use the this.finished
instance variable to record that the algorithm has finished executing (similar to Step 1 that is implemented for you) and the this.pathFound
instance variable to record that a solution path has been found.
As part of Step 4, you can add a neighbor to the exploration collection using the public void add(Square sq)
method of the MazeSolver
class.
The public void buildPath(Square sq)
method is not already implemented, so you will need to fill in the necessary code after the comment:
/*
* TODO: fill in this.path with the Squares from the start to the end.
*/
You can assume the public void buildPath(Square sq)
is only called with the exit location as its Square sq
argument. You can use the Square
class’s getPrevious()
method to find the full path of Square
s from the exit location back to the start location. As you add each Square
to the path, do not forget to record that it is on the path using the appropriate method of the Square
class. Finally, the built-in function:
Collections.reverse(this.path);
might be helpful to then reverse the order of your path so that it instead goes from the start location to the exit location (although you are welcome to fill in the this.path
instance variable however you wish).
Unfortunately, at this point, we cannot quite test our progress, yet. Instead, we first need to implement children classes that are not abstract, which we will do next in Part 5 of the lab.
Don’t forget to frequently save your progress periodically to GitHub using the add
, commit
, and push
commands with git.