(svn r13463) [NoAI] -Change [Library CHANGE]: AyStar is now more object oriented, and you can indicate the amount of iterations FindPath should do in one go (tnx to Yexo and TrueBrain) noai
authortruebrain
Wed, 11 Jun 2008 13:04:27 +0000
branchnoai
changeset 10912 48d072a192b5
parent 10910 85f71706f496
child 10915 21ea526fd075
(svn r13463) [NoAI] -Change [Library CHANGE]: AyStar is now more object oriented, and you can indicate the amount of iterations FindPath should do in one go (tnx to Yexo and TrueBrain)
bin/ai/library/graph/aystar/library.nut
bin/ai/library/graph/aystar/main.nut
bin/ai/regression/regression.nut
--- a/bin/ai/library/graph/aystar/library.nut	Wed Jun 11 12:13:22 2008 +0000
+++ b/bin/ai/library/graph/aystar/library.nut	Wed Jun 11 13:04:27 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 1; }
+	function GetVersion()     { return 2; }
 	function GetDate()        { return "2008-06-11"; }
 	function CreateInstance() { return "AyStar"; }
 }
--- a/bin/ai/library/graph/aystar/main.nut	Wed Jun 11 12:13:22 2008 +0000
+++ b/bin/ai/library/graph/aystar/main.nut	Wed Jun 11 13:04:27 2008 +0000
@@ -9,88 +9,147 @@
  */
 class AyStar
 {
+	_cost_callback = null;
+	_estimate_callback = null;
+	_neighbours_callback = null;
+	_open = null;
+	_closed = null;
+	_goals = null;
+
 	/**
-	 * Find a single path between sources and goals with the lowest cost.
-	 * @param sources The source tiles.
-	 * @param goals The target tiles.
 	 * @param cost_callback A function that returns the cost of a path. It
-	 *  should accept 2 parameters, old_path and new_tile. old_path is an
-	 *  instance of AyStar.Path, and new_tile is the new tile that is added to
-	 *  that path. It should return the cost of the path including new_tile.
-	 * @param estimate_callback A function that returns an estimate from a tile
-	 *  to the goal tile. It should accept two parameters, tile and goal_tiles.
+	 *  should accept 2 parameters, old_path and new_node. 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 two parameters, node and goal_nodes.
 	 *  It should return an estimate to the cost from the lowest cost between
-	 *  tile and any tile out of goal_tiles. Note that this estimate is not
-	 *  allowed to be higher than the real cost between tile and any of
-	 *  goal_tiles. A lower value is fine, however the closer it is to the real
+	 *  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 tiles
-	 *  from a given tile. It should accept two parameters, current_path and tile,
-	 *  and return an array containing all neighbouring tiles.
-	 * @return A route to from an element of sources to an element of goals or
-	 *  null if there is no route.
+	 * @param neighbours_callback A function that returns all neighbouring nodes
+	 *  from a given node. It should accept two parameters, current_path and node,
+	 *  and return an array containing all neighbouring nodes.
 	 */
-	function FindPath(sources, goals, cost_callback, estimate_callback, neighbours_callback);
+	constructor(cost_callback, estimate_callback, neighbours_callback)
+	{
+		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.");
+
+		this._cost_callback = cost_callback;
+		this._estimate_callback = estimate_callback;
+		this._neighbours_callback = neighbours_callback;
+	}
+
+	/**
+	 * Initialize a path search between sources and goals.
+	 * @param sources The source nodes.
+	 * @param goals The target nodes.
+	 */
+	function InitializePath(sources, goals);
+
+	/**
+	 * Try to find the path as indicated with InitializePath with the lowest cost.
+	 * @param iterations After how many iterations it should abort for a moment.
+	 *  This value should either be -1 for infinite, or > 0. Any other value
+	 *  aborts immediatly and will never find a path.
+	 * @return A route if one was found, or false if the amount of iterations was
+	 *  reached, or null if no path was found.
+	 *  You can call this function over and over as long as it returns false,
+	 *  which is an indication it is not yet done looking for a route.
+	 */
+	function FindPath(iterations);
 }
 
-function AyStar::FindPath(sources, goals, cost_callback, estimate_callback, neighbours_callback)
+function AyStar::InitializePath(sources, goals)
 {
-	local closed = AIList();
-	local open = Queue();
-	/* Create the initial open list */
-	foreach (tile in sources) {
-		local new_path = this.Path(null, tile, cost_callback);
-		open.Insert(new_path, new_path.GetCost() + estimate_callback(tile, goals));
+	if (typeof(sources) != "array" || sources.len() == 0) throw("sources has be a non-empty array.");
+	if (typeof(goals) != "array" || goals.len() == 0) throw("goals has be a non-empty array.");
+
+	this._open = Queue();
+	this._closed = AIList();
+
+	foreach (node in sources) {
+		local new_path = this.Path(null, node, this._cost_callback);
+		this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node, goals));
 	}
