Searching through Grids
You will submit your heap and priority queue implementations as a checkpoint on November 13th, a week before the full project sumission! We'll grade them using the supplied tester. Submit them in your project 7 folder.
Tasks
Testing
As always, as you are coding up this project create testing files for each class you create exercising the public methods implemented in this class.
Maze Class
README: In order to expedite the process of this project, we're including a fully implemented Maze class. However, do take a few minutes to peruse the methods described below and their implementations to make sure you understand the underlying Maze class.
The Maze class will manage a grid of Cells.
- Maze Fields and Methods
What fields you keep track of in your Maze class is up to you. However, you'll need to implement the following methods:
public Maze(int rows, int cols, double chance)
constructs the Maze to have the specified number of rows and columns, where each Cell has a probability given by chance of being an Obstacle.public int getRows()
returns the number of rows.public int getCols()
returns the number of columns.public Cell get(int row, int col)
returns the Cell at the specified row and column.public void reset()
resets all the Cells in the Maze.public LinkedList<Cell> getNeighbors(Cell c)
returns a LinkedList of Cells containing all the adjacent (diagonals not included) non-Obstacle neighbors of the specified Cell in the grid.
The MazeSearchers
- The AbstractMazeSearch class:
This class will manage the following fields:
private Maze
: a Maze object that will be the basis for the searchprivate Cell start
: the Cell which the search starts fromprivate Cell target
: the Cell which the search is trying to reachprivate Cell cur
: the Cell where the search is currently at
public AbstractMazeSearch(Maze maze)
: initializes the fields. In particular, saves the given Maze to the relevant field and sets cur, start, and target to be null.
public abstract Cell findNextCell()
: this method returns the next Cell to explore.public abstract void addCell(Cell next)
: this method adds the given Cell to whatever structure is storing the future Cells to explore.public abstract int numRemainingCells()
: this method returns the number of future Cells to explore (so just the size of whatever structure is storing the future Cells).
public Maze getMaze()
: just returns the underlying Maze.public void setTarget(Cell target)
: sets the given target to be the target of the search.public Cell getTarget()
: returns the target of the search.public void setCur(Cell cell)
: sets the given cell to be the current location of the search.public Cell getCur()
: returns the current Cell location of the search.public void setStart(Cell start)
: sets the given start to be the start of the search. Additionally, set start's prev to be itself - the reason for this will make sense a bit later.public Cell getStart()
: returns the start of the search.public void reset()
: sets the current, start, and target Cells to be null.public LinkedList<Cell> traceback(Cell cell)
: finds a path from the start to the specified cell by repeatedly following the prev path. Returns the path if found, otherwise returns null.:public LinkedList<Cell> traceback(Cell cell){ Cell curCell = cell; LinkedList<Cell> path = new LinkedList<>(); while (curCell != null){ add curCell to the front of path if (curCell is the start){ return path; // we've completed the path from the start to the specified cell } curCell = curCell.getPrev(); } return null; // we weren't able to find a path, so we return null }
public LinkedList<Cell> search(Cell start, Cell target)
: the most important method of the class. This is the method that actually does the searching. Despite that we don't event know exactly which search algorithm we'll use, we can use the abstract methods to organize the search regardless. The LinkedList we return will be the path our search algorithm finds to reach the target from the starting Cell. If one isn't found, we'll return null. To aid you, consider the following pseudo-code:public LinkedList<Cell> search(Cell start, Cell target){ setStart(start); setTarget(target); setCur(start); add the starting cell to the set of Cells to explore while (there are still Cells left to explore){ set Cur to be the next Cell from the Cells to explore (findNextCell()) for (each neighboring Cell of cur) { if (this neighbor hasn't been visited before (if its prev is null)){ set this neighbor's prev to be cur add this neighbor to the future Cells to explore if (this neighbor is the target){ return traceback(target); // we found the target, we're done } } } } return null; // we couldn't find the target, but we're done }
Now that we've got our AbstractMazeSearch class, we just need to extend it with a few specific
algorithms:
-
MazeDepthFirstSearch: this class will extend the AbstractMazeSearch class and implement the DFS algorithm. We've seen this algorithm before, actually, back when we solved Sudoku! We'll use a Stack as the backbone of the algorithm and continually dive down a particular solution until we reach the target or exhaust all reachable Cells.
In order to write this class, we'll need to manage a Stack:
private Stack<Cell> stack
. In addition, we'll need a constructorpublic MazeDepthFirstSearch(Maze maze)
: this will call the parent's class constructor (via thesuper
keyword) with the given Maze and initialize the Stack (as a new LinkedList).Additionally, we need to implement the abstract methods of the parent class - these all simply call the relevant Stack method. And that's it! Ah, the usefulness of polymorphism...
-
MazeBreadthFirstSearch: another class to extend the AbstractMazeSearch class and implement the BFS algorithm. This one is new: we'll use a Queue as the backbone of the algorithm and simultaneously work on 'every solution' by working on all directions at once until we reach the target or exhaust all reachable Cells.
In order to write this class, we'll need to manage a Queue:
private Queue<Cell> queue
. In addition, we'll need a constructorpublic MazeBreadthFirstSearch(Maze maze)
: this will call the parent's class constructor (via thesuper
keyword) with the given Maze and initialize the Queue (as a new LinkedList).Additionally, we need to implement the abstract methods of the parent class - these all simply call the relevant Queue method. And that's it! Ah, the usefulness of polymorphism...
-
MazeAStarSearch: the final class to extend the AbstractMazeSearch class and implement the A* algorithm. This one is slick: we'll use a PriorityQueue as the backbone of the algorithm and use our knowledge of where the target is to try to walk in the 'right direction' until we reach the target or exhaust all reachable Cells.
In order to write this class, we'll need to manage a PriorityQueue:
private PriorityQueue<Cell> priorityQueue
. In addition, we'll need a constructorpublic MazeAStarSearch(Maze maze)
: this will call the parent's class constructor (via thesuper
keyword) with the given Maze and initialize the PriorityQueue (as a new Heap). Of course, Cells aren't innately Comparable, so we'll need to provide the Heap constructor a Comparator to tell it how to prioritize Cells.To build the Comparator, we'll need to make a
public int compare(Cell cell1, Cell cell2)
method that returns a negative number if cell1 is of greater priority than cell2, a positive number if cell2 is of greater priority, or 0 if they have the same priority. To do this, consider what we want to prioritize: we want to follow the Cell that results in the shortest overall path to the target. Note that we only add Cells to the priorityQueue if they have already been reached, so we now how many steps it takes to get to the given Cell (you can use the traceback method for this). We might not know exactly how many steps are required to get to the target from each Cell, but we do have a lowerbound: we have to take at least one step for each difference in column and one step for each difference in row! So the Cell with higher priority should be whichever Cell has the smaller sum of the number of steps to reach it and the estimated distance to the target.Lastly, we need to implement the abstract methods of the parent class - these all simply call the relevant PriorityQueue method. And that's it! Ah, the usefulness of polymorphism...
Now that we have our Maze class, let's write a few different implementations of searching a Maze, specifically:
To unite these classes (as they will all behave extremely similarly), we'll start out with an AbstractMazeSearch class.Visualizing the Search
The final task (you're in the home stretch!) is to add a visualization component to your searches. As per usual, we're providing you with the draw methods of the AbstractMazeSearch class (they are already in the Cell and Maze classes provided). The draw method for your AbstractMazeSearch class calls every other class' draw method. Make sure to import java.awt.Graphics and java.awt.Color.
public void draw(Graphics g, int scale) { // Draws the base version of the maze getMaze().draw(g, scale); // Draws the paths taken by the searcher getStart().drawAllPrevs(getMaze(), g, scale, Color.RED); // Draws the start cell getStart().draw(g, scale, Color.BLUE); // Draws the target cell getTarget().draw(g, scale, Color.RED); // Draws the current cell getCur().draw(g, scale, Color.MAGENTA); // If the target has been found, draws the path taken by the searcher to reach // the target sans backtracking. if (getTarget().getPrev() != null) { Cell traceBackCur = getTarget().getPrev(); while (!traceBackCur.equals(getStart())) { traceBackCur.draw(g, scale, Color.GREEN); traceBackCur = traceBackCur.getPrev(); } getTarget().drawPrevPath(g, scale, Color.BLUE); } }
Lastly, download MazeSearchDisplay class. To use it, add two parameters
to your search method in the AbstractMazeSearch class:
public LinkedList<Cell> search(Cell start, Cell target, boolean display, int delay)
: if
display == true, create a MazeSearchDisplay with arguments: this, 20 before the start of the while loop. (If display is false you'll want to create a null MazeSearchDisplay object).
In the while loop, if display == true call Thread.sleep for
delay milliseconds and call
repaint on the MazeSearchDisplay object. You'll need to handle the Interrupted Exception that can be thrown by calling Thread.sleep. One option to do that is this:
try{ Thread.sleep(delay); }
catch(InterruptedException e) {} ;
Exploration
Use your simulation to explore the following questions. You can change the density of obtacles in the constructor for the Maze class. Note, you can always set the delay to 0 to maximize the speed of the solve.
- What is the relationship between the density of obstacles to probability of reaching the target? Note that this should be independent of whichever search algorithm you use, as they should all find the target if it is reachable. Specifically, around what density of obstacles does the probability of reaching the target drop to 0?
- What is the relationship between the lengths of the paths found by DFS, BFS, and A*?
- What is the relationship between the average number of Cells explored by DFS, BFS, and A*?
Present your results as a table or as multiple graphs and be sure to include both a caption explaining any tables/graphs and at least a paragraph describing your findings. You should include the results of experiments with at least 10 different settings of the density of obstacles spaced out between 0 and 1.
Extensions
- Implement different types of Cells, like mud (where it's slow to walk through) or ice (where it's difficult to turn, but going straight takes less time). How do your algorithms do on this changed environment?
- Realistically, some of these algorithms don't make a lot of sense from a human's perspective. How are you, a human, going to teleport around the maze to get to the next Cell your search wants to move to? Analyze the number of steps required in a grid to 'walk to' the next Cell while finding the target and see how your search algorithms fare.
- Somewhat relatedly to the previous extension, try and find the 'shortest path' making some assumptions about acceleration, such as in the Micro Mouse game. For example, you could assume that going straight is faster than turning, and try to determine the fastest path under that assumption.
Report
Your intended audience for your report are your peers not in the class, but who have taken a CS course. Your report should explain the core CS concept used, present the results of your program, and discuss the meaning of the results: what did you discover and does it make sense?
Your project report should contain the following elements. Please include a header for each section.
- Abstract
A brief summary of the project, in your own words. This should be no more than a few sentences. Give the reader context and identify the key purpose of the assignment. Each assignment will have both a core CS purpose--usually a new data structure--and an application such as a simulation or analysis.
Writing an effective abstract is an important skill. Consider the following questions while writing it.
- Does it describe the specific project application?
- Does it describe the results of your simulation or analysis?
- Is it concise?
- Are all of the terms well-defined?
- Does it read logically and in the proper order?
- Results
Present your results. Include text output, images, tables, graphs, or qualitative descriptions, as appropriate. Include a discussion as to whether the results make sense and what they mean. This week, be sure to present your analysis for the exploration section.
- Reflections
A semi-brief overview of the data structure implemented/used in this project. What was the role of the PriorityQueue / Heap in this project? How was it used in the A* algorithm?
- Extensions
Describe any extensions you undertook, including text output, graphs, tables, or images demonstrating those extensions. If you added any modules, functions, or other design components, note their structure and the algorithms you used.
- References/Acknowledgements
A list of people you worked with, including TAs and instructors. Include in that list anyone whose code you may have seen, such as those of friends who have taken the course in a previous semester. If you found a website for help, such as StackOverflow, include a link to the post.