From 8d95d6401d6d1f2e517fc35e883d7f95559e55a3 Mon Sep 17 00:00:00 2001 From: Daniel Dugovic Date: Fri, 22 Nov 2024 02:17:29 -0600 Subject: [PATCH] Rating color advantage test WIP (#598) --- .../rating/glicko/GlickoCalculator.scala | 9 +- ...ickoCalculatorWithColorAdvantageTest.scala | 168 +++++++++++++----- 2 files changed, 127 insertions(+), 50 deletions(-) diff --git a/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala b/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala index bd9f14afd..99ca9d11a 100644 --- a/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala +++ b/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala @@ -4,6 +4,11 @@ import chess.{ ByColor, Outcome } import munit.ScalaCheckSuite class GlickoCalculatorTest extends ScalaCheckSuite with chess.MunitExtensions: + // Validate results with reference implementations + // http://www.glicko.net/glicko/glicko2.pdf + val R: Double = 1500d + val RD: Double = 350d + val V: Double = 0.06d val calc = GlickoCalculator( ratingPeriodsPerDay = RatingPeriodsPerDay(0.21436d) @@ -15,9 +20,7 @@ class GlickoCalculatorTest extends ScalaCheckSuite with chess.MunitExtensions: { val players = ByColor.fill: Player( - Glicko(rating = 1500d, deviation = 500d, volatility = 0.09d), - numberOfResults = 0, - lastRatingPeriodEnd = None + Glicko(rating = R, deviation = RD, volatility = V) ) test("default deviation: white wins"): val (w, b) = computeGame(players, Outcome.white) diff --git a/test-kit/src/test/scala/rating/glicko/GlickoCalculatorWithColorAdvantageTest.scala b/test-kit/src/test/scala/rating/glicko/GlickoCalculatorWithColorAdvantageTest.scala index 3973d4fe4..cd0957eec 100644 --- a/test-kit/src/test/scala/rating/glicko/GlickoCalculatorWithColorAdvantageTest.scala +++ b/test-kit/src/test/scala/rating/glicko/GlickoCalculatorWithColorAdvantageTest.scala @@ -1,55 +1,129 @@ -package chess -package rating.glicko +package chess.rating.glicko -import cats.syntax.all.* +import chess.{ ByColor, Outcome } import munit.ScalaCheckSuite -class RatingCalculatorWithColorAdvantageTest extends ScalaCheckSuite with chess.MunitExtensions: - - val smallAdvantage = GlickoCalculator( - Tau.default, - RatingPeriodsPerDay.default, - ColorAdvantage(7d) - ) - - // test that the rating calculator correctly applies the color advantage - import Outcome.* +class GlickoCalculatorTest extends ScalaCheckSuite with chess.MunitExtensions: + // Validate results with reference implementations // http://www.glicko.net/glicko/glicko2.pdf - // If the player is unrated, set the rating to 1500 - // and the RD to 350. Set the player’s volatility to - // 0.06 (this value depends on the particular application) + val R: Double = 1500d val RD: Double = 350d val V: Double = 0.06d - private def ratingDiff(r: Int, opRating: Int, outcome: Outcome, expected: Int)(using - munit.Location - ) = - val player = Player(Glicko(r, RD, V)) - val opponent = Player(Glicko(opRating, RD, V)) - val game = Game(ByColor(player, opponent), outcome) - // TODO: calculator with color advantage - val calculator = GlickoCalculator() - calculator.computeGame(game) + val calc = GlickoCalculator( + ratingPeriodsPerDay = RatingPeriodsPerDay(0.21436d), + colorAdvantage = ColorAdvantage.standard +One + ) + + def computeGame(players: ByColor[Player], outcome: Outcome) = + calc.computeGame(Game(players, outcome), skipDeviationIncrease = true).get.toPair + + { + val players = ByColor.fill: + Player( + Glicko(rating = R, deviation = RD, volatility = V) + ) + test("default deviation: white wins"): + val (w, b) = computeGame(players, Outcome.white) + assertCloseTo(w.rating, 1741d, 1d) + assertCloseTo(b.rating, 1258d, 1d) + assertCloseTo(w.deviation, 396d, 1d) + assertCloseTo(b.deviation, 396d, 1d) + assertCloseTo(w.volatility, 0.0899983, 0.00000001d) + assertCloseTo(b.volatility, 0.0899983, 0.0000001d) + test("default deviation: black wins"): + val (w, b) = computeGame(players, Outcome.black) + assertCloseTo(w.rating, 1258d, 1d) + assertCloseTo(b.rating, 1741d, 1d) + assertCloseTo(w.deviation, 396d, 1d) + assertCloseTo(b.deviation, 396d, 1d) + assertCloseTo(w.volatility, 0.0899983, 0.00000001d) + assertCloseTo(b.volatility, 0.0899983, 0.0000001d) + test("default deviation: draw"): + val (w, b) = computeGame(players, Outcome.draw) + assertCloseTo(w.rating, 1500d, 1d) + assertCloseTo(b.rating, 1500d, 1d) + assertCloseTo(w.deviation, 396d, 1d) + assertCloseTo(b.deviation, 396d, 1d) + assertCloseTo(w.volatility, 0.0899954, 0.0000001d) + assertCloseTo(b.volatility, 0.0899954, 0.0000001d) + } + + { + val players = ByColor( + Player( + Glicko(rating = 1400d, deviation = 79d, volatility = 0.06d), + numberOfResults = 0, + lastRatingPeriodEnd = None + ), + Player( + Glicko(rating = 1550d, deviation = 110d, volatility = 0.065d), + numberOfResults = 0, + lastRatingPeriodEnd = None + ) + ) + test("mixed ratings and deviations: white wins"): + val (w, b) = computeGame(players, Outcome.white) + assertCloseTo(w.rating, 1422d, 1d) + assertCloseTo(b.rating, 1506d, 1d) + assertCloseTo(w.deviation, 77d, 1d) + assertCloseTo(b.deviation, 105d, 1d) + assertCloseTo(w.volatility, 0.06, 0.00001d) + assertCloseTo(b.volatility, 0.065, 0.00001d) + test("mixed ratings and deviations: black wins"): + val (w, b) = computeGame(players, Outcome.black) + assertCloseTo(w.rating, 1389d, 1d) + assertCloseTo(b.rating, 1568d, 1d) + assertCloseTo(w.deviation, 78d, 1d) + assertCloseTo(b.deviation, 105d, 1d) + assertCloseTo(w.volatility, 0.06, 0.00001d) + assertCloseTo(b.volatility, 0.065, 0.00001d) + test("mixed ratings and deviations: draw"): + val (w, b) = computeGame(players, Outcome.draw) + assertCloseTo(w.rating, 1406d, 1d) + assertCloseTo(b.rating, 1537d, 1d) + assertCloseTo(w.deviation, 78d, 1d) + assertCloseTo(b.deviation, 105.87d, 0.01d) + assertCloseTo(w.volatility, 0.06, 0.00001d) + assertCloseTo(b.volatility, 0.065, 0.00001d) + } - test("new rating calculation over one game"): - ratingDiff(1500, 1500, white, 20) - ratingDiff(1500, 1500, black, -20) - ratingDiff(1500, 1500, draw, 0) - ratingDiff(1500, 1900, white, 37) - ratingDiff(1500, 1900, black, -3) - ratingDiff(1500, 1900, draw, 17) - ratingDiff(1500, 2900, white, 37) - ratingDiff(1500, 2900, black, -3) - ratingDiff(1500, 2900, draw, 17) - ratingDiff(1500, 1600, white, 26) - ratingDiff(1500, 1600, black, -14) - ratingDiff(1500, 1600, draw, 6) - ratingDiff(2000, 1600, white, 3) - ratingDiff(2000, 1600, black, -37) - ratingDiff(2000, 1600, draw, -17) - ratingDiff(2000, 1000, white, 3) - ratingDiff(2000, 1000, black, -37) - ratingDiff(2000, 1000, draw, -17) - ratingDiff(2000, 1900, white, 14) - ratingDiff(2000, 1900, black, -26) - ratingDiff(2000, 1900, draw, -6) + { + val players = ByColor( + Player( + Glicko(rating = 1200d, deviation = 60d, volatility = 0.053d), + numberOfResults = 0, + lastRatingPeriodEnd = None + ), + Player( + Glicko(rating = 1850d, deviation = 200d, volatility = 0.062d), + numberOfResults = 0, + lastRatingPeriodEnd = None + ) + ) + test("more mixed ratings and deviations: white wins"): + val (w, b) = computeGame(players, Outcome.white) + assertCloseTo(w.rating, 1216.7d, 0.1d) + assertCloseTo(b.rating, 1636d, 0.1d) + assertCloseTo(w.deviation, 59.9d, 0.1d) + assertCloseTo(b.deviation, 196.9d, 0.1d) + assertCloseTo(w.volatility, 0.053013, 0.000001d) + assertCloseTo(b.volatility, 0.062028, 0.000001d) + test("more mixed ratings and deviations: black wins"): + val (w, b) = computeGame(players, Outcome.black) + assertCloseTo(w.rating, 1199.3d, 0.1d) + assertCloseTo(b.rating, 1855.4d, 0.1d) + assertCloseTo(w.deviation, 59.9d, 0.1d) + assertCloseTo(b.deviation, 196.9d, 0.1d) + assertCloseTo(w.volatility, 0.052999, 0.000001d) + assertCloseTo(b.volatility, 0.061999, 0.000001d) + test("more mixed ratings and deviations: draw"): + val (w, b) = computeGame(players, Outcome.draw) + assertCloseTo(w.rating, 1208.0, 0.1d) + assertCloseTo(b.rating, 1745.7, 0.1d) + assertCloseTo(w.deviation, 59.90056, 0.1d) + assertCloseTo(b.deviation, 196.98729, 0.1d) + assertCloseTo(w.volatility, 0.053002, 0.000001d) + assertCloseTo(b.volatility, 0.062006, 0.000001d) + }