Scoreboard online

This is an interesting matter and I think everybody handle it in a different way.
Since I’ve almost finished a game I would like to discuss with you (and @Gildas especially since he showed interest in another post) the way you manage to have a scoreboard online.

Usually, as for example Gamecenter does, the system is an infinite table (maybe diveded in different tables by game mode) of names and scores

What the user see is the first ten names (which is usually never you and seldomly is obviously hacked with 9999999) and then an extract of your position (something like 34.566th) and your fellow unknown neighbours of “anonymity town” .

This IMHO doesn’t really work and especially doesn’t give the user any push to make a better score, because it’s terribly useless.

Few months ago I’ve played the beautiful SpaceChem (especially funny for a programmer, since basically it’s like a programming language with two simultaneous threads)
http://www.zachtronics.com/spacechem/

the guy who developed the game implemented a beautiful score system.
You never know who is the best of every level (it’s a game with a lot of levels), what you know is how good you are compared to everybody else.

So basically you have a graph (in this game best is always lower value) with all the score submitted (the graph axis are x=“score” and y=“number of submission”).
This is what it looks like

This system is brilliant and I’ve loved it since the first time I’ve played the game.
It pushes you to play against the average player (you wanna be better than the average don’t you) instead of competing with the best 10 (which probably is too much challenge and most of the players will never care).

So everybody feel pushed to be better. There is no showing off (which would involve just few players) but a lot of challenge and payback for your efforts (which involve the majority of players).

So I’ve tried to get the idea and develop my system based on this.

Problems:

  1. back-end (server) structure
  2. showing the score on the game (client)
  3. block users from submitting forged request to the back-end

My solution:

  1. STORE THE SCORE ON THE SERVER

the back end I’ve set up is based on two tables, but I will talk about the second on later.
the SCORE_TABLE has two fields
SCORE Uint
HITS Ubigint

everytime a score is submitted (there is a game-over in my game and with an URLRequest I call my server page) the back-end perform a query this made

"INSERT INTO score_table (score, hits) VALUES (" . mysql_escape_string($score) . ",1) ON DUPLICATE KEY UPDATE hits=hits+1"

that’s it.
what I get is a table with a row per every score (which is a number) with how many submission where made.

  1. SHOW THE SCORE ON THE CLIENT

Now the point is showing them on the client.
Since I want a graph made of a discrete number of X (columns) I have to aggregate the raw table in 25 (or any number you want) rows.

So I divide the the difference between the lowest score and the highest score in 25 ranges and get the aggregate hits on every range.

Then I give this data as json.
The client load this json and after it has just to draw the bars anyway I want.

example raw table:

score | hits
   60 | 1
   67 | 1
   99 | 1
  228 | 1
  252 | 1
  263 | 1
  274 | 1
  300 | 1
  354 | 1
  367 | 1
  375 | 1
  399 | 1
  401 | 1
  405 | 1
  425 | 1
  429 | 1
  435 | 1
  437 | 1
  500 | 1
  519 | 1
  527 | 2
  552 | 1
  597 | 1
  598 | 1
  618 | 1

example aggregate data:
(every tier is made of FROM_SCORE,TO_SCORE,HITS)
(I make two queries, one to get MAX and MIN, and the other one is query made of lots (in this case 25) SUM(IF(score >= $low and score <= $high, hits, 0)) AS s$i )

{   "top_score":1120,
 "bottom_score":60,
     "top_hits":6,
        "tiers":[[60,102,3],
                 [103,144,0],
                 [145,187,0],
                 [188,229,1],
                 [230,272,2],
                 [272,314,2],
                 [315,356,1],
                 [357,399,3],
                 [400,441,6],
                 [442,483,0],
                 [484,526,2],
                 [527,568,3],
                 [569,611,2],
                 [612,653,2],
                 [654,696,1],
                 [696,738,2],
                 [739,780,0],
                 [781,823,0],
                 [824,865,0],
                 [866,908,1],
                 [908,950,0],
                 [951,992,0],
                 [993,1035,0],
                 [1036,1077,0],
                 [1078,1120,1]]
}

