php

Building Browsergames: Retrieving Items (PHP)

Yesterday, we worked through setting up the initial database structure for our items system - today, we’re going go write the code that we’ll be using to retrieve our items.

The code to retrieve our items is relatively simple - it’s just a couple of functions for retrieving information on items. We don’t even need to include anything for actually setting item information - because our players should never be in a situation where they are able to(although if you want them to in your game, you should be able to figure out how to make the change after this). We’ll start off with a simple getItem function, which will return an associative array with the item’s attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
 
function getItem($itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	$query = sprintf("SELECT name, type FROM items WHERE id = '%s'",
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	$item = mysql_fetch_assoc($result);
	$item['id']	= $itemID;
	return $item;
}
 
?>

There isn’t really anything fancy to this code - the only thing that’s different from most of our other database code is that we are using mysql_fetch_assoc instead of mysql_fetch_row to retrieve our information. By using mysql_fetch_assoc, we can store the retrieved data into an associative array, which is easier to deal with than a large collection of random variables.

We aren’t going to be building anything to take advantage of this code today, but we can at least build some testing code. To start off, run this SQL to insert an item into the database:

INSERT INTO items(name,type) VALUES ('Wooden Sword','Weapon');

And once that’s done, create a file named test.php with this code inside:

1
2
3
4
5
6
7
8
9
10
11
<?php
 
require_once 'items.php';
 
$item = getItem(1);
 
echo 'name: ' .$item['name'];
echo '<br />type: ' .$item['type'];
echo '<br />ID: ' .$item['id'];
 
?>

Once you’ve uploaded test.php to your server, if you visit it you should see the information on the Wooden Sword - which shows us that our item retrieval code works.

This is really the only ’special’ part of the item code - all that’s left now is to customize our stats code so that we can retrieve stats for a specific item:

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
function getItemStat($statName,$itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	createIfNotExistsItem($statName,$itemID);
	$query = sprintf("SELECT value FROM item_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	list($value) = mysql_fetch_row($result);
	return $value;		
}
function setItemStat($statName,$itemID,$value) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql');
	mysql_select_db($dbname);
	createIfNotExistsItem($statName,$itemID);
	$query = sprintf("UPDATE item_stats SET value = '%s' WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($value),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
}
 
function createIfNotExistsItem($statName,$itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	$query = sprintf("SELECT count(value) FROM item_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	list($count) = mysql_fetch_row($result);
	if($count == 0) {
		// the stat doesn't exist; insert it into the database
		$query = sprintf("INSERT INTO item_stats(stat_id,item_id,value) VALUES ((SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s'),'%s','%s')",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID),
		'0');
		mysql_query($query);
	}	
}

There isn’t anything at all that you haven’t seen before, here - it’s essentially our code to retrieve stats, modified so that it can retrieve stats for items.

If we want to test it, we’ll need to add a stat to our wooden sword:

INSERT INTO item_stats(item_id,stat_id,value) VALUES ((SELECT id FROM items WHERE name = 'Wooden Sword'),(SELECT id FROM stats WHERE short_name = 'atk'),2);

And then we’ll modify our testing page to retrieve the ‘atk’ stat for our sword:

10
11
12
 
$item['atk'] = getItemStat('atk',1);
echo '<br />Attack: ' . $item['atk'];

And that’s all there is to it! Once you upload both pieces of code, you’ll be able to see all of the information for the Wooden Sword. Here’s the item retrieval code all in one place:

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
<?php
 
function getItem($itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	$query = sprintf("SELECT name, type FROM items WHERE id = '%s'",
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	$item = mysql_fetch_assoc($result);
	$item['id']	= $itemID;
	return $item;
}
 
function getItemStat($statName,$itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	createIfNotExistsItem($statName,$itemID);
	$query = sprintf("SELECT value FROM item_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	list($value) = mysql_fetch_row($result);
	return $value;		
}
function setItemStat($statName,$itemID,$value) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql');
	mysql_select_db($dbname);
	createIfNotExistsItem($statName,$itemID);
	$query = sprintf("UPDATE item_stats SET value = '%s' WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($value),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
}
 
function createIfNotExistsItem($statName,$itemID) {
	include 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql:');
	mysql_select_db($dbname);
	$query = sprintf("SELECT count(value) FROM item_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND item_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID));
	$result = mysql_query($query);
	list($count) = mysql_fetch_row($result);
	if($count == 0) {
		// the stat doesn't exist; insert it into the database
		$query = sprintf("INSERT INTO item_stats(stat_id,item_id,value) VALUES ((SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s'),'%s','%s')",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($itemID),
		'0');
		mysql_query($query);
	}	
}
 
?>

And here’s the tester page code, to make sure that our items code is working:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
 
require_once 'items.php';
 
$item = getItem(1);
 
echo 'name: ' .$item['name'];
echo '<br />type: ' .$item['type'];
echo '<br />ID: ' .$item['id'];
 
$item['atk'] = getItemStat('atk',1);
echo '<br />Attack: ' . $item['atk'];
 
?>

Thursday, July 3rd, 2008 buildingbrowsergames, code, php 4 Comments

Building Browsergames: forcing users to log in (PHP)

While we’ve been building our game, we haven’t really been focusing too much on securing our game against users who haven’t logged in yet. Most of our pages rely on the fact that the user needs to be logged in to see them, and they’ll break horribly if the user isn’t. So today, we’re going to add some handling to our game that will make sure that users are logged in before they try to access something.

You might be wondering how we’re going to figure out whether a user is logged in or not. And the answer to that question is a lot simpler than you might think: we’ll just use what we already have.

Any of our pages that use our stats code have a snippet at the top of them that retrieves the current user’s User ID, so that we can interact with their stats. We can use that code as our starting point - here’s a refresher on what it looks like:

1
2
3
4
5
6
7
8
9
10
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);
$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);

All we do in that code is retrieve the username we stored into session, and then use that value in our SQL to find out what the user’s User ID is. We can easily modify that code, to do a quick check to see what was returned and redirect based on whether or not anything came back:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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);
$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);
if(!$userID) {
	// not logged in!
	header('Location: login.php');	
}
 
?>

If you save that file as login-check.php, you can now add this line to any file that you want to require a login for:

1
require_once 'login-check.php';

And if a user attempts to access the page without having logged in first, they’ll be automatically redirected to the login page. Easy!

Tuesday, June 24th, 2008 buildingbrowsergames, code, design, php, security No Comments

Building Browsergames: Healing your players (PHP)

For all that we’ve built a banking and a combat system, we need a way for users to heal themselves after combat now. So today, we’ll be building the healer page.

For once, we don’t need to add any new stats to our game - we’ll be working off of the Maximum HP and Current HP stats from earlier. What that means is that we can dig right into building our new page - starting with the link that we add to index.tpl:

17
	<p><a href='healer.php'>The Healer</a></p>

Once that’s set up, we can move on to creating the template for our healer page. There isn’t really much to it, and this is healer.tpl in its entirety:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
	<title>The Healer</title>
</head>
<body>
	<p>Welcome to the healer. You currently have <strong>{$curhp}</strong> HP out of a maximum of <strong>{$maxhp}</strong>.</p>
	<p>You have <strong>{$gold}</strong> gold to heal yourself with, and it will cost you <strong>1 gold per HP healed</strong> to heal yourself.</p>
	{if $healed ne 0}
		<p>You have been healed for <strong>{$healed}</strong> HP.</p>
	{/if}
	<form action='healer.php' method='post'>
		<input type='text' name='amount' id='amount' /><br />
		<input type='submit' name='action' value='Heal Me' />
	</form>
	<p><a href='index.php'>Back to main</a></p>
	<script type='text/javascript'>
		document.getElementById('amount').focus();
	</script>
</body>
</html>

Just be looking at that template, chances are you can tell what we’re going to be doing with it - but I’ll let you in on the code anyways. Here’s the starter code, to load in our template and populate it with some values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
require_once 'stats.php';
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve user 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);
 
