3

I am building a flash game that requires correct pathfinding. I used the pseudo code in this tutorial and a diagonal heuristic. I did not closely follow their code. The language is ActionScript 3 and I am also using flashpunk libraries.

My current issue is that the program is producing a path that is clearly not the shortest path possible. Here is a screenshot showing the problem:

enter image description here

The grey blocks are non traversable, the green blocks mark nodes that have been "visited" and the blue blocks show the path generated by the algorithm.

It looks as if the diagonal travel cost is equal to the non-diagonal travel cost, despite my attempt to make the diagonal cost higher (1.414).

This is the overall algorithm implementation.

function solveMaze() {

    // intitialize starting node
    startingNode.g = 0;
    startingNode.h = diagonalHeuristic(startingNode, destinationNode);
    startingNode.f = startingNode.g + startingNode.h;

    // Loop until destination node has been reached.
    while (currentNode != destinationNode) {

        if (openNodes.length == 0) {
            return null;
        }

        // set lowest cost node in openNode list to current node
        currentNode = lowestCostInArray(openNodes);

        //remove current node from openList
        openNodes.splice(openNodes.indexOf(currentNode), 1);

        //find 8 nodes adjacent to current node
        connectedNodes = findConnectedNodes(currentNode);

        //for each adjacent node,
        for each (var n:Node in connectedNodes) {
            // if node is not in open list AND its not in closed list AND its traversable
            if ((openNodes.indexOf(n) == -1) && (closedNodes.indexOf(n) == -1) && n.traversable) {

                // Calculate g and h values for the adjacent node and add the adjacent node to the open list
                // also set the current node as the parent of the adjacent node

                if ((n.mapX != currentNode.mapX) && (n.mapY != currentNode.mapY)) {
                    cost = 1.414; 
                } else {
                    cost = 1;
                }
                if(n.g> currentNode.g + cost){
                n.g = currentNode.g + cost;
                n.f=calculateCostOfNode(n);
                n.parentNode =currentNode;
                openNodes.push(n);
               }
            }
        }
        // turn current node into grass to indicate its been traversed
        currentNode.setType("walked_path");

        //var temp2:TextEntity = new TextEntity(n.h.toFixed(1).toString(), 32 * currentNode.mapX, 32 * currentNode.mapY);
        //add(temp2);

        // add current node to closed list
        closedNodes.push(currentNode);
    }

    // create a path from the destination node back to the starting node by following each parent node
    var tempNode:Node = destinationNode.parentNode;

    tempNode.setType("path2"); // blue blocks
    while(tempNode != startingNode){
        tempNode = tempNode.parentNode;
        tempNode.setType("path2");

    }
}

These were the helper functions used:

