php

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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:

1
2
3
4
5
6
7
8
9
<?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:

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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:

21
22
23
24
25
26
27
28
29
30
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:

19
<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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<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

  1. Refactor the weapon shop code to use the same code as the armor shop for displaying current equipment.
  2. Refactor the weapon/armor shop templates so that both pages can use the same template.
  3. 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!

Monday, September 22nd, 2008 buildingbrowsergames, code, php Comments

Sending unknown number of variable parameters to new page in HTML

Before I get started I wanted to say that I am not a web server expert so if you know of a better way to do this, post it in the comments and I will thank you as I implement your knowledge in my game.

When using PHP you eventually come to the realization that your gatekeepers for everything you do and display are HTTP and HTML.  They are both great tools but were designed with a more static implementation in mind. Values passed to new web pages are set up in key->value pairs that are accessed with an associative array using the name of the parameter as the key (ie. $_POST['myParam']).  This post will deal with the issue of how to send an unknown number of parameters which are user set to a new page which can then use those parameters correctly.

My particular implementation is my Mining page in my game TerraTanks.  The mining page allows the user to set the type of mining on each of their planets so that if they are deficient in a particular resource, they can mine it out faster.  I wanted to make sure that this could be changed globally, so the mining page will show all of your planets and each planet will have a set of radio buttons allowing you to change the mining settings.  With these constraints, you cannot know how many planets the user will have and you must write your code to account for any number of parameters.

This is a simplified version of what I have:

$count = 0;
 
// I have the result set for all my mining planets in $result
while ($result && $element = mysql_fetch_object($result))
{
    // code displaying what I need about planet //
 
    echo "<INPUT TYPE=\"radio\" name=\"miner{$count}\" value=\"normal,$element->location\" checked>Normal";
    echo "<INPUT TYPE=\"radio\" name=\"miner{$count}\" value=\"iron,$element->location\">Iron";
    $count++;
}

I am creating the name of the radio button group by appending the value of $count the the word miner and then incrementing count as long as there are planets. Basically, I am hacking together my own array. It should be noted that I am setting the first radio button to the checked state. The web page that will read the input depends on everything that exists having a value so it is necessary to make sure that something is selected. In your work you will want to check what the value is previously set to and make sure the correct radio button is checked when the page loads.

You may also notice that I am shoving a lot of data into the value sent appended by commas. This is a decent way of shoving related data through without having to define a ton of variables.

Now we have to create something on the receiving page that will correctly read in the values that we are sending. This is the code that I am using:

$x = 0;
$indexName = "miner" . $x;
while ($_POST[$indexName])
{
	$miner[$x] = $_POST[$indexName];
	$x++;
	$indexName = "miner" . $x;	
}

If you read the code in English it roughly says, as long as there is a POST value at the index miner{$x} where $x starts at 0 and increments by one then just keep incrementing $x and saving the value into my own array. This is why it was so important that as long as you had a planet you had a value that was passed. If you set a value for planet 0, 1, and 5 but not 2, 3, or 4 then the code would stop at 2, determine there was no value and would never get to 5.

Now instead of creating a second pseudo array with hidden inputs that pointed to the location of the planet, I packed all the information into a comma appended string. When I get the value of $miner[$x] I can use the php function explode to separate out the sub values.

$minerParts[$x] = explode (",", $miner[$x]);
 
// now $minerParts[$x][0] is the mining type and $minerParts[$x][1] is the location

So there is a way to send an unknown number of variables to a new page. Like I said, if you know of a better way make sure you post it in the comments. I don’t claim to be an expert on this subject, it is just how I resolved this problem.

Wednesday, September 17th, 2008 code, php, terratanks, tutorial Comments

Building Browsergames: Integrating weapons into our combat system (PHP)

For all that we’ve built both a combat system and a weapons system, we haven’t yet done anything that allows the weapon a user has selected as their primary weapon to affect the combat system. Today, we will be integrating weapons into our combat system, so that the stats of the weapon actually affect the combat results.

This is actually a lot easier than it sounds - all we have to do is retrieve the weapon’s ‘attack’ stat, use it to modify the damage that the player does, and we’re finished! We’ll open up forest.php, to the piece of code that retrieves our player’s attack:

21
22
23
24
25
26
$player = array (
	name		=>	$_SESSION['username'],
	attack 		=>	getStat('atk',$userID),
	defence		=>	getStat('def',$userID),
	curhp		=>	getStat('curhp',$userID)
);