-	while (open.Count() > 0) {
+
+	this._goals = goals;
+}
+
+function AyStar::FindPath(iterations)
+{
+	if (this._open == null) throw("can't execute over an uninitialized path");
+
+	while (this._open.Count() > 0 && (iterations == -1 || iterations-- > 0)) {
 		/* Get the path with the best score so far */
-		local path = open.Pop();
-		local cur_tile = path.GetTile();
+		local path = this._open.Pop();
+		local cur_node = path.GetNode();
 		/* Make sure we didn't already passed it */
-		if (closed.HasItem(cur_tile)) continue;
+		if (this._closed.HasItem(cur_node)) continue;
 		/* Check if we found the end */
-		foreach (tile in goals) {
-			if (cur_tile == tile) return path;
+		foreach (node in this._goals) {
+			if (cur_node == node) {
+				this._CleanPath();
+				return path;
+			}
 		}
-		closed.AddItem(cur_tile, 0);
+		this._closed.AddItem(cur_node, 0);
 		/* Scan all neighbours */
-		local neighbours = neighbours_callback(path, cur_tile);
-		foreach (tile in neighbours) {
-			if (closed.HasItem(tile)) continue;
+		local neighbours = this._neighbours_callback(path, cur_node);
+		foreach (node in neighbours) {
+			if (this._closed.HasItem(node)) continue;
 			/* Calculate the new paths and add them to the open list */
-			local new_path = this.Path(path, tile, cost_callback);
-			open.Insert(new_path, new_path.GetCost() + estimate_callback(tile, goals));
+			local new_path = this.Path(path, node, this._cost_callback);
+			this._open.Insert(new_path, new_path.GetCost() + this._estimate_callback(node, this._goals));
 		}
 	}
 
+	if (this._open.Count() > 0) return false;
+	this._CleanPath();
 	return null;
 }
 
+function AyStar::_CleanPath()
+{
+	this._closed = null;
+	this._open = null;
+	this._goals = null;
+}
+
+/**
+ * The path of the AyStar algorithm.
+ *  It is reversed, that is, the first entry is more close to the goal-nodes
+ *  than his GetParent(). You can walk this list to find the whole path.
+ *  The last entry has a GetParent() of null.
+ */
 class AyStar.Path
 {
 	_prev = null;
-	_tile = null;
+	_node = null;
 	_cost = null;
 
-	constructor(old_path, new_tile, cost_callback)
+	constructor(old_path, new_node, cost_callback)
 	{
 		this._prev = old_path;
-		this._tile = new_tile;
-		this._cost = cost_callback(old_path, new_tile);
+		this._node = new_node;
+		this._cost = cost_callback(old_path, new_node);
 	};
 
 	/**
-	 * Return the tile where this (partial-)path ends.
+	 * Return the node where this (partial-)path ends.
 	 */
-	function GetTile() { return this._tile; }
+	function GetNode() { return this._node; }
 
 	/**
-	 * Return an instance of this class leading to the previous tile.
+	 * Return an instance of this class leading to the previous node.
 	 */
 	function GetParent() { return this._prev; }
 
 	/**
-	 * Return the cost of this (partial-)path from the beginning up to this tile.
+	 * Return the cost of this (partial-)path from the beginning up to this node.
 	 */
 	function GetCost() { return this._cost; }
 }
--- a/bin/ai/regression/regression.nut	Wed Jun 11 12:13:22 2008 +0000
+++ b/bin/ai/regression/regression.nut	Wed Jun 11 13:04:27 2008 +0000
@@ -1,6 +1,6 @@
 import("queue.priority_queue", "PQ", 2);
 import("queue.binary_heap", "BH", 1);
-import("graph.aystar", "AS", 1);
+import("graph.aystar", "AS", 2);
 
 class Regression extends AIController {
 	function Start();
@@ -380,10 +380,14 @@
 {
 	print("--AyStar--");
 	print("  Fastest path:");
-	local as = AS();
-	local path = as.FindPath([1], [10], cost_callback, estimate_callback, neighbours_callback);
+	local as = AS(cost_callback, estimate_callback, neighbours_callback);
+
+	local path = false;
+	as.InitializePath([1], [10]);
+	while (path == false) path = as.FindPath(5);
+
 	while (path != null) {
-		print("    Tile " + path.GetTile());
+		print("    Tile " + path.GetNode());
 		path = path.GetParent();
 	}
 }