A Simple Top-Down Space Game in JavaScript

The code given below implements a simple top-down space game in JavaScript. The code is adapted from that given for an even simpler game described in an earlier post.

Use the W key to accelerate and the A and D keys to turn. There’s no way to either win or die at the moment, but you can fly back and forth between Sol and Alpha Centauri via the “stargate” in the corner of each starsystems. You can also fire your gun with the F key, but the bullets won’t hit anything. There are other small bugs, including whatever ones I failed to encounter on my playthroughs so far.

UPDATE 2016/06/05 – I have updated this code to more closely match the way I do things nowadays.

UPDATE 2016/06/06 – More updates.

UPDATE 2016/06/07 – Still more updates. Refactored to remove the “Category” class, replacing it with a variety of more explicitly named classes that can be passed as the “properties” of the EntityDefn to do the same things.

UPDATE 2016/06/08 – More updates. You can now both die and be killed, though both are a little buggy at the moment. I find it helps to aim for the right side of the bad guys.

UPDATE 2016/06/09 – I have improved the collisions a bit and made the universe a continuous loop of 10 systems, each system with a random number of planets and enemies in random locations.

UPDATE 2016/06/14 – In addition to various updates, I have split each of the classes into its own file and uploaded the result to “https://github.com/thiscouldbebetter/TopDownSpaceGame” as an experiment. If I do keep updating this code, I may start doing it on Github instead.

TopDownSpaceGame



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

// application

function Simulation()
{}
{
	Simulation.prototype.main = function()
	{
		var universe = new Demo().universeGrid(new Coords(10, 10));

		Globals.Instance.initialize
		(
			100, //realWorldMillisecondsPerTick,
			new Coords(600, 600), //viewSizeInPixels, 
			universe
		);
	}
}

// extensions

function ArrayExtensions()
{
	// extension class
}
{
	Array.prototype.addLookups = function(keyName)
	{
		for (var i = 0; i < this.length; i++)
		{
			var element = this[i];
			var elementKey = element[keyName];
			this[elementKey] = element;
		}

		return this;
	}

	Array.prototype.cloneShallow = function()
	{
		return this.slice();
	}

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

	Array.prototype.insertElementAt = function(element, index)
	{
		return this.splice(index, 0, element);
	}

	Array.prototype.remove = function(element)
	{
		return this.removeAt(this.indexOf(element));
	}

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

// classes

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

function Action_Accelerate()
{
	this.name = "Accelerate";
}
{
	Action_Accelerate.prototype.perform = function(actor)
	{
		var actorLoc = actor.body.loc;
		var actorOrientation = actorLoc.orientation;

		actorLoc.accel.add
		(
			actorOrientation.forward
		)
	}
}

function Action_Fire()
{
	// do nothing
}
{
	Action_Fire.prototype.perform = function(actor)
	{
		var venue = actor.body.loc.venue;
		var entityDefnProjectile = Globals.Instance.universe.entityDefns["Projectile"];

		var entityToSpawn = new Entity
		(
			"[projectile]", 
			entityDefnProjectile.name,
			[
				new Body(actor.body.loc.clone())
			]
		);

		entityToSpawn.body.loc.vel = actor.body.loc.orientation.forward.clone().normalize().multiplyScalar
		(
			entityDefnProjectile.mover.speedMax
		);

		venue.entitiesToSpawn.push(entityToSpawn);
	}	
}

function Action_Turn(directionToTurn)
{
	this.name = "Turn" + directionToTurn;
	this.directionToTurn = directionToTurn;
}
{
	Action_Turn.prototype.perform = function(actor)
	{
		var actorLoc = actor.body.loc;
		var actorOrientation = actorLoc.orientation;

		var turnRate = .25;

		actorLoc.orientation = new Orientation
		(
			actorOrientation.forward.clone().add
			(
				actorOrientation.right.multiplyScalar
				(
					turnRate
					* this.directionToTurn
				)
			)
		);

		var actorHeading = Heading.fromCoords
		(
			actorLoc.orientation.forward, 
			Heading.NumberOfHeadings
		);

		actor.drawable.visual.animationDefnNameNext = "" + actorHeading;
	}
}

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

	Activity.prototype.defn = function()
	{
		return Globals.Instance.universe.activityDefns[this.defnName];
	}

	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(name, initialize, perform)
{
	this.name = name;
	this.initialize = initialize;
	this.perform = perform;
}

function ActorDefn(activityDefnNameInitial)
{
	this.activityDefnNameInitial = activityDefnNameInitial;
}
{
	ActorDefn.prototype.propertyName = function() { return "Actor"; }

	ActorDefn.prototype.initializeEntityForVenue = function(entity, venue)
	{
		entity.actions = [];

		entity.activity
		(
			new Activity
			(
				entity,
				entity.defn().actor.activityDefnNameInitial,
				null
			)
		);
	}


	ActorDefn.prototype.updateEntityForVenue = function(entity, venue)
	{
		entity.activity().perform();

		var entityActions = entity.actions;

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

		entityActions.length = 0;
	}
}

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

	AnimationDefnSet.buildFromImage = function(image)
	{
		var imageAsImageSets = 
		[
			[
				image
			]
		];

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

		return returnValue;
	}

	AnimationDefnSet.buildFromImages = function(animationDefnSetName, images)
	{
		var returnValue = new AnimationDefnSet
		(
			animationDefnSetName,
			[
				new AnimationDefn
				(
					"",
					"",
					AnimationFrame.buildManyFromImages(images)
				),
			]
		);

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

	AnimationDefnSet.fromImagesForHeadings = function(name, imagesForHeadings)
	{
		var animationDefnsForHeadings = [];

		var numberOfHeadings = imagesForHeadings.length;

		for (var i = 0; i < numberOfHeadings; i++)
		{
			var imageForHeading = imagesForHeadings[i];
			var animationFrameForHeading = new AnimationFrame(imageForHeading);
			var animationDefnForHeading = new AnimationDefn
			(
				"" + i, "" + i, [ animationFrameForHeading ]
			);
			animationDefnsForHeadings.push(animationDefnForHeading); 
		}

		var returnValue = new AnimationDefnSet
		(
			name,
			animationDefnsForHeadings
		);

		return returnValue;
	}

	// instance methods

	AnimationDefnSet.prototype.toAnimationRun = function()
	{
		return new AnimationRun(this);
	}
}

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.animationDefnNameNext = null;
	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.clone = function()
	{
		var returnValue = new AnimationRun(this.animationDefnSet);
		returnValue.animationDefnNameCurrent = this.animationDefnNameCurrent;
		returnValue.animationDefnNameNext = this.animationDefnNameNext;
		returnValue.frameIndexCurrent = this.frameIndexCurrent;
		return returnValue;
	}

	AnimationRun.prototype.drawAtPos = function(pos)
	{
		var frameCurrent = this.frameCurrent();
		frameCurrent.image.drawAtPos(this.pos);
	}

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

	AnimationRun.prototype.update = function()
	{
		if (this.animationDefnNameNext != null)
		{
			this.animationDefnNameCurrent = this.animationDefnNameNext;
			this.frameIndexCurrent = 0;
			this.animationDefnNameNext = null;
		}
		else
		{
			this.frameIndexCurrent++;
		}

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

function Body(loc)
{
	this.loc = loc;
}
{
	Body.prototype.propertyName = function() { return "Body" }
}

function BodyDefn(sizeInPixels)
{
	this.sizeInPixels = sizeInPixels;

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);
}
{
	BodyDefn.prototype.propertyName = function() { return "Body"; }

	BodyDefn.prototype.initializeEntityForVenue = function(entity, venue)
	{
		entity.body.loc.venue = venue;
	}
}

function Bounds(min, max)
{
	this.min = min;
	this.max = max;

	this.minAndMax = [ this.min, this.max ];
}
{
	Bounds.prototype.overlapWith = function(other)
	{
		var returnValue = 
		(
			(
				(
					this.min.x > other.min.x
					&& this.min.x < other.max.x
				)
				|| 
				(
					this.max.x > other.min.x
					&& this.max.x < other.max.x
				)
				||
				(
					other.min.x > this.min.x
					&& other.min.x < this.max.x
				)
				||
				(
					other.max.x > this.min.x
					&& other.max.x < this.max.x
				)
			)
			&&
			(
				(
					this.min.y > other.min.y
					&& this.min.y < other.max.y
				)
				|| 
				(
					this.max.y > other.min.y
					&& this.max.y < other.max.y
				)
				||
				(
					other.min.y > this.min.y
					&& other.min.y < this.max.y
				)
				||
				(
					other.max.y > this.min.y
					&& other.max.y < this.max.y
				)
			)
		);	

		return returnValue;
	}
}

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

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

	this.entity = new Entity
	(
		this.name + "_CameraEntity",
		Camera.EntityDefn.name,
		[
			new Body(new Location(new Coords(0, 0))),
		]
	);
}
{
	// constants

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

		Camera.EntityDefn = new EntityDefn
		(
			"CameraEntity",
			// properties
			[
				new BodyDefn
				(
					new Coords(0, 0) // sizeInPixels
				),
			]
		);

	}
}

