0% found this document useful (0 votes)
21 views19 pages

Maze Project

The Maze project involves creating a program that generates a random two-dimensional maze and implements an algorithm to solve it, utilizing Java programming and object-oriented principles. The project is structured into checkpoints that include developing various classes such as Maze, Cell, and Explorer, and implementing algorithms for maze generation and solving. It also emphasizes the use of a graphical user interface to visualize the maze and the exploration process in real time.

Uploaded by

schareq
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views19 pages

Maze Project

The Maze project involves creating a program that generates a random two-dimensional maze and implements an algorithm to solve it, utilizing Java programming and object-oriented principles. The project is structured into checkpoints that include developing various classes such as Maze, Cell, and Explorer, and implementing algorithms for maze generation and solving. It also emphasizes the use of a graphical user interface to visualize the maze and the exploration process in real time.

Uploaded by

schareq
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 19

the Maze project

In this project …
…You will write a program accomplishing two major tasks:

Generate a random two-dimensional maze. The Maze will be created on a grid of Cells of various kinds and will be
rendered in a graphical UI (GUI). It will be semantically correct: fills the entire grid, doesn’t isolate unreachable areas,
includes one entry and one exit location, includes some “no-trespass” blocks and guarantees the existence of a path from
the entry to the exit.

Solve the randomly generated maze. You will design and implement the algorithm that determines automatically
the path from the entry to the exit. The algorithm will render its path exploration in the GUI.

…You will put to use your knowledge of:


• Java programming language,
• Multi-dimensional arrays,
• Random numbers,
• Object-Oriented programming,
• Complex recursive algorithms,
• ...

…You will be exposed to and practice with:


• Coding larger, complex, multi-class Java projects,
• Testing, Tracing and Debugging your code,
• Using libraries and working with a Graphical User Interface,
• ...
Maze project

Checkpoints
the

Specification and
The complete project specification can be broken down into the following sections: To reach project completeness, the development effort is divided into the
following checkpoints:

Checkpoint 1:
Maze class. Practicing with MazeCanvas.
Object Model Algorithms UI design UML Diagram API Reference
Checkpoint 2:
When complete, your project includes a full implementation of the following classes: Cell classes: Cell, ShadedCell, EdgeCell.
Initialize the Maze.
Program.java:
Main class of the project. Implements the main() method for creating, generating and solving a Maze.
Maze.java: Checkpoint 3:
Cell classes: Adding BlockCell, EntryCell,
Implements the Maze as the container for the grid of Cells and for the entrance and exit cells. ExitCell to the Maze.
Implements the maze initialization code.
Cell.java:
Implements the core state and behaviors common to all type of cells. Checkpoint 4:
Generator class: Implement the building
ShadedCell.java: blocks and the algorithm for generating the
Extends Cell with the ability shade the cell in a custom color. maze.
EdgeCell.java and BlockCell.java: Checkpoint 5:
Extend ShadedCell with the specific custom color for the edge or the light-gray shade for the block Solver class: Implement the building blocks
cell. These cells include the ability to prevent stepping outside the maze’s edges or stepping in or out and the algorithm for solving the maze.
of a block cell.
EntryCell.java and ExitCell.java:
Extend EdgeCell with the specific custom entry and exit shade colors. Used to identify the entrance
Final 6:
Explorer class: Refactor Generator and
into and exit from the maze. Solver as extensions of Explorer.
Explorer.java:
Implements the depth-first, recursive exploration algorithm. Provides default implementation for
helper methods called internally from within this algorithm.
Generator.java and Solver.java:
Extend Explorer overriding some of its methods in order to generate the maze in a consistent and
complete manner, or to solve it by finding a path from the entrance to the exit.
the Maze project

the Object Model


This project is representing a real-life Maze through a collection of classes – known in Here is an example of a generated Maze of 12 rows and 16 columns. The EdgeCells have a
Object-Oriented Programming as an Object Model. It can be described as follows: pink shade, the EntryCell is blue and the ExitCell is green.

A Maze is a two-dimensional grid of nRows x nCols cells. Each Cell is a square space which
may be surrounded by walls on any of its Top, Left, Right or Bottom sides. Some Cells may EdgeCell Maze
have a specific shade (background) color. These are ShadedCells. There are several kinds is a ShadedCell
is a Cell A nRows x nCols two-dimensional grid
of such cells: of Cells of various kinds. Contains a
pink shade, walls on their outer sides. special EntryCell and ExitCell.
 EdgeCell: These are the cells at the edge of the Maze: first and last row and first and
last column. Their shade is of a specific edgeColor (your choice) different than the
default white. They have walls on their outer sides, but they may or may not have walls 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
on their inner sides. For instance, all EdgeCells on the first row have a wall on their Top ExitCell 1
side while the ones on last column have a wall on their Right side. The EdgeCell from is an EdgeCell
2
is a ShadedCell
the bottom right corner of the maze has walls on its Right and Bottom sides. is a Cell 3 EntryCell
is an EdgeCell
 BlockCell: There are few such cells inside the maze: less than 5% of their total number. EdgeCell of green shade, 4 is a ShadedCell
marks the maze exit is a Cell
These cells have walls on all their sides and have a light gray shade. They represent 5
impassable areas, like a block of concrete covering its entire cell. 6 EdgeCell of blue
shade, marks the
7 maze entrance
 EntryCell: This is a special kind of EdgeCell: There is only one such cell, representing
8
the entrance in the maze. It has a shade of a distinct color (your choice), different than
9
the edge shade or the default white.
10
 ExitCell: This is yet another special kind of EdgeCell: There is only one such cell, 11
representing the exit from the maze. Its shade is distinct from all the other shade
colors in the maze.
Cell BlockCell
is a ShadedCell
The size of the maze (nRows and nCols) and the various shade colors are your choice. The Interior to the maze, no is a Cell
maze shown on the right is only giving an example of such choices. shade (default – white),
may or may not have walls Light gray shade, walls on
on any of its sides. all their sides, impassable.

Ref: classes UML diagram :


the Maze project

exploration Algorithms An algorithmic description of the dept-first recursive exploration is as follows:

Explorer.run(cell)  boolean // true if done, false


otherwise
There are two tasks this project aims to achieve: generate a random maze and once
mark cell as visited
generated, solve the maze by finding a path from its entrance to the exit. A verbose, high-
level description of the logic to approach each of these tasks is as follows: done = false
if cell is the final step then
Generating the maze mark cell as solution
Start with a Maze initialized with the grid of cells, each of their specific kind. In their initial return true
state, all cells are walled on all their sides. Starting from the EntryCell tear down the walls end if
separating it from a randomly chosen but valid neighbor. Step to the selected neighbor get valid next steps from cell
and repeat this process by exploring its neighbors. If there are no more neighbors to go for each next step and as long as not done do
to, step back to the previous cell for another try. When there are no more next steps if neighbor in the next step is not visited then
possible, the maze is fully generated, is consistent, and with a path guaranteed to exist done = run(neighbor)
between any two cells.
end if
end for
Solving the maze if done then
Start with a Maze initialized and already generated as described above. Starting from the
EntryCell, choose randomly a valid neighbor to go to. Step to the selected neighbor and mark cell as solution
repeat this process. If there are no such neighbors, step back to the previous cell and look end if
for another neighbor from that location. At any point the ExitCell is reached, mark the cell return done
as part of the path and return “done” to the previous cell. At any point “done” is returned end run
from a neighbor, do the same: mark that cell as part of the path and return “done”. In the
end the chain of marked cells indicates the path from the entrance to the exit of the There are very few parts in this algorithm specific to either the generator or solver: In
maze. the generator case, no cell is a final step. Each will have to visit all its neighbors in order
to generate the maze fully. In the solver case, if cell is an ExitCell then the terminal step
Notice the similarities between these algorithms. This is a typical depth-first recursive of the path has been discovered so the exploration can be cut short.
exploration algorithm. The common behavior can be implemented in an Explorer class
while behavior specific to each of the generating / solving algorithms can be captured in While the core part of the algorithm shown above is implemented in the Explorer class,
the Generator and Solver subclasses. the small differences can be captured in two subclasses, Generator and Solver via
overridden methods. More details on how to build these classes are provided further
out in this document.

Ref: classes UML diagram :


the Maze project

the User Interface


The startup Java project provided along with this specification contains the MazeCanvas Solving the maze
class which can be used for rendering a visual representation of the Maze. The same As the solving algorithm progresses, each step forward is shown as a path connecting
class provides functionality for visualizing in real time the progress of the exploration the current cell to the next one. The path is rendered in a color of your choice. You can
algorithms. see below some examples of how the UI reflects the progress of the solving algorithm:

Generating the maze Step into the cell from its Left side Solve further by exploring the Top side
As the generating algorithm progresses, each step forward is rendered as a path
connecting the current cell to the next one. The path is rendered in a color of your
choice. You can see below some examples of how the UI reflects the progress of the
generating algorithm:
drawPath(.. Side.Left, Color.RED) drawPath(.. Side.Top, Color.RED)
Step into the cell from its Bottom side Generate further by exploring the Right side drawPath(.. Side.Center, Color.RED)

Each time a step forward is no longer possible, and the algorithm backtracks to the
previous cell, the path connecting that cell to the current one is retained but
drawPath(..Side.Bottom..) drawPath(..Side.Right..)
drawPath(..Side.Center..)
rendered in a different color of your choice.

Each time a step forward is no longer possible, and the algorithm backtracks to the No path via the Bottom side,
No path via Top, solve further
previous cell, the path connecting that cell to the current one is erased. by exploring the Bottom side return to the previous cell.

Right side done, generate further by


exploring the Top side All sides done, return to the previous cell

drawPath(..Side.Top, Color.Yellow) drawPath(..Side.Bottom, Color.Yellow)


drawPath(..Side.Bottom, Color.Red) drawPath(..Side.Center, Color.Yellow)
drawPath(..Side.Left, Color.Yellow)

erasePath(..Side.Right..) erasePath(..Side.Top)
drawPath(..Side.Top..) erasePath(..Side.Center..)
erasePath(..Side.Bottom..)

Ref: MazeCanvas GUI framework:


the Maze project

the UML diagram


Explorer Maze Cell
_mazeCanvas _mazeCanvas _mazeCanvas
_maze _grid[][] _row, _col Models any kind of cell.
_fwdPathColor, _bktPathColor _enterCell, _exitCell _walls[]
getOpposite() getRows(), getCols() _visited
Models the engine capable to
explore the maze either for shuffle() getEnterCell(), getExitCell() getRow(), getCol()
generating or for scanning onEnterCell() getCell(), getNeighbor() getWalls()
onGetNextSteps() Initialize() getPaths()
onStepForward() getVisited(), setVisited()
onStepBack() removeWall()
onExitCell() Models the entire structure of a
run() Maze as a grid of Cells.

Models any kind of


ShadedCell shaded cell.
_shadeColor
Generator Solver
onEnterCell() onEnterCell()
onGetNextSteps() onGetNextSteps()
onStepForward() EdgeCell BlockCell
Models any kind of
edge (shaded) cell. _edges[] _blockShadeColor Models a block
In charge of generating In charge of solving a _edgeColor (shaded) cell.
the maze. generated maze. getVisited()
getWalls()
getPaths()

EntryCell ExitCell
_entryShadeColor _exitShadeColor

Models an Entry Models an Exit


(edge, shaded) cell (edge, shaded) cell
Checkpoint 1: the Maze class. Practice with MazeCanvas.
Think  Code
In this checkpoint we will create the main entry point into the project and the first class: Maze. Download the Maze_Startup.zip file and import it in Eclipse.
We’ll use this code structure to practice with MazeCanvas functionality by creating the Add a Program class to the Maze project src folder, implementing the main() method.
following snake-like pattern. A few things to notice about this pattern:
• In the Program class define class constants for the number of rows and columns of
0 1 2 3 4 5 6 7 8 the maze and the size of a maze cell. Set them to values of your choice.
 Most of the cells (the ones in the center area of the maze)
