gettingstarted

Putting It All Together (Ruby on Rails)

Introduction

This entry is based upon this entry from the Building Browsergames blog: Putting It All Together (PHP)

A Quick Recap

In past installments we provided a way for users to authenticate who they were (login, logout, signup) and have users with stats. The original article shows the SQL that was used to create the tables but we used migrations from our Rails code to do the same thing.

The example code we’ve already built is basically equivalent to what is built in the PHP entry so all we’re going to do is a few minor tweaks to match the version outlined in the article about the PHP version:

  • Our pages don’t have titles - We’ll use the layout feature of Rails to fix that.
  • Our pages don’t have any style - The PHP examples don’t have much style either, but at least they’ve got messages in green and red depending upon the type of message.

Note: One other difference is that restful_authentication logs you in immediately after registration. If the plan is not to require email confirmation, I prefer that we do it that way.

Let’s go through the changes required to make our code match the PHP version feature for feature so we’re up with the latest version.

Add Titles

We’re going to use another part of the templating engine in Rails to make it easy to have a title on every page. Rails has a concept called layouts. A layout takes the [something].html.erb files we’ve been creating and wraps them with another [something].html.erb file. It’s handy where you want a group of pages to all have the same headers, footers, navigation, etc. which, if you think about it, is most every website you go to. In order to wrap all the pages you create a layout called application.html.erb. A layout with that name in the layouts directory will be used as a default layout for the entire application. You can override the default easily if you need to though, one of the extra credit learning opportunities this week is about that. Here’s the app/views/layouts/application.html.erb I created:

<html>
  <head>
    <title><%= @title %></title>
  </head>
  <body>
    <%= yield  %>
  </body>
</html>

Now we can edit the various controllers (welcome_controller.rb, users_controller.rb, and sessions_controller.rb) adding assignments to @title in the methods that get called before we go to the view. For example:

class WelcomeController < ApplicationController
  def index
    # Call the function current_user to get the user assigned to the 
    # @current_user variable (if anyone is logged in). The
    # function comes with restful_authentication.
    current_user
 
    @title = "Welcome"
  end
end

Note that all we added here was an assignment to the @title variable in the controller action. Once the action is finished Rails will render the app/views/welcome/index.html.erb (with our new layout surrounding it) and the @title will go into the title of the page.

Add A Little Style

Styling message display is pretty easy, we just put a div tag with a class around the flash[:notice] output and we’re set. The class can then be styled any way we like:

<% if flash[:notice] %>
  <div class="flashNotice"><%= h flash[:notice] %></div>
<% end %>

To handle styling our entire game we’ll create a new file (public/stylesheets/pbbg.css). To get the stylesheet loaded we can add the following line within the head section of app/views/layouts/application.html.erb:

<%= stylesheet_link_tag "pbbg"  %>

Styling the errors that come out in the form whenever a user’s input fails validation isn’t much more complicated. If we take our registration form as an example, we can botch the password confirmation field and submit to see what we get:

<div class="errorExplanation" id="errorExplanation">
  <h2>3 errors prohibited this user from being saved</h2>
  <p>There were problems with the following fields:</p>
  <ul>
    <li>Password confirmation can't be blank</li>
    <li>Password can't be blank</li>
    <li>Password is too short (minimum is 6 characters)</li>
  </ul>
</div>
 
<form action="/users" method="post"> 
  <div style="margin:0;padding:0">
    <input name="authenticity_token" type="hidden" value="31249bde4aede2633a4391a44eafff49ec27ae17" />
  </div>
 
  <p><label for="login">Login</label><br/>
  <input id="user_login" name="user[login]" size="30" type="text" value="Testing" /></p>
 
  <p><label for="email">Email</label><br/>
  <input id="user_email" name="user[email]" size="30" type="text" value="sample@email.com" /></p>
 
  <p><label for="password">Password</label><br/>
  <div class="fieldWithErrors">
    <input id="user_password" name="user[password]" size="30" type="password" />
  </div></p>
 
  <p><label for="password_confirmation">Confirm Password</label><br/>
  <div class="fieldWithErrors">
    <input id="user_password_confirmation" name="user[password_confirmation]" size="30" type="password" />
  </div></p>
 
  <p><input name="commit" type="submit" value="Sign up" /></p>
</form>

That’s the error output and the form (with some of the error output wrapped through it) generated automatically by Rails. The thing to notice there is that Rails has inserted a couple of div tags automatically. One wraps the description of any and all errors (i.e. class=”errorExplanation”) and the other wraps individual fields which had errors (i.e. class=”fieldWithErrors”). By styling those two classes and even tags within each one (e.g. the h2 and ul tags in the explanation section) we can make the error output from Rails a little more appealing.