function findConnectedNodes(inputNode:Node):Array {
    var outputArray:Array=[];

    // obtain all nodes that are either 1 unit away or 1.4 units away.
    for each (var n:Node in listOfNodes){
        if ((diagonalHeuristic(inputNode, n) == 1)||(diagonalHeuristic(inputNode, n) == 1.4) {
            outputArray.push(n);
        }
    }
    return outputArray;
}

public static function diagonalHeuristic(node:Node, destinationNode:Node, cost:Number = 1.0, diagonalCost:Number = 1.4):Number {
    var dx:Number = Math.abs(node.mapX - destinationNode.mapX);
    var dy:Number = Math.abs(node.mapY - destinationNode.mapY);

    if (dx > dy) {
        return diagonalCost * dy + (dx - dy);
    }else {
        return diagonalCost * dx + (dy - dx);
    }       
}

function lowestCostInArray(inputArray:Array):Node {
    var tempNode:Node = inputArray[0];
    for each (var n:Node in inputArray) {
        if (n.f < tempNode.f) {
            tempNode = n;
        }
    }
    return tempNode;
}

I can provide the project source code if it would help.

Don Zhang
  • 55
  • 4
  • [Here is the swf.](http://puu.sh/iPWwg/39f1dde5e2.swf) You can click the tiles to mark them as non traversable. Also I found another [question](http://stackoverflow.com/questions/29871851/a-pathfinding-not-taking-shortest-path) on stackoverflow that had the same problem, but his solution doesn't apply to me I think? – Don Zhang Jul 07 '15 at 04:54
  • 1
    Where is the code for `calculateCostOfNode`? – Wheeldog Jul 07 '15 at 06:47
  • your maze is more suited for map approach instead of graph. see [A* in C++](http://stackoverflow.com/a/23779490/2521214) – Spektre Jul 07 '15 at 07:14
  • `f` should be `g + h`. You don't seem to use `h` in your `solveMaze` function, except for the first node. There should be no function that finds it, it should only be one line. You got it right for the first node at the beginning of `solveMaze`. – IVlad Jul 07 '15 at 07:32
  • `function calculateCostOfNode(inputNode:Node) { var f:Number = inputNode.g + diagonalHeuristic(inputNode, destinationNode); return f; }` Here is the code for calculateCostOfNode. You are right, it is just one line. – Don Zhang Jul 07 '15 at 09:13
  • What happens if, only in the `diagonlHeuristic` function, you change `1.414` to `1.4` (also in `findConnectedNodes` of course)? – IVlad Jul 07 '15 at 10:37
  • The behaviour is the same if I change 1.414 to 1.4 in diagonalHeuristic and findConnectedNodes. If I also change the g cost from 1.414 to 1.4 in solveMaze(), the behavior is also the same. – Don Zhang Jul 07 '15 at 10:53

1 Answers1

1

I see a few potential things wrong.

  1. You are potentially overwriting values here:

            n.g = currentNode.g + cost;
            n.f=calculateCostOfNode(n);
            n.parentNode =currentNode;
            openNodes.push(n);
    

    It should be:

         if n.g > currentNode.g + cost:
            n.g = currentNode.g + cost;
            n.f=calculateCostOfNode(n);
            n.parentNode =currentNode;
            if n not already in openNodes:
                openNodes.push(n);
    

    With n.g initiated to a very large value, or you can do the check like if n not in the open set or n.g > currentNode.g + cost.

    You should remove the check if ((openNodes.indexOf(n) == -1) from where you have it now and put it where I said. If the new g cost is better, you should update it, even if it's in the open list. You only update each node once. If it so happens that you check diagonals first, you will completely ignore side steps.

    This is likely the problem: by ignoring neighbors that are in the open list, you will only update their cost once. It is OK to update their cost as long as they are not in the closed list.

  2. I don't know for sure if this is a valid concern, but I think you're playing with fire a little by using 1.414 in the heuristic function. The heuristic function has to be admissible, which means it should never overestimate the cost. If you run into some floating point issues, you might overestimate. I'd play it safe and use 1.4 for the heuristic and 1.414 for the actual cost between diagonally adjacent nodes.

IVlad
  • 43,099
  • 13
  • 111
  • 179
  • Hi, thank you for your comment. 1. I have implemented the the testing of `n.g > currentNode.g + cost` as per your suggestion. I have not implemented the testing of whether or not it was already in the list of openNodes. It is already a condition for the adjacent node to not be in the open or closed list in order to execute the lines of code discussed in this point. 2. The last line of code in the for loop pushes the current node to the closed list. The indentation may have made things confusing, my fault. 3.Implemented as per your suggestion :) The program still has the same issue. – Don Zhang Jul 07 '15 at 13:08
  • 1
    @DonZhang you should remove the check `if ((openNodes.indexOf(n) == -1)` from where you have it now and put it where I said. If the new `g` cost is better, you should update it, even if it's in the open list. You only update each node once. If it so happens that you check diagonals first, you will completely ignore side steps. This is likely the problem: by ignore neighbors that are in the open list, you will only update their cost once. It is OK to update their cost as long as they are not in the closed list. – IVlad Jul 07 '15 at 15:36