Pathfinder

Wanted to post my new pathfinding applet and let people play with it. It uses A* and its a almost a port of the code I did in Blitz3d a year or so ago. The java version came out much cleaner and more efficient, of course the Java version is 2d where the Blitz version was for 3d. Try it out and let me know what you think.

It would be great if someone can compare the timing to their own pathfinding algo’s out there, the timing is purely the time it takes from starting the pathfinding to finishing the search, no drawing or rendering is involved in the timing.

Let me know what you think!
Make sure you click inside the applet to capture the focus.
Press the 6 button to throw random blocks throughout the map
left click a cell to make it the start cell, turns blue
left click again to make selected cell the target cell, turns red
Right click the mouse anywhere in the applet to find the path.

You can use the middle mouse button to create a new block or erase a block

PathFinder Applet

Heres a screen shot…

http://www.yellzbellz.com/images/screens/pathfinder.jpg

Pretty cool, I like path finding a lot too. Have you tried many optimisiations? I found that the binary heap was a massive performance boost.

This site has some good speed tips and a link about binary heaps at the bottom: http://www.policyalmanac.org/games/aStarTutorial.htm

tried it, played whit it , kools nice!!!
tried a lot or diferent scenarios…labrynts?
very responsive =)
keep up the good work.

:o Why would anyone implement A* and not use a heap at least as sophisticated as a binary heap?

Xyle, if you want to get useful comparisons then you’re going to have to post your source code. You’re also going to have to look at larger test cases - a 16ms test is far too small to measure real differences unless the one you’re measuring against is at least an order of magnitude slower. As a general rule of thumb I would say that a performance test which doesn’t run for at least a minute won’t give you any useful information.

Back at university I did an independent research project into pathfinding algorithms, implementation and optimization.

Well, here’s my code, and it’s executable using bat files (so beware!).

It’s essentially an analyzing tool, to help visualize how the pathfinders works. The A* implementation supports multiple “storages” for the open and closed lists, and also different heuristics.

http://gamadu.com/temp/Pathfinder.zip

Few things you can do in that application:

  • You can resize the grid by using arrow keys (WARNING, don’t exceed 256x256…starts to lag)
  • You can toggle on/off the gridlines using G
  • You can move grid grid using W,A,S,D (buggy though)
  • You can zoom in/out using + and - on the numkeypad.
  • You can change the heuristics by pressing 1-5 on the qwerty-keypad (only in A*)
  • You can press SPACEBAR to show how the nodes were traversed (pink show paths tried, fading to black means not tried as much)
  • You can reset the view of the grid by pressing R.
  • Press enter to relocate the start and goal nodes (sorry, you can’t manually position them yet!)
  • Press F5 to refresh the pathfinding operation… nice to do a few times to see the timer.

Awesome! Thanks for the responses. Glad a some people found it entertaining.

You can check out the source code here. Its a zip file that includes the classes, java file and the .htm file. Its hosted on Filefront so it should be there for a while. Get it here!

As for

Hehehe, I’ve never used them before and haven’t looked at them, but since CommaderKeith recommended it, I will definitely look into it.

I really appreciate the feedback and the responses. Thank you!

What’s better than a binary heap?

Appel, I really appreciate the sharing of your code. I changed my timing to match yours and ran a few tests that matched the map up with yours. I was excited to see that the paths found were identical and the search paths were relatively close for the most part. Although I was really bummed to see that your code is almost 3 times faster than mine, hahahaha!!

Now, who said what about Binary Heaps? lol, I’m going to have learn em I reckon!

heres the screenie…

No problem Xyle. You can use it to study why it’s so fast :wink:

I probably made a bit more complex than it needed to be, in order to support multiple algorithms.

Also try out different heuristics by pressing 1 through 5 keys. I found that 4 is the fastest, but 5 produces the smoothest paths. You’ll need to enlarge the map to see the difference.

There are hundreds of heaps in the literatures. AIUI Fibonacci heaps were designed specifically for use with Dijkstra’s algorithm, and are asymptotically better; relaxed heaps are also asymptotically better, and I’m sure many others are too. Then there are things like the C algorithm which can be seen as an algorithmic variant of A* or as A* with a variant heap. I use it with a non-admissible heuristic where I care about finding a route quickly more than finding the best route.

Cool, thanks for the info, that’s very interesting. What are you making with your path finding algorithm? An RTS?

Hm, you definitely need some optimization here, yeah. Actually, you might even have done something wrong. I’ve got a maze app I wrote that solves random mazes more or less instantly, even if they’re fairly enormous. I’ve also got an algorithm on the iPhone that can solve situations comparable to the one you posted in less than a second… that’s with a mobile processor. It’s weird that such a small area is taking you 3 seconds to solve.

The maze app I wrote does not really use any optimizations like using binary heaps, but the one on the phone uses a HashMap equivalent for faster lookup. Actually you can have a look at my maze app, it’s open source.
http://www.otcsw.com/maze.php

Technically yes, although it’s not exactly what most people think of when they hear RTS. It’s an alternative history strategy game.

I’d love the completely different approach: not calculating but finding your path.

http://www.red3d.com/cwr/steer/
http://www.red3d.com/cwr/steer/Obstacle.html
http://www.red3d.com/cwr/steer/CrowdPath.html
http://www.red3d.com/cwr/steer/Unaligned.html

There is a high chance you can find most targets without encountering obstructions. Of there are obstructions, just let A* create a very rough path, and ‘feel’ your way from target to target. This is very realistically looking for crowds and group movement.

That’s very cool. I would actually use that approach if I hadn’t already written A*. But I find myself spending lots and lots of time trying to tweak A* to look more natural, especially when diagonal movements are allowed in a grid-based environment.

