Advertisement

A* scanning extra nodes?

Started by March 26, 2015 03:04 PM
22 comments, last by polyfrag 9 years, 6 months ago

Hello everyone,

A while ago I implemented my own A* pathfinding algorithm, and it works great. But I feel like my implementation of the A* is wrong. I feel like my algorithm is scanning extra nodes that should not be scanned.

Here is a picture of my program.

kBCSgAb.png?1

The yellow square is the starting node, the green square is the target node, the black squares are solid non-walkable nodes, the blue squares are the final path, the white squares are the squares that were not scanned and the red squares are the nodes that were in the closed list.

And here is another website that has A* implemented.

FOaN92l.png?1

As you can see this website algorithm is scanning less nodes than my program. I don't know how and I don't know why.

This is the website.

Can you guys please help. I don't know how to fix this.

And here is my code:-


namespace Pathfinding
{
    public class Node
    {
        internal Node Parent;
        public Vector2 Position;
        internal bool Diagonal;
        internal bool Passable = true;
        internal int H_Value;
        internal int G_Vaule;
        internal int F_Vaule
        {
            get { return H_Value + G_Vaule; }
            private set { }
        }

        public Node(Vector2 Position)
        {
            this.Position = Position;
        }
    }
}

namespace Pathfinding
{
    public class Astar
    {
        public List<Node> OpenList;
        public List<Node> ClosedList;
        List<Node> NonePassable;

        Vector2 GridSize;

        Node CurrentNode, StartingNode, TargetNode;
        bool ReachedTarget;

        const int G_Score = 10;
        const int Diagonal_G_Score = 14;

        Map map;

        public Astar()
        {
            OpenList = new List<Node>();
            ClosedList = new List<Node>();
            NonePassable = new List<Node>();

            ReachedTarget = false;
            GridSize = new Vector2(15, 15);
        }

        void FindAllNeighborNodes()
        {
            Node RightNeighbor = new Node(new Vector2(CurrentNode.Position.X + 1, CurrentNode.Position.Y));
            GetNeighbor(RightNeighbor);

            Node LeftNeighbor = new Node(new Vector2(CurrentNode.Position.X - 1, CurrentNode.Position.Y));
            GetNeighbor(LeftNeighbor);

            Node TopNeighbor = new Node(new Vector2(CurrentNode.Position.X, CurrentNode.Position.Y - 1));
            GetNeighbor(TopNeighbor);

            Node BottomNeighbor = new Node(new Vector2(CurrentNode.Position.X, CurrentNode.Position.Y + 1));
            GetNeighbor(BottomNeighbor);
        }

        void GetNeighbor(Node Neighbor)
        {
            if (Neighbor.Position.X < GridSize.X && Neighbor.Position.X >= 0 && Neighbor.Position.Y >= 0 && Neighbor.Position.Y < GridSize.Y)
            {
                IsNodePassable(Neighbor);

                if (Neighbor.Passable)
                {
                    if (!ClosedList.Any(node => node.Position == Neighbor.Position))
                    {
                        if (!OpenList.Any(node => node.Position == Neighbor.Position))
                        {
                            Neighbor.Parent = CurrentNode;
                            Neighbor.G_Vaule = CurrentNode.G_Vaule + G_Score;
                            Neighbor.H_Value = GetHeuristicCost(Neighbor);
                            OpenList.Add(Neighbor);
                        }
                        else if (OpenList.Any(node => node.Position == Neighbor.Position))
                        {
                            if (OpenList.SingleOrDefault(node => node.Position == Neighbor.Position).G_Vaule + G_Score < CurrentNode.G_Vaule)
                            {
                                int NodeIndex = OpenList.FindIndex(node => node.Position == Neighbor.Position);

                                OpenList[NodeIndex].Parent = CurrentNode;
                                OpenList[NodeIndex].G_Vaule = CurrentNode.G_Vaule + G_Score;
                            }
                        }
                    }
                }
            }
        }

        void IsNodePassable(Node node)
        {
            if (NonePassable.Any(nonePassableNodes => nonePassableNodes.Position == node.Position))
                node.Passable = false;
            else
                node.Passable = true;
        }

