lila/app/controllers/Round.scala

323 lines
11 KiB
Scala
Raw Normal View History

2013-03-25 11:52:18 -06:00
package controllers
2013-05-31 09:55:11 -06:00
import play.api.libs.json._
import play.api.mvc._
2013-12-27 15:12:20 -07:00
import lila.api.Context
2013-05-07 17:44:26 -06:00
import lila.app._
2017-08-17 16:07:43 -06:00
import lila.chat.Chat
2018-12-15 02:23:41 -07:00
import lila.common.HTTPRequest
2019-12-08 11:12:00 -07:00
import lila.game.{ Pov, Game => GameModel, PgnDump }
import lila.tournament.{ Tournament => Tour }
2017-01-15 05:26:08 -07:00
import lila.user.{ User => UserModel }
2013-05-31 09:55:11 -06:00
import views._
2013-03-25 11:52:18 -06:00
2019-12-04 21:32:03 -07:00
final class Round(
env: Env,
2019-12-05 14:51:18 -07:00
gameC: => Game,
challengeC: => Challenge,
analyseC: => Analyse,
tournamentC: => Tournament
2019-12-13 07:30:20 -07:00
) extends LilaController(env)
with TheftPrevention {
2013-03-25 11:52:18 -06:00
2019-12-04 16:39:16 -07:00
private def analyser = env.analyse.analyser
2013-03-25 11:52:18 -06:00
2019-12-13 07:30:20 -07:00
private def renderPlayer(pov: Pov)(implicit ctx: Context): Fu[Result] =
negotiate(
html =
if (!pov.game.started) notFound
else
PreventTheft(pov) {
pov.game.playableByAi ?? env.fishnet.player(pov.game)
env.tournament.api.miniView(pov.game, true) flatMap {
2019-12-13 07:30:20 -07:00
tour =>
gameC.preloadUsers(pov.game) zip
(pov.game.simulId ?? env.simul.repo.find) zip
getPlayerChat(pov.game, tour.map(_.tour)) zip
(ctx.noBlind ?? env.game.crosstableApi
.withMatchup(pov.game)) zip
2019-12-13 07:30:20 -07:00
(pov.game.isSwitchable ?? otherPovs(pov.game)) zip
env.bookmark.api.exists(pov.game, ctx.me) zip
env.api.roundApi.player(pov, lila.api.Mobile.Api.currentVersion) map {
case _ ~ simul ~ chatOption ~ crosstable ~ playing ~ bookmarked ~ data =>
simul foreach env.simul.api.onPlayerConnection(pov.game, ctx.me)
Ok(
html.round.player(
pov,
data,
tour = tour,
simul = simul,
cross = crosstable,
playing = playing,
chatOption = chatOption,
bookmarked = bookmarked
)
)
}
2019-11-26 16:03:52 -07:00
}
2019-12-13 07:30:20 -07:00
},
api = apiVersion => {
if (isTheft(pov)) fuccess(theftResponse)
else {
pov.game.playableByAi ?? env.fishnet.player(pov.game)
gameC.preloadUsers(pov.game) zip
env.api.roundApi.player(pov, apiVersion) zip
getPlayerChat(pov.game, none) map {
case _ ~ data ~ chat =>
Ok {
data.add("chat", chat.flatMap(_.game).map(c => lila.chat.JsonView(c.chat)))
}
2017-07-29 10:29:40 -06:00
}
2019-12-13 07:30:20 -07:00
}
2019-11-26 16:03:52 -07:00
}
2019-12-13 07:30:20 -07:00
) dmap NoCache
def player(fullId: String) = Open { implicit ctx =>
2019-12-04 21:46:58 -07:00
OptionFuResult(env.round.proxyRepo.pov(fullId)) { pov =>
renderPlayer(pov)
2013-05-07 17:44:26 -06:00
}
}
private def otherPovs(game: GameModel)(implicit ctx: Context) = ctx.me ?? { user =>
2019-12-04 21:32:03 -07:00
env.round.proxyRepo urgentGames user map {
_ filter { pov =>
2018-04-07 11:07:26 -06:00
pov.gameId != game.id && pov.game.isSwitchable && pov.game.isSimul == game.isSimul
}
2014-12-02 17:34:34 -07:00
}
}
2019-12-08 11:12:00 -07:00
private def getNext(currentGame: GameModel)(povs: List[Pov]) =
2014-12-23 12:07:31 -07:00
povs find { pov =>
pov.isMyTurn && (pov.game.hasClock || !currentGame.hasClock)
}
2014-12-23 12:07:31 -07:00
def whatsNext(fullId: String) = Open { implicit ctx =>
2019-12-04 21:46:58 -07:00
OptionFuResult(env.round.proxyRepo.pov(fullId)) { currentPov =>
if (currentPov.isMyTurn) fuccess {
2015-10-06 12:00:36 -06:00
Ok(Json.obj("nope" -> true))
2019-12-13 07:30:20 -07:00
} else
otherPovs(currentPov.game) map getNext(currentPov.game) map { next =>
Ok(Json.obj("next" -> next.map(_.fullId)))
}
}
}
2017-01-15 05:26:08 -07:00
def next(gameId: String) = Auth { implicit ctx => me =>
2019-12-04 21:46:58 -07:00
OptionFuResult(env.round.proxyRepo game gameId) { currentGame =>
2017-01-15 05:26:08 -07:00
otherPovs(currentGame) map getNext(currentGame) map {
_ orElse Pov(currentGame, me)
} flatMap {
case Some(next) => renderPlayer(next)
2019-12-13 07:30:20 -07:00
case None =>
fuccess(Redirect(currentGame.simulId match {
case Some(simulId) => routes.Simul.show(simulId)
case None => routes.Round.watcher(gameId, "white")
}))
}
2017-01-15 05:26:08 -07:00
}
2014-12-22 10:12:29 -07:00
}
2014-02-17 02:12:19 -07:00
def watcher(gameId: String, color: String) = Open { implicit ctx =>
proxyPov(gameId, color) flatMap {
2019-12-13 07:30:20 -07:00
case Some(pov) =>
get("pov") match {
case Some(requestedPov) =>
(pov.player.userId, pov.opponent.userId) match {
case (Some(_), Some(opponent)) if opponent == requestedPov =>
Redirect(routes.Round.watcher(gameId, (!pov.color).name)).fuccess
case (Some(player), Some(_)) if player == requestedPov =>
Redirect(routes.Round.watcher(gameId, pov.color.name)).fuccess
case _ =>
Redirect(routes.Round.watcher(gameId, "white")).fuccess
}
case None => {
watch(pov)
}
}
2019-12-04 21:46:58 -07:00
case None => challengeC showId gameId
2013-05-07 17:44:26 -06:00
}
2014-01-28 02:57:59 -07:00
}
2013-05-07 17:44:26 -06:00
2019-08-20 07:35:41 -06:00
private def proxyPov(gameId: String, color: String): Fu[Option[Pov]] = chess.Color(color) ?? {
2019-12-04 21:46:58 -07:00
env.round.proxyRepo.pov(gameId, _)
}
2019-12-13 07:30:20 -07:00
private[controllers] def watch(pov: Pov, userTv: Option[UserModel] = None)(
implicit ctx: Context
): Fu[Result] =
playablePovForReq(pov.game) match {
case Some(player) if userTv.isEmpty => renderPlayer(pov withColor player.color)
case _ if pov.game.variant == chess.variant.RacingKings && pov.color.black =>
Redirect(routes.Round.watcher(pov.gameId, "white")).fuccess
2019-12-13 07:30:20 -07:00
case _ =>
negotiate(
html = {
if (pov.game.replayable) analyseC.replay(pov, userTv = userTv)
else if (HTTPRequest.isHuman(ctx.req))
env.tournament.api.miniView(pov.game, false) zip
2019-12-13 07:30:20 -07:00
(pov.game.simulId ?? env.simul.repo.find) zip
getWatcherChat(pov.game) zip
(ctx.noBlind ?? env.game.crosstableApi.withMatchup(pov.game)) zip
env.api.roundApi.watcher(
pov,
lila.api.Mobile.Api.currentVersion,
tv = userTv.map { u =>
lila.round.OnUserTv(u.id)
}
) zip
2019-12-04 16:39:16 -07:00
env.bookmark.api.exists(pov.game, ctx.me) map {
2019-12-13 07:30:20 -07:00
case tour ~ simul ~ chat ~ crosstable ~ data ~ bookmarked =>
Ok(
html.round.watcher(
pov,
data,
tour,
simul,
crosstable,
userTv = userTv,
chatOption = chat,
bookmarked = bookmarked
)
)
}
else
for { // web crawlers don't need the full thing
initialFen <- env.game.gameRepo.initialFen(pov.gameId)
pgn <- env.api.pgnDump(pov.game, initialFen, none, PgnDump.WithFlags(clocks = false))
} yield Ok(html.round.watcher.crawler(pov, initialFen, pgn))
},
api = apiVersion =>
for {
data <- env.api.roundApi.watcher(pov, apiVersion, tv = none)
analysis <- analyser get pov.game
chat <- getWatcherChat(pov.game)
} yield Ok {
data
.add("chat" -> chat.map(c => lila.chat.JsonView(c.chat)))
.add("analysis" -> analysis.map(a => lila.analyse.JsonView.mobile(pov.game, a)))
}
) map { NoCache(_) }
}
2013-08-02 03:27:28 -06:00
2019-12-13 07:30:20 -07:00
private[controllers] def getWatcherChat(
game: GameModel
)(implicit ctx: Context): Fu[Option[lila.chat.UserChat.Mine]] = {
2019-12-04 16:39:16 -07:00
ctx.noKid && ctx.me.fold(true)(env.chat.panic.allowed) && {
game.finishedOrAborted || !ctx.userId.exists(game.userIds.contains)
}
2017-10-28 15:40:52 -06:00
} ?? {
val id = Chat.Id(s"${game.id}/w")
2019-12-04 16:39:16 -07:00
env.chat.api.userChat.findMineIf(id, ctx.me, !game.justCreated) flatMap { chat =>
env.user.lightUserApi.preloadMany(chat.chat.userIds) inject chat.some
2017-08-16 18:39:52 -06:00
}
2016-06-17 05:30:55 -06:00
}
2016-06-15 02:49:05 -06:00
2019-12-13 07:30:20 -07:00
private[controllers] def getPlayerChat(game: GameModel, tour: Option[Tour])(
implicit ctx: Context
): Fu[Option[Chat.GameOrEvent]] = ctx.noKid ?? {
def toEventChat(resource: String)(c: lila.chat.UserChat.Mine) =
Chat
.GameOrEvent(
Right(
(
c truncate 100,
lila.chat.Chat.ResourceId(resource)
)
)
)
.some
2017-08-18 05:39:38 -06:00
(game.tournamentId, game.simulId) match {
2017-10-28 15:40:52 -06:00
case (Some(tid), _) => {
2019-12-04 21:46:58 -07:00
ctx.isAuth && tour.fold(true)(tournamentC.canHaveChat(_, none))
2019-12-04 16:39:16 -07:00
} ?? env.chat.api.userChat.cached.findMine(Chat.Id(tid), ctx.me).map(toEventChat(s"tournament/$tid"))
2019-12-13 07:30:20 -07:00
case (_, Some(_)) =>
game.simulId.?? { sid =>
env.chat.api.userChat.cached.findMine(Chat.Id(sid), ctx.me).map(toEventChat(s"simul/$sid"))
}
case _ =>
game.hasChat ?? {
env.chat.api.playerChat.findIf(Chat.Id(game.id), !game.justCreated) map { chat =>
Chat
.GameOrEvent(
Left(
Chat.Restricted(
chat,
restricted = game.fromLobby && ctx.isAnon
)
)
)
.some
}
2017-08-16 18:39:52 -06:00
}
2016-06-12 02:16:14 -06:00
}
2017-08-16 18:39:52 -06:00
}
2016-06-12 02:16:14 -06:00
2017-08-17 16:07:43 -06:00
def sides(gameId: String, color: String) = Open { implicit ctx =>
2019-08-20 07:35:41 -06:00
OptionFuResult(proxyPov(gameId, color)) { pov =>
env.tournament.api.withTeamVs(pov.game) zip
2019-12-04 16:39:16 -07:00
(pov.game.simulId ?? env.simul.repo.find) zip
env.game.gameRepo.initialFen(pov.game) zip
env.game.crosstableApi.withMatchup(pov.game) zip
env.bookmark.api.exists(pov.game, ctx.me) map {
2019-12-13 07:30:20 -07:00
case tour ~ simul ~ initialFen ~ crosstable ~ bookmarked =>
Ok(html.game.bits.sides(pov, initialFen, tour, crosstable, simul, bookmarked = bookmarked))
}
2017-08-17 16:07:43 -06:00
}
2013-12-24 03:47:52 -07:00
}
2017-01-15 05:26:08 -07:00
def writeNote(gameId: String) = AuthBody { implicit ctx => me =>
import play.api.data.Forms._
import play.api.data._
implicit val req = ctx.body
Form(single("text" -> text)).bindFromRequest.fold(
2019-12-08 11:12:00 -07:00
_ => fuccess(BadRequest),
2019-12-04 16:39:16 -07:00
text => env.round.noteApi.set(gameId, me.id, text.trim take 10000)
)
}
2019-12-08 11:12:00 -07:00
def readNote(gameId: String) = Auth { _ => me =>
2019-12-04 16:39:16 -07:00
env.round.noteApi.get(gameId, me.id) map { text =>
2017-01-15 05:26:08 -07:00
Ok(text)
}
2016-06-14 05:13:41 -06:00
}
2014-02-17 02:12:19 -07:00
def continue(id: String, mode: String) = Open { implicit ctx =>
2019-12-04 16:39:16 -07:00
OptionResult(env.game.gameRepo game id) { game =>
2019-12-13 07:30:20 -07:00
Redirect(
"%s?fen=%s#%s".format(
routes.Lobby.home(),
get("fen") | (chess.format.Forsyth >> game.chess),
mode
)
)
2013-05-31 09:55:11 -06:00
}
}
def resign(fullId: String) = Open { implicit ctx =>
2019-12-04 21:46:58 -07:00
OptionFuRedirect(env.round.proxyRepo.pov(fullId)) { pov =>
2016-11-05 05:00:17 -06:00
if (isTheft(pov)) {
2019-12-04 21:46:58 -07:00
lila.log("round").warn(s"theft resign $fullId ${HTTPRequest.lastRemoteAddress(ctx.req)}")
2016-11-05 05:00:17 -06:00
fuccess(routes.Lobby.home)
} else {
2019-12-04 21:46:58 -07:00
env.round resign pov
2016-11-05 05:00:17 -06:00
import scala.concurrent.duration._
2019-12-04 21:46:58 -07:00
akka.pattern.after(500 millis, env.system.scheduler)(fuccess(routes.Lobby.home))
2016-11-05 05:00:17 -06:00
}
}
}
def mini(gameId: String, color: String) = Open { implicit ctx =>
2019-12-13 07:30:20 -07:00
OptionOk(
chess.Color(color).??(env.round.proxyRepo.povIfPresent(gameId, _)) orElse env.game.gameRepo
.pov(gameId, color)
)(html.game.bits.mini)
}
2015-10-07 11:18:48 -06:00
2016-02-17 09:00:47 -07:00
def miniFullId(fullId: String) = Open { implicit ctx =>
2019-12-13 07:30:20 -07:00
OptionOk(env.round.proxyRepo.povIfPresent(fullId) orElse env.game.gameRepo.pov(fullId))(
html.game.bits.mini
)
2016-02-17 09:00:47 -07:00
}
2013-03-25 11:52:18 -06:00
}