Simulating an Ant Farm in JavaScript

This code isn’t quite done yet, but it does run, and I figured I’d better put it out there on the Internet before it got lost forever the next time something horrible happens to my hard disk.

To use the code, you’ll need to copy the code into an .html file, download the images below (the big one is a modified version of the “vanilla” tileset from NetHack), put them in the same directory as the .html file, and open the .html file in a web browser that runs JavaScript.

RedGreenBlueYellow

NetHack Tiles - Vanilla - Transparent

<html>
<body>
<script type='text/javascript'>
function Terrarium()
{}
{
	var prototype = Terrarium.prototype;
	prototype.main = function()
	{
		this.imageLoader = new ImageLoader
		(
			new Array
			(
				new ImageLoadingData("NetHack", "NetHack-VanillaTiles-Transparent.png"),
				new ImageLoadingData("RedGreenBlueYellow", "redgreenblueyellow.png")
			),
			this,
			this.main2
		);
	}

	prototype.main2 = function()
	{
		MapTerrain.Instances.Initialize(this.imageLoader);

		var sizeStandard = new Coords(32, 32);

		var map0 = new Map
		(
			"Map 0",
			sizeStandard,
			new Array
			(
				"..................~.............",
				"..................~.............",
				"...~~~............~.............",
				".....~...........~~.....~~~~~~~.",
				".....~...........~......~.....~.",
				".....~~~~~.......~......~.....~.",
				".........~~~.....~~.....~.....~.",
				"...........~......~.....~.....~.",
				".........~~~......~.....~~~..~~.",
				".....~~~~~......................",
				".....~..........................",
				".....~..........................",
				".....~..........................",
				".....~..........................",
				".....~..........................",
				".....~.........................."
			)
		);

		var imageLookup = this.imageLoader.imageLookup;

		var imageNetHackTiles = imageLookup["NetHack"];

		var tileSize = new Coords(16, 16);

		var imageAntFire 	= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(3, 0).multiply(tileSize), tileSize);
		var imageAntWorker 	= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(0, 0).multiply(tileSize), tileSize);
		var imageAntSoldier 	= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(2, 0).multiply(tileSize), tileSize);
		var imageApple 		= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(4, 16).multiply(tileSize), tileSize);
		var imageHole 		= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(2, 22).multiply(tileSize), tileSize);
		var imageTree 		= ImageHelper.copyRegionFromImage(imageNetHackTiles, new Coords(7, 21).multiply(tileSize), tileSize);

		var bodyDefnAntFire = new BodyDefn
		(
			"Ant Fire", 	
			1, 
			4, 
			Color.Instances.Red, 	
			sizeStandard, 
			new Array(),  // desiresSatisfied
			AnimationDefnSet.buildFromImageForHeadings(imageAntFire, 4)
		);

		var bodyDefnAntSoldier 	= new BodyDefn
		(
			"Ant Soldier", 	
			1, 
			4, 
			Color.Instances.Orange, 	
			sizeStandard, 
			new Array(), // desiresSatisfied
			AnimationDefnSet.buildFromImageForHeadings(imageAntSoldier, 4)
		);

		var bodyDefnAntWorker = new BodyDefn
		(
			"Ant Worker", 
			1, 
			4, 
			Color.Instances.Yellow, 
			sizeStandard, 
			new Array(), // desiresSatisfied
			AnimationDefnSet.buildFromImageForHeadings(imageAntWorker, 4)
		);

		var bodyDefnFood = new BodyDefn
		(
			"Food", 		
			1, 
			0, 
			Color.Instances.Gray, 	
			sizeStandard, 
			new Array(Desire.Instances.FoodCollect),
			AnimationDefnSet.buildFromImage(imageApple)
		);

		var bodyDefnHole = new BodyDefn
		(
			"Hole", 		
			0, 
			0, 
			Color.Instances.Black, 	
			sizeStandard, 
			new Array(Desire.Instances.FoodStore),
			AnimationDefnSet.buildFromImage(imageHole)
		);

		var bodyDefnTree = new BodyDefn
		(
			"Tree", 
			1, 
			0, 
			Color.Instances.Green, 	
			sizeStandard, 
			new Array(), // desiresSatisified
			AnimationDefnSet.buildFromImage(imageTree)
		);

		var world0 = new World
		(
			"World0",
			map0,
			new Array
			(
				new Body("Hole0", bodyDefnHole, new Coords(0, 0)),
				new Body("Hole1", bodyDefnHole, new Coords(13, 2)),
				new Body("Hole2", bodyDefnHole, new Coords(2, 14)),
				new Body("Hole3", bodyDefnHole, new Coords(28, 6)),
				new Body("Hole4", bodyDefnHole, new Coords(19, 12)),

				new Body("Tree0", bodyDefnTree, new Coords(7, 4)),
				new Body("Tree1", bodyDefnTree, new Coords(7, 8)),
				new Body("Tree2", bodyDefnTree, new Coords(11, 12)),
				new Body("Tree3", bodyDefnTree, new Coords(16, 10)),
				new Body("Tree4", bodyDefnTree, new Coords(23, 12)),
				new Body("Tree3", bodyDefnTree, new Coords(17, 12)),
				new Body("Tree4", bodyDefnTree, new Coords(21, 10))
			),
			new Array
			(
				new Body("Food0", bodyDefnFood, new Coords(8, 4)),
				new Body("Food1", bodyDefnFood, new Coords(8, 8)),
				new Body("Food2", bodyDefnFood, new Coords(12, 12)),
				new Body("Food3", bodyDefnFood, new Coords(17, 10)),
				new Body("Food4", bodyDefnFood, new Coords(24, 12)),
				new Body("Food3", bodyDefnFood, new Coords(18, 12)),
				new Body("Food4", bodyDefnFood, new Coords(22, 10))
			),
			new Array
			(
				new Body("Mover0", bodyDefnAntWorker, 	new Coords(21, 11)),
				new Body("Mover1", bodyDefnAntWorker, 	new Coords(2, 2)),
				new Body("Mover2", bodyDefnAntWorker, 	new Coords(3, 13)),
				new Body("Mover3", bodyDefnAntWorker, 	new Coords(12, 3)),
				new Body("Mover4", bodyDefnAntSoldier, 	new Coords(25, 1)),
				new Body("Mover5", bodyDefnAntFire, 	new Coords(16, 2))

			)
		);

		Globals.Instance = new Globals();

		Globals.Instance.initialize
		(
			100, //realWorldMillisecondsPerTick,
			new Coords(640, 480), //viewSizeInPixels, 
			world0
		);
	}
}

