Skip to main content
GameDev.net gamedev.net
🔒 Locked

Pathfinding with A* problem

Started by ma3a Aug 14, 2006 at 9:00 PM 9 replies 1.3k views
Original Post
ma3a
ma3a
Hello everyone! This is my first post here :) I am having a problem with my A* pathfinding implementation using Java. It is getting stuck on this one node. I've provided links to the AStar and the Main clasess below. You can use the toogle to expand/collapse methods. I've commented my code heavily so it is easy to understand what is going on, but for the life of me I can't figure out why it is not working. Please if someone could look it over I would be very happy. Main class

import java.awt.Point;

public class Main 
{	
	public static void main(String[] args)
	{
		//dummy map file
		/*
		 * a = start location
		 * b = target location
		 * 0 = walkable path
		 * 1 = walls, impassible
		 * -----------
		 * a 0 0
		 * 1 1 0
		 * b 1 0
		 * 0 0 0 
		 * -----------
		 */
		int[][] map = new int[][]{{0,0,0},{1,1,0},{0,1,0},{0,0,0}};

		//new instance of the AStar class, with the test map
		AStar astar = new AStar(map);
		
		//set the start location and the target location
		AStar.AStarNode a = astar.new AStarNode(0,0,null);
		AStar.AStarNode b = astar.new AStarNode(0,2,null);
		
		//find path between the two
		astar.findPath(a,b);
			
		//print the final path if there is one.
		astar.printPath();		
	}
}



AStar class


import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;

public class AStar 
{
	//Necessary lists for the algorithm
	private ArrayList openList, closedList, solution, neighbors;
	
	//starting and target nodes
	private AStarNode startNode, goalNode;
	
	//the map of tiles with information on weather the tile is walkable or not
	private int[][] tileMap;
	
	/*
	 * Constructs a new AStar object
	 * @param m is the tile map representing tile information for each tile such as 1, 0 , etc
	 * Currently only 0 values are considered to be passable terrain
	 */
	public AStar(int[][] m)
	{	
		tileMap = new int[m.length][m[0].length];
		for(int i = 0; i < m.length; i++)
		{
			for(int j = 0; j < m[0].length; j++)
			{
				tileMap[j] = m[j];
			}
		}
	}
	
	/*
	 * Methor for finding a path between two nodes
	 * @param A the starting node
	 * @param B the target node
	 */
	public void findPath(AStarNode a, AStarNode b)
	{
		//initialize necessary lists for the algorithm
		openList = new ArrayList();//holds nodes that are to be considered as a possible path to take.
		closedList = new ArrayList();//holds nodes that are already considered.
		solution = new ArrayList();//holds the final path that lead from the starting node to the target node
		neighbors = new ArrayList();//holds neighbors for the current node that is being evaluated,
									//starts fresh everytime we are considering a new node, as it will have new neighbors.
									//each node will have eight neighbors unless the neighbor is an impassible tile or
									//it is out of the map as it would occur on tiles that are at the edges of the map
		
		//set the start node and the target node;
		startNode = a;
		goalNode = b;
		
		//add the start node to the list of open nodes as we prepare to start our search,
		//this list holds nodes that will be considered for the path we will take.
		openList.add(startNode);
		
		//while the open list is not empty, grab the next node in the list with the lowest cost
		while(!openList.isEmpty())
		{
			//if the we've considered all the nodes on the map, and still havent found the target, give up.
			if(closedList.size() == (tileMap.length*tileMap[0].length)-1)
				break;			
			
			//Get the node with the lowest TotalCost from the open list, this is now our current node.
			AStarNode NodeCurrent = getLowestCostNode();			
			
			// If this current node is the same as the target node, we have reached our goal.
			// they are only the same if their x and y values match.
			// copy the path we took to get this target node to the solution list
			if(NodeCurrent.isGoal()) 
			{
				while(NodeCurrent != null) {
					solution.add(0,NodeCurrent);
					NodeCurrent = NodeCurrent.parent;
				}
				//Done, QUIT!
				break;					
			}			
			/*OTHERWISE*/
			//Get all of the surrounding neighbors of the current node, and add them to the neighbors list.
			//The neighbors list is renewed everytime getneighbors method is called, so there is no accumulation.
			//get neighbors gets all the surrounding nodes unless impassible.
			//The impassible status is determinded according to the tileMap.
			NodeCurrent.getNeighbors();

			//for each node in the neighbors list, do the following:
			Iterator it = neighbors.iterator();
			while(it.hasNext())
			{
				//Set the node as our potential successor (the next node to be added to the open list)
				AStarNode successor = (AStarNode)it.next();
				
				// Test if the currect successor node is already on the open list, if it is and
				// the TotalCost is higher than the one we already have on the open list,
				// we will throw away this current successor node.
				AStarNode nodeOpen = null;
				if(openList.contains(successor))
					nodeOpen = (AStarNode)openList.get(openList.indexOf(successor));
				if((nodeOpen != null) && (successor.getCost() > nodeOpen.getCost())) 
					continue;
				
				//Test if the currect successor node is on the closed list, if it is and
				// the TotalCost is higher, we will throw away this current successor.
				AStarNode nodeClosed = null;
				if(closedList.contains(successor))
					nodeClosed = (AStarNode)closedList.get(closedList.indexOf(successor));
				if((nodeClosed != null) && (successor.getCost() > nodeClosed.getCost())) 
					continue;
				
		
				
				//if the successor was already on the open list
				//but the one in the list (old successor) had a higher cost value
				//remove the old successor from the open list
				//and two lines below: replace it with the current successor.
				openList.remove(nodeOpen);

				//if the successor was already on the closed list
				//but the one in the list (old successor) had a higher cost value
				//remove the old successor from the closed list
				//and a line below: replace it with the current successor.
				closedList.remove(nodeClosed);
				
				/*
				pay attention to the fact that the remove(nodeOpen) only works if the nodeOpen is not null,
				and nodeOpen is not null if and only if it was already on one of the lists before.
				This means they are the same node, but with different paths leading to them so
				they have different cost associated with them.
				This node is checked against the one on the list, comparing their total cost.				 
				the one with the lower cost replaces the other, since it has a better path leading to it.
				sorry about the long explanation, if you read on A* algorithm, it will make more sense.
				*/
				
				// Add the current successor to the open list
				openList.add(successor);				
			}		
			
			//closed list holds the already considered nodes.
			//we are done evaluating this node, so we can safely add it to the closed list.			
			closedList.add(NodeCurrent);			
		}
	}
	
	
	