0
look the same: • In the main() method, create an instance of MazeCanvas setting it with the chosen
1 number of rows, columns and cell size from the class constants. You’ll need to
2 - They have no Top and no Bottom walls.
import edu.ftdev.Maze.MazeCanvas in order to have the class available to
3 - They have a path connecting the Top, Center and Bottom, your code.
4
all of which are colored in RED. • On that instance, call open(), breakLeap() and close() methods of the maze canvas.
5
6  Cells on the first and last row look different, based on their
Run the program! You should see an empty window showing up. Press ‘C’ to allow
0 1 2 3 4 5 6 7 8 row and on which column they are placed.
the program to go past the pause() call. You should see then the window
disappearing and the program terminating.
Top row: If the cell’s column is an even number then the cell has no Left and no Bottom walls,
and the path is connecting the Left, Center and Bottom sides. Otherwise, the cell has no Right Add a Maze class to the project.
wall and the path is connecting the Bottom, Center and Right sides. • Add a Maze(MazeCanvas mazeCanvas) constructor to the class. Save its parameter
Bottom row: If the cell’s column is an even number then the cell has no Top and Right walls and in a class data field.
the path is connecting the Top, Center and Right sides. Otherwise the cell has no Left and no Top • Add a method genSnake() to the Maze class. In it, implement the algorithm
walls and the path is connecting the Left, Center and Top sides. described on the left for generating the snake. The method takes no parameters since
In addition, all cells on these two rows have a darker red center. it already has all the information needed for rendering, through the MazeClass data
field. It will use it to determine the number or rows and columns of the maze and to
Structuring the code: This is the first checkpoint in a larger project. We need to structure the draw the cells, erase their walls and draw the paths as needed.
code such that the project can grow easily. The Maze class will encapsulate the functionality for
this and future checkpoints. For now, this class implements the genSnake() method only. The • In the main() method of the Program class, create an instance of the Maze class and
Program class will contain the main() method can then be as simple as follows: call its genSnake() method. Make this call right after the maze canvas was opened
and before the call into the breakLeap() method.
 Create and open a MazeCanvas instance.
 Create a Maze instance passing to it the MazeCanvas object as the rendering engine. Run the program! If you implemented everything correctly you should see the
maze canvas filled in with the snake kind of shape as shown on the left.
 Call the Maze’s genSnake() method from main(). All rendering happens inside that method.

Verify the MazeCheckpoint1.java tests are passing.

Ref: MazeCanvas GUI framework:


Checkpoint 2a: the Cell, ShadedCell, EdgeCell classes.
Think  Code
So far, we have an instance of the Maze class, implementing the genSnake() method. Add a Cell class to the project, containing several data fields: the row and column (int), a
In this checkpoint we create the smallest building blocks making up the maze, starting with the mazeCanvas reference and a listOfWalls sides surrounding the cell.
Cell class. Each square area in the maze is modeled by a Cell object. • Add a Cell(MazeCanvas mazeCanvas, int row, int col) constructor to the class. Inside,
save the parameter values in the class data fields. Initialize the listOfWalls to all sides
Cell A Cell “knows” its row and column and has a reference to a mazeCanvas {Top, Left, Right, Bottom}. Use the mazeCanvas to draw the cell at the row and col
which it can use for drawing itself at the given location. It also contains a location.
_mazeCanvas
_row, _col list of wall sides, which initially are all of them: Top, Left, Right, Bottom. • Add the getRow(), getCol() and getWalls() accessors for the data fields.
_walls[] All this information represents the cell’s state (class fields). The cell’s Note: the getWalls() needs to return a copy of the internal listOfWalls to prevent the
behaviors (methods) allow accessing its internal data as well as removing callers of this method from changing the internal state of the cell.
getRow(), getCol()
getWalls() the wall on a given side. This latter capability is going to be needed a • Add the removeWall(Side side) method. It uses the mazeCanvas to erase the wall on
little later, when we generate the maze. that side of the cell and keeps track of this change of state by removing the given side
removeWall() from the internal listOfWalls.

ShadedCell A ShadedCell is a very simple extension of a generic Cell. It only contains


a specific color which is used to render its shade as soon as the cell has
_shadeColor Add a ShadedCell class as an extension of Cell and containing a shadeColor class field
been drawn on the canvas. storing the shade of the cell.

The EdgeCell is an extension of a ShadedCell. It contains a color of • Add a ShadedCell(MazeCanvas mazeCanvas, int row, int col, Color shadeColor)
EdgeCell constructor to the class. After calling its super() with the parameters it needs, use the
choice, used for shading cells bordering the maze. It also keeps track of mazeCanvas and the shadeColor to draw the shade of this cell. Save the shadeColor
_edges[]
the edges of the cells bordering the maze such that they can be removed parameter in the corresponding class field.
_edgeColor
from the list of walls returned by the cell’s getWalls() accessor. This will
getWalls() be needed later, when implementing the exploration algorithms.
Add an EdgeCell class as an extension of ShadedCell. It contains a listOfEdges as a data
Determining the edge sides of this cell is as field and an edgeColor constant set to a color of your choice.
EdgeCell(mazeCanvas, row, col)
.. simple as testing the row and column for 0 and
for the size of the grid (given by the • Add an EdgeCell(MazeCanvas mazeCanvas, int row, int col) constructor to the class. It
if first row then calls its super() with the same parameters, plus the chosen edgeColor. In addition, it
add Side.Top to listOfEdges mazeCanvas). These edges need to be removed uses the mazeCanvas, row and col to determine the maze edges for this cell and add
else if last row then
add Side.Bottom to listOfEdges
from the list of walls of this cell, as shown in them to the listOfEdges data field.
endIf the pseudo-code below: • Override the getWalls() method from the Cell class and have it remove the maze edges
getWalls()  walls
(available in the listOfEdges) from the list of walls it returns.
if first col then
add Side.Left to listOfEdges walls = super.getWalls()
else if last col then for each edge in listOfEdges do
add Side.Right to listOfEdges remove edge from walls
endIf endFor Verify all MazeCheckpoint2a.java tests are passing.
 return walls