// classes

function Activity(actor, defn, target)
{
	this.actor = actor;
	this.defn = defn;
	this.target = target;
}
{
	var prototype = Activity.prototype;

	prototype.initialize = function()
	{
		this.defn.initialize(this);
	}

	prototype.perform = function()
	{
		this.defn.perform(this);
	}
}

function Activity_Variables()
{}

function ActivityDefn_Instances()
{}
{
	ActivityDefn_Instances.DoNothing = new function()
	{
		this.initialize = function() {}

		this.perform = function(activity)
		{
			// do nothing
		}
	}

	ActivityDefn_Instances.GatherFood = function()
	{}
	{
		var prototype = ActivityDefn_Instances.GatherFood.prototype;

		prototype.initialize = function(activity)
		{
			var loc = activity.actor.loc;
			var map = loc.venue.map;
			var posInCells = loc.pos.clone().divide(map.cellSizeInPixels).floor();

			activity.vars = new Activity_Variables();		
			activity.actor.desireCurrent = Desire.Instances.FoodCollect;
		}

		prototype.perform = function(activity)
		{
			var actor = activity.actor;
			var loc = actor.loc;
			var venue = loc.venue;
			var map = venue.map;
			var cellSizeInPixels = map.cellSizeInPixels;

			if (activity.vars.path == null)
			{						
				var bodiesToChooseBetween = new Array();

				for (var i = 0; i < venue.bodies.length; i++)
				{
					var body = venue.bodies[i];

					if (body.desireSatisfied(actor.desireCurrent) == true)
					{
						bodiesToChooseBetween.push(body);
					}
				}

				var numberOfBodiesToChooseBetween = bodiesToChooseBetween.length;

				if (numberOfBodiesToChooseBetween > 0)
				{
					var bodyIndexRandom = Math.floor(Math.random() * numberOfBodiesToChooseBetween);
					var bodyToMoveTo = bodiesToChooseBetween[bodyIndexRandom];
					var targetPosInCells = bodyToMoveTo.loc.pos.clone().divide(cellSizeInPixels).floor();
					var posInCells = loc.pos.clone().divide(cellSizeInPixels);

					activity.vars.path = new Path(map, posInCells, targetPosInCells);
					activity.vars.nodeIndex = 0;	
				}
			}

			var path = activity.vars.path;

			if (path != null)
			{
				var targetPosInCells = path.nodes[activity.vars.nodeIndex].cellPos;

				var targetPosInPixels = targetPosInCells.clone().multiply(cellSizeInPixels);
				var displacementToTarget = targetPosInPixels.clone().subtract(loc.pos);
				var distanceToTarget = displacementToTarget.magnitude();

				var distancePerTick = actor.speedCurrent();
				if (distanceToTarget <= distancePerTick)
				{
					loc.pos.overwriteWith(targetPosInPixels);
					activity.vars.nodeIndex++;
					if (activity.vars.nodeIndex >= path.nodes.length)
					{
						activity.vars.path = null;
					}
				}
				else
				{
					var directionToTarget = displacementToTarget.clone().directions();

					var heading = Heading.fromCoords(directionToTarget);
					actor.animationRun.animationDefnNameCurrent_Set("" + heading);

					var displacementToMove = directionToTarget.clone().multiplyScalar(distancePerTick);
					loc.pos.add(displacementToMove);	
				}
			}
		}
	}
}

