Ever since the World Wide Web was created, its multimedia potential has attracted both game designers and game players. In addition, the worldwide connectivity of the Internet makes the Web a natural arena for multi-player games.
CGI programs are, for the most part, the backbone of gaming on the Web. Whether it is a two-player game of Checkers or an extensive open-ended MUD, CGI provides the capabilities required to provide on-the-fly page generation and state retention necessary to maintain a multi-player game on the Web.
The first step in creating a multi-user game for the Web is to check to see if it has already been done. The Web is a big place, and there is a good chance that someone else has done something similar to what you have planned. There is no need to re-invent the wheel (unless, of course, your wheel is better).
A quick glance at Yahoo! shows several dozen entries under the category of "Interactive Web Games" (see Figure 20.1). Only a few of these are true multi-player games, but even those that do not fit this description can be useful as a learning tool in the quest for the perfect multi-player game. Web-based games as a whole can be grouped by "multi-playerness" to gauge better how they relate to true multi-player gaming:
Figure 20.1: Yahoo!'s interactive Web games.
Figure 20.2: Lingua Center's Web Scavenger Hunt.
Figure 20.3: Welcome to Netropolis.
Figure 20.4: On the battlefield of Onslaught.
The first (and arguably the most important) part of creating a Web-based multi-player game (or any game, for that matter) is planning. This is also the part that most programmers skip or gloss over. After you have your idea for BlastMasters from Outer Space in 3D, it is very tempting to begin hacking away at it all at once with only your inspiration to guide you. Not giving in to this temptation is important, especially for games meant for the Internet. It is a waste of time to code an earth-shattering 24-bit graphics full-motion video epic only to find out that no browser on the planet can play it.
Planning is often not that difficult. A couple of pages of outline and a little research can cut your coding time in half. To begin, write down the answers to the following questions:
Now you know what you want to do, you have a vague idea of how you are going to do it, and you have an idea of the size of the project. The next step is outlining. Ask yourself what your game entails. Break it up into segments and even write pseudo-code for parts. As an example, consider one of the most basic game designs: A bunch of people in a room shooting at each other. (Remember "Tank" on the Atari 2600?)
What does this game entail?
You could even break this down further into more detailed segments, including pseudocode, along the way. The more detailed your outline is, the sooner design flaws will show up, and the quicker coding will be.
Now we must choose how the information in this game will be stored. Again, because the Web is a connectionless medium, the server forgets about the client after data has transferred. The CGI program itself is therefore responsible for "remembering" information about the player. There are many ways to do this, but the three most common (and easiest to implement) are E-Mail, Database, and Daemon.
In addition to these three options, you can make your form interface with your game in other ways. For example, you can store retrievable information about users on their browsers in the form of small packets of data called cookies. However, cookies are supported in only a small number of browsers (and in a nonstandard way among those browsers).
For our own small multi-player game (let's call it "The Cage" because it's really just a bunch of people in a cage shooting at each other), we'll use a database to store information about the players and the game. This allows us to make the game relatively fast paced and still avoid the programming complexities of an entire daemon.
Only now, after completing a good portion of the game in thought and theory, do we make the step of choosing a language in which to write the game. There are several choices available for CGI programming, all of which have strengths and weaknesses in regard to game necessities. The first thing to consider is complexity. Which languages do you know? While it never hurts to learn a new language, if you can write the program with a language you already know, it may be best to stick with that language. If you've already had classes in Visual Basic or Pascal, it's very possible that they will be able to handle the great majority of your CGI needs. Then there is speed. How fast does this really have to be? If your game is to have hundreds of simultaneous players and large computations, speed will be a factor, and a fast language such as C or C++ might be called for. In our case, there will be only 5 to 10 players at once, and the computations are minimal, so this is not really a factor. Power is another concern. Whatever type of game you are making, it will be necessary to parse text (if only to read the form input). For this reason, Perl has become, by far, the most widely used language on the Web and the one we will use to write "The Cage."
As per our earlier outline, the first thing to provide is a means
for character generation. What is a character? The character is
the human player's presence in the world of the game, and until
computers acquire the capability to be subjective, our only means
of representation of a character is by statistics. Luckily, computers
are very good at statistics. In our case, the statistics are relatively
straightforward. Each player needs a unique identifier; in this
case, we'll use an ID number. A player name is also nice for color.
Because we'll be displaying on a two-dimensional screen, a 2D
grid is natural for our playing field (although 3D or even more
is possible and worth exploring). So we need two coordinates to
place our players in the "cage." If players will be
attacking each other, we need some measure of health; call it
"hit points" for lack of originality. A mechanism for
preventing other players from interfering with a player's move
would also be helpful. We will implement that mechanism with another
stat that is a "hidden" password. Finally, we will include
a list of players who have hit the player recently, so he or she
will know who to plan that sneak attack against. For simplicity,
we will represent these
statistics in a form similar to a UNIX password file:
id:name:x position:y position:hit points:password:attackers
To avoid conflicts, before we write a new character to our database, we first have to check it against the existing players. So our first function will gather information from our database (in a file called state.dat) and store it in an associative array.
Caution |
Perl 5 is required for this example because of the use of references. Perl 5 contains many new features and is almost completely backward compatible. It is available from http://www.perl.com/perl. |
sub getinfo { # Get information about existing players.
open (P, "state.dat"); # Open player database.
my $n = 0; # Number of active players.
while (<P>) { # Read through each player.
chop; # Remove newline.
@dat = split(/:/,$_,7); # Get values from lines.
$p[$dat[0]]{'name'} = $dat[1]; # Get Name.
$p[$dat[0]]{'xpos'} = $dat[2]; # Get X Position
$p[$dat[0]]{'ypos'} = $dat[3]; # Get Y Position
$p[$dat[0]]{'pass'} = $dat[4]; # Get Password
$p[$dat[0]]{'hp'} = $dat[5]; # Get Hit Points
$p[$dat[0]]{'mes'} = $dat[6]; # Get List of Attackers
if ($p[$dat[0]]{'name'}) {$n++;} # If there was a character
# on this line, increment $n.
}
close P;
$n; # Since the last character was incremented. Decrement $n.
return $n; # Return $n. The rest of the data is stored in the
# associative references \%$p[0..n]
}
This function uses some of the most basic and common functions of Perl with no actual CGI interface. You can use functions similar to this to retrieve and store any kind of information from text files (including e-mail messages).
Now we can actually create the new character. The actual HTML form can be anything as long as it returns a field with the name name. In this function, I make use of a Perl module called cgi_head.pm. This takes input from a form and stores it in variables called $FORM{'x'} where x is the name of the input field. It also prints the correct HTML header so the browser knows to expect HTML code. There are many modules available for Perl that provide similar functionality (including CGI.pm from CPAN http://www.perl.com/perl/CPAN/).
require cgi_head; # include the cgi_head.pm module
sub new_char {
$| = 1; # Turn off buffering. Mostly for good luck.
srand; # initialize the random number generator.
while (-e "statelock") { sleep 1; } # If someone else is modifying the
# database, wait till they finish.
system("touch statelock"); # Lock the database for our use.
$n = &getinfo; # Get player information.
$xpos = int(rand(28)) + 1; # Create x and y coordinates
$ypos = int(rand(13)) + 1; # for new character.
loop:
for ($I = 0; $I <= $n; $I++) { # Look through all existing players.
# If another player has these
# coordinates, make new ones.
if (($xpos == $p[$I]{'xpos'})&&($ypos == $p[$I]{'ypos'})) {
$xpos = int(rand(28)) + 1;
$ypos = int(rand(13)) + 1;
next loop;
}
}
open (P, ">>state.dat"); # append to database.
$pass = int(rand(1000)); # Make new password.
if ($n > 0) { # A kludge to correctly increment $n if there are 0 or 1
# existing players.
$newp = $n++;
} elsif ($n eq "0") { $newp = 1; }
else { $newp = 0; }
print P "$newp:$FORM{'name'}:$xpos:$ypos:$pass:10:\n"; # make database entry.
close P;
system ("rm statelock"); # Remove lock.
print <<EOP; # Print confirmation.
<HTML><HEAD><TITLE>Welcome!</title></head><BODY>
<H2>Your character has been approved!</h2>
<FORM ACTION="begin.cgi">
<INPUT TYPE=HIDDEN NAME="name" VALUE="$FORM{'name'}">
<INPUT TYPE=HIDDEN NAME="pass" VALUE="$pass">
<INPUT TYPE=SUBMIT VALUE="Enter the Cage!">
</form></body></html>
EOP
There are two main things to note about this function. First, the "password" is just a random number out of 1000. The only purpose for this is to discourage players from forging other players' actions. The password is silently included on all transactions and matched with the password in the database. Also, the end of the script just prints out an HTML page that links to the game. The only reason this exists is to allow people to refresh the game screen by reloading the page on their browser. We could have directly loaded the game page from this CGI, but a reload would then re-create the character.
Now that we have any number of characters created, we need to design the actual game page on which to place them. Creating games on the Web is unique among all Web applications. It is not necessarily a bad thing (in fact, it can be a very good thing) to push the speed and technology limits of the Web. While you don't want your game to take 15 minutes to load, it does have to hold your player's attention, and graphics are vital to that. Additionally, while you'd like your game to be playable on as many browsers as possible, a game is the perfect showcase for the latest HTML extension or browser toy. Our example is going to be extremely Spartan, with one concession: frames. While frames have serious issues for serious pages, they bring a great advantage to game pages: the capability to display a "control panel" independent of the main display.
To accomplish this, we are actually going to have three CGI programs controlling the game: one to generate the frames, one for the control panel, and one for the game itself (the functions we've already created can be made part of the cage CGI with the use of hidden variables in the form, or they can be separate files).
The first CGI (which was referenced as begin.cgi in the new_char function) simply prints out the frames page (passing along the name and password of the player):
#!/bin/perl
require cgi_head;
$| = 1;
print <<EOP;
<HTML><HEAD><TITLE>The Cage!</title></head>
<FRAMESET ROWS="80%,20%">
<FRAME SRC="cage.cgi?name=$FORM{'name'}&pass=$FORM{'pass'}" NAME=Cage>
<FRAME SRC="con.cgi?name=$FORM{'name'}&pass=$FORM{'pass'}" NAME=Con>
</frameset>
</html>
EOP
The control panel (called "con.cgi" here) is virtually the same code.
#!/bin/perl
require cgi_head;
print <<EOP;
<HTML><HEAD><TITLE>Control Panel</title></head><BODY>
<A HREF="cage.cgi?d=up&name=$FORM{'name'}&pass=$FORM{'pass'}" TARGET=Cage>Go Up
Â</a><br>
<A HREF="cage.cgi?d=down&name=$FORM{'name'}&pass=$FORM{'pass'}" TARGET=Cage>
ÂGo Down</a><br>
<A HREF="cage.cgi?d=left&name=$FORM{'name'}&pass=$FORM{'pass'}" TARGET=Cage>
ÂGo Left</a><br>
<A HREF="cage.cgi?d=right&name=$FORM{'name'}&pass=$FORM{'pass'}" TARGET=Cage>
ÂGo Right</a><br>
To attack, click on the player you wish to attack.
</body></html>
EOP
Again note that the generated links pass the name and password with every transaction.
Finally, the heart of the game: "cage.cgi":
#!/bin/perl
require cgi_head;
require getpos; # Or include the text of the getpos function from above.
srand; # Initialize the random number generator.
$| = 1;
$name = $FORM{'name'}; # Grab name and password from the form.
$pass = $FORM{'pass'};
while ( -e "statelock") { sleep 1; } # Wait until database is free.
system ("touch statelock"); # Lock database.
$n = &getpos; # Get player information.
loop: # Find the player that initiated
for ($I = 0; $I <= $n; $I++) { # this transaction.Store their id
if ($name ne $p[$I]{'name'}) { # number in $me.
next loop;
}
$me = $I;
}
# Make sure the correct password was passed to us.
# If not, print an error message and die (rememebering to free the database.)
if ($p[$me]{'pass'} ne $pass) { print <<EOP;
<HTML><HEAD><TITLE>Sorry!</title></head><BODY>
You are not authorized to play this character.
<A HREF="index.html">Go to the entry room</a> to create a new character.<br>
</body></html>
EOP
system ("rm statelock");
die;
}
@atts = split(',',$p[$me]{'mes'}); # Make an array of the recent attackers.
if ($d = $FORM{'d'}) { # If the player is requesting movement, first
# Check to see if they're trying to move past the
# Boundries of the cage. If not, move them.
# We've chosen the cage to be 30x15, which displays
# well on most browsers.
if (($d eq "up") && ($p[$me]{'ypos'} == 1)) {
} elsif ($d eq "up") { $p[$me]{'ypos'}--; }
if (($d eq "down") && ($p[$me]{'ypos'} == 13)) {
} elsif ($d eq "down") { $p[$me]{'ypos'}++; }
if (($d eq "right") && ($p[$me]{'xpos'} == 28)) {
} elsif ($d eq "right") { $p[$me]{'xpos'}++; }
if (($d eq "left") && ($p[$me]{'xpos'} == 1)) {
} elsif ($d eq "left") { $p[$me]{'xpos'}--; }
}
if ($a = $FORM{'a'}) { # If attacking,
$I = 0; # Find id number of target
while ($p[$I]{'name'} ne $a) { $I++; } # and store it in $I.
# Now calculate the distance between the players (using the
# Pythagorean theorem.) The maximum range is (arbitrarily) sqrt(50)
# (Which is just above 7.)
$dis = abs(sqrt(abs($p[$I]{'xpos'}**2 + $p[$I]{'ypos'}**2)) -
sqrt(abs($p[$me]{'xpos'}**2 + $p[$me]{'ypos'}**2)));
# If the player are out of range, set a message.
if ($dis**2 > 50) {
$message = "$p[$I]{'name'} is out of range!<br>\n";
} else {
# If they are in range, give them a 50-50 chance of hitting.
$roll = int(rand(100)) + 1;
# If they miss, set a message.
if ( $roll < 50 ) {
$message = "You missed $p[$I]{'name'}<br>\n";
} else {
# If they hit, set a message, add the attackers name to the target's
# list of attackers, and decrement the target's hit points.
$message = "You hit!\n";
$p[$I]{'mes'} = "$me,";
$p[$I]{'hp'}-;
}
}
}
# Now we actually print the game page. The "cage" itself is done in a
# 30x15 table, bordered with '*'s. Enemies are presented by their id number
# in a link which initiates attack. The player himself is represented by
# an asterisk.
print "<HTML><HEAD><TITLE>The Cage</title></head><BODY><TABLE BORDER=0>\n";
for ($y = 0; $y < 15; $y++) {
print "<TR>\n";
for ($x = 0; $x < 30; $x++) {
for ($I = 0; $I <= $n; $I++) {
if (($x == $p[$I]{'xpos'}) && ($y == $p[$I]{'ypos'})) {
if ($I == $me) { $c = "*"; }
else {
$c = "<A HREF=\"cage.cgi?name=$p[$me]{'name'}&pass=$p[$me]{'name'}&a=$p[$I]{'name'}
Â\">$I</a>";
}
}
}
if ($c eq "") { $c = " "; }
if (($x==0)||($y==0)||($x==29)||($y==14)) { $c = "*"; }
print "<TD>$c</td>";
$c = "";
}
print "\n</tr>\n";
}
print "</table>\n";
# Now print any message that may have been set above.
if ($message) { print $message, "<BR>"; }
# Then print the names of all recent attackers.
foreach $att (@atts) {
print "$p[$att]{'name'} hit you!<br>\n";
}
# Print the player's hit points.
print "You have $p[$me]{'hp'} hit points left!<br>\n";
print "</body></html>\n";
# Now open the database and regenerate the entire thing. We must rewrite
# all players, because we may have changed another player's hit points.
open (P, ">state.dat");
for ($I = 0; $I <= $n; $I++) {
print P "$I:$p[$I]{'name'}:$p[$I]{'xpos'}:$p[$I]{'ypos'}:$p[$I]{'pass'}:$p[$I]
Â{'hp'}:$p[$I]{'mes'}\n";
}
close P;
system ("rm statelock"); # Unlock database.
And that's it. An entire multi-player Web game in less than 200 lines of Perl. Granted, it's not Mortal Kombat 5, but it has possibilities. It also has a few notable areas that could be improved:
Tip |
Flags are your friends. One good way to check this is to run the script with the -T flag in Perl. This turns on taint checking, which is designed to prevent such abuses of your script. In addition, always test Perl scripts with the -w flag before using them. This turns on Perl's warning mode that gives out helpful hints on any mistakes, or even just bad decisions, you have made. Having Perl spot goofs you might have made can save hours of debugging for mistakes you have made. |
These are just the glaring problems. Many (if not most) of the routines in the previous scripts could be made much more efficient and less naive. Also, you can make many improvements by using this game as a springboard:
Caution |
As of this writing, Mosaic (and possibly other browsers, as well) has problems with generating inline images within tables. Most other graphical browsers (including OmniWeb, Netscape, and Internet Explorer) do handle this correctly. |
This is just the beginning of what you can do with this game. Slight modifications in the idea could produce entirely new games. Perhaps there could be another database that includes coordinates of obstacles (some that block movement, some that block attacks, some that do both). Maybe a goal could be included (the player must reach a certain coordinate while preventing other players from doing the same).
In this chapter, we have used Perl exclusively to create our programs. This is not to say that it could not be done in other languages. Perl's text-handling routines are superb, but C's speed can be vital. If you are working on a Windows machine, perhaps Visual Basic or C++ is all that is available. The sample Perl scripts here are not magical; you could write equivalent programs in any CGI language-even "sh" (the exact implementation is left as an exercise for the reader).
With the tools in this chapter as a springboard, there is an entire world of possibility when using CGI to design multi-player games for the Web. Take a look at what's out there (and don't limit yourself to multi-player games, either). Plan out your game and make an outline. Test your game on as many browsers as possible, and beware of performance issues. Take care to make your game secure, especially if you are using databases.
Finally, when considering the capabilities of the Web with respect to gaming, don't ignore the non-CGI alternatives available. Java has become a popular platform for gaming because of its graphical capabilities. However, Java does come with a whole bag full of security concerns, so use it with caution. Macromedia Shockwave is another environment that allows you to be very flashy and graphical, but its multi-user capabilities are limited. Finally, Penguin, a new module for Perl, promises to enable Perl code to be executed securely on remote machines. This opens the door for Web games that maintain stable connections with servers and other players, allowing for the real-time interaction that will really bring Web multi-player games to the next level.