glicko2js icon indicating copy to clipboard operation
glicko2js copied to clipboard

Typescript

Open c0ncentus opened this issue 1 year ago • 0 comments

I try to put this file in typescript but maybe its not finished but you have a "get started" ...

const scalingFactor = 173.7178;

class Race {
    matches: any[]
    constructor(results: any) { this.matches = this.computeMatches(results); }
    computeMatches = function (results) {
        let players: { player: string, position: number }[] = [];
        let position = 0;

        results.forEach(function (rank) {
            position += 1;
            rank.forEach(function (player) { players.push({ "player": player, "position": position }); })
        })

        function computeMatches(players: any) {
            if (players.length === 0) return [];

            const player1 = players.shift()
            const player1_results = players.map(function (player2) {
                return [player1.player, player2.player, (player1.position < player2.position) ? 1 : 0.5];
            });

            return player1_results.concat(computeMatches(players));
        }
        return computeMatches(players)
    }
}

class Player {
    _tau = 0; defaultRating = 1500; volatility_algorithm: (v: any, delta: any) => number | undefined = undefined; id = ""; adv_ranks: number[] = []; adv_rds: number[] = []; outcomes = []; __rating = 0; __rd = 0; __vol = 0;

    getRating = function () { return this.__rating * scalingFactor + this.defaultRating; };
    setRating = function (rating) { this.__rating = (rating - this.defaultRating) / scalingFactor; };
    getRd = function () { return this.__rd * scalingFactor; };
    setRd = function (rd) { this.__rd = rd / scalingFactor; };
    getVol = function () { return this.__vol; };
    hasPlayed = function () { return this.outcomes.length > 0; };
    _preRatingRD = function () { this.__rd = Math.sqrt(Math.pow(this.__rd, 2) + Math.pow(this.__vol, 2)); };

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

