Leaderboard Service

Using the iFun Engine Leaderboard, you can easily implement various types of ranking. Also, by combining the 2 dimensions described below, you can have multiple ranking types within a single game.

  1. Competing group dimension

    • Social: particular group such as friend list, guild, etc.
    • All players
  2. Competing period dimension

    • Daily
    • Weekly
    • All-time

For example, the supporting following rankings are possible by combining the two dimensions above.

  • Ranking by weekly highest score within friends
  • Ranking by all-time high score of all users
  • Ranking by daily score between guilds
  • Ranking by daily activity score between guild members

iFun Engine-Leaderboard Agent

As with authentication and billing, ranking is also structured as an agent in iFun Engine.

Installation

When developing a game server with iFun Engine, you can set use_leaderboard to false as explained here to work in test mode. You can install later if you are evaluating or in the early stages of development and don’t need accurate ranking storage and calculation.

Install the funapi auto-configuration package as explained here and install with the following commands.

$ sudo apt-get update
$ sudo apt-get install funapi-leaderboard1

Modify to enabled=1 in the file opened with the following command.

$ sudo vim /etc/default/funapi-leaderboard

Start the service with the following command. (The log is created in /var/log/funapi/funapi-leaderboard1/) Before starting the service, see below to configure the database information. If the database configuration is incorrect, the service will not start.

$ sudo service funapi-leaderboard1 start

Configuration

Open the configuration file as below and modify the appropriate values.

$ sudo vim /usr/share/funapi-leaderboard/manifests/MANIFEST.json

Usage

You can either use the provided iFun Engine API (if developing the game server with iFun Engine) or the independent HTTP Interface (RESTful API). Refer to the following sections. The HTTP Interface will be provided in the future.

After installing the agent, you need to configure according to your tastes. This will be through MANIFEST.json as explained in the next section.

MANIFEST.json Configuration

Configure the iFun Engine-Leaderboard Agent by referring to here and the description below.

  • Component name: LeaderboardClient

  • Arguments

    • use_leaderboard: Set as true or false. If false, the ranking feature is bypassed and all ranking related requests will be responded with dummy values. This is useful for local testing environments.
    • remote_leaderboard_ip_address: Set the IP address of funapi- Leaderboard. The default value is used if not set.
    • remote_leaderboard_port: Set the port of funapi-leaderboard. The default value is used if not set.
    • reset_on: Set the ranking reset period. Values of the form “7am daily” means daily reset, “7am Monday weekly” means weekly reset, and “7am 4 monthly” means monthly reset. Keep the form of the strings, but replace the numbers and day of the week in 7am, Monday, and 4 as needed.

Example

Score Update

This is an example to update the score. Both synchronous and asynchronous methods are available.

Synchronous method

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";   // Ranking between players
// const char *kGuildGameScore = "guild_game";  // Ranking between guilds

// We assume a Facebook game as in the previous examples.
// Of course, the authentication method isn't really relevant.
void example(const string &facebook_id, int score) {
  // Create a raking update request message.
  ScoreSubmissionRequest request(
      kPlayerGameScore, "Facebook", facebook_id, score);
  ScoreSubmissionResponse response;

  // Call the synchronized version of the ranking update function.
  if (not SubmitScoreSync(request, &response)) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // Handle the result accordingly.
  switch (response) {
    case kNewRecord: {
      // New all-time record!
    } case kNewRecordWeekly: {
      // New weekly record.
    } case kNewRecordDaily: {
      // New daily record.
      break;
    } case kNone: {
      // no update
      break;
    } default: {
      BOOST_ASSERT(false);
    }
  }
}

Asynchronous method

As with authentication and billing, asynchronous score updates can be made by simply changing SubmitScoreSync() to SubmitScore().

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";
// const char *kGuildGameScore = "guild_game";


// The callback function to be called after the score update.
void OnScoreSubmitted(
    const ScoreSubmissionRequest &request,
    const ScoreSubmissionResponse &response,
    const bool &error) {
  if (error) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  switch (response) {
    case kNewRecord: {
      // all time new record
    } case kNewRecordWeekly: {
      // weekly new record
    } case kNewRecordDaily: {
      // daily new record
      break;
    } case kNone: {
      // no update
      break;
    } default: {
      BOOST_ASSERT(false);
    }
  }
}


// We assume a Facebook game as in the previous examples.
// Of course, the authentication method isn't really relevant.
void example(const string &facebook_id, int score) {
  ScoreSubmissionRequest request(kPlayerGameScore, "Facebook", facebook_id, score);

  ScoreSubmissionResponseHandler callback = OnScoreSubmitted;

  // Start asynchronous processing.
  SubmitScore(request, callback);
}

