Building Browsergames: Buying Armor (PHP)

While we’ve built and integrated a weapons system into our game, we’re still missing something that (most) other games have – armor! Today we will be laying the initial groundwork down for our armor system.

To begin with, check out a copy of the source code for our tutorial:

svn checkout http://building-browsergames-tutorial.googlecode.com/svn/trunk/php/pbbg tutorial -r 26
With that done, we’re ready to get started.

Based on our poll from earlier, the majority chose that there should be 5 armor slots – head, torso, legs, right arm, and left arm. That’s a lot of slots – but not very hard to add to our stats table:

INSERT INTO stats(display_name,short_name) VALUES
	('Armor - Head','ahead'),
	('Armor - Torso','atorso'),
	('Armor - Legs','alegs'),
	('Armor - Right Arm','aright'),
	('Armor - Left Arm','aleft'),
	('Item Armor Slot','aslot');

You may have noticed that there’s an extra stat being added there – Item Armor Slot. This stat was added so that we can keep track of which armor slot an item should go in – we’ll take advantage of it later.

To begin with, we’ll need an Armor Shop page. We’ll start with a template, called armor-shop.tpl:

 

<html>
<head>
	<title>The Armor Shop</title>
</head>
<body>
	<p>Welcome to the Armor Shop.</p>
	<p><a href='index.php'>Back to main</a></p>
	<h3>Current Armor:</h3>
	<ul>
		<li>
			Head:
			{if $ahead ne ''}
				{$ahead}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='ahead' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Torso:
			{if $atorso ne ''}
				{$atorso}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='atorso' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Legs:
			{if $alegs ne ''}
				{$alegs}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='alegs' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Right Arm:
			{if $aright ne ''}
				{$aright}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='aright' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Left Arm:
			{if $aleft ne ''}
				{$aleft}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='aleft' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
 
	</ul>
	<p>You may purchase any of the armor listed below.</p>
	{if $error ne ''}
		<p style='color:red'>{$error}</p>
	{/if}
	{if $message ne ''}
		<p style='color:green'>{$message}</p>
	{/if}
	<ul>
		{foreach from=$armor key=id item=i}
			<li>
				<strong>{$i.name}</strong> - <em>{$i.price} gold coins</em>
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='armor-id' value='{$i.id}' />
					<input type='submit' value='Buy' />
				</form>
		{/foreach}
	</ul>
</body>
</html>

If that code looks similar to you at all, that’s because it is – it’s essentially a copy of the weapon shop code. While I personally prefer to write templates for each individual piece of functionality, you could probably refactor the two templates into one – but I will leave that as an exercise for you, the reader.

There will probably be a lot more armor in our game than weapons – after all, there are 5 slots to fill with unique item types, as opposed to the single unique item type needed for weapons. We’ll retrieve a random list of 10 pieces of armor for our shop to display using this query:

SELECT DISTINCT(id), name, price FROM items WHERE type = ‘Armor’ ORDER BY RAND() LIMIT 10;
We’ve figured out how to retrieve the armor, and how to display it – so it’s time to write the logic that will actually do our heavy lifting for us. We’re going to keep all of our code inside armor-shop.php:

<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve player's ID
$query = sprintf("SELECT id FROM users WHERE UPPER(username) = UPPER('%s')",
			mysql_real_escape_string($_SESSION['username']));
$result = mysql_query($query);
list($userID) = mysql_fetch_row($result);
 
require_once 'stats.php';	// player stats
 
$query = "SELECT DISTINCT(id), name, price FROM items WHERE type = 'Armor' ORDER BY RAND() LIMIT 10;";
$result = mysql_query($query);
$armor = array();
while($row = mysql_fetch_assoc($result)) {
	array_push($armor,$row);
}
$stats = array('atorso','ahead','alegs','aright','aleft');
foreach ($stats as $key) {
	$id = getStat($key,$userID);
	$query = sprintf("SELECT name FROM items WHERE id = %s",
			mysql_real_escape_string($id));
	$result = mysql_query($query);
	if($result) {
		list($name) = mysql_fetch_row($result);
		$smarty->assign($key,$name);
	}
}
 
$smarty->assign('armor',$armor);
$smarty->display('armor-shop.tpl');
 
?>

This code is also similar to the code for the weapons shop – however, you might notice one fairly large change. There doesn’t seem to be any code to display the user’s current armor!

This isn’t actually true, however – all we’ve done is convert our retrieval logic to be a little more generic. The 10 lines of code between line 25 and line 35 of armor-shop.php are all the code we need to display however many stats we want – although we’re currently only displaying 5.

