Search code examples
javaalgorithmoopdepth-first-searchmaze

Java DepthFirstSearch Algorithm not working


I have tried to code a depthfirstsearch algorithm to solve a maze. This is what I have so far. I have tried to add as much detail so the logic is understandable:

//Upgraded dfs algorithm that creates an array of nodeclass objects

package depthfirstsearch;

public class DepthFirstSearch {

    public static void main(String[] args) {
       
        int[][] stateArr = 
        {
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 2, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0},
        {0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0},
        {0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0},
        {0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0},
        {0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0},
        {0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0},
        {0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0},
        {0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0},
        {0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0},
        {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
        {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0},
        {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
        //Maze array declaration
        
        int mazeRows = stateArr.length;
        int mazeCols = stateArr[0].length;
        
        //Creates an array of NodeClass objects
        NodeClass[][] mazeArr = new NodeClass[mazeRows][mazeCols];
        
        //Assigning values to the nodes (state, isPath, coords)
        for(int i = 0; i < mazeRows; i++)
        {
            for(int j = 0; j < mazeCols; j++)
            {
                int state = stateArr[i][j];
                mazeArr[i][j] = new NodeClass(i, j, state);    //Assigning coordinates & state (wall, path) to the node              
            }   //End of col for-loop            
        }   //End of row for-loop
        
        //Currently have an array of nodes with coordinates, state and isPath = false
        
        NodeClass currentNode = mazeArr[1][1];  //This is the node we'll be working with. Assigned the value of the starting node at position (1;1)
 
        
        while(currentNode.getState() != 3)  //3 Indicates the end of the maze. While we haven't reached the end
        {
            //Getting the position of the current node
            int row = currentNode.getNodeRow();
            int col = currentNode.getNodeCol();
            System.out.println("Run");
            System.out.println(currentNode);
            
            //We'll be taking the currentNode and checking every unchecked adjacent node:

            //Check up
            if((mazeArr[row-1][col].getState() == 1 || mazeArr[row-1][col].getState() == 3) && (mazeArr[row-1][col].haveTried() == false))
            {
                //Storing the parent node:
                NodeClass temp = currentNode;   //Stores the current node as the node temp
                mazeArr[row-1][col].setParentNode(temp); //Stores the current node as the parent node the new checked node
                
                mazeArr[row][col].setTried(true);   //Sets the current node as tried
                mazeArr[row][col].setIsPath(true);  //Considers the current node as a potential path

                currentNode = mazeArr[row-1][col];  //The new current node is the node above the original node

                System.out.println("Up");   //We moved up
            }
            
            //Check right
            else if((mazeArr[row][col+1].getState() == 1 || mazeArr[row][col+1].getState() == 3) && (mazeArr[row][col+1].haveTried() == false))
            {
                //Storing the parent node:
                NodeClass temp = currentNode;   //Stores the current node as the node temp
                mazeArr[row-1][col].setParentNode(temp); //Stores the current node as the parent node the new checked node
                
                mazeArr[row][col].setTried(true);   //Sets the current node as tried
                mazeArr[row][col].setIsPath(true);  //Considers the current node as a potential path

                currentNode = mazeArr[row][col+1];  //The new current node is the node above the original node

                System.out.println("Right");   //We moved right
            }
            
            //Check down
            else if((mazeArr[row-1][col].getState() == 1 || mazeArr[row-1][col].getState() == 3) && (mazeArr[row-1][col].haveTried() == false))
            {
                //Storing the parent node:
                NodeClass temp = currentNode;   //Stores the current node as the node temp
                mazeArr[row-1][col].setParentNode(temp); //Stores the current node as the parent node the new checked node
                
                mazeArr[row][col].setTried(true);   //Sets the current node as tried
                mazeArr[row][col].setIsPath(true);  //Considers the current node as a potential path

                currentNode = mazeArr[row][col+1];  //The new current node is the node above the original node

                System.out.println("Down");   //We moved down
            }
            
            //Check left
            else if((mazeArr[row][col-1].getState() == 1 || mazeArr[row][col-1].getState() == 3) && (mazeArr[row][col-1].haveTried() == false))
            {
                //Storing the parent node:
                NodeClass temp = currentNode;   //Stores the current node as the node temp
                mazeArr[row-1][col].setParentNode(temp); //Stores the current node as the parent node the new checked node
                
                mazeArr[row][col].setTried(true);   //Sets the current node as tried
                mazeArr[row][col].setIsPath(true);  //Considers the current node as a potential path

                currentNode = mazeArr[row][col+1];  //The new current node is the node above the original node

                System.out.println("Left");   //We moved left
            }
            
            //If there are no paths left: we reached a dead end and need to back track
            else
            {
                mazeArr[row][col].setTried(true);   //Sets the current node as tried
                mazeArr[row][col].setIsPath(false); //Since it's a dead end, we remove it from the potential solution

                currentNode = mazeArr[row][col].getParentNode();    //Sets the current node as the previous node    
            }
            
        }   //End of while-loop for fining the solution
        
        System.out.println("Yay yay, we did it!");
        
    }   //End of main method
    
}   //End of DepthFirstSearch Class

The issue is, I am getting an error message: Exception in thread "main" java.lang.NullPointerException: Cannot invoke "depthfirstsearch.NodeClass.getState()" because "currentNode" is null

I printed the currentNode node but I found that it was, in fact, not null. What could be the problem? I would appreciate any tips. Thank you.


Solution

  • First, you're debugging at the wrong spot. The error occurs in the while condition, so if you want to see that currentNode is null, you'd print it just before that condition gets evaluated, i.e. at the very end of the loop's body.

    The immediate cause is that getParentNode() returns null at the first iteration of the loop. This is odd, because that part of the code -- the final else block -- is not supposed to execute that early in the search process. And yes, there is no parent when you are only starting the search from the very first node at coordinate 1, 1.

    The real cause is that your blocks of code that deal with the 4 different neighbors have several errors. There is a mix of row-1, row+1, col-1 and col+1 that doesn't make sense. Only the first block (for going up) is correct, all the others have inconsistencies, and the one for going down is the worst: none of the neighbor references are pointing at the down neighbor! When you correct all those inconsistencies (really do a thorough check!) it will work.

    A way to make such errors less likely, is to avoid code repetition and deal with the four directions in a loop, making the addition to row and col dynamically determined by the loop variable. Here is how that could look for your main loop:

            while (currentNode.getState() != 3)  
            {
                int row = currentNode.getNodeRow();
                int col = currentNode.getNodeCol();
                currentNode.setTried(true);
                int side;
                for (side = -1; side < 3; side++) {
                    int nextRow = row + side % 2;
                    int nextCol = col + (side - 1) % 2;
                    NodeClass neighbor = mazeArr[nextRow][nextCol];
                    int state = neighbor.getState(); 
                    if ((state == 1 || state == 3) && !neighbor.haveTried()) {
                        neighbor.setParentNode(currentNode);
                        currentNode.setIsPath(true);
                        currentNode = neighbor;
                        break;
                    }
                }
                if (side == 3) { // No neighbor available
                    currentNode.setIsPath(false);
                    currentNode = currentNode.getParentNode();
                }
            } 
    

    NB: I assume that your grid will always have a wall at the outer boundaries, as is the case in your example. If not, you'll need to check whether the coordinates are still in range of the grid.