A* Reference: “Artificial Intelligence for Games”, Ian Millington.
Goals Find a “pretty- short” path from point A to point B. – The best path is often prohibitively expensive to find. S G
Graph-based algorithm Graph – Vertices (Nodes) – Edges Single edge Double edge Cost – (For map traversal), distance is the norm – Cost multiplier (>1) for rough terrain. Representation – Adjacency Matrix – Adjacency List
Graph Nodes vs. Search Nodes Graph Start == 0, Goal == 6 Search Tree There are usually many search trees for the same graph. 2.0
Graph Nodes vs. Search Nodes, cont. One approach: – GraphNode(id, …) Id … searchNode = None – SearchNode(graphNode, parent) You could also have a list of children (instead of parent) Create a search node as you traverse the graph.
Depth-first search S G Need a (bool) visited value for each graph node (to prevent re-visiting). Visit a (“random”) new node from the current node. Total Cost is the edge cost of all edges traversed. We have to try all possible search graphs to find the least costly path – EXPENSIVE! Total=17 Total=19
Breadth-first search S G Need a list of “frontier” nodes and a (bool) visited flag. Expand the frontier by one hop each iteration. We end up with several search trees. Problem: A lot of search trees – MEMORY + TIME INTENSIVE (especially for large graphs). The search trees are dependent on the graph structure (2=>6 vs 0=>6)
Heuristics (dictionary.reference.com) Pertaining to a trial- and-error method of problem solving used when an algorithmic approach is impractical. In A*: an estimate of how far a given node is from the goal state. – Accuracy is critical to good performance of A* – For path-finding, often the straight-line distance. We'll store 3 things for each search node: – The parent search node (if any) – The cost-so-far value – The cost-heuristic
OPEN, CLOSED “lists” We'll maintain two "lists" of search nodes: – OPEN: Those on the search "frontier" – CLOSED: Those we've already visited – anything not on these lists is unexplored The graph node doesn't have a search node. Often more efficient to maintain: – A Priority Queue for OPEN – A state variable for each search node (OPEN or CLOSED) – We don't then need a list for CLOSED.
A* Algorithm 1.Create search node for Start Node a)Parent = None b)Cost-so-far = 0 c)Heuristic = (distance to goal) 2.Add this new node to OPEN 3.Repeat these steps as long as OPEN is empty. a)Take the best node, C, off OPEN i.If C is the goal, construct a Graph-node sequence and return. ii.If not, add C to CLOSED. b)Repeat for each of C's neighbors, N: i.If N is unexplored, create a Search Node for it and add it to OPEN. ii.If N is on OPEN and path from C c.s.f. + cost(C,N) < N c.s.f. : – Update the parent and cost-so-far of N iii.If N is on CLOSED and path from C c.s.f. + cost(C,N) < N c.s.f. : – remove it from CLOSED (???) – Update the parent and cost-so-far of N – Add N to OPEN (???) 4.Return False (no path found)
MinHeap (OPEN list) A bottle-neck is Step3a (finding best node on OPEN) Recall: OPEN is a priority-queue (lowest total cost first) A good data structure is a MinHeap – Adds: O(log n) – Removes: O(log n) – Membership: O(n), but we don't have to use this…if we're clever.
MinHeap, cont. Logical Structure: – a binary tree root is always the lowest valued node (highest priority) – a "generation" is completely filled L=>R – a node always has lower value than all descendents
MinHeap, cont. Internal structure – A list of nodes (not a tree) First element (pos=0) is a dummy node Nodes are stored L=>R, T=>B (if looking at the tree) pos node??
MinHeap, cont. Internal structure, cont. – Interesting (useful) properties For any given node index i, not the root: – i / 2 is the parent's index » i >> 1 For any given node index i, – The left child is i * 2 (i << 1) » If this value is >= len(list), there are no children – The right child is i * (i << 1 + 1) » If this value is >= len(list), the node doesn't have a right child pos node??
MinHeap (add) Step1: Add the new node to the end of the list Step2: Ensure heap constraints are satisfied. – Swap nodes if child > parent. Then check new parent against its parent pos node?? pos node?? pos node?? pos node?? Add Node(6.8) Parent(12.1) >= Child(6.8) Parent(10.3) >= Child(6.8)
MinHeap (remove) Step1: Swap the node is position 1 and position len(list)-1. Step2: Pop the last node (and save it to return at the end) Step3: Ensure heap constraints are satisfied (swap if necessary). – If the root is bigger than either child, swap the root with the smallest child. Check this node against its children recursively. – If not, end pos node?? Swap (19.8) and remove 4.5 Swap 19.8 with 7.0 Swap 8.5 with 19.8 pos node?? pos node?? pos node??
Under-/over-estimating heuristic Very important to get as close as possible! – Too high => find a "bad" path before a "good" – Too low => look at too many nodes.
Nav-Meshes A common way to represent: – Walkable areas of a map. – Cover-spots – Jumping-points (to cross a chasm) – Ladders – Save points, ammo drops, etc. Key Ideas: – Not usually visible – Usually much lower poly-count than the actual ground
Nav-Meshes, cont. Display Mesh (16541 faces) Nav Mesh (301 faces)
Nav-Meshes, cont. We can use these for pathfinding… – Nodes are faces (their center?) – Edges are connections between neighboring faces Cost is just the euclidean distance between centers. Probably best done off-line – Finding neighbors can be costly.
Another application of A* … … … … … … … … … … Hueristic?