It probably wouldn’t take you a lot of effort to come up with better CSS to style the tags than what I present here, but at least it will give you a starting point (public/stylesheets/pbbg.css):

.errorExplanation {
  background-color:#ffcccc;
  border:1px solid red;
  vertical-align:top;
  padding: 10px;
}
 
.errorExplanation h2 {
  color: maroon;
}
 
.errorExplanation ul li {
  list-style: square;
}
 
.fieldWithErrors input[type="text"],
.fieldWithErrors input[type="password"] {
  border-color: red;
}
 
.flashNotice {
    border: 1px solid green;
    background-color: #ccffcc;
}

Here’s what the error page above looks like with that crude CSS applied to it. Note that not only is the error explanation styled, but that the fields that are in error are outlined in red:

The sign up form showing some simple CSS attributes applied to error messages and error fields.

The sign up form showing some simple CSS attributes applied to error messages and error fields.

Extra Credit

  1. You’ve already seen that you can easily have a default layout for the entire application. Watch Railscasts #7 - All About Layouts to learn about default layouts for any view in a particular controller and about how to override the default layout when you want something different for certain actions or even dynamically decide which layout to use.

Building Browsergames: now on Google Code!

If you’ve been following along with our tutorial at all, you may have noticed that our code files tend to…evolve over time. Templates go from being simple list-of-links affairs to being filled with loops and conditionals and all kinds of other goodies.

Today, I have news for you that will make it much easier to follow the different versions that our tutorial goes through - it’s now under source control! With the help of John Munsch, the Building Browsergames tutorial is now on Google Code. You can take a look at the project by visiting http://code.google.com/p/building-browsergames-tutorial/. You can check out the latest version of the entire tutorial’s codebase by issuing this command:

svn checkout http://building-browsergames-tutorial.googlecode.com/svn/trunk building-browsergames-tutorial-read-only

Which will retrieve the latest version(in all languages) and store it into a directory called building-browsergames-tutorial-read-only. If you’d like to check out the latest version of the code for a specific language, you can use this command:

svn checkout http://building-browsergames-tutorial.googlecode.com/svn/trunk/language/pbbg buildingbrowsergames-tutorial-read-only

..Where ‘language’ is one of the languages that the tutorial has been implemented in(currently, ‘perl’, ‘php’, and ‘rubyonrails’ are available).

You can also update the code if you retrieved the latest copy and then new changes are committed by running the svn update command:

svn update

Don’t know what Subversion is, or how to use it on your system? Take a look at this introduction to Subversion screencast to learn more.

Ask the readers: what’s holding you back?

Are you working on building a browsergame? Have you got all your ideas carefully sketched out and organized, so that you know exactly what to build? Have you designed your game’s database properly? Figured out what makes your game unique? Started?

It might sound surprising, but a lot of the great ideas out there never start. They get planned and planned to death, and there are dozens of ideas on how to build them, but they never actually get implemented.

Are you having this problem? What’s holding you back? Is it a lack of time? A problem making some sort of difficult decision? Trouble making up your mind in regards to a certain approach or language? A lack of resources?

What’s holding you back from building your own browsergame? Why haven’t you started?

Building Browsergames exists to try and help people who are interested in building a browsergame, but aren’t sure what step to take next - and that means that we’d like to help you as much as we can towards your goal. Send an e-mail to buildingbrowsergames@gmail.com and tell us: what’s holding you back?

Creating your games template

I talk to a lot of people who are interested in building a browsergame, but never really know where to start. I tend to recommend that they draw up their plans for what their game will be, and then come back to me once they have that - from there, they’ll be able to figure out what to build.

But there’s a nebulous step in between designing your game and actually building it, that a lot of new developers tend to miss: your game’s template.

While we’ve been working through how to build a game, we’ve been creating some very basic HTML for our templates - but nothing fancy, and definitely not what you’d want to use for a game you planned on releasing to the public.

So what’s a developer to do? Most of the developers I know couldn’t design their way out of a wet paper bag - no matter how much they may try to delude themselves otherwise. If you’re a developer reading this and you actually can design, go celebrate - you are a true rarity among developers.

