add %eval annotations to study PGN exports - closes #9533

russelldavis-analysis-material
Thibault Duplessis 2021-08-08 10:16:43 +02:00
parent 57b1e93e74
commit ffb4edb9b0
6 changed files with 53 additions and 45 deletions

View File

@ -465,10 +465,11 @@ final class Study(
_.fold(notFound) { case WithChapter(study, chapter) =>
CanView(study, ctx.me) {
lila.mon.export.pgn.studyChapter.increment()
Ok(env.study.pgnDump.ofChapter(study, requestPgnFlags(ctx.req))(chapter).toString)
.pipe(asAttachment(s"${env.study.pgnDump.filename(study, chapter)}.pgn"))
.as(pgnContentType)
.fuccess
env.study.pgnDump.ofChapter(study, requestPgnFlags(ctx.req))(chapter) map { pgn =>
Ok(pgn.toString)
.pipe(asAttachment(s"${env.study.pgnDump.filename(study, chapter)}.pgn"))
.as(pgnContentType)
}
}(privateUnauthorizedFu(study), privateForbiddenFu(study))
}
}

View File

@ -21,6 +21,32 @@ final class Annotator(netDomain: lila.common.config.NetDomain) {
)
}
def addEvals(p: Pgn, analysis: Analysis): Pgn =
analysis.infos.foldLeft(p) { case (pgn, info) =>
pgn.updateTurn(
info.turn,
turn =>
turn.update(
info.color,
move => {
val comment = info.cp
.map(_.pawns.toString)
.orElse(info.mate.map(m => s"#${m.value}"))
move.copy(
comments = comment.map(c => s"[%eval $c]").toList ::: move.comments
)
}
)
)
}
def toPgnString(pgn: Pgn) = {
// merge analysis & eval comments
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }
s"$pgn\n\n\n".replaceIf("] } { [", "] [")
}
private def annotateStatus(winner: Option[Color], status: Status)(p: Pgn) =
lila.game.StatusText(status, winner, chess.variant.Standard) match {
case "" => p

View File

@ -28,6 +28,7 @@ final class GameApiV2(
playerRepo: lila.tournament.PlayerRepo,
swissApi: lila.swiss.SwissApi,
analysisRepo: lila.analyse.AnalysisRepo,
annotator: lila.analyse.Annotator,
getLightUser: LightUser.Getter,
realPlayerApi: RealPlayerApi,
gameProxy: GameProxyRepo
@ -57,7 +58,7 @@ final class GameApiV2(
analysis,
config.flags,
realPlayers = realPlayers
) dmap pgnDump.toPgnString
) dmap annotator.toPgnString
}
} yield export
}
@ -274,7 +275,7 @@ final class GameApiV2(
pgn <-
withFlags.pgnInJson ?? pgnDump
.apply(g, initialFen, analysisOption, withFlags, realPlayers = realPlayers)
.dmap(pgnDump.toPgnString)
.dmap(annotator.toPgnString)
.dmap(some)
} yield Json
.obj(

View File

@ -34,32 +34,13 @@ final class PgnDump(
}
else fuccess(pgn)
} map { pgn =>
val evaled = analysis.ifTrue(flags.evals).fold(pgn)(addEvals(pgn, _))
val evaled = analysis.ifTrue(flags.evals).fold(pgn)(annotator.addEvals(pgn, _))
if (flags.literate) annotator(evaled, game, analysis)
else evaled
} map { pgn =>
realPlayers.fold(pgn)(_.update(game, pgn))
}
private def addEvals(p: Pgn, analysis: Analysis): Pgn =
analysis.infos.foldLeft(p) { case (pgn, info) =>
pgn.updateTurn(
info.turn,
turn =>
turn.update(
info.color,
move => {
val comment = info.cp
.map(_.pawns.toString)
.orElse(info.mate.map(m => s"#${m.value}"))
move.copy(
comments = comment.map(c => s"[%eval $c]").toList ::: move.comments
)
}
)
)
}
def formatter(flags: WithFlags) =
(
game: Game,
@ -67,12 +48,5 @@ final class PgnDump(
analysis: Option[Analysis],
teams: Option[GameTeams],
realPlayers: Option[RealPlayers]
) => apply(game, initialFen, analysis, flags, teams, realPlayers) dmap toPgnString
def toPgnString(pgn: Pgn) = {
// merge analysis & eval comments
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }
s"$pgn\n\n\n".replaceIf("] } { [", "] [")
}
) => apply(game, initialFen, analysis, flags, teams, realPlayers) dmap annotator.toPgnString
}

View File

@ -25,6 +25,8 @@ final class Env(
timeline: lila.hub.actors.Timeline,
fishnet: lila.hub.actors.Fishnet,
chatApi: lila.chat.ChatApi,
analyser: lila.analyse.Analyser,
annotator: lila.analyse.Annotator,
mongo: lila.db.Env,
net: lila.common.config.NetConfig,
cacheApi: lila.memo.CacheApi

View File

@ -4,33 +4,37 @@ import akka.stream.scaladsl._
import chess.format.pgn.{ Glyphs, Initial, Pgn, Tag, Tags }
import chess.format.{ pgn => chessPgn }
import org.joda.time.format.DateTimeFormat
import scala.concurrent.ExecutionContext
import lila.common.String.slugify
import lila.tree.Node.{ Shape, Shapes }
final class PgnDump(
chapterRepo: ChapterRepo,
analyser: lila.analyse.Analyser,
annotator: lila.analyse.Annotator,
lightUserApi: lila.user.LightUserApi,
net: lila.common.config.NetConfig
) {
)(implicit ec: ExecutionContext) {
import PgnDump._
def apply(study: Study, flags: WithFlags): Source[String, _] =
chapterRepo
.orderedByStudySource(study.id)
.map(ofChapter(study, flags))
.map(_.toString)
.intersperse("\n\n\n")
.mapAsync(1)(ofChapter(study, flags))
def ofChapter(study: Study, flags: WithFlags)(chapter: Chapter) =
Pgn(
tags = makeTags(study, chapter),
turns = toTurns(chapter.root)(flags).toList,
initial = Initial(
chapter.root.comments.list.map(_.text.value) ::: shapeComment(chapter.root.shapes).toList
def ofChapter(study: Study, flags: WithFlags)(chapter: Chapter): Fu[String] =
chapter.serverEval.exists(_.done) ?? analyser.byId(chapter.id.value) map { analysis =>
val pgn = Pgn(
tags = makeTags(study, chapter),
turns = toTurns(chapter.root)(flags).toList,
initial = Initial(
chapter.root.comments.list.map(_.text.value) ::: shapeComment(chapter.root.shapes).toList
)
)
)
annotator toPgnString analysis.fold(pgn)(annotator.addEvals(pgn, _))
}
private val fileR = """[\s,]""".r