Building Browsergames: Creating the bank (Perl)

The other day, we built a combat system that would allow players to fight monsters, and get gold when they defeated those monsters. Today, we’re going to be building the bank for users to put their gold into.

To start off, we’ll add a link to our index page to bank.cgi, which will handle all of the logic for our users to do their banking.

 

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

And now that the link is there, we’ll create the template for that page. As usual, we’ll start off with just the basics:

 

<html>
<head>
	<title>The Bank</title>
</head>
<body>
	<p>Welcome to the bank. You currently have <strong><!--tmpl_var name='inbank'--></strong> gold in the bank, and <strong><!--tmpl_var name='gold'--></strong> gold in hand.</p>
	<form action='bank.cgi' 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.cgi'>Back to main</a></p>
</body>
</html>

Save that file as bank.tmpl.

Even though our players have a gold stat to keep track of their gold in hand, we still need to add another one: gold in the bank. We’ll add another stat to our stats table, so that we can keep track of how much gold the player currently has in the bank. Here’s the relevant SQL query:

INSERT INTO stats(display_name,short_name) VALUES (‘Gold In Bank’,’bankgc’);
And now that we’ve inserted the stat, we’ll start writing the basic code behind our banking page. Because the amount of gold that a player has in the bank starts at 0, we won’t need any special code to make sure that we default it or anything – our stats code default behavior of returning 0 will work perfectly for us in this case.

 

#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use HTML::Template;
use DBI;
use config;
use stats;
 
my $query = new CGI;
my $cookie = $query->cookie('username+password');
 
my $template = HTML::Template->new(
		filename	=>	'bank.tmpl',
	);
my %parameters;
 
my ($username) = split(/\+/,$cookie);
my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
my $sth = $dbh->prepare("SELECT id FROM users WHERE UPPER(username) = UPPER(?)");
$sth->execute($username);
my $userID;
$sth->bind_columns(\$userID);
$sth->fetch;
 
$parameters{inbank} = stats::getStat('bankgc',$userID);
$parameters{gold} = stats::getStat('gc',$userID);
 
$template->param(%parameters);
print $query->header(), $template->output;

And if you visit your bank page as it is right now, it will show you that you have no gold in the bank, along with however much you have in your character’s inventory. Now that that’s finished, we can start writing the logic to handle what happens when a player presses one of the action buttons on our page. We’re first going to customize our template slightly, so that it displays two different messages for deposits and withdrawals:

 

<tmpl_if name='deposited'>
		<p>You deposited <strong><!--tmpl_var name='deposited'--></strong> gold into your bank account. Your total in the bank is now <strong><!--tmpl_var name='inbank'--></strong>.</p>	
	</tmpl_if>
	<tmpl_if name='withdrawn'>
		<p>You withdraw <strong><!--tmpl_var name='withdrawn'--></strong> gold from your bank account. Your total gold in hand is now <strong><!--tmpl_var name='gold'--></strong>.</p>
	</tmpl_if>

We’re also going to make a small tweak to the area where users enter the amount that they want to deposit/withdraw, by adding an ID to the field and then using that ID to make it auto-focus:

 

<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.cgi'>Back to main</a></p>
	<script type='text/javascript'>
		document.getElementById('amount').focus();
	</script>

And with that finished, we can start working on the code to handle the interactions on this page.

One thing that gets handled differently in every browsergame that I come accross is how forms handle values they don’t expect – things like trying to deposit ‘wq’ gold, trying to deposit or withdraw too much or too little, or how the form handles it when a user submits the form without entering a value at all. Some games stop and tell you that you made a mistake, forcing you to try to re-enter the correct value – but one approach that I’ve encountered and prefer is assuming that a value that’s ‘weird’(unexpected) means ‘use the maximum’ – so the game will just quietly shift my ‘wq2′ into ‘100′, or the ‘200′ into ‘100′(because it’s all I have). I like this option, so that’s how I’m going to write the following logic – but you’re allowed to do whatever you want with your game, and it shouldn’t be too hard to customize the following logic to display an error message if you prefer that way.

 

my $gold = stats::getStat('gc',$userID);
my %arguments = $query->Vars;
if(%arguments) {
	my $amount = $arguments{amount};
	if($arguments{action} eq 'Deposit') {
		if($amount > $gold || $amount eq '') {
			# weird input - assume maximum
			$amount = $gold;	
		}
		stats::setStat('gc',$userID,stats::getStat('gc',$userID) - $amount);
		stats::setStat('bankgc',$userID,stats::getStat('bankgc',$userID) + $amount);
		$parameters{deposited} = $amount;	
	} else {
		my $bankGold = stats::getStat('bankgc',$userID);
		if($amount > $bankGold || $amount == '') {
			# weird input - assume maximum

 

$amount = $bankGold;	
		}
		stats::setStat('gc',$userID,stats::getStat('gc',$userID) + $amount);
		stats::setStat('bankgc',$userID,stats::getStat('bankgc',$userID) - $amount);
		$parameters{withdrawn} = $amount;
	}	
}

And with that code written, our logic is finished – there isn’t anything else to worry about in our simple banking system. We check whether the user wants to make a deposit or a withdrawal, and then we handle each case individually – although as you can see, the code is virtually the same. The code is so similar that I’m sure someone could re-implement in a less repetitious way, via a creative use of references – but I’ll leave that as an exercise for a reader.

Here’s all the banking code in one place(albeit with some slight organizational tweaks):

 

#!/usr/bin/perl -w
 
use strict;
use CGI qw(:cgi);
use HTML::Template;
use DBI;
use config;
use stats;
 
my $query = new CGI;
my %arguments = $query->Vars;
my $cookie = $query->cookie('username+password');
 
my $template = HTML::Template->new(
		filename	=>	'bank.tmpl',
	);
my %parameters;
 
my ($username) = split(/\+/,$cookie);
my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass,{RaiseError => 1});
my $sth = $dbh->prepare("SELECT id FROM users WHERE UPPER(username) = UPPER(?)");
$sth->execute($username);
my $userID;
$sth->bind_columns(\$userID);
$sth->fetch;
 
my $gold = stats::getStat('gc',$userID);
 
if(%arguments) {
	my $amount = $arguments{amount};
	if($arguments{action} eq 'Deposit') {
		if($amount > $gold || $amount eq '') {
			# weird input - assume maximum
			$amount = $gold;	
		}
		stats::setStat('gc',$userID,stats::getStat('gc',$userID) - $amount);
		stats::setStat('bankgc',$userID,stats::getStat('bankgc',$userID) + $amount);
		$parameters{deposited} = $amount;	
	} else {
		my $bankGold = stats::getStat('bankgc',$userID);
		if($amount > $bankGold || $amount == '') {
			# weird input - assume maximum
			$amount = $bankGold;	
		}
		stats::setStat('gc',$userID,stats::getStat('gc',$userID) + $amount);
		stats::setStat('bankgc',$userID,stats::getStat('bankgc',$userID) - $amount);
		$parameters{withdrawn} = $amount;
	}	
}
 
$parameters{inbank} = stats::getStat('bankgc',$userID);
$parameters{gold} = stats::getStat('gc',$userID);
 
$template->param(%parameters);
print $query->header(), $template->output;