A Simple Turn-Based Game Loop in JavaScript

The JavaScript code below implements a turn-based version of a simple game from a previous post. The goal is to navigate through the “maze”, go through the door, grab the elixir, and return it to the gravestone back at the start, all without running into any of the randomly-moving enemy agents.

To see the code in action, copy it into an .html file and open it in a web browser that runs JavaScript.  Or, for an online version (albeit a somewhat broken one) visit https://thiscouldbebetter.wordpress.com/simpleturnbasedgame.html.

UPDATE 2015/03/06 – I have cleaned up this code somewhat and added a screenshot and a link to an online version, though the online version is displaying incorrectly right now, due to problems with the way this program renders things as DOM elements with the style attribute “position:absolute”.  Someday I hope to find time to fix this, as I’ve had to do for other programs from this era.

SimpleTurnBasedGame

<html>
<body>
<script type="text/javascript">

// application

function Simulation()
{}
{
	Simulation.prototype.main = function()
	{
		Camera.initializeStatic();

		var universe0 = Universe.demo();

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

// classes

function Action(name)
{
	this.name = name;
}

function Action_Move(directionToMove)
{
	this.name = "Move" + directionToMove.toString();
	this.directionToMove = directionToMove;
}
{
	Action_Move.prototype.perform = function(actor)
	{
		var loc = actor.loc;
		var venue = loc.venue;
		var posInCellsNew = actor.loc.posInCells.clone().add
		(
			this.directionToMove
		);

		var cellAtPos = venue.map.cellAtPos(posInCellsNew);
		var costToTraverse = cellAtPos.terrain.costToTraverse;
		if (costToTraverse <= actor.movesThisTurn)
		{
			actor.movesThisTurn -= costToTraverse;

			actor.loc.posInCells.overwriteWith
			(
				posInCellsNew
			);
		}
	}
}

function Activity(actor, defn, target)
{
	this.actor = actor;
	this.defn = defn;
	this.target = target;
}
{
	// instance methods

	Activity.prototype.initialize = function()
	{
		this.vars = new Activity_Variables();
		this.defn.initialize(this);
	}

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

	// inner classes
	function Activity_Variables()
	{}
}

function ActivityDefn()
{}
{
	ActivityDefn.Instances = new ActivityDefn_Instances();

	function ActivityDefn_Instances()
	{
		this.DoNothing = new function()
		{
			this.initialize = function(activity) 
			{
				// do nothing
			}

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

		this.MoveRandomly = new function()
		{
			this.initialize = function(activity) 
			{
				this.actionsMoveESWN = 
				[
					new Action_Move(new Coords(1, 0)),
					new Action_Move(new Coords(0, 1)),
					new Action_Move(new Coords(-1, 0)),
					new Action_Move(new Coords(0, -1)),
				];
			}

			this.perform = function(activity)
			{
				var actionsFromActor = activity.actor.actions; 

				var numberOfDirectionsAvailable = 
					this.actionsMoveESWN.length;

				var directionIndexRandom = Math.floor
				(
					numberOfDirectionsAvailable
					* Math.random()
				);

				var actionMoveInRandomDirection = 
					this.actionsMoveESWN[directionIndexRandom];

				actionsFromActor.push(actionMoveInRandomDirection);
			}
		}

		this.UserInputAccept = new function()
		{
			this.initialize = function(activity) 
			{}

			this.perform = function(activity)
			{
				var inputHelper = Globals.Instance.inputHelper;
				var actionsFromInput = inputHelper.actionsBeingPerformed;
				var actionsFromActor = activity.actor.actions; 

				for (var a = 0; a < actionsFromInput.length; a++)
				{
					actionsFromActor.push(actionsFromInput[a]);
				}	
			}
		}

	}
}

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

		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;

	for (var i = 0; i < this.animationDefns.length; i++)
	{
		var animationDefn = this.animationDefns[i];
		this.animationDefns[animationDefn.name] = animationDefn;
	}
}
{
	AnimationDefnSet.buildFromImage = function(image)
	{
		var imageAsImageSets = 
		[
			[
				image
			]
		];

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

		return returnValue;
	}

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

		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 = [];

		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;
}
{
	AnimationRun.prototype.advance = function()
	{
		this.frameIndexCurrent++;

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

	AnimationRun.prototype.animationDefnCurrent = function()
	{
		return this.animationDefnSet.animationDefns[this.animationDefnNameCurrent];
	}

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

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

	// html

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

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

		this.htmlElement = returnValue;

		return this.htmlElement;
	}

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

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

function ArrayExtensions()
{}
{
	Array.prototype.addLookups = function(keyName)
	{
		for (var i = 0; i < this.length; i++)
		{
			var item = this[i];
			var key = item[keyName];
			this[key] = item;
		}
	}

	Array.prototype.contains = function(item)
	{
		return (this.indexOf(item) >= 0);
	}

	Array.prototype.insert = function(indexToInsertAt, itemToInsert)
	{
		this.splice(indexToInsertAt, 0, itemToInsert);
	}

	Array.prototype.remove = function(itemToRemove)
	{
		var indexToRemoveAt = this.indexOf(itemToRemove);
		if (indexToRemoveAt >= 0)
		{
			this.removeAt(indexToRemoveAt);
		}
	}

	Array.prototype.removeAt = function(indexToRemoveAt)
	{
		this.splice(indexToRemoveAt, 1);
	}
}

function Body(name, defn, pos, propertyNameValuePairs)
{
	this.name = name;
	this.defn = defn;
	this.loc = new Location(null, pos);

	if (propertyNameValuePairs != null)
	{
		for (var i = 0; i < propertyNameValuePairs.length; i++)
		{
			var propertyNameValuePair = propertyNameValuePairs[i];
			var propertyName = propertyNameValuePair[0];
			var propertyValue = propertyNameValuePair[1];

			this[propertyName] = propertyValue;
		}
	}

	this.movesThisTurn = 0;
	this.integrity = this.defn.integrityMax;
	this.items = [];
}
{
	// constants

	Body.BodyText = "Body";

	// acitivities	

	Body.prototype.activity_Get = function() 
	{ 
		return this._activity; 
	}

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

	// html

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

		return this.htmlElement;
	}

	Body.prototype.htmlElementUpdate = function()
	{	
		var venue = this.loc.venue;
		var mapCellSizeInPixels = venue.map.cellSizeInPixels;
		var mapCellSizeInPixelsHalf = mapCellSizeInPixels.clone().divideScalar(2);
		var camera = venue.camera;
		var cameraPosInPixels = camera.body.loc.posInCells; // hack

		var posInCells = this.loc.posInCells;
		var bodySizeInPixelsHalf = this.defn.sizeInPixelsHalf;

		var drawPos = posInCells.clone().multiply
		(
			mapCellSizeInPixels
		).add
		(
			mapCellSizeInPixelsHalf
		).subtract
		(
			bodySizeInPixelsHalf
		).subtract
		(
			cameraPosInPixels
		);

		this.htmlElement.style.left = drawPos.x + "px";
		this.htmlElement.style.top = drawPos.y + "px";

		this.animationRun.htmlElementUpdate();
	}

	Body.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;
	}
}

function BodyDefn
(
	name, 
	categories, 
	integrityMax, 
	ticksToLive,
	massBase, 
	movesPerTurn,
	sizeInPixels, 
	activityDefnInitial,
	animationDefnSet
)
{
	this.name = name;
	this.categories = categories;
	this.ticksToLive = ticksToLive;
	this.integrityMax = integrityMax;
	this.massBase = massBase;
	this.movesPerTurn = movesPerTurn;
	this.sizeInPixels = sizeInPixels;
	this.activityDefnInitial = activityDefnInitial;
	this.animationDefnSet = animationDefnSet;

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);
}