We start off by defining the keys for our stats – we will use those to both retrieve the stat values, and set the appropriate values within our smarty template. Then, we loop through each of our stat keys, and retrieve their information. We can get away with this because the keys that we set up in our template and the keys that we used within our stats system are the same; if they weren’t, you would have to do something a little more complex to gain this amount of flexibility. After retrieving the stat’s information, we assign it to our template – and we’re displaying the user’s equipped armor, in 10 lines of code!

However cool that may be, however, we haven’t gotten to the main point of the armor shop yet – actually buying armor! Therefore, we’ll now add to our code in armor-shop.php so that it will respond appropriately when a user clicks on the ‘Buy’ button next to a piece of armor for sale. Before we can do that though, we will need to create armor-stats.php, so that we can retrieve stats for individual pieces of armor:

 

<?php
 
require_once 'stats-dry.php';
 
function getArmorStat($statName,$armorID) {
	return getStatDRY('Item',$statName,$armorID);
}
 
?>

With that done, we can now write the code to purchase armor:

if($_POST) {
	require_once 'armor-stats.php';		// armor stats
	$armorID = $_POST['armor-id'];
	$query = sprintf("SELECT price FROM items WHERE id = %s",mysql_real_escape_string($armorID));
	$result = mysql_query($query);
	list($cost) = mysql_fetch_row($result);
	$gold = getStat('gc',$userID);
	if ($gold > $cost) {
		$slot = getArmorStat('aslot',$armorID);
		$equipped = getStat($slot,$userID);
		if(!$equipped) {
			setStat($slot,$userID,$armorID);
			setStat('gc',$userID,($gold - $cost));
			$smarty->assign('message','You purchased and equipped the new armor.');
		} else {
			// they already have something equipped - display an error
			$smarty->assign('error','You are already wearing a piece of that kind of armor! You will need to sell your current armor before you can buy new armor.');
		}
	} else {
		$smarty->assign('error','You cannot afford that piece of armor.');
	}
}

And with that piece of code added, users can now purchase armor. As you can see, we are using the aslot stat that we added earlier to determine which armor slot a specific piece of armor should go in – this way, we can reduce the amount of code that we need to write to handle multiple armor slots.

Right now, players can buy armor – but they can’t sell it. Let’s add the code to handle users selling their armor:

 

