martedì 13 maggio 2014

Glicko 2 - Chess Ranking System


Initial rankings


Rankings after tournament 1


Rankings after tournament 2


Rankings after tournament 3


Codice utilizzato per generare la pagina:


<script type="text/javascript">
(function(exports){
    var scalingFactor = 173.7178;

    function Player(rating, rd , vol, tau){
        this._tau = tau; 

        this.setRating(rating);
        this.setRd(rd);
        this.setVol(vol);
    }

    Player.prototype.getRating = function (){
        return Math.round(this.__rating * scalingFactor) + this.defaultRating;
    };

    Player.prototype.setRating = function (rating){
        this.__rating = (rating - this.defaultRating) / scalingFactor;
    };

    Player.prototype.getRd = function(){
        return Math.round(100 * this.__rd * scalingFactor)/100;
    };

    Player.prototype.setRd = function(rd){
        this.__rd = rd / scalingFactor;
    };

    Player.prototype.getVol = function(){
        return this.__vol;
    };

    Player.prototype.setVol = function(vol){
        this.__vol = vol;
    };

    Player.prototype.addResult = function(opponent, outcome){
        this.adv_ranks.push(opponent.__rating);
        this.adv_rds.push(opponent.__rd);
        this.outcomes.push(outcome);
    };

    // Calculates the new rating and rating deviation of the player.
    // Follows the steps of the algorithm described at http://www.glicko.net/glicko/glicko2.pdf
    Player.prototype.update_rank = function(){
        if (!this.hasPlayed()){
            // Applies only the Step 6 of the algorithm
            this._preRatingRD();
            return;
        }

        //Step 1 : done by Player initialization
        //Step 2 : done by setRating and setRd

        //Step 3
        var v = this._variance();

        //Step 4
        var delta = this._delta(v);

        //Step 5
        this.__vol = this.volatility_algorithm(v, delta);

        //Step 6
        this._preRatingRD();

        //Step 7
        this.__rd = 1 / Math.sqrt((1 / Math.pow(this.__rd, 2)) + (1 / v));

        var tempSum = 0;
        for (var i=0,len = this.adv_ranks.length;i< len;i++){
            tempSum += this._g(this.adv_rds[i]) * (this.outcomes[i] - this._E(this.adv_ranks[i], this.adv_rds[i]));
        }
        this.__rating += Math.pow(this.__rd, 2) * tempSum;

        //Step 8 : done by getRating and getRd
    };

    Player.prototype.hasPlayed = function(){
        return this.outcomes.length > 0;
    };

    // Calculates and updates the player's rating deviation for the beginning of a rating period.
    // preRatingRD() -> None
    Player.prototype._preRatingRD = function(){
        this.__rd = Math.sqrt(Math.pow(this.__rd, 2) + Math.pow(this.__vol, 2));
    };


    // Calculation of the estimated variance of the player's rating based on game outcomes
    Player.prototype._variance = function (){
        var tempSum = 0;
        for (var i = 0, len = this.adv_ranks.length;i<len;i++){
            var tempE = this._E(this.adv_ranks[i], this.adv_rds[i]);
            tempSum += Math.pow(this._g(this.adv_rds[i]), 2) * tempE * (1 - tempE);
        }
        return 1 / tempSum;
    };

    // The Glicko E function.
    Player.prototype._E = function (p2rating, p2RD){
        return 1 / (1 + Math.exp(-1 * this._g(p2RD) *  (this.__rating - p2rating)));
    };

    // The Glicko2 g(RD) function.
    Player.prototype._g = function(RD){
        return 1 / Math.sqrt(1 + 3 * Math.pow(RD, 2) / Math.pow(Math.PI, 2));
    };

    // The delta function of the Glicko2 system.
    // Calculation of the estimated improvement in rating (step 4 of the algorithm)
    Player.prototype._delta = function(v){
        var tempSum = 0;
        for (var i = 0, len = this.adv_ranks.length;i<len;i++){
            tempSum += this._g(this.adv_rds[i]) * (this.outcomes[i] - this._E(this.adv_ranks[i], this.adv_rds[i]));
        }
        return v * tempSum;
    };

    Player.prototype._makef = function(delta, v, a){
        var pl = this;
        return function(x){
            return Math.exp(x) * (Math.pow(delta, 2) - Math.pow(pl.__rd, 2) - v - Math.exp(x)) / (2*Math.pow(Math.pow(pl.__rd, 2) + v + Math.exp(x), 2)) - (x - a) / Math.pow(pl._tau, 2);
        };
    };

    //=========================  Glicko2 class =============================================
    function Glicko2(settings){
        default_settings = {
            tau : 0.5, // Internal glicko2 parameter. "Reasonable choices are between 0.3 and 1.2, though the system should be tested to decide which value results in greatest predictive accuracy."
            rating : 1500, //default rating
            rd : 350, //Default rating deviation (small number = good confidence on the rating accuracy)
            vol : 0.06, //Default volatility (expected fluctation on the player rating)
            volatility_algorithm : 'newprocedure' //Default volatility calculation algorithm (step 5 of the global algorithm)
        };
        settings = settings || default_settings;
        Player.prototype.defaultRating = settings.rating;
        Player.prototype.volatility_algorithm = volatility_algorithms[settings.volatility_algorithm || default_settings.volatility_algorithm];

        this._tau = settings.tau;
        this._default_rating = settings.rating;
        this._default_rd = settings.rd;
        this._default_vol = settings.vol;
        this.players = [];
        this.players_index = 0;
    }

    Glicko2.prototype.removePlayers = function() {
        this.players = [];
        this.players_index = 0;
    };

    Glicko2.prototype.getPlayers = function(){
        return this.players;
    };

    Glicko2.prototype.cleanPreviousMatches = function(){
        for (var i = 0, len = this.players.length;i < len;i++){
            this.players[i].adv_ranks = [];
            this.players[i].adv_rds = [];
            this.players[i].outcomes = [];
        }
    };

    Glicko2.prototype.calculatePlayersRatings = function(){
        var keys = Object.keys(this.players);
        for (var i=0, len = keys.length;i<len;i++){
            this.players[keys[i]].update_rank();
        }
    };

    /** 
     * Add players and match result to be taken in account for the new rankings calculation
     * players must have ids, they are not created if it has been done already.
     * @param {Object litteral} pl1 The first player
     * @param {Object litteral} pl2 The second player
     * @param {number} outcom The outcome : 0 = defeat, 1 = victory, 0.5 = draw
     */
      Glicko2.prototype.addMatch = function(player1, player2, outcome){
          var pl1 = this._createInternalPlayer(player1.rating, player1.rd, player1.vol, player1.id);
          var pl2 = this._createInternalPlayer(player2.rating, player2.rd, player2.vol, player2.id);
          this.addResult(pl1, pl2, outcome);
          return {pl1:pl1, pl2:pl2};
      };

      Glicko2.prototype.makePlayer = function (rating, rd , vol) {
          //We do not expose directly createInternalPlayer in order to prevent the assignation of a custom player id whose uniqueness could not be guaranteed
          return this._createInternalPlayer(rating, rd, vol);
      };

      Glicko2.prototype._createInternalPlayer = function (rating, rd , vol, id){
          if (id === undefined){
              id = this.players_index;
              this.players_index = this.players_index + 1;
          } else {
              //We check if the player has already been created
              var candidate = this.players[id];
              if (candidate !== undefined){
                  return candidate;
              }
          }
          var player = new Player(rating || this._default_rating, rd || this._default_rd, vol || this._default_vol, this._tau);
          player.id = id;
          player.adv_ranks = [];
          player.adv_rds = [];
          player.outcomes = [];
          this.players[id] = player;
          return player;
      };

      /** 
       * Add a match result to be taken in account for the new rankings calculation
       * @param {Player} player1 The first player
       * @param {Player} player2 The second player
       * @param {number} outcome The outcome : 0 = defeat, 1 = victory, 0.5 = draw
       */
        Glicko2.prototype.addResult = function(player1, player2, outcome){
            player1.addResult(player2, outcome);
            player2.addResult(player1, 1 - outcome);
        };

        Glicko2.prototype.updateRatings = function(matches){
            if (typeof(matches) !== 'undefined'){
                this.cleanPreviousMatches();
                for (var i=0, len = matches.length;i<len;i++){
                    var match = matches[i];
                    this.addResult(match[0], match[1], match[2]);
                }
            }
            this.calculatePlayersRatings();
        };

        //============== VOLATILITY ALGORITHMS (Step 5 of the global glicko2 algorithm)
        var volatility_algorithms = {
            oldprocedure: function(v, delta) {
                var sigma = this.__vol;
                var phi = this.__rd;
                var tau = this._tau;

                var a, x1, x2, x3, y1, y2, y3, upper;
                var result;

                upper = find_upper_falsep(phi, v, delta, tau);

                a = Math.log(Math.pow(sigma, 2));
                y1 = equation(phi, v, 0, a, tau, delta);
                if (y1 > 0 ){
                    result = upper;
                } else {
                    x1 = 0;
                    x2 = x1;
                    y2 = y1;
                    x1 = x1 - 1;
                    y1 = equation(phi, v, x1, a, tau, delta);
                    while (y1 < 0){
                        x2 = x1;
                        y2 = y1;
                        x1 = x1 - 1;
                        y1 = equation(phi, v, x1, a, tau, delta);
                    }
                    for (var i = 0; i<21; i++){
                        x3 = y1 * (x1 - x2) / (y2 - y1) + x1;
                        y3 = equation(phi, v, x3, a, tau, delta);
                        if (y3 > 0 ){
                            x1 = x3;
                            y1 = y3;
                        } else {
                            x2 = x3;
                            y2 = y3;
                        }
                    }
                    if (Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2) > upper ){
                        result = upper;
                    } else {
                        result = Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2);
                    }
                }
                return result;

                //
                function new_sigma(sigma , phi , v , delta , tau ) {
                    var a = Math.Math.log(Math.pow(sigma, 2));
                    var x = a;
                    var old_x = 0;
                    while (x != old_x){
                        old_x = x;
                        var d = Math.pow(phi, 2) + v + Math.exp(old_x);
                        var h1 = -(old_x - a) / Math.pow(tau, 2) - 0.5 * Math.exp(old_x) / d + 0.5 * Math.exp(old_x) * Math.pow((delta / d), 2);
                        var h2 = -1 / Math.pow(tau, 2) - 0.5 * Math.exp(old_x) * (Math.pow(phi, 2) + v) / Math.pow(d, 2) + 0.5 * Math.pow(delta, 2) * Math.exp(old_x) * (Math.pow(phi, 2) + v - Math.exp(old_x)) / Math.pow(d, 3);
                        x = old_x - h1 / h2;
                    }
                    return  Math.exp(x / 2);
                }

                function equation(phi , v , x , a , tau , delta) {
                    var d = Math.pow(phi, 2) + v + Math.exp(x);
                    return -(x - a) / Math.pow(tau, 2) - 0.5 * Math.exp(x) / d + 0.5 * Math.exp(x) * Math.pow((delta / d), 2);
                }

                function new_sigma_bisection(sigma , phi , v , delta , tau ) {
                    var a, x1, x2, x3;
                    a = Math.Math.log(Math.pow(sigma, 2));
                    if (equation(phi, v, 0, a, tau, delta) < 0 ){
                        x1 = -1;
                        while (equation(phi, v, x1, a, tau, delta) < 0){
                            x1 = x1 - 1;
                        }
                        x2 = x1 + 1;
                    } else {
                        x2 = 1;
                        while (equation(phi, v, x2, a, tau, delta) > 0){
                            x2 = x2 + 1;
                        }
                        x1 = x2 - 1;
                    }

                    for (var i = 0; i < 27; i++) {
                        x3 = (x1 + x2) / 2;
                        if (equation(phi, v, x3, a, tau, delta) > 0 ){
                            x1 = x3;
                        } else {
                            x2 = x3;
                        }
                    }
                    return  Math.exp((x1 + x2)/ 4);
                }

                function Dequation(phi , v , x , tau , delta) {
                    d = Math.pow(phi, 2) + v + Math.exp(x);
                    return -1 / Math.pow(tau, 2) - 0.5 * Math.exp(x) / d + 0.5 * Math.exp(x) * (Math.exp(x) + Math.pow(delta, 2)) / Math.pow(d, 2) - Math.pow(Math.exp(x), 2) * Math.pow(delta, 2) / Math.pow(d, 3);
                }

                function find_upper_falsep(phi , v , delta , tau) {
                    var x1, x2, x3, y1, y2, y3;
                    y1 = Dequation(phi, v, 0, tau, delta);
                    if (y1 < 0 ){
                        return 1;
                    } else {
                        x1 = 0;
                        x2 = x1;
                        y2 = y1;
                        x1 = x1 - 1;
                        y1 = Dequation(phi, v, x1, tau, delta);
                        while (y1 > 0){
                            x2 = x1;
                            y2 = y1;
                            x1 = x1 - 1;
                            y1 = Dequation(phi, v, x1, tau, delta);
                        }
                        for (var i = 0; i < 21 ; i++){
                            x3 = y1 * (x1 - x2) / (y2 - y1) + x1;
                            y3 = Dequation(phi, v, x3, tau, delta);
                            if (y3 > 0 ){
                                x1 = x3;
                                y1 = y3;
                            } else {
                                x2 = x3;
                                y2 = y3;
                            }
                        }
                        return Math.exp((y1 * (x1 - x2) / (y2 - y1) + x1) / 2);
                    }
                }
            },
            newprocedure: function(v, delta){
                //Step 5.1
                var A = Math.log(Math.pow(this.__vol, 2));
                var f = this._makef(delta, v, A);
                var epsilon = 0.0000001;

                //Step 5.2
                var B, k;
                if (Math.pow(delta, 2) >  Math.pow(this.__rd, 2) + v){
                    B = Math.log(Math.pow(delta, 2) -  Math.pow(this.__rd, 2) - v);
                } else {
                    k = 1;
                    while (f(A - k * this._tau) < 0){
                        k = k + 1;
                    }
                    B = A - k * this._tau;
                }

                //Step 5.3
                var fA = f(A);
                var fB = f(B);

                //Step 5.4
                var C, fC;
                while (Math.abs(B - A) > epsilon){
                    C = A + (A - B) * fA /(fB - fA );
                    fC = f(C);
                    if (fC * fB < 0){
                        A = B;
                        fA = fB;
                    } else {
                        fA = fA / 2;
                    }
                    B = C;
                    fB = fC;
                }
                //Step 5.5
                return Math.exp(A/2);
            },
            newprocedure_mod: function(v, delta){
                //Step 5.1
                var A = Math.log(Math.pow(this.__vol, 2));
                var f = this._makef(delta, v, A);
                var epsilon = 0.0000001;

                //Step 5.2
                var B, k;
                //XXX mod
                if (delta >  Math.pow(this.__rd, 2) + v){
                    //XXX mod
                    B = Math.log(delta -  Math.pow(this.__rd, 2) - v);
                } else {
                    k = 1;
                    while (f(A - k * this._tau) < 0){
                        k = k + 1;
                    }
                    B = A - k * this._tau;
                }

                //Step 5.3
                var fA = f(A);
                var fB = f(B);

                //Step 5.4
                var C, fC;
                while (Math.abs(B - A) > epsilon){
                    C = A + (A - B) * fA /(fB - fA );
                    fC = f(C);
                    if (fC * fB < 0){
                        A = B;
                        fA = fB;
                    } else {
                        fA = fA / 2;
                    }
                    B = C;
                    fB = fC;
                }
                //Step 5.5
                return Math.exp(A/2);
            },
            oldprocedure_simple: function(v, delta){
                var i = 0;
                var a = Math.log(Math.pow(this.__vol, 2));
                var tau = this._tau;
                var x0 = a;
                var x1 = 0;
                var d,h1,h2;

                while (Math.abs(x0 - x1) > 0.00000001){
                    // New iteration, so x(i) becomes x(i-1)
                    x0 = x1;
                    d = Math.pow(this.__rating, 2) + v + Math.exp(x0);
                    h1 = -(x0 - a) / Math.pow(tau, 2) - 0.5 * Math.exp(x0) / d + 0.5 * Math.exp(x0) * Math.pow(delta / d, 2);
                    h2 = -1 / Math.pow(tau, 2) - 0.5 * Math.exp(x0) * (Math.pow(this.__rating, 2) + v) / Math.pow(d, 2) + 0.5 * Math.pow(delta, 2) * Math.exp(x0) * (Math.pow(this.__rating, 2) + v - Math.exp(x0)) / Math.pow(d, 3);
                    x1 = x0 - (h1 / h2);
                }

                return Math.exp(x1 / 2);
            }
        };
        //==== End of volatility algorithms

        exports.Glicko2 = Glicko2;

    })(typeof exports === 'undefined'? this['glicko2']={}: exports);