function Camera(name, viewSizeInPixels)
{
	this.name = name;
	this.viewSizeInPixels = viewSizeInPixels;

	this.viewSizeInPixelsHalf = this.viewSizeInPixels.clone().divideScalar(2);

	this.body = new Body
	(
		this.name + "_CameraBody",
		Camera.BodyDefn,
		new Coords(0, 0)
	);
}
{
	// static methods

	Camera.initializeStatic = function()
	{
		Camera.ViewSizeStandard = new Coords(480, 480);

		Camera.BodyDefn = new BodyDefn
		(
			"CameraBody",
			[Category.Instances.Camera],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(0, 0), // sizeInPixels
			ActivityDefn.Instances.DoNothing,
			null // animationDefnSet
		);

	}
}

function Category
(
	name, methodForCollision, initializeBodyForVenue, updateBodyForVenue
)
{
	this.name = name;
	this.methodForCollision = methodForCollision;
	this.initializeBodyForVenue = initializeBodyForVenue;
	this.updateBodyForVenue = updateBodyForVenue;
}
{
	// instances

	Category.Instances = new Category_Instances();

	function Category_Instances()
	{
		this.Actor = new Category
		(
			"Actor", 
			null, 
			// initialize
			function(body, venue)
			{
				body.actions = [];

				body.activity_Set
				(
					new Activity
					(
						body,
						body.defn.activityDefnInitial,
						null
					)
				);
			},
			// update
			function(body, venue)
			{
				body.activity_Get().perform();

				var bodyActions = body.actions;

				for (var a = 0; a < bodyActions.length; a++)
				{
					bodyActions[a].perform(body);
				}

				bodyActions.length = 0;
			}
		);

		this.Camera = new Category("Camera", null, null, null);
		this.Collidable = new Category("Collidable", null, null, null);

		this.Drawable = new Category
		(
			"Drawable", 
			null, 
			// initialize
			function(body, venue)
			{
				body.animationRun = new AnimationRun
				(
					body.defn.animationDefnSet
				);

				venue.htmlElement.appendChild(body.htmlElementBuild());
			},
			// update
			function(body, venue)
			{
				body.animationRun.advance();

				body.htmlElementUpdate();
			}
		);

		this.Emplacement = new Category("Emplacement", null, null, null);		

		this.Enemy = new Category("Enemy", null, null, null);

		this.Ephemeral 	= new Category
		(
			"Ephemeral", 
			null, 
			// initialize
			function(body, venue) 
			{ 
				body.ticksToLive = body.defn.ticksToLive; 
			}, 
			// update
			function(body, venue) 
			{ 
				body.ticksToLive--; 
				if (body.ticksToLive <= 0)
				{
					venue.bodiesToRemove.push(body);
				}
			}
		);

		this.Item = new Category("Item", null, null, null);

		this.Killable = new Category
		(
			"Killable", 
			null, 
			// initialize
			function(body, venue) 
			{ 
				body.integrity = body.defn.integrityMax; 
			},
			function(body, venue) 
			{ 
				if (body.integrity <= 0) 
				{ 
					venue.bodiesToRemove.push(body); 
				} 
			} 
		);

		this.Mover = new Category
		(
			"Mover", 
			null, // collide
			null, // initialize
			// update
			function(body, venue)
			{
				var mover = body;

				var moverMass = mover.massCurrent();
				var moverLoc = mover.loc;

				moverLoc.posInCells.trimToRange
				(
					venue.map.sizeInCellsMinusOnes
				);
			}
		);

		this.Player = new Category
		(
			"Player",
			// collide
			function(bodyThis, bodyOther)
			{
				var bodyOtherCategories = bodyOther.defn.categories;
				if (bodyOtherCategories.contains(Category.Instances.Enemy))
				{
					bodyThis.integrity = 0;

					var venue = bodyThis.loc.venue;

					venue.bodiesToSpawn.push
					(
						new Body
						(
							"messageLose",
							venue.defn.bodyDefns["MessageLose"],
							venue.camera.viewSizeInPixelsHalf.clone().divide(venue.map.cellSizeInPixels)
						)
					);
				}
				else if (bodyOtherCategories.contains(Category.Instances.Item))
				{
					bodyThis.items.push(bodyOther);
					bodyOther.loc.venue.bodiesToRemove.push(bodyOther);
				}
				else if (bodyOtherCategories.contains(Category.Instances.Portal))
				{
					var portal = bodyOther;
					var venue = bodyThis.loc.venue;

					venue.bodiesToRemove.push(bodyThis);

					var portalData = portal.portalData;
					var destinationVenueName = portalData.destinationVenueName;
					var universe = Globals.Instance.universe;
					var destinationVenue = universe.venues[destinationVenueName];

					destinationVenue.bodiesToSpawn.push(bodyThis);
					bodyThis.loc.posInCells.overwriteWith(portalData.destinationPos);
					Globals.Instance.venueNext = destinationVenue;
				}
				else if (bodyOtherCategories.contains(Category.Instances.Emplacement))
				{
					var emplacement = bodyOther;
					if (emplacement.defn.name == "Gravestone")
					{
						var items = bodyThis.items;
						for (var i = 0; i < items.length; i++)
						{
							var item = items[i];
							if (item.defn.name == "Elixir")
							{
								var venue = bodyThis.loc.venue;

								venue.bodiesToRemove.push(bodyThis);

								var bodyMessageWin = new Body
								(
									"messageWin",
									venue.defn.bodyDefns["MessageWin"],
									venue.camera.viewSizeInPixelsHalf.clone().divide(venue.map.cellSizeInPixels)
								);

								venue.bodiesToSpawn.push
								(
									bodyMessageWin
								);

							}
						}
					}
				}
			},
			null, // initialize
			// update
			function(body, venue) 
			{
				if (body.movesThisTurn <= 0)
				{
					var moversToRecharge = venue.bodiesByCategory[Category.Instances.Mover.name];
					for (var i = 0; i < moversToRecharge.length; i++)
					{
						var mover = moversToRecharge[i];
						mover.movesThisTurn = mover.defn.movesPerTurn;
					}					
				}
			}
		);

		this.Portal = new Category("Portals", null, null, null);

		this.Projectile = new Category
		(
			"Projectile",
			function(bodyThis, bodyOther)
			{
				var bodyOtherCategories = bodyOther.defn.categories;
				if (bodyOtherCategories.contains(Category.Instances.Enemy))
				{
					bodyOther.integrity = 0;
				}
			},
			null,
			null // update
		);

	}
}