function CollidableDefn(propertyNamesCollidedWith, collide)
{
	this.propertyNamesCollidedWith = propertyNamesCollidedWith;
	this.collide = collide;
}
{
	CollidableDefn.prototype.propertyName = function() { return "Collidable"; }

	CollidableDefn.prototype.updateEntityForVenue = function(entity, venue)
	{
		var collisionHelper = Globals.Instance.collisionHelper;

		var collidableThis = entity;

		for (var i = 0; i < this.propertyNamesCollidedWith.length; i++)
		{
			var propertyName = this.propertyNamesCollidedWith[i];
			var entitiesToCollideWith = venue.entitiesByPropertyName[propertyName];

			for (var j = 0; j < entitiesToCollideWith.length; j++)
			{
				var collidableOther = entitiesToCollideWith[j];
				if (collidableOther != collidableThis)	
				{
					var doEntitiesCollide = collisionHelper.doEntitiesCollide
					(
						collidableThis, collidableOther
					);

					if (doEntitiesCollide == true)
					{	
						collisionHelper.collideEntities
						(
							null, collidableThis, collidableOther
						);
					}
				}	
			}
		}
	}
}

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

function CollisionHelper()
{}
{
	CollisionHelper.prototype.collideEntities = function(collision, entity0, entity1)
	{
		entity0.defn().collidable.collide(entity0, entity1);
		entity1.defn().collidable.collide(entity1, entity0);
	}

	CollisionHelper.prototype.doEntitiesCollide = function(entity0, entity1)
	{
		var returnValue = entity0.bounds().overlapWith(entity1.bounds());
		return returnValue;
	}

	CollisionHelper.prototype.findCollisionsBetweenEntitiesInSets = function(entitySet0, entitySet1)
	{
		var returnValues = [];

		var numberOfEntitiesInSet0 = entitySet0.length;
		var numberOfEntitiesInSet1 = entitySet1.length;

		for (var i = 0; i < numberOfEntitiesInSet0; i++)
		{
			var entityFromSet0 = entitySet0[i];

			for (var j = 0; j < numberOfEntitiesInSet1; j++)
			{
				var entityFromSet1 = entitySet1[j];

				if (this.doEntitiesCollide(entityFromSet0, entityFromSet1) == true)
				{
					var collision = new Collision
					(
						[entityFromSet0, entityFromSet1]
					);
					returnValues.push(collision);
				}				
			}
		}

		return returnValues;
	}
}

function Color(name, symbol, componentsRGBA)
{
	this.name = name;
	this.symbol = symbol;
	this.componentsRGBA = componentsRGBA;
	
	this.systemColorRecalculate();
}
{
	// constants
	
	Color.NumberOfComponentsRGB = 3;
	Color.NumberOfComponentsRGBA = 4;
	Color.SystemColorComponentMax = 255;

	// static methods

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

	// methods

	Color.prototype.componentAlpha = function()
	{
		return this.componentsRGBA[3];
	}

	Color.prototype.componentBlue = function()
	{
		return this.componentsRGBA[2];
	}

	Color.prototype.componentGreen = function()
	{
		return this.componentsRGBA[1];
	}

	Color.prototype.componentRed = function()
	{
		return this.componentsRGBA[0];
	}

	Color.prototype.luminance = function()
	{
		var returnValue =  
		(
			this.componentRed()
			+ this.componentGreen()
			+ this.componentBlue()
		) / Color.NumberOfComponentsRGB;

		return returnValue;
	}

	Color.prototype.multiply = function(other)
	{
		var luminance = this.luminance();

		for (var i = 0; i < Color.NumberOfComponentsRGB; i++)
		{
			this.componentsRGBA[i] = other.componentsRGBA[i] * luminance;
		}				

		this.systemColorRecalculate();
	}

	Color.prototype.systemColorRecalculate = function()
	{ 
		this.systemColor = 
			"rgba(" 
			+ Math.floor(this.componentRed() * Color.SystemColorComponentMax) + ","
			+ Math.floor(this.componentGreen() * Color.SystemColorComponentMax) + ","
			+ Math.floor(this.componentBlue() * Color.SystemColorComponentMax) + ","
			+ Math.floor(this.componentAlpha())	
			+ ")";
	}

	Color.prototype.toGray = function()
	{
		var luminance = this.luminance();

		for (var i = 0; i < Color.NumberOfComponentsRGB; i++)
		{
			this.componentsRGBA[i] = luminance;
		}

		this.systemColorRecalculate();

		return this;
	}

	// instances

	Color.Instances = new Color_Instances(); // must defer this?

	function Color_Instances()
	{
		this.Transparent = new Color("Transparent", ".", [0, 0, 0, 0]);

		this.Black 	= new Color("Black", 	"k", [0, 0, 0, 1]);
		this.Blue 	= new Color("Blue", 	"b", [0, 0, 1, 1]);
		this.BlueDark 	= new Color("BlueDark", "B", [0, 0, 0.5, 1]);
		this.Brown	= new Color("Brown", 	"n", [.5, .25, 0, 1]);
		this.Cyan	= new Color("Cyan", 	"c", [0, 1, 1, 1]);
		this.Gray 	= new Color("Gray", 	"a", [0.5, 0.5, 0.5, 1]);
		this.GrayDark 	= new Color("GrayDark", "A", [0.25, 0.25, 0.25, 1]);
		this.Green 	= new Color("Green", 	"g", [0, 1, 0, 1]);
		this.GreenDark 	= new Color("GreenDark","G", [0, 0.5, 0, 1]);
		this.Orange 	= new Color("Orange", 	"o", [1, 0.5, 0, 1]);
		this.Red 	= new Color("Red", 	"r", [1, 0, 0, 1]);
		this.Violet 	= new Color("Violet", 	"v", [1, 0, 1, 1]);
		this.White 	= new Color("White", 	"w", [1, 1, 1, 1]);
		this.Yellow 	= new Color("Yellow", 	"y", [1, 1, 0, 1]);

		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.Violet,
			this.Red,
			this.White,
			this.Yellow,
		];

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


}

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

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 entityConstrained = constraint.entityConstrained;

				// hack
				var entityToFollow = entityConstrained.body.loc.venue.entitiesByPropertyName
				[
					"Player"
				][0];

				entityConstrained.body.loc.pos.overwriteWith
				(
					entityToFollow.body.loc.pos
				);
			}
		)
	}
}

function ControlContainer(name, size, pos, children)
{
	this.name = name;
	this.size = size;
	this.pos = pos;
	this.children = children;

	for (var i = 0; i < this.children.length; i++)
	{
		var child = this.children[i];
		child.pos.add(this.pos);
	}
}
{
	ControlContainer.prototype.draw = function()
	{
		var display = Globals.Instance.display;

		display.drawRectangle(this.pos, this.size, "Blue", "Gray");

		for (var i = 0; i < this.children.length; i++)
		{
			var child = this.children[i];
			child.draw();
		}
	}
}

function ControlText(name, pos, text)
{
	this.name = name;
	this.pos = pos;
	this.text = text;
}
{
	ControlText.prototype.draw = function()
	{
		var display = Globals.Instance.display;

		display.drawText(this.text, this.pos, "White");
	}
}

function Controllable(control)
{
	this.control = control;
}
{
	Controllable.prototype.propertyName = function() { return "Controllable"; }
}

function ControllableDefn(buildControlForEntityAndVenue)
{
	this.buildControlForEntityAndVenue = buildControlForEntityAndVenue;
}
{
	ControllableDefn.prototype.propertyName = function() { return "Controllable"; }

	ControllableDefn.prototype.initializeEntityForVenue = function(entity, venue)
	{
		var control = this.buildControlForEntityAndVenue(entity, venue);
		entity.controllable = new Controllable(control);
	}

	ControllableDefn.prototype.updateEntityForVenue = function(entity, venue)
	{
		entity.controllable.control.draw();
	}
}

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.addXY = function(x, y)
	{
		this.x += x;
		this.y += 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.randomize = function()
	{
		this.x = Math.random();
		this.y = Math.random();
		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 + "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;
	}

	Coords.prototype.wrapToRange = function(range)
	{
		if (this.x < 0)
		{
			this.x += range.x;
		}
		else if (this.x >= range.x)
		{
			this.x -= range.x;
		}

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

		return this;
	}
}

function Display(sizeInPixels)
{
	this.sizeInPixels = sizeInPixels;

	// temporary variables
	this.drawPos = new Coords();
}
{
	Display.prototype.drawImageAtPos = function(imageToDraw, pos)
	{
		this.drawPos.overwriteWith
		(
			pos
		).subtract
		(
			imageToDraw.sizeInPixelsHalf
		);

		this.graphics.drawImage
		(
			imageToDraw.systemImage, 
			this.drawPos.x, this.drawPos.y
		);
	}

	Display.prototype.drawRectangle = function(pos, size, systemColorFill, systemColorStroke)
	{
		this.graphics.fillStyle = systemColorFill;
		this.graphics.fillRect(pos.x, pos.y, size.x, size.y);

		this.graphics.strokeStyle = systemColorStroke;
		this.graphics.strokeRect(pos.x, pos.y, size.x, size.y);

	}

	Display.prototype.drawText = function(text, pos, systemColor)
	{
		this.graphics.fillStyle = systemColor;
		this.graphics.strokeStyle = systemColor;
		this.graphics.fillText(text, pos.x, pos.y);
		this.graphics.strokeText(text, pos.x, pos.y);
	}

	Display.prototype.drawVenue = function(venue)
	{
		this.drawRectangle(Coords.Instances.Zeroes, venue.sizeInPixels, "Black", "Gray")
	}

	Display.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.sizeInPixels.x;
		canvas.height = this.sizeInPixels.y;

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

		document.body.appendChild(canvas);
	}
}