Thankfully, for those of us who can’t design, there are options out there. Here’s a couple of sites that will help you out with building your template:

  • Layout Gala

    Layout Gala is one of my favorite websites for this sort of thing - 40 CSS based layouts written with valid CSS and HTML, and good cross-browser compatability. Whenever I’m building something, I tend to start with a LayoutGala layout and hack it to suit my needs. They’re all free to use, and as far as I can tell there’s no license placed on them stating you need to attribute their author - although I tend to leave a comment in my source code for anyone who’s really interested.

  • Arcsin Templates

    Arcsin Templates has a lot of neat, free templates - both for regular websites and Wordpress. The only rule is that if you use their templates for free, you need to leave the link back to their website intact - but if you’re willing to spend $20 US, you can pay for the rights to remove the link. The benefit to using an Arcsin template over one from LayoutGala is that it’s already nicely styled and graphic’d - with a LayoutGala template you’ll need to re-style it yourself, whereas an Arcsin template is ready to go right out of the box.

  • Elance

    Finally, if you’re willing to shell out a bit more cash for your design and get it done by a professional, you can use services like Elance - all you have to do is put up your project and a detailed description, and watch people bid on it - then you choose which bidder will do the project for you, and wait. While I haven’t personally tried Elance, I’ve heard a couple of raving reviews over it - much moreso than any other online outsourcing service.

These are just a few of the options available to you when looking to build your site’s template - and everyone’s preferences are different. While I prefer Layout Gala templates and then modifying them to whatever I need them to be, you might prefer something pre-built like Arcsin’s templates, or even getting something build-to-order with Elance. They’re all valid approaches, and they’ll all help you hammer out that template before you start building anything.

Monday, June 23rd, 2008 design, gettingstarted, templates 4 Comments

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.

Building Browsergames: Putting it all together (PHP)

So, we’ve built a simple registration page, and a simple login page. Now it’s time to tie it all together, so that we can use the things we’ve been working on in our game.

First off, we’re not going to require users to validate their e-mail addresses. This is a decision that’s really up to you - but for the sake of this tutorial, users won’t need to validate their e-mail. If you want to make them verify it, you can always use the e-mail confirmation system we walked you through building earlier.

But before we start writing any code, we’re going to need a database structure to work with. We’ll just work off of the structure we set up in the other tutorials:

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)
);

On the code side, we’ll start with making our registration page from earlier and make it auto-focus, remember the values sent to it, and use Smarty. This is what the Smarty template for our registration page will 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>
		{if $error ne ""}
			<span style='color:red'>Error: {$error}</span>
		{/if}
		{if $message ne ""}
			<span style='color:green'>{$message}</span>
		{/if}
		<form method='post' action='register.php'>
			Username: <input type='text' name='username' id='username' value='{$smarty.post.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 used a conditional statement to make sure that our error and success messages only display when there is actually something to display - but other than that, you’ve seen all of this before. Save that file as register.tpl, and put it into your smarty templates directory that we set up earlier.

The next thing we need to build is the actual registration page that uses the Smarty template. It’s pretty easy to just modify our other registration code to work with Smarty:

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
<?php
 
// put full path to Smarty.class.php
require('/usr/local/lib/php/Smarty/Smarty.class.php');
$smarty = new Smarty();
 
$smarty->template_dir = '/web/www.domain.com/smarty/templates';
$smarty->compile_dir = '/web/www.domain.com/smarty/templates_c';
$smarty->cache_dir = '/web/www.domain.com/smarty/cache';
$smarty->config_dir = '/web/www.domain.com/smarty/configs';
 
if($_POST) {
	$password = $_POST['password'];
	$confirm = $_POST['confirm'];
	if($password != $confirm) {
		$error = 'Passwords do not match!';	
	} else {
		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 COUNT(id) FROM users WHERE UPPER(username) = UPPER('%s')",
			mysql_real_escape_string($_POST['username']));
		$result = mysql_query($query);
		list($count) = mysql_fetch_row($result);
		if($count >= 1) { 
			$error = 'that username is taken.';
		} else {
			$query = sprintf("INSERT INTO users(username,password) VALUES ('%s','%s');",
				mysql_real_escape_string($_POST['username']),
				mysql_real_escape_string(md5($password)));
			mysql_query($query);			
			$message = 'Congratulations, you registered successfully!';
		}
	}	
}
$smarty->assign('error',$error);
$smarty->assign('message',$message);
$smarty->display('bb-register.tpl');
 
?>

