Categories
Code

Javascript 6 Nations’ Sweepstake Calculator

I do a bit of voluntary work for CR Caernarfon, my local rugby club.

In 2014 we organised a 6 Nations sweepstake to a bit of fun and raise some money for charity. If you’re not aware of the 6 Nations championship, it’s a springtime rugby competition between England, France, Ireland, Italy, Scotland and Wales. They play each other once and a winner is decided at the end.

Players are invited to guess the score for each match plus the number of tries. They’re also asked to predict the final table.

There are six point-scoring rules for the sweepstake, and with 15 matches (n(n-1)/2) it can take a few hours to calculate the points for each person. It was therefore a ripe programming challenge.

I coded a simple solution in javascript last year, but didn’t get used as there was no easy way for the organizer (hi, mum!) to input results. So I updated it for this year and it works well. Checkout the latest results.

The rules for individual matches are:

  1. 1 point for either predicting the correct winning team or predicting a draw for each match.
  2. 1 point for the correct score of each team in each match (max of 2).
  3. 1 point for the correct points difference between the teams in each match.
  4. 2 points for predicting the total number of tries (including penalty tries) in each match.

Maximum available points are 6 points for each match.

What I Did

(Note: the code is on github.)

First off I asked the organizer to input all data into a spreadsheet. Participants’ details were listed, along with their predictions for each match. Each match has a home score, away score and try count. This sheet was then exported as a CSV file.

D3.js is used to read in this data. I’ve used D3 on the Wales Yearbook so have a good grasp of its features. It has a csv method, d3.csv(url[[, accessor], callback]) which issues a GET request for the file, processes each row and then passes the result to a callback.

Using this method I turned the CSV data into a JSON structure for each participant:

return {
    // enw = name 
    // and anyone who's been involved in rugby will know the importance of nicknames!
    person : { name: d.enw1+' '+d.enw2, nickname : d.nickname, email : d.ebost },
    rankings : [d.finalFirst, d.finalSecond, d.finalThird, d.finalFourth, d.finalFifth, d.finalSixth],
    games : [
        {
            home: +d.game1Home,
            away: +d.game1Away,
            tries: +d.game1Tries
        },
        /* another 13 match results */
        {
            home: +d.game15Home,
            away: +d.game15Away,
            tries: +d.game15Tries
        },
      ]
    }

This object is added to an array which is then passed to the callback.

But before I discuss that, I’ll demonstrate the API created to calculate the scores.

A Sweepstake javascript object holds all the data and does the calculations. Briefly:

var Sweep = {

    // array of participants
    players : [],

    // array of match results
    results : [],

    // a singleplayer object
    singleplayer : {},

    /**
     * Add a player to the object
     * 
     */
    addPlayer : function(name)
    {
        this.singleplayer = { name: name, matches: [] };
        this.players.push(this.singleplayer)
        return this;
    },

    /**
     * Add a match prediction. This must be chained to a player via addPlayer()
     * 
     */
    addScore : function(home, away, tries)
    {
        this.singleplayer.matches.push( {
            home : home,
            away : away,
            tries : tries
        });

        return this;
    },

    /**
     * Add a match result
     * 
     */
    addResult : function(name, home, away, tries)
    {        
        this.results.push({
            name: name,
            home : home,
            away : away,
            tries : tries
        });

        return this;
    },

    /**
     * Caculate and store the scores
     * 
     */
    calculate : function()
    {
        // loop through each player, calculate and store the points for each match
    },

    /**
     * Display the scores in a table
     * 
     */
    showResult : function()
    {
        // format the results in a table
    }

} 

Using this object we can pass in each participant’s predictions and the final match results. Using rows passed by the D3.csv callback:

for(var i in rows)
  {
    var player = rows[i];

    var result = Sweep.addPlayer(player.person.name);
    for(var j in player.games)
    {
        result.addScore(player.games[j].home, player.games[j].away, player.games[j].tries);
    }
  }

Each participant is now stored in the Sweepstake.players array.

Next up is to add the match results:

    // results from the 2015 first weekend
    Sweep.addResult('Cym v Llo', 16,21,3) // Wal v Eng
            .addResult('Eid v Iwe',3,26, 2) // Ita v Ire
            .addResult('Ffr v Alb',15,8,1) // Fra v Sco

And then still within the callback we call Sweepstake.calculate() and Sweepstake.showResult().

The calculate method loops through the players and checks their scores with the results:

// for each match
// individual points breakdown of points allocation
player.matches[j].points = {winner:'',score_home:'',score_away:'',diff:'',tries:'',total:0}

// match points difference
result.pd = Math.abs(result.home - result.away);

// match winner prediction
// Home Win
if( (player.matches[j].home > player.matches[j].away) && (result.home > result.away) 
||  // OR away win
(player.matches[j].home < player.matches[j].away) && (result.home < result.away) 
|| //  OR draw
(player.matches[j].home == player.matches[j].away && result.home == result.away)
)
{
player.matches[j].points.winner = '1'; // a string to be outputted in html
player.matches[j].points.total++; // final score as an integer
}

// compare with player's predictions

// home score
if(result.home == player.matches[j].home) 
{
player.matches[j].points.score_home = '1';
player.matches[j].points.total++;
}
// away score
if(result.away == player.matches[j].away) 
{
player.matches[j].points.score_away = '1';
player.matches[j].points.total++;
}

// prediction points difference
player.matches[j].pd = Math.abs(player.matches[j].home - player.matches[j].away);             
if(result.pd == player.matches[j].pd)
{
player.matches[j].points.diff = '1';
player.matches[j].points.total++;
}

// try count, two points
if(result.tries == player.matches[j].tries)
{
player.matches[j].points.tries = '2';
player.matches[j].points.total = player.matches[j].points.total + 2;
}

// total player score
player.points = player.points + player.matches[j].points.total;

The Sweepstake.showResult() is simply a method to output the above in a table row, and then render the full table on the page.

In order for participants to check their individual results, I added a click event to their name which opens a Bootstrap modal window. It gives a breakdown of their points for each match.

breakdown

This is a simple jQuery click() method to listen for the click on the player name, and then show the table of results in the modal.

Conclusion

What took hours now takes seconds to calculate, and results are updated on the club website after each round’s final game.

My main issue is that this is updated by editing the HTML file (with Sweepstake.addResult()) and uploading via FTP. I’d love to provide a way so that I can add scores off my smartphone, but this would call for back end programming, data stores and authentication etc. I will probably look at this for next year.

Finishing Off

As usual time constraints has stopped me in adding the rules for the final table, but as the competition has still not ended (in three weeks’ time, as of time of writing) I still have time for this. I’ll update this post accordingly.

All the code is available on my github repo.