2013-03-25 08:44:51 -06:00
|
|
|
package lila.game
|
2012-05-08 05:23:49 -06:00
|
|
|
|
2017-03-28 10:01:05 -06:00
|
|
|
import chess.Color
|
2012-05-08 05:23:49 -06:00
|
|
|
import chess.format.Forsyth
|
2015-10-01 01:54:38 -06:00
|
|
|
import chess.format.pgn.{ Pgn, Tag, Parser, ParsedPgn }
|
2014-02-17 02:12:19 -07:00
|
|
|
import chess.format.{ pgn => chessPgn }
|
2013-10-03 03:02:23 -06:00
|
|
|
import org.joda.time.format.DateTimeFormat
|
|
|
|
|
2017-03-28 06:53:58 -06:00
|
|
|
import lila.common.{ Centis, LightUser }
|
2012-05-08 05:23:49 -06:00
|
|
|
|
2014-01-27 15:20:08 -07:00
|
|
|
final class PgnDump(
|
2014-07-17 15:53:37 -06:00
|
|
|
netBaseUrl: String,
|
2017-02-14 08:34:07 -07:00
|
|
|
getLightUser: LightUser.GetterSync
|
|
|
|
) {
|
2012-05-08 05:23:49 -06:00
|
|
|
|
2012-05-28 16:27:44 -06:00
|
|
|
import PgnDump._
|
|
|
|
|
2014-11-10 08:13:52 -07:00
|
|
|
def apply(game: Game, initialFen: Option[String]): Pgn = {
|
2015-10-01 01:54:38 -06:00
|
|
|
val imported = game.pgnImport.flatMap { pgni =>
|
|
|
|
Parser.full(pgni.pgn).toOption
|
|
|
|
}
|
|
|
|
val ts = tags(game, initialFen, imported)
|
2014-11-10 08:13:52 -07:00
|
|
|
val fenSituation = ts find (_.name == Tag.FEN) flatMap { case Tag(_, fen) => Forsyth <<< fen }
|
2014-11-16 16:58:59 -07:00
|
|
|
val moves2 = fenSituation.??(_.situation.color.black).fold(".." :: game.pgnMoves, game.pgnMoves)
|
2017-03-28 10:01:05 -06:00
|
|
|
Pgn(ts, turns(moves2, fenSituation.map(_.fullMoveNumber) | 1, ~game.bothClockStates, game.startColor))
|
2014-11-10 08:13:52 -07:00
|
|
|
}
|
2012-05-08 05:23:49 -06:00
|
|
|
|
2016-01-25 18:34:02 -07:00
|
|
|
private val fileR = """[\s,]""".r
|
|
|
|
|
2014-07-17 15:53:37 -06:00
|
|
|
def filename(game: Game): String = gameLightUsers(game) match {
|
2016-01-25 18:34:02 -07:00
|
|
|
case (wu, bu) => fileR.replaceAllIn(
|
|
|
|
"lichess_pgn_%s_%s_vs_%s.%s.pgn".format(
|
|
|
|
dateFormat.print(game.createdAt),
|
|
|
|
player(game.whitePlayer, wu),
|
|
|
|
player(game.blackPlayer, bu),
|
|
|
|
game.id
|
2017-02-14 08:34:07 -07:00
|
|
|
), "_"
|
|
|
|
)
|
2013-10-03 03:02:23 -06:00
|
|
|
}
|
|
|
|
|
2014-07-17 15:53:37 -06:00
|
|
|
private def gameUrl(id: String) = s"$netBaseUrl/$id"
|
2013-10-03 03:02:23 -06:00
|
|
|
|
2014-07-17 15:53:37 -06:00
|
|
|
private def gameLightUsers(game: Game): (Option[LightUser], Option[LightUser]) =
|
|
|
|
(game.whitePlayer.userId ?? getLightUser) -> (game.blackPlayer.userId ?? getLightUser)
|
2012-07-01 12:31:13 -06:00
|
|
|
|
2013-09-16 07:58:49 -06:00
|
|
|
private val dateFormat = DateTimeFormat forPattern "yyyy.MM.dd";
|
2012-05-08 05:23:49 -06:00
|
|
|
|
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
|
|
|
|
2014-07-17 15:53:37 -06:00
|
|
|
private 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 " + _)
|
2014-07-17 15:53:37 -06:00
|
|
|
|
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
|
|
|
|
2016-05-19 12:39:19 -06:00
|
|
|
def tags(
|
2015-10-01 01:54:38 -06:00
|
|
|
game: Game,
|
|
|
|
initialFen: Option[String],
|
2017-02-14 08:34:07 -07:00
|
|
|
imported: Option[ParsedPgn]
|
|
|
|
): List[Tag] = gameLightUsers(game) match {
|
2014-11-10 08:13:52 -07:00
|
|
|
case (wu, bu) => List(
|
2015-10-01 01:54:38 -06:00
|
|
|
Tag(_.Event, imported.flatMap(_ tag "event") | {
|
2015-09-02 18:32:29 -06:00
|
|
|
if (game.imported) "Import"
|
2015-10-01 01:54:38 -06:00
|
|
|
else game.rated.fold("Rated game", "Casual game")
|
|
|
|
}),
|
2014-11-10 08:13:52 -07:00
|
|
|
Tag(_.Site, gameUrl(game.id)),
|
2015-10-01 01:54:38 -06:00
|
|
|
Tag(_.Date, imported.flatMap(_ tag "date") | dateFormat.print(game.createdAt)),
|
2014-11-10 08:13:52 -07:00
|
|
|
Tag(_.White, player(game.whitePlayer, wu)),
|
|
|
|
Tag(_.Black, player(game.blackPlayer, bu)),
|
|
|
|
Tag(_.Result, result(game)),
|
2017-01-14 13:45:03 -07:00
|
|
|
Tag(_.WhiteElo, rating(game.whitePlayer)),
|
|
|
|
Tag(_.BlackElo, rating(game.blackPlayer)),
|
2014-11-10 08:13:52 -07:00
|
|
|
Tag("PlyCount", game.turns),
|
|
|
|
Tag(_.Variant, game.variant.name.capitalize),
|
|
|
|
Tag(_.TimeControl, game.clock.fold("-") { c => s"${c.limit}+${c.increment}" }),
|
2016-02-25 03:03:09 -07:00
|
|
|
Tag(_.ECO, game.opening.fold("?")(_.opening.eco)),
|
|
|
|
Tag(_.Opening, game.opening.fold("?")(_.opening.name)),
|
2015-08-18 07:56:18 -06:00
|
|
|
Tag(_.Termination, {
|
|
|
|
import chess.Status._
|
|
|
|
game.status match {
|
2017-02-14 08:34:07 -07:00
|
|
|
case Created | Started => "Unterminated"
|
|
|
|
case Aborted | NoStart => "Abandoned"
|
|
|
|
case Timeout | Outoftime => "Time forfeit"
|
2015-08-18 07:58:36 -06:00
|
|
|
case Resign | Draw | Stalemate | Mate | VariantEnd => "Normal"
|
2017-02-14 08:34:07 -07:00
|
|
|
case Cheat => "Rules infraction"
|
|
|
|
case UnknownFinish => "Unknown"
|
2015-08-18 07:56:18 -06:00
|
|
|
}
|
|
|
|
})
|
2014-11-10 08:13:52 -07:00
|
|
|
) ::: customStartPosition(game.variant).??(List(
|
|
|
|
Tag(_.FEN, initialFen | "?"),
|
|
|
|
Tag("SetUp", "1")
|
|
|
|
))
|
2014-05-11 02:23:30 -06:00
|
|
|
}
|
2012-07-01 12:31:13 -06:00
|
|
|
|
2017-03-28 10:01:05 -06:00
|
|
|
private def turns(moves: List[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 {
|
2017-03-28 09:39:28 -06:00
|
|
|
case (moves, index) =>
|
2017-03-28 10:01:05 -06:00
|
|
|
val clockOffset = startColor.fold(0, 1)
|
2017-03-28 09:39:28 -06:00
|
|
|
chessPgn.Turn(
|
|
|
|
number = index + from,
|
|
|
|
white = moves.headOption filter (".." !=) map { san =>
|
|
|
|
chessPgn.Move(
|
|
|
|
san = san,
|
2017-03-29 05:51:33 -06:00
|
|
|
secondsLeft = clocks lift (index * 2 - clockOffset) map (_.roundSeconds)
|
2017-03-28 09:39:28 -06:00
|
|
|
)
|
|
|
|
},
|
|
|
|
black = moves lift 1 map { san =>
|
|
|
|
chessPgn.Move(
|
|
|
|
san = san,
|
2017-03-29 05:51:33 -06:00
|
|
|
secondsLeft = clocks lift (index * 2 + 1 - clockOffset) map (_.roundSeconds)
|
2017-03-28 09:39:28 -06:00
|
|
|
)
|
|
|
|
}
|
2017-03-28 06:53:58 -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
|
|
|
|
2013-03-25 08:44:51 -06:00
|
|
|
def result(game: Game) = game.finished.fold(
|
2014-02-17 02:12:19 -07:00
|
|
|
game.winnerColor.fold("1/2-1/2")(color => color.white.fold("1-0", "0-1")),
|
2017-02-14 08:34:07 -07:00
|
|
|
"*"
|
|
|
|
)
|
2012-05-08 05:23:49 -06:00
|
|
|
}
|