Pathfinding Advaith Siddharthan
Context What is Intelligence? Rational? Search Optimisation Reasoning Impulsive? Quicker response Less predictable Personality/Emotions: Angry/Bored/Curious
Overview The first half of this course is about classical AI Rational behaviour based on planning, search and optimisation You've encountered decision making Next 2 weeks on Pathfinding Afterwards, Planning
Introduction to Pathfinding Perhaps the most ubiquitous task in AI games
Introduction to Pathfinding Many Algorithms exist Different levels of complexity Different quality of solutions
Pathfinding: Representations Grid representation (e.g., dungeon worlds) Can move to any of 8 neighbours at each step Which one?
Pathfinding: Representations Graph Representations (e.g., streetmaps, etc) More general: you can assign costs to edges Best path = Path with lowest cost If all edges have same cost, equivalent to grid
Pathfinding Algorithms Bug Model Naive, used in many early games Breadth First Search and Dijkstra's Algorithm expensive, but finds the shortest path Best First Search A* Search Quick, but needn't find shortest path Finds shortest path reasonably fast Most games use some variation of A*
How this course works (in theory) Lectures: Introduce and give context to algorithms Practicals: Tutorials: Implement algorithms in dungeon game Make sure you do this or you risk failing One assessment will likely be a competition of NPCs coded by you I'll give some problems on the website. Email solutions to me before the tutorial. We will discuss solutions (anonymously)
How this course works in practice Lectures and tutorials are interchangable Interaction is encouraged in BOTH
Pathfinding Algorithm Properties Completeness Optimality Efficiency Will it find a path if one exists Will it find the shortest path Time: How many nodes need to be searched? Space: How many nodes need to be stored in memory?
Pathfinding algorithms Bug models Breadth First Dijkestra's Algorithm A* Algorithm Basically, Dijkestra with Heuristics A* is the objective. You will need to understand and program this algorithm. Most games use some version of A*
Bug Models Move towards goal and circumnavigate obstacles: BugPath(start, goal) 1. current = start 2. while (current!= goal) 3. return; (a) next = next square on straight line to goal (b) if (next is in obstacle) current = circumnavigate_obstacle (current); else current = next
Bug Model 1 Circumnavigate_obstacle(current) 1. walk all around obstacle, keeping track of distance to goal 2. return the closest point to goal
Bug Model 2 Circumnavigate_obstacle (current) 1. walk around the obstacle, until you hit the original line from source to goal 2. return the point on this line
Which is better?
Which is better?
Bug models Direction towards goal is known Obstacles are avoided when encountered Unlikely to give shortest path But guaranteed to find some path (if implemented properly!)
Breadth-first Search Look at all possible moves at successive depths: BFS(start, goal) 1. Initialise: queue={start}; start->parent = NULL 2. while (queue!= {}) (a) next = pop from queue (b) if (next == goal) return path; (the path is the sequence of parents from goal) (c) Add new successors of next to queue (d) Mark next as parent of each successor 3. return no path;
Breadth-first Search Cells in Queue: Step 2
Breadth-first Search Cells in Queue: Step 3
Breadth-first Search Demo http://qiao.github.com/pathfinding.js/visual/
Breadth-first Search Completeness Optimality Efficiency Time: Space: Answers will be discussed again in tutorial next week
Breadth-first search Completeness Yes Optimality Yes, if each movement has same cost No, if different costs
Pathfinding with costs A 1 C 2 8 1 D 1 F B 1 0.5 E 1 2 G
Pathfinding with costs Breadth-first is no longer guaranteed to find shortest path (in terms of cost) Which brings us to Dijkstra's Algorithm A very important algorithm in CS Used extensively for example, in networks and routing Performs similar to breadth-first when movement costs are equal Guarantees shortest path as long as costs are non-negative
Dijkstra's Algorithm Maintains two lists: Open (nodes/squares that need to be investigated) Closed (nodes/squares that have been investigated) Maintains least cost from start to each node. This is commonly called the G-function We will use notation: g(node) Uses a greedy approach Always picks the closest (least cost from start) node to investigate next
Dijkstra's Algorithm (basics) Step 1: a) Initialise closed list to NULL b) Move start (D) to open list c) Set g(d)=0 Step 2 (repeated): (a) (a) (a) Pop lowest cost member of open list (D) Assign cost-from-source to its neighbours: g(e)=0.5, g(f)=1, g(a)=8 Move D to closed list, and E,F,A to open list (b) Set parents of E,F,A to D A 2 B 1 8 C 1 D 1 0.5 1 E F 1 2 G
Dijkstra's Algorithm (basics) Now, closed={d}, open={e,f,a} Step 2 (repeated): a) Pop lowest cost member of open list (E) b) Assign cost-from-source to its neighbours: g(b)=0.5+1, g(g)=0.5+1 a) Move E to closed list, and B,G to open list a) Set parents of B,G to E A 2 1 8 C 1 B 1 D 0.5 1 E F 1 2 G
Dijkstra's Algorithm (Basics) In Step 2b, when assigning cost-from-source: If the node is already in the open list, you need to check if you have found a less costly path to it. If yes, update that node's cost and parent to reflect the cheaper path. In Step 2b and 2c: you should ignore any neighbours that are already in the closed list (do not add members of closed list back to open list!) Algorithm ends when Goal is popped from open list in Step 2a (success) Open list is empty (no path to goal)
Dijkstra's Algorithm for Grids Dijkestra (start, goal) 1. Initialise: Closed={}; Open={start}; g(start)=0; 2. While (Open!={}) (a) current=square with minimum g-value in Open (b) If (current==goal) return Path (c) Push (Closed, current) (d) For each adjacent square X i. If (X is Obstacle OR X is in Closed) Ignore X; ii. Calculate new g(x) iii. If (X is in Open AND the new g(x) < g(x)) Change parent(x) to current and Update g(x) Else Add X to Open and change parent(x) to current 3. Return NULL
Dijkstra's Algorithm Demo http://qiao.github.com/pathfinding.js/visual/
A* Algorithm Similar to Dijkstra's Algorithm, but with a Heuristic Function: So for each node in graph (or square in grid), we associate: g(node) = Cost of path from Start to Node h(node) = An estimate of cost of path from Node to Goal f(node) = g(node) + h(node) is the estimate of the best path from Start to Goal that passes through Node Just replace g() with f() in Dijkstra to get A* Replace g() with h() to get Best First Algorithm
Heuristics In simple grid worlds, straight line distance to goal is often a sensible h() function 6 5 4 6 5 5 6 6 6 3 2 1
Heuristics for A* If h(n)=0 for all n A* is identical to Dijkstra's Algorithm If h(n) <= actual cost of moving from `n' to goal A* is guaranteed to find the shortest path The lower h(n) is, the more nodes are expanded If h(n) > than actual cost of moving from `n' to goal Longer paths can be found first (not optimal). If h(n) = actual cost of moving from `n' to goal A* will search only the shortest path and nowhere else.
Heuristics for A* A good heuristic should be Examples: Fast to compute Always less than actual cost Euclidean Diatance h(n) = sqrt((n.x-goal.x)^2 + (n.y-goal.y)^2) Manhattan Distance ( diagonals not allowed) h(n) = abs(n.x-goal.x) + abs(n.y-goal.y) Chebyshev Distance (diagonals allowed) h(n) = max(abs(n.x-goal.x), abs(n.y-goal.y))
Negative Weights E -0.29 A -0.38 D 0.51 C 0.36 0.32 B