Global High Score Lists
Games have had high score lists for a very long time. In fact, for many past games high score lists were the main reason to keep playing. While this idea worked well for arcade games where strangers could compete asynchronously against each-other it didn’t initially translate to console and other single player games. Clearly, it’s not as much fun to beat your past high score as it is to beat someone else’s. That’s where the idea of a global high score list comes in; The high score list lives on a server instead of next to the game code. This is a simple way to re-ignite competition between players and extend a game’s replay value.
Obviously, placing the high score list on a server introduces some new complications. Where before you could simply read and write to a local file you now must perform some sort of web service call! Don’t worry, while implementing a client-server model in your next game may sound challenging it’s really straightforward. I’ll show you how below.
Server
First let’s look at the server side of the equation. I’m going to implement a very simple system which spans two web pages and operates solely via HTTP. I’m going to assume the following:
- You have access to a running server (can be local)
- You are able (or willing to learn) to read PHP
Don’t worry the code is incredibly straightforward in this example, so it
should be easy to understand if you are familiar with Java. To start,
create a file post.php. Your game will call this page when trying to
save a score. It should return “success” when it writes the score and
“failure” when it doesn’t.
<?php
/** Read in a username and score and save them to a file. */
// read variables from POST data.
$username = @$_POST['name'];
$score = @$_POST['score'];
// verify the username is set and not empty
if (!isset($username) || $username == "") {
echo "failure";
exit;
}
// verify the score is set and a number
if (!isset($score) || !is_numeric($score)) {
echo "failure";
exit;
}
// format the username and score as a comma delimited row
$entry = $username . "," . $score . "\n";
// append entry to the score file
if (!file_put_contents("scores.csv", $entry, FILE_APPEND)) {
echo "failure"; // failed to write to file
exit;
}
echo "success";
?>
See, I told you it was straightforward. As you can see we’re saving
everything in a comma delimited file. If you anticipate a massive number
of scores then it might make more sense to refactor the above code to use
a database. To register a high score your game will call
http://server/post.php and pass name=foobar&score=1234 as POST data.
Next let’s create a page to retrieve a list of high scores. Start by
creating board.php. This should return some number of top scores. Since
CSV is a pretty neutral format we’ll keep using it here.
<?php
/** Return a list of top scores. */
// read variables from GET data
$num_scores = $_GET['num_scores'];
// read each line in scores.csv as a string into an array
$scores = file("scores.csv");
// define a comparator to sort items by score
function compare($s1, $s2) {
// split the strings by their delimiter
$a1 = explode(",", $s1);
$a2 = explode(",", $s2);
// compare the scores
return $a2[1] - $a1[1];
}
// sort the array of scores
usort($scores, "compare");
// output the requested number of top scores
for ($i = 0; $i < $num_scores && $i < count($scores); $i++) {
echo $scores[$i];
}
?>
So your game will call http://server/board.php?num_scores=10 to retrieve
the top ten high scores. The server will respond with a CSV file
containing each player-score pair. That’s it! Now that the server is
implemented let’s turn to the client (read game).
Client
Fortunately, things are even easier on the game side. Since we’ve been
leveraging HTTP we can simply use Java’s built in URL class! Here’s an
example call to post.php.
public static boolean post(String name, int score) {
// target URL to post to
String target = "http://server/post.php";
// data to write to the server
String content = "name=" + URLEncoder.encode(name, "US-ASCII");
content += "&score=" + URLEncoder.encode(score, "US-ASCII");
// open connection to write
URL url = new URL(target);
URLConnection c = url.openConnection();
c.setConnectionTimeout(1000); // timeout after 1 second
c.setDoOutput(true); // we're going to write to the server
DataOutputStream o = new DataOutputStream(c.getOutputStream());
// write the content
o.writeBytes(content);
o.flush();
o.close();
// read response and check for success
DataInputStream i = new DataInputStream(c.getInputStream());
String response = i.readLine();
return response.equals("success");
}
Alright, so Java doesn’t win any points for conciseness. Still I think
this is pretty straightforward. Notice that I use URLEncoder to encode
the parameters I’m sending to the server. This is a necessary step if you
intend to allow special characters to be used in, say, the user’s name.
For example, if you want to support names such as “John Doe”.
Next let’s read the scores back in from the server. This is simple after the last example:
public static String[] fetch(int num) {
// target URL to post to
String target = "http://server/board.php";
String target += "?num_scores=" + URLEncoder.encode(num, "US_ASCII");
// open connection to read
URL url = new URL(target);
URLConnection c = url.openConnection();
c.setConnectionTimeout(1000); // timeout after 1 second
DataInputStream i = new DataInputStream(c.getInputStream());
// read lines into an array and return
LinkedList<String> lines = new LinkedList<String>();
String line;
while ((line = i.readLine()) != null) {
lines.add(line);
}
return lines.toArray(new String[0]);
}
See, easy. Notice that this time we’re performing a GET request
meaning that we pass num_scores in through the url. Also note that I do
not transform the data from CSV format to something suitable for printing
on-screen. I’ll leave that as an exercise for you!
Conclusion
That’s that, you now have no good reason not to have a global high score list in your next game. If you do end up using this implementation let me know; I’m especially interested in what limitations you’ve found. Before you run off and start making the next killer shooter, though, let me leave you with one warning.
There is no security in this approach
You should worry that someone will enter in fake information or spam your
server. Right now my approach doesn’t have any sort of security in place
to prevent that. I would start by adding a password of some sort to
prevent people from calling post.php. Of course, I don’t believe it’s
possible to ship said password with your game code without compromising
it, but at least you’ll stop most people. If there’s interest I can make a
second post on this topic to look at different ways to combat spam and
cheaters.
comments
-
aschearer
-
systat
-
aschearer
-
systat
-
systat
-
aleix
-
aschearer