$smarty->assign('curhp',getStat('curhp',$userID));
$smarty->assign('maxhp',getStat('maxhp',$userID));
$smarty->assign('gold',getStat('gc',$userID));
 
$smarty->display('healer.tpl');
 
?>

With this code written, all we need to do is write a little bit more to handle the value that the user enters as how much they want to get healed. Just like we did for our bank page, we’re going to assume that any ‘weird’ values that the user entered just mean ‘use the maximum available’ - and either heal them to full, or heal them as much as we can with their current gold on hand. Here’s our code to handle the user inputting a value and then clicking on the ‘Heal’ button:

18
19
20
21
22
23
24
25
26
27
28
29
30
31
if($_POST) {
	$amount = $_POST['amount'];
	$gold = getStat('gc',$userID);
	$needed = getStat('maxhp',$userID) - getStat('curhp',$userID);
	if($amount > $needed || $amount == '') {
		$amount = $needed;	
	}
	if($amount > $gold) {
		$amount = $gold;	
	}
	setStat('gc',$userID,getStat('gc',$userID) - $amount);
	setStat('curhp',$userID,getStat('curhp',$userID) + $amount);
	$smarty->assign('healed',$amount);
}

If you compare this code to the code from our banking page from earlier, you might notice a slight difference when we test the amount that the user wants to heal for. That’s becaused the first check makes sure that they’re healing for, at maximum, the amount of HP that they need to be healed for. The second test makes sure that they can afford to be healed for the amount that they entered. Once the two checks are done, we heal the player for whatever amount we can, in addition to subtracting from their current gold value. We also set the $healed variable in our Smarty template, so that we can display a message to the user showing them that they’ve been healed.

