You are on page 1of 25

# ECS 170 Project 1

Michael Riedlin
993807860

Oleg Lokhvitsky
994557753

Cyprian Bergonia
994627191

Table of Contents Heuristic Admissibility Division Cost Function Terminology Cost Function Heuristic Function Heuristic Path Diagram Heuristic Explanation Heuristic Admissibility Proof Exponential Cost Function Additional Terminology Cost Function Heuristic Function Heuristic Explanation Heuristic Path Diagram Heuristic Admissibility Proof Algorithms Division Cost Function A* Bidirectional A* Dual Heuristic Bidirectional A* Exponential Cost Function Metrics Division Cost Function Exponential Cost Function Code Division Cost Function Exponential Cost Function

Division Cost Function
Terminology C is the height of the current node N is the height of the node being considered E is the height of the end node S is the Chebyshev distance between the current node and the end node S = max( | x1 - x2 |, | y1 - y2 | ) S is the Chebyshev distance between the node being considered and the end node S can be either S - 1, S, or S + 1 Cost Function (1) Heuristic Function (2a) (2b) Heuristic Path Diagram

Heuristic Explanation The idea is to tunnel at a height of 1 to the endpoint. Barring 0, a half is the minimum value a cost can take and spaces represents the minimum number of terms in the final cost function for the path. The reason we can ignore zeros is because we know that no two adjacent spaces can be zero. Climbing up from a zero height will cost at least one giving a minimum combined term for the two spaces equal to 1, the same cost as though we had tunneled. Heuristic Admissibility Proof We will prove that this heuristic is admissible by proving that it is consistent. First, we prove that equation 2a is consistent. (3a) (3b)

(3c) (3d) Equation 3d will always hold making the heuristic consistent and therefore admissible. Now, we prove that equation 2b is consistent. (3e) (3f) (3g) (3h) (3i) (3j) (3k) If N and C are small (N = C = 1) (3l) (3m) If N and C are large (N = C = 256) (3n) (3o) If N is small and C is large (N = 1, C = 256) (3p) (3q) If N is large and C is small (N = 256, C = 1) (3r) (3s)

## Exponential Cost Function