function Drawable(visual)
{
	this.visual = visual;
}
{
	Drawable.prototype.propertyName = function() { return "Drawable"; }	
}

function DrawableDefn(visual)
{
	this.visual = visual;
}
{
	DrawableDefn.prototype.propertyName = function() { return "Drawable"; }

	DrawableDefn.prototype.initializeEntityForVenue = function(entity, venue)
	{
		if (entity.drawable == null)
		{
			entity.drawable = new Drawable(entity.defn().drawable.visual.clone());
			entity.drawable.visual.pos = entity.body.loc.pos;
		}
	}

	DrawableDefn.prototype.updateEntityForVenue = function(entity, venue)
	{
		var visual = entity.drawable.visual;
		visual.update();
		visual.drawAtPos(entity.body.loc.pos);
	}
}

function EnemyDefn()
{
	// do nothing
}
{
	EnemyDefn.prototype.propertyName = function() { return "Enemy"; }
}

function Entity(name, defnName, properties)
{
	this.name = name;
	this.defnName = defnName;
	this.properties = properties;

	if (this.properties != null)
	{
		for (var i = 0; i < this.properties.length; i++)
		{
			var property = this.properties[i];
			var propertyName = property.propertyName();
			this.properties[propertyName] = property;
			this[propertyName.toLowerCase()] = property;
		}
	}
}
{
	// methods

	// static methods

	Entity.fromDefn = function(name, defn, properties)
	{
		var returnValue = new Entity(name, null, properties);
		returnValue._defn = defn;
		return returnValue;
	}

	// instance methods

	Entity.prototype.activity = function(value) 
	{ 
		if (value != null)
		{
			this._activity = value;
			this._activity.initialize(); 
		} 

		return this._activity;
	}

	Entity.prototype.bounds = function()
	{
		if (this._bounds == null)
		{
			this._bounds = new Bounds(new Coords(), new Coords());
		}

		var pos = this.body.loc.pos;
		var sizeHalf = this.defn().body.sizeInPixelsHalf;

		this._bounds.min.overwriteWith(pos).subtract(sizeHalf);
		this._bounds.max.overwriteWith(pos).add(sizeHalf);

		return this._bounds;
	}

	Entity.prototype.defn = function()
	{
		var returnValue;

		if (this.defnName != null)
		{
			var entityDefns = Globals.Instance.universe.entityDefns;
			returnValue = entityDefns[this.defnName];
		}
		else
		{
			return this._defn;
		}

		return returnValue;
	}
}

function EntityDefn
(
	name, 	
	properties
)
{
	this.name = name;
	this.properties = properties;

	for (var i = 0; i < this.properties.length; i++)
	{
		var property = this.properties[i];
		var propertyName = property.propertyName();
		this.properties[propertyName] = property;
		this[propertyName.toLowerCase()] = property;
	}
}

function EphemeralDefn(ticksToLive)
{
	this.ticksToLive = ticksToLive;
}
{
	EphemeralDefn.prototype.propertyName = function() { return "Ephemeral"; }

	EphemeralDefn.prototype.initializeEntityForVenue = function(entity, venue) 
	{ 
		entity.ticksToLive = entity.defn().ephemeral.ticksToLive; 
	}
	
	EphemeralDefn.prototype.updateEntityForVenue = function(entity, venue) 
	{ 
		entity.ticksToLive--; 
		if (entity.ticksToLive <= 0)
		{
			venue.entitiesToRemove.push(entity);
		}
	}
}

function Font(name, characterSize, charactersAvailable, characterImages)
{
	this.name = name;
	this.characterSize = characterSize;
	this.charactersAvailable = charactersAvailable;
	this.characterImages = characterImages;
}
{
	// methods
	
	Font.prototype.buildEntityForText = function(text, pos, isFloater)
	{
		var characterSize = this.characterSize;

		var messageSizeInPixels = new Coords
		(
			characterSize.x * text.length, 
			characterSize.y
		);

		var messageSizeInPixelsHalf = messageSizeInPixels.clone().divideScalar(2);

		text = text.toUpperCase();

		var entityDefnProperties = 
		[	
			new BodyDefn(messageSizeInPixels),

			new DrawableDefn
			(
				new VisualText(text)
			),
		];

		if (isFloater == true)
		{
			entityDefnProperties.push(new EphemeralDefn(30));
			entityDefnProperties.push(new MoverDefn(1, 1, 1));
		}

		var entityDefn = new EntityDefn
		(
			"Message_" + text,
			entityDefnProperties
		);

		var returnValue = Entity.fromDefn
		(
			"Message_" + text,
			entityDefn,
			[
				new Body(new Location(pos)),
			]
		);

		if (isFloater == true)
		{
			returnValue.body.loc.vel = new Coords(0, -1);
		}

		return returnValue;
	}
}

function FriendlyDefn()
{	
	// do nothing
}
{
	FriendlyDefn.prototype.propertyName = function() { return "Friendly"; }
}

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

		this.realWorldMillisecondsPerTick = realWorldMillisecondsPerTick;

		this.display = new Display(viewSizeInPixels);
		this.display.initialize();

		this.universe = universe;

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

		this.timer = setInterval
		(
			this.handleEventTimerTick.bind(this), 
			this.realWorldMillisecondsPerTick
		);

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

	Globals.prototype.handleEventTimerTick = function()
	{
		if (this.venueNext != null)
		{
			this.venueNext.initialize();

			this.venueCurrent = this.venueNext;

			this.venueNext = null;
		}

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

function Heading()
{}
{
	// constants

	Heading.NumberOfHeadings = 8;
	Heading.RadiansInCircle = 2 * Math.PI;
	Heading.RadiansPerHeading = 
		Heading.RadiansInCircle 
		/ Heading.NumberOfHeadings;

	// static methods

	Heading.fromCoords = function(coordsToConvert, numberOfHeadings)
	{
		var angleInRadians = Math.atan2
		(
			coordsToConvert.y, coordsToConvert.x
		);

		if (coordsToConvert.y < 0)
		{
			angleInRadians += Heading.RadiansInCircle;
		}

		var heading = Math.round
		(
			angleInRadians 
			* numberOfHeadings 
			/ Heading.RadiansInCircle
		);

		if (heading == Heading.NumberOfHeadings)
		{
			heading = 0;
		}

		return heading;
	}
}

function Image(name, filePath, sizeInPixels, systemImage)
{
	this.name = name;
	this.filePath = filePath;
	this.sizeInPixels = sizeInPixels;
	this.systemImage = systemImage;

	this.sizeInPixelsHalf = this.sizeInPixels.clone().divideScalar(2);
}
{
	// static methods

	Image.fromFilePath = function(filePath)
	{
		var systemImage = document.createElement("img");
		systemImage.src = filePath;

		var returnValue = new Image
		(
			filePath, 
			filePath,
			null, // todo - sizeInPixels
			systemImage
		);

		return returnValue;
	}

	Image.fromSystemImage = function(name, systemImage)
	{
		var sizeInPixels = new Coords
		(
			systemImage.width, systemImage.height
		);

		var returnValue = new Image
		(
			name, 
			systemImage.src,
			sizeInPixels,
			systemImage
		);

		return returnValue;
	}

	// instance methods

	// drawable

	Image.prototype.clone = function()
	{
		var returnValue = new Image
		(
			this.name,
			this.filePath,
			this.sizeInPixels,
			this.systemImage
		);

		return returnValue;
	}

	Image.prototype.drawAtPos = function(pos)
	{
		Globals.Instance.display.drawImageAtPos(this, pos); 
	}

	Image.prototype.update = function()
	{
		// do nothing
	}
}

function ImageHelper()
{
	// do nothing
}
{
	// methods

	ImageHelper.prototype.applyColorFunctionToImage = function
	(
		image, nameOfColorFunctionToApply, argumentForColorFunction
	)
	{
		var canvasAndGraphics = this.imageToCanvasAndGraphicsContext(image);
		var canvas = canvasAndGraphics[0];
		var graphics = canvasAndGraphics[1];

		var imageSizeInPixels = image.sizeInPixels;
		var pixelPos = new Coords(0, 0);
		var pixelColor = new Color("Color", "x", [.1, .2, .3, 1]);

		for (var y = 0; y < imageSizeInPixels.y; y++)
		{
			pixelPos.y = y;
		
			for (var x = 0; x < imageSizeInPixels.x; x++)
			{
				pixelPos.x = x;

				this.overwriteColorWithPixelFromGraphicsAtPos
				(
					pixelColor, graphics, pixelPos
				);
		
				pixelColor[nameOfColorFunctionToApply](argumentForColorFunction);
	
				graphics.fillStyle = pixelColor.systemColor;
				graphics.fillRect(pixelPos.x, pixelPos.y, 1, 1);
			}
		}

		var returnValue = this.canvasToImage
		(
			image.name + "_" + nameOfColorFunctionToApply, 
			canvas
		);
		return returnValue;	
	}

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

	ImageHelper.prototype.buildImagesFromStringArrays = function(name, stringArraysForImagePixels)
	{
		var returnValue = [];

		for (var i = 0; i < stringArraysForImagePixels.length; i++)
		{
			var stringsForImagePixels = stringArraysForImagePixels[i];
			var image = new ImageHelper().buildImageFromStrings(name + i, stringsForImagePixels);
			returnValue.push(image);
		}

		return returnValue;
	}

	ImageHelper.prototype.buildImageFromStringsScaled = function(name, 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 returnValue = this.canvasToImage(name, canvas);

		return returnValue;
	}

	ImageHelper.prototype.buildImageFromStringsTiled = function(name, 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 returnValue = this.canvasToImage(name, canvas);

		return returnValue;
	}

	ImageHelper.prototype.canvasToImage = function(name, canvas)
	{
		var imageFromCanvasURL = canvas.toDataURL("image/png");

		var systemImage = document.createElement("img");
		systemImage.width = canvas.width;
		systemImage.height = canvas.height;
		systemImage.src = imageFromCanvasURL;

		var returnValue = Image.fromSystemImage(name, systemImage);

		return returnValue;
	}

	ImageHelper.prototype.imageToCanvasAndGraphicsContext = function(image)
	{
		var canvas = document.createElement("canvas");
		canvas.width = image.sizeInPixels.x;
		canvas.height = image.sizeInPixels.y;

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

		graphics.drawImage
		(
			image.systemImage, 
			0, 0
		);

		return [ canvas, graphics ];
	}

	ImageHelper.prototype.imageToColor = function(image, color)
	{
		var returnValue = this.applyColorFunctionToImage
		(
			image, "toGray"
		);

		returnValue = this.applyColorFunctionToImage
		(
			image, "multiply", color
		);

		return returnValue;
	}

	ImageHelper.prototype.imageToGray = function(image)
	{
		return this.applyColorFunctionToImage(image, "toGray");
	}

	ImageHelper.prototype.overwriteColorWithPixelFromGraphicsAtPos = function(color, graphics, pixelPos)
	{
		var pixelColorAsComponentsRGBA = graphics.getImageData(pixelPos.x, pixelPos.y, 1, 1).data;

		for (var i = 0; i < Color.NumberOfComponentsRGBA; i++)
		{
			color.componentsRGBA[i] = 
				pixelColorAsComponentsRGBA[i]
				/ Color.SystemColorComponentMax;
		}

		color.systemColorRecalculate();
	}

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

		var systemImageToSlice = imageToSlice.systemImage;		

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

				var imageFromCanavs = this.canvasToImage("Tile", canvas);

				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.fromSystemImage(imageData.id, image);
	}
}
{
	ImageLoader.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 InputBinding(key, action)
{
	this.key = key;
	this.action = action;	
}

function InputBindingSet(bindings)
{
	this.bindings = bindings;
	for (var i = 0; i < this.bindings.length; i++)
	{
		var binding = this.bindings[i];
		var keyCode = "_" + binding.key.systemKeyCode;
		this.bindings[keyCode] = binding;
	}
}

function InputHelper()
{
	this.keyCodesPressed = [];
}
{
	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.handleEventKeyDown.bind(this);
		document.body.onkeyup = this.handleEventKeyUp.bind(this);
	}

	InputHelper.prototype.handleEventKeyDown = function(event)
	{
		var keyCode = "_" + event.keyCode;
		if (this.keyCodesPressed[keyCode] == null)
		{
			this.keyCodesPressed[keyCode] = keyCode;
			this.keyCodesPressed.push(keyCode);
		}
	}

	InputHelper.prototype.handleEventKeyUp = function(event)
	{
		var keyCode = "_" + event.keyCode;
		delete this.keyCodesPressed[keyCode];
		this.keyCodesPressed.remove(keyCode);
	}
}

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.W,
		];

		this.systemKeyCodeToKeyLookup = [];
		for (var i = 0; i < this._All.length; i++)
		{
			var key = this._All[i];
			this.systemKeyCodeToKeyLookup[key.systemKeyCode] = key;
		}
	}
}

