lila/modules/study/src/main/ServerEval.scala

174 lines
6.6 KiB
Scala

package lila.study
import chess.format.pgn.Glyphs
import chess.format.{ Forsyth, Uci, UciCharPair }
import play.api.libs.json._
import scala.concurrent.duration._
import lila.analyse.{ Analysis, Info }
import lila.hub.actorApi.fishnet.StudyChapterRequest
import lila.security.Granter
import lila.tree.Node.Comment
import lila.user.{ User, UserRepo }
import lila.{ tree => T }
object ServerEval {
final class Requester(
fishnet: lila.hub.actors.Fishnet,
chapterRepo: ChapterRepo,
userRepo: UserRepo
)(implicit ec: scala.concurrent.ExecutionContext) {
private val onceEvery = lila.memo.OnceEvery(5 minutes)
def apply(study: Study, chapter: Chapter, userId: User.ID, unlimited: Boolean = false): Funit =
chapter.serverEval.fold(true) { eval =>
!eval.done && onceEvery(chapter.id.value)
} ?? {
val unlimitedFu =
fuccess(unlimited) >>|
fuccess(userId == User.lichessId) >>| userRepo
.byId(userId)
.map(_.exists(Granter(_.Relay)))
unlimitedFu flatMap { unlimited =>
chapterRepo.startServerEval(chapter) >>- {
fishnet ! StudyChapterRequest(
studyId = study.id.value,
chapterId = chapter.id.value,
initialFen = chapter.root.fen.some,
variant = chapter.setup.variant,
moves = chess.format
.UciDump(
moves = chapter.root.mainline.map(_.move.san),
initialFen = chapter.root.fen.some,
variant = chapter.setup.variant,
force960Notation = true
)
.toOption
.map(_.flatMap(chess.format.Uci.apply)) | List.empty,
userId = userId,
unlimited = unlimited
)
}
}
}
}
final class Merger(
sequencer: StudySequencer,
socket: StudySocket,
chapterRepo: ChapterRepo,
divider: lila.game.Divider
)(implicit ec: scala.concurrent.ExecutionContext) {
def apply(analysis: Analysis, complete: Boolean): Funit =
analysis.studyId.map(Study.Id.apply) ?? { studyId =>
sequencer.sequenceStudyWithChapter(studyId, Chapter.Id(analysis.id)) {
case Study.WithChapter(_, chapter) =>
(complete ?? chapterRepo.completeServerEval(chapter)) >> {
lila.common.Future
.fold(chapter.root.mainline.zip(analysis.infoAdvices).toList)(Path.root) {
case (path, (node, (info, advOpt))) =>
chapter.root.nodeAt(path).flatMap { parent =>
analysisLine(parent, chapter.setup.variant, info) map { subTree =>
parent.addChild(subTree) -> subTree
}
} ?? { case (newParent, subTree) =>
chapterRepo.addSubTree(subTree, newParent, path)(chapter)
} >> {
import BSONHandlers._
import Node.{ BsonFields => F }
((info.eval.score.isDefined && node.score.isEmpty) || (advOpt.isDefined && !node.comments.hasLichessComment)) ??
chapterRepo
.setNodeValues(
chapter,
path + node,
List(
F.score -> info.eval.score
.ifTrue {
node.score.isEmpty ||
advOpt.isDefined && node.comments.findBy(Comment.Author.Lichess).isEmpty
}
.flatMap(EvalScoreBSONHandler.writeOpt),
F.comments -> advOpt
.map { adv =>
node.comments + Comment(
Comment.Id.make,
Comment.Text(adv.makeComment(withEval = false, withBestMove = true)),
Comment.Author.Lichess
)
}
.flatMap(CommentsBSONHandler.writeOpt),
F.glyphs -> advOpt
.map { adv =>
node.glyphs merge Glyphs.fromList(List(adv.judgment.glyph))
}
.flatMap(GlyphsBSONHandler.writeOpt)
)
)
} inject path + node
} void
} >>- {
chapterRepo.byId(Chapter.Id(analysis.id)).foreach {
_ ?? { chapter =>
socket.onServerEval(
studyId,
ServerEval.Progress(
chapterId = chapter.id,
tree = lila.study.TreeBuilder(chapter.root, chapter.setup.variant),
analysis = toJson(chapter, analysis),
division = divisionOf(chapter)
)
)
}
}
} logFailure logger
}
}
def divisionOf(chapter: Chapter) =
divider(
id = chapter.id.value,
pgnMoves = chapter.root.mainline.map(_.move.san).toVector,
variant = chapter.setup.variant,
initialFen = chapter.root.fen.some
)
private def analysisLine(root: RootOrNode, variant: chess.variant.Variant, info: Info): Option[Node] =
chess.Replay.gameMoveWhileValid(info.variation take 20, root.fen, variant) match {
case (_, games, error) =>
error foreach { logger.info(_) }
games.reverse match {
case Nil => none
case (g, m) :: rest =>
rest
.foldLeft(makeBranch(g, m)) { case (node, (g, m)) =>
makeBranch(g, m) addChild node
} some
}
}
private def makeBranch(g: chess.Game, m: Uci.WithSan) =
Node(
id = UciCharPair(m.uci),
ply = g.turns,
move = m,
fen = Forsyth >> g,
check = g.situation.check,
crazyData = g.situation.board.crazyData,
clock = none,
children = Node.emptyChildren,
forceVariation = false
)
}
case class Progress(chapterId: Chapter.Id, tree: T.Root, analysis: JsObject, division: chess.Division)
def toJson(chapter: Chapter, analysis: Analysis) =
lila.analyse.JsonView.bothPlayers(
lila.analyse.Accuracy.PovLike(chess.White, chapter.root.color, chapter.root.ply),
analysis
)
}