function AnimationDefn(name, animationDefnNameNext, frames)
{
	this.name = name;
	this.animationDefnNameNext = animationDefnNameNext;
	this.frames = frames;
}
{
	AnimationDefn.buildManyFromImageSets = function(imageSetsForFrames)
	{
		var returnValues = new Array();

		var numberOfImageSets = imageSetsForFrames.length;

		for (var i = 0; i < numberOfImageSets; i++)
		{
			var imageSet = imageSetsForFrames[i];

			var animationDefn = new AnimationDefn
			(
				"" + i,
				"" + i,
				AnimationFrame.buildManyFromImages(imageSet)
			);

			returnValues.push(animationDefn);
		}

		return returnValues;
	}
}

function AnimationDefnSet(name, animationDefns)
{
	this.name = name;
	this.animationDefns = animationDefns;

	this.animationDefnLookup = new Lookup(this.animationDefns);
}
{
	AnimationDefnSet.buildFromImage = function(image)
	{
		var imageAsImageSet = new Array();
		imageAsImageSet.push(image);
		var imageAsImageSets = new Array();
		imageAsImageSets.push(imageAsImageSet);

		var returnValue = new AnimationDefnSet
		(
			image.id, 
			AnimationDefn.buildManyFromImageSets
			(
				imageAsImageSets
			)
		);

		return returnValue;
	}

	AnimationDefnSet.buildFromImageForHeadings = function(image, numberOfHeadings)
	{
		var imageAsImageSet = new Array();
		imageAsImageSet.push(image);
		var imageAsImageSets = new Array();

		for (var i = 0; i < numberOfHeadings; i++)
		{
			imageAsImageSets.push(imageAsImageSet);
		}

		var returnValue = new AnimationDefnSet
		(
			image.id, 
			AnimationDefn.buildManyFromImageSets
			(
				imageAsImageSets
			)
		);

		return returnValue;
	}

}

function AnimationFrame(image, ticksToHold)
{
	this.image = image;
	this.ticksToHold = ticksToHold;
}
{
	AnimationFrame.buildManyFromImages = function(imagesForFrames)
	{
		var returnValues = new Array();

		var numberOfImages = imagesForFrames.length;

		for (var i = 0; i < numberOfImages; i++)
		{
			var imageForFrame = imagesForFrames[i];
			var frame = new AnimationFrame(imageForFrame, 1);

			returnValues.push(frame);
		}

		return returnValues;
	}
}

function AnimationRun(animationDefnSet)
{
	this.animationDefnSet = animationDefnSet;

	this.animationDefnNameCurrent = this.animationDefnSet.animationDefns[0].name;
	this.frameIndexCurrent = 0;
}
{
	var prototype = AnimationRun.prototype;

	prototype.advance = function()
	{
		this.frameIndexCurrent++;

		var animationDefnCurrent = this.animationDefnCurrent();
		if (this.frameIndexCurrent >= animationDefnCurrent.frames.length)
		{
			this.animationDefnNameCurrent_Set(animationDefnCurrent.animationDefnNameNext);
			this.frameIndexCurrent = 0;
		}
	}

	prototype.animationDefnCurrent = function()
	{
		return this.animationDefnSet.animationDefnLookup.get(this.animationDefnNameCurrent);
	}

	prototype.animationDefnNameCurrent_Set = function(value)
	{
		if (this.animationDefnNameCurrent != value)
		{
			this.animationDefnNameCurrent = value;
			this.frameIndexCurrent = 0;
		}
	}

	prototype.frameCurrent = function()
	{
		return this.animationDefnCurrent().frames[this.frameIndexCurrent];
	}

	prototype.htmlElementBuild = function()
	{
		var returnValue = document.createElement("div");
		returnValue.id = "AnimationRun_" + this.name;

		this.htmlElementForImage = document.createElement("img");
		this.htmlElementForImage.src = this.animationDefnSet.animationDefns[0].frames[0].image.htmlElement.src;
		returnValue.appendChild(this.htmlElementForImage);

		this.htmlElement = returnValue;

		return this.htmlElement;
	}

	prototype.htmlElementUpdate = function()
	{
		var htmlElementForImageNext = this.frameCurrent().image.htmlElement;

		if (this.htmlElementForImage != htmlElementForImageNext)
		{
			this.htmlElementForImage.src = htmlElementForImageNext.src;
		}
	}
}

