/// <summary> /// Record a new result from a match between two players. /// </summary> /// <param name="winner"></param> /// <param name="loser"></param> /// <param name="isDraw"></param> public Result(Rating winner, Rating loser, bool isDraw = false) { if (!ValidPlayers(winner, loser)) { throw new ArgumentException("Players winner and loser are the same player"); } _winner = winner; _loser = loser; _isDraw = isDraw; }
/// <summary> /// Given a particular player, returns the opponent. /// </summary> /// <param name="player"></param> /// <returns></returns> public Rating GetOpponent(Rating player) { Rating opponent; if (_winner == player) { opponent = _loser; } else if (_loser == player) { opponent = _winner; } else { throw new ArgumentException("Player did not participate in match", "player"); } return opponent; }
/// <summary> /// Returns the "score" for a match. /// </summary> /// <param name="player"></param> /// <returns></returns> public double GetScore(Rating player) { double score; if (_winner == player) { score = PointsForWin; } else if (_loser == player) { score = PointsForLoss; } else { throw new ArgumentException("Player did not participate in match", "player"); } if (_isDraw) { score = PointsForDraw; } return score; }
/// <summary> /// Check that we're not doing anything silly like recording a match with only one player. /// </summary> /// <param name="player1"></param> /// <param name="player2"></param> /// <returns></returns> private static bool ValidPlayers(Rating player1, Rating player2) { return player1 != player2; }
/// <summary> /// This is a formula as per step 4 of Glickman's paper. /// </summary> /// <param name="player"></param> /// <param name="results"></param> /// <returns></returns> private double Delta(Rating player, IList<Result> results) { return V(player, results)*OutcomeBasedRating(player, results); }
/// <summary> /// Test whether a particular player participated in the match represented by this result. /// </summary> /// <param name="player"></param> /// <returns></returns> public bool Participated(Rating player) { return player == _winner || player == _loser; }
/// <summary> /// This is the main function in step 3 of Glickman's paper. /// </summary> /// <param name="player"></param> /// <param name="results"></param> /// <returns></returns> private static double V(Rating player, IEnumerable<Result> results) { var v = 0.0; foreach (var result in results) { v = v + ( (Math.Pow(G(result.GetOpponent(player).GetGlicko2RatingDeviation()), 2)) *E(player.GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2RatingDeviation()) *(1.0 - E(player.GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2RatingDeviation()) )); } return Math.Pow(v, -1); }
/// <summary> /// This is the function processing described in step 5 of Glickman's paper. /// </summary> /// <param name="player"></param> /// <param name="results"></param> private void CalculateNewRating(Rating player, IList<Result> results) { var phi = player.GetGlicko2RatingDeviation(); var sigma = player.GetVolatility(); var a = Math.Log(Math.Pow(sigma, 2)); var delta = Delta(player, results); var v = V(player, results); // step 5.2 - set the initial values of the iterative algorithm to come in step 5.4 var A = a; double B; if (Math.Pow(delta, 2) > Math.Pow(phi, 2) + v) { B = Math.Log(Math.Pow(delta, 2) - Math.Pow(phi, 2) - v); } else { double k = 1; B = a - (k*Math.Abs(_tau)); while (F(B, delta, phi, v, a, _tau) < 0) { k++; B = a - (k*Math.Abs(_tau)); } } // step 5.3 var fA = F(A, delta, phi, v, a, _tau); var fB = F(B, delta, phi, v, a, _tau); // step 5.4 while (Math.Abs(B - A) > ConvergenceTolerance) { var C = A + (((A - B)*fA)/(fB - fA)); var fC = F(C, delta, phi, v, a, _tau); if (fC*fB < 0) { A = B; fA = fB; } else { fA = fA/2.0; } B = C; fB = fC; } var newSigma = Math.Exp(A/2.0); player.SetWorkingVolatility(newSigma); // Step 6 var phiStar = CalculateNewRatingDeviation(phi, newSigma); // Step 7 var newPhi = 1.0/Math.Sqrt((1.0/Math.Pow(phiStar, 2)) + (1.0/v)); // note that the newly calculated rating values are stored in a "working" area in the Rating object // this avoids us attempting to calculate subsequent participants' ratings against a moving target player.SetWorkingRating( player.GetGlicko2Rating() + (Math.Pow(newPhi, 2)*OutcomeBasedRating(player, results))); player.SetWorkingRatingDeviation(newPhi); player.IncrementNumberOfResults(results.Count); }
/// <summary> /// Add a result to the set. /// </summary> /// <param name="winner"></param> /// <param name="loser"></param> public void AddResult(Rating winner, Rating loser) { var result = new Result(winner, loser); _results.Add(result); }
/// <summary> /// This is a formula as per step 4 of Glickman's paper. /// </summary> /// <param name="player"></param> /// <param name="results"></param> /// <returns>Expected rating based on outcomes.</returns> private static double OutcomeBasedRating(Rating player, IEnumerable<Result> results) { double outcomeBasedRating = 0; foreach (var result in results) { outcomeBasedRating = outcomeBasedRating + (G(result.GetOpponent(player).GetGlicko2RatingDeviation()) *(result.GetScore(player) - E( player.GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2Rating(), result.GetOpponent(player).GetGlicko2RatingDeviation())) ); } return outcomeBasedRating; }
/// <summary> /// Add a participant to the rating period, e.g. so that their rating will /// still be calculated even if they don't actually compete. /// </summary> /// <param name="rating"></param> public void AddParticipant(Rating rating) { _participants.Add(rating); }
/// <summary> /// Record a draw between two players and add to the set. /// </summary> /// <param name="player1"></param> /// <param name="player2"></param> public void AddDraw(Rating player1, Rating player2) { var result = new Result(player1, player2, true); _results.Add(result); }
/// <summary> /// Get a list of the results for a given player. /// </summary> /// <param name="player"></param> /// <returns></returns> public IList<Result> GetResults(Rating player) { var filteredResults = new List<Result>(); foreach (var result in _results) { if (result.Participated(player)) { filteredResults.Add(result); } } return filteredResults; }
/// <summary> /// Test whether a particular player participated in the match represented by this result. /// </summary> /// <param name="player"></param> /// <returns></returns> public bool Participated(Rating player) { return(player == _winner || player == _loser); }
/// <summary> /// Check that we're not doing anything silly like recording a match with only one player. /// </summary> /// <param name="player1"></param> /// <param name="player2"></param> /// <returns></returns> private static bool ValidPlayers(Rating player1, Rating player2) { return(player1 != player2); }