function Collision(bodies)
{
	this.bodies = bodies;
}

function CollisionHelper()
{}
{
	CollisionHelper.prototype.collideBodies = function
	(
		collision, body0, body1
	)
	{
		var body0DefnCategories = body0.defn.categories;

		for (var c = 0; c < body0DefnCategories.length; c++)
		{
			var methodForCollision = body0DefnCategories[c].methodForCollision;
			if (methodForCollision != null)
			{
				methodForCollision(body0, body1);
			}
		}
	}

	CollisionHelper.prototype.doBodiesCollide = function(body0, body1)
	{
		var returnValue = body0.loc.posInCells.equals(body1.loc.posInCells);

		return returnValue;
	}

	CollisionHelper.prototype.findCollisionsBetweenBodiesInSets = function
	(
		bodySet0, bodySet1
	)
	{
		var returnValues = [];

		var numberOfBodiesInSet0 = bodySet0 == null ? 0 : bodySet0.length;
		var numberOfBodiesInSet1 = bodySet1 == null ? 0 : bodySet1.length;

		for (var i = 0; i < numberOfBodiesInSet0; i++)
		{
			var bodyFromSet0 = bodySet0[i];

			for (var j = 0; j < numberOfBodiesInSet1; j++)
			{
				var bodyFromSet1 = bodySet1[j];

				if (this.doBodiesCollide(bodyFromSet0, bodyFromSet1) == true)
				{
					var collision = new Collision
					(
						[bodyFromSet0, bodyFromSet1]
					);
					returnValues.push(collision);
				}				
			}
		}

		return returnValues;
	}
}

function Color(name, symbol, systemColor)
{
	this.name = name;
	this.symbol = symbol;
	this.systemColor = systemColor;
}
{
	// static methods

	Color.getBySymbol = function(symbolToGet)
	{
		var returnValue = Color.Instances._All[symbolToGet];
		return returnValue;
	}

	// instances

	Color.Instances = new Color_Instances();

	function Color_Instances()
	{
		// Not sure why rgba is necessary, but it is.
		this.Transparent = new Color("Transparent", ".", "rgba(0, 0, 0, 0)");

		this.Black 	= new Color("Black", 	"k", "#000000");
		this.Blue 	= new Color("Blue", 	"b", "#0000ff");
		this.BlueDark 	= new Color("BlueDark", "B", "#000080");
		this.Brown	= new Color("Brown", 	"n", "#804000");
		this.Cyan	= new Color("Cyan", 	"c", "#00ffff");
		this.Gray 	= new Color("Gray", 	"a", "#808080");
		this.GrayDark 	= new Color("GrayDark", "A", "#404040");
		this.Green 	= new Color("Green", 	"g", "#00ff00");
		this.GreenDark 	= new Color("GreenDark","G", "#008000");
		this.Orange 	= new Color("Orange", 	"o", "#ff8000");
		this.Purple 	= new Color("Purple", 	"p", "#ff00ff");
		this.Red 	= new Color("Red", 	"r", "#ff0000");
		this.White 	= new Color("White", 	"w", "#ffffff");
		this.Yellow 	= new Color("Yellow", 	"y", "#ffff00");

		this._All = 
		[
			this.Transparent,

			this.Black,
			this.Blue,
			this.BlueDark,
			this.Brown,
			this.Cyan,
			this.Gray,
			this.GrayDark,
			this.Green,
			this.GreenDark,
			this.Orange,
			this.Purple,
			this.Red,
			this.White,
			this.Yellow	
		];

		this._All.addLookups("symbol");
	}	
}

function Constraint(defn, bodyConstrained)
{
	this.defn = defn;
	this.bodyConstrained = bodyConstrained;
}

function ConstraintDefn(name, methodToRun)
{
	this.name = name;
	this.methodToRun = methodToRun;
}
{
	ConstraintDefn.Instances = new ConstraintDefn_Instances();

	function ConstraintDefn_Instances()
	{
		this.follow = new ConstraintDefn
		(
			"Follow",
			function (constraint)
			{
				var bodyConstrained = constraint.bodyConstrained;
				var bodyConstrainedLoc = bodyConstrained.loc;
				// hack
				var venue = bodyConstrainedLoc.venue;
				var bodyToFollow = venue.bodiesByCategory
				[
					Category.Instances.Player.name
				][0];

				bodyConstrainedLoc.posInCells.overwriteWith
				(
					bodyToFollow.loc.posInCells
				);
			}
		)
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	// constants

	Coords.NumberOfDimensions = 2;

	// instances

	Coords.Instances = new Coords_Instances();

	function Coords_Instances()
	{
		this.Ones = new Coords(1, 1);
		this.Twos = new Coords(2, 2);
		this.Zeroes = new Coords(0, 0);
	}

	// instance methods

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

		return this;
	}

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

		return this;
	}

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

		return this;
	}

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

		return returnValue;
	}

	Coords.prototype.dimension = function(dimensionIndex)
	{
		var returnValue;

		if (dimensionIndex == 0)
		{
			returnValue = this.x;
		}
		else
		{
			returnValue = this.y;
		}

		return returnValue;
	}

	Coords.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;
	}

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

		return this;
	}

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

		return this;
	}

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

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

		return this;
	}

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

		return returnValue;
	}

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

		return this;
	}

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

		return this;
	}

	Coords.prototype.normalize = function()
	{
		var returnValue;

		var magnitude = this.magnitude();
		if (magnitude == 0)
		{
			returnValue = this;
		}
		{
			returnValue = this.divideScalar(magnitude);
		}

		return returnValue;
	}

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

		return this;
	}

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

		return this;
	}

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

		return this;
	}

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

		return this;
	}

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

	Coords.prototype.toString = function()
	{
		return "(" + this.x + "," + this.y + ")";
	}

	Coords.prototype.trimToRange = function(rangeToTrimTo)
	{
		if (this.x < 0)
		{
			this.x = 0;
		}
		else if (this.x > rangeToTrimTo.x)
		{
			this.x = rangeToTrimTo.x;
		}

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

		return this;
	}
}

