Building Browsergames: a simple combat system (Perl)

One thing that virtually all browsergames have is some sort of combat system – users enounter 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’ll need to make a few database changes – adding a few tables, and a little bit more data for our combat system to work with. We’ll start with a monsters table, for keeping track of our monsters:

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

We’ll also need to add two more stats to our game – Maximum HP, and Current HP. These two stats are fairly self-explanatory. Monsters are goingt o use the same stats as players do, using 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);

There are now three monsters within our database, complete with some basic stats – attack, defence, maximum hp, and gold. Attack, defence, and maximum HP all make sense on their own – but if you were wondering what the ‘gold’ stat is for with a monster, it’s so that we know how much gold to give the player after they manage to kill the monster. These stats(for now) will be the basis behind our combat system.

The first thing we’ll do to prepare for our combat system is modify our main page, so that it displays the user’s current HP and has a link to the ‘Forest’ page, so that the user can fight monsters. Here’s what the new index.tmpl looks like:

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, <!--tmpl_var name='username'-->!</p>
	<ul>
		<li>Attack: <strong><!--tmpl_var name='attack'--></strong></li>
		<li>Defence: <strong><!--tmpl_var name='defence'--></strong></li>
		<li>Magic: <strong><!--tmpl_var name='magic'--></strong></li>
		<li>Gold in hand: <strong><!--tmpl_var name='gold'--></strong></li>
		<li>Current HP: <strong><!--tmpl_var name='current_hp'-->/<!--tmpl_var name='maximum_hp'--></strong></li>
	</ul>	
	<p><a href='logout.cgi'>Logout</a></p>
	<p><a href='forest.cgi'>The Forest</a></p>
</body>
</html>

We also make a minor change to index.cgi, so that it retrieves and then displays the user’s current HP and maximum HP stats:

25
26
$stats{current_hp} = stats::getStat('curhp',$userID);
$stats{maximum_hp} = stats::getStat('maxhp',$userID);

Now, if you login and visit your main page, you’ll notice that both of your HP values are currently set to 0 – not exactly setting you up for success.

There are a few options available to you when introducing a new stat to your game. You could go through your database, 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 new defaults – or you could do it the easier way, and use a second stat, that exists solely to check to see if the player has had the stat’s default value set yet.

Are you wondering how that works? We’re taking advantage of the fact that our stats library returns 0 and inserts a new row for any stat we try to retrieve that doesn’t have a value yet. All we do is check to see if the ’set HP value’ stat has a value of 0 – if it does, we know that the user hasn’t had their HP set up, and we can set it. To start the process off, we’ll insert the stat into the database:

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

And then, we just quickly check the value on our main page(although you could do it on any page – just after a successful login on the login page would probably be a good spot):

25
26
27
28
29
30
31
32
33
my $setHP = stats::getStat('sethp',$userID);
if($setHP == 0) {
	# haven't set up the user's HP - set to defaults
	stats::setStat('curhp',$userID,10);
	stats::setStat('maxhp',$userID,10);
	stats::setStat('sethp',$userID,1);	
}
$stats{current_hp} = stats::getStat('curhp',$userID);
$stats{maximum_hp} = stats::getStat('maxhp',$userID);

And that code will handle setting up our player’s default HP values for us. All it does is check to see if their HP has been set – and if it isn’t, it sets it for them. It also updates the stat ’sethp’, so that we don’t accidentally reset the user’s HP values to the defaults the next time they visit the page. Now that we’ve gotten that all working, we can start building our forest page.

To start with, we’ll need a template. The template will display the monster that a player encountered, and two options – ‘Attack’, or ‘Run Away’. By clicking on ‘Attack’ users can go through a few rounds of combat with the monster, using their attack and defence stats to figure out whether the player or monster wins the fight. Clicking on ‘Run Away’ will redirect the player back to the index page. Here’s our starting template, called forest.tmpl:

<html>
<head>
	<title>The Forest</title>
</head>
<body>
	<p>You've encountered a <!--tmpl_var name='monster'-->!</p>
	<form action='forest.cgi' method='post'>
		<input type='submit' name='action' value='Attack' /> or 
		<input type='submit' name='action' value='Run Away' />
		<input type='hidden' name='monster' value='<!--tmpl_var name='monster'-->' />
	</form>
