119 lines
4.5 KiB
Scala
119 lines
4.5 KiB
Scala
package lila.importer
|
|
|
|
import cats.data.Validated
|
|
import chess.format.pgn.{ ParsedPgn, Parser, Reader, Tag, TagType, Tags }
|
|
import chess.format.{ FEN, Forsyth }
|
|
import chess.{ Color, Mode, Replay, Status }
|
|
import play.api.data._
|
|
import play.api.data.Forms._
|
|
import scala.util.chaining._
|
|
|
|
import lila.game._
|
|
|
|
final class ImporterForm {
|
|
|
|
lazy val importForm = Form(
|
|
mapping(
|
|
"pgn" -> nonEmptyText.verifying("invalidPgn", p => checkPgn(p).isValid),
|
|
"analyse" -> optional(nonEmptyText)
|
|
)(ImportData.apply)(ImportData.unapply)
|
|
)
|
|
|
|
def checkPgn(pgn: String): Validated[String, Preprocessed] = ImportData(pgn, none).preprocess(none)
|
|
}
|
|
|
|
private case class TagResult(status: Status, winner: Option[Color])
|
|
case class Preprocessed(
|
|
game: NewGame,
|
|
replay: Replay,
|
|
initialFen: Option[FEN],
|
|
parsed: ParsedPgn
|
|
)
|
|
|
|
case class ImportData(pgn: String, analyse: Option[String]) {
|
|
|
|
private type TagPicker = Tag.type => TagType
|
|
|
|
private val maxPlies = 600
|
|
|
|
private def evenIncomplete(result: Reader.Result): Replay =
|
|
result match {
|
|
case Reader.Result.Complete(replay) => replay
|
|
case Reader.Result.Incomplete(replay, _) => replay
|
|
}
|
|
|
|
def preprocess(user: Option[String]): Validated[String, Preprocessed] =
|
|
Parser.full(pgn) flatMap { parsed =>
|
|
Reader.fullWithSans(
|
|
pgn,
|
|
sans => sans.copy(value = sans.value take maxPlies),
|
|
Tags.empty
|
|
) map evenIncomplete map {
|
|
case replay @ Replay(setup, _, state) =>
|
|
val initBoard = parsed.tags.fen.map(_.value) flatMap Forsyth.<< map (_.board)
|
|
val fromPosition = initBoard.nonEmpty && !parsed.tags.fen.contains(FEN(Forsyth.initial))
|
|
val variant = {
|
|
parsed.tags.variant | {
|
|
if (fromPosition) chess.variant.FromPosition
|
|
else chess.variant.Standard
|
|
}
|
|
} match {
|
|
case chess.variant.Chess960 if !Chess960.isStartPosition(setup.board) =>
|
|
chess.variant.FromPosition
|
|
case chess.variant.FromPosition if parsed.tags.fen.isEmpty => chess.variant.Standard
|
|
case chess.variant.Standard if fromPosition => chess.variant.FromPosition
|
|
case v => v
|
|
}
|
|
val game = state.copy(situation = state.situation withVariant variant)
|
|
val initialFen = parsed.tags.fen.map(_.value) flatMap {
|
|
Forsyth.<<<@(variant, _)
|
|
} map Forsyth.>> map FEN.apply
|
|
|
|
val status = parsed.tags(_.Termination).map(_.toLowerCase) match {
|
|
case Some("normal") | None => Status.Resign
|
|
case Some("abandoned") => Status.Aborted
|
|
case Some("time forfeit") => Status.Outoftime
|
|
case Some("rules infraction") => Status.Cheat
|
|
case Some(_) => Status.UnknownFinish
|
|
}
|
|
|
|
val date = parsed.tags.anyDate
|
|
|
|
def name(whichName: TagPicker, whichRating: TagPicker): String =
|
|
parsed.tags(whichName).fold("?") { n =>
|
|
n + ~parsed.tags(whichRating).map(e => s" (${e take 8})")
|
|
}
|
|
|
|
val dbGame = Game
|
|
.make(
|
|
chess = game,
|
|
whitePlayer = Player.make(chess.White, None) withName name(_.White, _.WhiteElo),
|
|
blackPlayer = Player.make(chess.Black, None) withName name(_.Black, _.BlackElo),
|
|
mode = Mode.Casual,
|
|
source = Source.Import,
|
|
pgnImport = PgnImport.make(user = user, date = date, pgn = pgn).some
|
|
)
|
|
.sloppy
|
|
.start pipe { dbGame =>
|
|
// apply the result from the board or the tags
|
|
game.situation.status match {
|
|
case Some(situationStatus) => dbGame.finish(situationStatus, game.situation.winner).game
|
|
case None =>
|
|
parsed.tags.resultColor
|
|
.map {
|
|
case Some(color) => TagResult(status, color.some)
|
|
case None if status == Status.Outoftime => TagResult(status, none)
|
|
case None => TagResult(Status.Draw, none)
|
|
}
|
|
.filter(_.status > Status.Started)
|
|
.fold(dbGame) { res =>
|
|
dbGame.finish(res.status, res.winner).game
|
|
}
|
|
}
|
|
}
|
|
|
|
Preprocessed(NewGame(dbGame), replay.copy(state = game), initialFen, parsed)
|
|
}
|
|
}
|
|
}
|