Ref: classes UML diagram :
Checkpoint 2b: initialize the Maze.
Think  Code
Having the Cell and EdgeCell classes, the Maze class now needs to define a grid of cells as a class In the Maze class:
data field. There are two steps in this process: • Add a new gridOfCells data field of type Cell[][].
1. Creating the grid means creating a new two- • In the Maze(MazeCanvas mazeCanvas) constructor create a two-dimensional array of
Maze Cell dimensional array of type Cell[][] and save it in the Cells sized to the number of rows and columns of the mazeCanvas parameter. Save
_mazeCanvas
class data field. Its number of rows and columns are the array in the gridOfCells class data field.
_grid[][]
ShadedCell given by the size of the MazeCanvas instance, which • Add a new method initialize(). It takes no parameters and returns no value. In it,
getRows(), getCols() is already a data field in the Maze class. implement the algorithm described on the left to fill in the gridOfCells with the
getCell(), initialize() correct Cell instances.
Creating the grid can be implemented inside the
EdgeCell Maze class constructor. • Add a new method getCell(int row, int col). It takes a row and column location as
parameter and returns the Cell instance at that location. This method is going to be
needed later.
2. Initializing the grid means creating a new instance of type Cell for each of the locations in
the grid. If the location is on the edge, then the actual instance is of type EdgeCell
otherwise is a regular Cell. The polymorphic capability of Java allows for both to be “seen”
as Cell objects since EdgeCell is-a ShadedCell which is-a Cell.
This can be implemented as part of a new initialize() method of the Maze class. The In the Program class, in the main() method you should already have a Maze instance
pseudo-code for this method looks like below: created and used for calling into its genSnake() method. This was done as part of
Checkpoint 1.
Maze.initialize() Notice that Cell and EdgeCell instances are drawing
for each [row, col] in the grid themselves on the maze canvas when they are • Replace the call to genSnake() with a call to its new initialize() method.
do created. This means that once the maze
if [row, col] is on edge then
grid[row,col]  new initialization is complete, the canvas will
EdgeCell automatically be filled with a drawing like below:
else Run and verify the program. You should see a drawing like the one on the left.
grid[row,col]  new Cell
endIf
endFor Verify all MazeCheckpoint2b.java tests are passing.

Notice that unlike the genSnake() method from before,
now the Maze class is no longer in charge of doing any
drawing. It is delegating this task to the various Cell
objects which will draw themselves based on their
specific type. From the Maze point of view, they are all
abstracted out as Cells.

Ref: classes UML diagram :


Checkpoint 3a: the BlockCell class.
Think  Code
The maze includes another special kind of cells: the block cell. These are a small subset (<5%) of Add a BlockCell class to the project as an extension of the ShadedCell and containing a
blockShadeColor class constant set to LIGHT_GRAY.
its interior cells. They represent areas in the maze which cannot be passed through: they have
walls on all sides and are drawn with a light-gray shade. As such, a block cell is nothing more • Add a BlockCell(MazeCanvas mazeCanvas, int row, int col) constructor to the class.
than as a specialized version of a shaded cell. Inside, call the super() constructor providing to it the same parameters plus the
blockShadeColor value.
In OOP terminology, a block cell is modeled as the
Cell BlockCell class, an extension of the ShadedCell class.

ShadedCell A ShadedClass is drawn in its class constructor. As such,


drawing a BlockCell at a given row and column can be
done by calling its super class constructor with a light-gray In the Maze class, modify the initialize() method by implementing the logic in the pseudo
shade color added to its list of parameters. code described on this page.
EdgeCell BlockCell
_blockShadeColor The code drawing the maze is in the Maze class initialize()
Run and verify the program.
method which now needs to be updated.

The simplest way to incorporate BlockCells in it is to modify the initialize() method as follows: Verify all MazeCheckpoint3a.java tests are passing.
When a random value is <= 0.05 and the count BlockCells generated so far is less than 5% of the
count of interior Cells, add to the grid a new BlockCell instead of a regular Cell and update the
count tracking their number. This is shown in the pseudo code below:

When all is done, running the program Extra challenges:


Maze.initialize()
count  5% of all interior cells
should draw a maze like below: How would you modify the code to guarantee that exactly 5% of the interior cells
for each [row, col] in the grid do are BlockCells?
if [row, col] is on edge then
grid[row,col]  new EdgeCell How would you guarantee that no two BlockCells are adjacent to each other?
else if rnd < 0.05 and count > 0
then Hint: A more sophisticated way to select the BlockCells is to start by constructing a
grid[row,col]  new BlockCell
count  count - 1
separate list of all the interior cell locations, then randomly eliminate locations from that
else
list until it contains no more than the desired number. The remaining locations would
grid[row, col]  new Cell then be replaced in the grid by BlockCell objects.
endIf
endFor

Ref: classes UML diagram :


Checkpoint 3b: Adding the EntryCell and ExitCell to the Maze.

Think  Code
Cell
There are two more kinds of cells that need to be implemented: The cell Add an EntryCell class and an ExitCell class to the project, as an extension of the
modeling the entrance and exit from the maze. These are the EntryCell ShadedCell EdgeCell, each containing an entryShadeColor and exitShadeColor class constants, set to a
and the ExitCell. There’s only one instance of each, located on the edge color of your choice, different than all the other.
of the maze, so these classes are natural extensions of the EdgeCell. • Add class constructors EntryCell(MazeCanvas mazeCanvas, int row, int col) and
Each has a different shade, which is also different than any other EntryCell(MazeCanvas mazeCanvas, int row, int col). After the call to super() use the
EdgeCell BlockCell mazeCanvas to change the shade of the cells to their respective colors.
shade used already (edge or block).

Like in the previous case, drawing an EntryCell or an


ExitCell means drawing the underlining EdgeCell then EntryCell ExitCell
customizing shade color. This is all done in the class _entryShadeColor _exitShadeColor In the Maze class, modify the initialize() method by implementing the logic in the pseudo
constructors. code described on this page.
With the EntryCell and ExitCell classes implemented, we need to modify the initialize() method Run the program. At this point the maze should show all cells: Edge, Block, Entry
of Maze in order to place one instance of each along the edge of the maze. and Exit and should be ready for designing the generating and solving algorithms.

