From 24ea61025a6a6ea8045882db654a3b1b221f63cd Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 21 Jul 2013 18:20:52 +0200 Subject: [PATCH] store, precompute and display user variant elos --- app/views/user/show.scala.html | 9 ++++ modules/db/src/main/api/enumerate.scala | 7 +++- modules/game/src/main/ComputeElos.scala | 44 +++++++++++++------- modules/user/src/main/User.scala | 3 ++ modules/user/src/main/UserRepo.scala | 7 +++- modules/user/src/main/VariantElos.scala | 55 +++++++++++++++++++++++++ modules/user/src/main/package.scala | 1 + todo | 3 +- 8 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 modules/user/src/main/VariantElos.scala diff --git a/app/views/user/show.scala.html b/app/views/user/show.scala.html index 5b29371c8a..eb2c902a3a 100644 --- a/app/views/user/show.scala.html +++ b/app/views/user/show.scala.html @@ -47,6 +47,15 @@ } } + @u.variantElos.toMap.map { + case (variant, x) => { +
+

@variant.toString

+ @x.elo ELO
+ @x.nb @trans.games() +
+ } + } } diff --git a/modules/db/src/main/api/enumerate.scala b/modules/db/src/main/api/enumerate.scala index 898b6d06e1..7d4ac698b9 100644 --- a/modules/db/src/main/api/enumerate.scala +++ b/modules/db/src/main/api/enumerate.scala @@ -23,6 +23,9 @@ object $enumerate { ) } - def fold[A: BSONDocumentReader, B: Zero](query: QueryBuilder)(f: (B, A) ⇒ B): Fu[B] = - query.cursor[A].enumerate |>>> Iteratee.fold(∅[B])(f) + def fold[A: BSONDocumentReader, B](query: QueryBuilder)(zero: B)(f: (B, A) ⇒ B): Fu[B] = + query.cursor[A].enumerate |>>> Iteratee.fold(zero)(f) + + def foldZero[A: BSONDocumentReader, B: Zero](query: QueryBuilder)(f: (B, A) ⇒ B): Fu[B] = + fold(query)(∅[B])(f) } diff --git a/modules/game/src/main/ComputeElos.scala b/modules/game/src/main/ComputeElos.scala index c37f7abe57..676be9b8a0 100644 --- a/modules/game/src/main/ComputeElos.scala +++ b/modules/game/src/main/ComputeElos.scala @@ -30,23 +30,39 @@ private[game] final class ComputeElos(system: ActorSystem) { funit } - def apply(user: User): Funit = $enumerate.fold[Option[Game], SpeedElos](gamesQuery(user)) { - case (elos, gameOption) ⇒ (for { + def apply(user: User): Funit = $enumerate.fold[Option[Game], User](gamesQuery(user))(user) { + case (user, gameOption) ⇒ (for { game ← gameOption player ← game player user opponentElo ← game.opponent(player).elo - } yield { - val speed = Speed(game.clock) - val speedElo = elos(speed) - val opponentSpeedElo = SubElo(0, opponentElo) - val (white, black) = player.color.fold[(eloCalculator.User, eloCalculator.User)]( - speedElo -> opponentSpeedElo, - opponentSpeedElo -> speedElo) - val newElos = eloCalculator.calculate(white, black, game.winnerColor) - val newElo = player.color.fold(newElos._1, newElos._2) - elos.addGame(speed, newElo) - }) | elos - } flatMap UserRepo.setSpeedElos(user.id) + } yield user.copy( + speedElos = { + val speed = Speed(game.clock) + val speedElo = user.speedElos(speed) + val opponentSpeedElo = SubElo(0, opponentElo) + val (white, black) = player.color.fold[(eloCalculator.User, eloCalculator.User)]( + speedElo -> opponentSpeedElo, + opponentSpeedElo -> speedElo) + val newElos = eloCalculator.calculate(white, black, game.winnerColor) + val newElo = player.color.fold(newElos._1, newElos._2) + user.speedElos.addGame(speed, newElo) + }, + variantElos = { + val variantElo = user.variantElos(game.variant) + val opponentVariantElo = SubElo(0, opponentElo) + val (white, black) = player.color.fold[(eloCalculator.User, eloCalculator.User)]( + variantElo -> opponentVariantElo, + opponentVariantElo -> variantElo) + val newElos = eloCalculator.calculate(white, black, game.winnerColor) + val newElo = player.color.fold(newElos._1, newElos._2) + user.variantElos.addGame(game.variant, newElo) + } + ) + ) | user + } flatMap { user ⇒ + UserRepo.setSpeedElos(user.id, user.speedElos) >> + UserRepo.setVariantElos(user.id, user.variantElos) + } private def usersQuery = $query.apply[User]( Json.obj( diff --git a/modules/user/src/main/User.scala b/modules/user/src/main/User.scala index e6d7958210..b6c5a6a4f9 100644 --- a/modules/user/src/main/User.scala +++ b/modules/user/src/main/User.scala @@ -9,6 +9,7 @@ case class User( username: String, elo: Int, speedElos: SpeedElos, + variantElos: VariantElos, count: Count, troll: Boolean = false, ipBan: Boolean = false, @@ -57,6 +58,7 @@ object User { private implicit def countTube = Count.tube private implicit def speedElosTube = SpeedElos.tube + private implicit def variantElosTube = VariantElos.tube private[user] lazy val tube = Tube[User]( (__.json update ( @@ -69,6 +71,7 @@ object User { private def defaults = Json.obj( "speedElos" -> SpeedElos.default, + "variantElos" -> VariantElos.default, "troll" -> false, "ipBan" -> false, "settings" -> Json.obj(), diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index 8a713b3a23..623b6f7f8e 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -146,11 +146,16 @@ object UserRepo { def setBio(id: ID, bio: String) = $update.field(id, "bio", bio) - def setSpeedElos(id: ID)(ses: SpeedElos) = { + def setSpeedElos(id: ID, ses: SpeedElos) = { import tube.speedElosTube $update.field(id, "speedElos", ses) } + def setVariantElos(id: ID, ses: VariantElos) = { + import tube.variantElosTube + $update.field(id, "variantElos", ses) + } + def enable(id: ID) = $update.field(id, "enabled", true) def disable(id: ID) = $update.field(id, "enabled", false) diff --git a/modules/user/src/main/VariantElos.scala b/modules/user/src/main/VariantElos.scala new file mode 100644 index 0000000000..25ab2685be --- /dev/null +++ b/modules/user/src/main/VariantElos.scala @@ -0,0 +1,55 @@ +package lila.user + +import chess.Variant +import lila.db.Tube +import play.api.libs.json._ +import Tube.Helpers._ + +case class VariantElos( + standard: SubElo, + chess960: SubElo) { + + def apply(variant: Variant) = variant match { + case Variant.Chess960 ⇒ chess960 + case _ ⇒ standard + } + + def toMap = Map( + Variant.Standard -> standard, + Variant.Chess960 -> chess960) + + def addGame(variant: Variant, newElo: Int) = variant match { + case Variant.Chess960 ⇒ copy(chess960 = chess960 addGame newElo) + case Variant.Standard ⇒ copy(standard = standard addGame newElo) + case _ ⇒ this + } + + def adjustTo(to: Int) = { + val nb = toMap.values.map(_.nb).sum + if (nb == 0) this else { + val median = (toMap.values map { + case SubElo(nb, elo) ⇒ nb * elo + }).sum / nb + val diff = to - median + def amortize(se: SubElo) = se withElo (se.elo + (diff * se.nb / nb)) + VariantElos( + standard = amortize(standard), + chess960 = amortize(chess960)) + } + } +} + +object VariantElos { + + val default = VariantElos(SubElo.default, SubElo.default) + + private implicit def subEloTube = SubElo.tube + + private[user] lazy val tube = Tube[VariantElos]( + __.json update merge(defaults) andThen Json.reads[VariantElos], + Json.writes[VariantElos]) + + private def defaults = Json.obj( + "standard" -> SubElo.default, + "chess960" -> SubElo.default) +} diff --git a/modules/user/src/main/package.scala b/modules/user/src/main/package.scala index 501df67762..a9895b3281 100644 --- a/modules/user/src/main/package.scala +++ b/modules/user/src/main/package.scala @@ -10,6 +10,7 @@ package object user extends PackageObject with WithPlay { implicit lazy val userTube = User.tube inColl Env.current.userColl private[user] implicit lazy val speedElosTube = SpeedElos.tube + private[user] implicit lazy val variantElosTube = VariantElos.tube private[user] implicit lazy val historyTube = Tube.json inColl Env.current.historyColl diff --git a/todo b/todo index d888017aa6..eb5f4ae825 100644 --- a/todo +++ b/todo @@ -70,8 +70,7 @@ customize piece images opera bug http://postimg.org/image/zcv8hse8n/full/ customize sound notifications http://imgur.com/70WVyb5 show friend game results on timeline -profile tweaks proposed by clarkey http://i.imgur.com/C57qoRC.jpg -bullet < 3 min +opera issue http://en.lichess.org/forum/lichess-feedback/new-game-wont-show-on-games-list-opera#1 ---