tutorial
Sending unknown number of variable parameters to new page in HTML
Before I get started I wanted to say that I am not a web server expert so if you know of a better way to do this, post it in the comments and I will thank you as I implement your knowledge in my game.
When using PHP you eventually come to the realization that your gatekeepers for everything you do and display are HTTP and HTML. They are both great tools but were designed with a more static implementation in mind. Values passed to new web pages are set up in key->value pairs that are accessed with an associative array using the name of the parameter as the key (ie. $_POST['myParam']). This post will deal with the issue of how to send an unknown number of parameters which are user set to a new page which can then use those parameters correctly.
My particular implementation is my Mining page in my game TerraTanks. The mining page allows the user to set the type of mining on each of their planets so that if they are deficient in a particular resource, they can mine it out faster. I wanted to make sure that this could be changed globally, so the mining page will show all of your planets and each planet will have a set of radio buttons allowing you to change the mining settings. With these constraints, you cannot know how many planets the user will have and you must write your code to account for any number of parameters.
This is a simplified version of what I have:
$count = 0; // I have the result set for all my mining planets in $result while ($result && $element = mysql_fetch_object($result)) { // code displaying what I need about planet // echo "<INPUT TYPE=\"radio\" name=\"miner{$count}\" value=\"normal,$element->location\" checked>Normal"; echo "<INPUT TYPE=\"radio\" name=\"miner{$count}\" value=\"iron,$element->location\">Iron"; $count++; }
I am creating the name of the radio button group by appending the value of $count the the word miner and then incrementing count as long as there are planets. Basically, I am hacking together my own array. It should be noted that I am setting the first radio button to the checked state. The web page that will read the input depends on everything that exists having a value so it is necessary to make sure that something is selected. In your work you will want to check what the value is previously set to and make sure the correct radio button is checked when the page loads.
You may also notice that I am shoving a lot of data into the value sent appended by commas. This is a decent way of shoving related data through without having to define a ton of variables.
Now we have to create something on the receiving page that will correctly read in the values that we are sending. This is the code that I am using:
$x = 0; $indexName = "miner" . $x; while ($_POST[$indexName]) { $miner[$x] = $_POST[$indexName]; $x++; $indexName = "miner" . $x; }
If you read the code in English it roughly says, as long as there is a POST value at the index miner{$x} where $x starts at 0 and increments by one then just keep incrementing $x and saving the value into my own array. This is why it was so important that as long as you had a planet you had a value that was passed. If you set a value for planet 0, 1, and 5 but not 2, 3, or 4 then the code would stop at 2, determine there was no value and would never get to 5.
Now instead of creating a second pseudo array with hidden inputs that pointed to the location of the planet, I packed all the information into a comma appended string. When I get the value of $miner[$x] I can use the php function explode to separate out the sub values.
$minerParts[$x] = explode (",", $miner[$x]); // now $minerParts[$x][0] is the mining type and $minerParts[$x][1] is the location
So there is a way to send an unknown number of variables to a new page. Like I said, if you know of a better way make sure you post it in the comments. I don’t claim to be an expert on this subject, it is just how I resolved this problem.
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:
Extra Credit
- 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.
Making Your Forms Auto-Focus (Ruby on Rails)
Introduction
This covers the same material as this entry from Building Browsergames: Making Your Forms Auto-Focus
Why Do We Have To Do Anything Different?
The advice is excellent and it works perfectly, but it has to be tweaked ever so slightly for Rails. Rails actually generates your form fields for you and names them itself in order to easily copy an object into the fields when populating a form and then make a new object out of the form inputs afterward. As a result, the form_for command you see in a page like app/views/users/new.html.erb:
<% form_for :user, :url => users_path do |f| -%> <p><%= label_tag 'login' %><br/> <%= f.text_field :login %></p> <p><%= label_tag 'email' %><br/> <%= f.text_field :email %></p> <p><%= label_tag 'password' %><br/> <%= f.password_field :password %></p> <p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/> <%= f.password_field :password_confirmation %></p> <p><%= submit_tag 'Sign up' %></p> <% end -%>
Generates HTML that looks like this:
<form action="/users" method="post"> <div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="41adf13eec9e99506a55c427ce59052a5543cf70" /></div> <p><label for="login">Login</label><br/> <input id="user_login" name="user[login]" size="30" type="text" /></p> <p><label for="email">Email</label><br/> <input id="user_email" name="user[email]" size="30" type="text" /></p> <p><label for="password">Password</label><br/> <input id="user_password" name="user[password]" size="30" type="password" /></p> <p><label for="password_confirmation">Confirm Password</label><br/> <input id="user_password_confirmation" name="user[password_confirmation]" size="30" type="password" /></p> <p><input name="commit" type="submit" value="Sign up" /></p> </form>
So when we write our JavaScript, we need to make sure it specifies the correct element ID for the HTML that will actually be generated by Rails:
<script type="text/javascript"> window.onload = function() { document.getElementById('user_login').focus(); } </script>
user_login is the name of the field that Rails will generate for login input in our case. If you aren’t sure of the name that will be used, just go to the page in your browser and view the page source. You can see the actual label used there.
Extra Credit
- Line #3 of the HTML code above features an “authenticity_token” that Rails inserted automatically for us. Find out what it does and why you want it.
Making Your Forms Remember Their Values (Ruby on Rails)
Introduction
This entry is based on an entry of the same name from Building Browsergames: Making Your Forms Remember Their Values
I Think We’re Already There
You may have noticed from our existing forms that it was remembering things like the login and feeding it back to the form automatically whenever there was an error. That’s because form_for in your view can take an object you created in the controller and use it to fill in the various fields as it builds them for the HTML. Likewise, when a form gets submitted, it automatically takes those fields and puts them into a Ruby hash which can be directly assigned to a new object to initialize it. That natural flow from controller to view and back to another controller when the user submits makes it easy to have forms “remember” their previous values whenever an error occurs or whenever they need to edit an existing record in the database.
<% form_for :user, :url => users_path do |f| -%> <p><%= label_tag 'login' %><br/> <%= f.text_field :login %></p> <p><%= label_tag 'email' %><br/> <%= f.text_field :email %></p> <p><%= label_tag 'password' %><br/> <%= f.password_field :password %></p> <p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/> <%= f.password_field :password_confirmation %></p> <p><%= submit_tag 'Sign up' %></p> <% end -%>
Simply by specifying the text field as “f.text_field :login” pulls the username from @user (which was supplied by the new function in app/controllers/users_controller.rb) and if there is any value on @user for that field then it is used to fill in the field as the page is built. You don’t have to do any of this yourself. Just learn and follow the standard Rails conventions for these things and it will handle the rest automatically.
If you’d like to see it in action, start entering in some intentionally wrong values into the sign up form and submit it. What you will see is how you get bounced in a loop between the new and create functions in the UsersController (app/controllers/users_controller.rb) and the app/views/users/new.html.erb. As you do so, even though it is may be erroneous, what you enter in for login and email will be preserved between one submission and the next and filled back into the form for you to correct and submit again (though Rails is noticing that the destination fields are password fields and it doesn’t fill those in for security reasons).
Extra Credit
- Dizzy offers several excellent one page cheatsheets you can print out for quick reference on the topics of forms, migrations, and ActionMailer. You might want to download the PDF for the FormHelper cheatsheet and print it out for quick reference.
Getting Started With A Templating System (Ruby on Rails)
Introduction
This entry is based on the Building Browsergames entry: Getting Started With A Templating System (PHP)
Since we’re using Ruby on Rails, we’re don’t have to select a templating system, there’s one already built into Rails itself. It offers many different features to make it easy to build web pages out of parts, cache parts of pages or whole pages, etc. As with most (probably all) parts of Rails, alternatives are available to what’s built in but you’ll be satisfied with what’s already there for quite a while.
I particularly want to amplify something Luke said in the original posting. He thought a lot of developers were fans of developing their own templating systems. Wow. If that is in fact true, then you are seriously misguided if you go down that path. What’s built into Rails and the Smarty templating system Luke used for his PHP work is more than enough to get your pages done. Do not waste your time on side projects like building a templating system, use the time to improve your overall programming skills or build your game. If you do, you might be one of the few percent who actually finishes a project and others get to play it.
Using Rails Views
The design of Rails is already about separating the view which builds our HTML from the controller which connects the model to the view. So displaying the user’s name in the view is as simple as adding some code like the following to the already existing app/views/welcome/index.html.erb:
<%= render :partial => 'users/user_bar' %> <% if flash[:notice] %> <%= h flash[:notice] %> <% end %> <h1>Welcome To The Game</h1> <% if logged_in? %> <p>Hello, <%= @current_user.login %>!</p> <% end %>
It’s the same as it was before except that we’ve added the section at the end that checks to see if we’re logged in and greets us if we are. Now, as long as the controller method that is called for this page sets up a @current_user variable the view will pull the login out of it and display it within the page. The restful_authentication plugin provides us with an easy to use function to check if the user is logged in or not and put the user’s info into the @current_user variable if he/she is. We’ll call that from our controller (app/controllers/welcome_controller.rb) in the index function (remember, the flow of a user’s page request always goes to the controller first to set everything up and then to the view from there).
def index current_user end
If you recall from before, the old index method didn’t do anything. Now it calls the function which will fill in the @current_user variable before the index.html.erb file gets used to generate the view. At this point if you login you should find that everything works nicely.
By this point we’ve seen that the templating code (in the .html.erb files) gives us the ability to include partials pages to reuse sections of HTML, pull data from variables, and even conditionally show or not show parts of the page.
Extra Credit
- To learn more about the functions that restful_authentication makes available to you, look in the file lib/authenticated_system.rb. There are several functions in there to help you determine whether or not the user is logged in, restrict pages from users who aren’t yet logged in, etc.
Simple Cron
One of the questions that I seem to get asked most about building a browsergame is “how do I make things happen periodically?”.
The answer is actually very simple, and lies with a utility that comes on all Linux servers called cron.
Cron exists to run periodic, scheduled tasks for you - based on a file called a “crontab”. You can run any command you want at any time that you want using cron.
In order to edit your crontab, get access to the console of your webserver and type the following command:
crontab -e
If your server doesn’t have any cronjobs currently in place, you’ll just see an empty file. We will be editing this file to make things happen periodically.
The first five columns in your crontab correspond to measurements of time; the sixth column is the command to run. The time columns are for the hour, minute, day of the month, month, and day of week to run the command on. Here’s what a command that runs every day at 3:30 AM might look like:
30 3 * * * echo "ran cron at 3:30 AM" > /var/log/misc/crons.log
If you wanted to make something happen periodically in your game, you would just need to write the code for whatever you wanted to have happen periodically, and then write a cronjob to call it with the proper parameters.
You can also configure cronjobs to run in shorter intervals, or even at multiple intervals:
0 */3 * * * echo "this cronjob runs every 3 hours"
The above cronjob will run every 3 hours - and the one below will run only once a month:
0 0 1 * * echo "this cronjob runs every 3 hours"
If you don’t have access to your server’s command line to play around with cron, try talking to your webhost or investigating your control panel if you have one - in most cases, webhosts that won’t allow you command-line access will still allow you to create your own crontabs.
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); |