One way of doing it is as follows:


• Calculate the number of cells on the perimeter of the maze: nPerimeter.
• Generate two distinct random numbers, iEntry and iExit, in the range [0 .. nPerimeter).
• In the loop generating the cells, keep a counter for the cells on the edge. If the counter Add two accessor methods to the Maze class, returning the unique EntryCell and ExitCell
instances: getEntryCell() and getExitCell(). Those are going to be needed later.
matches iEntry or iExit, generate an EntryCell or ExitCell instead.
Verify all MazeCheckpoint3b.java tests are passing.
To the right you can see the Maze.initialize()
pseudo-code implementing this logic: nPerim  2 * rows + 2 * cols – 4
iEntry and iExit  random nums in [0 ..
Example: In the maze below, the nPerim)
edgeCount  0 Extra challenge:
perimeter count is 20, the iEntry is for each [row, col] in the grid do
3 and iExit is 17. if [row, col] is on edge then How would you modify the code to guarantee that the two numbers iEntry and
if edgeCount is iEntry then iExit are not only distinct, but also farther away from each other?
grid[row,col]  new EntryCell
0 1 2 3 4 5 6 else if edgeCount is iExit then Hint: Generate a random iEntry number. To have iExit at a ¼ perimeter distance from
grid[row,col]  new ExitCell iEntry, its range will be [iEntry + ¼ * nPerim, iEntry + ¾ * nPerim). Apply a modulo nPerim
7 8
else
to its value to guarantee it will not get out of bounds.
9 10 grid[row, col]  new EdgeCell
endIf
11 12 increment edgeCount
else ...
13 14 15 16 17 18 19 ...

Ref: classes UML diagram :
Checkpoint 4a: the Generator class building blocks.
Think  Code
At this moment, the maze is initialized as a grid of various kinds of cells. To generate it, we need In the Cell class add a visited boolean class field, initialized to false.
to randomly break down the walls separating them, such that in the end there is a path leading • Add the getVisited() and setVisited(boolean visited) accessor and mutator methods.
from the EntryCell to the ExitCell. In “human” words imagine you are parachuted into the center
of the EntryCell and you follow a very simple checklist of actions:
1. Leave visited a mark in the cell, saying that “you’ve been here”. In the BlockCell class override the getVisited() method and have it always returning true.
2. Choose a random wall, leading to a cell you have not visited before (initially true for all cells). This way, BlockCells are pre-marked as visited, will not be stepped in so will be left with all
walls intact, making them impassable, as per specification.
3. Break the walls separating the cell you’re in and the chosen cell.
4. Step into the new cell and repeat the process  go to step (1).
5. If you can’t find a wall to break, return to the previous cell and do it again go to step (2). In the Maze class add a getNeighbor(Cell cell, Side side) method. The method returns a
reference to the cell neighboring the one given as parameter on the given side. The Maze
The whole process finishes when you are back Generate.run(cell, fromSide)
mark cell as visited
class contains the grid and the given cell provides its location through its getRow() and
to the EntryCell and there are no more walls to remove cell’s wall fromSide
getCol() methods.
break without getting into a cell visited already. get and shuffle the cell’s list of
walls
foreach side in the list of walls do
The pseudo code for this logic can be seen on Add a new Generator class to the project containing a mazeCanvas instance and a maze
get neighbor from that side
the right and will be coded in a new Generator if neighbor is not visited then
instance as class fields. This class is the engine generating the maze by removing the walls
class, the “engine” for generating the maze. remove cell’s wall on that side between its cells. It uses the mazeCanvas to draw the exploration path.
To get there we need to take care of a few Generate.run(neighbor, Add a Generator(MazeCanvas mc, Maze m) constructor and save the parameters in
opposite(side))

building blocks first: endIf
the class fields.
endForeach • Add a shuffle(List<Side> sides) method shuffling the given list of sides and returning
 the shuffled result.
 Track whether a cell has been visited or not: this is naturally a visited field in the Cell class
(since it applies to all kinds of cells), paired with the necessary accessor and mutator. • Add a getOpposite(Side side) method, returning the opposite side for the one given as
parameter. I.e. getOpposite(Side.Left) returns Side.Right and
 Find out the neighboring cell on a given side: knowing the current cell and the side of the getOpposite(Side.Bottom) returns Side.Top.
wall to break we need to find the next cell instance to go to. • Add a run(Cell cell, Side fromSide) method, returning a boolean. For now, let it return
 Choose randomly the walls to break: the list of walls returned by Cell.getWalls() can be false. It is going to be needed in the next checkpoint. This method can be private.
shuffled randomly such that next steps are random and thus the maze pattern is random. • Add a run() method returning a boolean. It calls the overloaded run method passing
to it the EntryCell of the maze and Side.Center and it returns its result.
 Find out the side opposing a given side: stepping out of a cell in a given direction (i.e.
towards Left) means stepping into the next cell from the opposite direction (i.e. from the
Right). Keeping track of these allows us to draw the path later.

This checkpoint is all about coding these building blocks. Verify all MazeCheckpoint4a.java tests are passing.
Checkpoint 4b: the Generator algorithm.
Think  Code
This checkpoint is about completing the maze generating logic which is implemented in the run() In the Program class, in the main() method you should already have a maze instance of
method of the Generator class. Its client code is wired in the main() method of the Program class: class Maze and a mazeCanvas instance of MazeCanvas. They were created as part of
Checkpoint 1. You should also have a call to maze.initialize() method, done as part of
To do its job, the generator instance Program.main() Checkpoint 2b.c
needs the Maze instance holding all the create new mazeCanvas instance • Create a generator instance of the class Generator, right after the line initializing the
data of the maze, and the MazeCanvas create new maze instance  mazeCanvas maze. Pass the maze and mazeCanvas instances as its parameters.
instance needed for rendering the open mazeCanvas
initialize maze • Call the generator’s run() method right after the generator initialization.
progress of the generating logic. Those
create new generator  mazeCanvas, You can add a call to mazeCanvas.breakLeap() after these two lines such that the
are provided in the Generator class
maze execution is suspended once the maze is generated and before the program terminates.
constructor.
generator.run()
close mazeCanvas
Two more methods need to be  In the Generator class, add a generatePathColor class constant defining the color of the
implemented in the Generator class: run() exploration path shown during maze generation. Set it to a color of your choice.
and run(cell, fromSide). run() is the starting point, calling the run(cell, fromSide)
with the entryCell of the maze, available through maze’s • Implement the run(Cell cell, Side fromSide) method from the pseudo code.
getEntryCell() method, and Side.Center as the first • Use generatePathColor when drawing the path components inside the cell.
Generate.run()
direction of movement. This is akin to: You can add one or more mazeCanvas.breakStep(millis) calls inside the method such that
Generate.run(
maze.getEntryCell(),
“you just got parachuted in middle of the EntryCell”. you can visualize the progression of the algorithm in a step-by-step manner.
Side.Center)
 Generate.run(cell, fromSide)
