lila/modules/tv/src/main/Tv.scala

251 lines
8.0 KiB
Scala

package lila.tv
import lila.common.LightUser
import lila.game.{ Game, GameRepo, Pov }
import lila.hub.SyncActor
final class Tv(
gameRepo: GameRepo,
trouper: SyncActor,
gameProxyRepo: lila.round.GameProxyRepo
)(implicit ec: scala.concurrent.ExecutionContext) {
import Tv._
import ChannelSyncActor._
private def roundProxyGame = gameProxyRepo.game _
def getGame(channel: Tv.Channel): Fu[Option[Game]] =
trouper.ask[Option[Game.ID]](TvSyncActor.GetGameId(channel, _)) flatMap { _ ?? roundProxyGame }
def getReplacementGame(channel: Tv.Channel, oldId: Game.ID, exclude: List[Game.ID]): Fu[Option[Game]] =
trouper
.ask[Option[Game.ID]](TvSyncActor.GetReplacementGameId(channel, oldId, exclude, _))
.flatMap { _ ?? roundProxyGame }
def getGameAndHistory(channel: Tv.Channel): Fu[Option[(Game, List[Pov])]] =
trouper.ask[GameIdAndHistory](TvSyncActor.GetGameIdAndHistory(channel, _)) flatMap {
case GameIdAndHistory(gameId, historyIds) =>
for {
game <- gameId ?? roundProxyGame
games <-
historyIds
.map { id =>
roundProxyGame(id) orElse gameRepo.game(id)
}
.sequenceFu
.dmap(_.flatten)
history = games map Pov.naturalOrientation
} yield game map (_ -> history)
}
def getGames(channel: Tv.Channel, max: Int): Fu[List[Game]] =
getGameIds(channel, max) flatMap {
_.map(roundProxyGame).sequenceFu.map(_.flatten)
}
def getGameIds(channel: Tv.Channel, max: Int): Fu[List[Game.ID]] =
trouper.ask[List[Game.ID]](TvSyncActor.GetGameIds(channel, max, _))
def getBestGame = getGame(Tv.Channel.Best) orElse gameRepo.random
def getBestAndHistory = getGameAndHistory(Tv.Channel.Best)
def getChampions: Fu[Champions] =
trouper.ask[Champions](TvSyncActor.GetChampions.apply)
}
object Tv {
import chess.{ Speed => S, variant => V }
import lila.rating.{ PerfType => P }
case class Champion(user: LightUser, rating: Int, gameId: Game.ID)
case class Champions(channels: Map[Channel, Champion]) {
def get = channels.get _
}
private[tv] case class Candidate(game: Game, hasBot: Boolean)
private[tv] def toCandidate(lightUser: LightUser.GetterSync)(game: Game) =
Tv.Candidate(
game = game,
hasBot = game.userIds.exists { userId =>
lightUser(userId).exists(_.isBot)
}
)
sealed abstract class Channel(
val name: String,
val icon: String,
val secondsSinceLastMove: Int,
filters: Seq[Candidate => Boolean]
) {
def isFresh(g: Game): Boolean = fresh(secondsSinceLastMove, g)
def filter(c: Candidate): Boolean = filters.forall { _(c) } && isFresh(c.game)
val key = s"${toString.head.toLower}${toString.drop(1)}"
}
object Channel {
case object Best
extends Channel(
name = "Top Rated",
icon = "",
secondsSinceLastMove = freshBlitz,
filters = Seq(rated(2150), standard, noBot)
)
case object Bullet
extends Channel(
name = S.Bullet.name,
icon = P.Bullet.iconChar.toString,
secondsSinceLastMove = 35,
filters = Seq(speed(S.Bullet), rated(2000), standard, noBot)
)
case object Blitz
extends Channel(
name = S.Blitz.name,
icon = P.Blitz.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(speed(S.Blitz), rated(2000), standard, noBot)
)
case object Rapid
extends Channel(
name = S.Rapid.name,
icon = P.Rapid.iconChar.toString,
secondsSinceLastMove = 60 * 5,
filters = Seq(speed(S.Rapid), rated(1800), standard, noBot)
)
case object Classical
extends Channel(
name = S.Classical.name,
icon = P.Classical.iconChar.toString,
secondsSinceLastMove = 60 * 8,
filters = Seq(speed(S.Classical), rated(1650), standard, noBot)
)
case object Chess960
extends Channel(
name = V.Chess960.name,
icon = P.Chess960.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.Chess960), noBot)
)
case object KingOfTheHill
extends Channel(
name = V.KingOfTheHill.name,
icon = P.KingOfTheHill.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.KingOfTheHill), noBot)
)
case object ThreeCheck
extends Channel(
name = V.ThreeCheck.name,
icon = P.ThreeCheck.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.ThreeCheck), noBot)
)
case object Antichess
extends Channel(
name = V.Antichess.name,
icon = P.Antichess.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.Antichess), noBot)
)
case object Atomic
extends Channel(
name = V.Atomic.name,
icon = P.Atomic.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.Atomic), noBot)
)
case object Horde
extends Channel(
name = V.Horde.name,
icon = P.Horde.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.Horde), noBot)
)
case object RacingKings
extends Channel(
name = V.RacingKings.name,
icon = P.RacingKings.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.RacingKings), noBot)
)
case object Crazyhouse
extends Channel(
name = V.Crazyhouse.name,
icon = P.Crazyhouse.iconChar.toString,
secondsSinceLastMove = freshBlitz,
filters = Seq(variant(V.Crazyhouse), noBot)
)
case object UltraBullet
extends Channel(
name = S.UltraBullet.name,
icon = P.UltraBullet.iconChar.toString,
secondsSinceLastMove = 20,
filters = Seq(speed(S.UltraBullet), rated(1600), standard, noBot)
)
case object Bot
extends Channel(
name = "Bot",
icon = "",
secondsSinceLastMove = freshBlitz,
filters = Seq(standard, hasBot)
)
case object Computer
extends Channel(
name = "Computer",
icon = "",
secondsSinceLastMove = freshBlitz,
filters = Seq(computerFromInitialPosition)
)
val all = List(
Best,
Bullet,
Blitz,
Rapid,
Classical,
Crazyhouse,
Chess960,
KingOfTheHill,
ThreeCheck,
Antichess,
Atomic,
Horde,
RacingKings,
UltraBullet,
Bot,
Computer
)
val byKey = all.map { c =>
c.key -> c
}.toMap
}
private def rated(min: Int) = (c: Candidate) => c.game.rated && hasMinRating(c.game, min)
private def speed(speed: chess.Speed) = (c: Candidate) => c.game.speed == speed
private def variant(variant: chess.variant.Variant) = (c: Candidate) => c.game.variant == variant
private val standard = variant(V.Standard)
private val freshBlitz = 60 * 2
private def computerFromInitialPosition(c: Candidate) = c.game.hasAi && !c.game.fromPosition
private def hasBot(c: Candidate) = c.hasBot
private def noBot(c: Candidate) = !c.hasBot
private def fresh(seconds: Int, game: Game): Boolean = {
game.isBeingPlayed && !game.olderThan(seconds)
} || {
game.finished && !game.olderThan(7)
} // rematch time
private def hasMinRating(g: Game, min: Int) = g.players.exists(_.rating.exists(_ >= min))
private[tv] val titleScores = Map(
"GM" -> 500,
"WGM" -> 500,
"IM" -> 300,
"WIM" -> 300,
"FM" -> 200,
"WFM" -> 200,
"NM" -> 100,
"CM" -> 100,
"WCM" -> 100,
"WNM" -> 100
)
}