example rendering on client:
I just draw the line of the local LAST SCORE and BEST SCORE in a different way to show that to the user (and push to be better)
The graphic design of this screen has to be improved (give info about the axis and their values), but it is a working prototype.

  1. DAMN CHEATERS

When you deal with online stuff you know people are going to expoit your system and try to submit crazy score or break it.
We have to defend ourselves.
So, basically the system now has a SUBMIT page (called with the score) to send a score on the online board and a AGGREGATE page (called on the statistics screen) to get the aggregated score to be showed on the client.

If I see this, as a cheater, I will try to call the SUBMIT page directly with a very high score parameter. In this case there is no proper leaderboard so it is quite pointless in the act of showing off, but anyway this could f*ck your system and make your graph quite pointless with all the score on the bottom and one little one on the top and nothing in the middle, when actually what we are looking for is a kind of gaussian curve.

How to defend?
We need a kind of authentication to check if our application sent the score (legit) and not something else (cheating).
I’ve looked around, and since I don’t want the user to be registered but rather an anonymous system, I thought the best way could be a shared secret.

basically the client and the server knows the same way to encrypt data so when a request is sent a CHECKSUM field is sent as well (based on a seed which in this case is the SCORE).

so I send the SCORE and the CHECKSUM to the SUBMIT page.
noone else knows how to make the CHECKSUM from a score so it is easy to know if it was the game and not a cheater who sent the score.

This system is dangerous because IN the game code there is written the algorithm to generate a CHECKSUM from a SCORE.
The best way is trying to obfuscate the code as best as possible (lots of guide on the internet, break every good writing code rule and lots of programming fantasy are your friend).
It is not completely safe but it works and if someone breaks it you can make it more complicated updating the server and the client.

The last problem is multiple submissions: with this system if you have a SUBMIT request for a specific score (for example 200) you have the checksum as well.
So a cheater can submit that score thousand times and the server will accept it because the checksum is right.
Solution: a token!

(The system I’ve developed follow a principle: one request only.
I don’t want the server and client to talk and talk, just one request and the job is done.)

So what is this token? Every time the client send a request to the server generate a random string and use it (with the SCORE) as a seed for the checksum.
So now we send SCORE, CHECKSUM and TOKEN.
The tokens are one use only.
The back-end has a second table who store the TOKENS.
At every succesful request the token is written in that table.
If the same request (same score, checksum and token) is sent again the checksum is right, but the token is already used and the double submission denied.


I know this post was long and maybe it contains some big ingenuities, this is why I’m sharing this with you, hoping to get your suggestion and ideas and improve myself and my ideas.

Thank you for reading everything and hopefully to join the discussion.

4 Likes

I’ve forget just another trick I use to make it complete.

To show the score on the client (which can be a phone with no internet sometimes) I keep a double cache.

First everytime I ask for the data online I store them in a variable and put a cache time of X minutes (so next time there will be no request until the cache time expires).
(obviously when you submit a score the cache time is reset so you get fresh data on the statistics)

Second every time you get data from the server I store the data in a SharedObject locally.

So, when the game open the statistics screen first look online, if there is no connection look on the cache var, and if that is empty as well it looks on the SharedObject.

This way, except the first run with no connection, the user will always see the statistics screen filled with the newest data available.

Wow.
That seems a very good way to do it !

A little bit complex for my needs, but I’ll definitively use the score + checksum + token combo.
Love the idea of getting the next token after using the previous one… but how is it done for the first recorded score of a new player ?
I guess a default “white card” token would break it all.

The graph scoreboard display is a great idea I didn’t know about, like you explained it should work well to motivate players.
In my experience you are less hacked if there’s less to win in it, so there’s a benefic side effect of the graph approach : the hacker will be less rewarded if he doesn’t see his name at the top of the pops !
So he’ll go and hack another game instead (let’s hope).

