3

I'm trying to implement an A star searching method for a college work, so i kinda need to make it from scratch, but I'm having some trouble making it work the correct way. Here's a picture of my problem:

gray box = Start, Green = End, Yellow = path

As you can see, it does find a path, but not the easiest one.

Here is my implement:

public List<node> updateAstar(){
    //clear all the lists
    openedNodes.Clear();
    closedNodes.Clear();
    nodesToLook.Clear();
    //check if starpos and endpos are okay
    if(startPos!=null && endPos!=null){
        float F;
        node currentNote=Grid.getNodeAtPos(startPos);

        openedNodes.Add(currentNote);
        int i = 0;
        int size = 100;
        while(currentNote.type!=tilesType.END){
            if(i<=size){ //debugging purpose. prevent infinite loop 
                nodesToLook.Clear();
                foreach(node nearNode in currentNote.getNearestTiles()){
                    if(closedNodes.Find(r => ((r.pos.x==nearNode.pos.x)&&(r.pos.y==nearNode.pos.y)))==null){
                        nodesToLook.Add(nearNode);
                    }
                }

                float bestValue=float.PositiveInfinity;
                node bestNode=new node();

                foreach(node lookingNode in nodesToLook){
                    //check if current node is not on the closed list
                    if((closedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null)
                        &&(openedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null) 
                        && lookingNode.type!=tilesType.BLOCK){
                        //calculate F=G+H

                        //assume path number is 0 for the question purpose
                        F=lookingNode.G[pathNumber]+lookingNode.H[pathNumber];
                        if(F<bestValue){
                            bestValue=F;
                            bestNode=lookingNode;
                        }else
                            closedNodes.Add(lookingNode);
                    }
                }
                openedNodes.Add(bestNode);
                currentNote=bestNode;
                i++;
            }else{
                Debug.Log("Error getting better path");
                break;
            }
        }
    }else Debug.Log("Current path does not have an startpos nor endpos");
    return openedNodes;
}

Here is how I instantiate each node (I save it on a matrix):

coordinate posAux=new coordinate();
this.myNodes=new node[columnNumber,lineNumber];
this.lineNumber=lineNumber;
this.columnNumber=columnNumber;
for(int y=0;y<lineNumber;y++){                      // Y Desce = linhas
    for(int x=0; x<columnNumber; x++){               // X vai pro lado = colunas
        //create a node based on matrix position
        posAux.Set(x, y);
        tilesType type;
        node current=new node(posAux);
        //update up and left nodes
        //"nodeDireita" means rightNode and "nodeEsquerda" means left node
        if(x-1>=0){                 
            current.nodeEsquerda=myNodes[x-1, y];
            myNodes[x-1, y].nodeDireita=current;
        }             
        if(y-1>=0){
            current.nodeAcima=myNodes[x, y-1];
            current.nodeAcima.nodeAbaixo=current;
        }

        //UNity stuff to set type of node visually based on what object is in it
        Collider[] colliders;
        if((colliders = Physics.OverlapSphere(coordinate.gridToUnity(posAux), 3f)).Length >0){
            foreach(Collider collider in colliders){
                objScript obj = collider.gameObject.GetComponent<objScript>();
                current.type=obj.type;
                if(current.type==tilesType.START){
                    path Path = new path (obj.pos, obj.posEnd, this);
                    addPath (Path); 
                    Path.numeroPath=paths.IndexOf(Path);
                }
            }
        }
        myNodes[x,y]=current;
    }
}   
//adicionar vetor[] para H e G com numero de paths nos nodes
//create a vector for multiple paths in each node
int numeroPaths = paths.Count;
for (int y = 0; y < lineNumber; y++) {
    for (int x = 0; x < columnNumber; x++) { 
        myNodes [x, y].H=new float[numeroPaths];
        myNodes [x, y].G=new float[numeroPaths];
    }
}
//adicionar Heuristica e G para cada node em cada path
//calculate heuristic and G for each node in each path
foreach (path Path in paths) {
    coordinate start=Path.startPos, end=Path.endPos;
    int numeroPath=paths.IndexOf(Path);

    for (int y = 0; y < lineNumber; y++) {
        for (int x = 0; x < columnNumber; x++) { 
            coordinate pos = myNodes [x, y].pos;
            //G e H as manhattan distance

            /*Mathf.Sqrt(Mathf.Pow((start.x - pos.x), 2) + Mathf.Pow((start.y - pos.y), 2)); euclidian-does not apply x.x */
            myNodes [x, y].H[numeroPath]=Mathf.Abs(pos.x-end.x) + Mathf.Abs(pos.y-end.y);
            myNodes [x, y].G[numeroPath]=Mathf.Abs(start.x-pos.x) + Mathf.Abs(start.y-pos.y);
        }
    }
}

