0

I am having a bit of trouble perfecting my A* algorithm. I works pretty well but needs some fine tuning. I am getting a StackOverflowException while checking to see if my tile is valid. The Exception occurs either at the aStar() method or the isValid() function. Here is my code:

    private void aStar(int x, int y) { //Problem
        Point[] refPoints = { new Point(x - 1, y), new Point(x, y - 1), new Point(x, y + 1), new Point(x + 1, y) };
        Point lowPoint = new Point(x, y);
        int lowCost = 1000;

        for (int i = 0; i < refPoints.Length; i++) {
            if (isValid(refPoints[i], true)) { //Problem
                map[refPoints[i].X, refPoints[i].Y].H = Math.Abs(refPoints[i].X - player.X) + Math.Abs(refPoints[i].Y - player.Y);
                map[refPoints[i].X, refPoints[i].Y].G = map[x, y].G + 10;
                map[refPoints[i].X, refPoints[i].Y].F = map[refPoints[i].X, refPoints[i].Y].H + map[refPoints[i].X, refPoints[i].Y].G;

                if (map[refPoints[i].X, refPoints[i].Y].F < lowCost) {
                    lowCost = map[refPoints[i].X, refPoints[i].Y].F;
                    lowPoint = refPoints[i];
                }

                map[refPoints[i].X, refPoints[i].Y].AType = AType.OPEN;
            }
        }

        if (!(lowPoint.Equals(player))) {
            map[lowPoint.X, lowPoint.Y].AType = AType.CLOSED;
            path.Add(lowPoint);
            aStar(lowPoint);
        }
    }

    private void aStar(Point p) {
        aStar(p.X, p.Y); //Problem
    }

    private bool isValid(int x, int y, bool aStar) { //Problem
        if (aStar) {
            return (x >= 0 && x < tileX && y >= 0 && y < tileY && !map[x, y].TileType.Equals(TileType.BLOCK) && 
                !map[x, y].AType.Equals(AType.CLOSED) && !map[x, y].AType.Equals(AType.OPEN));
        } else {
            return (x >= 0 && x < tileX && y >= 0 && y < tileY && !map[x, y].TileType.Equals(TileType.BLOCK));
        }
    }

    private bool isValid(Point p, bool aStar) {
        return isValid(p.X, p.Y, aStar); //Problem
    }

I cannot seem to trace the origin of the problem but it only happens sometimes (usually when there is an obstacle in the way, but not always).

Instead of an open and closed list in my code I have an enum (AType) in each Tile of my map (BLANK, OPEN, CLOSED). The OPEN enum really doesn't affect anything except mark tiles that have been tested and are not the best move. The CLOSED enum is just applied to all Tiles that are identified as the best move thus eventually building a path. The BLANK enum is just the default state of the Tile (neither in the open or closed list).

zfollette
  • 475
  • 4
  • 15
  • Have you tried stepping through it? Find a scenario you know will fail and then step through the code. – pquest Jan 26 '15 at 17:37
  • I am currently trying to find a solid reproduction of the error. I can't find exactly when it happens – zfollette Jan 26 '15 at 17:44
  • I have put a comment everywhere that shows up on the error tree, if it helps. I am still trying to find a situation where the error is 100% prevalent – zfollette Jan 26 '15 at 17:53
  • Any method implemented using recursion can overflow the stack if it recurses too deeply and thereby exhausts the available stack space. The solution is to create your own stack (or some other storage structure, depending on the nature of the algorithm) and remove the recursion. All recursive algorithms can be rewritten to remove the recursion. That said, as usr points out, what you're currently implementing here is not actually A*; it appears to just be a local-minimum follower, which can be easily stopped if it hits a wall that requires backtracking to escape. – Dan Bryant Jan 26 '15 at 18:19
  • Well put. I guess I'll restructure the method. I was trying to achieve A* without sucking up RAM, but I guess that's not an option. – zfollette Jan 26 '15 at 18:27

2 Answers2

1

A* does not have a recursive step. You should be pulling work items out of a priority queue.

Your code is tail recursive. There is no need for recursion here. Just wrap the entire method in a while (true) loop:

private void aStar(int x, int y) {
    while (true) {
    //...
    if (!(lowPoint.Equals(player))) {
        map[lowPoint.X, lowPoint.Y].AType = AType.CLOSED;
        path.Add(lowPoint);
        continue;
    }
    else break;
}
}

I suspect the StackOverflowEx is gone after making this change but the algorithm will not work because I don't see a priority queue anywhere.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I'm not sure exactly what you mean but my method works by first calling the method externally (when the player moves). From then it checks all linear adjacent tiles (nodes) and when it finds the one with the lowest F cost, the a star method is called on that tile. This recursion happens until the method reaches the player (goal) – zfollette Jan 26 '15 at 17:44
  • 2
    That way you keep track only of the adjacent nodes of the current one. That's not what A* does. A* keeps track of **all** unvisited nodes in priority order. Your code can't work that way. Also you don't need recursion - you need a loop. There is nothing recursive about this. – usr Jan 26 '15 at 17:59
  • I added a missing piece to the code snippet. Is it more clear now? – usr Jan 26 '15 at 18:00
  • I'm still not understanding the need to store all unvisited nodes – zfollette Jan 26 '15 at 18:09
  • Well, don't take it from me, then. Ask Wikipedia. A* uses a priority queue or something equivalent. You don't have that. That's not A*. It's something else. Probably something that gets stuck in local minima instead of finding the best path globally. – usr Jan 26 '15 at 18:10
0

What you're basically doing:

void foo(Point a)
{
   ...
   foo(a.X, a.Y);
}
void foo(int x, int y)
{
   foo(new Point(x, y));
}

Now, recursion can be very handy, but in this case it isn't, because it isn't necessary and it'll simply get called too much, causing a StackOverFlow. So you need to redesign the function so you it doesn't call itself, but instead having a while loop with a certain condition.

Something like this:

void aStar(int x, int y)
{
   // just declarations to show you
   Path path;
   Point currentPoint;
   while (true)
   {
      // Your modified function goal. Instead of calling the function, just let the loop continue
      // if node is goal, break
      if (!(lowPoint.Equals(player)))
      {
        map[lowPoint.X, lowPoint.Y].AType = AType.CLOSED;
        path.Add(lowPoint);
        aStar(lowPoint);
      }
      else
      {
        break;
      }
   }
joppiesaus
  • 5,471
  • 3
  • 26
  • 36
  • I see what you mean, but your code example has nothing to do with my problem. What you have just displayed is an example of an overload method (the same method with different parameters). Unless I'm interpreting the code example incorrectly. – zfollette Jan 26 '15 at 17:59