Building Browsergames: Putting it all together (Perl)

We’ve already built a registration and page, in addition to getting started with a templating system – so let’s put it all together, and start making a game out of all our individual parts.

To begin with, a quick design decision – we will not require new users to verify their e-mail address(or even supply it when they register). This is a decision that’s up to you, really – and if you really want to make users verify their address, you can always adjust the code supplied here to use the e-mail confirmation system we built earlier for it.

To start off, we need a database structure. We’ve been sort of building one organically as we walked through each of the individual parts of our game, so let’s put it all in one place:

CREATE TABLE users (
	id int NOT NULL AUTO_INCREMENT,
	username varchar(250),
	password varchar(50),
	is_admin tinyint(1) DEFAULT 0,
	PRIMARY KEY(id)
);
CREATE TABLE stats (
	id int NOT NULL AUTO_INCREMENT,
	display_name text,
	short_name varchar(10),
	PRIMARY KEY(id)
);
INSERT INTO stats(display_name,short_name) VALUES ('Magic','mag');
INSERT INTO stats(display_name,short_name) VALUES ('Attack','atk');
INSERT INTO stats(display_name,short_name) VALUES ('Defence','def');
CREATE TABLE user_stats (
	id int NOT NULL AUTO_INCREMENT,
	user_id int,
	stat_id int,
	value text,
	PRIMARY KEY(id)
);

With that done, we need to modify our registration page – so that it auto-focuses into the right textbox, remembers what was sent to it, and uses HTML::Template. Here’s what the template it will use is going to look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	<html>
	<head>
		<title>Register</title>
	</head>
	<body>
		<tmpl_if name='error'>
			<span style='color:red'>Error: <!--tmpl_var name='error'--></span>
		</tmpl_if>
		<tmpl_if name='message'>
			<span style='color:green'><!--tmpl_var name='message'--></span>
		</tmpl_if>
		<form method='post' action='register.cgi'>
			Username: <input type='text' name='username' id='username' value='<!--tmpl_var name="username"-->' /><br />
			Password: <input type='password' name='password' /><br />
			Confirm Password: <input type='password' name='confirm' /><br />
			<input type='submit' value='Register!' />
		</form>
		<script type='text/javascript'>
		document.getElementById('username').focus();
		</script>
	</body>
	</html>

We use a <tmpl_if> tag to make sure that our error and success messages only appear when there’s something to display – but other than that, you’ve seen all of the code in this template before. Save the file as register.tmpl.

The next piece of code we need to write is for the actual page that loads in the template and handles all of the database work that we need behind it. It’s pretty easy to just modify our other registration code to work with a 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
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use DBI;
use config;		# this is our database settings
use HTML::Template;
 
my $query = new CGI;
my %arguments = $query->Vars;
 
my $template = HTML::Template->new(
		filename	=>	'register.tmpl',
		associate	=>	$query,		# for argument memory
	);
my %parameters;
if(%arguments) {
	if($arguments{password} ne $arguments{confirm}) {
		$parameters{error} = 'Those passwords do not match!';
	} else {
		my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
		my $sth = $dbh->prepare("SELECT count(id) FROM users WHERE UPPER(username) = UPPER(?)");
		$sth->execute($arguments{username});
		my $count;
		$sth->bind_columns(\$count);
		$sth->fetch;
		if($count >= 1) {
			$parameters{error} = 'That username is taken.';
		} else {
			$sth = $dbh->prepare("INSERT INTO users(username,password) VALUES (?,?)");
			$sth->execute($arguments{username},crypt($arguments{password},$arguments{username}));
			$parameters{message} = 'Congratulations! You registered successfully!';
		}
	}
}
 
$template->param(%parameters);
print $query->header(),$template->output();

So now we have a registration form…but what about logging in? And stats? Players are going to need stats to play our game. We’ll just build in our flexible stats system code from earlier, as a file called stats.pm – and then any code that needs it can use it. We’ll modify it slightly so that it inserts a default value of 0 for stats that it can’t retrieve, though:

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
package stats;
use DBI;
 
sub getStat {
	my ($statName,$userID) = @_;
	use config;
	my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
	my $sth = $dbh->prepare("SELECT count(value) FROM user_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = ? OR short_name = ?) AND user_id = ?");
	$sth->execute($statName,$statName,$userID);
	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 user_stats(stat_id,user_id,value) VALUES ((SELECT id FROM stats WHERE display_name = ? OR short_name = ?),?,?)");
		$sth->execute($statName,$statName,$userID,0);
	}
	$sth = $dbh->prepare("SELECT value FROM user_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = ? OR short_name = ?) AND user_id = ?");
	$sth->execute($statName,$statName,$userID);
	my $value;
	$sth->bind_columns(\$value);
	$sth->fetch;
	return $value;
}
sub setStat {
	my ($statName,$userID,$statValue) = @_;
	use config;
	my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
	my $sth = $dbh->prepare("UPDATE user_stats SET value = ? WHERE stat_id = (SELECT id FROM stats WHERE display_name = ? OR short_name = ?) AND user_id = ?");
	$sth->execute($statValue,$statName,$statName,$userID);
}
 
