(svn r13595) [NoAI] -Add [Library CHANGE]: introducing graph.aystar v4 and pathfinder.road v3, allowing directional searches, tweaking those few bad routes into perfect routes (Yexo) noai
authortruebrain
Fri, 20 Jun 2008 23:12:21 +0000
branchnoai
changeset 11039 a45899beee2a
parent 11029 776c7cc8bda5
child 11040 711ae78a503c
(svn r13595) [NoAI] -Add [Library CHANGE]: introducing graph.aystar v4 and pathfinder.road v3, allowing directional searches, tweaking those few bad routes into perfect routes (Yexo)
bin/ai/library/graph/aystar/library.nut
bin/ai/library/graph/aystar/main.nut
bin/ai/library/pathfinder/road/library.nut
bin/ai/library/pathfinder/road/main.nut
bin/ai/regression/regression.nut
bin/ai/regression/regression.txt
--- a/bin/ai/library/graph/aystar/library.nut	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/library/graph/aystar/library.nut	Fri Jun 20 23:12:21 2008 +0000
@@ -4,7 +4,7 @@
 	function GetAuthor()      { return "OpenTTD NoAI Developers Team"; }
 	function GetName()        { return "AyStar"; }
 	function GetDescription() { return "An implementation of AyStar"; }
-	function GetVersion()     { return 3; }
+	function GetVersion()     { return 4; }
 	function GetDate()        { return "2008-06-11"; }
 	function CreateInstance() { return "AyStar"; }
 }
--- a/bin/ai/library/graph/aystar/main.nut	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/library/graph/aystar/main.nut	Fri Jun 20 23:12:21 2008 +0000
@@ -10,58 +10,70 @@
 	_cost_callback = null;
 	_estimate_callback = null;
 	_neighbours_callback = null;
+	_check_direction_callback = null;
 	_cost_callback_param = null;
 	_estimate_callback_param = null;
 	_neighbours_callback_param = null;
+	_check_direction_callback_param = null;
 	_open = null;
 	_closed = null;
 	_goals = null;
 
 	/**
 	 * @param cost_callback A function that returns the cost of a path. It
-	 *  should accept three parameters, old_path, new_node and
+	 *  should accept four parameters, old_path, new_tile, new_direction and
 	 *  cost_callback_param. old_path is an instance of AyStar.Path, and
 	 *  new_node is the new node that is added to that path. It should return
 	 *  the cost of the path including new_node.
 	 * @param estimate_callback A function that returns an estimate from a node
-	 *  to the goal node. It should accept three parameters, node, goal_nodes and
-	 *  estimate_callback_param. It should return an estimate to the cost from
-	 *  the lowest cost between node and any node out of goal_nodes. Note that
-	 *  this estimate is not allowed to be higher than the real cost between node
-	 *  and any of goal_nodes. A lower value is fine, however the closer it is to
-	 *  the real value, the better the performance.
+	 *  to the goal node. It should accept four parameters, tile, direction,
+	 *  goal_nodes and estimate_callback_param. It should return an estimate to
+	 *  the cost from the lowest cost between node and any node out of goal_nodes.
+	 *  Note that this estimate is not allowed to be higher than the real cost
+	 *  between node and any of goal_nodes. A lower value is fine, however the
+	 *  closer it is to the real value, the better the performance.
 	 * @param neighbours_callback A function that returns all neighbouring nodes
 	 *  from a given node. It should accept three parameters, current_path, node
 	 *  and neighbours_callback_param. It should return an array containing all
-	 *  neighbouring nodes.
+	 *  neighbouring nodes, which are an array in the form [tile, direction].
+	 * @param check_direction_callback A function that returns either false or
+	 *  true. It should accept four parameters, tile, existing_direction,
+	 *  new_direction and check_direction_callback_param. It should check
+	 *  if both directions can go together on a single tile.
 	 * @param cost_callback_param This parameters will be passed to cost_callback
-	 *  as third parameter. Useful to send is an instance of an object.
+	 *  as fourth parameter. Useful to send is an instance of an object.
 	 * @param estimate_callback_param This parameters will be passed to
-	 *  estimate_callback as third parameter. Useful to send is an instance of an
+	 *  estimate_callback as fourth parameter. Useful to send is an instance of an
 	 *  object.
 	 * @param neighbours_callback_param This parameters will be passed to
 	 *  neighbours_callback as third parameter. Useful to send is an instance of
 	 *  an object.
+	 * @param check_direction_callback_param This parameters will be passed to
+	 *  check_direction_callback as fourth parameter. Useful to send is an
+	 *  instance of an object.
 	 */
