2013-12-17 15:20:18 -07:00
|
|
|
package lila.round
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
import chess.{ Color, Speed }
|
2013-12-17 15:20:18 -07:00
|
|
|
import org.goochjs.glicko2._
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
import lila.game.{ Game, GameRepo, PerfPicker, RatingDiffs }
|
2014-07-26 13:22:21 -06:00
|
|
|
import lila.history.HistoryApi
|
2019-04-20 00:34:49 -06:00
|
|
|
import lila.rating.{ Glicko, Perf, RatingFactors, RatingRegulator, PerfType => PT }
|
2019-12-13 07:30:20 -07:00
|
|
|
import lila.user.{ Perfs, RankingApi, User, UserRepo }
|
2013-12-17 15:20:18 -07:00
|
|
|
|
2016-02-20 22:03:26 -07:00
|
|
|
final class PerfsUpdater(
|
2019-12-02 09:41:05 -07:00
|
|
|
gameRepo: GameRepo,
|
|
|
|
userRepo: UserRepo,
|
2016-02-20 22:03:26 -07:00
|
|
|
historyApi: HistoryApi,
|
2019-01-22 03:41:50 -07:00
|
|
|
rankingApi: RankingApi,
|
2019-04-20 00:34:49 -06:00
|
|
|
botFarming: BotFarming,
|
|
|
|
ratingFactors: () => RatingFactors
|
2019-12-13 18:17:43 -07:00
|
|
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
2013-12-17 15:20:18 -07:00
|
|
|
|
2017-07-07 06:59:54 -06:00
|
|
|
// returns rating diffs
|
2020-05-05 22:11:15 -06:00
|
|
|
def save(game: Game, white: User, black: User): Fu[Option[RatingDiffs]] =
|
|
|
|
botFarming(game) flatMap {
|
|
|
|
case true => fuccess(none)
|
|
|
|
case _ =>
|
|
|
|
PerfPicker.main(game) ?? { mainPerf =>
|
|
|
|
(game.rated && game.finished && game.accountable && !white.lame && !black.lame) ?? {
|
|
|
|
val ratingsW = mkRatings(white.perfs)
|
|
|
|
val ratingsB = mkRatings(black.perfs)
|
|
|
|
game.ratingVariant match {
|
|
|
|
case chess.variant.Chess960 =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.chess960, ratingsB.chess960, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.KingOfTheHill =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.kingOfTheHill, ratingsB.kingOfTheHill, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.ThreeCheck =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.threeCheck, ratingsB.threeCheck, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.Antichess =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.antichess, ratingsB.antichess, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.Atomic =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.atomic, ratingsB.atomic, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.Horde =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.horde, ratingsB.horde, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.RacingKings =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.racingKings, ratingsB.racingKings, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.Crazyhouse =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.crazyhouse, ratingsB.crazyhouse, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case chess.variant.Standard =>
|
|
|
|
game.speed match {
|
|
|
|
case Speed.Bullet =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.bullet, ratingsB.bullet, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case Speed.Blitz =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.blitz, ratingsB.blitz, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case Speed.Rapid =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.rapid, ratingsB.rapid, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case Speed.Classical =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.classical, ratingsB.classical, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case Speed.Correspondence =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.correspondence, ratingsB.correspondence, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
case Speed.UltraBullet =>
|
2021-03-21 05:37:41 -06:00
|
|
|
updateRatings(ratingsW.ultraBullet, ratingsB.ultraBullet, game)
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
|
|
|
case _ =>
|
|
|
|
}
|
|
|
|
val perfsW = mkPerfs(ratingsW, white -> black, game)
|
|
|
|
val perfsB = mkPerfs(ratingsB, black -> white, game)
|
|
|
|
def intRatingLens(perfs: Perfs) = mainPerf(perfs).glicko.intRating
|
|
|
|
val ratingDiffs = Color.Map(
|
|
|
|
intRatingLens(perfsW) - intRatingLens(white.perfs),
|
|
|
|
intRatingLens(perfsB) - intRatingLens(black.perfs)
|
|
|
|
)
|
|
|
|
gameRepo.setRatingDiffs(game.id, ratingDiffs) zip
|
|
|
|
userRepo.setPerfs(white, perfsW, white.perfs) zip
|
|
|
|
userRepo.setPerfs(black, perfsB, black.perfs) zip
|
|
|
|
historyApi.add(white, game, perfsW) zip
|
|
|
|
historyApi.add(black, game, perfsB) zip
|
|
|
|
rankingApi.save(white, game.perfType, perfsW) zip
|
|
|
|
rankingApi.save(black, game.perfType, perfsB) inject ratingDiffs.some
|
2017-12-02 05:09:00 -07:00
|
|
|
}
|
2014-07-26 01:50:19 -06:00
|
|
|
}
|
2020-05-05 22:11:15 -06:00
|
|
|
}
|
2013-12-17 15:20:18 -07:00
|
|
|
|
2019-12-08 11:21:43 -07:00
|
|
|
private case class Ratings(
|
2017-08-23 17:56:39 -06:00
|
|
|
chess960: Rating,
|
|
|
|
kingOfTheHill: Rating,
|
|
|
|
threeCheck: Rating,
|
|
|
|
antichess: Rating,
|
|
|
|
atomic: Rating,
|
|
|
|
horde: Rating,
|
|
|
|
racingKings: Rating,
|
|
|
|
crazyhouse: Rating,
|
|
|
|
ultraBullet: Rating,
|
|
|
|
bullet: Rating,
|
|
|
|
blitz: Rating,
|
2017-11-29 10:58:08 -07:00
|
|
|
rapid: Rating,
|
2017-08-23 17:56:39 -06:00
|
|
|
classical: Rating,
|
|
|
|
correspondence: Rating
|
2017-02-14 08:34:07 -07:00
|
|
|
)
|
2013-12-17 15:20:18 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
private def mkRatings(perfs: Perfs) =
|
|
|
|
Ratings(
|
|
|
|
chess960 = perfs.chess960.toRating,
|
|
|
|
kingOfTheHill = perfs.kingOfTheHill.toRating,
|
|
|
|
threeCheck = perfs.threeCheck.toRating,
|
|
|
|
antichess = perfs.antichess.toRating,
|
|
|
|
atomic = perfs.atomic.toRating,
|
|
|
|
horde = perfs.horde.toRating,
|
|
|
|
racingKings = perfs.racingKings.toRating,
|
|
|
|
crazyhouse = perfs.crazyhouse.toRating,
|
|
|
|
ultraBullet = perfs.ultraBullet.toRating,
|
|
|
|
bullet = perfs.bullet.toRating,
|
|
|
|
blitz = perfs.blitz.toRating,
|
|
|
|
rapid = perfs.rapid.toRating,
|
|
|
|
classical = perfs.classical.toRating,
|
|
|
|
correspondence = perfs.correspondence.toRating
|
|
|
|
)
|
2013-12-17 15:20:18 -07:00
|
|
|
|
2021-03-21 05:37:41 -06:00
|
|
|
private def updateRatings(white: Rating, black: Rating, game: Game): Unit = {
|
2021-09-30 06:51:49 -06:00
|
|
|
// if the winner is rated more than 500 higher, leave both ratings untouched.
|
|
|
|
// this helps prevent farming and keeping deviation low by playing 1500? players
|
|
|
|
val isObviousResult = game.winnerColor.?? {
|
|
|
|
case chess.White => white.getRating - black.getRating
|
|
|
|
case chess.Black => black.getRating - white.getRating
|
|
|
|
} > 500
|
|
|
|
if (!isObviousResult) {
|
|
|
|
val result = game.winnerColor match {
|
|
|
|
case Some(chess.White) => Glicko.Result.Win
|
|
|
|
case Some(chess.Black) => Glicko.Result.Loss
|
|
|
|
case None => Glicko.Result.Draw
|
|
|
|
}
|
|
|
|
val results = new RatingPeriodResults()
|
|
|
|
result match {
|
|
|
|
case Glicko.Result.Draw => results.addDraw(white, black)
|
|
|
|
case Glicko.Result.Win => results.addResult(white, black)
|
|
|
|
case Glicko.Result.Loss => results.addResult(black, white)
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
Glicko.system.updateRatings(results, true)
|
|
|
|
} catch {
|
|
|
|
case e: Exception => logger.error(s"update ratings #${game.id}", e)
|
|
|
|
}
|
2013-12-17 15:20:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
private def mkPerfs(ratings: Ratings, users: (User, User), game: Game): Perfs =
|
|
|
|
users match {
|
|
|
|
case (player, opponent) =>
|
|
|
|
val perfs = player.perfs
|
|
|
|
val speed = game.speed
|
|
|
|
val isStd = game.ratingVariant.standard
|
|
|
|
val isHumanVsMachine = player.noBot && opponent.isBot
|
|
|
|
def addRatingIf(cond: Boolean, perf: Perf, rating: Rating) =
|
|
|
|
if (cond) {
|
|
|
|
val p = perf.addOrReset(_.round.error.glicko, s"game ${game.id}")(rating, game.movedAt)
|
2020-12-06 11:37:22 -07:00
|
|
|
if (isHumanVsMachine)
|
|
|
|
p.copy(glicko = p.glicko average perf.glicko) // halve rating diffs for human
|
2020-05-05 22:11:15 -06:00
|
|
|
else p
|
|
|
|
} else perf
|
|
|
|
val perfs1 = perfs.copy(
|
|
|
|
chess960 = addRatingIf(game.ratingVariant.chess960, perfs.chess960, ratings.chess960),
|
|
|
|
kingOfTheHill =
|
|
|
|
addRatingIf(game.ratingVariant.kingOfTheHill, perfs.kingOfTheHill, ratings.kingOfTheHill),
|
|
|
|
threeCheck = addRatingIf(game.ratingVariant.threeCheck, perfs.threeCheck, ratings.threeCheck),
|
|
|
|
antichess = addRatingIf(game.ratingVariant.antichess, perfs.antichess, ratings.antichess),
|
|
|
|
atomic = addRatingIf(game.ratingVariant.atomic, perfs.atomic, ratings.atomic),
|
|
|
|
horde = addRatingIf(game.ratingVariant.horde, perfs.horde, ratings.horde),
|
|
|
|
racingKings = addRatingIf(game.ratingVariant.racingKings, perfs.racingKings, ratings.racingKings),
|
|
|
|
crazyhouse = addRatingIf(game.ratingVariant.crazyhouse, perfs.crazyhouse, ratings.crazyhouse),
|
|
|
|
ultraBullet =
|
|
|
|
addRatingIf(isStd && speed == Speed.UltraBullet, perfs.ultraBullet, ratings.ultraBullet),
|
|
|
|
bullet = addRatingIf(isStd && speed == Speed.Bullet, perfs.bullet, ratings.bullet),
|
|
|
|
blitz = addRatingIf(isStd && speed == Speed.Blitz, perfs.blitz, ratings.blitz),
|
|
|
|
rapid = addRatingIf(isStd && speed == Speed.Rapid, perfs.rapid, ratings.rapid),
|
|
|
|
classical = addRatingIf(isStd && speed == Speed.Classical, perfs.classical, ratings.classical),
|
|
|
|
correspondence =
|
|
|
|
addRatingIf(isStd && speed == Speed.Correspondence, perfs.correspondence, ratings.correspondence)
|
|
|
|
)
|
|
|
|
val r = RatingRegulator(ratingFactors()) _
|
|
|
|
val perfs2 = perfs1.copy(
|
|
|
|
chess960 = r(PT.Chess960, perfs.chess960, perfs1.chess960),
|
|
|
|
kingOfTheHill = r(PT.KingOfTheHill, perfs.kingOfTheHill, perfs1.kingOfTheHill),
|
|
|
|
threeCheck = r(PT.ThreeCheck, perfs.threeCheck, perfs1.threeCheck),
|
|
|
|
antichess = r(PT.Antichess, perfs.antichess, perfs1.antichess),
|
|
|
|
atomic = r(PT.Atomic, perfs.atomic, perfs1.atomic),
|
|
|
|
horde = r(PT.Horde, perfs.horde, perfs1.horde),
|
|
|
|
racingKings = r(PT.RacingKings, perfs.racingKings, perfs1.racingKings),
|
|
|
|
crazyhouse = r(PT.Crazyhouse, perfs.crazyhouse, perfs1.crazyhouse),
|
|
|
|
bullet = r(PT.Bullet, perfs.bullet, perfs1.bullet),
|
|
|
|
blitz = r(PT.Blitz, perfs.blitz, perfs1.blitz),
|
|
|
|
rapid = r(PT.Rapid, perfs.rapid, perfs1.rapid),
|
|
|
|
classical = r(PT.Classical, perfs.classical, perfs1.classical),
|
|
|
|
correspondence = r(PT.Correspondence, perfs.correspondence, perfs1.correspondence)
|
|
|
|
)
|
|
|
|
if (isStd) perfs2.updateStandard else perfs2
|
|
|
|
}
|
2013-12-17 15:20:18 -07:00
|
|
|
}
|