Depending on the game, you may also send other data, and verify it’s plausability.

I was going to send other stuff on my board, like the duration of the game or the number of jump-button hits, to compare it with the resultant score (all that srambled in a way, of course)…
If you get 200000 points with 2 jumps, then there’s something wrong :wink:

But with your token idea at the top of the classical score + checksum… maybe it’s not worth the shot.
And I could get a scoreboard API I could re-use on various games (classic arcade kind, no graph display for oldschool games !).

I’ll have to think about it, but thanks for this, it’s really valuable !

Thank you, it would like to be more a brainstorming than a perfect solution. But it works, and I am quite happy

The token is not retrieved from the server!
The token is randomly generated by the client and used to make the checksum. Then sent along with the seed and the checksum to allow the server to perform the same stuff and compare the checksums.

In the end the server store the client generated token to avoid other submission with same token + seed.

The only problem is the token table. It will get filled very quickly with thousands of records.
The idea is getting rid of very old token after a while to keep the table not infinite.
Let’s say we delete one month old token.
This would means that a cheater could submit again all the score after one month from submission, but only once because the token will be recorded and blocked again.
So if a cheater has to find a solution it would be more valuable to break the encryption system than trying this way.

Not a big problem in the end. Too much effort with no tangible result.

[quote]I was going to send other stuff on my board, like the duration of the game or the number of jump-button hits, to compare it with the resultant score (all that srambled in a way, of course)…
If you get 200000 points with 2 jumps, then there’s something wrong wink[/quote]

Obviously you can send everything you want more than just the score.
If the score is your seed you can add any parameter you want, it will not be validated unless the basic three-parameter-combo is not validated.

Also if you have a lot of players it could be a good idea to validate the score with additional parameters.
This is exactly what spacechem does as explained in this interesting article.
http://www.zachtronics.com/dev-post-spacechem-score-validation/
For him it is easier because the score is produced by a puzzle which follow some rules. The puzzle solution and the score are submitted and if the solution doesn’t give the score (it is run server side by the same algorithm as the game) the score is rejected.
But in his case the score production is very high. It doesn’t worth to make stuff overlycomplicated before seeing how the game will perform public.


If you like I can give you my code and you can implement it to see if it fits your needs.
I don’t want to put it on GIT, all this idea is based on obfuscation and “secrecy”, if I disclose the code it would be still difficult to break it but for sure a lot easier.

Let me know.
cheers

Well at least I’ll have to store the players’ pseudo (arcade style).
So the number of hits for every recorded score won’t work for me, nor the average statistics.

But I’d be glad to look at your code, and see how you encrypt the checksum from the seed for exemple (moreover I’ve not looked at what can be done with encryption and haxe).
I’m a bit confused by the cache solution too… I know and use sharedobjects but have no clue about how to store data only for a few minutes.

It’s been a while since I had to build a scoreboard, and last time it has been hacked several times, until I finally figured something very complicated (and not so much relevant).
There was a car to win from the game, so we had to change the rules too (a lottery among the best players).
I was a noob and it’s been a lot of stress !
This time, nothing to win except the glory of a good score :slight_smile:

I just store the data (as object) using this class of mine https://github.com/yupswing/plik/blob/master/com/akifox/plik/Data.hx
This is the permanent cache. Updated every time I get fresh score from the online server.

Then there is a volatile cache which is just a static var.

I use a Haxe.Timer.stamp() to know when I’ve got the fresh data and I just fallback on the static var until 10 minutes passed (remember Timer.stamp() is not the time, it could be, but only deltas are the same across platforms).

If the game open and there is no connection (and obviously no volatile cache) I load the shared object, which represent the last time I’ve got fresh data.

Maybe it will be clearer now, maybe I’m not good at explaining :wink:


If you send me a pvt with your email I’ll send the code to you. It will be not runnable, but it is commented and you can look at it and understand quite easily.

Ok, I get it now : time deltas.
I thought maybe I had missed some Haxe functionality… and your explanations are very clear !