Skip to content
Max Aller edited this page Oct 2, 2010 · 9 revisions

Advanced Usage

Some of these may seem obvious, but may give you some ideas, nevertheless.

Accessing the position of a node after its been added to a graph

Short version: use the x and y property of the node object.

Long version: for performance reasons, the getX() and getY() methods on the node object get deleted in favor of the x and y properties on the node. How this works is when you add the node to a graph, the node gets properly initialized, causing getX() and getY() to be called one last time and assigned into the x and y properties. So remember, in all your callbacks that expect a node -- use x, because getX() is long gone!

Changing the distance between nodes

Are some nodes harder to traverse than others? You can represent this in the graph so units will avoid slow spots unless it's actually faster. The example dunesAndDemise does this, but here's the core of it:

	function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; };
	MyNode.prototype = new crow.GridNode();
	// Here we're setting a default resistance
	MyNode.prototype.resistance = 1.0;
	MyNode.prototype.distanceTo = function(other){
	  // This is calling the default distanceTo method
		var baseDistance = crow.GridNode.prototype.distanceTo.apply(this, arguments);
		
		// Now we just multiply the base distance by the average of this node's resistance
		// and the other node's resistance.
		var realDistance = baseDistance * (this.resistance + other.resistance) / 2;
		return realDistance;
	};
	
	// Hey, it's a subclass!  And it's identical to MyNode except in name.
	function Road(){ MyNode.apply(this, arguments); }
	Road.prototype = new MyNode();
	
	// Another subclass, but this has a higher resistance, since it's slower to walk on.
	function Sand(){ MyNode.apply(this, arguments); }
	Sand.prototype = new MyNode();
	Sand.prototype.resistance = 2.0; // Sand has double the "resistance" of regular path

With the above resistances, you get the following results:

  • Walking from Road -> Road is cost 1
  • Walking from Road -> Sand or Sand -> Road is cost 1.5
  • Walking from Sand -> Sand is cost 2

Adding unwalkable terrain to nodes

It's not usually convenient to add/remove nodes from the graph (generally not recommended anyway), but there's another way to make a node unwalkable besides removing it from the graph. And that is by writing your distanceTo method such that unwalkable terrain is Infinity distance away. Here's a quick example:

	function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; }
	MyNode.prototype = new crow.GridNode();
	MyNode.prototype.distanceTo = function(other){
		if(other instanceof Wall){
			return Infinity;
		} else {
			return crow.GridNode.prototype.distanceTo.apply(this, arguments);
		}
	};
	
	// Road is walkable
	function Road(){ MyNode.apply(this, arguments); }
	Road.prototype = new MyNode();
	
	// Wall is unwalkable
	function Wall(){ MyNode.apply(this, arguments); }
	Wall.prototype = new MyNode();

Cross-map integration

Sometimes, you want more information than is convenient to code in a single graph. In the following example, there's one graph that represents what's visible on screen (land, water) and the second graph represents what parts are walkable (some water is shallow enough to wade across). Now, in practice the water that's shallow enough to walk across should probably have a "shallow_water" type in the first graph or something, but ignore that for now.

	function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; }
	MyNode.prototype = new crow.GridNode();
	MyNode.prototype.distanceTo = function(other){
		// This section will make more sense when you see the rest of the example
		var walkableNode = walkableGraph.getNode(other.x, other.y);
		if(!other.walkable){
			return Infinity;
		} else {
			return crow.GridNode.prototype.distanceTo.apply(this, arguments);
		}
	};
	
	function Land(){ MyNode.apply(this, arguments); }
	Land.prototype = new MyNode();
	
	function Water(){ MyNode.apply(this, arguments); }
	Water.prototype = new MyNode();
	
	// This represents a tile that is either walkable or unwalkable
	function WalkableTile(arr, isWalkable){
		MyNode.apply(this, arguments);
		this.walkable = isWalkable;
	};
	
	// L = Land, W = Water
	var terrainGraph = crow.Graph.fromArray([
		"LWL",
		"LWL",
		"LWL"
	], function(x, y, value){
		if(value == "L") return new Land([x, y]);
		if(value == "W") return new Water([x, y]);
	});
	
	// - is walkable
	// X is not walkable
	var walkableGraph = crow.Graph.fromArray([
		"-X-",
		"---",
		"-X-"
	], function(x, y, value){
		return new WalkableTile([x, y], value == "-");
	});

There are some optimizations that I ommitted for the sake of the example. For instance, in this case, instead of actually creating a second walkable graph, you could replace the var walkableGraph declaration with:

	crow.Graph.fromArray([
		"-X-",
		"---",
		"-X-"
	], function(x, y, value){
		terrainGraph.getNode(x, y).walkable = value == "-";
	});

and update the distanceTo method appropriately.

Available paths vary by actor

What if the available path varies by the unit/vehicle/actor that's running the course? Aerial, motorized, infantry, and naval units all have different paths they can walk on. You could create a separate graph for each type of unit, but there's a (much) better way. You can pass in an actor to findGoal, which will then get passed as the second argument to the distanceTo method on your node, allowing you to vary distance on an actor-by-actor basis. So:

	// Same node class, and...
	MyNode.prototype.distanceTo = function(other, unit){
		if((other instanceof WaterTile && unit instanceof BoatUnit) ||
		   (other instanceof LandTile && unit instanceof TruckUnit)){
			return crow.GridNode.prototype.distanceTo.apply(this, arguments);
		}
	  // Trucks can't go in water and boats can't go on land
	  return Infinity;
	};
	
	// Then with a graph, you can call like this:
	var truck = new TruckUnit();
	var path = graph.findGoal({start:..., goal:..., actor: truck});

Clone this wiki locally