function Globals()
{
	// do nothing
}
{
	Globals.prototype.initialize = function
	(
		realWorldMillisecondsPerTick, 
		viewSizeInPixels, 
		universe
	)	
	{
		this.collisionHelper = new CollisionHelper();

		this.inputHelper = new InputHelper();
		this.inputHelper.initialize();

		this.realWorldMillisecondsPerTick = realWorldMillisecondsPerTick;
		this.viewSizeInPixels = viewSizeInPixels;
		this.viewSizeInPixelsHalf = 
			this.viewSizeInPixels.clone().divideScalar(2);

		this.universe = universe;

		this.venueNext = this.universe.venues[0];

		setInterval
		(
			this.processTick.bind(this),
			this.realWorldMillisecondsPerTick
		);
	}

	Globals.prototype.processTick = function()
	{
		if (this.venueNext != null)
		{
			this.venueNext.initialize();
			this.inputHelper.bindingsRegister
			(
				this.venueNext.defn.inputBindings
			);

			if (this.venueCurrent == null)
			{
				document.body.appendChild
				(
					this.venueNext.htmlElement
				);
			}
			else
			{
				document.body.replaceChild
				(
					this.venueNext.htmlElement,
					this.venueCurrent.htmlElement
				);
			}

			this.venueCurrent = this.venueNext;

			this.venueNext = null;
		}

		this.venueCurrent.update();
	}
}
{
	Globals.Instance = new Globals();
}

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()
{}
{
	// static methods

	Image.buildFromFilePath = function(filePath)
	{
		var returnValue = new Image();

		returnValue.filePath = filePath;

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

		return returnValue;
	}

	Image.buildFromSystemImage = function(systemImage)
	{
		var returnValue = new Image();

		returnValue.htmlElement = systemImage;
		returnValue.filePath = systemImage.src;

		return returnValue;
	}

	// instance methods

	Image.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()
{}
{
	// static methods

	ImageHelper.buildImageFromStrings = function(stringsForPixels)
	{
		return ImageHelper.buildImageFromStringsScaled
		(
			Coords.Instances.Ones, stringsForPixels
		);
	}

	ImageHelper.buildImageFromStringsScaled = function
	(
		scaleFactor, stringsForPixels
	)
	{
		var sizeInPixels = new Coords
		(
			stringsForPixels[0].length, 
			stringsForPixels.length
		);

		var canvas = document.createElement("canvas");
		canvas.width = sizeInPixels.x * scaleFactor.x;
		canvas.height = sizeInPixels.y * scaleFactor.y;

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

		var pixelPos = new Coords(0, 0);
		var colorForPixel = Color.Instances.Transparent;

		for (var y = 0; y < sizeInPixels.y; y++)
		{
			var stringForPixelRow = stringsForPixels[y];
			pixelPos.y = y * scaleFactor.y;

			for (var x = 0; x < sizeInPixels.x; x++)
			{
				var charForPixel = stringForPixelRow[x];
				pixelPos.x = x * scaleFactor.x;

				colorForPixel = Color.getBySymbol(charForPixel);

				graphics.fillStyle = colorForPixel.systemColor;
				graphics.fillRect
				(
					pixelPos.x, 
					pixelPos.y, 
					scaleFactor.x, 
					scaleFactor.y
				);				
			}
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = Image.buildFromSystemImage
		(
			htmlImageFromCanvas
		);

		return returnValue;
	}

	ImageHelper.buildImageFromStringsTiled = function
	(
		sizeInTiles, stringsForPixels
	)
	{
		var tileSizeInPixels = new Coords
		(
			stringsForPixels[0].length, 
			stringsForPixels.length
		);

		var canvas = document.createElement("canvas");
		canvas.width = tileSizeInPixels.x * sizeInTiles.x;
		canvas.height = tileSizeInPixels.y * sizeInTiles.y;

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

		var pixelPos = new Coords(0, 0);
		var colorForPixel = Color.Instances.Transparent;

		for (var j = 0; j < sizeInTiles.y; j++)
		for (var i = 0; i < sizeInTiles.x; i++)
		{
			for (var y = 0; y < tileSizeInPixels.y; y++)
			{
				var stringForPixelRow = stringsForPixels[y];
				pixelPos.y = j * tileSizeInPixels.y + y;

				for (var x = 0; x < tileSizeInPixels.x; x++)
				{
					var charForPixel = stringForPixelRow[x];
					pixelPos.x = 
						i * tileSizeInPixels.x + x;

					colorForPixel = Color.getBySymbol
					(
						charForPixel
					);

					graphics.fillStyle = 
						colorForPixel.systemColor;

					graphics.fillRect
					(
						pixelPos.x, 
						pixelPos.y, 
						1, 
						1
					);
				}
			}
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = Image.buildFromSystemImage(htmlImageFromCanvas);

		return returnValue;
	}

	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
		);

		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 = Image.buildFromSystemImage
		(
			htmlImageFromCanvas
		);

		return returnValue;
	}

	ImageHelper.sliceImageIntoTiles = function(imageToSlice, sizeInTiles)
	{
		var returnImages = [];

		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 = [];

			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,
					// source pos
					sourcePos.x, sourcePos.y, 
					// source size
					tileSize.x, tileSize.y, 
					// destination pos
					0, 0, 
					// destination size
					tileSize.x, tileSize.y 
				);

				// 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 = 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 = [];

	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] = Image.buildFromSystemImage
		(
			image
		);
	}
}
{
	ImageLoader.prototype.imageLoaded = function(event)
	{
		var imageLoader = event.target.imageLoader;
		imageLoader.numberOfImagesLoadedSoFar++;

		var numberOfImagesToLoad = 
			imageLoader.imageDatasToLoad.length;

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

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

function InputBinding(key, action)
{
	this.key = key;
	this.action = action;	
}

function InputHelper()
{
	this.mousePos = new Coords(0, 0);

	this.bindings = [];
	this.keyCodeToBindingLookup = [];

	this.actionsBeingPerformed = [];
	this.actionsBeingPerformedLookup = [];
}
{
	// instance methods

	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.processKeyDownEvent.bind(this);
		document.body.onkeyup = this.processKeyUpEvent.bind(this);
		document.body.onmousedown = this.processMouseDownEvent.bind(this);
	}

	InputHelper.prototype.bindingsRegister = function(bindingsToRegister)
	{
		this.bindings = [];
		this.keyCodeToBindingLookup = [];

		var numberOfBindings = bindingsToRegister.length;
		for (var b = 0; b < numberOfBindings; b++)
		{
			var binding = bindingsToRegister[b];

			this.bindings.push(binding);
			this.keyCodeToBindingLookup[binding.key.systemKeyCode] = binding;
		}
	}

	// events

	InputHelper.prototype.processKeyDownEvent = function(event)
	{
		var binding = this.keyCodeToBindingLookup["_" + event.which];
		if (binding != null)
		{
			var action = binding.action;

			var actionsBeingPerformedLookup = 
				this.actionsBeingPerformedLookup;

			if (actionsBeingPerformedLookup[action.name] == null)
			{
				var actionsBeingPerformed = this.actionsBeingPerformed;

				actionsBeingPerformedLookup[action.name] = action;
				actionsBeingPerformed.push(action);
			}
		}
	}

	InputHelper.prototype.processKeyUpEvent = function(event)
	{
		var binding = this.keyCodeToBindingLookup["_" + event.which];

		if (binding != null)
		{
			var action = binding.action;

			var actionsBeingPerformedLookup = 
				this.actionsBeingPerformedLookup;

			if (actionsBeingPerformedLookup[action.name] != null)
			{
				delete actionsBeingPerformedLookup[action.name];
				var actionsBeingPerformed = this.actionsBeingPerformed;
				actionsBeingPerformed.removeAt(action);
			}
		}
	}

	InputHelper.prototype.processMouseDownEvent = function(event)
	{
		this.mousePos.overwriteWithDimensions(event.x, event.y);
	}

}

function InputKey(name, systemKeyCode)
{
	this.name = name;
	this.systemKeyCode = systemKeyCode;
}
{
	InputKey.Instances = new InputKey_Instances();

	function InputKey_Instances()
	{
		this.A = new InputKey("A", "_65");
		this.D = new InputKey("D", "_68");
		this.F = new InputKey("F", "_70");
		this.S = new InputKey("S", "_83");
		this.W = new InputKey("W", "_87");

		this._All = 
		[
			this.A, 
			this.D, 
			this.F,
			this.S, 
			this.W,
		];

		this._All.addLookups("systemKeyCode");
	}
}

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

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

	this.sizeInCellsMinusOnes = this.sizeInCells.clone().subtract
	(
		new Coords(1, 1)
	);
}
{
	Map.prototype.cellAtPos = function(cellPos)
	{
		var codeChar = this.cellsAsStrings[cellPos.y][cellPos.x];

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

	Map.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.width = this.cellSizeInPixels.x;
				tdForCell.style.height = this.cellSizeInPixels.y; 

				var imgForCell = document.createElement("img");
				var terrain = MapTerrain.Instances._All[cellAsChar];
				imgForCell.src = terrain.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 MapTerrain_Instances();

	function MapTerrain_Instances()
	{
		var imageTerrainBlocked = ImageHelper.buildImageFromStringsTiled
		(
			new Coords(30, 30),
			[
				"A",
			]
		);

		var imageTerrainOpen = ImageHelper.buildImageFromStringsTiled
		(
			new Coords(30, 30),
			[
				"a",
			]
		);

		this.Blocked = new MapTerrain
		(
			"Blocked", 
			"x", 
			1000000,
			"#0000aa", 
			imageTerrainBlocked
		);
		this.Open = new MapTerrain
		(
			"Open", 
			".", 
			1, 		
			"#00aa00", 
			imageTerrainOpen
		);

		this._All = 
		[
			this.Blocked,
			this.Open,
		];

		this._All.addLookups("codeChar");
	}
}

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

	var openList = [];
	var openLookup = [];
	var closedLookup = [];

	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;

	startIndex = "_" + startIndex;

	openLookup[startIndex] = startNode;

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

		if (current.cellPos.equals(goalPos) == true)
		{	
			this.nodes = [];

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

		openList.removeAt(0);

		var currentIndex = 
			current.cellPos.y 
			* map.sizeInCells.x 
			+ current.cellPos.x;
		currentIndex = "_" + currentIndex;

		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;

			neighborIndex = "_" + neighborIndex;

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

					var costDifference = 
						neighbor.costFromStart 
						- nodeFromOpenList.costFromStart;

					if (costDifference < 0)
					{
						break;
					}
				}

				openList.insert(i, neighbor);
				openLookup[neighborIndex] = neighbor;
			}
		}
	}	
}
{
	Path.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;
	}

	Path.prototype.getNeighborsForNode = function(map, node, goalPos)
	{
		var returnValues = [];
		var cellPos = node.cellPos;

		var neighborPositions = [];

		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 mapCellAtPos = map.cellAtPos(neighborPos);
			var costToTraverse = mapCellAtPos.terrain.costToTraverse;

			var costToGoalEstimated = 
				costToTraverse 
				+ goalPos.clone().subtract
				(
					cellPos
				).absolute().sumOfXAndY();

			var neighborNode = new PathNode
			(
				neighborPos,
				node.costFromStart + costToTraverse,
				costToGoalEstimated,
				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 PortalData(destinationVenueName, destinationPos)
{
	this.destinationVenueName = destinationVenueName;
	this.destinationPos = destinationPos;
}

function Universe(name, venueDefns, venues)
{
	this.name = name;
	this.venueDefns = venueDefns;
	this.venues = venues;

	for (var i = 0; i < this.venues.length; i++)
	{
		var venue = this.venues[i];
		this.venues[venue.name] = venue;
	}
}
{
	Universe.demo = function()
	{
		var imageEmplacementGravestone = ImageHelper.buildImageFromStrings
		(
			[
				"...AAAAAAAA..",
				"..AwwwwwwwwA..",
				".AwwwwwwwwwwA.",
				"AwwwwwwwwwwwwA",
				"AwaaawwawaaawA",
				"AwawawwawawawA",
				"AwaaawwawaaawA",
				"AwawwawawawwwA",
				"AwwwwwwwwwwwwA",
				"AwwwwwwwwwwwwA",
				"AwwwwwwwwwwwwA",
				"AwwwwwwwwwwwwA",
			]
		);

		var imageItemElixir = ImageHelper.buildImageFromStrings
		(
			[
				".....cccc.....",
				"....c....c....",
				".....c..c.....",
				".....c..c.....",
				".....crrc.....",
				"....crrrrc....",
				"...crrrrrrc...",
				"..crrrrrrrrc..",
				".crrrrrrrrrrc.",
				"crrrrrrrrrrrrc",
				"crrrrrrrrrrrrc",
				"cccccccccccccc",
			]
		);

		var imageItemShield = ImageHelper.buildImageFromStrings
		(
			[
				".cccccccccccc.",
				".cwwwwrrwwwwc.",
				".cwwwwrrwwwwc.",
				".crrrrrrrrrrc.",
				".crrrrrrrrrrc.",
				".cwwwwrrwwwwc.",
				".cwwwwrrwwwwc.",
				"..cwwwrrwwwc..",
				"...cwwrrwwc...",
				"....cwrrwc....",
				".....crrc.....",
				"......cc......",
			]
		);

		var imageItemSword = ImageHelper.buildImageFromStrings
		(
			[
				".....yyyy.....",
				".....yyyy.....",
				".....yyyy.....",
				"..yyyyyyyyyy..",
				"..y..cwwc..y..",
				".....cwwc.....",
				".....cwwc.....",
				".....cwwc.....",
				".....cwwc.....",
				".....cwwc.....",
				".....cwwc.....",
				"......cc......",
			]
		);

		var imageMessageLose = ImageHelper.buildImageFromStringsScaled
		(
			Coords.Instances.Twos,
			[
				"r.....rrrrr.rrrrr.rrrrr.r",
				"r.....r...r.r.....r.....r",
				"r.....r...r.rrrrr.rrrrr.r",
				"r.....r...r.....r.r......",
				"rrrrr.rrrrr.rrrrr.rrrrr.r",
			]			
		);

		var imageMessageWin = ImageHelper.buildImageFromStringsScaled
		(
			Coords.Instances.Twos,
			[
				"g...........g.ggggg.gg....g.g",
				".g.........g....g...g.g...g.g",
				"..g...g...g.....g...g..g..g.g",
				"...g.g.g.g......g...g...g.g..",
				"....g...g.....ggggg.g....gg.g",
			]			
		);

		var imageMoverPlayer0 = ImageHelper.buildImageFromStrings
		(
			[
				".......bb..bb.......",
				"......baabbaab......",
				"......baaaaaab......",
				"....bbbbbbbbbbbb....",
				"......baaaaaab......",
				"......baaaaaab......",
				"......baaaaaab......",
				"......baaaaaab......",
				".......bbbbbb.......",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".bbbbbbbbbbbbbbbbbb.",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				"........b..b........",
				".......b....b.......",
				"......b......b......",
				".....b........b.....",
				"....b..........b....",
				"...b............b...",
				"..b..............b..",
				".b................b.",
				"b..................b",
			]
		);

		var imageMoverPlayer1 = ImageHelper.buildImageFromStrings
		(
			[
				".......bb..bb.......",
				"......baabbaab......",
				"......baaaaaab......",
				"....bbbbbbbbbbbb....",
				"......baaaaaab......",
				"......baaaaaab......",
				"......baaaaaab......",
				"......baaaaaab......",
				".......bbbbbb.......",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".bbbbbbbbbbbbbbbbbb.",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
				".........bb.........",
			]
		);

		var imageMoverEnemy = ImageHelper.buildImageFromStrings
		(
			[
				".......rr..rr.......",
				"......raarraar......",
				"......raaaaaar......",
				"....rrrrrrrrrrrr....",
				"......raaaaaar......",
				"......raaaaaar......",
				"......raaaaaar......",
				"......raaaaaar......",
				".......rrrrrr.......",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".rrrrrrrrrrrrrrrrrr.",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				".........rr.........",
				"........r..r........",
				".......r....r.......",
				"......r......r......",
				".....r........r.....",
				"....r..........r....",
				"...r............r...",
				"..r..............r..",
				".r................r.",
				"r..................r",
			]
		);

		var imageMoverProjectile = ImageHelper.buildImageFromStrings
		(
			[
				"yyy",
				"y.y",
				"yyy",
			]			
		);

		var imagePortal = ImageHelper.buildImageFromStrings
		(
			[
				"nnnnnnnnnnnnnnnnnnnn",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"nooooooooooooooyyoon",
				"nooooooooooooooyyoon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"noooooooooooooooooon",
				"nnnnnnnnnnnnnnnnnnnn",
			]
		);

		var bodyDefnEmplacementGravestone = new BodyDefn
		(
			"Gravestone",
			[
				Category.Instances.Collidable,
				Category.Instances.Drawable, 
				Category.Instances.Emplacement,
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(16, 16),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageEmplacementGravestone)
		);

		var bodyDefnItemElixir = new BodyDefn
		(
			"Elixir",
			[
				Category.Instances.Collidable,
				Category.Instances.Drawable, 
				Category.Instances.Item,
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(16, 16),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageItemElixir)
		);

		var bodyDefnItemShield = new BodyDefn
		(
			"Shield",
			[
				Category.Instances.Collidable,
				Category.Instances.Drawable, 
				Category.Instances.Item,
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(16, 16),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageItemShield)
		);

		var bodyDefnItemSword = new BodyDefn
		(
			"Sword",
			[
				Category.Instances.Collidable,
				Category.Instances.Drawable, 
				Category.Instances.Item,
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(16, 16),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageItemSword)
		);

		var bodyDefnMessageLose = new BodyDefn
		(
			"MessageLose",
			[Category.Instances.Drawable],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn
			new Coords(25, 5),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageMessageLose)
		);

		var bodyDefnMessageWin = new BodyDefn
		(
			"MessageWin",
			[Category.Instances.Drawable],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn,
			new Coords(29, 5),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageMessageWin)			
		);

		var bodyDefnMoverProjectile = new BodyDefn
		(
			"Projectile",
			[
				Category.Instances.Actor, 
				Category.Instances.Collidable,
				Category.Instances.Drawable,
				Category.Instances.Ephemeral,
				Category.Instances.Mover, 
				Category.Instances.Projectile,
			],
			1, // integrityMax
			16, // ticksToLive
			1, // mass,
			1000, // movesPerTurn,
			new Coords(3, 3),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imageMoverProjectile)
		);

		var animationDefnSetPlayer = new AnimationDefnSet
		(
			"AnimationDefnSetPlayer",
			[
				new AnimationDefn
				(
					"AnimationDefnPlayer",
					"AnimationDefnPlayer", // next
					[
						new AnimationFrame
						(
							imageMoverPlayer0
						),
						new AnimationFrame
						(
							imageMoverPlayer1
						),
					]
				)
			]
		);

		var bodyDefnMoverPlayer = new BodyDefn
		(
			"Player", 	
			[
				Category.Instances.Actor,
				Category.Instances.Collidable, 
				Category.Instances.Drawable,
				Category.Instances.Killable,
				Category.Instances.Mover, 
				Category.Instances.Player
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass
			1, // movesPerTurn
			new Coords(20, 25),
			ActivityDefn.Instances.UserInputAccept,
			animationDefnSetPlayer
		);

		var bodyDefnMoverEnemy = new BodyDefn
		(
			"Enemy", 	
			[
				Category.Instances.Actor,
				Category.Instances.Collidable,
				Category.Instances.Drawable,
				Category.Instances.Enemy,
				Category.Instances.Killable, 
				Category.Instances.Mover, 
			],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass
			1, // movesPerTurn
			new Coords(20, 25),
			ActivityDefn.Instances.MoveRandomly,
			AnimationDefnSet.buildFromImage(imageMoverEnemy)
		);

		var bodyDefnPortal = new BodyDefn
		(
			"Portal",
			[Category.Instances.Drawable, Category.Instances.Portal],
			1, // integrityMax
			-1, // ticksToLive
			1, // mass,
			0, // movesPerTurn,
			new Coords(20, 25),
			ActivityDefn.Instances.DoNothing,
			AnimationDefnSet.buildFromImage(imagePortal)
		);

		var venueDefns = 
		[
			new VenueDefn
			(
				"VenueDefn0",
				[
					Camera.BodyDefn,
					bodyDefnMessageLose,
					bodyDefnMessageWin,
					bodyDefnMoverProjectile,
					bodyDefnMoverEnemy,
					bodyDefnMoverPlayer,
					bodyDefnPortal,
				],
				[
					new InputBinding
					(
						InputKey.Instances.A, 
						new Action_Move(new Coords(-1, 0))
					),
					new InputBinding
					(
						InputKey.Instances.D, 
						new Action_Move(new Coords(1, 0))
					),
					new InputBinding
					(
						InputKey.Instances.S, 
						new Action_Move(new Coords(0, 1))
					),
					new InputBinding
					(
						InputKey.Instances.W, 
						new Action_Move(new Coords(0, -1))
					),
				]
			),
		];

		var venue0 = new Venue
		(
			"Venue0",
			venueDefns[0],
			new Coords(480, 480), // sizeInPixels
			new Map
			(
				"Venue0Map",
				new Coords(30, 30), // cellSizeInPixels
				[
					"xxxxxxxxxxxxxxxx",
					"x..............x",
					"x..............x",
					"xxxxxxxxx......x",
					"x..............x",
					"x..............x",
					"x..............x",
					"x..............x",
					"x...xxxxxxxxxxxx",
					"x..............x",
					"x..............x",
					"xxxxxxxxx......x",
					"x..............x",
					"x..............x",
					"x..............x",
					"xxxxxxxxxxxxxxxx",
				]
			),
			// bodies
			[
				// portals

				new Body
				(
					"Portal 0", 
					bodyDefnPortal, 
					new Coords(14, 14), 
					// propertyNameValuePairs
					[
						[
							"portalData",
							new PortalData
							(
								"Venue1",
								new Coords(2, 1)
							)
						]
					]
				),

				// player

				new Body
				(
					"Player", 
					bodyDefnMoverPlayer, 
					new Coords(1, 1)
				),

				// emplacements

				new Body
				(
					"Gravestone", 
					bodyDefnEmplacementGravestone, 
					new Coords(1, 2)
				),

				// other movers

				new Body("Enemy 0", bodyDefnMoverEnemy, new Coords(12, 1)),
				new Body("Enemy 1", bodyDefnMoverEnemy, new Coords(3, 12)),

				// items
				new Body("Item 2", bodyDefnItemSword, new Coords(3, 7)),
				new Body("Item 3", bodyDefnItemShield, new Coords(4, 10)),
			]
		);

		var venue1 = new Venue
		(
			"Venue1",
			venueDefns[0],
			new Coords(480, 480), // sizeInPixels
			new Map
			(
				"Venue0Map",
				new Coords(30, 30), // cellSizeInPixels
				[
					"xxxxxxxxxxxxxxxx",
					"x..............x",
					"x..............x",
					"x..............x",
					"x....xxxxx.....x",
					"x....x...x.....x",
					"x....x...x.....x",
					"x....x...x.....x",
					"x....xx.xx.....x",
					"x.....x.x......x",
					"x..............x",
					"x..............x",
					"x..............x",
					"x..............x",
					"x..............x",
					"xxxxxxxxxxxxxxxx",
				]
			),
			// bodies
			[
				// portals

				new Body
				(
					"Portal 0", 
					bodyDefnPortal, 
					new Coords(1, 1), 
					// propertyNameValuePairs
					[
						[
							"portalData",
							new PortalData
							(
								"Venue0",
								new Coords(13, 14)
							)
						]
					]
				),

				// enemies

				new Body("Enemy 0", bodyDefnMoverEnemy, new Coords(12, 1)),
				new Body("Enemy 1", bodyDefnMoverEnemy, new Coords(12, 12)),

				// items

				new Body("Elixir", bodyDefnItemElixir, new Coords(7, 6)),
			]
		);

		var returnValue = new Universe
		(
			"UniverseDemo",
			venueDefns, 
			[
				venue0,
				venue1,
			]
		);

		return returnValue;
	}
}