Unfortunately, we don’t yet have a weapon stats retrieval system - but thanks to our DRY changes from earlier, that’s an easy piece of code to add:

1
2
3
4
5
6
7
8
9
<?php
 
require_once 'stats-dry.php';
 
function getWeaponStat($statName,$weaponID) {
	return getStatDRY('Weapon',$statName,$weaponID);
}
 
?>

Because we won’t be allowing users to modify the stats of weapons in the game, we only need to add the getWeaponStat function. Save that file as weapon-stats.php, and go back to forest.php. First off, we’ll require our weapon stats code:

4
require_once 'weapon-stats.php';

Because we’re using a simple combat formula, we can directly modify the player’s attack attribute in the associative array that we are using to store their stats. Here’s how we would retrieve their primary weapon, and then add it’s attack to the player’s:

28
29
30
$phand = getStat('phand',$userID);
$atk = getWeaponStat('atk',$phand);
$player['attack'] += $atk;

And with that small piece of code added, we’re finished! If the weapon has an attack value(even if it’s negative), it will be added to the player’s when they attack the monster.

Extra Credit

  • Make the combat system display the name of the weapon when the player attacks - e.g. “You attack <monster> with your <weapon> for 10 damage!”.

There was a small bug with the stats retrieval code found in this version of the tutorial - it has been fixed in the latest revision of the Google Code Repository. Make sure to update if you’ve been using it!

Friday, September 12th, 2008 buildingbrowsergames, code, php Comments

Saving Database Space through Bit-masking

This is a trick you can use to increase the efficiency and readability of your project. It is an argument for good up front design as utilizing this is only plausible when you take the time and effort at the beginning. The following is a real world example from my game TerraTanks.

The problem is that you have an object with a ton of properties that are incredibly similar and they describe the object in a yes|no fashion. In my case, players can do 24 types of research and the state of the player is “yes, I have done that particular research” or “no, I have not done that research”.

One solution is to make a table with a column that associates with the player id and a boolean column for every type of research that you have. Now if you have 24 types of research your table is 25 columns big. This can get out of hand pretty quickly. The table becomes hard to read and you have to use different code (or procedurally dynamic code) to set individual columns.

Another solution is to add a column to your player definition table and make it type INT UNSIGNED. Then you let your code efficiently handle interpreting the integer as the player’s research definition through bit masking. Here’s how it works.

The maximum value of an unsigned INT in MySQL is 4294967295. In binary this number looks like 11111111111111111111111111111111. That is 32 1’s in a row. Each of those digits can describe a research type as ‘have’ (it is a 1) or ‘have not’ (it is a 0). Now in a global file for your code you need to define each research type as a number that is a power of 2. It would look something like this in PHP:

$g_shield_research = 1;              // in binary 001
$g_armor_piercing_research = 2;  // in binary 010
$g_mining_research = 4;             // in binary 100

Now if you want to know whether you have a particular research you would perform a bitmask operation on the integer you retrieve from your database using the & operator.

// will mask players research and return true if the mining bit is set to 1
if ($element->research & $g_mining_research)

The bit masking procedure is extremely efficient and fast and you can see how it compresses all the research information into the size of an integer. Also, if you want to know everything about a player’s research you only have to retrieve a single integer from the database.

Assigning research to a player is also very easy. Simply bitwise OR the current research integer with the set bitmask using the | operator:

$newPlayerResearch = $element->research | $g_shield_research;

You can technically add the two numbers to get the same result, but this is unsafe because if you add the research when it is already there it will throw everything off.

There are some pitfalls to using this trick. While it is easy to add another type of research just by assigning its mask to the next highest power of 2, you are limited to 32 total research types. One way to get around this is to make the column type BIGINT which would give you 64 bits to work with, but at the end of the day you are still limited. Also, once a game starts the research you choose for that bit position is pretty much stuck there unless you want to do some math maintenance.

While this trick will tend to make your code more readable because database statements won’t be as long, your database entry will not be human readable so it could slow down your debugging efforts.

So there you have it. A very powerful tool if used wisely. Please design well before you start writing code. It makes life easier.

Thursday, September 4th, 2008 SQL, database, optimization, php Comments

Building Browsergames: now on Google Code!

