A Graphical Inventory for a Video Game in JavaScript

The JavaScript code below implements a inventory session for a video game. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Use the arrow keys and the Enter key to select items and perform actions on them.

This program builds on an earlier post that used DOM elements to achieve more or less the same functionality. This version instead defines its own control classes, which are drawn to an HTML canvas.

There are some fixes and improvements yet to make. Among other things, there’s no images or descriptions for the selected items; all the status messages simply use JavaScript’s alert() function rather than being displayed to the canvas; the controls are rebuilt from scratch each time a key is pressed, when they should probably use some kind of databinding to handle changes instead; and the list of available actions should probably update itself based on the currently selected item rather than simply validating whether the selected action is valid after the Enter key is pressed. Also, some general code cleanup is probably in order.

InventorySession-Graphical



<html>
<body>

<div id="divMain"></div>

<script type="text/javascript">

// main

function main()
{
	var itemActions = ItemAction.Instances._All;

	var itemCategories = 
	[
		new ItemCategory("Gloves"),
		new ItemCategory("Handheld"),
		new ItemCategory("Hat"),
		new ItemCategory("Pants"),
		new ItemCategory("Potion"),
		new ItemCategory("Ring"),
		new ItemCategory("Scroll"),
		new ItemCategory("Shirt"),
		new ItemCategory("Shoes"),
		new ItemCategory("Treasure"),
	];

	var itemDefns = 
	[	
		new ItemDefn
		(
			"Boots of Stomping", 
			1, 
			[ "Shoes" ], 	 
			function(itemHolder, item)
			{
				alert("You step on some walnuts, and feast on their meat.");
			}
		),
		new ItemDefn("Coins", 				1, [ "Treasure" ], 	null ),
		new ItemDefn("Gem", 				1, [ "Treasure" ], 	null ),
		new ItemDefn
		(
			"Gloves of Dust Detection", 	
			1, 
			[ "Gloves" ],
			function(itemHolder, item) 
			{ 
				alert("You run a finger along the wall.  Yup, there's dust here.");
			}
		),
		new ItemDefn("Helmet of Increased Height", 	1, [ "Hat" ], 		null ),
		new ItemDefn("Jerkin of Jerkininess", 		1, [ "Shirt" ], 	null ),
		new ItemDefn("Pectoral Breastplate", 		1, [ "Shirt" ], 	null ),
		new ItemDefn
		(
			"Potion of Mind Erasing", 
			1, 
			[ "Potion" ], 	
			function(itemHolder, item)
			{
				itemHolder.itemSubtractOne(item);
				alert("You wake up several hours later.  It's amazing you weren't robbed.");
			}
		),
		new ItemDefn
		(
			"Potion of Soothe Guts", 
			1, 
			[ "Potion" ],
			function (itemHolder, item)
			{
				itemHolder.itemSubtractOne(item);
				alert("Ah, the refreshing taste of bismuth.  As usual, you vomit anyway.");
			}
		),
		new ItemDefn("Ring of Roundness", 		1, [ "Ring" ], 		null ),
		new ItemDefn("Ring of Shinyness", 		1, [ "Ring" ], 		null ),
		new ItemDefn
		(
			"Scroll of Teleport", 
			1, 
			[ "Scroll" ],
			function (itemHolder, item) 
			{ 
				itemHolder.itemSubtractOne(item);
				alert("Yeah, this place looks completely different from the other one."); 
			}
		),
		new ItemDefn("Shield of Concealment", 		1, [ "Handheld" ], 	null ),
		new ItemDefn
		(
			"Sword of Keenness", 
			1, 
			[ "Handheld" ],
			function(itemHolder, item)
			{
				alert("You test the blade.  Contrary to expectations, it's quite dull.");
			}
		),
		new ItemDefn("Trousers of Modesty", 		1, [ "Pants" ], 	null ),
	];

	var userEquipmentDefns =
	[
		new UserEquipmentDefn
		(
			"Biped",
			// slotDefns
			[
				new UserEquipmentSlotDefn("Head", [ "Hat" ] ),
				new UserEquipmentSlotDefn("Torso", [ "Shirt" ] ),
				new UserEquipmentSlotDefn("Gloves", [ "Gloves" ] ),
				new UserEquipmentSlotDefn("Left Finger", [ "Ring" ] ),
				new UserEquipmentSlotDefn("Right Finger", [ "Ring" ] ),
				new UserEquipmentSlotDefn("Left Hand", [ "Handheld" ] ),
				new UserEquipmentSlotDefn("Right Hand", [ "Handheld" ] ),
				new UserEquipmentSlotDefn("Legs", [ "Pants" ] ),
				new UserEquipmentSlotDefn("Feet", [ "Shoes" ] ),
			]
		)
	];

	var user = new User
	(
		"User0",
		new ItemHolder
		(
			"Inventory",
			[
				new Item("Boots of Stomping", 1),
				new Item("Gloves of Dust Detection", 1),
				new Item("Jerkin of Jerkininess", 1),
				new Item("Pectoral Breastplate", 1),
				new Item("Ring of Shinyness", 3),
				new Item("Sword of Keenness", 1),
				new Item("Scroll of Teleport", 5),
				new Item("Trousers of Modesty", 1),
			]
		),
		new UserEquipment
		(
			"Biped",
			[
				new UserEquipmentSlotAssignment
				(
					"Torso", "Pectoral Breastplate"
				),
			]
		)
	);

	var itemSession = new ItemSession
	(
		"ItemSession0",
		itemActions,
		itemDefns,
		userEquipmentDefns,
		user,
		// itemsOnGround
		[
			new Item("Coins", 100),
			new Item("Gem", 7),
			new Item("Shield of Concealment", 1),
			new Item("Potion of Soothe Guts", 1),
		]
	);

	var universe = new Universe(itemSession);

	var display = new Display
	(
		new Coords(800, 300), "LightGray", "White"
	);

	Globals.Instance.initialize(display, universe);

}