</script>
<script type="text/javascript">
// -------------------------- Codice da modificare --------------------------
            var settings = {
              tau : 0.5,
              rpd : 604800,
              rating : 1500,
              rd : 300,
              vol : 0.06
            };
            var glicko = new glicko2.Glicko2(settings);

            //Make players
            Ryan = glicko.makePlayer(); //use default values
            Mary = glicko.makePlayer(1700, 300, 0.06);
            Bob = glicko.makePlayer(1400, 30, 0.06);
            John = glicko.makePlayer(1550, 100, 0.06);
            Gaspard = glicko.makePlayer();
            Hector = glicko.makePlayer();

            var players = [
                { name:'Ryan', glicko: Ryan },
                { name:'Mary', glicko: Mary },
                { name:'Bob', glicko: Bob },
                { name:'John', glicko: John },
                { name:'Gaspard', glicko: Gaspard },
                { name:'Hector', glicko: Hector }
            ];

            function showRankings(){
                //Sort players
                players.sort(function(pl1, pl2){ return pl2.glicko.getRating() - pl1.glicko.getRating(); });

                //Display ratings
                for (var i=0,len= players.length;i<len;i++){
                    player = players[i];
                    document.write(player.name + ' : ' + player.glicko.getRating() + ' (rd : ' + player.glicko.getRd() + ')<br>');
                }
            }