If you’ve been following along with our tutorial at all, you may have noticed that our code files tend to…evolve over time. Templates go from being simple list-of-links affairs to being filled with loops and conditionals and all kinds of other goodies.

Today, I have news for you that will make it much easier to follow the different versions that our tutorial goes through - it’s now under source control! With the help of John Munsch, the Building Browsergames tutorial is now on Google Code. You can take a look at the project by visiting http://code.google.com/p/building-browsergames-tutorial/. You can check out the latest version of the entire tutorial’s codebase by issuing this command:

svn checkout http://building-browsergames-tutorial.googlecode.com/svn/trunk building-browsergames-tutorial-read-only

Which will retrieve the latest version(in all languages) and store it into a directory called building-browsergames-tutorial-read-only. If you’d like to check out the latest version of the code for a specific language, you can use this command:

svn checkout http://building-browsergames-tutorial.googlecode.com/svn/trunk/language/pbbg buildingbrowsergames-tutorial-read-only

..Where ‘language’ is one of the languages that the tutorial has been implemented in(currently, ‘perl’, ‘php’, and ‘rubyonrails’ are available).

You can also update the code if you retrieved the latest copy and then new changes are committed by running the svn update command:

svn update

Don’t know what Subversion is, or how to use it on your system? Take a look at this introduction to Subversion screencast to learn more.

Building Browsergames: Swapping Weapons (PHP)

It was a close race, but in the end the results of our poll on whether users should dual wield their weapons or have to toggle which one was active have been decided: users will toggle which weapon is currently active. Today, we’re going to be implementing that functionality.

The ’swap weapon’ page is a fairly simple one - all we need is a template and some code to handle it(we even wrote most of it earlier, when we built our weapons shop). We’ll start off with the template, and call it equipment.tpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
<head>
	<title>Equipment Management</title>
</head>
<body>
	<h3>Current Equipment:</h3>
	<p><a href='index.php'>Back to main</a></p>
	<ul>
		<li>
			Primary Hand:
			{if $phand ne ''}
				{$phand}
				<form action='weapon-shop.php' method='post'>
					<input type='hidden' name='sell' value='phand' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Secondary Hand:
			{if $shand ne ''}
				{$shand}
				<form action='weapon-shop.php' method='post'>
					<input type='hidden' name='sell' value='shand' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
	</ul>
	<p>
		<form action='equipment.php' method='post'>
			<input type='submit' value='Swap' name='swap' />
		</form>
	</p>
</body>
</html>

This template is relatively simple, and essentially just the top parts of the weapon shop’s template - we even left in the ‘Sell’ buttons, so that users can sell their equipment straight from their equipment page. We have to make sure that the ’swap’ button has a ‘name’ attribute - that way we can check $_POST later to see if it was clicked. Next we’ll build equipment.php, which is responsible for retrieving the current weapons a user is using, and displaying this template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?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
$phand = getStat('phand',$userID);
$shand = getStat('shand',$userID);
$phand_query = sprintf("SELECT name FROM items WHERE id = %s",
				mysql_real_escape_string($phand));
$result = mysql_query($phand_query);
if($result) {
	list($phand_name) = mysql_fetch_row($result);
	$smarty->assign('phand',$phand_name);
}
$shand_query = sprintf("SELECT name FROM items WHERE id = %s",
				mysql_real_escape_string($shand));
$result = mysql_query($shand_query);
if($result) {
	list($shand_name) = mysql_fetch_row($result);
	$smarty->assign('shand',$shand_name);
}
$smarty->display('equipment.tpl');
 
?>

Essentially, all that we’re going to be adding on to this code is handling for a POST request; if something is POSTed to this page, we swap the user’s current weapons. The code is fairly simple, as you can see:

20
21
22
23
24
25
26
if($_POST) {
	setStat('phand',$userID,$shand);
	setStat('shand',$userID,$phand);
	$temp = $shand;
	$shand = $phand;	
	$phand = $temp;
}

And that’s all there is to it! We just add another link to our index page:

19
	<p><a href='equipment.php'>Equipment Management</a></p>

And we’re finished! Here’s the code for our template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
<head>
	<title>Equipment Management</title>