-	constructor(cost_callback, estimate_callback, neighbours_callback, cost_callback_param = null,
-	            estimate_callback_param = null, neighbours_callback_param = null)
+	constructor(cost_callback, estimate_callback, neighbours_callback, check_direction_callback, cost_callback_param = null,
+	            estimate_callback_param = null, neighbours_callback_param = null, check_direction_callback_param = null)
 	{
-		if (typeof(cost_callback) != "function") throw("'cost_callback' has be a function-pointer.");
-		if (typeof(estimate_callback) != "function") throw("'estimate_callback' has be a function-pointer.");
-		if (typeof(neighbours_callback) != "function") throw("'neighbours_callback' has be a function-pointer.");
+		if (typeof(cost_callback) != "function") throw("'cost_callback' has to be a function-pointer.");
+		if (typeof(estimate_callback) != "function") throw("'estimate_callback' has to be a function-pointer.");
+		if (typeof(neighbours_callback) != "function") throw("'neighbours_callback' has to be a function-pointer.");
+		if (typeof(check_direction_callback) != "function") throw("'check_direction_callback' has to be a function-pointer.");
 
 		this._cost_callback = cost_callback;
 		this._estimate_callback = estimate_callback;
 		this._neighbours_callback = neighbours_callback;
+		this._check_direction_callback = check_direction_callback;
 		this._cost_callback_param = cost_callback_param;
 		this._estimate_callback_param = estimate_callback_param;
 		this._neighbours_callback_param = neighbours_callback_param;
+		this._check_direction_callback_param = check_direction_callback_param;
 	}
 
 	/**
 	 * Initialize a path search between sources and goals.
-	 * @param sources The source nodes.
-	 * @param goals The target nodes.
+	 * @param sources The source nodes, pairs of [tile, direction].
+	 * @param goals The target tiles.
 	 */
 	function InitializePath(sources, goals);
 
@@ -87,8 +99,10 @@
 	this._closed = AIList();
 
 	foreach (node in sources) {
-		local new_path = this.Path(null, node, this._cost_callback, this._cost_callback_param);
-		this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node, goals, this._estimate_callback_param));
+		if (node[1] <= 0) throw("directional value should never be zero or negative.");
+
+		local new_path = this.Path(null, node[0], node[1], this._cost_callback, this._cost_callback_param);
+		this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node[0], node[1], goals, this._estimate_callback_param));
 	}
 
 	this._goals = goals;