1;

Now, we’ve set up our database, created our registration page, and written the code that will allow us to retrieve a user’s stats once they’ve logged in. But users can’t login yet – there isn’t a page for them to login with! So we’ll build that next, starting with the template(called login.tmpl):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	<html>
	<head>
		<title>Login</title>
	</head>
	<body>
		<tmpl_if name='error'>
			<span style='color:red'>Error: <!--tmpl_var name='error'--></span>
		</tmpl_if>
		<form action='login.cgi' method='post'>
			Username: <input type='text' name='username' id='username' value='<!--tmpl_var name="username"-->' /><br />
			Password: <input type='password' name='password' /><br />
			<input type='submit' value='Login' />
		</form>	
		<script type='text/javascript'>
		document.getElementById('username').focus();
		</script>
	</body>
	</html>

And then we’ll modify our existing login code to use 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
44
45
46
47
48
49
50
51
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use DBI;
use config;		# this is our database settings
use HTML::Template;
 
my $query = new CGI;
my %arguments = $query->Vars;
 
my $template = HTML::Template->new(
		filename	=>	'login.tmpl',
		associate	=>	$query,		# for argument memory
	);
my %parameters;
 
if(%arguments) {
	my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
	my $sth = $dbh->prepare("SELECT COUNT(id) FROM users WHERE UPPER(username) = UPPER(?) AND password = ?");
	my $count;
	$sth->execute($arguments{username},crypt($arguments{password},$arguments{username}));
	$sth->bind_columns(\$count);
	$sth->fetch;
	if($count == 1) {
		$sth = $dbh->prepare("UPDATE users SET last_login = NOW() WHERE UPPER(username) = UPPER(?) AND password = ?");
		$sth->execute($arguments{username},crypt($arguments{username},$arguments{password}));
		$sth = $dbh->prepare("SELECT is_admin FROM users WHERE UPPER(username) = UPPER(?) AND password = ?");
		my $is_admin;
		$sth->execute($arguments{username},crypt($arguments{password},$arguments{username}));
		$sth->bind_columns(\$is_admin);
		$sth->fetch;
		my $cookie = $query->cookie(
				-name	=>	'username+password',
				-value	=>	$arguments{username} . '+' . crypt($arguments{password},$arguments{username}),
				-expires	=>	'+3M',
			);
		my $uri = 'index.cgi';
		if($is_admin == 1) {
			# redirect to admin page
			$uri = 'admin.cgi';
		}
		print $query->header(-cookie=>$cookie,-location=>$uri);
	} else {
		$parameters{error} = 'That username and password combination does not match any currently in our database.';
	}
}
 
$template->param(%parameters);
print $query->header(),$template->output();

And, finally, we’ll create an ultra-simple index page(for now) that will show the user their username, and give them a link to log back out. Here’s our template, saved as index.tmpl:

1
2
3
4
5
6
7
8
9
	<html>
	<head>
		<title>Index Page</title>
	</head>
	<body>
		<p>Hello, <!--tmpl_var name='username'-->!</p>
		<p><a href='logout.cgi'>Logout</a></p>
	</body>
	</html>

And this is the code to load in and display the template, inside index.cgi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use HTML::Template;
 
my $query = new CGI;
my $cookie = $query->cookie('username+password');
 
my ($username) = split(/\+/,$cookie);
 
my $template = HTML::Template->new(
		filename	=>	'index.tmpl',
	);
$template->param(username	=>	$username);
print $query->header(), $template->output;

As the one, final thing we need to do to bring everything together, we’ll build the logout page. It’s an extremely simple page to build:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
 
my $query = new CGI;
my $logout = $query->cookie(
	-name		=>	'username+password',
	-value		=>	'',
	-expires	=>	'-3M',
);
print $query->redirect(-cookie=>$logout,-uri=>'http://google.com');

All we do in that page is set the value of the cookie to nothing, and the expiration to sometime in the past – which means it will be deleted by the user’s browser. When we print out the header to remove the cookie, we also redirect the user – in this case, to http://google.com(but you could change this to whatever you wanted – Wordpress, for example, redirects to its login page).

And that’s all there is to the basic skeleton of our game! You now have a registration page, a login page, a very sparse index page(we’ll work on that more later), and a logout page – in addition to all that, you’ve also got the code written to play with a user’s stats(which we’ll get to later). It might not seem like much, but today we’ve set up the basic framework that every single browsergame needs – including yours.

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.

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