mark cell as visited
draw in cell path fromSide and If everything was implemented
Side.Center correctly, at this point you are
The run(cell, fromSide) pseudo code,
remove cell’s wall fromSide able to generate mazes and
shown to the right, implements the
get and shuffle the cell’s list of trace the algorithm progression
recursive algorithm for generating the walls like in these images:
maze. foreach side in the list of walls do
get neighbor from that side
Notice the highlighted code lines. They if neighbor is not visited then
are using the mazeCanvas class field in draw in cell path to side
order to draw the path into the cell, remove cell’s wall on that side Run and verify the program. Trace the execution by pressing the ‘S’ key and
showing the steps to and back from each Generate.run(neighbor, observe the recursive and backtracking steps of the exploration.
opposite(side))
neighboring cells. When the cell Generate mazes of different sizes using different shades and path colors.
erase cell path to side
exploration is complete, the path going endIf
through it is erased. endForeach Verify all MazeCheckpoint4b.java tests are passing.
erase Side.Center and path fromSide
Checkpoint 5a: the Solver class building blocks.
Think  Code
So far, the maze contains an Entry and an Exit cell, is initialized and generated. This checkpoint is In the Cell class:
all about implementing the algorithm for finding the path from the EntryCell to the ExitCell. • Add a getPaths() method taking no parameters and returning the listOfPaths available
from this cell. These are all the cell’s Sides not bordered by a wall. Start with a new list
Similar to the generator “engine”, the solving logic containing all four sides, then remove from it all the wall sides of the cell. These are
can be coded in a new Solver class. An instance of Solver.run(cell, fromSide) available in the cell’s listOfWalls. The method returns the resulting list.
mark cell as visited
this class - the solver - needs only a reference to if cell is the Exit then
the maze and to the mazeCanvas used for return true
drawing the progress. The pseudo code for this endIf In the EdgeCell class:
get and shuffle the cell’s list of
algorithm is shown to the right: • Overload the getPaths() method such that it removes the listOfEdges from the
paths
foreach side in list of paths do listOfPaths returned by super.getPaths(). This is needed because an EdgeCell does not
Following the pattern of the Generator class, the get neighbor from that side have walls on the sides edging the maze, but those openings are not to be considered
if neighbor is not visited then part of the possible paths out of the cell.
Solver class implements the solving logic through
Solve.run(neighbor,
the run() and run(cell, fromSide) methods. opposite(side))
if maze is solved then Add a new Solver class to the project containing a mazeCanvas and a maze as class fields.
Solver.run() return true
This is the engine solving the maze by stepping from one cell to the other, starting from the
mark all cells as not visited endIf
Solve.run(maze.getEntryCell(), endIf EntryCell, until the ExitCell is reached..
Side.Center) endForeach
• Add a Solver(MazeCanvas mc, Maze m) constructor and save the parameters in the
 return false

class fields.
The first run() method is the starting point, calling the second one with the maze’s EntryCell as the Add a run(Cell cell, Side fromSide) method, returning a boolean. For now, let it return

starting location and the Side.Center as the initial direction of movement. This is akin to: false. It is going to be needed in the next checkpoint.
“You’re parachuted in the center of the EntryCell. Find your way to the ExitCell starting from here”.
• Add a run() method returning a boolean. In it, in a loop, reset the visited state to false
for all the maze‘s cells. Then, call the overloaded run method passing to it the EntryCell
Most of the building blocks for implementing this algorithm have been put in place when the maze of the maze and Side.Center and it returns its result.
was generated, only a few more need to be coded. The complete list is as follows:
• Copy the shuffle() and getOpposite() methods from the Generator class into the
 Track the cell visited state: available via Cell’s getVisited(), setVisited() methods. Solver class.
 Get the cell’s neighbor on a given side: available via Maze’s getNeighbor() method. Notice: copying code is generally to be avoided. The generating and solving algorithms will
 Get the opposite of a given side: available in Generator’s getOpposite() method. be refactored later in a generic maze exploration algorithm, eliminating this redundancy.
 Get the list of possible paths from a given cell: these are sides of a cell without a wall. This
method fits in the Cell class, with some special handling (overload) in the EdgeCell. class.
 Reset the visited marker across for all cells: when the maze was generated, all cells were
marked as visited. Solving involves tracking their visited state again, so, before starting the Verify all MazeCheckpoint5a.java tests are passing.
exploration again it needs to be reset to not_visited.
Checkpoint 5b: the Solving algorithm.
Think  Code
This checkpoint is about completing the maze solving logic which is implemented in the run() In the Program class, in the main() method you should already have a maze, mazeCanvas
method of the Solver class. Its client code is wired in the main() method of the Program class. and a generator instances used to initialize and generate the maze (done as part of
Checkpoints 2b and 4b).
Program.main() So far, the client code creates and opens a
create new mazeCanvas instance mazeCanvas, creates and initializes a maze object, • Create a solver instance of the class Solver, after generator.run() call which is
create new maze instance  generating the maze. Pass the maze and mazeCanvas instances as its parameters.
mazeCanvas then creates a generator object and calls its run()
open mazeCanvas method to generate the maze. • Call the solver’s run() method (the overloaded one taking no parameters) right after.
initialize maze You can add a call to mazeCanvas.breakLeap() after these two lines such that the
create new generator  mazeCanvas, To solve the maze, the client code only needs to execution pauses once the maze is solved and before the program terminates.
maze create a solver object and call its run() method as
generator.run()
create new solver  mazeCanvas, maze
shown to the left.
solver.run() In the Solver class, add a fwdPathColor and a bktPathColor class constants defining the
The algorithm solving the maze is implemented
close mazeCanvas color of the forward and backgrack exploration paths as shown in the pseudocode on the
in
 the overloaded run(cell, fromSide) method of the Solver class: given a cell and the side that cell left. Set them to two different colors of your choice.