	/*
	 * iterates through the list of open nodes
	 * returns the one with the lowest cost.
	 */
	public AStarNode getLowestCostNode()
	{
		int f = Integer.MAX_VALUE;
		AStarNode node = null;		
		Iterator it = openList.iterator();		
		while(it.hasNext())
		{
			node = (AStarNode)it.next();
			if(node.getCost() < f)
				f = node.getCost();
		}
		return node;
	}
	
	/*
	 * @return The arraylist holding
	 * the final path leading to the target node if it is not empty.
	 * If it is empty, it means there is no path.
	 */
	public ArrayList getPath()
	{
		if(!solution.isEmpty())
			return solution;
		return null;			
	}
	
	/*
	 * prints the final path leading to the target node if there is a path.
	 */
	public void printPath()
	{		
		if(!solution.isEmpty())
		{
			System.out.println("Printing Path...");
			Iterator it = solution.iterator();
			while(it.hasNext())
			{
				AStarNode n = (AStarNode)it.next();
				System.out.println(n);
			}
		}
		else
		{
			System.out.println("Path is Empty");
		}
	}
	
	/*An inner class representing the node*/
	public class AStarNode
	{
		//row, column
		public int x,y;
		//cost variables
		public int g,h;
		//the parent node to this node
		public AStarNode parent;
		//the unique id of this node for comparing
		public Point ID;
		//the terrain type of this node
		public int type;
		
		/*
		 * Constructs a new node.
		 * @param x the Row location of this node on the tileMap.
		 * @param y the Column location of this node on the tileMap.
		 * @param parent the parent node that is leading to this node,
		 * it is null if we are the starting node.
		 */
		public AStarNode(int x, int y, AStarNode parent)
		{
			//parent node
			this.parent = parent;
			
			//set this nodes row, column info
			this.x = x;
			this.y = y;
			
			//ID, for comparing
			ID = new Point(x,y);

			//movement cost to this node
			g = 0;
			
			//heuristic cost, set using manhattan approach
			h = Math.abs(goalNode.x-x)+Math.abs(goalNode.y - y);

			//this nodes terrain type accordinig to the tileMap, 0,1,etc...
			type = tileMap[x][y];		
		}
		
		/*
		 * compares the ID of the param to this node's ID
		 */
		public boolean equals(Object o)
		{
			return ID.equals(((AStarNode)o).ID);
		}
		
		/*
		 * The string representation of this node
		 */
		public String toString()
		{
			return "X: " + x + " Y: " + y;
		}
		
