bin/ai/library/graph/aystar/main.nut
author truebrain
Thu, 12 Jun 2008 19:47:02 +0000
branchnoai
changeset 10942 cd3f2d07199f
parent 10918 317f736e5fc6
child 11039 a45899beee2a
permissions -rw-r--r--
(svn r13496) [NoAI] -Fix: if a library depends on an other library, the import became globally known, which defeats the idea of imports. They are now restricted to their scope, and 'import' returns the class of import (if any)
/* $Id$ */

/**
 * An AyStar implementation.
 *  It solves graphs by finding the fastest route from one point to the other.
 */
class AyStar
{
	_queue_class = import("queue.binary_heap", "", 1);
	_cost_callback = null;
	_estimate_callback = null;
	_neighbours_callback = null;
	_cost_callback_param = null;
	_estimate_callback_param = null;
	_neighbours_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
	 *  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.
	 * @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.
	 * @param cost_callback_param This parameters will be passed to cost_callback
	 *  as third 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
	 *  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.
	 */
	constructor(cost_callback, estimate_callback, neighbours_callback, cost_callback_param = null,
	            estimate_callback_param = null, neighbours_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.");

		this._cost_callback = cost_callback;
		this._estimate_callback = estimate_callback;
		this._neighbours_callback = neighbours_callback;
		this._cost_callback_param = cost_callback_param;
		this._estimate_callback_param = estimate_callback_param;
		this._neighbours_callback_param = neighbours_callback_param;
	}

	/**
	 * 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::InitializePath(sources, 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 = this._queue_class();
	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));
	}

	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 = this._open.Pop();
		local cur_node = path.GetNode();
		/* Make sure we didn't already passed it */
		if (this._closed.HasItem(cur_node)) continue;
		/* Check if we found the end */
		foreach (node in this._goals) {
			if (cur_node == node) {
				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);
		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, 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));
		}
	}

	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;
	_node = null;
	_cost = null;

	constructor(old_path, new_node, cost_callback, cost_callback_param)
	{
		this._prev = old_path;
		this._node = new_node;
		this._cost = cost_callback(old_path, new_node, cost_callback_param);
	};

	/**
	 * Return the node where this (partial-)path ends.
	 */
	function GetNode() { return this._node; }

	/**
	 * 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 node.
	 */
	function GetCost() { return this._cost; }
}