        int GetHeuristicCost(Node node)
        {
            Vector2 Distance = new Vector2(Math.Abs(TargetNode.Position.X - node.Position.X), Math.Abs(TargetNode.Position.Y - node.Position.Y));
            return (int)(Distance.X + Distance.Y) * 10;
        }

        Node GetMinF_Value()
        {
            int Lowest_F_Value = OpenList.Min(node => node.F_Vaule);
            int Index = OpenList.FindIndex(node => node.F_Vaule == Lowest_F_Value);

            return OpenList[Index];
        }

        public List<Vector2> GetFinalPath()
        {
            List<Vector2> FinalPath = new List<Vector2>();

            while(true)
            {
                if (CurrentNode.Parent != null)
                {
                    FinalPath.Add(CurrentNode.Position);
                    CurrentNode = CurrentNode.Parent;
                }
                else
                    break;
            }
            FinalPath.Reverse();

            return FinalPath;
        }

        public void FindPath(Map map, Node StartingNode, Node TargetNode)
        {
            this.map = map;
            this.StartingNode = StartingNode;
            this.TargetNode = TargetNode;

            for (int i = 0; i < map.CollisionObjects.Count; i++)
                NonePassable.Add(new Node(map.CollisionObjects[i].Position));

            OpenList.Add(StartingNode);

            while(OpenList.Count > 0)
            {
                CurrentNode = GetMinF_Value();
                OpenList.Remove(CurrentNode);
                ClosedList.Add(CurrentNode);

                FindAllNeighborNodes();

                if(CurrentNode.Position == TargetNode.Position)
                {
                    ReachedTarget = true;
                    break;
                }
            }
        }
    }
}

Offhand, without looking at the code, it could just be a difference in heuristics. You may want to look at the other pathfinder's heuristic code and see how it matches up.

EDIT: Also, you could really condense your code quite a bit, the different neighbor code is all virtually the same.

EDIT2: Where is F value set on the node? Or F_vaule. NM

Advertisement

I don't think your diagram looks particularly problematic. It might just have been bad luck that you try to go right first, and in this particular instance that's not a good strategy.

You are the king of copying and pasting code. :) You should write a loop instead of four blocks of nearly identical code. For instance, you can write a function that returns a list of neighbors and then loop over them. I implemented A* as part of an engine to play Quoridor, and the implementation is 30 lines of code.

Offhand, without looking at the code, it could just be a difference in heuristics. You may want to look at the other pathfinder's heuristic code and see how it matches up.

EDIT: Also, you could really condense your code quite a bit, the different neighbor code is all virtually the same.

EDIT2: Where is F value set on the node? Or F_vaule.

The F value is calculated in the node class.


internal int F_Vaule
{
    get { return H_Value + G_Vaule; }
    private set { }
}

I don't think your diagram looks particularly problematic. It might just have been bad luck that you try to go right first, and in this particular instance that's not a good strategy.

You are the king of copying and pasting code. smile.png You should write a loop instead of four blocks of nearly identical code. For instance, you can write a function that returns a list of neighbors and then loop over them. I implemented A* as part of an engine to play Quoridor, and the implementation is 30 lines of code.

I see.

Hahahahaha biggrin.png

I know I can put the neighbour code in a single function and just make it 30 lines instead of 200. But When I wrote this code, I was just prototyping and see if I can get the algorithm to work tongue.png

I will clean it up. I was just too lazy tongue.png

... in general, IMHO, if you are going to ask for help, and then post code, take that extra 5 minutes and refactor the code to make it cleaner. I'm more likely to look at code that is concise that some cut and pasta dump.

... in general, IMHO, if you are going to ask for help, and then post code, take that extra 5 minutes and refactor the code to make it cleaner. I'm more likely to look at code that is concise that some cut and pasta dump.

I agree. I do apologize. Being lazy is no excuse. I will clean it up.

Advertisement

I cleaned up the code and posted it. I do apologize again for not cleaning it up in the first place.

I don't think your diagram looks particularly problematic. It might just have been bad luck that you try to go right first, and in this particular instance that's not a good strategy.

I did tried to go in this order Top, Right, Bottom, Left and this happened.