function Body(name, defn, pos)
{
	this.name = name;
	this.defn = defn;
	this.loc = new Location(null, pos);
	this.items = new Array();
	this.animationRun = new AnimationRun(this.defn.animationDefnSet);
}
{
	Body.BodyText = "Body";

	var prototype = Body.prototype;

	prototype.activity_Get = function() { return this._activity; }
	prototype.activity_Set = function(value) 
	{ 
		this._activity = value; 
		this._activity.initialize(); 
	}

	prototype.desireSatisfied = function(desireToCheck)
	{
		return this.defn.desireSatisfied(desireToCheck);
	}

	prototype.htmlElementBuild = function()
	{
		this.htmlElement = this.animationRun.htmlElementBuild();
		this.htmlElement.style.position = "absolute";

		return this.htmlElement;
	}

	prototype.htmlElementUpdate = function()
	{	
		var pos = this.loc.pos;
		var sizeHalf = this.defn.sizeHalf;

		this.htmlElement.style.left = (pos.x + sizeHalf.x) + "px";
		this.htmlElement.style.top = (pos.y + sizeHalf.y) + "px";

		this.animationRun.htmlElementUpdate();
	}

	prototype.massCurrent = function()
	{
		var returnValue = this.defn.massBase;

		for (var i = 0; i < this.items.length; i++)
		{
			returnValue += this.items[i].defn.massBase;
		}

		return returnValue;
	}

	prototype.speedCurrent = function()
	{
		// Technically, it's momentum, not force.
		return this.defn.force / this.massCurrent();
	}
}

function BodyDefn(name, massBase, force, color, size, desiresSatisfied, animationDefnSet)
{
	this.name = name;
	this.massBase = massBase;
	this.force = force;
	this.color = color;
	this.size = size;
	this.desiresSatisfied = desiresSatisfied;
	this.animationDefnSet = animationDefnSet;

	this.sizeHalf = this.size.clone().divideScalar(2);
}
{
	BodyDefn.prototype.desireSatisfied = function(desireToCheck)
	{
		var returnValue = false;

		var numberOfDesiresSatisfied = this.desiresSatisfied.length;

		for (var d = 0; d < numberOfDesiresSatisfied; d++)
		{
			var desireSatisfied = this.desiresSatisfied[d];

			if (desireSatisfied.name == desireToCheck.name)
			{
				returnValue = true;
				break;
			}
		}

		return returnValue;
	}
}

function Color(name, systemColor)
{
	this.name = name;
	this.systemColor = systemColor;
}
{
	// instances

	Color.Instances = new ColorInstances();

	function ColorInstances()
	{
		this.Black 	= new Color("Black", 	"#000000");
		this.Blue 	= new Color("Blue", 	"#0000ff");
		this.Gray 	= new Color("Gray", 	"#808080");
		this.Green 	= new Color("Green", 	"#00ff00");
		this.Orange 	= new Color("Orange", 	"#ff8800");
		this.Purple 	= new Color("Purple", 	"#ff00ff");
		this.Red 	= new Color("Red", 	"#ff0000");
		this.White 	= new Color("White", 	"#ffffff");
		this.Yellow 	= new Color("Yellow", 	"#ffff00");
	}	
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	var prototype = Coords.prototype;

	prototype.absolute = function()
	{
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);

		return this;
	}

	prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;

		return this;
	}

	prototype.clear = function()
	{
		this.x = 0;
		this.y = 0;

		return this;
	}

	prototype.clone = function()
	{
		var returnValue = new Coords(this.x, this.y);

		return returnValue;
	}

	prototype.directions = function()
	{
		if (this.x > 0)
		{
			this.x = 1;
		}
		else if (this.x < 0)
		{
			this.x = -1;
		}

		if (this.y > 0)
		{
			this.y = 1;
		}
		else if (this.y < 0)
		{
			this.y = -1;
		}

		return this;
	}

	prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;

		return this;
	}

	prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;

		return this;
	}

	prototype.equals = function(other)
	{
		return (this.x == other.x && this.y == other.y);
	}

	prototype.floor = function()
	{
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);

		return this;
	}

	prototype.magnitude = function()
	{
		var returnValue = Math.sqrt(this.x * this.x + this.y * this.y);

		return returnValue;
	}

	prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;

		return this;
	}

	prototype.multiplyScalar = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;

		return this;
	}

	prototype.normalize = function()
	{
		return this.divideScalar(this.magnitude());
	}

	prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;

		return this;
	}

	prototype.round = function()
	{
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);

		return this;
	}

	prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;

		return this;
	}

	prototype.sumOfXAndY = function()
	{
		return this.x + this.y;
	}

	prototype.toString = function()
	{
		return "<Coords x='" + this.x + "' y='" + this.y + "' />"
	}
}