</body>
</html>

We’ve placed a hidden field called ‘monster’ in our template so that we can keep track of which monster the player is about to fight – when they click a button on this page, we’ll be able to see which monster they are about to interact with. The next thing we need to do is create forest.cgi, which is the page that will display our forest.tmpl template, and select a random monster for the player to fight.

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
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use HTML::Template;
 
my $query = new CGI;
 
use DBI;
use config;
my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
my $sth = $dbh->prepare("SELECT name FROM monsters ORDER BY RAND() LIMIT 1");
$sth->execute();
my $monster;
$sth->bind_columns(\$monster);
$sth->fetch;
my %parameters;
$parameters{monster} = $monster;
 
 
my $template = HTML::Template->new(
		filename	=>	'forest.tmpl',
	);
$template->param(%parameters);
print $query->header(), $template->output;

We haven’t made our page actually handle it when any of our buttons are clicked just yet – for now, all this code is doing is retrieving a random monster’s name and displaying it to the user.

I wanted to explain the SQL query that we’re using to retrieve the monster information, as you may not have seen it before. The ORDER BY RAND() query is the easiest way to retrieve random rows in MySQL. Instead of telling MySQL to order by a specific column, we tell it to use a generated random number to order things – meaning that the order of the rows retrieved will be random. We use the LIMIT 1 clause at the end to make sure we only retrieve one random row. If you’re so inclined, you can also use the WHERE clause to limit what rows you are going to be selecting from randomly – we’ll use that later when we add some more functionality to our game.

Because we gave both of the buttons on our page the name of ‘action’, we can easily switch off based on which one was pressed:

8
9
10
11
12
13
14
15
16
17
18
 
my $query = new CGI;
my %arguments = $query->Vars;
if(%arguments) {
	if($arguments{action} eq 'Attack') {
		# fighting the monster	
	} else {
		# running away - back to the index page!
		print $query->redirect('index.cgi');
	}	
}

In order to handle the fight with our monster, we’re going to customize our template a little bit and add an area to display the results of the fight, along with hiding the “you’ve encountered a <___>!” message. Here’s the relevant portion of our new template:

6
7
8
9
10
11
12
13
14
15
16
17
18
19
	<tmpl_unless name='combat'>
		<p>You've encountered a <!--tmpl_var name='monster'-->!</p>
		<form action='forest.cgi' method='post'>
			<input type='submit' name='action' value='Attack' /> or 
			<input type='submit' name='action' value='Run Away' />
			<input type='hidden' name='monster' value='<!--tmpl_var name='monster'-->' />
		</form>
	<tmpl_else>
		<ul>
		<tmpl_loop name='combat'>
			<li><strong><!--tmpl_var name='attacker'--></strong> attacks <!--tmpl_var name='defender'--> for <!--tmpl_var name='damage'--> damage!</li>
		</tmpl_loop>
		</ul>
	</tmpl_unless>

By customizing our template this way, we can display the results of the fight when a user decides to fight a monster, without accidentally re-displaying the ‘you encountered a monster!’ message.

If that HTML::Template code looks a little new, that’s because it is – we’ve never used the <tmpl_loop> or <tmpl_unless> tags before. <tmpl_loop> allows us to create simple(and sometimes not-so-simple) loops which we can display in our template, and the <tmpl_unless> tag says “show what’s before the <tmpl_else> if my variable doesn’t have anything in it or evaluates to false – otherwise, show what’s inside the <tmpl_else> block.

Next we need to actually add the combat logic. But before we can add that, we’ll need a way to retrieve our monster’s stats – we haven’t built anything to do that yet. We’ll quickly modify our player stats code to work off of our monster_stats table, and take slightly different arguments:

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
package monsterstats;
use DBI;
 