		/*
		 * return the total cost of this node
		 * f=g+h
		 */		
		public int getCost()
		{
			return g+h;			
		}
		
		/*
		 * checks to see if this node is the same as the target node
		 */
		public boolean isGoal()
		{
			return ID.equals(goalNode.ID);
		}
		
		/*
		 * Gets all the surrounding neighbors of this node, 
		 * ignoring itself, its parent and the tiles that are impassible.
		 */
		public void getNeighbors()
		{			
			neighbors = new ArrayList();
			for(int b = y - 1; b <= y + 1; b++)
			{
				for(int a = x-1; a <= x + 1; a++)
				{
					//if a and b is on the map					
					if(a < tileMap.length && b< tileMap[0].length && a > -1 && b >-1)
					{
						//create a node with its parent being this
						AStarNode n = new AStarNode(a,b,this);
						//set the newly created nodes movement cost value
						n.g = g + 10;						
						//if we are the first node, we don't have a parent
						if(parent == null)
						{
							//so just make sure it isn't us (x,y) we are trying to add, and make sure it is walkable
							if(!ID.equals(n.ID) && tileMap[a] == 0)
							{
								//add to neighbors
								neighbors.add(n);
							}
							
						}
						else //if we are not the first node, we have a parent
						{
							//if the node we are trying to add is not our parent and it is not us, and it is walkable						
							if(!ID.equals(n.ID) && !parent.ID.equals(n.ID) && tileMap[a]==0)
							{
								//add to neighbors
								neighbors.add(n);								
							}							
						}	
					}
					
				}
			}
		}
		
	}

}


[Edited by - ma3a on August 14, 2006 9:43:53 PM]
ma3a
ma3a
BUMP
SuperNerd
SuperNerd
Can you better discribe what is not working?
ma3a
ma3a
Sure, sorry.

ok here is the map that I am feeding to the algo
it is a two dimensional array of integers

A 0 0
1 1 0
B 1 0
0 0 0

on this map

A = starting location.
B = the Destination desired.
0 = the walkable terrain
1 = walls

The A* algo suppose to find the best path from A to B avoiding obstacles such as Walls. I will add other values representing different obstacles later but just wall for now.

So what happens is that when trying to calculate the path from A to B, my findPath(a,b) algorithm is getting stuck at the location (3,2) which is the bottom right corner of the map described above. And it does this no matter what destination I pick.
It is really weird. Also if I let it it will go into an infinite loop on that same node. So the currentNOde will always be that same (3,2) but I added a line of code to stop that, it basically checks to see if the algorithm filled the closedList array and quits, given the closedList is the same size as the map grid, it will quit when there are as many nodes in the closedList as there are on the map. But this is not a solution, I just wanted to stop from going into an infinite loop so that I could see what was going on. I know you need to be a super genious to understand what I am talking about right now as I am doing a horrible job explaining it I think. LOL, but I hope you do :)
ma3a
ma3a
I just tried giving it a map with no obstacles

A 0 0
0 0 0
B 0 0
0 0 0

and it printed this:

X: 0 Y: 0
X: 1 Y: 1
X: 2 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2
X: 3 Y: 2

these are the NodeCurrent printouts in the while loop in findPath

it went and stuck on that same node(3,2)
the best path should of been straight down, not diagnol.
hmmmm.

dragongame
dragongame
Hi,

you allways select the last node on the openlist because you overwrite the node variable everytime ...
/*	 * iterates through the list of open nodes	 * returns the one with the lowest cost.	 */	public AStarNode getLowestCostNode()	{		int f = Integer.MAX_VALUE;		AStarNode node = null;				Iterator it = openList.iterator();				while(it.hasNext())		{			node = (AStarNode)it.next();			if(node.getCost() < f)				f = node.getCost();		}		return node;	}


just a small change

       /*	 * iterates through the list of open nodes	 * returns the one with the lowest cost.	 */	public AStarNode getLowestCostNode()	{		int f = Integer.MAX_VALUE;		AStarNode node = null;				Iterator it = openList.iterator();				while(it.hasNext())		{			AStarNode cnode = (AStarNode)it.next();			if(cnode.getCost() < f)                        {				f = cnode.getCost();                                node = cnode;                        }		}		return node;	}
“Always programm as if the person who will be maintaining your program is a violent psychopath that knows where you live”
let_bound
let_bound
For one thing, it looks like you mixed up X and Y in your last test. Your map is 3 cells wide on the X axis, but you have indices ranging from 0 to 3 inclusive. Hasn't that raised any kind of exception?