Code refs:

--node is a custom class that holds "G" and "H" that I use the Manhattan formula to define, "x", "y", "BLOCK" or "NORMAL"(availability of the position)

--openedNodes is a List that I put the correct nodes for the path

--closedNodes are the nodes I checked, but have bigger "F" values;

--nodesToLook are the neighbor nodes for checking.

I appreciate any help. Thanks.

Steven
  • 166,672
  • 24
  • 332
  • 435
markus
  • 1,011
  • 2
  • 10
  • 17
  • 1
    Do you specifically need to implement this yourself? For my final year project, I was just able to reference [this](http://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/article.php/c12527/AStar-A-Implementation-in-C-Path-Finding-PathFinder.htm) implementation as a dll, then it only needs one line of code to be implemented – Sayse Aug 12 '14 at 12:55
  • As I commented, the whole purpose was to learn how to code IA paths from scratch. – markus Aug 12 '14 at 12:56
  • I don't see a single comment that says that, either way that link has complete source code that might help you. – Sayse Aug 12 '14 at 12:59
  • 2
    Just a quick note: you should use HashSets instead of Lists (much quicker) or even *Bit*arrays – Random Dev Aug 12 '14 at 13:00
  • Oh sorry, I mentally tipped that part. Anyway, i'll check that source. thanks! – markus Aug 12 '14 at 13:00
  • can we see the complete source somewhere? As it is it's very hard to read (needs refactoring *badly*) and I might find the culprit if I can debug it... for example: your G or H might be flawed but we don't see the methods, and the way you handle your open/closed/visited sets looks a bit strange – Random Dev Aug 12 '14 at 13:02
  • 1
    For one, you mixed up the open and closed lists. The open lists contains nodes that have to be checked eventually and the closed list contains node that won't be checked anymore (because they can't be reached with a shorter path). Then, how do you calculate `G` and `H` for a node? I don't see an update of any costs anywhere (which is the essential part of the algorithm). Your handling of lists is overall a bit clumsy. E.g. you don't really need the `nodesToLook`. Instead add nodes directly to the open list or update their costs if they already are in this list. – Nico Schertler Aug 12 '14 at 13:03
  • Carsten, update the code with node instance and how I calculate the G and H values. Unfortunatelly, the full code is quite too long to post. I could link you a pastebin if would help. – markus Aug 12 '14 at 13:08
  • At first glance either your heuristic is wrongly predicting the path or you are adding only one best neighbor to the PQ instead of all. – cerkiewny Aug 12 '14 at 13:12

1 Answers1

4

Since you haven't posted your whole code, i have not the slightest clue what you are doing with your nodes, but what i can see:

  • You are not updating G. G is not a heuristic, it is the actual cost of reaching that node. Aka: nextTile.G = currentTile.G + distanceBetween(currentTile,nextTile)
  • You are only adding the best option to the open list. So instead of checking all 4, you only check 1

i can go on, but your whole algorithm does not work like A*, at all. Fixing your code means rewriting it completely.

The algorithm is realy easy. The pseudocode on wikipedia can be copied and implemented directly. It just seems you missed a couple of steps there and implemented a lot of things incorrectly.

DoXicK
  • 4,784
  • 24
  • 22
  • Both my G and H are being calculated at node creation using the Manhattan method. I do not update them at code run. Should I? – markus Aug 12 '14 at 13:27
  • no, H can stay since it's a heuristic. G however, has to be updated. It's like counting steps. "It took me 6 steps to get here. My next position will be 6 + 1 = 7 steps". Updating G means updating F (F=G+H, G changes, so will F) – DoXicK Aug 12 '14 at 13:32
  • Reason why H can stay precalculated: it doesn't change. unless you know how many steps it's going to cost you to get to the endpoint, you can't predict H. And if you do know how many steps it's going to take... you already know the path ;-) – DoXicK Aug 12 '14 at 13:39
  • well, i hope you understand the concept behind A* better now. it's been 4 years since i last implemented it so i don't know how to actually implement it anymore, but the concept is very much like how you would do it in real life. – DoXicK Aug 12 '14 at 13:50