function Venue(name, defn, sizeInPixels, map, bodies)
{
	this.name = name;
	this.defn = defn;
	this.sizeInPixels = sizeInPixels;
	this.map = map;
	this.bodies = [];

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);

	this.bodiesByCategory = [];
	this.bodiesToSpawn = [];
	this.bodiesToRemove = [];

	for (var i = 0; i < this.defn.bodyDefns.length; i++)
	{
		var categories = this.defn.bodyDefns[i].categories;

		for (var c = 0; c < categories.length; c++)
		{
			var category = categories[c];

			if (this.bodiesByCategory[category.name] == null)
			{
				this.bodiesByCategory[category.name] = [];
			}
		}
	}

	for (var i = 0; i < bodies.length; i++)
	{
		var body = bodies[i];
		body.loc.venue = this;
		this.bodiesToSpawn.push(body);
	}

	this.camera = new Camera
	(
		"Camera",
		Camera.ViewSizeStandard
	);

	this.camera.body.loc.venue = this;

	this.bodiesToSpawn.push(this.camera.body);
}
{
	// instance methods

	Venue.prototype.bodySpawn = function(bodyToSpawn)
	{
		bodyToSpawn.loc.venue = this;

		this.bodies.push(bodyToSpawn);		

		var bodyCategories = bodyToSpawn.defn.categories;
		for (var c = 0; c < bodyCategories.length; c++)
		{
			var bodyCategory = bodyCategories[c];
			var bodyCategoryName = bodyCategory.name;
			var bodyListForCategory = this.bodiesByCategory[bodyCategoryName];

			if (bodyListForCategory == null)
			{
				bodyListForCategory = [];
				this.bodiesByCategory[bodyCategoryName] = bodyListForCategory;
			}

			bodyListForCategory.push(bodyToSpawn);

			if (bodyCategory.initializeBodyForVenue != null)
			{
				bodyCategory.initializeBodyForVenue
				(
					bodyToSpawn, this
				);
			}
		}
	}

	Venue.prototype.initialize = function()
	{
		this.htmlElementBuild();

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

			var bodyCategories = body.defn.categories;
			for (var c = 0; c < bodyCategories.length; c++)
			{
				var bodyCategory = bodyCategories[c];

				if (bodyCategory.initializeBodyForVenue != null)
				{
					bodyCategory.initializeBodyForVenue(body, this);
				}
			}
		}

	}

	Venue.prototype.update = function()
	{
		this.update_BodiesToSpawn();

		var categories = 
		[
			Category.Instances.Player,
			Category.Instances.Actor,
			Category.Instances.Mover,
			Category.Instances.Drawable,
			Category.Instances.Killable,
			Category.Instances.Ephemeral,
		];

		var numberOfCategories = categories.length;

		for (var c = 0; c < numberOfCategories; c++)
		{
			var category = categories[c];

			if (category.updateBodyForVenue != null)
			{

				var bodiesInCategory = this.bodiesByCategory[category.name];

				var numberOfBodiesInCategory = bodiesInCategory.length;

				for (var b = 0; b < numberOfBodiesInCategory; b++)
				{
					var body = bodiesInCategory[b];
					category.updateBodyForVenue(body, this);
				}
			}
		}

		this.update_Collidables();

		this.update_BodiesToRemove();
	}

	Venue.prototype.update_BodiesToRemove = function()
	{
		for (var i = 0; i < this.bodiesToRemove.length; i++)
		{
			var bodyToRemove = this.bodiesToRemove[i];

			this.bodies.remove(bodyToRemove);

			var bodyCategories = bodyToRemove.defn.categories;
			for (var c = 0; c < bodyCategories.length; c++)
			{
				var bodyCategory = bodyCategories[c];
				var bodyCategoryName = bodyCategory.name;
				var bodiesForCategory = this.bodiesByCategory[bodyCategoryName];
				bodiesForCategory.remove
				(
					bodyToRemove
				);
			}

			this.htmlElement.removeChild(bodyToRemove.htmlElement);
		}

		this.bodiesToRemove.length = 0;
	}

	Venue.prototype.update_BodiesToSpawn = function()
	{
		for (var i = 0; i < this.bodiesToSpawn.length; i++)
		{
			var bodyToSpawn = this.bodiesToSpawn[i];
			this.bodySpawn(bodyToSpawn);

		}

		this.bodiesToSpawn.length = 0;
	}

	Venue.prototype.update_Collidables = function()
	{
		var categories = Category.Instances;
		var emplacements = this.bodiesByCategory[categories.Emplacement.name];
		var enemies = this.bodiesByCategory[categories.Enemy.name];
		var items = this.bodiesByCategory[categories.Item.name];
		var players = this.bodiesByCategory[categories.Player.name]
		var portals = this.bodiesByCategory[categories.Portal.name];
		var projectiles = this.bodiesByCategory[categories.Projectile.name]

		var collisionHelper = Globals.Instance.collisionHelper;

		var collisionSets = 
		[
			collisionHelper.findCollisionsBetweenBodiesInSets
			(
				players, 
				emplacements
			),

			collisionHelper.findCollisionsBetweenBodiesInSets
			(
				players, 
				enemies
			),

			collisionHelper.findCollisionsBetweenBodiesInSets
			(
				players, 
				items
			),

			collisionHelper.findCollisionsBetweenBodiesInSets
			(
				players, 
				portals
			),

			collisionHelper.findCollisionsBetweenBodiesInSets
			(
				enemies,
				projectiles
			),
		];

		for (var s = 0; s < collisionSets.length; s++)
		{
			var collisions = collisionSets[s];

			var numberOfCollisions = collisions.length;
			for (var c = 0; c < numberOfCollisions; c++)
			{
				var collision = collisions[c];

				var numberOfBodies = collision.bodies.length;

				for (var b0 = 0; b0 < numberOfBodies; b0++)
				{
					var bodyThis = collision.bodies[b0];
					for (var b1 = b0 + 1; b1 < numberOfBodies; b1++)
					{
						var bodyOther = collision.bodies[b1];

						collisionHelper.collideBodies
						(
							collision, 
							bodyThis, 
							bodyOther
						);
						collisionHelper.collideBodies
						(
							collision, 
							bodyOther, 
							bodyThis
						);
					}
				}
			}
		}
	}

	// html

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

		returnValue.style.backgroundColor = "#000000";
		returnValue.style.width = this.sizeInPixels.x + "px";
		returnValue.style.height = this.sizeInPixels.y + "px";

		var mapAsHTMLElement = this.map.htmlElementBuild();

		returnValue.appendChild(mapAsHTMLElement);

		this.htmlElement = returnValue;

		return returnValue;
	}
}

function VenueDefn(name, bodyDefns, inputBindings)
{
	this.name = name;
	this.bodyDefns = bodyDefns;
	this.inputBindings = inputBindings;

	for (var i = 0; i < this.bodyDefns.length; i++)
	{
		var bodyDefn = this.bodyDefns[i];
		this.bodyDefns[bodyDefn.name] = bodyDefn;
	}
}

// run

new Simulation().main();

</script>
</body>
</html>

Advertisements
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