And that’s how to build a healer page! Now your users can fight monsters, deposit their gold in the bank, and heal themselves when they get injured. Here’s all the code for the healer page in one spot:

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
<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
require_once 'stats.php';
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve user 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);
 
if($_POST) {
	$amount = $_POST['amount'];
	$gold = getStat('gc',$userID);
	$needed = getStat('maxhp',$userID) - getStat('curhp',$userID);
	if($amount > $needed || $amount == '') {
		$amount = $needed;	
	}
	if($amount > $gold) {
		$amount = $gold;	
	}
	setStat('gc',$userID,getStat('gc',$userID) - $amount);
	setStat('curhp',$userID,getStat('curhp',$userID) + $amount);
	$smarty->assign('healed',$amount);
}
 
$smarty->assign('curhp',getStat('curhp',$userID));
$smarty->assign('maxhp',getStat('maxhp',$userID));
$smarty->assign('gold',getStat('gc',$userID));
 
$smarty->display('healer.tpl');
 
?>

Tuesday, June 17th, 2008 buildingbrowsergames, code, php No Comments

Building Browsergames: Creating the bank (PHP)

Earlier, we built a combat system with which players can fight monsters, and gain gold when they defeat those monsters. Today, we’re going to build a bank for them to put that gold into.

To begin with, we’ll add another link to our index page - this link will go to bank.php, the page that will handle all of the logic on that page.

16
	<p><a href='bank.php'>The Bank</a></p>

And now that the link is there, we’re going to actually create the page. As usual, we’ll start off with a basic template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
	<title>The Bank</title>
</head>
<body>
	<p>Welcome to the bank. You currently have <strong>{$inbank}</strong> gold in the bank, and <strong>{$gold}</strong> gold in hand.</p>
	<form action='bank.php' method='post'>
		<input type='text' name='amount' /><br />
		<input type='submit' name='action' value='Deposit' /> or 
		<input type='submit' name='action' value='Withdraw' />
	</form>
	<p><a href='index.php'>Back to main</a></p>
</body>
</html>

Even though players have a gold stat, they’re missing one: gold in the bank. So we’re going to add another stat to our database, so that we can keep track of how much gold the player currently has in the bank. Here’s the SQL you’ll need to run:

INSERT INTO stats(display_name,short_name) VALUES ('Gold In Bank','bankgc');

Now that that’s finished, we’ll write the starter code for our bank page. Because the amount of gold that a player has in the bank starts at 0, we won’t need to add any special code to auto-set it to anything.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
require_once 'stats.php';
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve user 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);
 
$smarty->assign('gold',getStat('gc',$userID));
$smarty->assign('inbank',getStat('bankgc',$userID));
$smarty->display('bank.tpl');
 
?>

Now, we can start writing the logic to handle what happens when a player presses one of the action buttons. We’re going to modify our template slightly, so that it can display two different messages - one for when a user deposits some gold, and one for when a user withdraws some gold:

7
8
9
10
11
12
	{if $deposited ne 0}
		<p>You deposited <strong>{$deposited}</strong> gold into your bank account. Your total in the bank is now <strong>{$inbank}</strong>.</p>
	{/if}
	{if $withdrawn ne 0}
		<p>You withdraw <strong>{$withdrawn}</strong> gold from your bank account. Your total gold in hand is now <strong>{$gold}</strong>.</p>
	{/if}

We’re also going to make a slight modification to the area users input the amount they want to put into the bank, and make it auto-focus:

14
15
16
17
18
19
20
21
		<input type='text' name='amount' id='amount' /><br />
		<input type='submit' name='action' value='Deposit' /> or 
		<input type='submit' name='action' value='Withdraw' />
	</form>
	<p><a href='index.php'>Back to main</a></p>
	<script type='text/javascript'>
		document.getElementById('amount').focus();
	</script>