function Desire(name)
{
	this.name = name;
}
{
	Desire.Instances = new Desire_Instances();

	function Desire_Instances()
	{
		this.FoodCollect = new Desire("Collect Food");
		this.FoodStore = new Desire("Store Food");
	}
}

function Globals()
{
	this.initialize = function
	(
		realWorldMillisecondsPerTick, 
		viewSizeInPixels, 
		world
	)	
	{
		this.realWorldMillisecondsPerTick = realWorldMillisecondsPerTick;
		this.viewSizeInPixels = viewSizeInPixels;
		this.viewSizeInPixelsHalf = this.viewSizeInPixels.clone().divideScalar(2);

		this.world = world;

		this.world.initialize();

		this.world.htmlElementBuild();

		document.body.appendChild(world.htmlElement);

		setInterval("Globals.Instance.processTick()", this.realWorldMillisecondsPerTick);
	}

	this.processTick = function()
	{
		this.world.update();
	}
}

function Heading()
{}
{
	Heading.fromCoords = function(coordsToConvert)
	{
		// todo
		var returnValue;

		if (coordsToConvert.x == 0)
		{
			if (coordsToConvert.y > 0)
			{
				returnValue = 1;
			}
			else
			{
				returnValue = 3;
			}
		}
		else if (coordsToConvert.x > 0)
		{
			returnValue = 0;
		}
		else if (coordsToConvert.x < 0)
		{
			returnValue = 2;
		}

		return returnValue;
	}
}

function Image()
{}
{
	var prototype = Image.prototype;

	prototype.buildFromFilePath = function(filePath)
	{
		this.filePath = filePath;

		this.htmlElement = document.createElement("img");
		this.htmlElement.src = this.filePath;

		return this;
	}

	prototype.buildFromSystemImage = function(systemImage)
	{
		this.htmlElement = systemImage;
		this.filePath = this.htmlElement.src;

		return this;
	}

	prototype.clone = function()
	{
		var returnValue = new Image();

		returnValue.filePath = this.filePath;
		returnValue.htmlElement = document.createElement("img");
		returnValue.htmlElement.src = this.htmlElement.src;

		return returnValue;
	}
}