sub getMonsterStat {
	my ($statName,$monsterID) = @_;
	use config;
	my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
	createMonsterStatIfNotExists($statName,$monsterID);
	my $sth = $dbh->prepare("SELECT value FROM monster_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = ? OR short_name = ?) AND monster_id = ?");
	$sth->execute($statName,$statName,$monsterID);
	my $value;
	$sth->bind_columns(\$value);
	$sth->fetch;
	return $value;
}
sub createMonsterStatIfNotExists {
	my ($statName, $monsterID) = @_;	
	use config;
	my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
	my $sth = $dbh->prepare("SELECT count(value) FROM monster_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = ? OR short_name = ?) AND monster_id = ?");
	$sth->execute($statName,$statName,$monsterID);
	my $count;
	$sth->bind_columns(\$count);
	$sth->fetch;
	if($count == 0) {
		# no entry for that stat/user combination - insert one with a value of 0
		$sth = $dbh->prepare("INSERT INTO monster_stats(stat_id,monster_id,value) VALUES ((SELECT id FROM stats WHERE display_name = ? OR short_name = ?),?,?)");
		$sth->execute($statName,$statName,$monsterID,0);
	}	
}
 
1;

You can save this file as monsterstats.pm – as you can see, there’s very little changed from the code we use to retrieve player stats. Basically, we changed the function names so that there aren’t any naming collisions(and we can clearly see what stat we’re retrieving), and we’ve modified the SQL queries getting run so that they work off of the monster_stats table. We removed the setStat() function, because players aren’t supposed to be able to actually set a monster’s stats – they only need to be retrieved for combat.

Now that that’s finished, we can get back to writing our actual combat code, inside forest.cgi. First off, we’ll use our stats and monster stats code to retrieve the monster and player stats and store them into two hashes, %player and %monster. Because we’re now using a database connection in two areas of our code, I’ve also re-organized it a little bit – moving the database connection code to the top of the program. Here’s the new code:

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
my $dbh = DBI->connect("DBI:mysql:$config{dbName}:$config{dbHost}",$config{dbUser},$config{dbPass},{RaiseError => 1});
my $sth;
my %parameters;
 