function Killable(integrity)
{
	this.integrity = integrity;
}
{
	Killable.prototype.propertyName = function() { return "Killable"; }
}

function KillableDefn(integrityMax)
{
	this.integrityMax = integrityMax;
}
{
	KillableDefn.prototype.propertyName = function() { return "Killable"; }

	KillableDefn.prototype.initializeEntityForVenue = function(entity, venue) 
	{ 
		entity.killable = new Killable(entity.defn().killable.integrityMax); 
	}

	KillableDefn.prototype.updateEntityForVenue = function(entity, venue) 
	{ 
		if (entity.killable.integrity <= 0) { venue.entitiesToRemove.push(entity); } 
	} 
}

function Location(pos)
{
	//this.venue = venue;
	this.pos = pos;
	this.orientation = new Orientation(new Coords(1, 0));

	this.vel = new Coords(0, 0);
	this.accel = new Coords(0, 0);
	this.force = new Coords(0, 0);
}
{
	Location.prototype.clone = function()
	{
		var returnValue = new Location
		(
			this.pos.clone()
		);

		returnValue.venue = returnValue.venue;
		returnValue.vel = returnValue.vel.clone();
		returnValue.accel = returnValue.accel.clone();
		returnValue.force = returnValue.force.clone();


		return returnValue;
	}

	Location.prototype.overwriteWith = function(other)
	{
		this.venue = other.venue;
		this.pos.overwriteWith(other.pos);
	}
}

function MoverDefn(massBase, force, speedMax)
{
	this.massBase = massBase;
	this.force = force;
	this.speedMax = speedMax;
}
{
	MoverDefn.prototype.propertyName = function() { return "Mover"; }

	MoverDefn.prototype.updateEntityForVenue = function(entity, venue)
	{
		var mover = entity;

		var moverMass = 1; // todo
		var moverLoc = mover.body.loc;

		var accelToAdd = moverLoc.force.clone().divideScalar(moverMass);
		moverLoc.accel.add(accelToAdd);

		moverLoc.vel.add(moverLoc.accel);
		var moverSpeedMax = mover.defn().mover.speedMax;
		if (moverLoc.vel.magnitude() > moverSpeedMax)
		{
			moverLoc.vel.normalize().multiplyScalar(moverSpeedMax);
		}

		moverLoc.pos.add(moverLoc.vel);

		moverLoc.pos.trimToRange(venue.sizeInPixels);

		moverLoc.accel.clear();
		moverLoc.force.clear();
	}
}

function Orientation(forward)
{
	this.forward = forward.clone().normalize();
	this.right = new Coords
	(
		0 - this.forward.y, 
		this.forward.x
	);
}

function Planet(color)
{
	this.color = color;
}
{
	Planet.prototype.propertyName = function() { return "Planet"; }

	Planet.prototype.initializeEntityForVenue = function(entity, venue)
	{
		entity.drawable.visual = new VisualSet
		(
			"Planet-Recolored-PlusText",
			[
				new VisualImageRecolored
				(
					entity.drawable.visual,
					this.color
				)
			]
		);
	}
}

function PlanetDefn()
{
	// do nothing
}
{
	PlanetDefn.prototype.propertyName = function() { return "Planet"; }
}

function PlayerDefn()
{
	// do nothing
}
{
	PlayerDefn.prototype.propertyName = function() { return "Player"; }
}

function PortalData(destinationVenueName, destinationPos)
{
	this.destinationVenueName = destinationVenueName;
	this.destinationPos = destinationPos;
}
{
	PortalData.prototype.propertyName = function() { return "Portal"; }
}

function PortalDefn()
{
	// do nothing
}
{
	PortalDefn.prototype.propertyName = function() { return "Portal"; }
}

function ProjectileDefn(damage)
{
	this.damage = damage;
}
{
	ProjectileDefn.prototype.propertyName = function() { return "Projectile"; }
}

function SunDefn()
{
	// do nothing
}
{
	SunDefn.prototype.propertyName = function() { return "Sun"; }
}

function Universe(name, font, activityDefns, entityDefns, venueDefns, venues)
{
	this.name = name;
	this.font = font;
	this.activityDefns = activityDefns;
	this.entityDefns = entityDefns;
	this.venueDefns = venueDefns;
	this.venues = venues;
	
	this.activityDefns.addLookups("name");
	this.entityDefns.addLookups("name");
	this.venueDefns.addLookups("name");
	this.venues.addLookups("name");
}