function ImageHelper()
{}
{
	ImageHelper.copyRegionFromImage = function(imageToCopyFrom, regionPos, regionSize)
	{
		var canvas		 = document.createElement("canvas");
		canvas.id		 = "region_" + regionPos.x + "_" + regionPos.y;
		canvas.width		 = regionSize.x;
		canvas.height		 = regionSize.y;
		canvas.style.position	 = "absolute";

		var graphics = canvas.getContext("2d");

		graphics.drawImage
		(
			imageToCopyFrom.htmlElement,
			regionPos.x, regionPos.y, // source pos
			regionSize.x, regionSize.y, // source size
			0, 0, // destination pos
			regionSize.x, regionSize.y // destination size
		);

		// browser dependent?
		var imageFromCanvasURL = canvas.toDataURL("image/png");

		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.style.position = "absolute";
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = new Image().buildFromSystemImage(htmlImageFromCanvas);

		return returnValue;
	}

	ImageHelper.sliceImageIntoTiles = function(imageToSlice, sizeInTiles)
	{
		var returnImages = new Array();

		var systemImageToSlice = imageToSlice.htmlElement;		

		var imageToSliceSize = new Coords(systemImageToSlice.width, systemImageToSlice.height);
		var tileSize = imageToSliceSize.clone().divide(sizeInTiles);

		var tilePos = new Coords(0, 0);
		var sourcePos = new Coords(0, 0);

		for (var y = 0; y < sizeInTiles.y; y++)
		{
			tilePos.y = y;

			var returnImageRow = new Array();

			for (var x = 0; x < sizeInTiles.x; x++)
			{							
				tilePos.x = x;

				var canvas		 = document.createElement("canvas");
				canvas.id		 = "tile_" + x + "_" + y;
				canvas.width		 = tileSize.x;
				canvas.height		 = tileSize.y;
				canvas.style.position	 = "absolute";

				var graphics = canvas.getContext("2d");

				sourcePos.overwriteWith(tilePos).multiply(tileSize);

				graphics.drawImage
				(
					systemImageToSlice,
					sourcePos.x, sourcePos.y, // source pos
					tileSize.x, tileSize.y, // source size
					0, 0, // destination pos
					tileSize.x, tileSize.y // destination size
				);

				// browser dependent?
				var imageFromCanvasURL = canvas.toDataURL("image/png");

				var htmlImageFromCanvas = document.createElement("img");
				htmlImageFromCanvas.width = canvas.width;
				htmlImageFromCanvas.height = canvas.height;
				htmlImageFromCanvas.style.position = "absolute";
				htmlImageFromCanvas.src = imageFromCanvasURL;

				imageFromCanvas = new Image().buildFromSystemImage(htmlImageFromCanvas);

				returnImageRow.push(imageFromCanvas);
			}

			returnImages.push(returnImageRow);
		}

		return returnImages;
	}
}

function ImageLoader(imageDatasToLoad, contextForCallback, callbackForLoadingComplete)
{
	this.name = "ImageLoader";
	this.imageDatasToLoad = imageDatasToLoad;
	this.contextForCallback = contextForCallback;	
	this.callbackForLoadingComplete = callbackForLoadingComplete;
	this.numberOfImagesLoadedSoFar = 0;

	this.imageLookup = new Array();

	for (var i = 0; i < this.imageDatasToLoad.length; i++)
	{
		var imageData = this.imageDatasToLoad[i];

		var image = document.createElement("img");
		image.imageLoader = this;
		image.onload = this.imageLoaded;

		image.id = imageData.id;
		image.src = imageData.url;

		this.imageLookup[image.id] = new Image().buildFromSystemImage(image);
	}
}
{
	var prototype = ImageLoader.prototype;

	prototype.imageLoaded = function(event)
	{
		var imageLoader = event.target.imageLoader;
		imageLoader.numberOfImagesLoadedSoFar++;

		if (imageLoader.numberOfImagesLoadedSoFar >= imageLoader.imageDatasToLoad.length)
		{
			imageLoader.callbackForLoadingComplete.call
			(
				imageLoader.contextForCallback
			);
		}
	}
}

function ImageLoadingData(id, url)
{
	this.id = id;
	this.url = url;
}

function Location(venue, pos)
{
	this.venue = venue;
	this.pos = pos;
}

function Lookup(namables)
{
	this.systemLookup = new Array();

	var numberOfNamables = namables.length;

	for (var i = 0; i < numberOfNamables; i++)
	{
		var namable = namables[i];

		this.systemLookup[namable.name] = namable;
	}
}
{
	var prototype = Lookup.prototype;

	prototype.get = function(key)
	{
		return this.systemLookup[key];
	}
}

function Map(name, cellSizeInPixels, cellsAsStrings)
{
	this.cellsAsStrings = cellsAsStrings;	
	this.cellSizeInPixels = cellSizeInPixels;
	this.sizeInCells = new Coords
	(
		this.cellsAsStrings[0].length, 
		this.cellsAsStrings.length
	);
}
{
	var prototype = Map.prototype;

	prototype.cellAtPos = function(cellPos)
	{
		var codeChar = this.cellsAsStrings[cellPos.y][cellPos.x];

		return new MapCell
		(
			MapTerrain.Instances._Lookup[codeChar]
		);
	}

	prototype.htmlElementBuild = function()
	{
		var returnValue = document.createElement("table");
		returnValue.style.backgroundColor = "#888888";
		returnValue.cellPadding = 0;
		returnValue.border = 0;
		returnValue.cellSpacing = 0;

		for (var y = 0; y < this.cellsAsStrings.length; y++)
		{
			var trForRow = document.createElement("tr");

			var cellRowAsString = this.cellsAsStrings[y];

			for (var x = 0; x < cellRowAsString.length; x++)
			{
				var cellAsChar = cellRowAsString[x];

				tdForCell = document.createElement("td");

				//tdForCell.style.backgroundColor = MapTerrainInstances._Lookup[cellAsChar].color;
				//tdForCell.style.background = MapTerrainInstances._Lookup[cellAsChar].image.htmlElement.src;
				tdForCell.style.width = this.cellSizeInPixels.x;
				tdForCell.style.height = this.cellSizeInPixels.y; 

				var imgForCell = document.createElement("img");
				imgForCell.src = MapTerrain.Instances._Lookup[cellAsChar].image.htmlElement.src;
				tdForCell.appendChild(imgForCell);

				trForRow.appendChild(tdForCell);
			}

			returnValue.appendChild(trForRow);
		}

		this.htmlElement = returnValue;

		return returnValue;
	}
}

