Skip to content

Commit

Permalink
study server eval WIP + FEN type
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Jan 15, 2018
1 parent f539b1f commit 2347611
Show file tree
Hide file tree
Showing 21 changed files with 160 additions and 76 deletions.
5 changes: 3 additions & 2 deletions app/controllers/Analyse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controllers

import play.api.mvc._

import chess.format.FEN
import lila.api.Context
import lila.app._
import lila.common.HTTPRequest
Expand Down Expand Up @@ -42,7 +43,7 @@ object Analyse extends LilaController {
Env.api.roundApi.review(pov, lila.api.Mobile.Api.currentVersion,
tv = userTv.map { u => lila.round.OnUserTv(u.id) },
analysis,
initialFenO = initialFen.some,
initialFenO = initialFen.map(FEN).some,
withFlags = WithFlags(
movetimes = true,
clocks = true,
Expand Down Expand Up @@ -72,7 +73,7 @@ object Analyse extends LilaController {
case Some((game, initialFen)) =>
val pov = Pov(game, chess.Color(color == "white"))
Env.api.roundApi.review(pov, lila.api.Mobile.Api.currentVersion,
initialFenO = initialFen.map(_.value).some,
initialFenO = initialFen.some,
withFlags = WithFlags(opening = true)) map { data =>
Ok(html.analyse.embed(pov, data))
}
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Practice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object Practice extends LilaController {
private def analysisJson(us: UserStudy)(implicit ctx: Context): Fu[(JsObject, JsObject)] = us match {
case UserStudy(_, _, chapters, WithChapter(study, chapter), _) =>
studyEnv.jsonView(study, chapters, chapter, ctx.me) map { studyJson =>
val initialFen = chapter.root.fen.value.some
val initialFen = chapter.root.fen.some
val pov = UserAnalysis.makePov(initialFen, chapter.setup.variant)
val baseData = Env.round.jsonView.userAnalysisJson(pov, ctx.pref, initialFen, chapter.setup.orientation, owner = false, me = ctx.me)
val analysis = baseData ++ Json.obj(
Expand Down
17 changes: 13 additions & 4 deletions app/controllers/Study.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,19 @@ object Study extends LilaController {
chapter = resetToChapter | sc.chapter
_ <- Env.user.lightUserApi preloadMany study.members.ids.toList
_ = if (HTTPRequest isSynchronousHttp ctx.req) Env.study.studyRepo.incViews(study)
initialFen = chapter.root.fen.value.some
pov = UserAnalysis.makePov(initialFen, chapter.setup.variant)
initialFen = chapter.root.fen.some
analysis <- chapter.analysisId ?? Env.analyse.analyser.get
baseData = Env.round.jsonView.userAnalysisJson(pov, ctx.pref, initialFen, chapter.setup.orientation, owner = false, me = ctx.me)
pov = UserAnalysis.makePov(initialFen, chapter.setup.variant)
division = analysis.isDefined option Env.game.divider(
id = chapter.id.value,
pgnMoves = chapter.root.mainline.map(_.move.uci.uci)(scala.collection.breakOut),
variant = chapter.setup.variant,
initialFen = initialFen
)
baseData = Env.round.jsonView.userAnalysisJson(pov, ctx.pref, initialFen, chapter.setup.orientation,
owner = false,
me = ctx.me,
division = division)
studyJson <- Env.study.jsonView(study, chapters, chapter, ctx.me)
} yield WithChapter(study, chapter) -> JsData(
study = studyJson,
Expand Down Expand Up @@ -225,7 +234,7 @@ object Study extends LilaController {
members = lila.study.StudyMembers(Map.empty) // don't need no members
), List(chapter.metadata), chapter, ctx.me) flatMap { studyJson =>
val setup = chapter.setup
val initialFen = chapter.root.fen.value.some
val initialFen = chapter.root.fen.some
val pov = UserAnalysis.makePov(initialFen, setup.variant)
val baseData = Env.round.jsonView.userAnalysisJson(pov, ctx.pref, initialFen, setup.orientation, owner = false, me = ctx.me)
val analysis = baseData ++ Json.obj(
Expand Down
18 changes: 9 additions & 9 deletions app/controllers/UserAnalysis.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package controllers

import chess.format.Forsyth
import chess.format.Forsyth.SituationPlus
import chess.format.{ FEN, Forsyth }
import chess.Situation
import chess.variant.{ Variant, Standard, FromPosition }
import play.api.libs.json.Json
Expand Down Expand Up @@ -31,19 +31,19 @@ object UserAnalysis extends LilaController with TheftPrevention {
}

def load(urlFen: String, variant: Variant) = Open { implicit ctx =>
val decodedFen = lila.common.String.decodeUriPath(urlFen)
val decodedFen: Option[FEN] = lila.common.String.decodeUriPath(urlFen)
.map(_.replace("_", " ").trim).filter(_.nonEmpty)
.orElse(get("fen"))
.orElse(get("fen")) map FEN.apply
val pov = makePov(decodedFen, variant)
val orientation = get("color").flatMap(chess.Color.apply) | pov.color
Env.api.roundApi.userAnalysisJson(pov, ctx.pref, decodedFen, orientation, owner = false, me = ctx.me) map { data =>
Ok(html.board.userAnalysis(data, pov))
}
}

private[controllers] def makePov(fen: Option[String], variant: Variant): Pov = makePov {
fen.filter(_.nonEmpty).flatMap {
Forsyth.<<<@(variant, _)
private[controllers] def makePov(fen: Option[FEN], variant: Variant): Pov = makePov {
fen.filter(_.value.nonEmpty).flatMap { f =>
Forsyth.<<<@(variant, f.value)
} | SituationPlus(Situation(variant), 1)
}

Expand Down Expand Up @@ -71,7 +71,7 @@ object UserAnalysis extends LilaController with TheftPrevention {
if (game.replayable) Redirect(routes.Round.watcher(game.id, color)).fuccess
else for {
initialFen <- GameRepo initialFen game.id
data <- Env.api.roundApi.userAnalysisJson(pov, ctx.pref, initialFen, pov.color, owner = isMyPov(pov), me = ctx.me)
data <- Env.api.roundApi.userAnalysisJson(pov, ctx.pref, initialFen map FEN, pov.color, owner = isMyPov(pov), me = ctx.me)
} yield NoCache(Ok(html.board.userAnalysis(data, pov))),
api = apiVersion => mobileAnalysis(pov, apiVersion)
)
Expand All @@ -89,7 +89,7 @@ object UserAnalysis extends LilaController with TheftPrevention {
Env.api.roundApi.review(pov, apiVersion,
tv = none,
analysis,
initialFenO = initialFen.some,
initialFenO = initialFen.map(FEN).some,
withFlags = WithFlags(division = true, opening = true, clocks = true, movetimes = true)) map { data =>
Ok(data.add("crosstable", crosstable))
}
Expand All @@ -111,7 +111,7 @@ object UserAnalysis extends LilaController with TheftPrevention {
err => BadRequest(jsonError(err.shows)).fuccess, {
case (game, fen) =>
val pov = Pov(game, chess.White)
Env.api.roundApi.userAnalysisJson(pov, ctx.pref, initialFen = fen.map(_.value), pov.color, owner = false, me = ctx.me) map { data =>
Env.api.roundApi.userAnalysisJson(pov, ctx.pref, initialFen = fen, pov.color, owner = false, me = ctx.me) map { data =>
Ok(data)
}
}
Expand Down
31 changes: 16 additions & 15 deletions modules/api/src/main/RoundApi.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lila.api

import play.api.libs.json._
import chess.format.FEN

import lila.analyse.{ JsonView => analysisJson, Analysis }
import lila.common.ApiVersion
Expand All @@ -24,7 +25,7 @@ private[api] final class RoundApi(
) {

def player(pov: Pov, apiVersion: ApiVersion)(implicit ctx: Context): Fu[JsObject] =
GameRepo.initialFen(pov.game) flatMap { initialFen =>
GameRepo.initialFen(pov.game) map2 FEN.apply flatMap { initialFen =>
jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me,
withFlags = WithFlags(blurs = ctx.me ?? Granter(_.ViewBlurs)),
initialFen = initialFen) zip
Expand All @@ -46,8 +47,8 @@ private[api] final class RoundApi(
}

def watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] =
initialFenO.fold(GameRepo initialFen pov.game)(fuccess) flatMap { initialFen =>
initialFenO: Option[Option[FEN]] = None)(implicit ctx: Context): Fu[JsObject] =
initialFenO.fold(GameRepo initialFen pov.game map2 FEN)(fuccess) flatMap { initialFen =>
jsonView.watcherJson(pov, ctx.pref, apiVersion, ctx.me, tv,
initialFen = initialFen,
withFlags = WithFlags(blurs = ctx.me ?? Granter(_.ViewBlurs))) zip
Expand All @@ -69,9 +70,9 @@ private[api] final class RoundApi(
def review(pov: Pov, apiVersion: ApiVersion,
tv: Option[lila.round.OnTv] = None,
analysis: Option[Analysis] = None,
initialFenO: Option[Option[String]] = None,
initialFenO: Option[Option[FEN]] = None,
withFlags: WithFlags)(implicit ctx: Context): Fu[JsObject] =
initialFenO.fold(GameRepo initialFen pov.game)(fuccess) flatMap { initialFen =>
initialFenO.fold(GameRepo initialFen pov.game map2 FEN)(fuccess) flatMap { initialFen =>
jsonView.watcherJson(pov, ctx.pref, apiVersion, ctx.me, tv,
initialFen = initialFen,
withFlags = withFlags.copy(blurs = ctx.me ?? Granter(_.ViewBlurs))) zip
Expand All @@ -91,37 +92,37 @@ private[api] final class RoundApi(
}
}

def userAnalysisJson(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, owner: Boolean, me: Option[User]) =
def userAnalysisJson(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, owner: Boolean, me: Option[User]) =
owner.??(forecastApi loadForDisplay pov).map { fco =>
withForecast(pov, owner, fco)(
withTree(pov, analysis = none, initialFen, WithFlags(opening = true))(
withForecast(pov, owner, fco) {
withTree(pov, analysis = none, initialFen, WithFlags(opening = true)) {
jsonView.userAnalysisJson(pov, pref, initialFen, orientation, owner = owner, me = me)
)
)
}
}
}

def freeStudyJson(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, me: Option[User]) =
def freeStudyJson(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, me: Option[User]) =
withTree(pov, analysis = none, initialFen, WithFlags(opening = true))(
jsonView.userAnalysisJson(pov, pref, initialFen, orientation, owner = false, me = me)
)

private def withTree(pov: Pov, analysis: Option[Analysis], initialFen: Option[String], withFlags: WithFlags)(obj: JsObject) =
private def withTree(pov: Pov, analysis: Option[Analysis], initialFen: Option[FEN], withFlags: WithFlags)(obj: JsObject) =
obj + ("treeParts" -> partitionTreeJsonWriter.writes(lila.round.TreeBuilder(
id = pov.game.id,
pgnMoves = pov.game.pgnMoves,
variant = pov.game.variant,
analysis = analysis,
initialFen = initialFen | pov.game.variant.initialFen,
initialFen = initialFen | FEN(pov.game.variant.initialFen),
withFlags = withFlags,
clocks = withFlags.clocks ?? pov.game.bothClockStates
)))

private def withSteps(pov: Pov, initialFen: Option[String])(obj: JsObject) =
private def withSteps(pov: Pov, initialFen: Option[FEN])(obj: JsObject) =
obj + ("steps" -> lila.round.StepBuilder(
id = pov.game.id,
pgnMoves = pov.game.pgnMoves,
variant = pov.game.variant,
initialFen = initialFen | pov.game.variant.initialFen
initialFen = initialFen.fold(pov.game.variant.initialFen)(_.value)
))

private def withNote(note: String)(json: JsObject) =
Expand Down
17 changes: 9 additions & 8 deletions modules/api/src/main/RoundApiBalancer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import akka.pattern.ask
import play.api.libs.json.JsObject
import scala.concurrent.duration._

import chess.format.FEN
import lila.analyse.Analysis
import lila.common.ApiVersion
import lila.game.Pov
Expand All @@ -26,15 +27,15 @@ private[api] final class RoundApiBalancer(

case class Player(pov: Pov, apiVersion: ApiVersion, ctx: Context)
case class Watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
initialFenO: Option[Option[String]] = None,
initialFenO: Option[Option[FEN]] = None,
ctx: Context)
case class Review(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
analysis: Option[Analysis] = None,
initialFenO: Option[Option[String]] = None,
initialFenO: Option[Option[FEN]] = None,
withFlags: WithFlags,
ctx: Context)
case class UserAnalysis(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, owner: Boolean, me: Option[User])
case class FreeStudy(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, me: Option[User])
case class UserAnalysis(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, owner: Boolean, me: Option[User])
case class FreeStudy(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, me: Option[User])

val router = system.actorOf(
akka.routing.RoundRobinPool(nbActors).props(Props(new lila.hub.SequentialProvider {
Expand Down Expand Up @@ -74,21 +75,21 @@ private[api] final class RoundApiBalancer(
.result

def watcher(pov: Pov, apiVersion: ApiVersion, tv: Option[lila.round.OnTv],
initialFenO: Option[Option[String]] = None)(implicit ctx: Context): Fu[JsObject] = {
initialFenO: Option[Option[FEN]] = None)(implicit ctx: Context): Fu[JsObject] = {
router ? Watcher(pov, apiVersion, tv, initialFenO, ctx) mapTo manifest[JsObject]
}.mon(_.round.api.watcher)

def review(pov: Pov, apiVersion: ApiVersion,
tv: Option[lila.round.OnTv] = None,
analysis: Option[Analysis] = None,
initialFenO: Option[Option[String]] = None,
initialFenO: Option[Option[FEN]] = None,
withFlags: WithFlags)(implicit ctx: Context): Fu[JsObject] = {
router ? Review(pov, apiVersion, tv, analysis, initialFenO, withFlags, ctx) mapTo manifest[JsObject]
}.mon(_.round.api.watcher)

def userAnalysisJson(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, owner: Boolean, me: Option[User]): Fu[JsObject] =
def userAnalysisJson(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, owner: Boolean, me: Option[User]): Fu[JsObject] =
router ? UserAnalysis(pov, pref, initialFen, orientation, owner, me) mapTo manifest[JsObject]

def freeStudyJson(pov: Pov, pref: Pref, initialFen: Option[String], orientation: chess.Color, me: Option[User]): Fu[JsObject] =
def freeStudyJson(pov: Pov, pref: Pref, initialFen: Option[FEN], orientation: chess.Color, me: Option[User]): Fu[JsObject] =
router ? FreeStudy(pov, pref, initialFen, orientation, me) mapTo manifest[JsObject]
}
21 changes: 11 additions & 10 deletions modules/game/src/main/Divider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import scala.concurrent.duration._

import chess.Division
import chess.variant.Variant
import chess.format.FEN

final class Divider {

private val cache: Cache[Game.ID, Division] = Scaffeine()
.expireAfterAccess(5 minutes)
.build[Game.ID, Division]

def apply(game: Game, initialFen: Option[String]): Division =
if (!Variant.divisionSensibleVariants(game.variant)) Division.empty
else cache.get(game.id, _ => {
val div = chess.Replay.boards(
moveStrs = game.pgnMoves,
initialFen = initialFen map chess.format.FEN,
variant = game.variant
).toOption.fold(Division.empty)(chess.Divider.apply)
div
})
def apply(game: Game, initialFen: Option[FEN]): Division =
apply(game.id, game.pgnMoves, game.variant, initialFen)

def apply(id: Game.ID, pgnMoves: PgnMoves, variant: Variant, initialFen: Option[FEN]) =
if (!Variant.divisionSensibleVariants(variant)) Division.empty
else cache.get(id, _ => chess.Replay.boards(
moveStrs = pgnMoves,
initialFen = initialFen,
variant = variant
).toOption.fold(Division.empty)(chess.Divider.apply))
}
6 changes: 3 additions & 3 deletions modules/game/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ package lila.game

import play.api.libs.json._

import chess.format.Forsyth
import chess.format.{ FEN, Forsyth }
import chess.variant.Crazyhouse
import chess.{ Color, Clock }

object JsonView {

def gameJson(game: Game, initialFen: Option[String]) = Json.obj(
def gameJson(game: Game, initialFen: Option[FEN]) = Json.obj(
"id" -> game.id,
"variant" -> game.variant,
"speed" -> game.speed.key,
"perf" -> PerfPicker.key(game),
"rated" -> game.rated,
"initialFen" -> (initialFen | chess.format.Forsyth.initial),
"initialFen" -> initialFen.fold(chess.format.Forsyth.initial)(_.value),
"fen" -> (Forsyth >> game.toChess),
"player" -> game.turnColor,
"turns" -> game.turns,
Expand Down
15 changes: 8 additions & 7 deletions modules/round/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import lila.game.{ Pov, Game, Player => GamePlayer }
import lila.pref.Pref
import lila.user.{ User, UserRepo }

import chess.format.Forsyth
import chess.format.{ Forsyth, FEN }
import chess.{ Color, Clock }

import actorApi.SocketStatus
Expand Down Expand Up @@ -52,7 +52,7 @@ final class JsonView(
pref: Pref,
apiVersion: ApiVersion,
playerUser: Option[User],
initialFen: Option[String],
initialFen: Option[FEN],
withFlags: WithFlags
): Fu[JsObject] =
getSocketStatus(pov.game.id) zip
Expand Down Expand Up @@ -141,7 +141,7 @@ final class JsonView(
apiVersion: ApiVersion,
me: Option[User],
tv: Option[OnTv],
initialFen: Option[String] = None,
initialFen: Option[FEN] = None,
withFlags: WithFlags
) =
getSocketStatus(pov.game.id) zip
Expand Down Expand Up @@ -197,10 +197,11 @@ final class JsonView(
def userAnalysisJson(
pov: Pov,
pref: Pref,
initialFen: Option[String],
initialFen: Option[FEN],
orientation: chess.Color,
owner: Boolean,
me: Option[User]
me: Option[User],
division: Option[chess.Division] = none
) = {
import pov._
val fen = Forsyth >> game.toChess
Expand All @@ -209,12 +210,12 @@ final class JsonView(
"id" -> gameId,
"variant" -> game.variant,
"opening" -> game.opening,
"initialFen" -> (initialFen | game.variant.initialFen),
"initialFen" -> initialFen.fold(chess.format.Forsyth.initial)(_.value),
"fen" -> fen,
"turns" -> game.turns,
"player" -> game.turnColor.name,
"status" -> game.status
),
).add("division", division),
"player" -> Json.obj(
"id" -> owner.option(pov.playerId),
"color" -> color.name
Expand Down
2 changes: 1 addition & 1 deletion modules/round/src/main/Socket.scala
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private[round] final class Socket(
pgnMoves = a.game.pgnMoves,
variant = a.variant,
analysis = a.analysis.some,
initialFen = a.initialFen.value,
initialFen = a.initialFen,
withFlags = JsonView.WithFlags(),
clocks = none
)
Expand Down
Loading

0 comments on commit 2347611

Please sign in to comment.