2014-02-03 11:53:10 -07:00
|
|
|
package lila.rating
|
2013-12-15 14:06:11 -07:00
|
|
|
|
2018-04-29 18:42:26 -06: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(
|
2013-12-15 16:16:00 -07:00
|
|
|
rating: Double,
|
2013-12-16 15:40:19 -07:00
|
|
|
deviation: Double,
|
2017-02-14 08:34:07 -07:00
|
|
|
volatility: Double
|
|
|
|
) {
|
2013-12-15 16:16:00 -07:00
|
|
|
|
2020-12-22 14:08:21 -07:00
|
|
|
def intRating = rating.toInt
|
|
|
|
def intDeviation = deviation.toInt
|
2013-12-15 16:16:00 -07:00
|
|
|
|
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
|
2015-09-21 17:15:43 -06:00
|
|
|
def establishedIntRating = established option intRating
|
2015-06-01 08:18:38 -06:00
|
|
|
|
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)
|
|
|
|
|
2016-03-10 12:26:50 -07:00
|
|
|
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
|
|
|
|
)
|
2016-03-10 12:26:50 -07:00
|
|
|
|
2020-12-06 11:37:22 -07:00
|
|
|
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
|
|
|
|
)
|
2018-04-19 16:00:45 -06:00
|
|
|
|
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 16:16:00 -07:00
|
|
|
}
|
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
|
|
|
|
2020-01-07 12:39:06 -07: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
|
2015-06-11 17:51:17 -06:00
|
|
|
|
2017-06-19 08:17:12 -06:00
|
|
|
// past this, it might not stabilize ever again
|
2020-09-23 02:54:18 -06:00
|
|
|
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
|
|
|
|
2020-09-23 02:54:18 -06:00
|
|
|
val default = Glicko(1500d, maxDeviation, defaultVolatility)
|
2020-09-21 01:56:19 -06:00
|
|
|
|
|
|
|
// managed is for students invited to a class
|
2021-08-06 03:43:30 -06:00
|
|
|
val defaultManaged = Glicko(1100d, 400d, defaultVolatility)
|
2020-09-23 02:54:18 -06:00
|
|
|
val defaultManagedPuzzle = Glicko(1000d, 400d, defaultVolatility)
|
2021-08-06 03:43:30 -06:00
|
|
|
|
|
|
|
// bot accounts (usually a stockfish instance)
|
|
|
|
val defaultBot = Glicko(2000d, maxDeviation, defaultVolatility)
|
2020-09-21 01:56:19 -06:00
|
|
|
|
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)
|
2018-04-29 18:42:26 -06:00
|
|
|
|
2019-06-29 08:54:49 -06:00
|
|
|
def liveDeviation(p: Perf, reverse: Boolean): Double = {
|
2019-08-19 04:45:13 -06:00
|
|
|
system.previewDeviation(p.toRating, new DateTime, reverse)
|
2019-06-29 08:54:49 -06:00
|
|
|
} atLeast minDeviation atMost maxDeviation
|
2018-04-29 21:47:31 -06:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-10-02 13:02:37 -06: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 }
|
2019-10-02 13:02:37 -06:00
|
|
|
case object Draw extends Result { def negate = Draw }
|
2013-12-16 15:40:19 -07:00
|
|
|
}
|
2013-12-15 14:06:11 -07:00
|
|
|
}
|