function MapCell(terrain)
{
	this.terrain = terrain;
}

function MapTerrain(name, codeChar, costToTraverse, color, image)
{
	this.name = name;
	this.codeChar = codeChar;
	this.costToTraverse = costToTraverse;
	this.color = color;
	this.image = image;
}
{
	// instances

	MapTerrain.Instances = new MapTerrainInstances();

	function MapTerrainInstances()
	{}
	{
		// static methods

		MapTerrainInstances.prototype.Initialize = function(imageLoader)
		{
			var imageLookup = imageLoader.imageLookup;

			var imageForTerrains = imageLookup["RedGreenBlueYellow"];
			this.imageForTerrainsAsTiles = ImageHelper.sliceImageIntoTiles
			(
				imageForTerrains, new Coords(4, 1)
			);

			this.Plain = new MapTerrain("plain", ".", 1, 		"#00aa00", this.imageForTerrainsAsTiles[0][1]);
			this.Water = new MapTerrain("water", "~", 1000000, 	"#0000aa", this.imageForTerrainsAsTiles[0][2]);

			this._All = new Array
			(
				this.Plain,
				this.Water
			);

			this._Lookup = this.BuildLookup();
		}

		MapTerrainInstances.prototype.BuildLookup = function()
		{
			var returnValue = new Array();

			var numberOfTerrains = MapTerrain.Instances._All.length;

			for (var i = 0; i < numberOfTerrains; i++)
			{
				var terrain = MapTerrain.Instances._All[i];

				returnValue[terrain.codeChar] = terrain;
			}

			return returnValue;
		}

	}
}

