lila/modules/game/src/main/PgnDump.scala

150 lines
5.4 KiB
Scala
Raw Normal View History

2013-03-25 08:44:51 -06:00
package lila.game
2012-05-08 05:23:49 -06:00
import chess.format.Forsyth
2017-09-20 11:22:06 -06:00
import chess.format.pgn.{ Pgn, Tag, Tags, TagType, Parser, ParsedPgn }
2018-05-06 16:40:17 -06:00
import chess.format.{ FEN, pgn => chessPgn }
2017-06-09 04:59:34 -06:00
import chess.{ Centis, Color }
2013-10-03 03:02:23 -06:00
2019-11-30 11:06:50 -07:00
import lila.common.config.BaseUrl
import lila.common.LightUser
2012-05-08 05:23:49 -06:00
2014-01-27 15:20:08 -07:00
final class PgnDump(
2019-11-30 11:06:50 -07:00
baseUrl: BaseUrl,
2018-05-06 16:40:17 -06:00
getLightUser: LightUser.Getter
2017-06-09 04:59:34 -06:00
) {
2012-05-08 05:23:49 -06:00
2012-05-28 16:27:44 -06:00
import PgnDump._
2018-05-06 16:40:17 -06:00
def apply(game: Game, initialFen: Option[FEN], flags: WithFlags): Fu[Pgn] = {
val imported = game.pgnImport.flatMap { pgni =>
Parser.full(pgni.pgn).toOption
}
2018-05-06 16:40:17 -06:00
val tagsFuture =
if (flags.tags) tags(game, initialFen, imported, withOpening = flags.opening)
else fuccess(Tags(Nil))
tagsFuture map { ts =>
val turns = flags.moves ?? {
val fenSituation = ts.fen.map(_.value) flatMap Forsyth.<<<
val moves2 = if (fenSituation.exists(_.situation.color.black)) ".." +: game.pgnMoves else game.pgnMoves
2018-05-06 16:40:17 -06:00
makeTurns(
moves2,
fenSituation.map(_.fullMoveNumber) | 1,
flags.clocks ?? ~game.bothClockStates,
game.startColor
)
}
Pgn(ts, turns)
2018-04-03 18:22:32 -06:00
}
}
2012-05-08 05:23:49 -06:00
2019-11-30 11:06:50 -07:00
private def gameUrl(id: String) = s"$baseUrl/$id"
2013-10-03 03:02:23 -06:00
2018-05-06 16:40:17 -06:00
private def gameLightUsers(game: Game): Fu[(Option[LightUser], Option[LightUser])] =
(game.whitePlayer.userId ?? getLightUser) zip (game.blackPlayer.userId ?? getLightUser)
2013-12-17 15:20:18 -07:00
private def rating(p: Player) = p.rating.fold("?")(_.toString)
2012-05-08 05:23:49 -06:00
2018-05-06 18:10:47 -06:00
def player(p: Player, u: Option[LightUser]) =
2014-12-02 04:01:50 -07:00
p.aiLevel.fold(u.fold(p.name | lila.user.User.anonymous)(_.name))("lichess AI level " + _)
2015-03-09 09:33:16 -06:00
private val customStartPosition: Set[chess.variant.Variant] =
2015-09-04 15:31:10 -06:00
Set(chess.variant.Chess960, chess.variant.FromPosition, chess.variant.Horde, chess.variant.RacingKings)
2014-08-05 13:34:16 -06:00
2017-06-09 04:59:34 -06:00
private def eventOf(game: Game) = {
val perf = game.perfType.fold("Standard")(_.name)
game.tournamentId.map { id =>
s"${game.mode} $perf tournament https://lichess.org/tournament/$id"
} orElse game.simulId.map { id =>
s"$perf simul https://lichess.org/simul/$id"
} getOrElse {
s"${game.mode} $perf game"
}
}
private def ratingDiffTag(p: Player, tag: Tag.type => TagType) =
p.ratingDiff.map { rd => Tag(tag(Tag), s"${if (rd >= 0) "+" else ""}$rd") }
def tags(
game: Game,
2018-05-06 16:40:17 -06:00
initialFen: Option[FEN],
imported: Option[ParsedPgn],
withOpening: Boolean
): Fu[Tags] = gameLightUsers(game) map {
2017-09-20 11:22:06 -06:00
case (wu, bu) => Tags {
val importedDate = imported.flatMap(_.tags(_.Date))
2017-06-09 04:59:34 -06:00
List[Option[Tag]](
2017-09-20 11:22:06 -06:00
Tag(_.Event, imported.flatMap(_.tags(_.Event)) | { if (game.imported) "Import" else eventOf(game) }).some,
2017-06-09 04:59:34 -06:00
Tag(_.Site, gameUrl(game.id)).some,
Tag(_.Date, importedDate | Tag.UTCDate.format.print(game.createdAt)).some,
2017-09-20 11:22:06 -06:00
Tag(_.Round, imported.flatMap(_.tags(_.Round)) | "-").some,
2017-06-09 04:59:34 -06:00
Tag(_.White, player(game.whitePlayer, wu)).some,
Tag(_.Black, player(game.blackPlayer, bu)).some,
Tag(_.Result, result(game)).some,
2017-09-20 11:22:06 -06:00
importedDate.isEmpty option Tag(_.UTCDate, imported.flatMap(_.tags(_.UTCDate)) | Tag.UTCDate.format.print(game.createdAt)),
importedDate.isEmpty option Tag(_.UTCTime, imported.flatMap(_.tags(_.UTCTime)) | Tag.UTCTime.format.print(game.createdAt)),
2017-06-09 04:59:34 -06:00
Tag(_.WhiteElo, rating(game.whitePlayer)).some,
Tag(_.BlackElo, rating(game.blackPlayer)).some,
ratingDiffTag(game.whitePlayer, _.WhiteRatingDiff),
ratingDiffTag(game.blackPlayer, _.BlackRatingDiff),
wu.flatMap(_.title).map { t => Tag(_.WhiteTitle, t) },
bu.flatMap(_.title).map { t => Tag(_.BlackTitle, t) },
Tag(_.Variant, game.variant.name.capitalize).some,
2017-11-03 12:37:33 -06:00
Tag.timeControl(game.clock.map(_.config)).some,
2017-06-09 04:59:34 -06:00
Tag(_.ECO, game.opening.fold("?")(_.opening.eco)).some,
2018-05-06 16:40:17 -06:00
withOpening option Tag(_.Opening, game.opening.fold("?")(_.opening.name)),
2017-06-09 04:59:34 -06:00
Tag(_.Termination, {
import chess.Status._
game.status match {
case Created | Started => "Unterminated"
case Aborted | NoStart => "Abandoned"
case Timeout | Outoftime => "Time forfeit"
case Resign | Draw | Stalemate | Mate | VariantEnd => "Normal"
case Cheat => "Rules infraction"
case UnknownFinish => "Unknown"
}
}).some
).flatten ::: customStartPosition(game.variant).??(List(
2018-05-06 16:40:17 -06:00
Tag(_.FEN, initialFen.fold("?")(_.value)),
2017-06-09 04:59:34 -06:00
Tag("SetUp", "1")
))
2017-09-20 11:22:06 -06:00
}
}
2017-06-30 10:23:50 -06:00
private def makeTurns(moves: Seq[String], from: Int, clocks: Vector[Centis], startColor: Color): List[chessPgn.Turn] =
2013-11-25 16:02:44 -07:00
(moves grouped 2).zipWithIndex.toList map {
case (moves, index) =>
2017-03-28 10:01:05 -06:00
val clockOffset = startColor.fold(0, 1)
chessPgn.Turn(
number = index + from,
white = moves.headOption filter (".." !=) map { san =>
2017-08-23 17:56:39 -06:00
chessPgn.Move(
san = san,
secondsLeft = clocks lift (index * 2 - clockOffset) map (_.roundSeconds)
)
},
black = moves lift 1 map { san =>
2017-08-23 17:56:39 -06:00
chessPgn.Move(
san = san,
secondsLeft = clocks lift (index * 2 + 1 - clockOffset) map (_.roundSeconds)
)
}
2017-06-09 04:59:34 -06:00
)
2013-10-06 10:18:51 -06:00
} filterNot (_.isEmpty)
2012-05-28 16:27:44 -06:00
}
object PgnDump {
2012-05-08 05:23:49 -06:00
2017-05-22 05:00:25 -06:00
case class WithFlags(
2018-04-03 18:22:32 -06:00
clocks: Boolean = true,
moves: Boolean = true,
tags: Boolean = true,
2018-05-06 16:40:17 -06:00
evals: Boolean = true,
2018-05-09 11:16:47 -06:00
opening: Boolean = true,
literate: Boolean = false
2017-06-09 04:59:34 -06:00
)
2017-05-22 05:00:25 -06:00
def result(game: Game) =
2017-10-07 15:32:11 -06:00
if (game.finished) Color.showResult(game.winnerColor)
2017-05-22 05:00:25 -06:00
else "*"
2012-05-08 05:23:49 -06:00
}