glicko2js
glicko2js copied to clipboard
Typescript
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);
}
};