Also, shouldn't you be doing the following?
- get the current node's neighbors,- for each neighbor:  - if it isn't a wall and it isn't in the closed list:    - compute the cost from the current node to that neighbor,    - if the node is already in the open list:      - if the new cost is lower, then update the node in the list    - else (the node isn't already in the open list), add it


Right now you seem to be adding a neighbor to the open list even if it is in the closed list. Also, your getLowestCostNode() method always returns the last item in the list, no matter its cost.

I could be wrong though. I just glanced over your code and I don't know Java at all. Someone please correct me if I'm wrong though.


Hope this helps.
ma3a
ma3a
yeah my x is actually a y and vise versa. But I remedy that by switching the x,y when creating the goal node.

Oh and the check for closedList is done in the main while loop after getting the neighbors. get neighbors returns all neighbors that around node except itself and its parent, the evaluation is done in the while loop above in the findpath method.
ma3a
ma3a
I realized it is abit unclear the way I did the iteration of neighbors so I revised it but still the same problem occurs.

Here is the new iteration for the part where I go through the neighbors in findPath()

			//for each node in the neighbors list, do the following:			Iterator it = neighbors.iterator();			while(it.hasNext())			{				//Set the node as our potential successor (the next node to be added to the open list)				AStarNode successor = (AStarNode)it.next();								if(openList.contains(successor))				{					AStarNode twin = (AStarNode)openList.get(openList.indexOf(successor));					//if the successor's cost is lower than its twin replace the twin with successor					if(successor.getCost() < twin.getCost())					{						openList.remove(openList.indexOf(twin));						openList.add(successor);					}					else					{						//successor is unworthy of our love! Don't add											}										}				else if(closedList.contains(successor))				{					AStarNode twin = (AStarNode)openList.get(openList.indexOf(successor));					if(successor.getCost() < twin.getCost())					{						closedList.remove(closedList.indexOf(twin));						closedList.add(successor);					}					else					{						//successor is unworthy of our love! Don't add											}				}				else				{					//just add to open list as it is not there and it is not in closed list either					openList.add(successor);									}
ma3a
ma3a
Quote:
Original post by dragongame
Hi,

you allways select the last node on the openlist because you overwrite the node variable everytime ...
*** Source Snippet Removed ***

just a small change

*** Source Snippet Removed ***


Hey, Thank you dragongame, I didn't see that :) LOL
Lets see if that fixes things a bit.
ma3a
ma3a
Ok, Finally!!! I decided to rewrite the whole thing from scratch.
It is working now. I think my problem was that I was not removing the currentNode from the openList after evaluation.

Anyway, below is the code for main class and the Astar class, if anyone needs it :)

Main