@@ -101,24 +115,49 @@
 	while (this._open.Count() > 0 && (iterations == -1 || iterations-- > 0)) {
 		/* Get the path with the best score so far */
 		local path = this._open.Pop();
-		local cur_node = path.GetNode();
+		local cur_tile = path.GetTile();
 		/* Make sure we didn't already passed it */
-		if (this._closed.HasItem(cur_node)) continue;
+		if (this._closed.HasItem(cur_tile)) {
+			/* If the direction is already on the list, skip this entry */
+			if ((this._closed.GetValue(cur_tile) & path.GetDirection()) != 0) continue;
+
+			/* Scan the path for a possible collision */
+			local scan_path = path.GetParent();
+
+			local mismatch = false;
+			while (scan_path != null) {
+				if (scan_path.GetTile() == cur_tile) {
+					if (!this._check_direction_callback(cur_tile, scan_path.GetDirection(), path.GetDirection(), this._check_direction_callback_param)) {
+						mismatch = true;
+						break;
+					}
+				}
+				scan_path = scan_path.GetParent();
+			}
+			if (mismatch) continue;
+
+			/* Add the new direction */
+			this._closed.SetValue(cur_tile, this._closed.GetValue(cur_tile) | path.GetDirection());
+		} else {
+			/* New entry, make sure we don't check it again */
+			this._closed.AddItem(cur_tile, path.GetDirection());
+		}
 		/* Check if we found the end */
-		foreach (node in this._goals) {
-			if (cur_node == node) {
+		foreach (tile in this._goals) {
+			if (cur_tile == tile) {
 				this._CleanPath();
 				return path;
 			}
 		}
-		this._closed.AddItem(cur_node, 0);
 		/* Scan all neighbours */
-		local neighbours = this._neighbours_callback(path, cur_node, this._neighbours_callback_param);
+		local neighbours = this._neighbours_callback(path, cur_tile, this._neighbours_callback_param);
 		foreach (node in neighbours) {
-			if (this._closed.HasItem(node)) continue;
+			if (node[1] <= 0) throw("directional value should never be zero or negative.");
+
+			if ((this._closed.GetValue(node[0]) & node[1]) != 0) continue;
 			/* Calculate the new paths and add them to the open list */
-			local new_path = this.Path(path, node, this._cost_callback, this._cost_callback_param);
-			this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node, this._goals, this._estimate_callback_param));
+			local new_path = this.Path(path, node[0], node[1], this._cost_callback, this._cost_callback_param);
+			this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node[0], node[1], this._goals, this._estimate_callback_param));
 		}
 	}
 
