CSCI 335 - Artificial Intelligence
Fall 2017
Programming Project #1: Solving Mazes with A*
Overview
You will develop a series of search heuristics for solving a maze.
You will be provided with the code for representing mazes and
generating random mazes. You will develop a best-first search implementation,
along with several heuristics. Mazes can be of different levels of
"perfection", and they can also contain treasures that must be obtained.
Setup
The Java source files are provided in maze.zip.
To set up the files in Eclipse, do the following:
- Create a new Eclipse Java project. (You can call it whatever you like,
but make sure the name does not include spaces.)
- Make sure it is configured to generate separate "bin" and "src" folders. (This should be the default.)
- Place maze.zip inside your project directory,
and unzip the file.
- Click on your project in the package explorer, and press F5 to refresh.
- The files should now be there. If
MazeTest.java
has an
error:
- Right-click on the project to open "Properties".
- Select "Java Build Path". Then select "Libraries".
- There will be an "Add Library" button. Add the JUnit library. Be sure
to add JUnit4, not JUnit3.
Programming Assignment
The files are placed in four packages. Classes you will modify are in
boldface.
-
search.core
-
BestFirstHeuristic
interface: All of your heuristics will be classes that implement this interface.
-
BestFirstObject
interface: You will employ an implementation of this interface to represent positions in the maze.
-
BestFirstSearcher
class: As it stands, this class implements breadth-first search. You will modify it to implement best-first search.
-
maze.heuristics
-
BreadthFirst
class: Implements the BestFirstHeuristic
interface.
- You will place all of your implementations of the
BestFirstHeuristic
interface in this package.
-
maze.core
-
Maze
class: Represents a maze.
-
MazeCell
class: Represents a coordinate in a maze.
-
Direction
enum: Represents the four directions of movement in a Maze
.
-
MazePath
class: Represents a path through a maze. It can check to see if the path validly connects the entrance and exit.
-
MazeExplorer
class: Implements the BestFirstObject
interface. It represents an "explorer" collecting "treasure". It does not generate successor "explorers"; you are responsible for implementing this.
-
MazeTest
class: JUnit test class. It is currently failing. If MazeExplorer
is correctly implemented, it should pass testNoTreasure()
and testMany()
. If BestFirstSearcher
is also implemented correctly, it should pass testBestFirst()
.
-
maze.gui
-
MazeViewer
class: This is the main GUI class. This is what you will run to test your heuristics.
-
MazePanel
class: Draws the maze.
-
AIReflector
class: Utility class that finds all of the
heuristics for testing purposes.
Once MazeExplorer
is working, you will need to modify
BestFirstSearcher
to implement best-first search. Once both of
these are working, implement the following heuristics. Each one will be a class that implements the BestFirstHeuristic
interface. Each one
should be placed in maze.heuristics
:
- A heuristic that in some way uses a Manhattan distance calculation
- Two additional monotonic heuristics
- Two non-monotonic heuristics
Note: All heuristics should be completely deterministic. Any
heuristic making use of random numbers will receive no credit,
as a heuristic is supposed to correspond to actual information about the
path to a solution.
Here are some hints and tips to help you with your implementation:
- To get
MazeExplorer
working, you need to pass two unit
tests:
- The first unit test ignores the possibility of treasures. To pass
this test, it should suffice to generate one successor for each
Direction
. Of course, if a given direction is blocked,
or if a neighbor would be outside the maze, then no successor should
be generated for that direction.
- The second unit test takes treasures into account. It will be
necessary to copy over all of the claimed treasures from the parent
node to each successor node. If the successor node contains a treasure,
it will also be necessary to record that fact in that node's set of
treasures acquired.
- To get
BestFirstSearcher
working:
- Replace the queue with a heap. The
java.util
library contains the PriorityQueue
class.
- Make sure that
SearchNode
implements the Comparable
interface in order for it to work properly with the priority queue.
- The implementation of the
compareTo
method in the SearchNode
class will need to employ
the sum of the node depth (g(n)) and estimated distance to the goal node (h'(n)).
- For some heuristics, calculations can be expensive, so an efficient
implementation will calculate the heuristic estimate in the
SearchNode
constructor.
- The
testBestFirst()
unit test (in MazeTest
) will check to see if, over a large number of randomly generated mazes, the best first searcher creates fewer nodes than breadth-first search when equipped with a very simple heuristic. If your solution passes this test, you should consider your modifications to be successful.
Proofs
Your proofs need not be fully formal; a well-constructed argument will
suffice. Be sure to employ the
definition of monotonicity from class.
- Prove that the Manhattan-distance heuristic is monotonic.
- Prove that your two additional heuristics are monotonic.
- For each pair of the three monotonic heuristics:
- State which heuristic is better informed, and prove it.
- If neither heuristic is better informed than the other, give two
counterexamples to prove it.
- Prove that your two non-monotonic heuristics are non-monotonic.
Experiments
For each heuristic, determine
experimentally the "hardest" mazes that it can solve in two minutes or less
using the Linux machines in the lab. If you would rather use your own
computer, be sure to specify its clock speed. You will need to determine the
"hardness" of mazes in the following categories:
- Perfect (no loops) to imperfect (very few barriers)
- Amount of treasure to be collected
- Size of the maze
For each experiment you run:
- For each maze you generate, run each heuristic on that maze. This
ensures that comparisons between heuristics are not biased.
- For each maze, record the amount of perfection, number of treasures, and maze dimensions.
- For each heuristic, record the number of nodes expanded, maximum depth reached, solution length, and effective branching factor.
- If a heuristic takes longer than two minutes, feel free to terminate it and
record that, for that experiment, the heuristic took too long.
Paper
When you are finished with your experiments, you will write a short paper
summarizing your findings. Include the following details in your paper:
- A description of each heuristic, and a proof of its montonicity
(or lack thereof)
- The data you collected from each of your experiments
- An analysis of the data, including:
- A discussion of the relative merits of the heuristics relative to
breadth-first search and each other, and
- A discussion as to how varying the size, treasure, and perfection
affects the general difficulty of solving a maze by computer
- Discuss the degree to which the results matched your expectations
- Printouts of the code for your heuristics
Deadlines
- On Thursday, August 24, come to class with a one-slide PowerPoint
presentation. Briefly describe the heuristics you have already implemented,
in addition to other possibilities you are considering.
- Submit your paper and code by
1:15 pm on Tuesday, August 29.
Grading criteria
Grade | Content |
A | Program is working with all heuristics Paper is complete Analysis properly characterizes the basis of each claim |
B | One or two heuristics have small bugs The analysis is somewhat flawed |
C | Problematic bugs and/or somewhat incomplete or moderately flawed paper |
D | Severe bugs and/or multiple parts missing from paper |
F | Program does not work at all and/or paper is not seriously attempted |