</head>
<body>
	<h3>Current Equipment:</h3>
	<p><a href='index.php'>Back to main</a></p>
	<ul>
		<li>
			Primary Hand:
			{if $phand ne ''}
				{$phand}
				<form action='weapon-shop.php' method='post'>
					<input type='hidden' name='sell' value='phand' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
		<li>
			Secondary Hand:
			{if $shand ne ''}
				{$shand}
				<form action='weapon-shop.php' method='post'>
					<input type='hidden' name='sell' value='shand' />
					<input type='submit' value='Sell' />
				</form>
			{else}
				None
			{/if}
		</li>
	</ul>
	<p>
		<form action='equipment.php' method='post'>
			<input type='submit' value='Swap' name='swap' />
		</form>
	</p>
</body>
</html>

And here’s the code for the file that handles the functionality behind the template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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
$phand = getStat('phand',$userID);
$shand = getStat('shand',$userID);
if($_POST) {
	setStat('phand',$userID,$shand);
	setStat('shand',$userID,$phand);
	$temp = $shand;
	$shand = $phand;	
	$phand = $temp;
}
$phand_query = sprintf("SELECT name FROM items WHERE id = %s",
				mysql_real_escape_string($phand));
$result = mysql_query($phand_query);
if($result) {
	list($phand_name) = mysql_fetch_row($result);
	$smarty->assign('phand',$phand_name);
}
$shand_query = sprintf("SELECT name FROM items WHERE id = %s",
				mysql_real_escape_string($shand));
$result = mysql_query($shand_query);
if($result) {
	list($shand_name) = mysql_fetch_row($result);
	$smarty->assign('shand',$shand_name);
}
$smarty->display('equipment.tpl');
 
?>

Tuesday, August 19th, 2008 buildingbrowsergames, code, php Comments

Building Browsergames: Buying Weapons (PHP)

Based on the results of our poll, you’ve all voted that players should carry two weapons(of any type they want), with a primary hand and a secondary hand that can both have weapons in them.

Our stats system is perfect for this - all we have to do is add another stat for players that keeps track of which weapon is in their primary hand, and which weapon is in their secondary hand.

INSERT INTO stats(display_name, short_name) VALUES ('Primary Hand Weapon','phand'),('Secondary Hand Weapon','shand');

With the new stats inserted, we can now start writing code to take advantage of them. But how will users obtain weapons to equip?

For the moment, players will get weapons at the Weapons Shop - which is what we will be building today. The weapons shop will list off a random selection of weapons that are available for users to purchase, and automatically put weapons into primary or secondary hands after a user purchases them.

In order for our items to work in a shop, however, we need to make another change - adding prices to them! We’ll add a column to our items table called ‘price’, and set it to have a default value of 10(for 10 gold coins):

ALTER TABLE  'items' ADD  'price' INT NOT NULL DEFAULT  '10';

With that finished, we can start working a little more on the actual Weapon Shop page. First off, we’ll build a template for the shop and call it weapon-shop.tpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
	<title>The Weapon Shop</title>
</head>
<body>
	<p>Welcome to the Weapon Shop.</p>
	<h3>Current Equipment:</h3>
	<ul>
		<li>Primary Hand: {if $phand ne ''}{$phand}{else}None{/if}</li>
		<li>Secondary Hand: {if $shand ne ''}{$shand}{else}None{/if}</li>
	</ul>
	<p>Below are the weapons currently available for purchase.</p>
	<ul>
		{foreach from=$weapons key=id item=i}
			<li>
				<strong>{$i.name}</strong> - <em>{$i.price} gold coins</em>
				<form action='weapon-shop.php' method='post'>
					<input type='hidden' name='weapon-id' value='{$i.id}' />
					<input type='submit' value='Buy' />
				</form>
		{/foreach}
	</ul>
</body>
</html>

As you can probably see based on our template, we will be using a foreach loop to list off all of the weapons that are currently available. We’re going to use a fairly simple SQL query to list off 5 random weapons, each time that the user visits the weapon shop:

SELECT DISTINCT(id), name, price FROM items WHERE type = 'Weapon' ORDER BY RAND() LIMIT 5;

Now that we have the SQL query we’ll be using, we need to quickly write the PHP code to use that query for our shop, inside weapon-shop.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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 = 'Weapon' ORDER BY RAND() LIMIT 5;";
$result = mysql_query($query);
$weapons = array();
while($row = mysql_fetch_assoc($result)) {
	array_push($weapons,$row);
}
$phand = getStat('phand',$userID);
$phand_query = sprintf("SELECT name FROM items WHERE id = %%s",
				mysql_real_escape_string($phand));
$