import java.awt.Point;public class Main {		public static void main(String[] args)	{		//dummy map file		/*		 * a = start location		 * b = target location		 * 0 = walkable path		 * 1 = walls, impassible		 * -----------		 * a 0 0 0		 * 0 0 0 0		 * 0 0 0 b		 * -----------		 */		int[][] map = new int[][]{{0,0,0},{1,1,0},{0,1,0},{0,0,0}};		//int[][] map = new int[][]{{0,0,0},{0,0,0},{0,0,0},{0,0,0}};		//new instance of the AStar class, with the test map		AStar astar = new AStar(map);				//set the start location and the target location		Point a = new Point(0,0);		Point b = new Point(2,2);				//find path between the two		astar.findPath(a,b);					//print the final path if there is one.		astar.printPath();	}}



AStar

import java.awt.Point;import java.util.ArrayList;import java.util.Iterator;public class AStar {	private int[][] tileMap;	private ArrayList closedList;	private ArrayList openList;	private ArrayList neighbors;	private ArrayList solution;	private Node startNode;	private Node goalNode;		public AStar(int[][] map)	{		tileMap = map;			}		public void findPath(Point a, Point b)	{		//initialize the lists		closedList = new ArrayList();		openList = new ArrayList();		solution = new ArrayList();				//initialize startNode and goalNode		startNode = new Node(a.x,a.y,null);		goalNode = new Node(b.x,b.y,null);		startNode.h = Math.abs(goalNode.x - startNode.x) + Math.abs(goalNode.y - startNode.y);		//System.out.println(startNode + "--->" + goalNode);		Node currentNode = null;						//begin with starting node and add it to the openList		openList.add(startNode);							while(!openList.isEmpty())		{			//System.out.println(openList.size());			//printList(closedList);			//get currentNode			currentNode = getLowestCostNode();						//if current Node is the same as goal Node			if(currentNode.ID.equals(goalNode.ID))			{				while(currentNode != null) {					//insert currentNode to the beginning of the list					solution.add(0,currentNode);										currentNode = currentNode.parent;				}				//Done, QUIT!				break;								}						/*OTHERWISE*/						//Get the neighbors of the current node that are on the map excluding currentNode's parent and terrain that is unwalkable			//and place them in the neighbors list.			//we will evaluate each individually here.			currentNode.getNeighbors();			//System.out.println(currentNode);			//printList(neighbors);						//iterate throught the neighbors list and add nodes that are elligible to the openlist.			//eligible nodes must be:			//not parent of current node			//if in openList, must have better cost then their twin in the list			//if in closedList, must have better value then their twin in the list			//or not in either list, simply add			Iterator it = neighbors.iterator();			while(it.hasNext())			{				Node neighbor = (Node)it.next();								//if the neighbor is in the openList already				if(openList.contains(neighbor))				{					Node twinNeighbor = (Node)openList.get(openList.indexOf(neighbor));					//The neighbor must have better cost then its twin in the list in order to replace its twin.											if(neighbor.getCost() < twinNeighbor.getCost())					{						//replace twin						openList.remove(twinNeighbor);						closedList.add(twinNeighbor);						openList.add(neighbor);					}				}				else if(closedList.contains(neighbor))				{					//the neighbor must have a better value then the one in the closed list					//we can place the node in the open list again as it is better.					Node twinNeighbor = (Node)closedList.get(closedList.indexOf(neighbor));					if(neighbor.getCost() < twinNeighbor.getCost())					{						closedList.remove(twinNeighbor);						openList.add(neighbor);												}				}				else				{					openList.add(neighbor);				}											}			//remove from open list			openList.remove(currentNode);			//add to closed list			closedList.add(currentNode);		}			}			/*	 * prints the final path leading to the target node if there is a path.	 */	public void printPath()	{				if(!solution.isEmpty())		{			System.out.println("Printing Path...");			Iterator it = solution.iterator();			while(it.hasNext())			{				Node n = (Node)it.next();				System.out.println(n);			}		}		else		{			System.out.println("Path is Empty");		}	}		public void printList(ArrayList list)	{				System.out.println("==================");		System.out.println(list.toString());//		Iterator it = list.iterator();//		while(it.hasNext())//		{//			System.out.println((Node)it.next());//		}	}		public ArrayList getPath()	{		if(!solution.isEmpty())			return solution;		return null;			}		private Node getLowestCostNode()	{		int f = Integer.MAX_VALUE;		Node node = null;				Iterator it = openList.iterator();				while(it.hasNext())		{			Node cnode = (Node)it.next();			if(cnode.getCost() < f)			{				f = cnode.getCost();				node = cnode;			}		}		return node;	}		public class Node	{		public int x, y;		public int g, h;		public Point ID;		public Node parent;				public Node(int x, int y, Node parent)		{			this.x = x;			this.y = y;			g = 0;			h = 0;						this.parent = parent;			ID = new Point(x,y);		}		public int getCost()		{			return g+h;		}				public void getNeighbors()		{			neighbors = new ArrayList();			for(int b = y - 1; b <= y + 1; b++)			{				for(int a = x-1; a <= x + 1; a++)				{					//if a and b is on the map					if(a < tileMap.length && b< tileMap[0].length && a > -1 && b >-1)					{						//create the neighbor node, with its parent being us						Node neighbor = new Node(a,b,this);						//set neighbors costs						neighbor.g = g+10;						neighbor.h = Math.abs(goalNode.x - neighbor.x) + Math.abs(goalNode.y - neighbor.y);												if(parent != null)						{								//if the neighbor is not the same as our parent							if(!neighbor.ID.equals(parent.ID))							{								//add neighbor								addNeighbor(neighbor);							}						}												//if our parent is null, we are the starting node						//we don't need to compare the neighbor's parent to ourselves						else						{							//add neighbor							addNeighbor(neighbor);						}											}				}			}				}				private void addNeighbor(Node neighbor)		{			//if this node is not us (doesn't have same x, y)  and is walkable, add to neighbors			if(!neighbor.ID.equals(ID) && tileMap[neighbor.x][neighbor.y] == 0)			{				neighbors.add(neighbor);			}		}				/*		 * The string representation of this node		 */		public String toString()		{			return "X: " + x + " Y: " + y;		}				public boolean equals(Object o)		{			Node n = (Node)o;			return ID.equals(n.ID);		}	}	}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.