@@ -143,20 +182,27 @@
 class AyStar.Path
 {
 	_prev = null;
-	_node = null;
+	_tile = null;
+	_direction = null;
 	_cost = null;
 
-	constructor(old_path, new_node, cost_callback, cost_callback_param)
+	constructor(old_path, new_tile, new_direction, cost_callback, cost_callback_param)
 	{
 		this._prev = old_path;
-		this._node = new_node;
-		this._cost = cost_callback(old_path, new_node, cost_callback_param);
+		this._tile = new_tile;
+		this._direction = new_direction;
+		this._cost = cost_callback(old_path, new_tile, new_direction, cost_callback_param);
 	};
 
 	/**
-	 * Return the node where this (partial-)path ends.
+	 * Return the tile where this (partial-)path ends.
 	 */
-	function GetNode() { return this._node; }
+	function GetTile() { return this._tile; }
+
+	/**
+	 * Return the direction from which we entered the tile in this (partial-)path.
+	 */
+	function GetDirection() { return this._direction; }
 
 	/**
 	 * Return an instance of this class leading to the previous node.
--- a/bin/ai/library/pathfinder/road/library.nut	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/library/pathfinder/road/library.nut	Fri Jun 20 23:12:21 2008 +0000
@@ -4,7 +4,7 @@
 	function GetAuthor()      { return "OpenTTD NoAI Developers Team"; }
 	function GetName()        { return "Road"; }
 	function GetDescription() { return "An implementation of a road pathfinder"; }
-	function GetVersion()     { return 2; }
+	function GetVersion()     { return 3; }
 	function GetDate()        { return "2008-06-18"; }
 	function CreateInstance() { return "Road"; }
 }
--- a/bin/ai/library/pathfinder/road/main.nut	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/library/pathfinder/road/main.nut	Fri Jun 20 23:12:21 2008 +0000
@@ -12,7 +12,7 @@
  */
 class Road
 {
-	_aystar_class = import("graph.aystar", "", 3);
+	_aystar_class = import("graph.aystar", "", 4);
 	_max_cost = null;              ///< The maximum cost for a route.
 	_cost_tile = null;             ///< The cost for a single tile.
 	_cost_no_existing_road = null; ///< The cost that is added to _cost_tile if no road exists yet.
@@ -40,7 +40,7 @@
 		this._cost_coast = 20;
 		this._max_bridge_length = 10;
 		this._max_tunnel_length = 20;
-		this._pathfinder = this._aystar_class(this._Cost, this._Estimate, this._Neighbours, this, this, this);
+		this._pathfinder = this._aystar_class(this._Cost, this._Estimate, this._Neighbours, this._CheckDirection, this, this, this, this);
 
 		this.cost = this.Cost(this);
 		this._running = false;
@@ -48,11 +48,18 @@
 
 	/**
 	 * Initialize a path search between sources and goals.
-	 * @param sources The source nodes.
-	 * @param goals The target nodes.
+	 * @param sources The source tiles.
+	 * @param goals The target tiles.
 	 * @see AyStar::InitializePath()
 	 */
-	function InitializePath(sources, goals) { this._pathfinder.InitializePath(sources, goals); }
+	function InitializePath(sources, goals) {
+		local nsources = [];
+
+		foreach (node in sources) {
+			nsources.push([node, 0xFF]);
+		}
+		this._pathfinder.InitializePath(nsources, goals);
+	}
 
 	/**
 	 * Try to find the path as indicated with InitializePath with the lowest cost.
@@ -145,32 +152,32 @@
 	return slopes;
 }
 
-function Road::_Cost(path, new_node, self)
+function Road::_Cost(path, new_tile, new_direction, self)
 {
 	/* path == null means this is the first node of a path, so the cost is 0. */
 	if (path == null) return 0;
 
-	local prev_node = path.GetNode();
+	local prev_tile = path.GetTile();
 
 	/* If the new tile is a bridge / tunnel tile, check whether we came from the other
 	 * end of the bridge / tunnel or if we just entered the bridge / tunnel. */
-	if (AIBridge.IsBridgeTile(new_node)) {
-		if (AIBridge.GetOtherBridgeEnd(new_node) != prev_node) return path.GetCost() + self._cost_tile;
-		return path.GetCost() + AIMap.DistanceManhattan(new_node, prev_node) * self._cost_tile + self._GetBridgeNumSlopes(new_node, prev_node) * self._cost_slope;
+	if (AIBridge.IsBridgeTile(new_tile)) {
+		if (AIBridge.GetOtherBridgeEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
+		return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
 	}
-	if (AITunnel.IsTunnelTile(new_node)) {
-		if (AITunnel.GetOtherTunnelEnd(new_node) != prev_node) return path.GetCost() + self._cost_tile;
-		return path.GetCost() + AIMap.DistanceManhattan(new_node, prev_node) * self._cost_tile;
+	if (AITunnel.IsTunnelTile(new_tile)) {
+		if (AITunnel.GetOtherTunnelEnd(new_tile) != prev_tile) return path.GetCost() + self._cost_tile;
+		return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * self._cost_tile;
 	}
 
 	/* If the two tiles are more then 1 tile apart, the pathfinder wants a bridge or tunnel
 	 * to be build. It isn't an existing bridge / tunnel, as that case is already handled. */
-	if (AIMap.DistanceManhattan(new_node, prev_node) > 1) {
+	if (AIMap.DistanceManhattan(new_tile, prev_tile) > 1) {
 		/* Check if we should build a bridge or a tunnel. */
-		if (AITunnel.GetOtherTunnelEnd(new_node) == prev_node) {
-			return path.GetCost() + AIMap.DistanceManhattan(new_node, prev_node) * (self._cost_tile + self._cost_tunnel_per_tile);
+		if (AITunnel.GetOtherTunnelEnd(new_tile) == prev_tile) {
+			return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_tunnel_per_tile);
 		} else {
-			return path.GetCost() + AIMap.DistanceManhattan(new_node, prev_node) * (self._cost_tile + self._cost_bridge_per_tile) + self._GetBridgeNumSlopes(new_node, prev_node) * self._cost_slope;
+			return path.GetCost() + AIMap.DistanceManhattan(new_tile, prev_tile) * (self._cost_tile + self._cost_bridge_per_tile) + self._GetBridgeNumSlopes(new_tile, prev_tile) * self._cost_slope;
 		}
 	}
 
@@ -178,30 +185,30 @@
 	 * the TileID of the previous node and comparing that to the difference between the
 	 * previous node and the node before that. */
 	local cost = self._cost_tile;
-	if (path.GetParent() != null && (prev_node - path.GetParent().GetNode()) != (new_node - prev_node) &&
-		AIMap.DistanceManhattan(path.GetParent().GetNode(), prev_node) == 1) {
+	if (path.GetParent() != null && (prev_tile - path.GetParent().GetTile()) != (new_tile - prev_tile) &&
+		AIMap.DistanceManhattan(path.GetParent().GetTile(), prev_tile) == 1) {
 		cost += self._cost_turn;
 	}
 
 	/* Check if the new tile is a coast tile. */
-	if (AITile.IsCoastTile(new_node)) {
+	if (AITile.IsCoastTile(new_tile)) {
 		cost += self._cost_coast;
 	}
 
 	/* Check if the last tile was sloped. */
-	if (path.GetParent() != null && !AIBridge.IsBridgeTile(prev_node) && !AITunnel.IsTunnelTile(prev_node) &&
-	    self._IsSlopedRoad(path.GetParent().GetNode(), prev_node, new_node)) {
+	if (path.GetParent() != null && !AIBridge.IsBridgeTile(prev_tile) && !AITunnel.IsTunnelTile(prev_tile) &&
+	    self._IsSlopedRoad(path.GetParent().GetTile(), prev_tile, new_tile)) {
 		cost += self._cost_slope;
 	}
 
-	if (!AIRoad.AreRoadTilesConnected(prev_node, new_node)) {
+	if (!AIRoad.AreRoadTilesConnected(prev_tile, new_tile)) {
 		cost += self._cost_no_existing_road;
 	}
 
 	return path.GetCost() + cost;
 }
 
-function Road::_Estimate(cur_tile, goal_tiles, self)
+function Road::_Estimate(cur_tile, cur_direction, goal_tiles, self)
 {
 	local min_cost = self._max_cost;
 	/* As estimate we multiply the lowest possible cost for a single tile with
@@ -222,21 +229,21 @@
 	if ((AIBridge.IsBridgeTile(cur_node) || AITunnel.IsTunnelTile(cur_node)) &&
 	     AITile.HasTransportType(cur_node, AITile.TRANSPORT_ROAD)) {
 		local other_end = AIBridge.IsBridgeTile(cur_node) ? AIBridge.GetOtherBridgeEnd(cur_node) : AITunnel.GetOtherTunnelEnd(cur_node);
-		/* The other end of the bridge / tunnel is a neighbour. */
-		tiles.push(other_end);
 		local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
 		if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AITile.IsBuildable(next_tile) || AIRoad.IsRoadTile(next_tile)) {
-			tiles.push(next_tile);
+			tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
 		}
-	} else if (path.GetParent() != null && AIMap.DistanceManhattan(cur_node, path.GetParent().GetNode()) > 1) {
-		local other_end = path.GetParent().GetNode();
+		/* The other end of the bridge / tunnel is a neighbour. */
+		tiles.push([other_end, self._GetDirection(next_tile, cur_node) << 4]);
+	} else if (path.GetParent() != null && AIMap.DistanceManhattan(cur_node, path.GetParent().GetTile()) > 1) {
+		local other_end = path.GetParent().GetTile();
 		local next_tile = cur_node + (cur_node - other_end) / AIMap.DistanceManhattan(cur_node, other_end);
 		if (AIRoad.AreRoadTilesConnected(cur_node, next_tile) || AIRoad.BuildRoad(cur_node, next_tile)) {
-			tiles.push(next_tile);
+			tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
 		}
 	} else {
-		local offsets = [AIMap.GetTileIndex(0,1), AIMap.GetTileIndex(0, -1),
-		                 AIMap.GetTileIndex(1,0), AIMap.GetTileIndex(-1,0)];
+		local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
+		                 AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
 		/* Check all tiles adjacent to the current tile. */
 		foreach (offset in offsets) {
 			local next_tile = cur_node + offset;
@@ -245,17 +252,17 @@
 			 * 2) We can build a road to the next tile.
 			 * 3) The next tile is the entrance of a tunnel / bridge in the correct direction. */
 			if (AIRoad.AreRoadTilesConnected(cur_node, next_tile)) {
-				tiles.push(next_tile);
+				tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
 			} else if ((AITile.IsBuildable(next_tile) || AIRoad.IsRoadTile(next_tile)) &&
-					(path.GetParent() == null || AIRoad.CanBuildConnectedRoadPartsHere(cur_node, path.GetParent().GetNode(), next_tile)) &&
+					(path.GetParent() == null || AIRoad.CanBuildConnectedRoadPartsHere(cur_node, path.GetParent().GetTile(), next_tile)) &&
 					AIRoad.BuildRoad(cur_node, next_tile)) {
-				tiles.push(next_tile);
+				tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
 			} else if (self._CheckTunnelBridge(cur_node, next_tile)) {
-				tiles.push(next_tile);
+				tiles.push([next_tile, self._GetDirection(cur_node, next_tile)]);
 			}
 		}
 		if (path.GetParent() != null) {
-			local bridges = self._GetTunnelsBridges(path.GetParent().GetNode(), cur_node);
+			local bridges = self._GetTunnelsBridges(path.GetParent().GetTile(), cur_node, self._GetDirection(path.GetParent().GetTile(), cur_node) << 4);
 			foreach (tile in bridges) {
 				tiles.push(tile);
 			}
@@ -264,13 +271,26 @@
 	return tiles;
 }
 
+function Road::_CheckDirection(tile, existing_direction, new_direction, self)
+{
+	return false;
+}
+
+function Road::_GetDirection(from, to)
+{
+	if (from - to == 1) return 1;
+	if (from - to == -1) return 2;
+	if (from - to == AIMap.GetMapSizeX()) return 4;
+	if (from - to == -AIMap.GetMapSizeX()) return 8;
+}
+
 /**
  * Get a list of all bridges and tunnels that can be build from the
  * current tile. Bridges will only be build starting on non-flat tiles
  * for performance reasons. Tunnels will only be build if no terraforming
  * is needed on both ends.
  */
-function Road::_GetTunnelsBridges(last_node, cur_node)
+function Road::_GetTunnelsBridges(last_node, cur_node, bridge_dir)
 {
 	local slope = AITile.GetSlope(cur_node);
 	if (slope == AITile.SLOPE_FLAT) return [];
@@ -280,7 +300,7 @@
 		local bridge_list = AIBridgeList_Length(i + 1);
 		local target = cur_node + i * (cur_node - last_node);
 		if (!bridge_list.IsEmpty() && AIBridge.BuildBridge(AIVehicle.VEHICLE_ROAD, bridge_list.Begin(), cur_node, target)) {
-			tiles.push(target);
+			tiles.push([target, bridge_dir]);
 		}
 	}
 
@@ -292,7 +312,7 @@
 	local prev_tile = cur_node + (cur_node - other_tunnel_end) / tunnel_length;
 	if (AITunnel.GetOtherTunnelEnd(other_tunnel_end) == cur_node && tunnel_length >= 2 &&
 			prev_tile == last_node && tunnel_length < _max_tunnel_length && AITunnel.BuildTunnel(AIVehicle.VEHICLE_ROAD, cur_node)) {
-		tiles.push(other_tunnel_end);
+		tiles.push([other_tunnel_end, bridge_dir]);
 	}
 	return tiles;
 }
@@ -326,12 +346,12 @@
 	return false;
 }
 
-function Road::_CheckTunnelBridge(current_node, new_node)
+function Road::_CheckTunnelBridge(current_tile, new_tile)
 {
-	if (!AIBridge.IsBridgeTile(new_node) && !AITunnel.IsTunnelTile(new_node)) return false;
-	local dir = new_node - current_node;
-	local other_end = AIBridge.IsBridgeTile(new_node) ? AIBridge.GetOtherBridgeEnd(new_node) : AITunnel.GetOtherTunnelEnd(new_node);
-	local dir2 = other_end - new_node;
+	if (!AIBridge.IsBridgeTile(new_tile) && !AITunnel.IsTunnelTile(new_tile)) return false;
+	local dir = new_tile - current_tile;
+	local other_end = AIBridge.IsBridgeTile(new_tile) ? AIBridge.GetOtherBridgeEnd(new_tile) : AITunnel.GetOtherTunnelEnd(new_tile);
+	local dir2 = other_end - new_tile;
 	if ((dir < 0 && dir2 > 0) || (dir > 0 && dir2 < 0)) return false;
 	dir = abs(dir);
 	dir2 = abs(dir2);
--- a/bin/ai/regression/regression.nut	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/regression/regression.nut	Fri Jun 20 23:12:21 2008 +0000
@@ -1,7 +1,7 @@
 import("queue.priority_queue", "PQ", 2);
 import("queue.binary_heap", "BH", 1);
-import("graph.aystar", "AS", 3);
-import("pathfinder.road", "RPF", 2);
+import("graph.aystar", "AS", 4);
+import("pathfinder.road", "RPF", 3);
 
 class Regression extends AIController {
 	function Start();
@@ -377,22 +377,23 @@
 	AIEventController.EnableAllEvents();
 }
 
-function cost_callback(old_path, new_tile, self) { if (old_path == null) return 0; return old_path.GetCost() + 1; }
-function estimate_callback(tile, goals, self) { return goals[0] - tile; }
-function neighbours_callback(path, cur_tile, self) { return [cur_tile + 1]; }
+function cost_callback(old_path, new_tile, new_direction, self) { if (old_path == null) return 0; return old_path.GetCost() + 1; }
+function estimate_callback(tile, direction, goals, self) { return goals[0] - tile; }
+function neighbours_callback(path, cur_tile, self) { return [[cur_tile + 1, 1]]; }
+function check_direction_callback(tile, existing_direction, new_direction, self) { return false; }
 
 function Regression::Graph()
 {
 	print("--AyStar--");
 	print("  Fastest path:");
-	local as = AS(cost_callback, estimate_callback, neighbours_callback);
+	local as = AS(cost_callback, estimate_callback, neighbours_callback, check_direction_callback);
 
 	local path = false;
-	as.InitializePath([1], [10]);
+	as.InitializePath([[1, 1]], [10]);
 	while (path == false) path = as.FindPath(5);
 
 	while (path != null) {
-		print("    Tile " + path.GetNode());
+		print("    Tile " + path.GetTile());
 		path = path.GetParent();
 	}
 }
@@ -744,7 +745,7 @@
 	while (path == false) path = pathfinder.FindPath(1000);
 
 	while (path != null) {
-		print("    Tile " + path.GetNode());
+		print("    Tile " + path.GetTile());
 		path = path.GetParent();
 	}
 }
--- a/bin/ai/regression/regression.txt	Thu Jun 19 18:09:29 2008 +0000
+++ b/bin/ai/regression/regression.txt	Fri Jun 20 23:12:21 2008 +0000
@@ -5843,20 +5843,20 @@
     Tile 44443
     Tile 44187
     Tile 43931
-    Tile 43930
-    Tile 43929
-    Tile 43928
-    Tile 43927
-    Tile 43926
-    Tile 43925
-    Tile 43669
-    Tile 43413
-    Tile 43157
-    Tile 42901
-    Tile 42645
-    Tile 42389
-    Tile 42133
-    Tile 41877
+    Tile 43675
+    Tile 43419
+    Tile 43163
+    Tile 42907
+    Tile 42651
+    Tile 42395
+    Tile 42139
+    Tile 41883
+    Tile 41627
+    Tile 41626
+    Tile 41625
+    Tile 41624
+    Tile 41623
+    Tile 41622
     Tile 41621
     Tile 41365
     Tile 41109