An Improved Game Inventory Manager in JavaScript

The code below significantly enhances the simple inventory management system from a previous post. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

Inventory

<html>
<body>

<script type="text/javascript">

// main

function main()
{
	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(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(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(item)
			{
				alert("You wake up several hours later.  It's amazing you weren't robbed.");
			}
		),
		new ItemDefn
		(
			"Potion of Soothe Guts", 
			1, 
			[ "Potion" ],
			function (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 (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(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 world = new World
	(
		"World0",
		itemDefns,
		userEquipmentDefns,
		user,
		// itemsOnGround
		[
			new Item("Coins", 100),
			new Item("Gem", 7),
			new Item("Shield of Concealment", 1),
		]
	);

	Globals.Instance.initialize(world);

}

// classes

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

function DisplayHelper()
{}
{
	DisplayHelper.prototype.drawItem = function
	(
		item, actionsAvailable, parentElement
	)
	{
		var divItem = document.createElement("div");
		divItem.style.border = "1px solid";

		var itemAsString = item.toString();

		var divItemInfo = document.createElement("div");
		divItemInfo.innerHTML = itemAsString;
		divItemInfo.style.float = "left";
		divItem.appendChild(divItemInfo);

		for (var i = 0; i < actionsAvailable.length; i++)
		{
			var action = actionsAvailable[i];
			
			this.drawItemAction(action, item, divItem);
		}

		parentElement.appendChild(divItem);
	}

	DisplayHelper.prototype.drawItemAction = function
	(
		action, item, parentElement
	)
	{
		var buttonAction = document.createElement("button");
		buttonAction.innerHTML = action.name;
		buttonAction.onclick = action.perform.bind(action, item);
		parentElement.appendChild(buttonAction);
	}

	DisplayHelper.prototype.drawItemHolder = function
	(
		itemHolder, actionsAvailable, parentElement
	)
	{
		var divHolder = document.createElement("div");
		divHolder.style.border = "1px solid";
		divHolder.style.margin = "8px";

		var pHolderName = document.createElement("p");
		pHolderName.innerHTML = itemHolder.name + ":";
		divHolder.appendChild(pHolderName);

		var divItems = document.createElement("div");

		var items = itemHolder.items;
		for (var i = 0; i < items.length; i++)
		{
			var item = items[i];
			this.drawItem(item, actionsAvailable, divItems);
		}

		divHolder.appendChild(divItems);

		parentElement.appendChild(divHolder);
	}

	DisplayHelper.prototype.drawUser = function(user, parentElement)
	{
		var divUser = document.createElement("div");
		divUser.style.border = "1px solid";
		divUser.style.margin = "8px";

		var pUserName = document.createElement("p");
		pUserName.innerHTML = "User: " + user.name;
		divUser.appendChild(pUserName);

		this.drawUserEquipment
		(
			user.equipment, 
			divUser
		);

		var actionsAvailable = 
		[
			ItemAction.Instances.Use,
			ItemAction.Instances.Equip,
			ItemAction.Instances.Drop,
			ItemAction.Instances.Split,
			ItemAction.Instances.Merge,
		];
		
		this.drawItemHolder
		(
			user.itemHolder, 
			actionsAvailable,
			divUser
		);

		parentElement.appendChild(divUser);
	}


	DisplayHelper.prototype.drawUserEquipment = function
	(
		userEquipment, parentElement
	)
	{
		var divUserEquipment = document.createElement("div");
		divUserEquipment.style.border = "1px solid";
		divUserEquipment.style.margin = "8px";

		var pEquipment = document.createElement("p");
		pEquipment.innerHTML = "Equipment:";
		divUserEquipment.appendChild(pEquipment);

		var equipmentDefn = userEquipment.defn();
		var slots = userEquipment.slots;

		var actions = 
		[
			ItemAction.Instances.Use
		];

		for (var i = 0; i < slots.length; i++)
		{
			var slot = slots[i];
			this.drawUserEquipmentSlot
			(
				slot, actions, divUserEquipment
			);
		}

		parentElement.appendChild(divUserEquipment);
	}	


	DisplayHelper.prototype.drawUserEquipmentSlot = function
	(
		slot, actionsAvailable, parentElement
	)
	{
		var divSlot = document.createElement("div");
		divSlot.style.border = "1px solid";
		divSlot.style.margin = "8px";

		var divSlotDefnName = document.createElement("div");
		divSlotDefnName.innerHTML = slot.defnName + ":";
		divSlotDefnName.style.float = "left";
		divSlot.appendChild(divSlotDefnName);

		var slotItem = slot.item;

		if (slotItem == null)
		{
			var divEmpty = document.createElement("div");
			divEmpty.innerHTML = "[empty]";
			divSlot.appendChild(divEmpty);
		}
		else
		{
			var actionsAvailable = 
			[
				ItemAction.Instances.Unequip,
			];

			this.drawItem
			(
				slot.item, 
				actionsAvailable, 
				divSlot
			);
		}

		parentElement.appendChild(divSlot);
	}

	DisplayHelper.prototype.drawWorld = function(world)
	{
		var divWorldID = "divWorld";
		var divWorld = document.getElementById(divWorldID);

		if (divWorld != null)
		{
			document.body.removeChild(divWorld);
		}

		divWorld = document.createElement("div");
		divWorld.id = divWorldID;
		divWorld.style.border = "1px solid";
		divWorld.style.margin = "8px";

		var pWorldName = document.createElement("p");
		pWorldName.innerHTML = "World: " + world.name;
		divWorld.appendChild(pWorldName);

		this.drawUser(world.user, divWorld);

		var actionsAvailableForGround =
		[
			ItemAction.Instances.Take,
		];

		this.drawItemHolder
		(
			world.itemHolderForGround, 
			actionsAvailableForGround,
			divWorld
		);

		document.body.appendChild(divWorld);
	}
}

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

	Globals.prototype.initialize = function(world)
	{
		this.world = world;
		this.displayHelper = new DisplayHelper();

		this.world.initialize();

		this.displayHelper.drawWorld(this.world);
	}
}

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

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.world.itemDefns[this.defnName];
	}

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

	// 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(item)
			{
				var world = Globals.Instance.world;
				var user = world.user;
				if (user.equipment.itemEquipped(item) == true)
				{
					alert("Cannot drop an equipped item!");
				}
				else
				{
					user.itemHolder.itemRemove(item);
					world.itemHolderForGround.itemAdd(item);
					Globals.Instance.displayHelper.drawWorld(world);
				}
			}
		);

		this.Equip = new ItemAction
		(
			"Equip",
			function(item)
			{ 
				var world = Globals.Instance.world;
				var user = world.user;
				var equipment = user.equipment;
				user.equipment.itemEquip(item);
				Globals.Instance.displayHelper.drawWorld(world);
			}
		);

		this.Merge = new ItemAction
		(
			"Merge", 
			function(item)
			{ 
				var world = Globals.Instance.world;
				var user = world.user;
				var itemHolder = user.itemHolder;
				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;
						}
					}
				}
				Globals.Instance.displayHelper.drawWorld(world);
			}
		);

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

		this.Take = new ItemAction
		(
			"Take", 
			function(item)
			{ 
				var world = Globals.Instance.world;
				var user = world.user;
				world.itemHolderForGround.itemRemove(item);
				user.itemHolder.itemAdd(item);
				Globals.Instance.displayHelper.drawWorld(world);
			}
		);

		this.Unequip = new ItemAction
		(
			"Unequip", 
			function(item)
			{ 
				var world = Globals.Instance.world;
				var user = world.user;
				var equipment = user.equipment;
				user.equipment.itemUnequip(item);
				Globals.Instance.displayHelper.drawWorld(world);
			}
		);

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

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;
}
{
	// 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.itemUse = function(item)
	{
		item.use(this);
	}
}

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

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

	// instance methods

	UserEquipment.prototype.defn = function()
	{
		return Globals.Instance.world.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;
		}
	}

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

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

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

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

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

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

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

	this.user = user;
	this.itemHolderForGround = new ItemHolder
	(
		"Ground",
		itemsOnGround
	);
}
{
	World.prototype.initialize = function()
	{
		this.user.initialize();
	}
}

// run

main();

</script>

</body>
</html>

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

4 Responses to An Improved Game Inventory Manager in JavaScript

  1. Pingback: A Simple Game Inventory Manager In JavaScript | This Could Be Better

  2. Alex Verhoef says:

    Hello there! I was curious about something I saw in your code – is there any purpose/necessity to setting aside your prototypes in separate blocks of code?

    { <—these brackets
    World.prototype.initialize = function()
    {
    this.user.initialize();
    }
    } <—

    Great post by the way, I'm learning a ton working my way through it!

    • There is no necessity, no. The extra brackets and indentation are partly just an aid to my understanding, to help me distinguish the various “classes” in my giant blocks of code apart from one another. I also find that this style also helps to ensure, if I need to change or move things around later, that I do it correctly and completely.

      My very idiosyncratic programming style developed mostly from an attempt to make my JavaScript code look as much like Java/C# code as possible, so that I can more easily port the programs I post on this blog to one of those languages. To that end, I try to avoid using some of JavaScript’s more specialized features. For example, I always use an indexed for loop to iterate over an array rather than the Array object’s .forEach() method. Things like that tend to drive some JavaScript purists out of their minds with rage, but I’m programming for me, not them.

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