From 74630e1ad35a928d2c750a7579fcc01cbfa5e2df Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Mon, 1 Jun 2015 17:27:48 +0200 Subject: [PATCH] display provisional rating - closes #444 --- app/templating/UserHelper.scala | 23 +++++++++++++---------- modules/game/src/main/GameRepo.scala | 12 +++++++----- modules/game/src/main/Namer.scala | 10 +++++++--- modules/game/src/main/Player.scala | 11 +++++++---- modules/rating/src/main/Perf.scala | 2 ++ ui/game/src/view/user.js | 3 ++- ui/round/src/view/table.js | 4 ++-- 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/app/templating/UserHelper.scala b/app/templating/UserHelper.scala index 93e0d36ce8..930a8991c1 100644 --- a/app/templating/UserHelper.scala +++ b/app/templating/UserHelper.scala @@ -41,14 +41,16 @@ trait UserHelper { self: I18nHelper with StringHelper => best3Of(u, List(PerfType.Bullet, PerfType.Blitz, PerfType.Classical, PerfType.Correspondence)) ::: best3Of(u, List(PerfType.Chess960, PerfType.KingOfTheHill, PerfType.ThreeCheck, PerfType.Antichess, PerfType.Atomic, PerfType.Horde)) - def showPerfRating(rating: Int, name: String, nb: Int, icon: Char, klass: String) = Html { + def showPerfRating(rating: Int, name: String, nb: Int, provisional: Boolean, icon: Char, klass: String) = Html { val title = s"$name rating over $nb games" val attr = if (klass == "title") "title" else "data-hint" - s"""${(nb > 0).fold(rating, "   -")}""" + val number = if (nb > 0) s"$rating${if (provisional) "?" else ""}" + else "   -" + s"""$number""" } def showPerfRating(perfType: PerfType, perf: Perf, klass: String): Html = - showPerfRating(perf.intRating, perfType.name, perf.nb, perfType.iconChar, klass) + showPerfRating(perf.intRating, perfType.name, perf.nb, perf.provisional, perfType.iconChar, klass) def showPerfRating(u: User, perfType: PerfType, klass: String = "hint--bottom"): Html = showPerfRating(perfType, u perfs perfType, klass) @@ -226,15 +228,16 @@ trait UserHelper { self: I18nHelper with StringHelper => s"""$content""" } + private def renderRating(perf: Perf) = + s" (${perf.intRating}${if (perf.provisional) "?" else ""})" + private def userRating(user: User, withPerfRating: Option[PerfType], withBestRating: Boolean) = - withPerfRating map (_.key) flatMap user.perfs.ratingOf map { rating => - s" ($rating)" - } getOrElse { - withBestRating ?? { - user.perfs.bestPerf ?? { - case (pt, perf) => s" ${showPerfRating(pt, perf, "hint--bottom")}" - } + withPerfRating match { + case Some(perfType) => renderRating(user.perfs(perfType)) + case _ if withBestRating => user.perfs.bestPerf ?? { + case (_, perf) => renderRating(perf) } + case _ => "" } private def userHref(username: String, params: String = "") = diff --git a/modules/game/src/main/GameRepo.scala b/modules/game/src/main/GameRepo.scala index 023e117dd1..858cbdc994 100644 --- a/modules/game/src/main/GameRepo.scala +++ b/modules/game/src/main/GameRepo.scala @@ -115,13 +115,15 @@ object GameRepo { s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white._2), s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black._2)))) - def setUsers(id: ID, white: Option[(String, Int)], black: Option[(String, Int)]) = + def setUsers(id: ID, white: Option[Player.UserInfo], black: Option[Player.UserInfo]) = (white.isDefined || black.isDefined) ?? { $update($select(id), BSONDocument("$set" -> BSONDocument( - s"${F.whitePlayer}.${Player.BSONFields.rating}" -> white.map(_._2).map(BSONInteger.apply), - s"${F.blackPlayer}.${Player.BSONFields.rating}" -> black.map(_._2).map(BSONInteger.apply), - F.playerUids -> lila.db.BSON.writer.listO(List(~white.map(_._1), ~black.map(_._1))), - F.playingUids -> List(white.map(_._1), black.map(_._1)).flatten.distinct + s"${F.whitePlayer}.${Player.BSONFields.rating}" -> white.map(_.rating).map(BSONInteger.apply), + s"${F.blackPlayer}.${Player.BSONFields.rating}" -> black.map(_.rating).map(BSONInteger.apply), + s"${F.whitePlayer}.${Player.BSONFields.provisional}" -> white.map(_.provisional).filter(identity), + s"${F.blackPlayer}.${Player.BSONFields.provisional}" -> black.map(_.provisional).filter(identity), + F.playerUids -> lila.db.BSON.writer.listO(List(~white.map(_.id), ~black.map(_.id))), + F.playingUids -> List(white.map(_.id), black.map(_.id)).flatten.distinct ))) } diff --git a/modules/game/src/main/Namer.scala b/modules/game/src/main/Namer.scala index ece024a479..2ef46d3905 100644 --- a/modules/game/src/main/Namer.scala +++ b/modules/game/src/main/Namer.scala @@ -13,12 +13,16 @@ object Namer { def player(p: Player, withRating: Boolean = true, withTitle: Boolean = true)(implicit lightUser: String => Option[LightUser]) = Html { p.aiLevel.fold( p.userId.flatMap(lightUser).fold(lila.user.User.anonymous) { user => - withRating.fold( - s"${withTitle.fold(user.titleNameHtml, user.name)} (${p.rating getOrElse "?"})", - withTitle.fold(user.titleName, user.name)) + if (withRating) s"${withTitle.fold(user.titleNameHtml, user.name)} (${ratingString(p)})" + else withTitle.fold(user.titleName, user.name) }) { level => s"A.I. level $level" } } + private def ratingString(p: Player) = p.rating match { + case Some(rating) => s"$rating${if (p.provisional) "?" else ""}" + case _ => "?" + } + def playerString(p: Player, withRating: Boolean = true, withTitle: Boolean = true)(implicit lightUser: String => Option[LightUser]) = player(p, withRating, withTitle)(lightUser).body.replace(" ", " ") } diff --git a/modules/game/src/main/Player.scala b/modules/game/src/main/Player.scala index b42833ba99..19030c8ac8 100644 --- a/modules/game/src/main/Player.scala +++ b/modules/game/src/main/Player.scala @@ -29,9 +29,8 @@ case class Player( def withUser(id: String, perf: lila.rating.Perf): Player = copy( userId = id.some, - rating = perf.intRating.some) - - def withRating(rating: Int) = copy(rating = rating.some) + rating = perf.intRating.some, + provisional = perf.glicko.provisional) def isAi = aiLevel.isDefined @@ -41,7 +40,9 @@ case class Player( def isUser(u: User) = userId.fold(false)(_ == u.id) - def userInfos: Option[(String, Int)] = (userId |@| rating).tupled + def userInfos: Option[Player.UserInfo] = (userId |@| rating) { + case (id, ra) => Player.UserInfo(id, ra, provisional) + } def wins = isWinner getOrElse false @@ -104,6 +105,8 @@ object Player { def suspicious = ply >= 20 && ply <= 30 } + case class UserInfo(id: String, rating: Int, provisional: Boolean) + import reactivemongo.bson.Macros implicit val holdAlertBSONHandler = Macros.handler[HoldAlert] diff --git a/modules/rating/src/main/Perf.scala b/modules/rating/src/main/Perf.scala index cb8399b493..2b7296a450 100644 --- a/modules/rating/src/main/Perf.scala +++ b/modules/rating/src/main/Perf.scala @@ -36,6 +36,8 @@ case class Perf( nb) def nonEmpty = nb > 0 + + def provisional = glicko.provisional } case object Perf { diff --git a/ui/game/src/view/user.js b/ui/game/src/view/user.js index 17f9ce4deb..dea6c1bf1f 100644 --- a/ui/game/src/view/user.js +++ b/ui/game/src/view/user.js @@ -17,9 +17,10 @@ module.exports = function(ctrl, player, klass) { href: '/@/' + player.user.username, target: game.isPlayerPlaying(ctrl.data) ? '_blank' : '_self', 'data-icon': 'r', + title: player.provisional ? 'Provisional rating' : null }, [ (player.user.title ? player.user.title + ' ' : '') + player.user.username, - rating ? ' (' + rating + ')' : '', + rating ? ' (' + rating + (player.provisional ? '?' : '') + ')' : '', ratingDiff(player), player.engine ? m('span[data-icon=j]', { title: ctrl.trans('thisPlayerUsesChessComputerAssistance') diff --git a/ui/round/src/view/table.js b/ui/round/src/view/table.js index 75ad3a0266..7ca9dc39a4 100644 --- a/ui/round/src/view/table.js +++ b/ui/round/src/view/table.js @@ -85,8 +85,8 @@ function renderTablePlay(ctrl) { return [ renderReplay(ctrl), m('div.control.icons', [ - game.abortable(ctrl.data) ? button.standard(ctrl, null, 'L', 'abortGame', 'abort') : - button.standard(ctrl, game.takebackable, 'i', 'proposeATakeback', 'takeback-yes', partial(ctrl.takebackYes)), + game.abortable(ctrl.data) ? button.standard(ctrl, null, 'L', 'abortGame', 'abort') : + button.standard(ctrl, game.takebackable, 'i', 'proposeATakeback', 'takeback-yes', partial(ctrl.takebackYes)), button.standard(ctrl, game.drawable, '2', 'offerDraw', 'draw-yes'), button.standard(ctrl, game.resignable, 'b', 'resign', 'resign') ]),