And that’s our registration form, using Smarty. But we still don’t quite have any stats set up for the user - and they’re going to need some stats. Let’s take our flexible stats code from earlier, and bake it into our code. To begin with, we’ll start off with the basic stats code from before, and just add the fact that whenever a stat is requested that doesn’t exist, it creates a new row in the database for that stat for the user, and sets the stat’s value to 0:

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
function getStat($statName,$userID) {
	require_once 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql');
	mysql_select_db($dbname);
	$query = sprintf("SELECT count(value) FROM user_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND user_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($userID));
	$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 user_stats(stat_id,user_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($userID),
		'0');
		mysql_query($query);
	}
	$query = sprintf("SELECT value FROM user_stats WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND user_id = '%s'",
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($userID));
	$result = mysql_query($query);
	list($value) = mysql_fetch_row($result);
	return $value;		
}
function setStat($statName,$userID,$value) {
	require_once 'config.php';
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql');
	mysql_select_db($dbname);
	$query = sprintf("UPDATE user_stats SET value = '%s' WHERE stat_id = (SELECT id FROM stats WHERE display_name = '%s' OR short_name = '%s') AND user_id = '%s'",
		mysql_real_escape_string($value),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($statName),
		mysql_real_escape_string($userID));
	$result = mysql_query($query);
}

At this point, we’ve set up our database structure, created our registration page, and set up the stats code so that we can retrieve a user’s stats after they’ve logged in. But we still don’t have a login page! We’ll start off by creating another Smarty template, called login.tpl:

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>
		{if $error ne ""}
			<span style='color:red'>Error: {$error}</span>
		{/if}
		<form action='login.php' method='post'>
			Username: <input type='text' name='username' id='username' value='{$smarty.post.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>

Next, we’ll modify our login page to use Smarty and the template we just built:

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
<?php
 
// put full path to Smarty.class.php
require('/usr/local/lib/php/Smarty/Smarty.class.php');
$smarty = new Smarty();
 
$smarty->template_dir = '/web/www.domain.com/smarty/templates';
$smarty->compile_dir = '/web/www.domain.com/smarty/templates_c';
$smarty->cache_dir = '/web/www.domain.com/smarty/cache';
$smarty->config_dir = '/web/www.domain.com/smarty/configs';
 
session_start();
if($_POST) {
	require_once 'config.php';
	$username = $_POST['username'];
	$password = $_POST['password'];		
	$conn = mysql_connect($dbhost,$dbuser,$dbpass)
		or die ('Error connecting to mysql');
	mysql_select_db($dbname);
	$query = sprintf("SELECT COUNT(id) FROM users WHERE UPPER(username) = UPPER('%s') AND password='%s'",
		mysql_real_escape_string($username),
		mysql_real_escape_string(md5($password)));
	$result = mysql_query($query);
	list($count) = mysql_fetch_row($result);
	if($count == 1) {
		$_SESSION['authenticated'] = true;
		$_SESSION['username'] = $username;
		$query = sprintf("UPDATE users SET last_login = NOW() WHERE UPPER(username) = UPPER('%s') AND password = '%s'",
			mysql_real_escape_string($username),
			mysql_real_escape_string(md5($password)));
		mysql_query($query);
		$query = sprintf("SELECT is_admin FROM users WHERE UPPER(username) = UPPER('%s') AND password='%s'",
			mysql_real_escape_string($username),
			mysql_real_escape_string(md5($password)));
		$result = mysql_query($query);
		list($is_admin) = mysql_fetch_row($result);
		if($is_admin == 1) {
			header('Location:admin.php');			
		} else {
			header('Location:index.php');				
		}
	} else {	
		$error = 'There is no username/password combination like that in the database.';
	}
}
 
$smarty->assign('error',$error);
$smarty->display('bb-login.tpl');
?>

And finally, we’ll add an ultra-basic index page - for now, it will just show the user their current username, and give them a link to log out. Here’s what the template will look like - save it as index.tpl:

1
2
3
4
5
6
7
8
9
	<html>
	<head>
		<title>Index Page</title>
	</head>
	<body>
		<p>Hello, {$name}!</p>
		<p><a href='logout.php'>Logout</a></p>
	</body>
	</html>

And here’s the code behind our ultra-simple index page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
 
// put full path to Smarty.class.php
require('/usr/local/lib/php/Smarty/Smarty.class.php');
$smarty = new Smarty();
 
$smarty->template_dir = '/web/www.domain.com/smarty/templates';
$smarty->compile_dir = '/web/www.domain.com/smarty/templates_c';
$smarty->cache_dir = '/web/www.domain.com/smarty/cache';
$smarty->config_dir = '/web/www.domain.com/smarty/configs';
 
session_start();
$smarty->assign('name', $_SESSION['username']);
$smarty->display('index.tpl');
 
?>

And, to put the icing on the cake, let’s add our logout page. It’s pretty si