void FindAllNeighborNodes()
{
    Node TopNeighbor = new Node(new Vector2(CurrentNode.Position.X, CurrentNode.Position.Y - 1));
    GetNeighbor(TopNeighbor);

    Node RightNeighbor = new Node(new Vector2(CurrentNode.Position.X + 1, CurrentNode.Position.Y));
    GetNeighbor(RightNeighbor);

    Node BottomNeighbor = new Node(new Vector2(CurrentNode.Position.X, CurrentNode.Position.Y + 1));
    GetNeighbor(BottomNeighbor);

    Node LeftNeighbor = new Node(new Vector2(CurrentNode.Position.X - 1, CurrentNode.Position.Y));
    GetNeighbor(LeftNeighbor); 
}

YZAXG1d.png?1

The same kind of thing happens if you go to the website and change the Weight to anything above 1 (example 2). But the website A* still searches less nodes than my algorithm. I don't understand what I'm doing wrong. Both my algorithm and the website algorithm are using the Manhattan Heuristic.

bc7gPBg.png?1

One thought off the top of my head is that when you're grabbing the most promising node off the open list, you're grabbing the first best one that you've found. (At least, that's what I presume OpenList.FindIndex() does.) That is, if you have multiple equally promising nodes, you'll process the one that was added to the list first, which is naturally dependent upon the order in which you add neighbors.

Other implementations are likely to use a priority queue to find the best node, which is going to have different properties in this regard. In particular, they're likely to be semi-random in choosing amongst equally best nodes. Even if the final paths they find are equal in cost to yours, many of the intermediate states, as well as the actual nuances of the final path itself, are bound to be different because of this difference in ordering.

"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke

One thought off the top of my head is that when you're grabbing the most promising node off the open list, you're grabbing the first best one that you've found. (At least, that's what I presume OpenList.FindIndex() does.) That is, if you have multiple equally promising nodes, you'll process the one that was added to the list first, which is naturally dependent upon the order in which you add neighbors.

Other implementations are likely to use a priority queue to find the best node, which is going to have different properties in this regard. In particular, they're likely to be semi-random in choosing amongst equally best nodes. Even if the final paths they find are equal in cost to yours, many of the intermediate states, as well as the actual nuances of the final path itself, are bound to be different because of this difference in ordering.

Now that you pointed this out. I started debugging my code and I found out that the program never execute this part of the code. That is really odd.


        void GetNeighbor(Node Neighbor)
        {
            if (Neighbor.Position.X < GridSize.X && Neighbor.Position.X >= 0 && Neighbor.Position.Y >= 0 && Neighbor.Position.Y < GridSize.Y)
            {
                IsNodePassable(Neighbor);

                if (Neighbor.Passable)
                {
                    if (!ClosedList.Any(node => node.Position == Neighbor.Position))
                    {
                        if (!OpenList.Any(node => node.Position == Neighbor.Position))
                        {
                            Neighbor.Parent = CurrentNode;
                            Neighbor.G_Vaule = CurrentNode.G_Vaule + G_Score;
                            Neighbor.H_Value = GetHeuristicCost(Neighbor);
                            OpenList.Add(Neighbor);
                        }
                        else if (OpenList.Any(node => node.Position == Neighbor.Position))
                        {
//--------------------------This part of the code is never executed. From here 
                            if (OpenList.SingleOrDefault(node => node.Position == Neighbor.Position).G_Vaule + G_Score < CurrentNode.G_Vaule)
                            {
                                int NodeIndex = OpenList.FindIndex(node => node.Position == Neighbor.Position);

                                OpenList[NodeIndex].Parent = CurrentNode;
                                OpenList[NodeIndex].G_Vaule = CurrentNode.G_Vaule + G_Score;
                            }
//--------------------------To here
                        }
                    }
                }
            }
        }

This if statement is never true....


if (OpenList.SingleOrDefault(node => node.Position == Neighbor.Position).G_Vaule + G_Score < CurrentNode.G_Vaule)

Two thoughts:

1) Is that a floating point Vector2? If that's your custom one, you might want to double check the comparison operator

2) Toss some parenthesis around that if, though I think it should be okay without them, if you're not getting in there, maybe that's the problem.

(OpenList.SingleOrDefault(node => node.Position == Neighbor.Position).G_Vaule + G_Score) < CurrentNode.G_Vaule

Also, why are you doing the if elseif above it, it looks like you have if(A) else if(!A), couldn't that just be if(A) else {//not A implied}

This topic is closed to new replies.

Advertisement