-
-
Notifications
You must be signed in to change notification settings - Fork 210
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #597 from lichess-org/glicko
import glicko implementation from lila
- Loading branch information
Showing
14 changed files
with
797 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package chess.rating | ||
package glicko | ||
|
||
import chess.{ Black, ByColor, Outcome, White } | ||
|
||
import java.time.Instant | ||
import scala.util.Try | ||
|
||
/* Purely functional interface hiding the mutable implementation */ | ||
final class GlickoCalculator( | ||
tau: Tau = Tau.default, | ||
ratingPeriodsPerDay: RatingPeriodsPerDay = RatingPeriodsPerDay.default | ||
): | ||
|
||
private val calculator = new impl.RatingCalculator(tau, ratingPeriodsPerDay) | ||
|
||
// Simpler use case: a single game | ||
def computeGame(game: Game, skipDeviationIncrease: Boolean = false): Try[ByColor[Player]] = | ||
val ratings = game.players.map(conversions.toRating) | ||
val gameResult = conversions.toGameResult(ratings, game.outcome) | ||
val periodResults = impl.GameRatingPeriodResults(List(gameResult)) | ||
Try: | ||
calculator.updateRatings(periodResults, skipDeviationIncrease) | ||
ratings.map(conversions.toPlayer) | ||
|
||
/** This is the formula defined in step 6. It is also used for players who have not competed during the rating period. */ | ||
def previewDeviation(player: Player, ratingPeriodEndDate: Instant, reverse: Boolean): Double = | ||
calculator.previewDeviation(conversions.toRating(player), ratingPeriodEndDate, reverse) | ||
|
||
/** Apply rating calculations and return updated players. | ||
* Note that players who did not compete during the rating period will have see their deviation increase. | ||
* This requires players to have some sort of unique identifier. | ||
*/ | ||
// def computeGames( games: List[Game], skipDeviationIncrease: Boolean = false): List[Player] | ||
|
||
private object conversions: | ||
|
||
import impl.* | ||
|
||
def toGameResult(ratings: ByColor[Rating], outcome: Outcome): GameResult = | ||
outcome.winner match | ||
case None => GameResult(ratings.white, ratings.black, true) | ||
case Some(White) => GameResult(ratings.white, ratings.black, false) | ||
case Some(Black) => GameResult(ratings.black, ratings.white, false) | ||
|
||
def toRating(player: Player) = impl.Rating( | ||
rating = player.rating, | ||
ratingDeviation = player.deviation, | ||
volatility = player.volatility, | ||
numberOfResults = player.numberOfResults, | ||
lastRatingPeriodEnd = player.lastRatingPeriodEnd | ||
) | ||
|
||
def toPlayer(rating: Rating) = Player( | ||
glicko = Glicko( | ||
rating = rating.rating, | ||
deviation = rating.ratingDeviation, | ||
volatility = rating.volatility | ||
), | ||
numberOfResults = rating.numberOfResults, | ||
lastRatingPeriodEnd = rating.lastRatingPeriodEnd | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Loosely ported from java: https://github.com/goochjs/glicko2 | ||
|
||
The implementation is not idiomatic scala and should not be used directly. | ||
Use the public API `chess.rating.glicko.GlickoCalculator` instead. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package chess.rating.glicko | ||
package impl | ||
|
||
final private[glicko] class Rating( | ||
var rating: Double, | ||
var ratingDeviation: Double, | ||
var volatility: Double, | ||
var numberOfResults: Int, | ||
var lastRatingPeriodEnd: Option[java.time.Instant] = None | ||
): | ||
|
||
import RatingCalculator.* | ||
|
||
// the following variables are used to hold values temporarily whilst running calculations | ||
private[impl] var workingRating: Double = scala.compiletime.uninitialized | ||
private[impl] var workingRatingDeviation: Double = scala.compiletime.uninitialized | ||
private[impl] var workingVolatility: Double = scala.compiletime.uninitialized | ||
|
||
/** Return the average skill value of the player scaled down to the scale used by the algorithm's internal | ||
* workings. | ||
*/ | ||
private[impl] def getGlicko2Rating: Double = convertRatingToGlicko2Scale(this.rating) | ||
|
||
/** Set the average skill value, taking in a value in Glicko2 scale. | ||
*/ | ||
private[impl] def setGlicko2Rating(r: Double) = | ||
rating = convertRatingToOriginalGlickoScale(r) | ||
|
||
/** Return the rating deviation of the player scaled down to the scale used by the algorithm's internal | ||
* workings. | ||
*/ | ||
private[impl] def getGlicko2RatingDeviation: Double = convertRatingDeviationToGlicko2Scale(ratingDeviation) | ||
|
||
/** Set the rating deviation, taking in a value in Glicko2 scale. | ||
*/ | ||
private[impl] def setGlicko2RatingDeviation(rd: Double) = | ||
ratingDeviation = convertRatingDeviationToOriginalGlickoScale(rd) | ||
|
||
/** Used by the calculation engine, to move interim calculations into their "proper" places. | ||
*/ | ||
private[impl] def finaliseRating() = | ||
setGlicko2Rating(workingRating) | ||
setGlicko2RatingDeviation(workingRatingDeviation) | ||
volatility = workingVolatility | ||
workingRatingDeviation = 0d | ||
workingRating = 0d | ||
workingVolatility = 0d | ||
|
||
private[impl] def incrementNumberOfResults(increment: Int) = | ||
numberOfResults = numberOfResults + increment | ||
|
||
override def toString = f"Rating($rating%1.2f, $ratingDeviation%1.2f, $volatility%1.2f, $numberOfResults)" |
Oops, something went wrong.