function Path(map, startPos, goalPos)
{
	this.map = map;
	this.startPos = startPos;
	this.goalPos = goalPos;

	var openList = new Array();
	var openLookup = new Array();
	var closedLookup = new Array();

	var startNode = new PathNode
	(
		startPos,
		0,
		goalPos.clone().subtract(startPos).absolute().sumOfXAndY(),
		null
	);

	openList.push(startNode);
	var startIndex = "_" + (startNode.cellPos.y * map.sizeInCells.x + startNode.cellPos.x);
	openLookup[startIndex] = startNode;

	while (openList.length > 0)
	{
		var current = openList[0];

		if (current.cellPos.equals(goalPos) == true)
		{	
			this.nodes = new Array();

			while (current != null)
			{
				this.nodes.splice(0, 0, current);
				current = current.prev;
			}
			break;
		}

		openList.splice(0, 1);
		var currentIndex = "_" + (current.cellPos.y * map.sizeInCells.x + current.cellPos.x);
		delete openLookup[currentIndex];

		closedLookup[currentIndex] = current;

		var neighbors = this.getNeighborsForNode(map, current, goalPos);

		for (var n = 0; n < neighbors.length; n++)
		{
			var neighbor = neighbors[n];
			var neighborPos = neighbor.cellPos;

			var neighborIndex = "_" + (neighborPos.y * map.sizeInCells.x + neighborPos.x);

			if (closedLookup[neighborIndex] == null && openLookup[neighborIndex] == null)
			{
				var i;
				for (i = 0; i < openList.length; i++)
				{
					var nodeFromOpenList = openList[i];
					if (neighbor.costFromStart < nodeFromOpenList.costFromStart)
					{
						break;
					}
				}

				openList.splice(i, 0, neighbor);
				openLookup[neighborIndex] = neighbor;
			}
		}
	}	
}
{
	var prototype = Path.prototype;

	prototype.convertToHTMLElement = function()
	{
		var returnValue = document.createElement("div");
		returnValue.cellPadding = 0;
		returnValue.border = 0;
		returnValue.cellSpacing = 0;

		var cellSizeInPixels = this.map.cellSizeInPixels;

		for (var i = 0; i < this.nodes.length; i++)
		{
			var node = this.nodes[i];
			var nodeCellPos = node.cellPos;

			var divForNode = document.createElement("div");
			divForNode.style.position = "absolute";
			divForNode.style.width = cellSizeInPixels.x + "px";
			divForNode.style.height = cellSizeInPixels.y + "px";
			divForNode.style.left = (nodeCellPos.x * cellSizeInPixels.x) + "px";
			divForNode.style.top = (nodeCellPos.y * cellSizeInPixels.y) + "px";
			divForNode.style.backgroundColor = "#ff0000";	

			returnValue.appendChild(divForNode);
		}

		return returnValue;
	}

	prototype.getNeighborsForNode = function(map, node, goalPos)
	{
		var returnValues = new Array();
		var cellPos = node.cellPos;

		var neighborPositions = new Array();

		if (cellPos.x > 0)
		{
			neighborPositions.push
			(
				new Coords(cellPos.x - 1, cellPos.y)
			);

		}
		if (cellPos.x < map.sizeInCells.x - 1)
		{
			neighborPositions.push
			(
				new Coords(cellPos.x + 1, cellPos.y)
			);
		}		
		if (cellPos.y > 0)
		{
			neighborPositions.push
			(
				new Coords(cellPos.x, cellPos.y - 1)
			);
		}
		if (cellPos.y < map.sizeInCells.y - 1)
		{
			neighborPositions.push
			(
				new Coords(cellPos.x, cellPos.y + 1)
			);
		}		

		var tempPos = new Coords(0, 0);

		for (var i = 0; i < neighborPositions.length; i++)
		{
			var neighborPos = neighborPositions[i];

			var costToTraverse = map.cellAtPos(neighborPos).terrain.costToTraverse;

			var neighborNode = new PathNode
			(
				neighborPos,
				node.costFromStart + costToTraverse,
				costToTraverse + goalPos.clone().subtract(cellPos).absolute().sumOfXAndY(),
				node
			);

			returnValues.push(neighborNode);
		}

		return returnValues;
	}
}

function PathNode(cellPos, costFromStart, costToGoalEstimated, prev)
{
	this.cellPos = cellPos;
	this.costFromStart = costFromStart;
	this.costToGoalEstimated = costToGoalEstimated;
	this.prev = prev;
}

function World(name, map, stations, items, movers)
{
	this.name = name;
	this.map = map;
	this.stations = stations;
	this.items = items;
	this.movers = movers;

	this.bodies = new Array();

	for (var i = 0; i < this.stations.length; i++)
	{
		this.bodies.push(this.stations[i]);
	}

	for (var i = 0; i < this.items.length; i++)
	{
		this.bodies.push(this.items[i]);
	}

	for (var i = 0; i < this.movers.length; i++)
	{
		this.bodies.push(this.movers[i]);
	}
}
{
	var prototype = World.prototype;

	prototype.htmlElementBuild = function()
	{
		var returnValue = document.createElement("div");

		returnValue.appendChild(this.map.htmlElementBuild());

		var numberOfBodies = this.bodies.length;
		for (var i = 0; i < numberOfBodies; i++)
		{
			var body = this.bodies[i];

			returnValue.appendChild(body.htmlElementBuild());
		}

		this.htmlElement = returnValue;

		return returnValue;
	}

	prototype.initialize = function()
	{
		var numberOfBodies = this.bodies.length;
		for (var i = 0; i < numberOfBodies; i++)
		{
			var body = this.bodies[i];

			body.loc.venue = this;
			body.loc.pos.multiply(this.map.cellSizeInPixels);

			body.activity_Set
			(
				new Activity
				(
					body,
					new ActivityDefn_Instances.GatherFood(),
					null
				)
			);
		}
	}

	prototype.update = function()
	{
		var numberOfMovers = this.movers.length;
		for (var i = 0; i < numberOfMovers; i++)
		{
			var mover = this.movers[i];

			mover.activity_Get().perform();
		}

		var numberOfBodies = this.bodies.length;
		for (var i = 0; i < numberOfBodies; i++)
		{
			var body = this.bodies[i];

			body.animationRun.advance();

			body.htmlElementUpdate();
		}
	}
}

new Terrarium().main();

</script>
</body>
</html>
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s