if($_POST['sell']) {
	$armorSlot = getArmorStat('aslot',$_POST['sell']);
	$armorID = getStat($armorSlot,$userID);
	$query = sprintf("SELECT price FROM items WHERE id = %s",mysql_real_escape_string($armorID));
	$result = mysql_query($query);
	list($price) = mysql_fetch_row($result);
	$gold = getStat('gc',$userID);
	setStat('gc',$userID,($gold + $price));
	setStat($armorSlot,$userID,'');		
} else {

And with that piece of code added, users can now buy and sell their armor. The single last change that we need to make is adding the ‘Armor Shop’ link to index.tpl:

 

<p><a href='armor-shop.php'>The Armor Shop</a></p>

And that’s that! Here is all of the code for our new armor shop:

<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve player's ID
$query = sprintf("SELECT id FROM users WHERE UPPER(username) = UPPER('%s')",
			mysql_real_escape_string($_SESSION['username']));
$result = mysql_query($query);
list($userID) = mysql_fetch_row($result);
 
require_once 'stats.php';	// player stats
 
if($_POST) {
	require_once 'armor-stats.php';		// armor stats
	if($_POST['sell']) {
		$armorSlot = getArmorStat('aslot',$_POST['sell']);
		$armorID = getStat($armorSlot,$userID);
		$query = sprintf("SELECT price FROM items WHERE id = %s",mysql_real_escape_string($armorID));
		$result = mysql_query($query);
		list($price) = mysql_fetch_row($result);
		$gold = getStat('gc',$userID);
		setStat('gc',$userID,($gold + $price));
		setStat($armorSlot,$userID,'');		
	} else {	
		$armorID = $_POST['armor-id'];
		$query = sprintf("SELECT price FROM items WHERE id = %s",mysql_real_escape_string($armorID));
		$result = mysql_query($query);
		list($cost) = mysql_fetch_row($result);
		$gold = getStat('gc',$userID);
		if ($gold > $cost) {
			$slot = getArmorStat('aslot',$armorID);
			$equipped = getStat($slot,$userID);
			if(!$equipped) {
				setStat($slot,$userID,$armorID);
				setStat('gc',$userID,($gold - $cost));
				$smarty->assign('message','You purchased and equipped the new armor.');
			} else {
				// they already have something equipped - display an error
				$smarty->assign('error','You are already wearing a piece of that kind of armor! You will need to sell your current armor before you can buy new armor.');
			}
		} else {
			$smarty->assign('error','You cannot afford that piece of armor.');
		}
	}
}
$query = "SELECT DISTINCT(id), name, price FROM items WHERE type = 'Armor' ORDER BY RAND() LIMIT 10;";
$result = mysql_query($query);
$armor = array();
while($row = mysql_fetch_assoc($result)) {
	array_push($armor,$row);
}
$stats = array('atorso','ahead','alegs','aright','aleft');
foreach ($stats as $key) {
	$id = getStat($key,$userID);
	$query = sprintf("SELECT name FROM items WHERE id = %s",
			mysql_real_escape_string($id));
	$result = mysql_query($query);
	if($result) {
		list($name) = mysql_fetch_row($result);
		$smarty->assign($key,$name);
	}
}
 
$smarty->assign('armor',$armor);
$smarty->display('armor-shop.tpl');
 
?>

And here’s the code for the template(armor-shop.tpl):

 

<html>
<head>
	<title>The Armor Shop</title>
</head>
<body>
	<p>Welcome to the Armor Shop.</p>
	<p><a href='index.php'>Back to main</a></p>
	<h3>Current Armor:</h3>
	<ul>
		<li>
			Head:
			{if $ahead ne ''}
				{$ahead}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='ahead' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Torso:
			{if $atorso ne ''}
				{$atorso}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='atorso' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Legs:
			{if $alegs ne ''}
				{$alegs}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='alegs' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Right Arm:
			{if $aright ne ''}
				{$aright}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='aright' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Left Arm:
			{if $aleft ne ''}
				{$aleft}
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='sell' value='aleft' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
 
	</ul>
	<p>You may purchase any of the armor listed below.</p>
	{if $error ne ''}
		<p style='color:red'>{$error}</p>
	{/if}
	{if $message ne ''}
		<p style='color:green'>{$message}</p>
	{/if}
	<ul>
		{foreach from=$armor key=id item=i}
			<li>
				<strong>{$i.name}</strong> - <em>{$i.price} gold coins</em>
				<form action='armor-shop.php' method='post'>
					<input type='hidden' name='armor-id' value='{$i.id}' />
					<input type='submit' value='Buy' />
				</form>
		{/foreach}
	</ul>
</body>
</html>

If you’re having trouble getting some armor into your shop to play around with, here’s a quick SQL query you can run to insert some sample armors:

INSERT INTO items(name,type,price) VALUES ('Sample Helmet','Armor',10);
	INSERT INTO entity_stats(stat_id,entity_id,value,entity_type) VALUES ((SELECT id FROM stats WHERE short_name='aslot'),(SELECT id FROM items WHERE name='Sample Helmet'),'ahead','Item');
INSERT INTO items(name,type,price) VALUES ('Sample Torso','Armor',10);
	INSERT INTO entity_stats(stat_id,entity_id,value,entity_type) VALUES ((SELECT id FROM stats WHERE short_name='aslot'),(SELECT id FROM items WHERE name='Sample Torso'),'atorso','Item');
INSERT INTO items(name,type,price) VALUES ('Sample Legs','Armor',10);
	INSERT INTO entity_stats(stat_id,entity_id,value,entity_type) VALUES ((SELECT id FROM stats WHERE short_name='aslot'),(SELECT id FROM items WHERE name='Sample Legs'),'alegs','Item');
INSERT INTO items(name,type,price) VALUES ('Sample Right Arm','Armor',10);
	INSERT INTO entity_stats(stat_id,entity_id,value,entity_type) VALUES ((SELECT id FROM stats WHERE short_name='aslot'),(SELECT id FROM items WHERE name='Sample Right Arm'),'aright','Item');
INSERT INTO items(name,type,price) VALUES ('Sample Left Arm','Armor',10);
	INSERT INTO entity_stats(stat_id,entity_id,value,entity_type) VALUES ((SELECT id FROM stats WHERE short_name='aslot'),(SELECT id FROM items WHERE name='Sample Left Arm'),'aleft','Item');

Extra Credit

Refactor the weapon shop code to use the same code as the armor shop for displaying current equipment.
Refactor the weapon/armor shop templates so that both pages can use the same template.
Refactor both pieces of code, so that there is only one template and one code file for weapons and armor.
There was a small bug found in the weapon stat’s retrieval code during the writing of this entry – make sure to update your checked out version