function Venue(name, defnName, sizeInPixels, entities)
{
	this.name = name;
	this.defnName = defnName;
	this.sizeInPixels = sizeInPixels;
	this.entities = [];

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

	this.entitiesByPropertyName = [];
	this.entitiesToSpawn = entities.slice();
	this.entitiesToRemove = [];
}
{
	// instance methods

	Venue.prototype.defn = function()
	{
		return Globals.Instance.universe.venueDefns[this.defnName];
	}

	Venue.prototype.initialize = function()
	{
		var entityDefns = Globals.Instance.universe.entityDefns;

		for (var i = 0; i < entityDefns.length; i++)
		{
			var properties = entityDefns[i].properties;

			for (var c = 0; c < properties.length; c++)
			{
				var property = properties[c];
				var propertyName = property.propertyName();

				if (this.entitiesByPropertyName[propertyName] == null)
				{
					this.entitiesByPropertyName[propertyName] = [];
				}
			}
		}
	
		this.camera = new Camera
		(
			"Camera",
			Camera.ViewSizeStandard
		);
	
		this.camera.entity.body.loc.venue = this;
	
		this.entitiesToSpawn.push(this.camera.entity);

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

			var entityProperties = entity.defn().properties;
			for (var c = 0; c < entityProperties.length; c++)
			{
				var entityProperty = entityProperties[c];

				if (entityProperty.initializeEntityForVenue != null)
				{
					entityProperty.initializeEntityForVenue(entity, this);
				}
			}

			var entityProperties = entity.properties;
			for (var c = 0; c < entityProperties.length; c++)
			{
				var entityProperty = entityProperties[c];

				if (entityProperty.initializeEntityForVenue != null)
				{
					entityProperty.initializeEntityForVenue(entity, this);
				}
			}
		}
	}

	Venue.prototype.update = function()
	{
		Globals.Instance.display.drawVenue(this);

		this.update_EntitiesToSpawn();

		var propertyNames = 
		[
			"Actor",
			"Mover",
			"Collidable",
			"Controllable",
			"Killable",
			"Ephemeral",
			"Drawable",
		];

		var numberOfProperties = propertyNames.length;

		for (var c = 0; c < numberOfProperties; c++)
		{
			var propertyName = propertyNames[c];

			var entitiesWithProperty = this.entitiesByPropertyName[propertyName];

			var numberOfEntitiesWithProperty = entitiesWithProperty.length;

			for (var b = 0; b < numberOfEntitiesWithProperty; b++)
			{
				var entity = entitiesWithProperty[b];

				var property = entity.defn().properties[propertyName];
				
				if (property == null)
				{
					property = entity.properties[propertyName];
				}

				if (property.updateEntityForVenue != null)
				{
					property.updateEntityForVenue(entity, this);
				}
			}
		}

		//this.update_Collidables();

		this.update_EntitiesToRemove();
	}

	Venue.prototype.update_EntitiesToRemove = function()
	{
		for (var i = 0; i < this.entitiesToRemove.length; i++)
		{
			var entityToRemove = this.entitiesToRemove[i];

			this.entities.remove(entityToRemove);

			var entityProperties = entityToRemove.defn().properties;
			for (var c = 0; c < entityProperties.length; c++)
			{
				var entityProperty = entityProperties[c];
				var entityPropertyName = entityProperty.propertyName();
				var entitiesForProperty = this.entitiesByPropertyName[entityPropertyName];
				entitiesForProperty.remove(entityToRemove);
			}
		}

		this.entitiesToRemove.length = 0;
	}

	Venue.prototype.update_EntitiesToSpawn = function()
	{
		for (var i = 0; i < this.entitiesToSpawn.length; i++)
		{
			var entityToSpawn = this.entitiesToSpawn[i];

			this.entities.push(entityToSpawn);		

			var entityToSpawnDefn = entityToSpawn.defn();

			var entityProperties = entityToSpawnDefn.properties;
			for (var c = 0; c < entityProperties.length; c++)
			{
				this.update_EntitiesToSpawn_Spawn(entityToSpawn, entityProperties[c]);
			}

			var entityProperties = entityToSpawn.properties;
			for (var c = 0; c < entityProperties.length; c++)
			{
				this.update_EntitiesToSpawn_Spawn(entityToSpawn, entityProperties[c]);
			}

		}

		this.entitiesToSpawn.length = 0;
	}

	Venue.prototype.update_EntitiesToSpawn_Spawn = function(entity, entityProperty)
	{
		var entityPropertyName = entityProperty.propertyName();
		var entitiesForPropertyName = this.entitiesByPropertyName[entityPropertyName];
		if (entitiesForPropertyName.contains(entity) == false)
		{
			entitiesForPropertyName.push(entity);
		}

		if (entityProperty.initializeEntityForVenue != null)
		{
			entityProperty.initializeEntityForVenue(entity, this);
		}
	}

	/*
	Venue.prototype.update_Collidables = function()
	{
		var enemies = this.enemies();
		var players = this.players();
		var portals = this.portals();
		var projectiles = this.projectiles();

		var collisionHelper = Globals.Instance.collisionHelper;

		var collisionsOfProjectilesAndEnemies = collisionHelper.findCollisionsBetweenEntitiesInSets
		(
			projectiles, 
			enemies
		);			

		var collisionSets = 
		[
			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				portals
			),

			collisionHelper.findCollisionsBetweenEntitiesInSets
			(
				players, 
				enemies
			),

			collisionsOfProjectilesAndEnemies,

		];

		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 numberOfEntities = collision.entities.length;

				for (var b0 = 0; b0 < numberOfEntities; b0++)
				{
					var entityThis = collision.entities[b0];
					for (var b1 = b0 + 1; b1 < numberOfEntities; b1++)
					{
						var entityOther = collision.entities[b1];

						collisionHelper.collideEntities
						(
							collision, entityThis, entityOther
						);
						collisionHelper.collideEntities
						(
							collision, entityOther, entityThis
						);
					}
				}
			}
		}
	}

	// entities

	Venue.prototype.enemies = function() { return this.entitiesByPropertyName["Enemy"]; }
	Venue.prototype.players = function() { return this.entitiesByPropertyName["Player"]; }
	Venue.prototype.portals = function() { return this.entitiesByPropertyName["Portal"]; }
	Venue.prototype.projectiles = function() { return this.entitiesByPropertyName["Projectile"]; }
	*/

}

function VenueDefn(name, inputBindingSet)
{
	this.name = name;
	this.inputBindingSet = inputBindingSet;
}

function VisualImageRecolored(image, color)
{
	this.image = image;
	this.color = color;

	var imageHelper = new ImageHelper();

	this.imageRecolored = imageHelper.imageToGray
	(
		this.image.clone()
	);

	this.imageRecolored = imageHelper.imageToColor
	(
		this.imageRecolored, this.color
	);
}
{
	VisualImageRecolored.prototype.drawAtPos = function(pos)
	{
		this.imageRecolored.drawAtPos(pos);
	}

	VisualImageRecolored.prototype.update = function()
	{
		// do nothing
	}
	
}

function VisualPositioned(visual, pos)
{
	this.visual = visual;
	this.pos = pos;

	this.posAbsolute = new Coords();
}
{
	VisualPositioned.prototype.drawAtPos = function(pos)
	{
		this.visual.drawAtPos
		(
			this.posAbsolute.overwriteWith
			(
				this.pos
			).add
			(
				pos
			)
		);
	}

	VisualPositioned.prototype.update = function()
	{
		// do nothing
	}
}

function VisualSet(name, visuals)
{
	this.name = name;
	this.visuals = visuals;
}
{
	VisualSet.prototype.clone = function()
	{
		return new VisualSet(this.name, this.visuals);
	}

	VisualSet.prototype.drawAtPos = function(pos)
	{
		for (var i = 0; i < this.visuals.length; i++)
		{
			var visual = this.visuals[i];
			visual.drawAtPos(pos);
		}
	}

	VisualSet.prototype.update = function(entity, venue)
	{
		for (var i = 0; i < this.visuals.length; i++)
		{
			var visual = this.visuals[i];
			visual.update();
		}
	}
}

function VisualText(text)
{
	this.text = text.toUpperCase();
}
{
	VisualText.prototype.clone = function()
	{
		return this;
	}

	VisualText.prototype.drawAtPos = function(pos)
	{
		var font = Globals.Instance.universe.font;
		var characterSize = font.characterSize;
		var characterPos = pos.clone();
		var display = Globals.Instance.display;

		for (var i = 0; i < this.text.length; i++)
		{
			var character = this.text[i];
			var characterIndex = font.charactersAvailable.indexOf
			(
				character
			);

			if (characterIndex >= 0)
			{
				var characterImage = font.characterImages
				[
					characterIndex
				];

				characterPos.x = pos.x + i * characterSize.x;

				display.drawImageAtPos(characterImage, characterPos)
			}
		}
	}

	VisualText.prototype.update = function()
	{
		// do nothing
	}
}

// demo