Public Ranking

This is an example to query the weekly top 10 scores of all players. (The commented code is an example for querying the 5 users ranked above and below “testuser” for today.)

Synchronized method

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";


void example() {
  // Query the weekly ranking for the corresponding competing group.
  // Grab the 0th to 9th users from the top.
  LeaderboardQueryRequest request(
      kPlayerGameScore, kWeekly, LeaderboardRange(LeaderboardRange::kFromTop, 0, 9));

  // To grab the nearby rankings of specific player, do as the following.
  // string service_provider = "Facebook"; // service provider
  // string id = "testuser";  // user id in service provider
  // LeaderboardQueryRequest request(
  //    kPlayerGameScore, service_provider, id, kDaily,
  //    LeaderboardRange(LeaderboardRange::kNearBy, -5, 5));

  LeaderboardQueryResponse response;

  // Call the synchronized method.
  if (not GetLeaderboardSync(request, &response)) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // Print the fetched rankings.
  for (int i = 0; i < response.size(); ++i) {
    LOG(INFO) << "# " << response[i].rank << " - "
              << response[i].score << " - "
              << response[i].player_account.id();
  }
}

Asynchronous method

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";


// Define the callback function to be called after fetching the rankings.
void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  for (int i = 0; i < response.size(); ++i) {
    LOG(INFO) << "# " << response[i].rank << " - "
              << response[i].score << " - "
              << response[i].player_account.id();
  }
}


void example() {
  // Get the weekly ranking for the corresponding competing group.
  // Grab the 0th to 9th users from the top.
  LeaderboardQueryRequest request(kPlayerGameScore, kWeekly,
      LeaderboardRange(LeaderboardRange::kFromTop, 0, 9));

  // To grab the nearby rankings of specific player, do as the following.
  // string service_provider = "Facebook"; // service provider
  // string id = "testuser";  // user id in service provider
  // LeaderboardQueryRequest request(
  //    kPlayerGameScore, service_provider, id, kDaily,
  //    LeaderboardRange(LeaderboardRange::kNearBy, -5, 5));

  LeaderboardQueryResponseHandler callback = OnResponse;

  // Start the asynchronous method.
  GetLeaderboard(request, callback);
}

Social Ranking

The example below is for querying the weekly ranking of all friends of Facebook user “testuser”. If you see the commented out code in the example, you’ll see that you can also query last weeks rankings for the friends of “testuser”. In the example, we use the friend list; to fetch the friend list, refer to here.

Synchronous version

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";


void example() {
  string service_provider = "Facebook"; // service provider
  string id = "testuser";  // user id in service provider

  PlayerAccountVector friend_list;
  // Fetch the friend list as in the authentication service example.
  ...

  // Create a request to query the weekly ranking of friends.
  LeaderboardQueryRequest request(
      kPlayerGameScore, "Facebook", "testuser", friend_list, kWeekly);

  // You can query last week's friend rankings as below.
  //  LeaderboardQueryRequest request(
  //      kPlayerGameScore, "Facebook", "testuser", friend_list, kLastWeek);

  LeaderboardQueryResponse response;

  // Call the synchronous version.
  if (not GetLeaderboardSync(request, &response)) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // Print the fetched rankings.
  for (int i = 0; i < response.size(); ++i) {
    LOG(INFO) << "# " << response[i].rank << " - "
              << response[i].score << " - "
              << response[i].player_account.id();
  }
}

Asynchronous version

#include <funapi.h>

using namespace fun;

// Set the competing group.
// iFun Engine does not enforce the competing group name.
// The game developer can use any name to distinguish each ranking.
const char *kPlayerGameScore = "player_game";

// Define the callback function to be called after querying the rankings.
void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  for (int i = 0; i < response.size(); ++i) {
    LOG(INFO) << "# " << response[i].rank << " - "
              << response[i].score << " - "
              << response[i].player_account.id();
  }
}


void example() {
  PlayerAccountVector friend_list;
  // Fetch the friend list as in the authentication service example.
  ...

  // Create the request.
  LeaderboardQueryRequest request(
      kPlayerGameScore, "Facebook", "testuser", friend_list, kWeekly);
  // You can also query last week's friend rankings as below.
  //  LeaderboardQueryRequest request(
  //      kPlayerGameScore, "Facebook", "testuser", friend_list, kLastWeek);

  LeaderboardQueryResponseHandler callback = OnResponse;

  // Start the asynchronous method.
  GetLeaderboard(request, callback);
}

See here for fetching the friend list.