store, precompute and display user variant elos

pull/83/head
Thibault Duplessis 2013-07-21 18:20:52 +02:00
parent b7bb597954
commit 24ea61025a
8 changed files with 110 additions and 19 deletions

View File

@ -47,6 +47,15 @@
</div>
}
}
@u.variantElos.toMap.map {
case (variant, x) => {
<div class="speed_elos">
<h3>@variant.toString</h3>
<strong>@x.elo</strong> ELO<br/>
<strong>@x.nb</strong> @trans.games()
</div>
}
}
</div>
}

View File

@ -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)
}

View File

@ -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(

View File

@ -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(),

View File

@ -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)

View File

@ -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)
}

View File

@ -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

3
todo
View File

@ -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
---