    update_rank = function () {
        if (!this.hasPlayed()) { this._preRatingRD(); return; }
        const v: number = this._variance();
        const delta = this._delta(v);
        this.__vol = this.volatility_algorithm(v, delta);
        this._preRatingRD();
        this.__rd = 1 / Math.sqrt((1 / Math.pow(this.__rd, 2)) + (1 / v));

        let tempSum = 0;
        for (let 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;
    };

    _variance = function (): number {
        let tempSum = 0;
        for (let i = 0, len = this.adv_ranks.length; i < len; i++) {
            let 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.
    _E = function (p2rating: number, p2RD: number) { return 1 / (1 + Math.exp(-1 * this._g(p2RD) * (this.__rating - p2rating))); };

    predict = function (p2) {
        const diffRD = Math.sqrt(Math.pow(this.__rd, 2) + Math.pow(p2.__rd, 2));
        return 1 / (1 + Math.exp(-1 * this._g(diffRD) * (this.__rating - p2.__rating)));
    };

    _g = function (RD: number) { 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)
    _delta = function (v: number) {
        let tempSum = 0;
        for (let 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;
    };

    _makef = function (delta: number, v: number, a: number) {
        return function (x) {
            return Math.exp(x)
                * (Math.pow(delta, 2)
                    - Math.pow(this.__rd, 2)
                    - v - Math.exp(x)) / (2 * Math.pow(Math.pow(this.__rd, 2)
                        + v + Math.exp(x), 2)) - (x - a) / Math.pow(this._tau, 2);
        };
    };
    constructor(rating: number, rd: number, vol: number, tau: number, default_rating: number, volatility_algorithm: (v: any, delta: any) => number, id: string) {
        this.volatility_algorithm = volatility_algorithm; this.setRating(rating); this.setRd(rd);
        this._tau = tau; this.__rating = rating; this.__rd = rd; this.__vol = vol; this.id = id; this.defaultRating = default_rating;
    }
}

class Glicko2 {
    settings: Partial<{ _tau: number, rd: number, vol: number, volatility_algorithm: any, rating: number }> = {};
    _default_rating = 1500 || this.settings.rating;
    _default_rd = this.settings.rd || 350;
    _tau: number = 0.5;
    _default_vol = this.settings.vol || 0.06;
    _volatility_algorithm = volatility_algorithms[this.settings.volatility_algorithm || 'newprocedure'];
    players = [];
    players_index = 0;

    constructor(settings: Partial<{ _tau: number, rd: number, vol: number, volatility_algorithm: any, rating: number }>) { this.settings = settings; this._tau = settings._tau }

    makeRace = function (results) { return new Race(results) };
    removePlayers = function () { this.players = []; this.players_index = 0; };
    getPlayers = function () { return this.players; };
    cleanPreviousMatches = function () {
        for (let i = 0, len = this.players.length; i < len; i++) {
            this.players[i].adv_ranks = [];
            this.players[i].adv_rds = [];
            this.players[i].outcomes = [];
        }
    };

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

    addMatch = function (player1, player2, outcome) {
        let pl1 = this._createInternalPlayer(player1.rating, player1.rd, player1.vol, player1.id);
        let pl2 = this._createInternalPlayer(player2.rating, player2.rd, player2.vol, player2.id);
        this.addResult(pl1, pl2, outcome);
        return { pl1: pl1, pl2: pl2 };
    };

    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);
    };

    _createInternalPlayer = function (rating, rd, vol, id) {
        if (id === undefined) { id = this.players_index; this.players_index = this.players_index + 1; }
        else { const candidate = this.players[id]; if (candidate !== undefined) { return candidate; } }
        const player = new Player(rating || this._default_rating, rd || this._default_rd, vol || this._default_vol,
            this._tau, this._default_rating, this._volatility_algorithm, id);

        this.players[id] = player;
        return player;
    };
    addResult = function (player1: Player, player2: Player, outcome: number) { player1.addResult(player2, outcome); player2.addResult(player1, 1 - outcome); };
    updateRatings = function (matches) {
        if (matches instanceof Race) { matches = matches.matches; }
        if (typeof (matches) !== 'undefined') {
            this.cleanPreviousMatches();
            for (let i = 0, len = matches.length; i < len; i++) { const match = matches[i]; this.addResult(match[0], match[1], match[2]); }
        }
        this.calculatePlayersRatings();
    };
    predict = function (player1, player2) { return player1.predict(player2); };
}

const volatility_algorithms = {
    oldprocedure: function (v, delta) {
        const sigma = this.__vol;
        const phi = this.__rd;
        const tau = this._tau;
        let a, x1, x2, x3, y1, y2, y3, upper;
        let 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 (let 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) {
            const a = Math.log(Math.pow(sigma, 2));
            let x = a;
            let old_x = 0;
            while (x != old_x) {
                old_x = x;
                const d = Math.pow(phi, 2) + v + Math.exp(old_x);
                const 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);
                const 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: number, v: number, x: number, a: number, tau: number, delta: number) {
            const 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) {
            let a, x1, x2, x3;
            a = 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 (let 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: number, v: number, x: number, tau: number, delta: number) {
            const 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: number, v: number, delta: number, tau: number) {
            let x1: number, x2: number, x3: number, y1: number, y2: number, y3: number;
            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 (let 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: number, delta: number) {
        let A = Math.log(Math.pow(this.__vol, 2));
        let f = this._makef(delta, v, A);
        let epsilon = 0.0000001;

        let B: undefined | number;
        let k: undefined | number;
        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;
        }
        let fA = f(A); let fB = f(B); let 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;
        }
        return Math.exp(A / 2);
    },
    newprocedure_mod: function (v: number, delta: number) {
        let A = Math.log(Math.pow(this.__vol, 2));
        const f = this._makef(delta, v, A);
        const epsilon = 0.0000001;
        let B: number, k: number;
        if (delta > Math.pow(this.__rd, 2) + v) { 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;
        }

        let fA = f(A); let fB = f(B);
        let 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;
        }
        return Math.exp(A / 2);
    },
    oldprocedure_simple: function (v: number, delta: number) {
        let a = Math.log(Math.pow(this.__vol, 2)); let tau = this._tau; let x0 = a; let x1 = 0; let d, h1, h2;
        while (Math.abs(x0 - x1) > 0.00000001) {
            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);
    }
};

c0ncentus avatar Feb 15 '24 23:02 c0ncentus