if(%arguments) {
	if($arguments{action} eq 'Attack') {
		# fighting the monster	
		use stats;
		use monsterstats;
		my $cookie = $query->cookie('username+password'); 
		my ($username) = split(/\+/,$cookie);
		$sth = $dbh->prepare("SELECT id FROM users WHERE UPPER(username) = UPPER(?)");
		$sth->execute($username);
		my $userID;
		$sth->bind_columns(\$userID);
		$sth->fetch;
		my %player = (
			name		=>	$username,
			attack		=>	stats::getStat('atk',$userID),
			defence		=>	stats::getStat('def',$userID),
			curhp		=>	stats::getStat('curhp',$userID)
		);
		$sth = $dbh->prepare("SELECT id FROM monsters WHERE name = ?");
		$sth->execute($arguments{monster});
		my $monsterID;
		$sth->bind_columns(\$monsterID);
		$sth->fetch;
		my %monster = (
			name		=>	$arguments{monster},
			attack		=>	monsterstats::getMonsterStat('atk',$monsterID),
			defence		=>	monsterstats::getMonsterStat('def',$monsterID),
			curhp		=>	monsterstats::getMonsterStat('maxhp',$monsterID)
		);

What we’ve done here is retrieved the IDs of our user and our monster, and then used those in conjunction with our stats code to retrieve their stats. By doing this, we can refer to specific stats by using something like $monster{attack}, which is much easier then having a whole handful of confusingly-named variables.

Now that we’ve retrieved the stats for both the player and the monster, we can start with the actual combat in the system. It’s actually very simple, and boils down to a simple while loop:

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
		my @combat;
		my $turns = 0;
		my ($attacker,$defender);
		while($player{curhp} > 0 && $monster{curhp} > 0) {
			my %attack;
			if($turns % 2 != 0) {
				$attacker = \%monster;
				$defender = \%player;
			} else {
				$attacker = \%player;
				$defender = \%monster;
			}
			my $damage = 0;
			if($attacker->{attack} > $defender->{defence}) {
				$damage = $attacker->{attack} - $defender->{defence};	
			}
			my %attack = (
				attacker => $attacker->{name},
				defender => $defender->{name},
				damage => $damage
			);
			$defender->{curhp} -= $damage;
			push @combat, \%attack;
			$turns++;
		}
		stats::setStat('curhp',$userID,$player{curhp});
		if($player{curhp} > 0) {
			# player won
			stats::setStat('gc',$userID,stats::getStat('gc',$userID) + monsterstats::getMonsterStat('gc',$monsterID));
			$parameters{won} = 1;
			$parameters{gold} = monsterstats::getMonsterStat('gc',$monsterID);
		} else {
			# monster won	
			$parameters{lost} = 1;
		}
		$parameters{combat} = \@combat;

That code is actually much simpler than it looks – chances are, you’ve seen most of it before(although not neccessarily all in the same place). We first initialize the @combat array, which we will use to store each round of combat so that HTML::Template can display it. We also set up $turns with a value of 0 – we’ll use $turns to figure out if it’s the monster or player’s turn to attack, based on the value inside of it(for even numbers it’s the monster, and for odd numbers it’s the player). Then, we get to the actually looping part of our combat system.

To start off, we store two references to our stats hashes, based on what was inside $turns. Because our combat logic is exactly the same no matter who is attacking, all that changes are the variables – so references are perfectly suited to this situation. We then use those references to calculate the amount of damage that the attacker is going to do(using the formula damage = attack – defence), before taking that value off of our defender’s health. Finally, we put the relevant information about this attack into our temporary %attack hash, which we then push a reference of onto @combat. Our while loop continues until either the monster or the player has their current health reduced to 0.

Once one or the other has run out of health, the fight is over – we can figure out who won by checking what the current health values are. If the monster has no health remaining, the player won – and vice versa. We update the player’s current HP stat regardless of whether they won or lost, so that the value will persist accross to the next fight. If the player won the fight, we also give them the gold that our monster was carrying. There aren’t any penalties for losing the fight right now, but you’re free to experiment as you wish to in your game.

You might have noticed that we’re setting a few more template parameters inside the %parameters hash – these are flags that will be used to show the results of our combat. We also need to modify the template to react to those flags:

19
20
21
22
23
24
25
26
		<tmpl_if name='won'>
			<p>You killed <strong><!--tmpl_var name='monster'--></strong>! You gained <strong><!--tmpl_var name='gold'--></strong> gold.</p>
			<p><a href='forest.cgi'>Explore Again</a></p>
		</tmpl_if>
		<tmpl_if name='lost'>
			<p>You were killed by <strong><!--tmpl_var name='monster'--></strong>.</p>
		</tmpl_if>
		<p><a href='index.cgi'>Back to main</a></p>

We give our player a link back to the main page whether they won or not – if they won, we also give them a link to explore again in addition to showing them how much gold they gained. Because the name of the monster was sent to us through the form users submitted when they wanted to attack the monster, we can tell HTML::Template to ‘remember’ the value by using the associate argument in our call to HTML::Template’s constructor:

95
96
97
98
my $template = HTML::Template->new(
		filename	=>	'forest.tmpl',
		associate	=>	$query,
	);

And that’s all there is to our combat system! While this isn’t a complex system by any means, it’s the basics – and we can always come back and play with it more later. Here’s forest.cgi in its entirety:

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
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use HTML::Template;
use DBI;
use config;
 
my $query = new CGI;
my %arguments = $query->Vars;
 
my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
my $sth;
my %parameters;
 
if(%arguments) {
	if($arguments{action} eq 'Attack') {
		# fighting the monster	
		use stats;
		use monsterstats;
		my $cookie = $query->cookie('username+password'); 
		my ($username) = split(/\+/,$cookie);
		$sth = $dbh->prepare("SELECT id FROM users WHERE UPPER(username) = UPPER(?)");
		$sth->execute($username);
		my $userID;
		$sth->bind_columns(\$userID);
		$sth->fetch;
		my %player = (
			name		=>	$username,
			attack		=>	stats::getStat('atk',$userID),
			defence		=>	stats::getStat('def',$userID),
			curhp		=>	stats::getStat('curhp',$userID)
		);
		$sth = $dbh->prepare("SELECT id FROM monsters WHERE name = ?");
		$sth->execute($arguments{monster});
		my $monsterID;
		$sth->bind_columns(\$monsterID);
		$sth->fetch;
		my %monster = (
			name		=>	$arguments{monster},
			attack		=>	monsterstats::getMonsterStat('atk',$monsterID),
			defence		=>	monsterstats::getMonsterStat('def',$monsterID),
			curhp		=>	monsterstats::getMonsterStat('maxhp',$monsterID)
		);
		my @combat;
		my $turns = 0;
		my ($attacker,$defender);
		while($player{curhp} > 0 && $monster{curhp} > 0) {
			my %attack;
			if($turns % 2 != 0) {
				$attacker = \%monster;
				$defender = \%player;
			} else {
				$attacker = \%player;
				$defender = \%monster;
			}
			my $damage = 0;
			if($attacker->{attack} > $defender->{defence}) {
				$damage = $attacker->{attack} - $defender->{defence};	
			}
			my %attack = (
				attacker => $attacker->{name},
				defender => $defender->{name},
				damage => $damage
			);
			$defender->{curhp} -= $damage;
			push @combat, \%attack;
			$turns++;
		}
		stats::setStat('curhp',$userID,$player{curhp});
		if($player{curhp} > 0) {
			# player won
			stats::setStat('gc',$userID,stats::getStat('gc',$userID) + monsterstats::getMonsterStat('gc',$monsterID));
			$parameters{won} = 1;
			$parameters{gold} = monsterstats::getMonsterStat('gc',$monsterID);
		} else {
			# monster won	
			$parameters{lost} = 1;
		}
		$parameters{combat} = \@combat;
	} else {
		# running away - back to the index page!
		print $query->redirect('index.cgi');
	}	
} else {
	$sth = $dbh->prepare("SELECT name FROM monsters ORDER BY RAND() LIMIT 1");
	$sth->execute();
	my $monster;
	$sth->bind_columns(\$monster);
	$sth->fetch;
	$parameters{monster} = $monster;
}
 
 
my $template = HTML::Template->new(
		filename	=>	'forest.tmpl',
		associate	=>	$query,
	);
$template->param(%parameters);
print $query->header(), $template->output;

And here’s forest.tmpl, the template file that we used for our forest page:

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
<html>
<head>
	<title>The Forest</title>
</head>
<body>
	<tmpl_unless name='combat'>
		<p>You've encountered a <!--tmpl_var name='monster'-->!</p>
		<form action='forest.cgi' method='post'>
			<input type='submit' name='action' value='Attack' /> or 
			<input type='submit' name='action' value='Run Away' />
			<input type='hidden' name='monster' value='<!--tmpl_var name='monster'-->' />
		</form>
	<tmpl_else>
		<ul>
		<tmpl_loop name='combat'>
			<li><strong><!--tmpl_var name='attacker'--></strong> attacks <!--tmpl_var name='defender'--> for <!--tmpl_var name='damage'--> damage!</li>
		</tmpl_loop>
		</ul>
		<tmpl_if name='won'>
			<p>You killed <strong><!--tmpl_var name='monster'--></strong>! You gained <strong><!--tmpl_var name='gold'--></strong> gold.</p>
			<p><a href='forest.cgi'>Explore Again</a></p>
		</tmpl_if>
		<tmpl_if name='lost'>
			<p>You were killed by <strong><!--tmpl_var name='monster'--></strong>.</p>
		</tmpl_if>
		<p><a href='index.cgi'>Back to main</a></p>
	</tmpl_unless>
</body>
</html>

And with that being said, our browsergame is getting close to being playable! You can take a look at the results of all we’ve built so far by first visiting the registration page, and then the login page – all of those pages are running off the exact same code as you see here.

Wish there was more?

I'm considering writing an ebook - click here.

.

Luke is the primary editor of Building Browsergames, and has written a large portion of the articles that you read here. He generally has no idea what to say when asked to write about himself in the third person.

Thursday, June 12th, 2008 buildingbrowsergames, code, perl
blog comments powered by Disqus

About

Building Browsergames is a blog about browsergames(also known as PBBG's). It's geared towards the beginner to intermediate developer who has an interest in building their own browsergame.

Sponsors

Got Something to Say?

Send an e-mail to luke@buildingbrowsergames.com, or get in touch through Twitter at http://twitter.com/bbrowsergames