Building Browsergames: Buying Weapons (Perl)

The winner in our poll from earlier in regards to weapons was that players should be able to carry two weapons(of any type they want), with a primary and a secondary hand that can both have weapons in them.

Our stats system is the perfect way to implement this – we just add two more stats, so that we can keep track of which weapon is in the player’s primary hand, and which is in their secondary hand.

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

With our new stats inserted, we can write code to take advantage of them. But how will users get weapons to populate those stats with?

For now at least, players will buy weapons at the Weapon Shop – which is what we will build today. The weapon shop will display a list of weapons that are available for players to purchase, and automatically put the weapon purchased into a player’s primary or secondary hand for the player8.

In order to make our items work in a shop setting, however, we need to add something that we’re currently missing – prices! We’ll add a column to our items table called ‘price’, because every item should have a price. We’ll also set it to a default of 10(so prices are defaulted to 10 gold coins):

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

With that change made, we can start building the template for our Weapon Shop. To start off, we’ll build a template and save it as weapon-shop.tmpl:

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
<html>
<head>
	<title>The Weapon Shop</title>
</head>
<body>
	<p>Welcome to the Weapon Shop.</p>
	<h3>Current Equipment:</h3>
	<ul>
		<li>Primary Hand: <tmpl_if name='phand'><!--tmpl_var name='phand'--><tmpl_else>None</tmpl_if></li>
		<li>Secondary Hand: <tmpl_if name='shand'><!--tmpl_var name='shand'--><tmpl_else>None</tmpl_if></li>
	</ul>
	<p>Below are the weapons currently available for purchase.</p>
	<ul>
		<tmpl_loop name='weapons'>
			<li>
				<strong><!--tmpl_var name='name'--></strong> - <em><!--tmpl_var name='price'--> gold coins</em>
				<form action='weapon-shop.cgi' method='post'>
					<input type='hidden' name='weapon-id' value='<!--tmpl_var name="id"-->' />
					<input type='submit' value='Buy' />
				</form>
			</li>
		</tmpl_loop>
	</ul>
</body>
</html>

As you can see in our template, we’ll be using a tmpl_loop to list off all of the weapons that are available for users to purchase. We’re going to use a fairly simple SQL query to retrieve 5 weapons(If you’d like to see the SQL query to retrieve a random list of items, take a look at yesterday’s post on Building the Weapon Shop in PHP):

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

Now that we have written the SQL query we’ll be using to retrieve our wepaons for sale, we need to quickly write the Perl to handle the information returned by that query, inside weapon-shop.cgi:

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
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use HTML::Template;
use DBI;
use config;
 
my $query = new CGI;
my %arguments = $query->Vars;
 
my $dbh = my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
my $sth;
my %parameters;
 
use stats;
 
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;
 
$sth = $dbh->prepare("SELECT DISTINCT(id), name, price FROM items WHERE type = 'Weapon' LIMIT 5");
$sth->execute();
my @weapons = ();
while(my $row = $sth->fetchrow_hashref) {
	push @weapons, $row;
}
 
$parameters{weapons} = \@weapons;
 
my $phand = stats::getStat('phand',$userID);
$sth = $dbh->prepare("SELECT name FROM items WHERE id = ?");
$sth->execute($phand);
my $phand_name;
$sth->bind_columns(\$phand_name);
$sth->fetch;
if($phand_name) {
	$parameters{phand} = $phand_name;
}
 
my $shand = stats::getStat('shand',$userID);
# $sth is already prepared, so all we do is re-execute
$sth->execute($shand);
my $shand_name;
$sth->bind_columns(\$shand_name);
$sth->fetch;
$parameters{shand} = $shand_name if $shand_name;
my $template = HTML::Template->new(
		filename	=>	'weapon-shop.tmpl',
		associate	=>	$query,
	);
$template->param(%parameters);
print $query->header(), $template->output;

We loop through all of the data that our SQL query returns, and add the information to an array – using that array, we display information on each weapon that is available within our weapon shop. We display each weapon’s name, price, and a ‘Buy’ button that users can use to purchase the weapon in question. Now all we need to do is make it possible for users to actually buy the weapons.

As you saw in our template, there is a form inside each of our weapon entries, with a ‘Buy’ button and a hidden input that stores the ID of the weapon users might choose to buy. We will modify weapon-shop.cgi so that when users click on the ‘Buy’ button, we purchase the weapon in question for them – or display a helpful message if they can’t afford it. First off, we’ll modify our template so that it can display the messages for us:

13
14
15
16
17
18
<tmpl_if name='error'>
	<p style='color:red'><!--tmpl_var name='error'--></p>
</tmpl_if>
<tmpl_if name='message'>
	<p style='color:green'><!--tmpl_var name='message'--></p>
</tmpl_if>

With that quick modification made, we can add to our code so that we can handle users clicking on the ‘Buy’ button for specific weapons:

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
my $phand = stats::getStat('phand',$userID);
my $shand = stats::getStat('shand',$userID);
if(%arguments) {
	my $weaponID = $arguments{'weapon-id'};
	$sth = $dbh->prepare("SELECT price FROM items WHERE id = ?");
	$sth->execute($weaponID);
	my $cost;
	$sth->bind_columns(\$cost);
	$sth->fetch;
	my $gold = stats::getStat('gc',$userID);
	if($gold > $cost) {
		if(!$phand) {
			stats::setStat('phand',$userID,$weaponID);
			stats::setStat('gc',$userID,($gold - $cost));
			$phand = $weaponID;
			$parameters{message} = 'You equipped the weapon in your primary hand.';
		} else {
			if(!$shand) {
				stats::setStat('shand',$userID,$weaponID);
				stats::setStat('gc',$userID,($gold - $cost));
				$shand = $weaponID;
				$parameters{message} = 'You equipped the weapon in your secondary hand.';
			} else {
				$parameters{error} = 'You already have two weapons! You must sell one before equipping another.';
			}
		}
	} else {
		$parameters{error} = 'You cannot afford that weapon!';
	}
}

We have written some simple logic to swap the weapons for our player in this situation – if their primary hand is empty, that is where the weapon goes. If their secondary hand is empty, it goes there – and if both hands are full, a message is displayed telling the user to sell one of their weapons.

At the moment, there isn’t a way for users to sell their weapons if they want to purchase a new one – but not for long! Open up weapon-shop.tmpl again, and edit these lines:

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<li>
	Primary Hand:
	<tmpl_if name='phand'>
		<!--tmpl_var name='phand'-->
		<form action='weapon-shop.cgi' method='post'>
			<input type='hidden' name='sell' value='phand' />
			<input type='submit' value='Sell' />
		</form>
	<tmpl_else>
		None
	</tmpl_if>
</li>
<li>
	Secondary Hand:
	<tmpl_if name='shand'>
		<!--tmpl_var name='shand'-->
		<form action='weapon-shop.cgi' method='post'>
			<input type='hidden' name='sell' value='shand' />
			<input type='submit' value='Sell' />
		</form>
	<tmpl_else>
		None
	</tmpl_if>
</li>

With our template modified, we’ll switch back to editing weapon-shop.cgi, and make a small change so that we can work off of the new arguments sent to us:

30
31
32
33
34
35
36
37
38
39
40
41
42
	if($arguments{sell}) {
		my $weaponID = stats::getStat($arguments{sell},$userID);
		$sth = $dbh->prepare("SELECT price FROM items WHERE id = ?");
		$sth->execute($weaponID);
		my $cost;
		$sth->bind_columns(\$cost);
		$sth->fetch;
		my $gold = stats::getStat('gc',$userID);
		stats::setStat('gc',$userID,($gold + $cost));
		stats::setStat($arguments{sell},$userID,'');
		$shand = stats::getStat('shand',$userID);
		$phand = stats::getStat('phand',$userID);
	} else {

And with that done, users are able to buy and sell weapons at the weapon shop. There is only one change left to make – adding a link to the weapon shop in our main template:

18
	<p><a href='weapon-shop.cgi'>The Weapon Shop</a></p>

And that’s that! We’ve built a fully working weapon shop, that allows users to buy and sell weapons at will. Here’s the code from weapon-shop.cgi in it’s 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
101
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
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;
 
use stats;
 
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 $phand = stats::getStat('phand',$userID);
my $shand = stats::getStat('shand',$userID);
if(%arguments) {
	if($arguments{sell}) {
		my $weaponID = stats::getStat($arguments{sell},$userID);
		$sth = $dbh->prepare("SELECT price FROM items WHERE id = ?");
		$sth->execute($weaponID);
		my $cost;
		$sth->bind_columns(\$cost);
		$sth->fetch;
		my $gold = stats::getStat('gc',$userID);
		stats::setStat('gc',$userID,($gold + $cost));
		stats::setStat($arguments{sell},$userID,'');
		$shand = stats::getStat('shand',$userID);
		$phand = stats::getStat('phand',$userID);
	} else {
		my $weaponID = $arguments{'weapon-id'};
		$sth = $dbh->prepare("SELECT price FROM items WHERE id = ?");
		$sth->execute($weaponID);
		my $cost;
		$sth->bind_columns(\$cost);
		$sth->fetch;
		my $gold = stats::getStat('gc',$userID);
		if($gold > $cost) {
			if(!$phand) {
				stats::setStat('phand',$userID,$weaponID);
				stats::setStat('gc',$userID,($gold - $cost));
				$phand = $weaponID;
				$parameters{message} = 'You equipped the weapon in your primary hand.';
			} else {
				if(!$shand) {
					stats::setStat('shand',$userID,$weaponID);
					stats::setStat('gc',$userID,($gold - $cost));
					$shand = $weaponID;
					$parameters{message} = 'You equipped the weapon in your secondary hand.';
				} else {
					$parameters{error} = 'You already have two weapons! You must sell one before equipping another.';
				}
			}
		} else {
			$parameters{error} = 'You cannot afford that weapon!';
		}
	}
}
 
$sth = $dbh->prepare("SELECT DISTINCT(id), name, price FROM items WHERE type = 'Weapon' LIMIT 5");
$sth->execute();
my @weapons = ();
while(my $row = $sth->fetchrow_hashref) {
	push @weapons, $row;
}
 
$parameters{weapons} = \@weapons;
 
$sth = $dbh->prepare("SELECT name FROM items WHERE id = ?");
$sth->execute($phand);
my $phand_name;
$sth->bind_columns(\$phand_name);
$sth->fetch;
if($phand_name) {
	$parameters{phand} = $phand_name;
}
 
# $sth is already prepared, so all we do is re-execute
$sth->execute($shand);
my $shand_name;
$sth->bind_columns(\$shand_name);
$sth->fetch;
$parameters{shand} = $shand_name if $shand_name;
my $template = HTML::Template->new(
		filename	=>	'weapon-shop.tmpl',
		associate	=>	$query,
	);
$template->param(%parameters);
print $query->header(), $template->output;

And here’s our template file(weapon-shop.tmpl):

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
<html>
<head>
	<title>The Weapon Shop</title>
</head>
<body>
	<p>Welcome to the Weapon Shop.</p>
	<h3>Current Equipment:</h3>
	<ul>
		<li>
			Primary Hand:
			<tmpl_if name='phand'>
				<!--tmpl_var name='phand'-->
				<form action='weapon-shop.cgi' method='post'>
					<input type='hidden' name='sell' value='phand' />
					<input type='submit' value='Sell' />
				</form>
			<tmpl_else>
				None
			</tmpl_if>
		</li>
		<li>
			Secondary Hand:
			<tmpl_if name='shand'>
				<!--tmpl_var name='shand'-->
				<form action='weapon-shop.cgi' method='post'>
					<input type='hidden' name='sell' value='shand' />
					<input type='submit' value='Sell' />
				</form>
			<tmpl_else>
				None
			</tmpl_if>
		</li>
	</ul>
	<p>Below are the weapons currently available for purchase.</p>
	<tmpl_if name='error'>
		<p style='color:red'><!--tmpl_var name='error'--></p>
	</tmpl_if>
	<tmpl_if name='message'>
		<p style='color:green'><!--tmpl_var name='message'--></p>
	</tmpl_if>
	<ul>
		<tmpl_loop name='weapons'>
			<li>
				<strong><!--tmpl_var name='name'--></strong> - <em><!--tmpl_var name='price'--> gold coins</em>
				<form action='weapon-shop.cgi' method='post'>
					<input type='hidden' name='weapon-id' value='<!--tmpl_var name="id"-->' />
					<input type='submit' value='Buy' />
				</form>
			</li>
		</tmpl_loop>
	</ul>
</body>
</html>

Extra Credit

  1. Make the shop display how much gold a user has remaining after they purchase a weapon.
  2. Make the shop display only weapons that the player can afford.

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.

Tuesday, August 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