function Demo()
{
	// do nothing
}
{
	Demo.prototype.font = function()
	{
		var characterSize = new Coords(5, 5);
		var charactersAvailable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !>";		
		var characterImages = new ImageHelper().buildImagesFromStringArrays
		("Font", [
			[
				".rrr.",
				"r...r",
				"rrrrr",
				"r...r",
				"r...r",
			],

			[
				"rrrr.",
				"r...r",
				"rrrr.",
				"r...r",
				"rrrr.",
			],
			[
				".rrrr",
				"r....",
				"r....",
				"r....",
				".rrrr",
			],
			[
				"rrrr.",
				"r...r",
				"r...r",
				"r...r",
				"rrrr.",
			],
			[
				"rrrrr",
				"r....",
				"rrrr.",
				"r....",
				"rrrrr",
			],
			[
				"rrrrr",
				"r....",
				"rrrr.",
				"r....",
				"r....",
			],
			[
				".rrrr",
				"r....",
				"r..rr",
				"r...r",
				".rrrr",
			],
			[
				"r...r",
				"r...r",
				"rrrrr",
				"r...r",
				"r...r",
			],
			[
				"rrrrr",
				"..r..",
				"..r..",
				"..r..",
				"rrrrr",
			],
			[
				".rrrrr",
				"....r.",
				"....r.",
				".r..r.",
				"..rr..",
			],
			[
				"r...r",
				"r..r.",
				"rrr..",
				"r..r.",
				"r...r",
			],
			[
				"r....",
				"r....",
				"r....",
				"r....",
				"rrrrr",
			],
			[
				"r...r",
				"rr.rr",
				"r.r.r",
				"r...r",
				"r...r",
			],
			[
				"r...r",
				"rr..r",
				"r.r.r",
				"r..rr",
				"r...r",
			],
			[
				".rrr.",
				"r...r",
				"r...r",
				"r...r",
				".rrr.",
			],
			[
				"rrrr.",
				"r...r",
				"rrrr.",
				"r....",
				"r.....",
			],
			[
				".rrr.",
				"r...r",
				"r...r",
				".rrr.",
				"..r..",
			],
			[
				"rrrr.",
				"r...r",
				"rrrr.",
				"r..r.",
				"r...r",
			],
			[
				".rrrr",
				"r....",
				".rrr.",
				"....r",
				"rrrr.",
			],
			[
				"rrrrr",
				"..r..",
				"..r..",
				"..r..",
				"..r..",
			],
			[
				"r...r",
				"r...r",
				"r...r",
				"r...r",
				".rrr.",
			],
			[
				"r...r",
				"r...r",
				".r.r.",
				".r.r.",
				"..r..",
			],
			[
				"r...r",
				"r...r",
				"r.r.r",
				"rr.rr",
				"r...r",
			],
			[
				"r...r",
				".r.r.",
				"..r..",
				".r.r.",
				"r...r",
			],
			[
				"r...r",
				".r.r.",
				"..r..",
				"..r..",
				"..r..",
			],
			[
				"rrrrr",
				"...r.",
				"..r..",
				".r...",
				"rrrrr",
			],

			// numerals

			[
				".rrr.",
				"r...r",
				"r...r",
				"r...r",
				".rrr.",
			],
			[
				"..r..",
				".rr..",
				"..r..",
				"..r..",
				"rrrrr",
			],
			[
				".rrr.",
				"r...r",
				"...r.",
				"..r..",
				"rrrrr",
			],
			[
				".rrr.",
				"r...r",
				"...r.",
				"r...r",
				".rrr.",
			],
			[
				"r...r",
				"r...r",
				"rrrrr",
				"....r",
				"....r",
			],
			[
				"rrrrr",
				"r....",
				".rrr.",
				"....r",
				"rrrr.",
			],

			[
				".rrr.",
				"r....",
				"rrrr.",
				"r...r",
				".rrr.",
			],
			[
				"rrrrr",
				"...r.",
				"..rr.",
				".r...",
				"r....",
			],
			[
				".rrr.",
				"r...r",
				".rrr.",
				"r...r",
				".rrr.",
			],
			[
				".rrr.",
				"r...r",
				".rrrr",
				"....r",
				".rrr.",
			],

			// punctuation

			[
				".....",
				".....",
				".....",
				".....",
				".....",
			],
			[
				"..r..",
				"..r..",
				"..r..",
				".....",
				"..r..",
			],
			[
				".rr..",
				"..rr.",
				"...rr",
				"..rr.",
				".rr..",
			],
		]);

		var font = new Font
		(
			"Font",
			characterSize,
			charactersAvailable,
			characterImages
		);

		return font;
	}

	Demo.prototype.universeGrid = function(sizeInVenues)
	{
		if (sizeInVenues == null)
		{
			sizeInVenues = new Coords(10, 10);
		}

		var font = this.font();

		var activityDefns = this.universe_ActivityDefns();

		Camera.initializeStatic();

		var entityDefns = this.universe_EntityDefns();

		var venueDefns = this.universe_VenueDefns();

		var venueSizeInPixels = new Coords(600, 600);
		var venueSizeInPixelsHalf = venueSizeInPixels.clone().divideScalar(2);
		var venues = [];

		var venuePos = new Coords();
		var venueDefn0 = venueDefns[0];

		for (var y = 0; y < sizeInVenues.y; y++)
		{
			venuePos.y = y;

			for (var x = 0; x < sizeInVenues.x; x++)
			{
				venuePos.x = x;

				var venuePosWest = venuePos.clone().addXY(-1, 0).wrapToRange(sizeInVenues);
				var venuePosEast = venuePos.clone().addXY(1, 0).wrapToRange(sizeInVenues);
				var venuePosNorth = venuePos.clone().addXY(0, -1).wrapToRange(sizeInVenues);
				var venuePosSouth = venuePos.clone().addXY(0, 1).wrapToRange(sizeInVenues);

				var venueName = "Venue_" + venuePos.toString();

				var venue = new Venue
				(
					venueName,
					venueDefn0.name,
					venueSizeInPixels,
					// entities
					[
						/*
						font.buildEntityForText
						(
							venueName, 
							new Coords
							(
								venueSizeInPixelsHalf.x,
								venueSizeInPixelsHalf.y + font.characterSize.y * 4
							)
						),
						*/
						Entity.fromDefn
						(
							"Text" + venueName,
							new EntityDefn
							(
								".text",
								[
									new BodyDefn(new Coords(0, 0)),
									new DrawableDefn(new VisualText(venueName)),
								]
							),
							[
								new Body(new Location(venueSizeInPixelsHalf.clone())),
							] // properties
						),

						// sun

						new Entity
						(
							"Sun", 
							entityDefns["Sun"].name, 
							[ 
								new Body(new Location(venueSizeInPixelsHalf.clone())) 
							]
						),

						// portals

						new Entity
						(
							"PortalWest", 
							entityDefns["Portal"].name, 
							[
								new Body(new Location(new Coords(.05 * venueSizeInPixels.x, venueSizeInPixelsHalf.y))),
								new PortalData
								(
									"Venue_" + venuePosWest.toString(),
									new Coords(.9 * venueSizeInPixels.x, venueSizeInPixelsHalf.y)
								),
							]
						),

						new Entity
						(
							"PortalEast", 
							entityDefns["Portal"].name, 
							[
								new Body(new Location(new Coords(.95 * venueSizeInPixels.x, venueSizeInPixelsHalf.y))),
								new PortalData
								(
									"Venue_" + venuePosEast.toString(),
									new Coords(.1 * venueSizeInPixels.x, venueSizeInPixelsHalf.y)
								),
							]
						),

						new Entity
						(
							"PortalNorth", 
							entityDefns["Portal"].name, 
							[
								new Body(new Location(new Coords(venueSizeInPixelsHalf.x, .05 * venueSizeInPixels.y))),
								new PortalData
								(
									"Venue_" + venuePosNorth.toString(),
									new Coords(venueSizeInPixelsHalf.x, .9 * venueSizeInPixels.y)
								),
							]
						),

						new Entity
						(
							"PortalEast", 
							entityDefns["Portal"].name, 
							[
								new Body(new Location(new Coords(venueSizeInPixelsHalf.x, .95 * venueSizeInPixels.y))),
								new PortalData
								(
									"Venue_" + venuePosSouth.toString(),
									new Coords(venueSizeInPixelsHalf.x, .1 * venueSizeInPixels.y)
								),
							]
						),

					]
				);

				var numberOfPlanetsMin = 0;
				var numberOfPlanetsMax = 5;
				var numberOfPlanetsRange = numberOfPlanetsMax - numberOfPlanetsMin;
				var numberOfPlanets = 
					numberOfPlanetsMin 
					+ Math.floor(Math.random() * numberOfPlanetsRange);

				for (var p = 0; p < numberOfPlanets; p++)
				{
					var pos = new Coords().randomize().multiply(venueSizeInPixels).round();
					var entityPlanet = new Entity
					(
						"Planet" + p, 
						entityDefns["Planet"].name, 
						[ 
							new Body(new Location(pos)),
							new Planet(Color.Instances.Orange)
						]
					);
					venue.entitiesToSpawn.push(entityPlanet);
				}

				var numberOfEnemiesMin = 0;
				var numberOfEnemiesMax = 5;
				var numberOfEnemiesRange = numberOfEnemiesMax - numberOfEnemiesMin;
				var numberOfEnemies = 
						numberOfEnemiesMin 
						+ Math.floor(Math.random() * numberOfEnemiesRange);

				for (var e = 0; e < numberOfEnemies; e++)
				{
					var pos = new Coords().randomize().multiply(venueSizeInPixels).round();
					var entityEnemy = new Entity("Enemy" + e, entityDefns["Enemy"].name, [ new Body(new Location(pos)) ]);
					venue.entitiesToSpawn.push(entityEnemy);
				}

				if (venuePos.x == 0 && venuePos.y == 0)
				{
					var entityPlayer = new Entity("Player", entityDefns["Player"].name, [ new Body(new Location(new Coords(100, 100))) ] );
					venue.entitiesToSpawn.push(entityPlayer);

					// friendlies

					var entityFriendly = new Entity("Friendly0", entityDefns["Friendly"].name, [ new Body(new Location(new Coords(350, 50))) ] );
					venue.entitiesToSpawn.push(entityFriendly);
				}

				venues.push(venue);

			}  // end for sizeInVenues.x

		} // end for sizeInVenues.y
	
		var universe = new Universe
		(
			"UniverseGrid" + sizeInVenues.toString(),
			font,
			activityDefns,
			entityDefns,
			venueDefns, 
			venues
		);

		return universe;
	}

	Demo.prototype.universe_ActivityDefns = function()
	{
		var doNothing = new ActivityDefn
		(
			"DoNothing",
			// initialize
			function(activity) 
			{
				// do nothing
			},
			// perform 
			function(activity)
			{
				// do nothing
			}
		);

		var moveRandomly = new ActivityDefn
		(
			"MoveRandomly",

			// initialize
			function(activity) {},

			// perform 
			function(activity)
			{
				var actor = activity.actor;
				var actorLoc = actor.body.loc;
				var actorPos = actorLoc.pos;

				if (activity.vars.target == null)
				{					
					var actorVenue = actorLoc.venue;
					var venueSizeInPixels = actorVenue.sizeInPixels;

					var newTarget = new Coords	
					(
						Math.floor(Math.random() * venueSizeInPixels.x), 
						Math.floor(Math.random() * venueSizeInPixels.y)
					);	

					activity.vars.target = newTarget;
				}

				var target = activity.vars.target;

				var displacementToTarget = target.clone().subtract(actorPos);
				var distanceToTarget = displacementToTarget.magnitude();
				var directionToAccelerate = displacementToTarget.clone().normalize();

				var speedCurrent = actorLoc.vel.magnitude();
				if (speedCurrent > 0)
				{
					var timeToTarget = 
						distanceToTarget / speedCurrent;

					var moverDefn = actor.defn().mover;

					var accelerationCurrent = 
						moverDefn.force / moverDefn.mass;

					if (speedCurrent > accelerationCurrent * timeToTarget)
					{
						directionToAccelerate.multiplyScalar(-1);	
					}
				}

				var distanceThreshold = 8;
				if (distanceToTarget < distanceThreshold)
				{
					activity.vars.target = null;
				}
				else
				{
					var forceToApplyTowardTarget = directionToAccelerate.clone().multiplyScalar
					(
						actor.defn().mover.force
					);
					actorLoc.force.add(forceToApplyTowardTarget);
				}
			}
		);

		var userInputAccept = new ActivityDefn
		(
			"UserInputAccept",

			// initialize 
			function(activity) 
			{},

			// perform 
			function(activity)
			{
				var keyCodesPressed = Globals.Instance.inputHelper.keyCodesPressed;
				var actionsFromActor = activity.actor.actions;
				var inputBindings = Globals.Instance.venueCurrent.defn().inputBindingSet.bindings; 

				for (var i = 0; i < keyCodesPressed.length; i++)
				{
					var keyCodePressed = keyCodesPressed[i];
					var bindingForKeyPressed = inputBindings[keyCodePressed];
					if (bindingForKeyPressed != null)
					{
						var actionForKeyPressed = bindingForKeyPressed.action;
						actionsFromActor.push(actionForKeyPressed);
					}
				}	
			}
		);

		var _all = 
		[
			doNothing,
			moveRandomly,
			userInputAccept,
		];

		_all.addLookups("name");

		return _all;
	}

	Demo.prototype.universe_EntityDefns = function()
	{
		var imageHelper = new ImageHelper();

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

		var imagesForSun = imageHelper.buildImagesFromStringArrays
		( "Sun", [
			[
				".............y............",
				"............y.............",
				".............y............",
				"..........yyyyyy..........",
				"...yy...yy......yy....y...",
				".....y.y..........y...y...",
				".....yy............yyy....",
				".....y..............y.....",
				"....y................y....",
				"....y................y....",
				"...y..................y...",
				"...y..................y...",
				"y.yy..................y.y.",
				".y.y..................yy.y",
				"...y..................y...",
				"...y..................y...",
				"....y................y....",
				"....y................y....",
				".....y..............y.....",
				"......y............yy.....",
				".....yyy..........y.y.....",
				"....y...yy......yy...yy...",
				"....y.....yyyyyy..........",
				"............y.............",
				".............y............",
				"............y.............",
			],
			[
				"............y.............",
				".............y............",
				"............y.............",
				"..........yyyyyy..........",
				"...y....yy......yy...yy...",
				"...y...y..........y.y.....",
				"....yyy............yy.....",
				".....y..............y.....",
				"....y................y....",
				"....y................y....",
				"...y..................y...",
				"...y..................y...",
				".y.y..................yy.y",
				"y.yy..................y.y.",
				"...y..................y...",
				"...y..................y...",
				"....y................y....",
				"....y................y....",
				".....y..............y.....",
				"......y............yyy....",
				"......yy..........y...y...",
				"......y.yy......yy....y...",
				"....yy....yyyyyy..........",
				".............y............",
				"............y.............",
				".............y............",
			],
		]);

		var imagePlanet = imageHelper.buildImageFromStrings
		(
			"Planet",
			[
				"......cccc......",
				"....cc....cc....",
				"...c........c...",
				"..c..........c..",
				".c............c.",
				".c............c.",
				"c..............c",
				"c..............c",
				"c..............c",
				"c..............c",
				".c............c.",
				".c............c.",
				"..c..........c..",
				"...c........c...",
				"....cc.....cc....",
				"......ccccc.....",
			]
		)
		//imagePlanet = imageHelper.imageToColor(imagePlanet, Color.Instances.Green);

		var imagesForPortal = imageHelper.buildImagesFromStringArrays
		("Portal", [
			[
				"...v.....v.....v...",
				"..v.v...v.v...v.v..",
				".v...v.v...v.v...v.",
				"v.....v.....v.....v",
				".v...............v.",
				"..v.............v..",
				"...v...........v...",
				"..v.............v..",
				".v...............v.",
				"v.................v",
				".v...............v.",
				"..v.............v..",
				"...v...........v...",
				"..v.............v..",
				".v...............v.",
				"v.....v.....v.....v",
				".v...v.v...v.v...v.",
				"..v.v...v.v...v.v..",
				"...v.....v.....v...",
			],

			[
				"...v..v.....v..v...",
				"....v..v...v..v....",
				".....v..v.v..v.....",
				"v.....v..v..v.....v",
				".v...v.......v...v.",
				"..v.v.........v.v..",
				"v..v...........v..v",
				".v...............v.",
				"..v.............v..",
				"...v...........v...",
				"..v.............v..",
				".v...............v.",
				"v..v...........v..v",
				"..v.v.........v.v..",
				".v...v.......v...v.",
				"v.....v..v..v.....v",
				".....v..v.v..v.....",
				"....v..v...v..v....",
				"...v..v.....v..v...",
			],
		]);

		var imagesFriendly = imageHelper.buildImagesFromStringArrays
		("Friendly", [
			[
				"......aaaaa......",
				".....a.....a.....",
				"....a..ggg..a....",
				"...a..g...g..a...",
				"..a...ggggg...a..",
				"..a...........a..",
				"..a...........a..",
				"..a...........a..",
				"...aaaaaaaaaaa...",
				".................",
				".....vvvvvvvv....",
				".................",
				"......vvvvvv......",
				".................",
			],
			[
				"......aaaaa......",
				".....a.....a.....",
				"....a..ggg..a....",
				"...a..g...g..a...",
				"..a...ggggg...a..",
				"..a...........a..",
				"..a...........a..",
				"..a...........a..",
				"...aaaaaaaaaaa...",
				".....vvvvvvvv....",
				".................",
				"......vvvvvv......",
				".................",
				".......vvvv.......",
			],
		]);

		var imagesEnemy = imageHelper.buildImagesFromStringArrays
		("Enemy", [
			[
				"......aaaaa......",
				".....a.....a.....",
				"....a..rrr..a....",
				"...a..r...r..a...",
				"..a...rrrrr...a..",
				"..a...........a..",
				"..a...........a..",
				"..a...........a..",
				"...aaaaaaaaaaa...",
				".................",
				".....vvvvvvvv....",
				".................",
				"......vvvvvv......",
				".................",
			],
			[
				"......aaaaa......",
				".....a.....a.....",
				"....a..rrr..a....",
				"...a..r...r..a...",
				"..a...rrrrr...a..",
				"..a...........a..",
				"..a...........a..",
				"..a...........a..",
				"...aaaaaaaaaaa...",
				".....vvvvvvvv....",
				".................",
				"......vvvvvv......",
				".................",
				".......vvvv.......",
			],
		]);

		var imagesForPlayerClockwise = imageHelper.buildImagesFromStringArrays
		("Player", [
			[
				".................",
				".....b...........",
				".....bb..........",
				".....b.b.........",
				".....b..b........",
				".....b...b.......",
				".....b....b......",
				".....b.....b.....",
				".....b......b....",
				".....b.....b.....",
				".....b....b......",
				".....b...b.......",
				".....b..b........",
				".....b.b.........",
				".....bb..........",
				".....b...........",
				".................",
			],
			[
				".................",
				".................",
				".................",
				"............b....",
				"...........bb....",
				"..........b.b....",
				".........b..b....",
				"........b...b....",
				".......b....b....",
				"......b.....b....",
				".....b......b....",
				"....b.......b....",
				"...bbbbbbbbbb....",
				".................",
				".................",
				".................",
				".................",
			],
			[
				".................",
				".................",
				".................",
				".................",
				".................",
				".bbbbbbbbbbbbbbb.",
				"..b...........b..",
				"...b.........b...",
				"....b.......b....",
				".....b.....b.....",
				"......b...b......",
				".......b.b.......",
				"........b........",
				".................",
				".................",
				".................",
				".................",
			],
			[
				".................",
				".................",
				".................",
				"....b............",
				"....bb...........",
				"....b.b..........",
				"....b..b.........",
				"....b...b........",
				"....b....b.......",
				"....b.....b......",
				"....b......b.....",
				"....b.......b....",
				"....bbbbbbbbbb...",
				".................",
				".................",
				".................",
				".................",
			],
			[
				".................",
				"...........b.....",
				"..........bb.....",
				".........b.b.....",
				"........b..b.....",
				".......b...b.....",
				"......b....b.....",
				".....b.....b.....",
				"....b......b.....",
				".....b.....b.....",
				"......b....b.....",
				".......b...b......",
				"........b..b.....",
				".........b.b.....",
				"..........bb.....",
				"...........b.....",
				".................",
			],
			[
				".................",
				".................",
				".................",
				".................",
				"....bbbbbbbbbb...",
				"....b.......b....",
				"....b......b.....",
				"....b.....b......",
				"....b....b.......",
				"....b...b........",
				"....b..b.........",
				"....b.b..........",
				"....bb...........",
				"....b............",
				".................",
				".................",
				".................",
			],
			[
				".................",
				".................",
				".................",
				".................",
				"........b........",
				".......b.b.......",
				"......b...b......",
				".....b.....b.....",
				"....b.......b....",
				"...b.........b...",
				"..b...........b..",
				".bbbbbbbbbbbbbbb.",
				".................",
				".................",
				".................",
				".................",
				".................",
			],
			[
				".................",
				".................",
				".................",
				".................",
				"...bbbbbbbbbb....",
				"....b.......b....",
				".....b......b....",
				"......b.....b....",
				".......b....b....",
				"........b...b....",
				".........b..b....",
				"..........b.b....",
				"...........bb....",
				"............b....",
				".................",
				".................",
				".................",
			],
		]);

		var entityDefnProjectile = new EntityDefn
		(
			"Projectile",

			// properties
			[
				new KillableDefn(1), // integrityMax
				new EphemeralDefn(16), // ticksToLive
				new BodyDefn(new Coords(3, 3)), // sizeInPixels
				new MoverDefn(1, 1, 16), // mass, force, speedMax
				new DrawableDefn
				(
					new AnimationRun
					(
						AnimationDefnSet.buildFromImage(imageMoverProjectile)
					)
				),
				new ProjectileDefn(),
				new CollidableDefn
				(
					[ "Enemy" ],
					// collide
					function(entityThis, entityOther)
					{
						var entityOtherProperties = entityOther.defn().properties;
						if (entityOtherProperties["Enemy"] != null)
						{
							entityOther.killable.integrity -= entityThis.defn().projectile.damage;
							entityThis.killable.integrity = 0;
						}
					}
				),
			]
		);

		var entityDefnPlanet = new EntityDefn
		(	
			"Planet",
			[
				new BodyDefn(new Coords(16, 16)), // sizeInPixels
				new DrawableDefn
				(
					imagePlanet
				),
				new PlanetDefn(),
			]
		);

		var entityDefnPortal = new EntityDefn
		(
			"Portal",
			[
				new BodyDefn(new Coords(19, 19)), // sizeInPixels
				new DrawableDefn
				(
					new AnimationRun
					(
						AnimationDefnSet.buildFromImages("Portal", imagesForPortal)
					)
				),
				new PortalDefn(),
			]
		);		

		var entityDefnSun = new EntityDefn
		(
			"Sun",
			[
				new BodyDefn(new Coords(20, 20)), // sizeInPixels
				new DrawableDefn
				(
					new AnimationRun
					(
						AnimationDefnSet.buildFromImages("Sun", imagesForSun)
					)
				),
				new SunDefn(),
			]
		);

		var entityDefnFriendly = new EntityDefn
		(
			"Friendly", 
			[
				new KillableDefn(1), // integrityMax
				new BodyDefn(new Coords(16, 9)), // sizeInPixels
				new MoverDefn(1, 1, 4), // mass, forcePerTick, speedMax
				new ActorDefn("DoNothing"),
				new DrawableDefn
				(
					new AnimationRun
					(
						AnimationDefnSet.buildFromImages
						(
							"Friendly", imagesFriendly
						)
					)
				),
				new FriendlyDefn(),
			]
		);

		var entityDefnEnemy = new EntityDefn
		(
			"Enemy", 	
			[
				new ActorDefn("MoveRandomly"),
				new BodyDefn(new Coords(16, 9)), // sizeInPixels
				new CollidableDefn
				(
					[ "Player" ],
					// collide
					function (entityThis, entityOther) 
					{
						// do nothing
					}
				),
				new DrawableDefn
				(
					new AnimationRun
					(
						AnimationDefnSet.buildFromImages
						(
							"Enemy", imagesEnemy
						)
					)
				),
				new KillableDefn(1), // integrityMax
				new EnemyDefn(),				
				new MoverDefn(1, 1, 2), // mass, forcePerTick, speedMax
			]
		);

		var playerCollide = function(entityThis, entityOther)
		{
			var entityOtherProperties = entityOther.defn().properties;
			if (entityOtherProperties["Enemy"] != null)
			{
				entityThis.killable.integrity = 0;

				var venue = entityThis.body.loc.venue;

				venue.entitiesToSpawn.push
				(	
					Globals.Instance.universe.font.buildEntityForText
					(
						"You lose!", 
						entityThis.body.loc.pos.clone(),
						true // isFloater
					)
				);
			}
			else if (entityOtherProperties["Planet"] != null)
			{
				// todo
			}
			else if (entityOtherProperties["Portal"] != null)
			{
				var portal = entityOther;
				var venue = entityThis.body.loc.venue;	

				venue.entitiesToRemove.push(entityThis);

				var portalData = portal.portal;
				var destinationVenueName = portalData.destinationVenueName;
				var universe = Globals.Instance.universe;
				var destinationVenue = universe.venues[destinationVenueName];
	
				destinationVenue.entitiesToSpawn.push(entityThis);
				entityThis.body.loc.pos.overwriteWith(portalData.destinationPos);
				Globals.Instance.venueNext = destinationVenue;
			}
		}


		var entityDefnPlayer = new EntityDefn
		(
			"Player", 	
			[
				new ActorDefn("UserInputAccept"),
				new BodyDefn(new Coords(9, 9)), // sizeInPixels	
				new CollidableDefn
				(
					[ "Enemy", "Planet", "Portal" ],
					playerCollide
				),
				new ControllableDefn
				(
					// buildControlForEntityAndVenue
					function(entity, venue)
					{
						var returnValue = new ControlContainer
						(
							"containerPlayer",
							new Coords(64, 64), // size
							new Coords(8, 8), // pos
							// children
							[
								new ControlText
								(
									"textPlayer",
									new Coords(8, 8), // pos
									"[Player]"
								),
							]
						);

						return returnValue;
					}
				),
				new DrawableDefn
				(
					AnimationDefnSet.fromImagesForHeadings
					(
						"Player", imagesForPlayerClockwise
					).toAnimationRun()
				),
				new KillableDefn(1), // integrityMax
				new MoverDefn(1, 2, 8), // mass, forcePerTick, speedMax
				new PlayerDefn(),
			]
		);

		var entityDefns =
		[
			Camera.EntityDefn,
			entityDefnPlanet,
			entityDefnPortal,
			entityDefnSun,

			entityDefnProjectile,
			entityDefnEnemy,
			entityDefnFriendly,
			entityDefnPlayer,
		];

		entityDefns.addLookups("name");

		return entityDefns;
	}

	Demo.prototype.universe_VenueDefns = function()
	{
		var venueDefns = 
		[
			new VenueDefn
			(
				"VenueDefn0",
				new InputBindingSet
				([
					new InputBinding(InputKey.Instances.W, new Action_Accelerate()),
					new InputBinding(InputKey.Instances.A, new Action_Turn(-1)),
					new InputBinding(InputKey.Instances.D, new Action_Turn(1)),
					new InputBinding(InputKey.Instances.F, new Action_Fire()),
				])
			),
		];

		return venueDefns;

	}
}

// tests

function Tests()
{
	// static class
}
{
	Tests.run = function()
	{
		Tests.bounds();
	}

	Tests.bounds = function()
	{
		var bounds0 = new Bounds(new Coords(0, 0), new Coords(1, 1));
		var bounds1 = new Bounds(new Coords(0.1, 0.1), new Coords(0.9, 0.9));
		var bounds2 = new Bounds(new Coords(0.5, 0.5), new Coords(1.5, 1.5));
		var bounds3 = new Bounds(new Coords(2, 2), new Coords(3, 3));

		if (bounds0.overlapWith(bounds1) == false)
		{
			throw "failed"
		}

		if (bounds0.overlapWith(bounds2) == false)
		{
			throw "failed"
		}		

		else if (bounds0.overlapWith(bounds3) == true)
		{
			throw "failed"
		}
		
	}
}

Tests.run();

// run

new Simulation().main();

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

Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

One Response to A Simple Top-Down Space Game in JavaScript

  1. Pingback: Splitting a Single JavaScript Program File into Classes | This Could Be Better

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