was entered, the algorithm needs to identify • Implement the run(Cell cell, Side fromSide) method from the pseudo code.
Solver.run(cell, fromSide)
all possible “next steps” from that location. mark cell as visited • Use fwdPathColor when drawing the path components when stepping inside a cell, or
Those are governed by a few simple rules: draw fwdPath fromSide and Side.Center forward, to a neighboring cell.
 Can only go to a neighboring cell to if cell is the exit then
• Use bktPathColor when the recursive call didn’t result in a solution, such that the
return true
which a path exists (no wall on that side). explored part of the maze remains visible.
endIf
 Can only go to a cell which has not been get and shuffle the cell’s list of You can add one or more mazeCanvas.breakStep(millis) calls inside the method such that
visited before. paths you can trace the progression of the algorithm in a step-by-step manner.
foreach side in list of paths do
A valid next step is attempted. If that doesn’t get neighbor from that side
lead to the ExitCell, another step is tried if neighbor is not visited then
until either a path is found (return true) or draw fwdPath to side If everything was implemented
all next steps were explored unsuccessfully Solve.run(neighbor, correctly, at this point you are able to
(return false). When taking a next step, the opposite(side)) solve mazes and trace the exploration
algorithm runs again, recursively, on the if maze is solved then
logic like in this image:
neighboring cell. return true
endIf
Entering a cell and exploring a next step, is draw bktPath from side
drawn as a “forward path”, in some color. endIf
Coming back after exploring a next step is a endForeach Run and verify the program.
draw bktPath for Side.Center and
fromSide
“backtrack path”, drawn in a different color, unless that path led to the ExitCell. Verify all MazeCheckpoint5b.java tests are passing.
return false

Final 6a: refactor Generator and Solver. The Explorer class. A callback method is just an informal name given to a normal
method which is called from other parts of the code when
certain well-defined event or conditions occur.

Think  Code
For this checkpoint, review your implementations for generating and solving the maze. Notice how In each of the Generator and Solver class add the following callback protected methods:
similar they are. In both cases, there are different actions taken when a cell is entered, when • onEnterCell(Cell cell, Side side) - takes as parameters the cell being entered and the
determining the next possible steps, when stepping forward, stepping back and when exiting. side where it is entered from.
Other than that, the algorithms gluing these actions are identical. • onGetNextSteps(Cell cell) – takes as parameter the cell being inspected for the next
Let’s refactor the code by creating and calling these methods in each of the Generator and Solver maze generation step. Method returns the resulting list of walls in the Generator’s case,
classes. and the list of paths in the Solver’s case.
Generator/Solver.run(cell, fromSide) • onStepForward(Cell cell, Side side) – takes as parameters the cell and its side where
mark cell as visited the maze generating algorithm should step further.
done  onEnterCell(cell, fromSide) onStepBack(boolean done, Cell cell, Side side) : takes as parameter a cell and the side

list of paths  onGetNextSteps(cell) that was just explored. It also takes the boolean done, telling if the exploration is
foreach side in list of paths do complete or not (in Generator’s case always false; in Solver’s case, true if a path to the
if not done then exit was found, and false otherwise).
get neighbor from that side
if neighbor is not visited then • onExitCell(boolean done, Cell cell, Side side) : takes same parameters a cell and the
onStepForward(cell, side)
side on which the cell is about to be exited. It also takes the Boolean done, telling if the
exploration is complete.
done  Explorer.run(neighbor,
opposite(side)) In each of these methods, copy the code parts from the corresponding
onStepBack(done, cell, side) run(Cell cell, Side side) method of their classes, as described on the left side of this slide.
endIf
endIf
endForeach
onExitCell(done, cell, fromSide);
return done In the Solver class modify the run(Cell cell, Side side) method by replacing all code parts
 copied into the new methods, with the corresponding method calls and implementing the
