lila/modules/rating/src/main/Glicko.scala

128 lines
3.6 KiB
Scala
Raw Permalink Normal View History

2014-02-03 11:53:10 -07:00
package lila.rating
2013-12-15 14:06:11 -07:00
import org.goochjs.glicko2._
import org.joda.time.DateTime
2019-11-29 19:16:11 -07:00
import reactivemongo.api.bson.BSONDocument
2013-12-15 14:06:11 -07:00
import lila.db.BSON
case class Glicko(
rating: Double,
2013-12-16 15:40:19 -07:00
deviation: Double,
volatility: Double
) {
def intRating = rating.toInt
def intDeviation = deviation.toInt
2014-02-08 04:28:36 -07:00
def intervalMin = (rating - deviation * 2).toInt
def intervalMax = (rating + deviation * 2).toInt
2019-12-13 07:30:20 -07:00
def interval = intervalMin -> intervalMax
2013-12-17 15:20:18 -07:00
2020-05-05 22:11:15 -06:00
def rankable(variant: chess.variant.Variant) =
deviation <= {
if (variant.standard) Glicko.standardRankableDeviation
else Glicko.variantRankableDeviation
}
2019-12-13 07:30:20 -07:00
def provisional = deviation >= Glicko.provisionalDeviation
def established = !provisional
def establishedIntRating = established option intRating
2020-09-23 02:51:36 -06:00
def clueless = deviation >= Glicko.cluelessDeviation
2016-08-26 05:53:56 -06:00
def refund(points: Int) = copy(rating = rating + points)
def sanityCheck =
rating > 0 &&
rating < 4000 &&
deviation > 0 &&
deviation < 1000 &&
volatility > 0 &&
2017-06-19 08:17:12 -06:00
volatility < (Glicko.maxVolatility * 2)
2020-05-05 22:11:15 -06:00
def cap =
copy(
rating = rating atLeast Glicko.minRating,
deviation = deviation atLeast Glicko.minDeviation atMost Glicko.maxDeviation,
volatility = volatility atMost Glicko.maxVolatility
)
def average(other: Glicko, weight: Float = 0.5f) =
2020-12-11 06:54:44 -07:00
if (weight >= 1) other
else if (weight <= 0) this
else
Glicko(
rating = rating * (1 - weight) + other.rating * weight,
deviation = deviation * (1 - weight) + other.deviation * weight,
volatility = volatility * (1 - weight) + other.volatility * weight
)
2019-04-07 23:29:41 -06:00
def display = s"$intRating${provisional ?? "?"}"
2020-12-09 13:27:17 -07:00
override def toString = f"$intRating/$intDeviation/${volatility}%.3f"
}
2013-12-15 14:06:11 -07:00
case object Glicko {
2019-06-28 14:15:36 -06:00
val minRating = 600
2014-07-26 03:03:58 -06:00
val minDeviation = 45
2019-12-13 07:30:20 -07:00
val variantRankableDeviation = 65
2019-12-03 20:41:36 -07:00
val standardRankableDeviation = 75
2019-12-13 07:30:20 -07:00
val provisionalDeviation = 110
2020-09-23 02:51:36 -06:00
val cluelessDeviation = 230
2020-09-22 06:31:41 -06:00
val maxDeviation = 500d
2017-06-19 08:17:12 -06:00
// past this, it might not stabilize ever again
val maxVolatility = 0.1d
val defaultVolatility = 0.09d
2017-06-19 08:17:12 -06:00
2018-02-02 11:51:17 -07:00
// Chosen so a typical player's RD goes from 60 -> 110 in 1 year
2019-06-30 14:45:34 -06:00
val ratingPeriodsPerDay = 0.21436d
2018-01-13 17:05:41 -07:00
val default = Glicko(1500d, maxDeviation, defaultVolatility)
// managed is for students invited to a class
val defaultManaged = Glicko(1100d, 400d, defaultVolatility)
val defaultManagedPuzzle = Glicko(1000d, 400d, defaultVolatility)
// bot accounts (usually a stockfish instance)
val defaultBot = Glicko(2000d, maxDeviation, defaultVolatility)
2020-09-23 03:46:48 -06:00
// rating that can be lost or gained with a single game
val maxRatingDelta = 700
2019-12-13 07:30:20 -07:00
val tau = 0.75d
2019-06-30 14:45:34 -06:00
val system = new RatingCalculator(default.volatility, tau, ratingPeriodsPerDay)
2019-06-29 08:54:49 -06:00
def liveDeviation(p: Perf, reverse: Boolean): Double = {
system.previewDeviation(p.toRating, new DateTime, reverse)
2019-06-29 08:54:49 -06:00
} atLeast minDeviation atMost maxDeviation
2014-02-06 11:22:28 -07:00
implicit val glickoBSONHandler = new BSON[Glicko] {
2013-12-15 14:06:11 -07:00
2020-05-05 22:11:15 -06:00
def reads(r: BSON.Reader): Glicko =
Glicko(
rating = r double "r",
deviation = r double "d",
volatility = r double "v"
)
def writes(w: BSON.Writer, o: Glicko) =
BSONDocument(
"r" -> w.double(o.rating),
"d" -> w.double(o.deviation),
"v" -> w.double(o.volatility)
)
2013-12-15 14:06:11 -07:00
}
sealed abstract class Result {
2013-12-16 15:40:19 -07:00
def negate: Result
}
object Result {
2019-12-13 07:30:20 -07:00
case object Win extends Result { def negate = Loss }
case object Loss extends Result { def negate = Win }
case object Draw extends Result { def negate = Draw }
2013-12-16 15:40:19 -07:00
}
2013-12-15 14:06:11 -07:00
}