2048 PART 2: ADDING LEADERBOARDS

Welcome back to our series of articles demonstrating how to add social and network features to an existing application. If you are reading us for the first time today (welcome by the way!), we recommend that you take a look at our first article.

ADDING HIGH SCORE SUPPORT

The 2048 initially provides high score support by persisting it into the local storage and comparing any new score to it, persisting the new one when better. Clan of the Cloud provides online high score support in the form of leaderboards. Integrating leaderboards is extremely easy, you do not even need to create it in the backoffice, just post scores to it and retrieve them as any user!

Let’s start by adding two methods in the cloudbuilder.js file: one to post a score and one fetch the high score.

CloudBuilder.prototype.fetchHighScore = function(whenDone) {  
var lb = this.clan.withGamer(this.gamerData).leaderboards();  
lb.getHighscores(this.boardName, 1, 10, function(err, scores) {  
// No score (leaderboard absent)
if (!scores || scores.score.scores.length == 0) {  
whenDone(0);  
} else {
whenDone(scores.score.scores[0].score.score);  
}
}.bind(this));
};

CloudBuilder.prototype.postScore = function(score, whenDone) {  
var lb = this.clan.withGamer(this.gamerData).leaderboards();  
lb.set(this.boardName, "hightolow", {"score": score, "info": "Game finished"}, whenDone);  
};

The first one fetches the board (whose name is stored in this.boardName) and returns either the first score (meaning the highest) or 0 if the leaderboard is absent yet (meaning that you are the first player ever). The second one posts a score on the board, creating it when needed, and arranges it by “high to low” sorting.

We’ll now have to modify the game logic in order to use these values instead of those from the local storage. They are used in GameManager.actuate. The problem with this way of doing things is that it is synchronous, and probably called too many times. We will want to make that asynchronous and handle a cache if needed.

For that, we chose to introduce a new update method, which actuates the display and checks for a potential game over state, posting the score as needed. This method will be called instead of actuate upon move.

GameManager.prototype.update = function() {  
// Game over, post our score
if (this.over || this.won) {  
// After the score is posted, update high score (updateBestScore callback)
this.cloudBuilder.postScore(this.score, this.updateBestScore);  
}
// Update display
this.actuate();  
};
The last thing we need to get in place is to fetch the best score only after being logged in, since this function requires authentication. For that, we modified the CloudBuilder.setup function over the previous article to include a completion callback. The high score is thus fetched after the setup is complete as we pass the following callback.  
GameManager.prototype.updateBestScore = function() {  
// Update display with the new high score
this.cloudBuilder.fetchHighScore(function(score) {  
this.bestScore = score;  
this.actuate();  
}.bind(this));
};

This callback is called as well when the high score has been posted as shown previously, in order to update the potentially altered high score.

DISPLAYING A PROPER LEADERBOARD

Okay, so displaying the best score is fine, but why not display the entire leaderboard since we have this function ready at our fingertips? This is very simple to do; instead of displaying the very first score, we just need to display them all along with the player names.

This actually involves mostly CSS changes, which I won’t list there. But basically we just have two div’s, one for the score and one for the player name. In cloudbuilder.js, the fetchHighScore function will be transformed into fetchHighScores, returning the list of scores as shown below.

CloudBuilder.prototype.fetchHighScores = function(whenDone) {  
this.ensureLoggedIn(function() {  
var lb = this.clan.withGamer(this.gamerData).leaderboards();  
lb.getHighscores(this.boardName, 1, this.maxScores, function(err, result) {  
this.log("Got scores", err || result);  
whenDone(result.score.scores);  
}.bind(this));
}.bind(this));
};

Notice that we now limit the maximum number of entries returned (via this.maxScores). This is also the number of entries that will be shown on the screen. Then, we need to pass this additional information to the actuate function in html_actuator.js, which will render it.

HTMLActuator.prototype.updateLeaderboards = function (scores) {  
this.leaderboardNames.innerHTML = this.leaderboardScores.innerHTML = "";  
if (scores) {  
scores.forEach(function(entry) {  
this.leaderboardNames.innerHTML += entry.profile.displayName + "<br/>";  
this.leaderboardScores.innerHTML += entry.score.score + "<br/>";  
}.bind(this));
}
};

There are a number of things that we could add easily, such as highlighting the entry of the current player on the board. With the gamer data we received when logging in, we know his gamer id, and we could just compare it to the entry.gamer_id field.

This kind of modification (along with putting the name entry on the game over screen) were done on the final versions that you can play here.