import PGN variations and comments in study - WIP

pull/1885/head
Thibault Duplessis 2016-05-17 14:26:33 +02:00
parent f17ebc390b
commit 003ffad1f8
6 changed files with 92 additions and 10 deletions

@ -1 +1 @@
Subproject commit b32f8c8941602f4ff0ab0088ad29939934a9bf46
Subproject commit 286395e16efc955fbbd10fcbaccb26f081f3337a

View File

@ -21,11 +21,12 @@ private[importer] final class DataForm {
}
private[importer] case class Result(status: Status, winner: Option[Color])
private[importer] case class Preprocessed(
case class Preprocessed(
game: Game,
replay: Replay,
result: Option[Result],
initialFen: Option[FEN])
initialFen: Option[FEN],
parsed: ParsedPgn)
case class ImportData(pgn: String, analyse: Option[String]) {
@ -35,7 +36,7 @@ case class ImportData(pgn: String, analyse: Option[String]) {
def preprocess(user: Option[String]): Valid[Preprocessed] = Parser.full(pgn) flatMap {
case ParsedPgn(_, sans) if sans.size > maxPlies => !!("Replay is too long")
case ParsedPgn(tags, sans) => Reader.full(pgn) map {
case parsed@ParsedPgn(tags, sans) => Reader.full(pgn) map {
case replay@Replay(setup, _, game) =>
def tag(which: Tag.type => TagType): Option[String] =
tags find (_.name == which(Tag)) map (_.value)
@ -79,7 +80,7 @@ case class ImportData(pgn: String, analyse: Option[String]) {
binaryPgn = BinaryFormat.pgn write replay.state.pgnMoves
).start
Preprocessed(dbGame, replay, result, initialFen)
Preprocessed(dbGame, replay, result, initialFen, parsed)
}
}
}

View File

@ -38,7 +38,7 @@ final class Importer(
gameExists {
(data preprocess user).future flatMap {
case Preprocessed(g, replay, result, initialFen) =>
case Preprocessed(g, replay, result, initialFen, _) =>
val started = forceId.fold(g)(g.withId).start
val game = applyResult(started, result, replay.state.situation)
(GameRepo.insertDenormalized(game, initialFen = initialFen)) >> {
@ -55,6 +55,6 @@ final class Importer(
}
def inMemory(data: ImportData): Valid[(Game, Option[FEN])] = data.preprocess(user = none).map {
case Preprocessed(game, replay, _, fen) => (game withId "synthetic", fen)
case Preprocessed(game, replay, _, fen, _) => (game withId "synthetic", fen)
}
}

View File

@ -100,6 +100,7 @@ object Node {
case class Comment(text: String, by: String)
object Comment {
def byLichess(text: String) = Comment(text, "lichess")
def byUnknown(text: String) = Comment(text, "")
def sanitize(text: String) = text.trim().take(2000)
.replaceAll("""\r\n""", "\n") // these 3 lines dedup white spaces and new lines
.replaceAll("""(?m)(^ *| +(?= |$))""", "")

View File

@ -23,9 +23,17 @@ private final class ChapterMaker(domain: String, importer: Importer) {
}
private def fromPgn(study: Study, pgn: String, data: Data, orientation: Color, order: Int): Fu[Option[Chapter]] =
importer.inMemory(ImportData(pgn, analyse = none)).fold(err => fufail(err.shows), {
case (game, fen) => fromPov(study, Pov(game, orientation), data, order, fen)
})
PgnImport(pgn).future map { res =>
Chapter.make(
studyId = study.id,
name = data.name,
setup = Chapter.Setup(
none,
res.variant,
orientation),
root = res.root,
order = order).some
}
private def fromFenOrBlank(study: Study, data: Data, orientation: Color, order: Int): Option[Chapter] = {
val variant = data.variant.flatMap(Variant.apply) | Variant.default

View File

@ -0,0 +1,72 @@
package lila.study
import scalaz.Validation.FlatMap._
import chess.format.pgn.{ Pgn, Tag, TagType, Parser, ParsedPgn, Glyphs, San, Dumper }
import chess.format.{ Forsyth, FEN, Uci, UciCharPair }
import lila.importer.{ ImportData, Preprocessed }
import lila.socket.tree.Node.{ Comment, Comments }
private object PgnImport {
private type TagPicker = Tag.type => TagType
case class Result(
root: Node.Root,
variant: chess.variant.Variant)
def apply(pgn: String): Valid[Result] =
ImportData(pgn, analyse = none).preprocess(user = none).map {
case Preprocessed(game, replay, result, initialFen, ParsedPgn(tags, sans)) => Result(
root = Node.Root(
ply = replay.setup.turns,
fen = initialFen | FEN(game.variant.initialFen),
check = replay.setup.situation.check,
shapes = Nil,
comments = Comments(Nil),
glyphs = Glyphs.empty,
crazyData = replay.setup.situation.board.crazyData,
children = makeNode(replay.setup, sans).fold(
err => {
logger.warn(s"PgnImport $err")
Node.emptyChildren
},
node => Node.Children(node.toVector)
)),
variant = game.variant)
}
private def makeNode(prev: chess.Game, sans: List[San]): Valid[Option[Node]] =
sans match {
case Nil => success(none)
case san :: rest => san(prev.situation) flatMap { moveOrDrop =>
val game = moveOrDrop.fold(prev.apply, prev.applyDrop)
val uci = moveOrDrop.fold(_.toUci, _.toUci)
val sanStr = moveOrDrop.fold(Dumper.apply, Dumper.apply)
makeNode(game, rest) map { mainline =>
val variations = rest.headOption.?? {
_.metas.variations.flatMap { variation =>
makeNode(game, variation).fold({ err =>
logger.warn(s"$variation $err")
none
}, identity)
}
}
Node(
id = UciCharPair(uci),
ply = game.turns,
move = Uci.WithSan(uci, sanStr),
fen = FEN(Forsyth >> game),
check = game.situation.check,
shapes = Nil,
comments = Comments(san.metas.comments map Comment.byUnknown),
glyphs = san.metas.glyphs,
crazyData = game.situation.board.crazyData,
children = Node.Children {
mainline.fold(variations)(_ :: variations).toVector
}
).some
}
}
}
}