Just adding to this. In the maze examples there’s no variation in move cost, so your heuristic will be “too good” and you won’t be testing every aspect of the priority queues. One big advantage of Fibonacci heaps, pairing heaps etc. is improvement in decrease_key running time. If the heuristic can perfectly judge the distance to the goal when no obstacles exist, then there should never be duplicates in the open list that need updating, and decrease_key won’t have to be used.

I guess it all comes back to making sure you are testing for exactly the situation you’ll be using them in… reviled micro-benchmarks and so on.

Thanks for the looky! Those are millisecs, not seconds. I was thinking 3 millisecs was pretty fast considering that it could do that same routine 300 more times before getting even close to 1 second. Hahahaha. Until I saw Appels that is.

I am thinking that main slowdown is when I gather and set all the neighbor cells. It takes 6 checks to make sure each neighbor cell is valid, than it sets each cell’s f,g,h and parent values. It must do this 8 times for each cell that gets checked on the openlist to determine if the cell is valid and should be put on the openlist. This was the main problem in my code that took me a day to figure out. I dont think it can be broke, I tested it many many times on blocked targets and inaccessible targets, plus different size maps, etc. I cant think of any other way of checking and setting the neighbor cells. Ive looked through tons and tons of examples and source but cant really get a grip on how other people do it.

Heres the code that checks and sets the neighbor cells, topleft, top, topright, left, right, bottomleft, bottom, and bottomright cells of current cell being checked…


//Add adjacent squares to the openlist unless they are already there
//or they are on the closed list
//If they are already on the open list, supposed to check them for a better path
//TopLeft Cell
int celNum = currCell-(mapRow+1);
int totCells = mapRow*mapCol;
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row-1 && cell[celNum].col == cell[currCell].col-1){
		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("1Checked topleft cell "+celNum);
		}
	}
}
//TopCell
celNum = currCell-(mapRow);
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row-1 &&cell[celNum].col == cell[currCell].col){
		if(cell[celNum].listType == 0){  
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+10;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("2Checked top cell "+celNum);
		}
	}
}
//TopRight Cell
celNum = currCell-(mapRow-1);
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row-1 &&cell[celNum].col == cell[currCell].col+1){
		if(cell[celNum].listType == 0){  
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("3Checked topright cell "+celNum);
		}
	}
}
//Left Cell
celNum = currCell-1;
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row &&cell[celNum].col == cell[currCell].col-1){
		if(cell[celNum].listType == 0){  
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+10;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("4Checked left cell "+celNum);
		}
	}
}
//Right Cell
celNum = currCell+1;
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row &&cell[celNum].col == cell[currCell].col+1){
		if(cell[celNum].listType == 0){  
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+10;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("5Checked right cell "+celNum);
		}
	}
}
//BottomLeft Cell
celNum = currCell+(mapRow-1);
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row+1 && cell[celNum].col == cell[currCell].col-1){
		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("6Checked Bottomleft cell "+celNum);
		}
	}
}
//Bottom Cell
celNum = currCell+(mapRow);
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row+1 &&cell[celNum].col == cell[currCell].col){
		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+10;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("7Checked bottom cell "+celNum);
		}
	}
}
//BottomRight Cell
celNum = currCell+(mapRow+1);
if(celNum > -1 && celNum < totCells && cell[celNum].block == 0){
	if(cell[celNum].row == cell[currCell].row+1 &&cell[celNum].col == cell[currCell].col+1){
		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("8Checked bottomright cell "+celNum);
		}
	}
}

If anyone sees anything that could help speed this up, it would be greatly appreciated. As I said, Ive looked at others codes and tried chasing down this step, but it seems to be elluding me.

Hi Jono, what’s an example of this? I don’t understand what you’re saying. Do you mean that in some simulations the heuristic changes as you find out more info, so the heap needs re-ordering?

That’s cool, great applet examples. Looks like the best way to achieve unit avoidance.

3 milliseconds is good but it could be better. on such a tiny grid, the result should be almost instantaneous.

have a look at this pathfinder. it’s in JavaScript but does almast equally well in small portions of grid. in chrome it runs even faster. chrome’s JS engine perform really good btw…

Xyle, your code doesn’t actually look correct.

Firstly, a handy tip for reducing the size of the code: remember rotation matrices?

for (int diag = 0; diag < 2; diag++) // 0: not diagonal; 1: diagonal
{
    int edgeCost = 10 + diag * 4;
    int dx = 1;
    int dy = diag;
    for (int i = 0; i < 4; i++)
    {
        update(x+dx, y+dy);
        int t = dx;
        dx = dy;
        dy = -t;
    }
}

All 8 cases handled. If you don’t do diagonals you can ditch the outer loop.

Secondly, the actual processing code. You have (taking top-left for example)

		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
			//System.out.println("1Checked topleft cell "+celNum);
		}

Shouldn’t that be as follows?

		if(cell[celNum].listType == 0){ 
			addOpenList(celNum);
		}
		if (cell[currCell].gVal+14 < cell[celNum].gval)
		{
			cell[celNum].gVal = cell[currCell].gVal+14;
			cell[celNum].hVal = findDistance(celNum,tCell);
			cell[celNum].fVal = cell[celNum].gVal+cell[celNum].hVal;
			cell[celNum].parent = currCell; //Make currCell the parent of adjacent cells
		}

Assuming that gVal is initialised to Integer.MAX_VALUE / 2. Since you’re assigning a greater cost to diagonal movement than to horizontal movement your screenshot in the first post is quite clearly showing a non-optimal route, and I’m pretty sure that the reason is that you’re never reducing the key of a cell in the open list.