</script>
<br />
<h2>Initial rankings</h2>
<script>showRankings();</script>
<br />
<h2>Rankings after tournament 1</h2>
<script>
            var matches = [];
            matches.push([Ryan, Bob, 1]); //Ryan won over Bob
            matches.push([Ryan, John, 0]); //Ryan lost against John
            matches.push([Ryan, Mary, 0]); //Ryan lost against Mary

            glicko.updateRatings(matches);
            showRankings();
</script>
<br />
<h2>Rankings after tournament 2</h2>
<script>
            var matches = [];
            matches.push([Ryan, Gaspard, 1]); 
            matches.push([Hector, Bob, 0]); 
            matches.push([John, Mary, 0]);
            matches.push([John, Ryan, 0]);
            matches.push([Gaspard, Mary, 0]);
            matches.push([Mary, John, 0]);
            glicko.updateRatings(matches);
            showRankings();
</script>
<script>
//Aggiungi i giocatori
            Toni = glicko.makePlayer();
            Jane = glicko.makePlayer();

//Ricrei la lista dei giocatori con i nuovi giocatori aggiunti (metti sempre la , alla fine di ogni riga tranne l'ultima)
            var players = [
                { name:'Ryan', glicko: Ryan },
                { name:'Mary', glicko: Mary },
                { name:'Bob', glicko: Bob },
                { name:'John', glicko: John },
                { name:'Gaspard', glicko: Gaspard },
                { name:'Hector', glicko: Hector },
                { name:'Toni', glicko: Toni },
                { name:'Jane', glicko: Jane }
            ];
            
//Ricrei la funzione per visualizzare la lista dei giocatori ordinati per ranking
            function showRankings(){
                //Sort players
                players.sort(function(pl1, pl2){ return pl2.glicko.getRating() - pl1.glicko.getRating(); });

                //Display ratings
                for (var i=0,len= players.length;i<len;i++){
                    player = players[i];
                    document.write(player.name + ' : ' + player.glicko.getRating() + ' (rd : ' + player.glicko.getRd() + ')<br>');
                }
            }
</script>
<br />
<h2>Rankings after tournament 3</h2>
<script>
//Risultati partite del torneo 3
            var matches = [];
            matches.push([Ryan, Toni, 0]); 
            matches.push([Hector, Jane, 1]); 
            matches.push([John, Mary, 1]);
            glicko.updateRatings(matches);
            showRankings();
</script>

<br />

Nessun commento:

Posta un commento