forio Toggle navigation

How To: Create a Leaderboard

This example outlines how to create and display a leaderboard in your project.

Use Case

People who interact with your project have a score at the end of their run. (A run is a set of interactions with a project.)

You would like these people to be able to compare their score with the scores of others after they are finished playing with your project.

There are several variations for projects with leaderboards, for example:

  • The people interacting with your project might be end users who have logged in to your project, if your project is an authenticated team project; or they might be anonymous users, because your project is public.
  • The score for the leaderboard is associated with a run. In many projects, there is one run for each person. In multiplayer projects, there is one run for several end users who work together in a single world.
  • Creating a leaderboard is independent of how the score is calculated. For example, the score in each run may be based on one model variable, on a combination of several model variables, or even directly on user input.

In each case, the basic technique for creating a leaderboard is the same.

What You Need

  • A model variable that stores the score for each run
  • A collection in the Data API that stores all of the scores

Solution Outline

  1. In your model, determine how the score should be calculated.
  2. In your project, create a collection of data for storing scores.
  3. At the end of the run, calculate the score based on how the user plays with your project.
  4. Store the score in your data collection. Optionally, store a name for the user as well.
  5. Read the scores from your data collection, sort them, and display them on the leaderboard within your project.

The sample code below uses both Epicenter.js, to create and access the data collection for the project, and Flow.js, for otherwise hooking up the model to the user interface. These two libraries have dependencies on jquery and lodash, so you'll need to include them as well. See more on using Epicenter.js in your project and using Flow.js in your project for how to include jquery and lodash.

Solution Implementation

  1. In your model, determine how the score should be calculated.

    In the simplest case, the score can just be a number entered by the user. More likely, it will be a number calculated based on a formula specific to your project. For example, in a sales simulation, it might be the profit in the final year of the simulation. In a leadership simulation, it might be a weighted average of several factors that have changed throughout the game.

    You can create leaderboards in any of the supported modeling languages. In a basic SimLang model about financial planning, you might use:

     V Score = Total Savings

    More information: Supported Modeling Languages

  2. In your project, create a collection of data for storing scores.

    Use the Epicenter.js Data Service to create project-level data. Create a collection for the high scores.

     var ds = new F.service.Data({ root: 'leaderboard' });

    More information: Data Service, underlying RESTful Data API.

  3. At the end of the run, calculate the score.

    In SimLang, the declaration in Step 1 means that Score is automatically recalculated each step. In other modeling languages, you may want to perform an explicit calculation or function call at the end of the run.

  4. Store the score in your data collection. Optionally, store a name for the user as well.

    For example,

     function saveScore() {
         var rm = new F.manager.RunManager();
         rm.getRun().then(function (run) {
    
             // take the run id
             // this will be the unique key in the data collection
             var thisRunId = run.id;           
    
             // take the user's name from the run
             // this is for display only,
             // and is only present if the user is logged in
             var thisUser = 'anonymous';
             if (run.user) { thisUser = run.user.firstName; };
    
             // get the value of the model variable Score
             // store this value, using the run id as the document name (unique key)                
             // note that the model variable Score 
             // is an array with the values from all time steps
             // we want just the last element of this array
             var vs = rm.run.variables();
             vs.load('Score').then(function(thisRunScore){
                 ds.saveAs(thisRunId, 
                     { 'score': thisRunScore[thisRunScore.length - 1], 
                       'user': thisUser });
             });
         });
     };

    More information: Data Service, Run Manager, Variables Service.

    Extensions: For a multiplayer project, you might use the name of the world rather than the name of the user. More information: World Manager.

  5. Read the scores from your data collection, sort them, and display them on the leaderboard within your project.

    For example,

     function showLeaderboard() {
    
         document.write('<table>',
             '<tr><td>Score</td>',
             '<td>Name</td>',
             '<td>Run</td></tr>');
    
         // search your data collection
         // get all scores greater than 0, and sort them by score
         ds.query('', {'score': { '$gt': 0 } }, 
             { sort: 'score', direction: 'desc' })
             .then(function (results) {
                 if (results.length == 0) {
                     document.write('<tr><td>', 
                         'No high scores available',
                         '</td></tr>');
                 } else {
                     for (var i=0; i < results.length; i++) {
                         document.write('<tr><td>', 
                             results[i].score, '</td><td>',
                             results[i].user, '</td><td>',
                             results[i].id, '</td></tr>');
                     }
                 }
                 document.write('</table>');                    
             });
     };

    More information: Data Service.

Complete Model File, 'myModel.eqn'

M StartTime = 2015
M EndTime = 2020

V Previous Year Savings = PREVIOUS(Total Savings, Initial Savings)

V Initial Savings = 5000

D Interest Rate = 10%

V Interest = Previous Year Savings * Interest Rate

D Yearly Deposits = 1000

V Total Savings = Previous Year Savings + Interest + Yearly Deposits

V Score = Total Savings

Complete HTML File

<!doctype html>
<html>
<head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <title>Savings and Leaderboard Simulation</title>
</head>

<body data-f-model="myModel.eqn">

<p>Initial Savings was: <span data-f-bind="Initial Savings"></span></p>

<p>Current Year is: <span data-f-bind="time"></span>. 
(The simulation ends after 2020.)</p>

<p>Total Savings is: <span data-f-bind="Total Savings"></span></p>

<p>Current Score is: <span data-f-bind="Score"></span></p>

Please enter your:
<p>Interest Rate: <input type="text" data-f-bind="Interest Rate"></input></p>
<p>Yearly Deposit: <input type="text" data-f-bind="Yearly Deposits"></input></p>

<p>When you're ready, submit your decisions:
<button data-f-on-click="step()">Submit and Step</button>
</p>

<p>And when you reach the end of the game, save your score:
<button onclick="saveScore()">Save Score</button>
</p>

<p>Then check out the Leaderboard to see how you rank:
<button onclick="showLeaderboard()">Show Leaderboard</button>
</p>

   <script src="jquery.min.js"></script>
   <script src="lodash.js"></script>

   <script src="//forio.com/tools/js-libs/1.8.1/epicenter.min.js"></script>
   <script src="//forio.com/tools/js-libs/flow/latest/flow.min.js"></script>

   <script>

       var options = {
           channel: {
               strategy: 'always-new',
               run: { model: 'myModel.eqn' }
            }
        };

       Flow.initialize(options);

       var ds = new F.service.Data({ root: 'leaderboard' });

       function saveScore() {
           var rm = new F.manager.RunManager();
           rm.getRun().then(function (run) {
               var thisRunId = run.id;           

               var thisUser = 'anonymous';
               if (run.user) { thisUser = run.user.firstName; };

               var vs = rm.run.variables();
               vs.load('Score').then(function(thisRunScore){
                   ds.saveAs(thisRunId, 
                       { 'score': thisRunScore[thisRunScore.length - 1], 
                         'user': thisUser });
               });
           });
       };

       function showLeaderboard() {

           document.write('<table>',
               '<tr><td>Score</td>',
               '<td>Name</td>',
               '<td>Run</td></tr>');

           ds.query('', {'score': { '$gt': 0 } }, 
               { sort: 'score', direction: 'desc' })
               .then(function (results) {
                   if (results.length == 0) {
                       document.write('<tr><td>',
                       'No high scores available',
                       '</td></tr>');
                   } else {
                       for (var i=0; i < results.length; i++) {
                           document.write('<tr><td>', 
                               results[i].score, '</td><td>',
                               results[i].user, '</td><td>',
                               results[i].id, '</td></tr>');
                       }
                   }
                   document.write('</table>');                    
           });
       };
   </script>
</body>
</html>