diff --git a/modules/rating/src/main/java/glicko2/Rating.java b/modules/rating/src/main/java/glicko2/Rating.java index 78587a1de6f14..964e56e7367a7 100644 --- a/modules/rating/src/main/java/glicko2/Rating.java +++ b/modules/rating/src/main/java/glicko2/Rating.java @@ -19,6 +19,7 @@ */ public class Rating { + private double advantage; private double rating; private double ratingDeviation; private double volatility; @@ -31,10 +32,15 @@ public class Rating { private double workingVolatility; public Rating(double initRating, double initRatingDeviation, double initVolatility, int nbResults) { - this(initRating, initRatingDeviation, initVolatility, nbResults, null); + this(0.0d, initRating, initRatingDeviation, initVolatility, nbResults, null); } public Rating(double initRating, double initRatingDeviation, double initVolatility, int nbResults, DateTime lastRatingPeriodEndDate) { + this(0.0d, initRating, initRatingDeviation, initVolatility, nbResults, lastRatingPeriodEndDate); + } + + public Rating(double advantage, double initRating, double initRatingDeviation, double initVolatility, int nbResults, DateTime lastRatingPeriodEndDate) { + this.advantage = advantage; this.rating = initRating; this.ratingDeviation = initRatingDeviation; this.volatility = initVolatility; @@ -42,6 +48,20 @@ public Rating(double initRating, double initRatingDeviation, double initVolatili this.lastRatingPeriodEndDate = lastRatingPeriodEndDate; } + public Rating setAdvantage(double advantage) { + this.advantage = advantage; + return this; + } + + /** + * Return the skill advantage (first-player handicap) value. + * + * @return double + */ + public double getAdvantage() { + return this.advantage; + } + /** * Return the average skill value of the player. * @@ -55,6 +75,16 @@ public void setRating(double rating) { this.rating = rating; } + /** + * Return the average skill value of the player scaled down + * to the scale used by the algorithm's internal workings. + * + * @return double + */ + public double getGlicko2RatingWithAdvantage() { + return RatingCalculator.convertRatingToGlicko2Scale(this.rating + advantage); + } + /** * Return the average skill value of the player scaled down * to the scale used by the algorithm's internal workings. @@ -70,7 +100,7 @@ public double getGlicko2Rating() { * * @param double */ - public void setGlicko2Rating(double rating) { + private void setGlicko2Rating(double rating) { this.rating = RatingCalculator.convertRatingToOriginalGlickoScale(rating); } @@ -159,4 +189,12 @@ public void setWorkingRating(double workingRating) { public void setWorkingRatingDeviation(double workingRatingDeviation) { this.workingRatingDeviation = workingRatingDeviation; } + + // 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 + public void updateWorkingRatingAndResults(double workingRatingIncrement, double workingRatingDeviation, int results) { + setWorkingRating( getGlicko2Rating() + workingRatingIncrement ); + setWorkingRatingDeviation( workingRatingDeviation ); + incrementNumberOfResults( results); + } } diff --git a/modules/rating/src/main/java/glicko2/RatingCalculator.java b/modules/rating/src/main/java/glicko2/RatingCalculator.java index 0e012e14f7688..10558c649381e 100644 --- a/modules/rating/src/main/java/glicko2/RatingCalculator.java +++ b/modules/rating/src/main/java/glicko2/RatingCalculator.java @@ -94,9 +94,10 @@ public void updateRatings(RatingPeriodResults results, boolean skipDeviationIncr // if a player does not compete during the rating period, then only Step 6 applies. // the player's rating and volatility parameters remain the same but deviation increases - player.setWorkingRating(player.getGlicko2Rating()); - player.setWorkingRatingDeviation(calculateNewRD(player.getGlicko2RatingDeviation(), player.getVolatility(), elapsedRatingPeriods)); - player.setWorkingVolatility(player.getVolatility()); + double newSigma = player.getVolatility(); + double newPhi = calculateNewRD( player.getGlicko2RatingDeviation(), player.getVolatility(), elapsedRatingPeriods ); + player.updateWorkingRatingAndResults( 0.0d, newPhi, 0 ); + player.setWorkingVolatility( newSigma ); } } @@ -191,23 +192,18 @@ private void calculateNewRating(Rating player, List results, double elap throw new RuntimeException("Convergence fail"); } - double newSigma = Math.exp( A/2.0 ); - - player.setWorkingVolatility(newSigma); + double newSigma = Math.exp( A / 2.0 ); + player.setWorkingVolatility( newSigma ); // Step 6 double phiStar = calculateNewRD( phi, newSigma, elapsedRatingPeriods ); // Step 7 double newPhi = 1.0 / Math.sqrt(( 1.0 / Math.pow(phiStar, 2) ) + ( 1.0 / v )); + double muIncrement = Math.pow( newPhi, 2 ) * outcomeBasedRating( player, results ); - // 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.size()); + // Step 8 + player.updateWorkingRatingAndResults( muIncrement, newPhi, results.size() ); } private double f(double x, double delta, double phi, double v, double a, double tau) { @@ -254,11 +250,11 @@ private double v(Rating player, List results) { for ( Result result: results ) { v = v + ( ( Math.pow( g(result.getOpponent(player).getGlicko2RatingDeviation()), 2) ) - * E(player.getGlicko2Rating(), - result.getOpponent(player).getGlicko2Rating(), + * E(player.getGlicko2RatingWithAdvantage(), + result.getOpponent(player).getGlicko2RatingWithAdvantage(), result.getOpponent(player).getGlicko2RatingDeviation()) - * ( 1.0 - E(player.getGlicko2Rating(), - result.getOpponent(player).getGlicko2Rating(), + * ( 1.0 - E(player.getGlicko2RatingWithAdvantage(), + result.getOpponent(player).getGlicko2RatingWithAdvantage(), result.getOpponent(player).getGlicko2RatingDeviation()) )); } @@ -293,8 +289,8 @@ private double outcomeBasedRating(Rating player, List results) { outcomeBasedRating = outcomeBasedRating + ( g(result.getOpponent(player).getGlicko2RatingDeviation()) * ( result.getScore(player) - E( - player.getGlicko2Rating(), - result.getOpponent(player).getGlicko2Rating(), + player.getGlicko2RatingWithAdvantage(), + result.getOpponent(player).getGlicko2RatingWithAdvantage(), result.getOpponent(player).getGlicko2RatingDeviation() )) ); } diff --git a/modules/round/src/main/PerfsUpdater.scala b/modules/round/src/main/PerfsUpdater.scala index d9b69c6c8a7b9..b0b69ab4a81d8 100644 --- a/modules/round/src/main/PerfsUpdater.scala +++ b/modules/round/src/main/PerfsUpdater.scala @@ -121,9 +121,9 @@ final class PerfsUpdater( } val results = new RatingPeriodResults() result match { - case Glicko.Result.Draw => results.addDraw(white, black) - case Glicko.Result.Win => results.addResult(white, black) - case Glicko.Result.Loss => results.addResult(black, white) + case Glicko.Result.Draw => results.addDraw(white.setAdvantage(5), black.setAdvantage(-5)) + case Glicko.Result.Win => results.addResult(white.setAdvantage(5), black.setAdvantage(-5)) + case Glicko.Result.Loss => results.addResult(black.setAdvantage(-5), white.setAdvantage(5)) } try { Glicko.system.updateRatings(results, true)