// extensions

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

// classes

function ControlButton(name, controllable, pos, size, text, click)
{
	this.name = name;
	this.controllable = controllable;
	this.pos = pos;
	this.size = size;
	this.text = text;
	this.click = click;
}
{
	ControlButton.prototype.drawToDisplay = function(display)
	{
		display.drawRectangle(this.posAbsolute, this.size);
		display.drawText(this.text, this.posAbsolute);
	}

	ControlButton.prototype.posAbsoluteUpdate = function(parent)
	{
		this.posAbsolute = this.pos.clone();
		if (parent != null)
		{
			this.posAbsolute.add(parent.posAbsolute);
		}
		return this;
	}
}

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

	this.isHighlighted = false;
}
{
	ControlContainer.prototype.drawToDisplay = function(display, posOfParent)
	{
		display.drawRectangle(this.posAbsolute, this.size);

		if (this.isHighlighted == true)
		{
			var margin = new Coords(1, 1);
			display.drawRectangle
			(
				this.posAbsolute.clone().add(margin),
				this.size.clone().subtract(margin).subtract(margin)
			);
		}

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

	ControlContainer.prototype.posAbsoluteUpdate = function(parent)
	{
		this.posAbsolute = this.pos.clone();
		if (parent != null)
		{
			this.posAbsolute.add(parent.posAbsolute);
		}

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

		return this;
	}
}

function ControlLabel(name, controllable, pos, text)
{
	this.name = name;
	this.controllable = controllable;
	this.pos = pos;
	this.text = text;
}
{
	ControlLabel.prototype.drawToDisplay = function(display)
	{
		display.drawText(this.text, this.posAbsolute);
	}

	ControlLabel.prototype.posAbsoluteUpdate = function(parent)
	{
		this.posAbsolute = this.pos.clone();
		if (parent != null)
		{
			this.posAbsolute.add(parent.posAbsolute);
		}
		return this;
	}
}

function ControlList(name, controllable, pos, size, ySizeOfEntries, entries)
{
	this.name = name;
	this.controllable = controllable;
	this.pos = pos;
	this.size = size;
	this.entrySize = new Coords(this.size.x, ySizeOfEntries);
	this.entries = entries;

	this.numberOfEntriesVisible = Math.floor
	(
		this.size.y / this.entrySize.y
	);

	this.indexOfFirstVisibleEntry = 0;
}
{
	ControlList.prototype.drawToDisplay = function(display)
	{
		display.drawRectangle(this.posAbsolute, this.size);

		var entrySize = this.entrySize;
			
		for (var i = 0; i < this.numberOfEntriesVisible; i++)
		{
			var entryIndex = i + this.indexOfFirstVisibleEntry;
			if (entryIndex < this.entries.length)
			{
				var entry = this.entries[entryIndex];
				entry.drawToDisplay(display);

				if (entryIndex == this.indexOfChildWithFocus)
				{
					display.drawRectangleHollow
					(
						entry.posAbsolute,
						entrySize
					);
				}
			}
		}
	}

	ControlList.prototype.posAbsoluteUpdate = function(parent)
	{
		this.posAbsolute = this.pos.clone();
		if (parent != null)
		{
			this.posAbsolute.add(parent.posAbsolute);
		}

		for (var i = 0; i < this.numberOfEntriesVisible; i++)
		{
			var entryIndex = i + this.indexOfFirstVisibleEntry;
			if (entryIndex < this.entries.length)
			{
				var entry = this.entries[entryIndex];
				entry.pos.overwriteWith
				(
					new Coords
					(
						0, 
						i * this.entrySize.y
					)	
				);
				entry.posAbsoluteUpdate(this);
			}
		}

		return this;
	}
}

function Controllable()
{
	// static class
}
{
	Controllable.manyToControls = function(controllables, size)
	{
		var returnValues = [];

		for (var i = 0; i < controllables.length; i++)
		{
			var controllable = controllables[i];
			var controllableAsControl = controllable.toControl
			(
				new Coords(0, 0), size
			);
			returnValues.push(controllableAsControl);
		}

		return returnValues;
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	// instances
	
	Coords.Instances = new Coords_Instances();

	function Coords_Instances()
	{
		this.Zeroes = new Coords(0, 0);
	}

	// methods

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

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

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

	Coords.prototype.inRangeMax = function(max)
	{
		var returnValue = 
		(
			this.x >= 0 && this.x <= max.x
			&& this.y >= 0 && this.y <= max.y
		);

		return returnValue;
	}

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

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

function Display(sizeInPixels, colorFore, colorBack)
{
	this.sizeInPixels = sizeInPixels;
	this.colorFore = colorFore;
	this.colorBack = colorBack;

	this.fontHeightInPixels = 10; // hack
}
{
	Display.prototype.clear = function()
	{
		this.drawRectangle(Coords.Instances.Zeroes, this.sizeInPixels);
	}

	Display.prototype.colorsForeAndBack = function(colorFore, colorBack)
	{
		this.colorFore = colorFore;
		this.colorBack = colorBack;
	}

	Display.prototype.drawControl = function(control)
	{
		control.drawToDisplay(this);
	}

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

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

	Display.prototype.drawRectangleHollow = function(pos, size)
	{
		this.graphics.strokeStyle = this.colorFore;
		this.graphics.strokeRect(pos.x, pos.y, size.x, size.y);
	}

	Display.prototype.drawText = function(text, pos)
	{
		this.graphics.fillStyle = this.colorFore;
		this.graphics.fillText(text, pos.x, pos.y + this.fontHeightInPixels);
	}

	Display.prototype.drawUniverse = function(universe)
	{
		this.drawItemSession(universe.itemSession);
	}

	Display.prototype.drawItemSession = function(itemSession)
	{
		var itemSessionAsControl = itemSession.toControl
		(
			new Coords(0, 0), 
			this.sizeInPixels
		);

		itemSessionAsControl.posAbsoluteUpdate();

		this.drawControl(itemSessionAsControl);
	}

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

		this.graphics = this.canvas.getContext("2d");
		this.graphics.font = "" + this.fontHeightInPixels + "px sans-serif";

		var divMain = document.getElementById("divMain");
		divMain.appendChild(this.canvas);
	}
}

function Globals()
{}
{
	Globals.Instance = new Globals();

	Globals.prototype.initialize = function(display, universe)
	{
		this.display = display;
		this.inputHelper = new InputHelper();
		this.universe = universe;

		this.display.initialize();
		this.universe.initialize();

		this.display.drawUniverse(this.universe);

		this.inputHelper.initialize();
	}
}

function Image(filePath)
{
	this.filePath = filePath;
}

function InputHelper()
{
	this.keyPressed = null;
}
{
	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.handleEventKeydown.bind(this);
		document.body.onkeyup = this.handleEventKeyup.bind(this);
	}

	// event handlers

	InputHelper.prototype.handleEventKeydown = function(event)
	{
		event.preventDefault();
		this.keyPressed = event.key;
		Globals.Instance.universe.update();
	}

	InputHelper.prototype.handleEventKeyup = function(event)
	{
		this.keyPressed = null;
	}
}

function Item(defnName, quantity)
{
	this.id = Item.IDNext++;
	this.defnName = defnName;
	this.quantity = quantity;
}
{
	// static variables

	Item.IDNext = 0;

	// instance methods

	Item.prototype.defn = function()
	{
		return Globals.Instance.universe.itemSession.itemDefns[this.defnName];
	}

	Item.prototype.use = function(itemHolder)
	{
		var use = this.defn().use;
		if (use == null)
		{
			alert("Nothing happens.");
		}
		else
		{
			use(itemHolder, this);
		}
	}

	// controls

	Item.prototype.toControl = function(pos, size)
	{
		this._control = null; // hack - Always invalidate.

		if (this._control == null)
		{
			var labelItem = new ControlLabel
			(
				"labelItem" + this.id,
				this,
				pos,
				this.toString()
			);

			this._control = labelItem;
		}

		return this._control;
	}

	// string 

	Item.prototype.toString = function()
	{
		return this.defnName + " (" + this.quantity + ")";
	}
}

function ItemAction(name, perform)
{
	this.name = name;
	this.perform = perform;
}
{
	ItemAction.Instances = new ItemAction_Instances();

	function ItemAction_Instances()
	{
		this.Drop = new ItemAction
		(
			"Drop", 
			function(itemSession, itemHolder, item)
			{
				var user = itemSession.user;
				var itemHolderForGround = itemSession.itemHolderForGround;

				if (user.equipment.itemEquipped(item) == true)
				{
					alert("Cannot drop an equipped item!");
				}
				else if (itemHolder == itemHolderForGround)
				{
					alert("Already been dropped!");
				}
				else
				{
					user.itemHolder.itemRemove(item);
					itemHolderForGround.itemAdd(item);
				}
			}
		);

		this.Equip = new ItemAction
		(
			"Equip",
			function(itemSession, itemHolder, item)
			{ 
				if (itemHolder == itemSession.itemHolderForGround)
				{
					alert("Pick it up first!");
				}
				else
				{
					var equipment = itemSession.user.equipment;
					if (equipment.itemEquipped(item) == true)
					{
						alert("Already equipped!");
					}
					else
					{
						equipment.itemEquip(item);
					}
				}
			}
		);

		this.Merge = new ItemAction
		(
			"Merge", 
			function(itemSession, itemHolder, item)
			{ 
				var items = itemHolder.items;
				for (var i = 0; i < items.length; i++)
				{
					var itemOther = items[i];
					if (itemOther.defnName == item.defnName)
					{
						if (itemOther != item)
						{
							itemOther.quantity += item.quantity;
							itemHolder.itemRemove(item);
							break;
						}
					}
				}
			}
		);

		this.Split = new ItemAction
		(
			"Split", 
			function(itemSession, itemHolder, item)
			{ 
				if (item.quantity > 1)
				{
					var quantityToSplit = Math.floor
					(
						item.quantity / 2
					);
					item.quantity -= quantityToSplit;
					var itemNew = new Item
					(
						item.defnName, 
						quantityToSplit
					);
					itemHolder.items.push(itemNew);
				}
			}
		);

		this.Take = new ItemAction
		(
			"Take", 
			function(itemSession, itemHolder, item)
			{ 
				var itemHolderForUser = itemSession.user.itemHolder;
				if (itemHolder == itemHolderForUser)
				{
					alert("You've already got it!");
				}
				else
				{
					itemSession.itemHolderForGround.itemRemove(item);
					itemHolderForUser.itemAdd(item);
				}
			}
		);

		this.Unequip = new ItemAction
		(
			"Unequip", 
			function(itemSession, itemHolder, item)
			{ 
				if (itemHolder == itemSession.itemHolderForGround)
				{
					alert("Already not equipped!");
				}
				else
				{
					var equipment = itemSession.user.equipment;
					if (equipment.itemEquipped(item) == false)
					{
						alert("Already not equipped!");
					}
					else
					{
						equipment.itemUnequip(item);
					}
				}				
			}
		);

		this.Use = new ItemAction
		(
			"Use", 
			function(itemSession, itemHolder, item)
			{ 
				item.use(itemHolder);
			}
		);

		this._All = 
		[
			this.Drop,
			this.Equip,
			this.Merge,
			this.Split,
			this.Take,
			this.Unequip,
			this.Use,
		];
	}

	// methods

	// controllable

	ItemAction.prototype.toControl = function(pos, size)
	{
		var returnValue = new ControlLabel
		(
			"labelItemAction" + this.name,
			this,
			pos,
			this.name
		);

		return returnValue;
	}
}

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

function ItemDefn(name, weight, categoryNames, use)
{
	this.name = name;
	this.weight = weight;
	this.categoryNames = categoryNames;
	this.use = use;
}

function ItemHolder(name, items)
{
	this.name = name;
	this.items = items;

	this.indexOfItemSelected = null;
}
{
	// instance methods

	ItemHolder.prototype.itemAdd = function(itemToAdd)
	{
		var itemExisting = null;

		for (var i = 0; i < this.items.length; i++)
		{
			var item = this.items[i];
			if (item.defnName == itemToAdd.defnName)
			{
				itemExisting = item;
				break;
			}
		}

		if (itemExisting == null)
		{
			this.items.push(itemToAdd);
		}
		else
		{
			itemExisting.quantity += itemToAdd.quantity;
		}
	}

	ItemHolder.prototype.itemRemove = function(item)
	{
		var itemIndex = this.items.indexOf(item);
		this.items.splice
		(
			itemIndex, 1
		);
	}

	ItemHolder.prototype.item = function(itemDefnNameToGet)
	{
		var returnValue = null;

		for (var i = 0; i < this.items.length; i++)
		{	
			var item = this.items[i];
			var itemDefnName = item.defnName;
			if (itemDefnName == itemDefnNameToGet)
			{
				returnValue = item;
				break;
			}
		}

		return returnValue; 
	}

	ItemHolder.prototype.itemSelectNext = function(offset)
	{
		if (this.indexOfItemSelected == null)
		{
			this.indexOfItemSelected = 0;
		}
		else 
		{
			this.indexOfItemSelected += offset;

			if (this.indexOfItemSelected >= this.items.length)
			{
				this.indexOfItemSelected = 0;
			}		
			else if (this.indexOfItemSelected < 0)
			{
				this.indexOfItemSelected = this.items.length - 1;
			}
		}
	}

	ItemHolder.prototype.itemSelected = function()
	{
		return (this.indexOfItemSelected == null ? null : this.items[this.indexOfItemSelected]);
	}

	ItemHolder.prototype.itemSubtractOne = function(item)
	{
		item.quantity -= 1;
		if (item.quantity <= 0)
		{
			this.itemRemove(item);
		}
	}

	ItemHolder.prototype.itemUse = function(item)
	{
		item.use(this);
	}

	// controls

	ItemHolder.prototype.toControl = function(pos, size)
	{
		this._control = null; // hack - Always invalidate.

		if (this._control == null)
		{
			var sizeOfChildContainer = new Coords
			(
				(size.x - 20),
				size.y - 30
			);

			var itemsAsListEntries = Controllable.manyToControls
			(
				this.items,
				new Coords(0, 0) // size
			);

			var listItems = new ControlList
			(
				"listItems",
				this,
				new Coords(10, 20), // pos
				sizeOfChildContainer,
				10, // ySizeOfEntries
				itemsAsListEntries
			);

			listItems.indexOfChildWithFocus = this.indexOfItemSelected;
			
			var containerItemHolder = new ControlContainer
			(
				"containerItemHolder",
				this,
				pos,
				size,
				// children
				[
					new ControlLabel
					(
						"labelItemHolder",
						this,
						new Coords(10, 10), // pos
						this.name + ":"
					),

					listItems,
				]
			);

			this._control = containerItemHolder;
		}

		return this._control;
	}
}

function ItemSelection(itemActions)
{
	this.itemActions = itemActions;

	this.itemHolder = null;
	this.indexOfActionSelected = null;
}
{
	ItemSelection.prototype.actionSelectNext = function(offset)
	{
		if (this.indexOfActionSelected == null)
		{
			this.indexOfActionSelected = 0;
		}
		else
		{
			this.indexOfActionSelected += offset;
			if (this.indexOfActionSelected < 0)
			{
				this.indexOfActionSelected = this.itemActions.length - 1;
			}
			else if (this.indexOfActionSelected >= this.itemActions.length)
			{
				this.indexOfActionSelected = 0;
			}
		}
	}

	ItemSelection.prototype.actionSelected = function()
	{
		return this.itemActions[this.indexOfActionSelected];
	}	

	ItemSelection.prototype.itemSelected = function()
	{
		var returnValue = null;

		if (this.itemHolder != null)
		{
			returnValue = this.itemHolder.itemSelected();
		}

		return returnValue;
	}

	// controllable

	ItemSelection.prototype.toControl = function(pos, size)
	{
		var sizeOfColumn = size.clone().subtract
		(
			new Coords(30, 0)
		).divide
		(
			new Coords(2, 1)
		);

		var sizeOfImage = new Coords(sizeOfColumn.x, sizeOfColumn.x);
		var sizeOfDescription = new Coords(size.x - 20, sizeOfColumn.x / 2);
		var sizeOfActions = new Coords(size.x - 20, 100); // hack

		var listActions = new ControlList
		(
			"listActions",
			this.itemActions,
			new Coords(10, 20), // pos
			new Coords(sizeOfActions.x - 20, sizeOfActions.y - 30), // size
			10, // entrySizeY
			Controllable.manyToControls(this.itemActions)
		);

		if (this.indexOfActionSelected != null)
		{
			listActions.indexOfChildWithFocus = this.indexOfActionSelected;
		}
			
		var itemSelected = this.itemSelected();

		var containerItemSelected = new ControlContainer
		(
			"containerItemSelected",
			this,
			pos,
			size,
			// children
			[
				new ControlLabel
				(
					"labelSelected",
					this,
					new Coords(10, 10),
					"Selected:"
				),

				new ControlLabel
				(
					"labelItemSelectedName",
					this,
					new Coords(10, 20),
					(itemSelected == null ? "[none]" : itemSelected.defnName)
				),

				new ControlContainer
				(
					"containerItemSelectedImage",
					this,
					new Coords(20 + sizeOfColumn.x, 20),
					sizeOfImage,
					[
						new ControlLabel
						(
							"labelImage",
							this,
							new Coords(10, 10),
							"[image]"
						),
					]
				),

				new ControlContainer
				(
					"containerDescription",
					this,
					new Coords(10, 30 + sizeOfImage.x),
					sizeOfDescription,
					[
						new ControlLabel
						(
							"labelDescription", 
							this,
							new Coords(10, 10), 
							"[description]"
						)
					]
				),

				new ControlContainer
				(
					"containerActions",
					this,
					new Coords(10, 40 + sizeOfImage.y + sizeOfDescription.y),
					sizeOfActions,
					[
						new ControlLabel
						(
							"labelActions", 
							this,
							new Coords(10, 10), 
							"Actions:"
						),
					
						listActions
					]
				),
			]
		);

		return containerItemSelected;
	}		
}

function ItemSession(name, itemActions, itemDefns, equipmentDefns, user, itemsOnGround)
{
	this.name = name;

	this.itemActions = itemActions;
	this.itemActions.addLookups("name");

	this.itemDefns = itemDefns;
	this.itemDefns.addLookups("name");

	this.equipmentDefns = equipmentDefns;
	this.equipmentDefns.addLookups("name");

	this.user = user;
	this.itemHolderForGround = new ItemHolder
	(
		"Ground",
		itemsOnGround
	);

	this.itemSelection = new ItemSelection(this.itemActions);

	this.fieldWithFocus = this.user;
}
{
	ItemSession.prototype.update = function()
	{
		var keyPressed = Globals.Instance.inputHelper.keyPressed;

		if (this.fieldWithFocus == this.user)
		{
			if (keyPressed == "ArrowDown")
			{
				this.user.itemHolder.itemSelectNext(1);	
			}
			else if (keyPressed == "ArrowLeft")
			{
				this.fieldWithFocus = this.itemHolderForGround;
			}
			else if (keyPressed == "ArrowUp")
			{
				this.user.itemHolder.itemSelectNext(-1);
			}
			else if (keyPressed == "ArrowRight")
			{
				this.fieldWithFocus = this.itemSelection;
			}
	
		}
		else if (this.fieldWithFocus == this.itemSelection)
		{
			if (keyPressed == "ArrowDown")
			{
				this.itemSelection.actionSelectNext(1);	
			}
			else if (keyPressed == "ArrowLeft")
			{
				this.fieldWithFocus = this.user;
			}
			else if (keyPressed == "ArrowUp")
			{
				this.itemSelection.actionSelectNext(-1);
			}
			else if (keyPressed == "ArrowRight")
			{
				this.fieldWithFocus = this.itemHolderForGround;
			}
			else if (keyPressed == "Enter")
			{
				var actionSelected = this.itemSelection.actionSelected();

				if (actionSelected != null)
				{
					var itemSelected = this.itemSelection.itemSelected();
					if (itemSelected != null)
					{
						actionSelected.perform(this, this.itemSelection.itemHolder, itemSelected);
					}
				}
			}
		}
		else if (this.fieldWithFocus == this.itemHolderForGround)
		{
			if (keyPressed == "ArrowDown")
			{
				this.itemHolderForGround.itemSelectNext(1);
			}
			else if (keyPressed == "ArrowLeft")
			{
				this.fieldWithFocus = this.itemSelection;
			}
			else if (keyPressed == "ArrowUp")
			{
				this.itemHolderForGround.itemSelectNext(-1);
			}
			else if (keyPressed == "ArrowRight")
			{
				this.fieldWithFocus = this.user;
			}
		}

		if (this.fieldWithFocus == this.user)
		{
			this.itemSelection.itemHolder = this.user.itemHolder;
		}
		else if (this.fieldWithFocus == this.itemHolderForGround)
		{
			this.itemSelection.itemHolder = this.itemHolderForGround;
		}

		Globals.Instance.display.keyPressed = null;

		Globals.Instance.display.drawItemSession(this);
	}

	ItemSession.prototype.initialize = function()
	{
		this.user.initialize();
	}

	// controls

	ItemSession.prototype.toControl = function(pos, size)
	{
		this._control = null; // hack - Always invalidate.

		if (this._control == null)
		{
			var sizeOfUserContainer = size.clone().subtract
			(
				new Coords(40, 30)	
			).divide
			(
				new Coords(2, 1)
			);

			var sizeOfContainersGroundAndSelected = size.clone().subtract
			(
				new Coords(sizeOfUserContainer.x + 40, 30)
			).divide
			(
				new Coords(2, 1)
			);

			var containerUser = this.user.toControl
			(
				new Coords(10, 20), 
				sizeOfUserContainer
			);

			var containerSelection = this.itemSelection.toControl
			(
				new Coords
				(
					sizeOfUserContainer.x + 20, 20
				), // pos
				sizeOfContainersGroundAndSelected
			);

			var containerGround = this.itemHolderForGround.toControl
			(
				new Coords
				(
					sizeOfUserContainer.x + sizeOfContainersGroundAndSelected.x + 30, 
					20
				), // pos
				sizeOfContainersGroundAndSelected	
			);

			if (this.fieldWithFocus == this.user)
			{
				containerUser.isHighlighted = true;
			}
			else if (this.fieldWithFocus == this.itemHolderForGround)
			{
				containerGround.isHighlighted = true;
			}
			else if (this.fieldWithFocus == this.itemSelection)
			{
				containerSelection.isHighlighted = true;	
			}

			var containerItemSession = new ControlContainer
			(
				"containerItemSession",
				this,
				pos,
				size,
				[
					new ControlLabel
					(
						"labelItemSession",
						this,
						new Coords(10, 10),
						this.name + ":"	
					),

					containerUser,

					containerSelection,

					containerGround,
				]
			);

			this._control = containerItemSession;
		}

		return this._control;
	}
}

function Universe(itemSession)
{
	this.itemSession = itemSession;
}
{
	Universe.prototype.initialize = function()
	{
		this.itemSession.initialize();
	}

	Universe.prototype.update = function()
	{
		this.itemSession.update();
	}
}

function User(name, itemHolder, equipment)
{
	this.name = name;
	this.itemHolder = itemHolder;
	this.equipment = equipment;
}
{
	User.prototype.initialize = function()
	{
		this.equipment.initialize(this);
	}

	// controls

	User.prototype.toControl = function(pos, size)
	{
		this._control = null; // hack - Always invalidate.

		if (this._control == null)
		{
			var sizeOfChildContainer = size.clone().subtract
			(
				new Coords(30, 30)
			).divide
			(
				new Coords(2, 1)
			);
	
			var containerUser = new ControlContainer
			(
				"containerUser",
				this,
				pos,
				size,
				// children
				[
					new ControlLabel
					(
						"labelUserName",
						this,
						new Coords(10, 10), // pos
						this.name + ":"
					),

					this.equipment.toControl
					(
						new Coords(10, 20), // pos
						sizeOfChildContainer
					),

					this.itemHolder.toControl
					(
						new Coords(sizeOfChildContainer.x + 20, 20), // pos
						sizeOfChildContainer
					),

				]
			);

			this._control = containerUser;
		}

		return this._control;
	}
}

function UserEquipment(defnName, slotAssignments)
{
	this.defnName = defnName;
	this.slotAssignments = slotAssignments;
	this.slots = null;
}
{

	// instance methods

	UserEquipment.prototype.defn = function()
	{
		return Globals.Instance.universe.itemSession.equipmentDefns[this.defnName];
	}

	UserEquipment.prototype.initialize = function(user)
	{
		var slotDefns = this.defn().slotDefns;

		this.slots = [];

		for (var i = 0; i < slotDefns.length; i++)
		{
			var slotDefn = slotDefns[i];
			var slotDefnName = slotDefn.name;
			var slot = new UserEquipmentSlot(slotDefnName, null);
			this.slots.push(slot);
		}

		this.slots.addLookups("defnName");

		for (var i = 0; i < this.slotAssignments.length; i++)
		{
			var slotAssignment = this.slotAssignments[i];
			var slotDefnName = slotAssignment.slotDefnName;
			var itemDefnName = slotAssignment.itemDefnName;

			var slot = this.slots[slotDefnName];
			var itemInSlot = user.itemHolder.item(itemDefnName);
			slot.item = itemInSlot;
		}

		this.slotAssignments = null;
	}

	UserEquipment.prototype.itemEquip = function(itemToEquip)
	{
		var userEquipmentDefn = this.defn();
		var itemDefn = itemToEquip.defn();
		var itemDefnCategoryNames = itemDefn.categoryNames;

		var slotToEquipItemIn = null;

		for (var i = 0; i < this.slots.length; i++)
		{
			var slot = this.slots[i];
			if (slot.item == itemToEquip)
			{
				slotToEquipItemIn = slot;
				break;
			}
			else if (slot.item == null)
			{
				var slotDefn = slot.defn(userEquipmentDefn);
				var itemCategoryNamesEquippable =
					slotDefn.itemCategoryNamesEquippable;
				for (var j = 0; j < itemCategoryNamesEquippable.length; j++)
				{
					var itemCategoryNameEquippable =
						itemCategoryNamesEquippable[j];
					var index = itemDefnCategoryNames.indexOf
					(
						itemCategoryNameEquippable
					);
	
					if (index >= 0)
					{
						slotToEquipItemIn = slot;
						break;
					}
				}
			}
		}

		if (slotToEquipItemIn == null)
		{
			alert("No matching free slots!");
		}
		else
		{
			slotToEquipItemIn.item = itemToEquip;
		}
	}


	UserEquipment.prototype.itemEquipped = function(itemToCheck)
	{
		var returnValue = false;

		for (var i = 0; i < this.slots.length; i++)
		{
			var slot = this.slots[i];
			var slotItem = slot.item;
			if (slotItem == itemToCheck)
			{
				returnValue = true;
				break;
			}
		}

		return returnValue;
	}



	UserEquipment.prototype.itemUnequip = function(itemToUnequip)
	{
		for (var i = 0; i < this.slots.length; i++)
		{
			var slot = this.slots[i];
			var slotItem = slot.item;
			if (slotItem == itemToUnequip)
			{
				slot.item = null;
				break;
			}
		}
	}

	// controls

	UserEquipment.prototype.toControl = function(pos, size)
	{
		this._control = null; // hack - Always invalidate.

		if (this._control == null)
		{
			var slotsAsListEntries = Controllable.manyToControls
			(
				this.slots,
				null // size
			); 

			var listSlots = new ControlList
			(
				"listSlots",
				this,
				new Coords(10, 20),
				size.clone().subtract
				(
					new Coords(20, 30)
				),
				10, // entrySizeY
				slotsAsListEntries
			);

			var containerUserEquipment = new ControlContainer
			(
				"containerUserEquipment",
				this,
				pos,
				size,
				[
					new ControlLabel
					(
						"labelEquipment",
						this,
						new Coords(10, 10),
						"Equipped:"
					),

					listSlots
				]
			);

			this._control = containerUserEquipment;
		}

		return this._control;
	}

}

function UserEquipmentDefn(name, slotDefns)
{
	this.name = name;
	this.slotDefns = slotDefns;
	this.slotDefns.addLookups("name");
}

function UserEquipmentSlot(defnName, item)
{
	this.defnName = defnName;
	this.item = item;
}
{
	UserEquipmentSlot.prototype.defn = function(userEquipmentDefn)
	{
		return userEquipmentDefn.slotDefns[this.defnName];
	}
}
{
	UserEquipmentSlot.prototype.toControl = function(pos, size)
	{
		var thisAsText = 
			this.defnName + ": " 
			+ (this.item == null ? "[none]" : this.item.defnName);

		var returnValue = new ControlLabel
		(
			"label" + this.slotDefnName,
			this,
			pos,
			thisAsText
		);

		return returnValue;
	}
}

function UserEquipmentSlotAssignment(slotDefnName, itemDefnName)
{
	this.slotDefnName = slotDefnName;
	this.itemDefnName = itemDefnName;
}

function UserEquipmentSlotDefn(name, itemCategoryNamesEquippable)
{
	this.name = name;
	this.itemCategoryNamesEquippable = itemCategoryNamesEquippable;
}


// run

main();

</script>

</body>
</html>

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

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s