Additional Terminology D is the difference in height between current node and end node ( ). ) R1 is the remainder after dividing S by the absolute value of D ( R1 represents the steeper part of the path generated by our heuristic. R2 is the difference between S and R1 ( ) R2 represents the less steep part of the path generated by our heuristic. Cost Function (4) Heuristic Function

## (5) (6a) (6b) (6c) (6d)

(7a) (7b) (7c) (7d) Please note: Equations 5, 6a, 6b, 7a, 7b are derived from equations 6c and 7c (which are really the same equation) by plugging in the given conditions. Heuristic Explanation This heuristic represents a scenario where we descend or climb as smoothly as possible (as in the following picture), but only climb or descend an integer height per step. Because the heights are represented in integer values from 0 to 255, the smallest step we can take (that also changes height) will have a height difference of . The best case scenario involves taking steps with the smallest increments. Heuristic Path Diagram

## Heuristic Admissibility Proof First, we prove that it is admissible when

We can prove that the optimal path to take when is a flat path through a proof by contradiction. Imagine that there is a better path from C to E than a flat path. Then, that path must change height, either up and then down, or down and then up. Going down will help save cost over going flat, but going up is going to increase cost over going flat. By going down, we save (8a) By going up, we lose (8b) So, both operations together increase our path cost by (8c) This path will then cost more than going flat - thats a contradiction. What if we went down and up by more than 1 height unit? By going down, we save (8d) By going up, we lose (8e) So, both operations together increase our path cost by (8f) This path will then cost more than going flat - thats a contradiction. Second, we prove that it is admissible when . There are four possible alternate paths that we must disprove: 1. We climb extra on leg 1, and climb less somewhere on leg 1.

## 4. We climb extra on leg 2, and climb less somewhere on leg 2.

We will prove that our heuristic is admissible by showing that any alteration to our path produces a longer path, and thus our heuristic must be admissible. The key of this proof is that 2 path segments (one steeper, one less steep) are worse than a single path segment whose steepness is in the middle. For any path segment P with a slope of M and a length of L we can divide it into two path segments: P1 with a slope of M1 < M and a length of L1 P2 with a slope of M2 > M and a length of L2 We can infer: (9a) (9b) (9c) If we start at height H, then the cost of P is (10a) Likewise, if we start at height H, then the cost of P1 is (10b) And, if we start at height H, then the cost of P2 is (10c) So, now we will show than the cost of P is smaller than the sum of the costs of P1 and P2.

(11a) (11b) (11c) (11d) We can see that (12a) (12b) (12c) (12d) Finally, we can deduce that if the cost savings for P1 is less than the cost deficit for P2, then P is better than the combined path of P1 and P2. (13a) (13b) (13c) (13d) Taking the worse case scenario (14a) (14b) Clearly, equation 14b always holds. So, we have proved that any path P is at least as good as any two paths P1 and P2 that can be derived from it. That directly proves our cases #1 and #4 above. Cases #2 and #3 are proved throughly simply extending this idea: If breaking a single path into 2 is bad, then breaking each of those 2 paths into more pieces (or breaking the original into 3 or more pieces) is even worse. Finally, we prove that it is admissible when . However, this case is exactly symmetric to the case above (just swap the start and end points!), so, the proof is identical to the one given above.

Algorithms
Division Cost Function
A* The first algorithm we implemented was a simple A* algorithm using a PriorityQueue for the open list, a HashSet for the closed list and a HashMap to keep track of the g-values. This algorithm works functionally identical to what was described in class. It gives us an average time savings of 75% and nodes expanded savings of 80%. Bidirectional A* Our next step in refining the algorithm was running the A* bidirectionally. The basic idea is to just start one A* that searches for a path from start to end, and start another A* that searches for a path from end to start, and then wait until they meet.

Unfortunately, the node as which they meet (defined as a node that one algorithm tries to expand that is already in the closed (or open) list of the other algorithm) has no guarantee of being on the shortest path. So, how the Bidirectional search works is by using this (possibly) sub-optimal path as an upper bound on the f-values of nodes and expands all nodes of the reverse A* search that have an f-value thats smaller than this upper bound. After this is done, the forward A* is run again with the limitation that it is only allowed to expand nodes that are in the closed list on the reverse A* search. The outline of the algorithm is as follows: 1. Run both forward A* and reverse A* (expanding one node at a time from either forward A* or reverse A* depending on which node has the smaller f-value). Exit condition is that the node being examined already exists in the open or closed list of the other A*. 2. Run reverse A*. Exit condition is that the node being examined has a higher f-value than the path cost of the path discovered in step 1. 3. Run forward A*, expanding only nodes that are in reverse A*s closed list. Exit condition is the same as for regular A*. The optimality of this algorithm has been proved numerous times in various papers and we will just outline the way to think about it. It is basically a proof by contradiction. The algorithm is basically the same as A*, but it only allowed to expand nodes with an f-value that corresponds to some actual path. So, all the nodes on the optimal path will be expanded, since if there was a node that was on the optimal path, it has to have an f-value less than the cost of the optimal path, which is less than the cost of the suboptimal path found - so this node must be expanded in step 2. Dual Heuristic Bidirectional A* We attempted to improve on the performance of Bidirectional A* by capitalizing on the fact that the first path found (in Step 1) does not have to be optimal. So, we dont even need to use an admissible heuristic to find it. So, we implemented a version of the Bidirectional A* search where the reverse A* algorithm uses a secondary heuristic during step 1. The efficiency of this algorithm compared to the regular Bidirectional A* greatly depends on this secondary heuristic. Unfortunately, we could not find good secondary heuristics for the two cases we care about: 1. Division Cost Function on Seed Maps 2. Exponential Cost Function on Mt. St. Helen Map We did get a very good improvement (27.2% less nodes expanded, 20.3% less time taken) when using the algorithm on the map of Mt. St. Helen with the Division Cost Function. Since we couldnt get an improvement for our two cases of interest, we did not end up using this algorithm, and used the regular Bidirectional A* search for the Division Cost Function.

## Exponential Cost Function

We utilized all the same algorithms as described above for the Exponential Cost function but found that both Dual Heuristic Bidirectional A* search and regular Bidirectional A* search performed worse than simple A* search on the map of Mt. St. Helen with the Exponential Cost Function. So, we tried to optimize the A* search by utilizing our pre-existing knowledge of the geometry of this particular problem. We decided that we knew that certain areas of the map wouldnt be used because they represent geometry that looks good at the beginning, but looks bad later. For example, one wouldnt go into the crater of Mt. St. Helen even though its very easy to go in there since its difficult to get out.

So, we came up with a technique of height pruning. Basically, we defined radially expanding zones away from the end point and gave each one of those zones a minimum and a maximum height range within which nodes should be expanded. If a nodes height in one of those zones is outside this range, it is manually pruned. In effect, our algorithm is very fast on the map of Mt. St. Helen, but will not work properly (might not find the optimal path) on any other geometry. It also depends on the end point since we defined our zones around it.

Metrics
Division Cost Function (We used Bidirectional A*)
Seed 1 Path Cost Uncovered Time Taken Seed 2 Path Cost Uncovered Time Taken Seed 3 Path Cost Uncovered Time Taken Seed 4 Path Cost Uncovered Time Taken Seed 5 Path Cost Uncovered Time Taken Dijkstra
198.59165501141644 162340 1217

A*
198.59165501141644 35590 321

Bidirectional A*
198.59165501141644 19256 117

Dijkstra
198.56141550095256 162364 1231

A*
198.56141550095256 35572 281

Bidirectional A*
198.56141550095256 19362 103

Dijkstra
198.4864317411443 162247 1205

A*
198.4864317411443 35570 292

Bidirectional A*
198.4864317411443 19204 117

Dijkstra
198.70066424826052 162263 1255

A*
198.70066424826052 35588 302

Bidirectional A*
198.70066424826052 19391 120

Dijkstra
198.25499446810397 161946 1235

A*
198.25499446810397 35567 284

Bidirectional A*
198.25499446810397 19550 107

## Mt. St. Helen Path Cost Uncovered Time Taken

Dijkstra
548.3684300960452 1094119 11539

A*
548.3684300960452 334548 3801

Bidirectional A*
548.3684300960452 253619 2496

## Exponential Cost Function (We used Height Pruned A*)

Mt. St. Helen Path Cost Uncovered Time Taken Dijkstra
515.6645805015318 1203049 14754

A* (Height Pruned)
515.6645805015318 55232 328

Bidirectional A*
515.6645805015318 113647 1408

Code
Division Cost Function
import import import import import import java.awt.Point; java.util.List; java.util.LinkedList; java.util.PriorityQueue; java.util.HashSet; java.util.HashMap;

public class MtStHelensDiv_994557753 extends AStarBiAI_div { public final boolean printPath = false; } abstract class AIModuleH implements AIModule { public TerrainMap map; public Point start; public Point end; public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { return 0.0; } public final double getHeuristic(final Point pt1, final Point pt2) { return this.getHeuristic(this.map, pt1, pt2); } public final double getHeuristic(final Point p) { return this.getHeuristic(this.map, p, this.end); } public final double getHeuristic() { return this.getHeuristic(this.map, this.start, this.end); } public final void printPath(List<Point> path) { System.out.println(this.start.x + ", " + this.start.y); System.out.println("========================="); for (int i = 0; i < path.size(); i ++) {

Point p = path.get(i); System.out.println(p.x + ", " + p.y + ", " + map.getTile(p) + ", " + Node.distanceTo(p, this.end)); } System.out.println("========================="); System.out.println(end.x + ", " + end.y); } } class Node extends Point implements Comparable { private SearchAlgorithm search; public Node parent; // public int x; // public int y; public int z; private double g; private double h; public Node(SearchAlgorithm search, int x, int y, double g, Node parent) { this.search = search; this.x = x; this.y = y; this.z = (int)this.search.ai.map.getTile(this); this.g = g; this.h = -1; this.parent = parent; } public Node(SearchAlgorithm search, Point p, double g, Node parent) { this(search, p.x, p.y, g, parent); } public double f() { return this.g() + this.h(); } public double g() { return this.g; } public double h() { if (this.h != -1) return this.h; return this.h = this.search.getHeuristic(this, this.search.end); } public static int distanceTo(Point pt1, Point pt2) { return Math.max( Math.abs(pt1.x - pt2.x), Math.abs(pt1.y - pt2.y) ); } public int distanceTo(Point p) { return Node.distanceTo(this, p); } public List<Point> tracePath() { LinkedList<Point> path = new LinkedList<Point>(); Node node = this; while (node != null) { path.addFirst(node); node = node.parent; } return path;

} public List<Point> traceReversePath() { LinkedList<Point> path = new LinkedList<Point>(); Node node = this; if (node != null) node = node.parent; while (node != null) { path.add(node); node = node.parent; } return path; } @Override public int compareTo(Object node) { double a = this.f(); double b = ((Node)node).f(); if (a < b) return -1; if (a > b) return 1; return 0; } } abstract class SearchAlgorithm { public AIModuleH ai; public Node start, end, current; public abstract Node step(); public abstract double getCost(Point pt1, Point pt2); public abstract double getHeuristic(Point pt1, Point pt2); public boolean allowExpand(Point p) {return true;} } class AStarSearch extends SearchAlgorithm { protected PriorityQueue<Point> open; protected HashSet<Point> closed; protected HashMap<Point, Double> gvalues; public AStarSearch(AIModuleH ai, Point start, Point end) { // Initialize this.ai = ai; this.open = new PriorityQueue<Point>(); this.closed = new HashSet<Point>(); this.gvalues = new HashMap<Point, Double>(); // Start this.start = new Node(this, start, 0, null); this.open(this.start, 0); // End this.end = new Node(this, end, 0, null); } public Node step() { // 4. Expand Current Node this.expand(this.current); // 1. Get Next Best Node this.current = this.pop(); // 2. Close Current Node if (this.current != null) this.close(this.current, this.current.g());

// 3. Return Current Node for Examination return current; } protected void close(Point p, double g) { if (p == null) return; this.closed.add(p); } protected void unclose(Point p) { if (p == null) return; this.closed.remove(p); } public boolean isclosed(Point p) { if (p == null) return false; return this.closed.contains(p); } protected void open(Point p, double g) { if (p == null) return; this.open.add(p); this.gvalues.put(p, g); } public void unopen(Point p) { if (p == null) return; this.open.remove(p); } public boolean isopen(Point p) { if (p == null) return false; return this.open.contains(p); } public double getCost(Point p) { Double c = this.gvalues.get(p); return (c == null ? Double.MAX_VALUE : c.doubleValue()); } public double getCost(Point pt1, Point pt2) { return this.ai.map.getCost(pt1, pt2); } public double getHeuristic(Point pt1, Point pt2) { return this.ai.getHeuristic(pt1, pt2); } public Node top() { if (this.open == null || this.open.size() == 0) return null; return (Node)this.open.peek(); } protected Node pop() { if (this.open == null || this.open.size() == 0) return null; return (Node)this.open.poll(); } protected void expand(Node n) { if (n == null) return; for (Point neighbor : this.ai.map.getNeighbors(n)) { // If neighbor is closed (for consistent heuristics) if (this.isclosed(neighbor)) continue;

// Get potential new path value double cost = n.g() + this.getCost(n, neighbor); // If neighbor is closed (for non-consistnet heuristics) /* if (this.isclosed(neighbor)) { if (cost >= this.getCost(neighbor)) // If new path is worse continue; else // If new path is better this.unclose(neighbor); } */ // If neighbor is open if (cost >= this.getCost(neighbor)) continue; // Custom pruning if (!this.allowExpand(neighbor)) continue; // If neighbor has been open at one point in time before if (this.getCost(neighbor) != Double.MAX_VALUE) this.unopen(neighbor); // Open neighbor this.open(new Node( this, neighbor, cost, n ), cost); } } } class AStarAI_div extends AIModuleH { public final boolean printPath = false; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); if (distance if (distance return 0.5 * //return 0.5 } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); SearchAlgorithm search = new AStarSearch(this, this.start, this.end); Node current; while (true) { current = search.step(); == 0) return 0; == 1) return h2 / (h1 + 1); (distance - 2) + (1 / (h1 + 1)) + (h2 / 2); * Node.distanceTo(pt1, pt2);

if (current.equals(this.end)) { List<Point> path = current.tracePath(); if (printPath) this.printPath(path); return path; } } } } class AStarBiAI_div extends AIModuleH { public final boolean printPath = false; public int phase = 1; public double g = Double.MAX_VALUE; AStarSearch forward, reverse; Node fnode, rnode; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); if (distance if (distance return 0.5 * //return 0.5 } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); final AStarBiAI_div me = this; forward = new AStarSearch(this, this.start, this.end) { public AStarBiAI_div bistarai = me; public boolean allowExpand(Point p) { return !(this.bistarai.phase == 3 && !this.bistarai.reverse.isclosed(p)); } }; fnode = forward.step(); fnode = forward.step(); reverse = new AStarSearch(this, this.end, this.start) { public double getCost(Point pt1, Point pt2) { return super.getCost(pt2, pt1); } }; rnode = reverse.step(); rnode = reverse.step(); while (true) { if (phase == 1) { // Phase 1 if (forward.top().f() < reverse.top().f()) { fnode = forward.step(); if (reverse.isclosed(fnode)) { phase = 2; g = fnode.g() + reverse.getCost(fnode); } == 0) return 0; == 1) return h2 / (h1 + 1); (distance - 2) + (1 / (h1 + 1)) + (h2 / 2); * Node.distanceTo(pt1, pt2);

} else { rnode = reverse.step(); if (forward.isclosed(rnode)) { phase = 2; g = rnode.g() + forward.getCost(rnode); } } } else if (phase == 2) { // Phase 2 rnode = reverse.step(); if (rnode.f() > g) { phase = 3; } } else if (phase == 3) { //while (!reverse.isclosed(forward.top())) forward.pop(); fnode = forward.step(); if (fnode.equals(this.end)) { List<Point> path = fnode.tracePath(); if (printPath) this.printPath(path); return path; } } } } }

## Exponential Cost Function

import import import import import import java.awt.Point; java.util.List; java.util.LinkedList; java.util.PriorityQueue; java.util.HashSet; java.util.HashMap;

public class MtStHelensExp_994557753 extends AStarAI_exp { public final boolean printPath = false; } abstract class AIModuleH implements AIModule { public TerrainMap map; public Point start; public Point end; public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { return 0.0; } public final double getHeuristic(final Point pt1, final Point pt2) { return this.getHeuristic(this.map, pt1, pt2); } public final double getHeuristic(final Point p) { return this.getHeuristic(this.map, p, this.end); } public final double getHeuristic() { return this.getHeuristic(this.map, this.start, this.end); } public final void printPath(List<Point> path) { System.out.println(this.start.x + ", " + this.start.y);

System.out.println("========================="); for (int i = 0; i < path.size(); i ++) { Point p = path.get(i); System.out.println(p.x + ", " + p.y + ", " + map.getTile(p) + ", " + Node.distanceTo(p, this.end)); } System.out.println("========================="); System.out.println(end.x + ", " + end.y); } } class Node extends Point implements Comparable { private SearchAlgorithm search; public Node parent; // public int x; // public int y; public int z; private double g; private double h; public Node(SearchAlgorithm search, int x, int y, double g, Node parent) { this.search = search; this.x = x; this.y = y; this.z = (int)this.search.ai.map.getTile(this); this.g = g; this.h = -1; this.parent = parent; } public Node(SearchAlgorithm search, Point p, double g, Node parent) { this(search, p.x, p.y, g, parent); } public double f() { return this.g() + this.h(); } public double g() { return this.g; } public double h() { if (this.h != -1) return this.h; return this.h = this.search.getHeuristic(this, this.search.end); } public static int distanceTo(Point pt1, Point pt2) { return Math.max( Math.abs(pt1.x - pt2.x), Math.abs(pt1.y - pt2.y) ); } public int distanceTo(Point p) { return Node.distanceTo(this, p); } public List<Point> tracePath() { LinkedList<Point> path = new LinkedList<Point>(); Node node = this; while (node != null) { path.addFirst(node);

node = node.parent; } return path; } public List<Point> traceReversePath() { LinkedList<Point> path = new LinkedList<Point>(); Node node = this; if (node != null) node = node.parent; while (node != null) { path.add(node); node = node.parent; } return path; } @Override public int compareTo(Object node) { double a = this.f(); double b = ((Node)node).f(); if (a < b) return -1; if (a > b) return 1; return 0; } } abstract class SearchAlgorithm { public AIModuleH ai; public Node start, end, current; public abstract Node step(); public abstract double getCost(Point pt1, Point pt2); public abstract double getHeuristic(Point pt1, Point pt2); public boolean allowExpand(Point p) {return true;} } class AStarSearch extends SearchAlgorithm { protected PriorityQueue<Point> open; protected HashSet<Point> closed; protected HashMap<Point, Double> gvalues; public AStarSearch(AIModuleH ai, Point start, Point end) { // Initialize this.ai = ai; this.open = new PriorityQueue<Point>(); this.closed = new HashSet<Point>(); this.gvalues = new HashMap<Point, Double>(); // Start this.start = new Node(this, start, 0, null); this.open(this.start, 0); // End this.end = new Node(this, end, 0, null); } public Node step() { // 4. Expand Current Node this.expand(this.current); // 1. Get Next Best Node this.current = this.pop(); // 2. Close Current Node

if (this.current != null) this.close(this.current, this.current.g()); // 3. Return Current Node for Examination return current; } protected void close(Point p, double g) { if (p == null) return; this.closed.add(p); } protected void unclose(Point p) { if (p == null) return; this.closed.remove(p); } public boolean isclosed(Point p) { if (p == null) return false; return this.closed.contains(p); } protected void open(Point p, double g) { if (p == null) return; this.open.add(p); this.gvalues.put(p, g); } public void unopen(Point p) { if (p == null) return; this.open.remove(p); } public boolean isopen(Point p) { if (p == null) return false; return this.open.contains(p); } public double getCost(Point p) { Double c = this.gvalues.get(p); return (c == null ? Double.MAX_VALUE : c.doubleValue()); } public double getCost(Point pt1, Point pt2) { return this.ai.map.getCost(pt1, pt2); } public double getHeuristic(Point pt1, Point pt2) { return this.ai.getHeuristic(pt1, pt2); } public Node top() { if (this.open == null || this.open.size() == 0) return null; return (Node)this.open.peek(); } protected Node pop() { if (this.open == null || this.open.size() == 0) return null; return (Node)this.open.poll(); } protected void expand(Node n) { if (n == null) return; for (Point neighbor : this.ai.map.getNeighbors(n)) { // If neighbor is closed (for consistent heuristics)

if (this.isclosed(neighbor)) continue; // Get potential new path value double cost = n.g() + this.getCost(n, neighbor); // If neighbor is closed (for non-consistnet heuristics) /* if (this.isclosed(neighbor)) { if (cost >= this.getCost(neighbor)) // If new path is worse continue; else // If new path is better this.unclose(neighbor); } */ // If neighbor is open if (cost >= this.getCost(neighbor)) continue; // Custom pruning if (!this.allowExpand(neighbor)) continue; // If neighbor has been open at one point in time before if (this.getCost(neighbor) != Double.MAX_VALUE) this.unopen(neighbor); // Open neighbor this.open(new Node( this, neighbor, cost, n ), cost); } } } class AStarAI_div extends AIModuleH { public final boolean printPath = false; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); if (distance if (distance return 0.5 * //return 0.5 } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); SearchAlgorithm search = new AStarSearch(this, this.start, this.end); Node current; == 0) return 0; == 1) return h2 / (h1 + 1); (distance - 2) + (1 / (h1 + 1)) + (h2 / 2); * Node.distanceTo(pt1, pt2);

while (true) { current = search.step(); if (current.equals(this.end)) { List<Point> path = current.tracePath(); if (printPath) this.printPath(path); return path; } } } } class AStarBiAI_div extends AIModuleH { public final boolean printPath = false; public int phase = 1; public double g = Double.MAX_VALUE; AStarSearch forward, reverse; Node fnode, rnode; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); if (distance if (distance return 0.5 * //return 0.5 } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); final AStarBiAI_div me = this; forward = new AStarSearch(this, this.start, this.end) { public AStarBiAI_div bistarai = me; public boolean allowExpand(Point p) { return !(this.bistarai.phase == 3 && !this.bistarai.reverse.isclosed(p)); } }; fnode = forward.step(); fnode = forward.step(); reverse = new AStarSearch(this, this.end, this.start) { public double getCost(Point pt1, Point pt2) { return super.getCost(pt2, pt1); } }; rnode = reverse.step(); rnode = reverse.step(); while (true) { if (phase == 1) { // Phase 1 if (forward.top().f() < reverse.top().f()) { fnode = forward.step(); if (reverse.isclosed(fnode)) { == 0) return 0; == 1) return h2 / (h1 + 1); (distance - 2) + (1 / (h1 + 1)) + (h2 / 2); * Node.distanceTo(pt1, pt2);

phase = 2; g = fnode.g() + reverse.getCost(fnode); } } else { rnode = reverse.step(); if (forward.isclosed(rnode)) { phase = 2; g = rnode.g() + forward.getCost(rnode); } } } else if (phase == 2) { // Phase 2 rnode = reverse.step(); if (rnode.f() > g) { phase = 3; } } else if (phase == 3) { //while (!reverse.isclosed(forward.top())) forward.pop(); fnode = forward.step(); if (fnode.equals(this.end)) { List<Point> path = fnode.tracePath(); if (printPath) this.printPath(path); return path; } } } } } class AStarAI_exp extends AStarAI_div { public final boolean printPath = false; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); double dh = h2 - h1; double d1 = Math.abs(dh) % distance; double d2 = distance - d1; double dh2 = (int)(dh / distance); double dh1 = dh >= 0 ? dh2 + 1 : dh2 - 1; double cost1 = d1 * Math.exp(dh1); double cost2 = d2 * Math.exp(dh2); return cost1 + cost2; } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); SearchAlgorithm search = new AStarSearch(this, this.start, this.end) { public boolean allowExpand(Point p) { int distance = Node.distanceTo(p, this.end);

int height = (int)this.ai.map.getTile(p); if (distance < 50) { if (height > 70 || height < 48) return false; } else if (distance < 100) { if (height > 82 || height < 48) return false; } else if (distance < 150) { if (height > 87 || height < 81) return false; } else if (distance < 200) { if (height > 108 || height < 87) return false; } else if (distance < 300) { if (height > 171 || height < 108) return false; } else if (distance < 400) { if (height > 203 || height < 171) return false; } else if (distance < 1000) { if (height > 246 || height < 203) return false; } return super.allowExpand(p); } }; Node current; while (true) { current = search.step(); if (current.equals(this.end)) { List<Point> path = current.tracePath(); if (printPath) this.printPath(path); return path; } } } } class AStarBiAI_exp extends AStarBiAI_div { public final boolean printPath = false; public int phase = 1; public double g = Double.MAX_VALUE; AStarSearch forward, reverse; Node fnode, rnode; @Override public double getHeuristic(final TerrainMap map, final Point pt1, final Point pt2) { double distance = Node.distanceTo(pt1, pt2); double h1 = map.getTile(pt1); double h2 = map.getTile(pt2); double dh = h2 - h1; double d1 = Math.abs(dh) % distance; double d2 = distance - d1; double dh2 = (int)(dh / distance); double dh1 = dh >= 0 ? dh2 + 1 : dh2 - 1; double cost1 = d1 * Math.exp(dh1); double cost2 = d2 * Math.exp(dh2); return cost1 + cost2; }

public double getReverseHeuristic(final TerrainMap map, final Point pt2, final Point pt1) { return this.getHeuristic(map, pt1, pt2); } public List<Point> createPath(final TerrainMap map) { this.map = map; this.start = this.map.getStartPoint(); this.end = this.map.getEndPoint(); final AStarBiAI_exp me = this; forward = new AStarSearch(this, this.start, this.end) { public AStarBiAI_exp bistarai = me; public boolean allowExpand(Point p) { return !(this.bistarai.phase == 3 && !this.bistarai.reverse.isclosed(p)); } }; fnode = forward.step(); fnode = forward.step(); reverse = new AStarSearch(this, this.end, this.start) { public double getCost(Point pt1, Point pt2) { return super.getCost(pt2, pt1); } public double getHeuristic(Point pt1, Point pt2) { return this.ai.getHeuristic(pt2, pt1); } }; rnode = reverse.step(); rnode = reverse.step(); while (true) { if (phase == 1) { // Phase 1 if (forward.top().f() < reverse.top().f()) { fnode = forward.step(); if (reverse.isclosed(fnode)) { phase = 2; g = fnode.g() + reverse.getCost(fnode); } } else { rnode = reverse.step(); if (forward.isclosed(rnode)) { phase = 2; g = rnode.g() + forward.getCost(rnode); } } } else if (phase == 2) { // Phase 2 rnode = reverse.step(); if (rnode.f() > g) { phase = 3; } } else if (phase == 3) { //while (!reverse.isclosed(forward.top())) forward.pop(); fnode = forward.step(); if (fnode.equals(this.end)) { List<Point> path = fnode.tracePath(); if (printPath) this.printPath(path); return path; }

} } } }