improve on pools
This commit is contained in:
parent
82e3988cce
commit
8ff37890a3
|
@ -27,7 +27,7 @@ final class Env(
|
|||
nowPlaying = Env.round.nowPlaying,
|
||||
dailyPuzzle = Env.puzzle.daily,
|
||||
streamsOnAir = () => Env.tv.streamsOnAir,
|
||||
getPools = () => Env.pool.repo.all)
|
||||
getPools = () => Env.pool.repo.all apply true)
|
||||
|
||||
lazy val userInfo = mashup.UserInfo(
|
||||
countUsers = () => Env.user.countEnabled,
|
||||
|
|
|
@ -149,30 +149,40 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
s"$u1 vs $u2$clock"
|
||||
}
|
||||
|
||||
// whiteUsername 1-0 blackUsername
|
||||
def gameSummary(whiteUserId: String, blackUserId: String, finished: Boolean, result: Option[Boolean]) = {
|
||||
val res = if (finished) result match {
|
||||
case Some(true) => "1-0"
|
||||
case Some(false) => "0-1"
|
||||
case None => "½-½"
|
||||
}
|
||||
else "*"
|
||||
s"${usernameOrId(whiteUserId)} $res ${usernameOrId(blackUserId)}"
|
||||
}
|
||||
|
||||
def gameFen(game: Game, color: Color, ownerLink: Boolean = false, tv: Boolean = false)(implicit ctx: UserContext) = Html {
|
||||
val owner = ownerLink.fold(ctx.me flatMap game.player, none)
|
||||
var live = game.isBeingPlayed
|
||||
var isLive = game.isBeingPlayed
|
||||
val url = owner.fold(routes.Round.watcher(game.id, color.name)) { o =>
|
||||
routes.Round.player(game fullIdOf o.color)
|
||||
}
|
||||
"""<a href="%s" title="%s" class="mini_board parse_fen %s" data-live="%s" data-color="%s" data-fen="%s" data-lastmove="%s"></a>""".format(
|
||||
tv.fold(routes.Tv.index, url),
|
||||
gameTitle(game, color),
|
||||
live ?? ("live live_" + game.id),
|
||||
live ?? game.id,
|
||||
color.name,
|
||||
Forsyth exportBoard game.toChess.board,
|
||||
~game.castleLastMoveTime.lastMoveString)
|
||||
val href = tv.fold(routes.Tv.index, url)
|
||||
val title = gameTitle(game, color)
|
||||
val cssClass = isLive ?? ("live live_" + game.id)
|
||||
val live = isLive ?? game.id
|
||||
val fen = Forsyth exportBoard game.toChess.board
|
||||
val lastMove = ~game.castleLastMoveTime.lastMoveString
|
||||
s"""<a href="$href" title="$title" class="mini_board parse_fen $cssClass" data-live="$live" data-color="${color.name}" data-fen="$fen" data-lastmove="$lastMove"></a>"""
|
||||
}
|
||||
|
||||
def gameFenNoCtx(game: Game, color: Color, tv: Boolean = false, blank: Boolean = false) = Html {
|
||||
var live = game.isBeingPlayed
|
||||
var isLive = game.isBeingPlayed
|
||||
"""<a href="%s%s" title="%s" class="mini_board parse_fen %s" data-live="%s" data-color="%s" data-fen="%s" data-lastmove="%s"%s></a>""".format(
|
||||
blank ?? netBaseUrl,
|
||||
tv.fold(routes.Tv.index, routes.Round.watcher(game.id, color.name)),
|
||||
gameTitle(game, color),
|
||||
live ?? ("live live_" + game.id),
|
||||
live ?? game.id,
|
||||
isLive ?? ("live live_" + game.id),
|
||||
isLive ?? game.id,
|
||||
color.name,
|
||||
Forsyth exportBoard game.toChess.board,
|
||||
~game.castleLastMoveTime.lastMoveString,
|
||||
|
|
|
@ -28,20 +28,19 @@ goodies = goodies.some,
|
|||
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
|
||||
underchat = underchat.some) {
|
||||
|
||||
<div id="pool_side" class="scroll-shadow-soft">
|
||||
<div class="pairings">
|
||||
@p.pairings.take(50).map { pairing =>
|
||||
<a class="revert-underline" href="@routes.Round.watcher(pairing.gameId, "white")">
|
||||
@showPairingUser(pairing, pairing.user1) <em>vs</em> @showPairingUser(pairing, pairing.user2)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pool"
|
||||
data-id="@p.setup.id"
|
||||
data-version="@version"
|
||||
data-socket-url="@routes.Pool.websocket(p.setup.id)">
|
||||
<div class="pool_side scroll-shadow-soft">
|
||||
<div class="pairings">
|
||||
@p.pairings.take(50).map { pairing =>
|
||||
<a class="revert-underline" href="@routes.Round.watcher(pairing.gameId, "white")">
|
||||
@showPairingUser(pairing, pairing.user1) <em>vs</em> @showPairingUser(pairing, pairing.user2)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content_box no_padding pool_box pool_show">
|
||||
<h1 data-icon="8">
|
||||
@p.setup.name Pool
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@(p: lila.pool.Pool)(implicit ctx: Context)
|
||||
|
||||
<div class="standing_wrap">
|
||||
<div class="standing_wrap scroll-shadow-soft">
|
||||
<table class="slist standing">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="large" colspan="2">@trans.standing() (@p.nbPlayers)</th>
|
||||
<th>Recent games</th>
|
||||
<th>@p.setup.name rating</th>
|
||||
<th>Rating</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -19,17 +19,24 @@
|
|||
@lightUserLink(player.user)
|
||||
</td>
|
||||
<td class="recent_games">
|
||||
@score.recentGames.map { res =>
|
||||
<span class="@res match {
|
||||
case None => {draw}
|
||||
case Some(true) => {win}
|
||||
case Some(false) => {loss}
|
||||
}" data-icon="J"></span>
|
||||
@score.recentPairings.map { pairing =>
|
||||
<a href="@routes.Round.watcher(pairing.gameId, pairing.colorOf(player.user.id).??(_.toString))"
|
||||
data-hint="@gameSummary(pairing.user1, pairing.user2, pairing.finished, pairing.booleanResult)"
|
||||
class="hint--bottom-left
|
||||
@if(pairing.finished) {
|
||||
finished @if(pairing wonBy player.user.id) {win} else {
|
||||
@if(pairing lostBy player.user.id) {loss} else {draw}}
|
||||
">@pairing.ratingDiffOf(player.user.id) match {
|
||||
case Some(r) if r > 0 => {+@r}
|
||||
case Some(r) if r < 0 => {@r}
|
||||
case _ => {+0}
|
||||
}
|
||||
} else {
|
||||
playing">*
|
||||
}</a>
|
||||
}
|
||||
</td>
|
||||
<td class="rating">
|
||||
@player.rating
|
||||
</td>
|
||||
<td class="rating">@player.rating</td>
|
||||
</tr>
|
||||
<tr><td class="around-bar" colspan="4"><div class="bar" data-value="@score.ratingPercent"></div></td></tr>
|
||||
}
|
||||
|
@ -38,4 +45,3 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -16,8 +16,11 @@ private[pool] object AutoPairing {
|
|||
val (pairablePlayers, unpairablePlayers) = availablePlayers partition (_.pairable)
|
||||
val nbPlaying = pool.players count isPlaying
|
||||
|
||||
val pairings = if (pairablePlayers.size < ((nbPlaying + unpairablePlayers.size) / 2)) Nil
|
||||
else {
|
||||
val minPlayersForPairing = math.min(6, (nbPlaying + unpairablePlayers.size) / 3)
|
||||
val nbPlayersForPairing = pairablePlayers.size
|
||||
val canDoPairings = nbPlayersForPairing >= minPlayersForPairing && nbPlayersForPairing % 2 == 0
|
||||
|
||||
val pairings = canDoPairings ?? {
|
||||
|
||||
def basedOnRating = pairablePlayers.sortBy(-_.rating) grouped 2
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ case class Pairing(
|
|||
status: Status,
|
||||
user1: String,
|
||||
user2: String,
|
||||
user1RatingDiff: Option[Int],
|
||||
user2RatingDiff: Option[Int],
|
||||
turns: Int,
|
||||
winner: Option[String]) {
|
||||
|
||||
|
@ -24,6 +26,12 @@ case class Pairing(
|
|||
def wonBy(userId: String) = finished && winner.??(userId ==)
|
||||
def lostBy(userId: String) = finished && contains(userId) && winner.??(userId !=)
|
||||
|
||||
def booleanResult = winner match {
|
||||
case Some(uid) if user1 == uid => Some(true)
|
||||
case Some(uid) if user2 == uid => Some(false)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def opponentOf(user: String): Option[String] =
|
||||
if (user == user1) user2.some else if (user == user2) user1.some else none
|
||||
|
||||
|
@ -32,13 +40,23 @@ case class Pairing(
|
|||
else if (userId == user2) Color.Black.some
|
||||
else none
|
||||
|
||||
def ratingDiffOf(userId: String): Option[Int] =
|
||||
if (userId == user1) user1RatingDiff
|
||||
else if (userId == user2) user2RatingDiff
|
||||
else none
|
||||
|
||||
def povRef(userId: String): Option[PovRef] =
|
||||
colorOf(userId) map { PovRef(gameId, _) }
|
||||
|
||||
def withStatus(s: Status) = copy(status = s)
|
||||
|
||||
def finish(s: Status, t: Int, w: Option[String]) =
|
||||
copy(status = s, turns = t, winner = w)
|
||||
def finish(game: Game) =
|
||||
copy(
|
||||
status = game.status,
|
||||
turns = game.turns,
|
||||
user1RatingDiff = game.whitePlayer.ratingDiff,
|
||||
user2RatingDiff = game.blackPlayer.ratingDiff,
|
||||
winner = game.winnerUserId)
|
||||
}
|
||||
|
||||
case class PairingWithGame(pairing: Pairing, game: Game)
|
||||
|
@ -50,6 +68,8 @@ private[pool] object Pairing {
|
|||
status = Status.Created,
|
||||
user1 = user1,
|
||||
user2 = user2,
|
||||
user1RatingDiff = none,
|
||||
user2RatingDiff = none,
|
||||
turns = 0,
|
||||
winner = none)
|
||||
}
|
||||
|
|
|
@ -16,5 +16,5 @@ object Player {
|
|||
|
||||
case class Score(
|
||||
ratingPercent: Int,
|
||||
recentGames: List[Option[Boolean]])
|
||||
recentPairings: List[Pairing])
|
||||
}
|
||||
|
|
|
@ -18,14 +18,14 @@ case class Pool(
|
|||
lazy val nbPlayers = players.size
|
||||
|
||||
def scoreOf(p: Player) = Player.Score(
|
||||
ratingPercent = 100 * p.rating / bestRating,
|
||||
recentGames = pairings.foldLeft(List[Option[Boolean]]()) {
|
||||
case (res, pair) if pair.finished && pair.contains(p.user.id) && res.size <= 10 =>
|
||||
pair.winner.map(p.user.id ==) :: res
|
||||
ratingPercent = 100 * (p.rating - minRating) / math.max(1, (maxRating - minRating)),
|
||||
recentPairings = pairings.foldLeft(List[Pairing]()) {
|
||||
case (res, pairing) if pairing.contains(p.user.id) && res.size <= 10 => pairing :: res
|
||||
case (res, _) => res
|
||||
})
|
||||
|
||||
lazy val bestRating = players.map(_.rating).max
|
||||
lazy val maxRating = players.map(_.rating).max
|
||||
lazy val minRating = players.map(_.rating).min
|
||||
|
||||
def contains(userId: String): Boolean = players exists (_.user.id == userId)
|
||||
def contains(u: User): Boolean = contains(u.id)
|
||||
|
@ -78,7 +78,7 @@ case class Pool(
|
|||
|
||||
def finishGame(game: Game) = copy(
|
||||
pairings = pairings map {
|
||||
case p if p.gameId == game.id => p.finish(game.status, game.turns, game.winnerUserId)
|
||||
case p if p.gameId == game.id => p finish game
|
||||
case p => p
|
||||
}
|
||||
)
|
||||
|
|
|
@ -58,12 +58,17 @@ private[pool] final class PoolActor(
|
|||
pool.players map (_.user.id) filter isOnline foreach wavers.put
|
||||
pool.players filterNot (p => wavers get p.user.id) map (_.user.id) map Leave.apply foreach self.!
|
||||
|
||||
case FinishGame(game, Some(white), Some(black)) if game.poolId == Some(setup.id) =>
|
||||
pool = pool finishGame game
|
||||
UserRepo byIds List(white.id, black.id) map UpdateUsers.apply foreach self.!
|
||||
case FinishGame(g, Some(white), Some(black)) if g.poolId == Some(setup.id) =>
|
||||
GameRepo game g.id foreach {
|
||||
_ foreach { game =>
|
||||
pool = pool finishGame game
|
||||
UserRepo byIds List(white.id, black.id) map UpdateUsers.apply foreach self.!
|
||||
}
|
||||
}
|
||||
|
||||
case UpdateUsers(users) =>
|
||||
pool = pool updatePlayers users
|
||||
notifyReload
|
||||
|
||||
case RemindPlayers =>
|
||||
import makeTimeout.short
|
||||
|
@ -105,6 +110,8 @@ private[pool] final class PoolActor(
|
|||
status = game.status,
|
||||
user1 = user1,
|
||||
user2 = user2,
|
||||
user1RatingDiff = game.whitePlayer.ratingDiff,
|
||||
user2RatingDiff = game.blackPlayer.ratingDiff,
|
||||
turns = game.turns,
|
||||
winner = game.winnerUserId)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import akka.pattern.ask
|
|||
import lila.hub.actorApi.map.{ Ask, AskAll }
|
||||
import actorApi.GetPool
|
||||
import makeTimeout.short
|
||||
import scala.concurrent.duration._
|
||||
|
||||
final class PoolRepo(hub: ActorRef) {
|
||||
|
||||
|
@ -14,5 +15,9 @@ final class PoolRepo(hub: ActorRef) {
|
|||
case _: IllegalArgumentException => none
|
||||
}
|
||||
|
||||
def all: Fu[List[Pool]] = hub ? AskAll(GetPool) mapTo manifest[List[Pool]]
|
||||
val all = lila.memo.AsyncCache.single(fetchAll, timeToLive = 10.seconds)
|
||||
|
||||
private def fetchAll: Fu[List[Pool]] = hub ? AskAll(GetPool) mapTo manifest[List[Pool]] map { pools =>
|
||||
pools.sortBy(_.setup.id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,8 +49,9 @@ body.dark div.lichess_separator,
|
|||
body.dark div.lichess_table_wrap > div.clock > div.time,
|
||||
body.dark #GameText,
|
||||
body.dark #tournament_side,
|
||||
body.dark #pool_side,
|
||||
body.dark #pool > .pool_side,
|
||||
body.dark #tournament div.standing_wrap,
|
||||
body.dark #pool div.standing_wrap,
|
||||
body.dark #GameBoard,
|
||||
body.dark div.shortcuts .title,
|
||||
body.dark div.content_box,
|
||||
|
|
|
@ -4,16 +4,11 @@
|
|||
margin-right: -5px;
|
||||
}
|
||||
|
||||
#pool {
|
||||
#pool > .pool_box {
|
||||
width: 525px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#pool div.pool_box {
|
||||
width: 525px;
|
||||
}
|
||||
#pool table.slist .rank {
|
||||
font-weight: bold;
|
||||
}
|
||||
#pool_side {
|
||||
#pool > .pool_side {
|
||||
float: right;
|
||||
width: 246px;
|
||||
max-height: 510px;
|
||||
|
@ -21,37 +16,52 @@
|
|||
border: 1px solid #ccc;
|
||||
padding: 3px;
|
||||
}
|
||||
#pool_side:hover {
|
||||
#pool > .pool_side:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#pool_side div.pairings {
|
||||
#pool div.pairings {
|
||||
text-align: center;
|
||||
}
|
||||
#pool_side div.pairings a {
|
||||
#pool div.pairings a {
|
||||
padding: 0.3em 0.2em;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
#pool_side div.pairings span {
|
||||
#pool div.pairings span {
|
||||
font-weight: bold;
|
||||
}
|
||||
#pool_side div.pairings span.win {
|
||||
#pool div.pairings span.win {
|
||||
color: #00aa00;
|
||||
}
|
||||
#pool_side div.pairings span.loss {
|
||||
#pool div.pairings span.loss {
|
||||
color: #aa0000;
|
||||
}
|
||||
#pool_side div.pairings span.draw {
|
||||
#pool div.pairings span.draw {
|
||||
color: #aaaa00;
|
||||
}
|
||||
#pool_side div.pairings em {
|
||||
#pool div.pairings em {
|
||||
font-style: italic;
|
||||
}
|
||||
#pool div.game_list {
|
||||
margin: 25px 0 0 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#pool div.standing_wrap {
|
||||
max-height: 485px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
#pool div.standing_wrap:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#pool table.standing .rank {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#pool table.standing .rating {
|
||||
font-weight: bold;
|
||||
}
|
||||
#pool table.standing td.around-bar {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -64,10 +74,13 @@
|
|||
#pool table.standing td.recent_games {
|
||||
text-align: right;
|
||||
}
|
||||
#pool table.standing td.recent_games span {
|
||||
font-size: 1.2em;
|
||||
letter-spacing: -5px;
|
||||
color: #b0b060;
|
||||
#pool table.standing td.recent_games a {
|
||||
color: #888;
|
||||
text-decoration: none;
|
||||
margin-left: 1px;
|
||||
}
|
||||
#pool table.standing td.recent_games a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#pool table.standing td.recent_games .win {
|
||||
color: #759900;
|
||||
|
|
Loading…
Reference in a new issue