Now that that’s finished, we can get to writing the code that handles all of the interactions on this page.

One of the things that always seems to get handled differently in browsergames is what they do when you enter a value they don’t expect into a textbox - like trying to deposit 200 gold when you only have 100, or just hitting enter without entering a value at all. Some games stop and tell you that you made a mistake, before forcing you to try again. Another approach that I’ve encountered is to just assume that something ‘weird’ means ‘use the maximum’ - so the game just quietly shifts your 200 into the 100 you have, and then tells you how much it deposited. I like the second option, so that’s how I will write my logic - but in your game you’re completely free to do whatever you want(and it shouldn’t be too hard to cusomize this code to display an error message if you need to):

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$gold = getStat('gc',$userID);
if($_POST) {
	$amount = $_POST['amount'];	
	if($_POST['action'] == 'Deposit') {
		if($amount > $gold || $amount == '') {
			// the user input something weird - assume the maximum
			$amount = $gold;	
		}
		setStat('gc',$userID,getStat('gc',$userID) - $amount);
		setStat('bankgc',$userID,getStat('bankgc',$userID)+$amount);
		$smarty->assign('deposited',$amount);
	} else {
		$bankGold = getStat('bankgc',$userID);
		if($amount > $bankGold || $amount == '') {
			// the user input something weird again - again, assume the maximum
			$amount = $bankGold;
		}
		setStat('gc',$userID,getStat('gc',$userID) + $amount);
		setStat('bankgc',$userID,getStat('bankgc',$userID)-$amount);
		$smarty->assign('withdrawn',$amount);
	}
}

And that’s really all there is to handling a deposit or a withdrawal within our simple banking system. We check to see what action the user is taking, and then we perform almost exactly the same operation - the only differences are the value that we’re testing our posted amount against, and whether we’re increasing or decreasing their gold in hand vs. gold in the bank. Finally, we assign a variable in each case that will display the amount that the user either deposited or withdrew. The code is so similar I’m sure it could be implemented in a little less repetitious format using references - but I’ll leave that as an exercise for you, the reader.

Here’s all the code for the bank in a single block:

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
<?php
 
require_once 'smarty.php';
 
session_start();
 
require_once 'config.php';		// our database settings
require_once 'stats.php';
$conn = mysql_connect($dbhost,$dbuser,$dbpass)
	or die('Error connecting to mysql');
mysql_select_db($dbname);
// retrieve user 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);
 
$gold = getStat('gc',$userID);
if($_POST) {
	$amount = $_POST['amount'];	
	if($_POST['action'] == 'Deposit') {
		if($amount > $gold || $amount == '') {
			// the user input something weird - assume the maximum
			$amount = $gold;	
		}
		setStat('gc',$userID,getStat('gc',$userID) - $amount);
		setStat('bankgc',$userID,getStat('bankgc',$userID)+$amount);
		$smarty->assign('deposited',$amount);
	} else {
		$bankGold = getStat('bankgc',$userID);
		if($amount > $bankGold || $amount == '') {
			// the user input something weird again - again, assume the maximum
			$amount = $bankGold;
		}
		setStat('gc',$userID,getStat('gc',$userID) + $amount);
		setStat('bankgc',$userID,getStat('bankgc',$userID)-$amount);
		$smarty->assign('withdrawn',$amount);
	}
}
 
$smarty->assign('gold',getStat('gc',$userID));
$smarty->assign('inbank',getStat('bankgc',$userID));
$smarty->display('bank.tpl');
 
?>

Friday, June 13th, 2008 buildingbrowsergames, code, php 3 Comments

Building Browsergames: a simple combat system (PHP)

One of the things that virtually all browsergames other than mafia games have is some sort of battling system - users encounter monsters, and then they fight those monsters in order to gain gold, resources, and so on. And that’s what we’re going to build today.

To begin with, we’re going to need to make some database changes. To begin with, we’re going to add a monsters table, for keeping track of our monsters:

CREATE TABLE monsters (
	id int NOT NULL AUTO_INCREMENT,
	name text,
	PRIMARY KEY(id)
);

We’re also going to need to add two more stats to our game - Maximum HP, and Current HP. Maximum HP will be used to track the HP of users and monsters, and curent HP will be used to track a user’s HP. Monsters will use the exact same stats as players do, by way of a table we’ll create called monster_stats:

INSERT INTO stats(display_name,short_name) VALUES ('Maximum HP','maxhp');
INSERT INTO stats(display_name,short_name) VALUES ('Current HP','curhp');
CREATE TABLE monster_stats (
	id int NOT NULL AUTO_INCREMENT,
	monster_id int NOT NULL,
	stat_id int,
	value text,
	PRIMARY KEY(id)
);

And at this point, we can add a couple of monsters to our database, along with giving them some starting stats:

INSERT INTO monsters(name) VALUES ('Crazy Eric');
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Crazy Eric'),(SELECT id FROM stats WHERE short_name = 'atk'),2);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Crazy Eric'),(SELECT id FROM stats WHERE short_name = 'def'),2);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Crazy Eric'),(SELECT id FROM stats WHERE short_name = 'maxhp'),8);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Crazy Eric'),(SELECT id FROM stats WHERE short_name = 'gc'),5);
INSERT INTO monsters(name) VALUES ('Lazy Russell');
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Lazy Russell'),(SELECT id FROM stats WHERE short_name = 'atk'),1);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Lazy Russell'),(SELECT id FROM stats WHERE short_name = 'def'),0);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Lazy Russell'),(SELECT id FROM stats WHERE short_name = 'maxhp'),4);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Lazy Russell'),(SELECT id FROM stats WHERE short_name = 'gc'),20);
INSERT INTO monsters(name) VALUES ('Hard Hitting Louis');
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Hard Hitting Louis'),(SELECT id FROM stats WHERE short_name = 'atk'),4);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Hard Hitting Louis'),(SELECT id FROM stats WHERE short_name = 'def'),3);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Hard Hitting Louis'),(SELECT id FROM stats WHERE short_name = 'maxhp'),10);
	INSERT INTO monster_stats(monster_id,stat_id,value) VALUES ((SELECT id FROM monsters WHERE name = 'Hard Hitting Louis'),(SELECT id FROM stats WHERE short_name = 'gc'),5);

Now, we have three monsters within our database, along with their stats - attack, defence, Maximum HP, and gold. Attack and defence are pretty self-explanatory, and so is Maximum HP. If you’re wondering what the ‘gold’ stat is for, it’s so that we know how much gold to give the player after they manage to kill the monster. We’re going to use these four stats as the basic framework behind the combat system we’re going to build.

One of the first things that we’re going to do is modify our main page from earlier, so that it displays the user’s current HP, and has a link to the ‘Forest’ page - which is where the user will be fighting monsters. Here’s the new index.tpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
	<title>Index Page</title>
</head>
<body>
	<p>Hello, {$name}!</p>
	<ul>
		<li>Attack: <strong>{$attack}</strong></li>
		<li>Defence: <strong>{$defence}</strong></li>
		<li>Magic: <strong>{$magic}</strong></li>
		<li>Gold in hand: <strong>{$gold}</strong></li>
		<li>Current HP: <strong>{$currentHP}/{$maximumHP}</strong>
	</ul>
	<p><a href='logout.php'>Logout</a></p>
	<p><a href='forest.php'>The Forest</a></p>
</body>
</html>

And this is the minor code change we need to make to index.php, so that it retrieves and sets those values for our user:

21
22
$smarty->assign('currentHP',getStat('curhp',$userID));
$smarty->assign('maximumHP',getStat('maxhp',$userID));

Now, if you login and visit the main page for your game, you’ll notice that your HP is set to 0 - which doesn’t really set up your character for success.

There are a few options that you can use when introducing a new stat into your game. You could, if you’re so inclined, go through and hand-insert the new default values for every user who signed up before the stat was introduced, in addition to modifying your registration page to also set up the default - or you could do it the easier way, and actually introduce two stats - one to track the actual stat, and one to track whether the player has been ‘introduced’ to the stat yet - as in, had the stat’s default value set.

Are you wondering how that works? It’s actually very simple. If you think back to when we got started with our stats code, you’ll remember that we set up our stats code to insert the new row and set the stat to 0 when it encountered a situation where the user didn’t have the stat. We’re going to use that to our advantage today, by using a second stat to flag whether a particular stat has been set to the default value for a particular user.

First off, we’ll insert the stat into the database:

INSERT INTO stats(display_name,short_name) VALUES ('Set Default HP Values','sethp');

And then, it’s a simple matter of doing a quick check on our index page(or any page, really):

22
23
24
25
26
27
28
29
30
$setHP = getStat('sethp',$userID);
if($setHP == 0) {
	// haven't set up the user's HP values yet - let's set those!
	setStat('curhp',$userID,10);
	setStat('m