From 394df62c46ee60e33f5df37c506d50fd24c96306 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Wed, 20 Nov 2024 11:19:44 +0100 Subject: [PATCH] refine glicko code --- .../main/scala/glicko/GlickoCalculator.scala | 6 +++--- .../src/main/scala/glicko/impl/Rating.scala | 5 +++-- .../scala/glicko/impl/RatingCalculator.scala | 19 +++++-------------- .../src/main/scala/glicko/impl/results.scala | 16 +++++++++------- rating/src/main/scala/glicko/model.scala | 18 +++++++++++++----- .../rating/glicko/GlickoCalculator.scala | 2 +- .../glicko/impl/RatingCalculatorTest.scala | 3 ++- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/rating/src/main/scala/glicko/GlickoCalculator.scala b/rating/src/main/scala/glicko/GlickoCalculator.scala index a0b7aa60..64507ccb 100644 --- a/rating/src/main/scala/glicko/GlickoCalculator.scala +++ b/rating/src/main/scala/glicko/GlickoCalculator.scala @@ -7,11 +7,11 @@ import scala.util.Try /* Purely functional interface hiding the mutable implementation */ final class GlickoCalculator( - tau: impl.Tau = impl.Tau.default, - ratingPeriodsPerDay: impl.RatingPeriodsPerDay = impl.RatingPeriodsPerDay.default + tau: Tau = Tau.default, + ratingPeriodsPerDay: RatingPeriodsPerDay = RatingPeriodsPerDay.default ): - private val calculator = impl.RatingCalculator(tau, ratingPeriodsPerDay) + private val calculator = new impl.RatingCalculator(tau, ratingPeriodsPerDay) // Simpler use case: a single game def computeGame(game: Game, skipDeviationIncrease: Boolean = false): Try[ByColor[Player]] = diff --git a/rating/src/main/scala/glicko/impl/Rating.scala b/rating/src/main/scala/glicko/impl/Rating.scala index 5bebaeac..71d278e9 100644 --- a/rating/src/main/scala/glicko/impl/Rating.scala +++ b/rating/src/main/scala/glicko/impl/Rating.scala @@ -1,6 +1,7 @@ -package chess.rating.glicko.impl +package chess.rating.glicko +package impl -final class Rating( +final private[glicko] class Rating( var rating: Double, var ratingDeviation: Double, var volatility: Double, diff --git a/rating/src/main/scala/glicko/impl/RatingCalculator.scala b/rating/src/main/scala/glicko/impl/RatingCalculator.scala index b0f651be..94ea96cc 100644 --- a/rating/src/main/scala/glicko/impl/RatingCalculator.scala +++ b/rating/src/main/scala/glicko/impl/RatingCalculator.scala @@ -1,19 +1,10 @@ -package chess.rating.glicko.impl - -import scalalib.extensions.ifTrue -import scalalib.newtypes.OpaqueDouble +package chess.rating.glicko +package impl import java.time.Instant +import scalalib.extensions.ifTrue -opaque type Tau = Double -object Tau extends OpaqueDouble[Tau]: - val default: Tau = 0.75d - -opaque type RatingPeriodsPerDay = Double -object RatingPeriodsPerDay extends OpaqueDouble[RatingPeriodsPerDay]: - val default: RatingPeriodsPerDay = 0d - -object RatingCalculator: +private object RatingCalculator: private val MULTIPLIER: Double = 173.7178 val DEFAULT_RATING: Double = 1500.0 @@ -30,7 +21,7 @@ object RatingCalculator: def convertRatingDeviationToGlicko2Scale(ratingDeviation: Double): Double = (ratingDeviation / MULTIPLIER) -final class RatingCalculator( +final private[glicko] class RatingCalculator( tau: Tau = Tau.default, ratingPeriodsPerDay: RatingPeriodsPerDay = RatingPeriodsPerDay.default ): diff --git a/rating/src/main/scala/glicko/impl/results.scala b/rating/src/main/scala/glicko/impl/results.scala index 59d972be..22551d7b 100644 --- a/rating/src/main/scala/glicko/impl/results.scala +++ b/rating/src/main/scala/glicko/impl/results.scala @@ -1,6 +1,7 @@ -package chess.rating.glicko.impl +package chess.rating.glicko +package impl -trait Result: +private[glicko] trait Result: def getScore(player: Rating): Double @@ -11,7 +12,7 @@ trait Result: def players: List[Rating] // score from 0 (opponent wins) to 1 (player wins) -class FloatingResult(player: Rating, opponent: Rating, score: Float) extends Result: +final private[glicko] class FloatingResult(player: Rating, opponent: Rating, score: Float) extends Result: def getScore(p: Rating) = if p == player then score else 1 - score @@ -21,7 +22,7 @@ class FloatingResult(player: Rating, opponent: Rating, score: Float) extends Res def players = List(player, opponent) -final class GameResult(winner: Rating, loser: Rating, isDraw: Boolean) extends Result: +final private[glicko] class GameResult(winner: Rating, loser: Rating, isDraw: Boolean) extends Result: private val POINTS_FOR_WIN = 1.0d private val POINTS_FOR_LOSS = 0.0d private val POINTS_FOR_DRAW = 0.5d @@ -50,12 +51,13 @@ final class GameResult(winner: Rating, loser: Rating, isDraw: Boolean) extends R override def toString = s"$winner vs $loser = $isDraw" -trait RatingPeriodResults[R <: Result](): +private[glicko] trait RatingPeriodResults[R <: Result](): val results: List[R] def getResults(player: Rating): List[R] = results.filter(_.participated(player)) def getParticipants: Set[Rating] = results.flatMap(_.players).toSet -class GameRatingPeriodResults(val results: List[GameResult]) extends RatingPeriodResults[GameResult] +final private[glicko] class GameRatingPeriodResults(val results: List[GameResult]) + extends RatingPeriodResults[GameResult] -class FloatingRatingPeriodResults(val results: List[FloatingResult]) +final private[glicko] class FloatingRatingPeriodResults(val results: List[FloatingResult]) extends RatingPeriodResults[FloatingResult] diff --git a/rating/src/main/scala/glicko/model.scala b/rating/src/main/scala/glicko/model.scala index fbe8ef90..e9855ce0 100644 --- a/rating/src/main/scala/glicko/model.scala +++ b/rating/src/main/scala/glicko/model.scala @@ -3,6 +3,7 @@ package glicko import chess.{ IntRating, ByColor, Outcome } import java.time.Instant +import scalalib.newtypes.OpaqueDouble case class Glicko( rating: Double, @@ -11,10 +12,10 @@ case class Glicko( ): def intRating: IntRating = IntRating(rating.toInt) def intDeviation = deviation.toInt - def provisional = RatingProvisional(deviation >= Glicko.provisionalDeviation) + def provisional = RatingProvisional(deviation >= provisionalDeviation) def established = provisional.no def establishedIntRating = Option.when(established)(intRating) - def clueless = deviation >= Glicko.cluelessDeviation + def clueless = deviation >= cluelessDeviation def display = s"$intRating${if provisional.yes then "?" else ""}" def average(other: Glicko, weight: Float = 0.5f): Glicko = if weight >= 1 then other @@ -27,9 +28,8 @@ case class Glicko( ) override def toString = f"$intRating/$intDeviation/${volatility}%.3f" -object Glicko: - val provisionalDeviation = 110 - val cluelessDeviation = 230 +val provisionalDeviation = 110 +val cluelessDeviation = 230 case class Player( glicko: Glicko, @@ -39,3 +39,11 @@ case class Player( export glicko.* case class Game(players: ByColor[Player], outcome: Outcome) + +opaque type Tau = Double +object Tau extends OpaqueDouble[Tau]: + val default: Tau = 0.75d + +opaque type RatingPeriodsPerDay = Double +object RatingPeriodsPerDay extends OpaqueDouble[RatingPeriodsPerDay]: + val default: RatingPeriodsPerDay = 0d diff --git a/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala b/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala index 22519334..344f30f7 100644 --- a/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala +++ b/test-kit/src/test/scala/rating/glicko/GlickoCalculator.scala @@ -7,7 +7,7 @@ import chess.{ ByColor, Outcome } class GlickoCalculatorTest extends ScalaCheckSuite with chess.MunitExtensions: val calc = GlickoCalculator( - ratingPeriodsPerDay = impl.RatingPeriodsPerDay(0.21436d) + ratingPeriodsPerDay = RatingPeriodsPerDay(0.21436d) ) def computeGame(players: ByColor[Player], outcome: Outcome) = diff --git a/test-kit/src/test/scala/rating/glicko/impl/RatingCalculatorTest.scala b/test-kit/src/test/scala/rating/glicko/impl/RatingCalculatorTest.scala index afa7c8cb..5e72431b 100644 --- a/test-kit/src/test/scala/rating/glicko/impl/RatingCalculatorTest.scala +++ b/test-kit/src/test/scala/rating/glicko/impl/RatingCalculatorTest.scala @@ -1,4 +1,5 @@ -package chess.rating.glicko.impl +package chess.rating.glicko +package impl import munit.ScalaCheckSuite import cats.syntax.all.*