pseudo code on this slide.
Generator Solver
Draws the cell’s path fromSide. Returns false Draw the cell’s path fromSide. Returns true
since there is no “exit” during maze if the cell is the exit from the maze. In the Generator class modify both the run() and the run(Cell cell, Side side) method by
generating process (all maze needs exploring) copying over the same methods from the Solver class.
Returns the cell’s list of paths, shuffled.
Returns the cell’s list of walls, shuffled. Draw the cell’s path side leading out to the At this point, the project is functionally unchanged, but the Generator and Solver classes
Draws the cell’s path side leading out and next cell that has not been explored yet. contain a fair amount of identical code. This is going to be factored out next, into a new
removes the wall separating it from next cell. Explorer class.
If not done, draws the path side coming
Erases the path side coming back into the cell. back into the cell, in a distinct color.
Erases the path fromSide, leaving the cell. If not done, draws the path leaving the cell, Verify all MazeCheckpoint6a.java tests are passing.
in a distinct color.
Final 6b: refactor Generator and Solver. The Explorer class.
Think  Code
Add an Explorer class to the project, having maze, mazeCanvas, fwdPathColor and
The Generator and Solver classes are now restructured in a way that highlights their common state bktPathColor as protected class fields. In it:
(fields) and behavior (methods):
• Add a constructor Explorer(MazeCanvas mc, Maze m, Color fwdColor, Color bktColor)
initializing the class fields from the given parameters.
Explorer  Both classes contain a reference to the maze and mazeCanvas
_mazeCanvas and a fwdPathColor marking the path forward as the • Copy the shuffle() and getOpposite() methods from either the Generator or Solver and
change their accessibility from private to protected.
_maze exploration process advances.
_fwdPathColor, _bktPathColor  The Solver class contains a bktPathColor constant for marking • Copy both the run() and run(Cell cell, Side fromSide) methods from either the
the path backtracking when the exploration cannot continue. Generator or Solver since their implementation is identical in both.
getOpposite()
shuffle()  Both classes contain identical implementations for shuffle(),
getOpposite(), run() and recursive run() methods. Add the callback methods to the Explorer class as protected methods:
onEnterCell()
onGetNextSteps()  Both classes contain implementations for the callback • onEnterCell(Cell cell, Side side) – draws the side path and center of the cell. In
onStepForward() methods onEnterCell(), onGetNextSteps(), onStepForward(), fwdPathColor (if color is not null). Returns false.
onStepBack() onStepBack() and onExitCell() which are very similar one to • onGetNextSteps(Cell cell) – returns a new and empty List<Side>.
onExitCell() the other. • onStepForward(Cell cell, Side side) – draws the side path of the cell, in fwdPathColor.
run()
This commonality can be extracted into one class, the • onStepBack(boolean done, Cell cell, Side side) – if not done, either draws or erases the
side path of the cell, depending on whether bktPathColor is set to a non-null value.
Explorer, working at a higher level of abstraction:
• onExitCell(boolean done, Cell cell, Side side) – if not done, either draws or erases the
Given a mazeCanvas, a maze and optionally a side path and center of the cell, depending on whether bktPathColor is not null.
Generator Solver fwdPathColor and a bktPathColor an Explorer
instance navigates through all the cells of the maze Make the Generator and Solver classes extensions of the Explorer class, delete their class
onEnterCell() onEnterCell() fields (now encapsulated in their super class) and modify their methods as follows:
drawing the progress forward and the steps
onGetNextSteps() onGetNextSteps()
onStepForward() backwards as needed. Exploration completes when • Delete shuffle() and getOpposite() and both run() methods from each class.
all cells have been visited. • Delete onStepBack() and onExitCell() methods from the Generator class.
With this class designed for such a generic exploration task, the Solver and Generator become • Delete onStepForward(), onStepBack() and onExitCell() methods from the Solver class.
very simple extensions of the Explorer, overriding only some of the callback methods, as needed: • Modify constructors and callback methods by leveraging their super() implementations.

Generator overrides Solver overrides


 onEnterCell(): removes the wall on the  onEnterCell(): returns true if the cell is the
side the cell is entered from. ExitCell of the maze, shortening the default
 onGenNextSteps(): returns the list of sides complete exploration of the maze.
having a wall that may be torn down.  onGenNextSteps(): returns the list of sides
Run and verify the program.
 onStepForward(): removes the wall in the not having a wall as these may be next
direction of the next step. steps in the search for the solution path. Verify all MazeCheckpoint6b.java tests are passing.
the Maze project

the MazeCanvas GUI framework


The Graphical User Interface (GUI) for this project is supported by a set of classes already The Java code used for rendering the maze cell at row 1, column 2 in the example on the
available in your startup project. The drawing-lib-1.0-shaded.jar package contains the left is as follows:
class MazeCanvas through which you can draw all the graphical elements making up the
maze, as follows:
10 rows
16 columns Cell size in
The Maze is represented as a two-dimensional grid of cells. Each Cell has four sides. On pixels
each side, the cell may or may not have a Wall. The cell’s area can have a specific shade
(background) color. On the cell’s area you can render a path connecting any of the four
sides of the cell to its center.

Using the MazeCanvas class you can draw or erase any of the four walls of a cell or set
the cell’s shade to a color of your choice. You can also draw or erase a path through the
cell by connecting its center to any of the four edges.

The graphical elements of a maze cell are reflected below:


Erases the bottom wall of
the cell at row 1 and column 2.
Cell at row 1, col 2

Shade Wall
lightYellow Side.Top
Center
darkRed The side of the wall
being erased.

The color of the


Path cell’s shade.
Side.Left Wall
Side.Right

The side and the color of the


Path path being drawn.
Side.Bottom

Ref: MazeCanvas API set:


the Maze project edu.ftdev.Maze
Class MazeCanvas
• java.lang.Object

MazeCanvas
• edu.ftdev.DrawingFactory

the API set • edu.ftdev.Maze.MazeCanvas

All Implemented Interfaces:


The MazeCanvas class is fully documented via javadocs at this link: MazeCanvas (drawing-lib 1.0 API)
DbgControls, FrameControls, Closeable,
a summary of which is shown below: AutoCloseable
Constructor and Description
MazeCanvas()
Constructs a Maze canvas of 16 rows and 24 columns.
MazeCanvas(int nRows, int nCols, int cellWidth)
Constructs a Maze canvas of a given number of rows and columns.

Method and Description


void clear()
Clears the canvas area of this maze. The window containing this canvas is brought back to its default state as it was when it was first
opened.
boolean drawCell(int row, int col)
Draws a cell on the rendering canvas of this maze. By default the cell is surrounded by walls on all sides, it has a white shade,
no center and no connecting paths.
boolean drawCell(int row, int col, Color color)
Draws a cell on the rendering canvas of this maze. The cell is surrounded by walls on all sides, it has the given shade color,
no center and no connecting paths.
boolean drawPath(int row, int col, MazeCanvas.Side side, Color color)
Draws a path segment on a given side of a cell, in the specified color.
boolean drawShade(int row, int col, Color color)
Draws the shade (background) of a cell in a given color.
boolean drawWall(int row, int col, MazeCanvas.Side side)
Draws a wall on a given side of a cell.
boolean erasePath(int row, int col, MazeCanvas.Side side)
Erases a path segment from a given side of a cell.
boolean eraseShade(int row, int col)
Erases the shade (background) of a cell to the default white color.
boolean eraseWall(int row, int col, MazeCanvas.Side side)
Erases a wall from the given side of a cell.
int getCols()
Provides the number of columns in the grid of cells for this maze.

You might also like