I was asked to create a Java program that will navigate through a series of mazes. I would like to know if this is the optimal way of using a DFS or is there a better way and I am doing something wrong?
Example of a Maze File:
10 10
1 1
8 8
1 1 1 1 1 1 1 1 1 1
0 0 1 0 0 0 0 0 0 0
0 0 1 0 1 1 1 1 1 1
1 0 1 0 0 0 0 0 0 1
1 0 1 1 0 1 0 1 1 1
1 0 1 0 0 1 0 1 0 1
1 0 1 0 0 0 0 0 0 1
1 0 1 1 1 0 1 1 1 1
1 0 1 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1
This file is then converted into a 2DInt array where 0 are empty spaces and 1 is a wall. The maze can wrap around itself too so you can go from one side to another if the space is 0 on either side. 10 10 is the maze size, 1 1 is starting location and 8 8 is ending location.
I have used the Depth-First-Search to solve this maze, here is the code:
MAIN CLASS
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
public class Main {
static Stack<Node> q = new Stack<Node>(); // Stack is used for Depth First Search Algorithm, Queue can be used to
// convert DFS into a Breadth-first search algorithm.
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
System.out.print("Enter exact maze file location: ");
String fileLocation = reader.nextLine();
reader.close();
ArrayList<String> mazeRaw = new ArrayList<String>();
File file = new File(fileLocation);
try {
String st;
BufferedReader br = new BufferedReader(new FileReader(file));
while ((st = br.readLine()) != null)
mazeRaw.add(st);
br.close();
} catch (IOException e) {
System.out.println("Error: " + e);
}
Maze testMaze = new Maze(mazeRaw); //2D Int array maze constructor
String[][] results = new String[testMaze.maze2D.length][testMaze.maze2D[0].length];
Node p = solveMaze(testMaze, testMaze.returnStart()); //Node p contains p.parent that hold the path through the maze.
testMaze.printMazeResult(results, p, testMaze.returnStart());
}
public static Node solveMaze(Maze maze, Node start) {
q.push(new Node(start.x, start.y, null)); // Adds the starting coordinates to the stack.
while (!q.isEmpty()) // Until the stack is empty continue loop.
{
Node p = q.pop(); // Get the top element from the Stack and assign it to p
// Valid States //-------------------------------------------------------------------------------//
//If Exit found at coordinates p.x,p.y in 2Dint Array return the p Node.
if (maze.getMaze2D()[p.x][p.y] == 9) {
return p;
}
//If x,y location cannot be wrapped on either ends then check if its sparse for formatting purposes.
if (!isWrappable(maze, p) && isSparse(maze, p)) {
maze.getMaze2D()[p.x][p.y] = -1;
Node nextP = new Node(p.x + 1, p.y, p);
q.push(nextP);
continue;
}
//Check if p node is at the border and if the border wraps around the array in a valid way and make sure that the node parent is not at border to avoid loops
if (isWrappingBorder(maze, p.x, p.y, p) != null && isWrappingBorder(maze, p.parent.x, p.parent.y, p.parent) == null) {
q.push(isWrappingBorder(maze, p.x, p.y, p));
}
// 4 Directional Movements //-------------------------------------------------------------------------------//
// South
if (isFree(maze, p.x + 1, p.y)) {
maze.getMaze2D()[p.x][p.y] = -1;
Node nextP = new Node(p.x + 1, p.y, p);
q.push(nextP);
}
// North
if (isFree(maze, p.x - 1, p.y)) {
maze.getMaze2D()[p.x][p.y] = -1;
Node nextP = new Node(p.x - 1, p.y, p);
q.push(nextP);
}
// West
if (isFree(maze, p.x, p.y + 1)) {
maze.getMaze2D()[p.x][p.y] = -1;
Node nextP = new Node(p.x, p.y + 1, p);
q.push(nextP);
}
// East
if (isFree(maze, p.x, p.y - 1)) {
maze.getMaze2D()[p.x][p.y] = -1;
Node nextP = new Node(p.x, p.y - 1, p);
q.push(nextP);
}
}
return null; // If Maze cannot be solved value null is returned;
}
//This method checks if the coordinates adjacent to the Node p are empty to detect sparse input for formatting purposes
public static boolean isSparse(Maze maze, Node p) {
if (maze.getMaze2D()[p.x][p.y - 1] == 0 && maze.getMaze2D()[p.x + 1][p.y] == 0
&& maze.getMaze2D()[p.x + 1][p.y - 1] == 0 || maze.getMaze2D()[p.x + 1][p.y] == 9)
return true;
else
return false;
}
//Checks if the array can be wrapped around at either ends and direction from the current Node p.
public static boolean isWrappable(Maze maze, Node p) {
if (maze.getMaze2D()[0][p.y] == 0 || maze.getMaze2D()[p.x][0] == 0
|| maze.getMaze2D()[maze.getMaze2D().length - 1][p.y] == 0
|| maze.getMaze2D()[p.x][maze.getMaze2D()[0].length - 1] == 0)
return true;
else
return false;
}
//Detect the point where Array wraps around itself and return a Node that comes out on the other side of the wrapped array
public static Node isWrappingBorder(Maze maze, int x, int y, Node parent) {
Node nextNode;
if (x == 0 && maze.getMaze2D()[x][y] != 1) {
if (maze.getMaze2D()[maze.getMaze2D().length - 1][y] != 1)
return nextNode = new Node(maze.getMaze2D().length - 1, y, parent);
}
if (x == maze.getMaze2D().length - 1 && maze.getMaze2D()[x][y] != 1) {
if (maze.getMaze2D()[0][y] != 1)
return nextNode = new Node(0, y, parent);
}
if (y == 0 && maze.getMaze2D()[x][y] != 1) {
if (maze.getMaze2D()[x][maze.getMaze2D().length - 1] != 1)
return nextNode = new Node(x, maze.getMaze2D().length - 1, parent);
}
if (y == maze.getMaze2D()[0].length - 1 && maze.getMaze2D()[x][y] != 1) {
if (maze.getMaze2D()[x][0] != 1)
return nextNode = new Node(x, 0, parent);
}
return null;
}
//Checks if int x, and int y which are passed from Node.x and Node.y values are valid coordinates
public static boolean isFree(Maze maze, int x, int y) {
if ((x >= 0 && x < maze.getMaze2D().length) && (y >= 0 && y < maze.getMaze2D()[x].length)
&& (maze.getMaze2D()[x][y] == 0 || maze.getMaze2D()[x][y] == 9))
return true;
else
return false;
}
}
MAZE CLASS
import java.util.ArrayList;
import java.util.Arrays;
public class Maze {
int[][] maze2D; //
private Node start;
private Node end;
public Maze(ArrayList<String> mazeString) {
this.maze2D = new int[(Integer.valueOf(mazeString.get(0).split(" ")[1]))][(Integer
.valueOf(mazeString.get(0).split(" ")[0]))]; // Assign maze size from the file.
start = new Node(Integer.valueOf(mazeString.get(1).split(" ")[1]), Integer.valueOf(mazeString.get(1).split(" ")[0]), null);
end = new Node(Integer.valueOf(mazeString.get(2).split(" ")[1]), Integer.valueOf(mazeString.get(2).split(" ")[0]), null);
for (int i = 0; i < this.maze2D.length; i++) {
for (int n = 0; n < this.maze2D[i].length; n++) {
this.maze2D[i][n] = Integer.valueOf(mazeString.get(i + 3).split(" ")[n]); // i + 3 to offset first 3 lines of text file.
}
}
this.maze2D[start.x][start.y] = 8; // Assign maze start from the file.
this.maze2D[end.x][end.y] = 9; // Assign maze end from the file.
}
@Override
public String toString() {
System.out.println(Arrays.deepToString(this.maze2D));
return (this.maze2D.toString());
}
public Node returnStart()
{
return start;
}
public Node returnEnd() {
return end;
}
public int[][] getMaze2D() {
return maze2D;
}
public void setMaze2D(int[][] maze2d) {
maze2D = maze2d;
}
public void printMazeResult(String[][] results, Node p, Node start) {
Boolean mazeState = false;
for (int i = 0; i < getMaze2D().length; i++) {
for (int j = 0; j < getMaze2D()[i].length; j++) {
switch (maze2D[i][j]) {
case 0:
results[i][j] = " ";
break;
case 1:
results[i][j] = "#";
break;
case -1:
results[i][j] = " ";
break;
case 9:
results[i][j] = "E";
break;
}
}
}
try {
while (p.getParent() != null) {
p = p.getParent();
if (maze2D[p.x][p.y] == 9)
continue;
results[p.x][p.y] = "X";
}
results[start.y][start.x] = "S";
mazeState = true;
} catch (java.lang.NullPointerException e) {
mazeState = false;
}
for (int i = 0; i < results.length; i++) {
for (int j = 0; j < results[i].length; j++) {
System.out.print(results[i][j]);
}
System.out.println();
}
if (mazeState == true)
System.out.println("- Maze has been solved :) -");
else
System.out.println("- Maze is unsolvable -");
}
}
NODE CLASS
import java.util.ArrayList;
public class Node
{
int x;
int y;
Node parent;
public Node(int x, int y, Node parent) {
this.x = x;
this.y = y;
this.parent = parent;
}
public Node() {
this.x = 0;
this.y = 0;
this.parent = null;
}
public Node getParent() {
return this.parent;
}
public String toString() {
return "x = " + x + " y = " + y;
}
}
OUTPUT:
##########
XS#XXXXXXX
#X######
# #XXXX #
# ## #X###
# # #X# #
# # XX #
# ###X####
# # XXXE#
##########