Skip to content

Commit

Permalink
Merge pull request #15062 from lenguyenthanh/support-emt-tag-in-study
Browse files Browse the repository at this point in the history
Support emt tag in study
  • Loading branch information
ornicar authored Apr 15, 2024
2 parents 936a53e + 60db16a commit a2cab41
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 117 deletions.
8 changes: 2 additions & 6 deletions modules/study/src/main/BSONHandlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ object BSONHandlers:
def reads(r: Reader) =
val brush = r.str("b")
r.getO[Square]("p")
.map { pos =>
Shape.Circle(brush, pos)
}
.map(Shape.Circle(brush, _))
.getOrElse(Shape.Arrow(brush, r.get[Square]("o"), r.get[Square]("d")))
def writes(w: Writer, t: Shape) =
t match
Expand Down Expand Up @@ -106,9 +104,7 @@ object BSONHandlers:
val intReader = collectionReader[List, Int]
tryHandler[Glyphs](
{ case arr: Barr =>
intReader.readTry(arr).map { ints =>
Glyphs.fromList(ints.flatMap(Glyph.find))
}
intReader.readTry(arr).map(ints => Glyphs.fromList(ints.flatMap(Glyph.find)))
},
x => BSONArray(x.toList.map(_.id).map(BSONInteger.apply))
)
Expand Down
20 changes: 14 additions & 6 deletions modules/study/src/main/CommentParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ private[study] object CommentParser:
private val arrowsRemoveRegex = """\[\%cal[\s\r\n]++((?:\w{5}[,\s]*+)++)\]""".r
private val clockRegex = """(?s)\[\%clk[\s\r\n]++([\d:\.]++)\]""".r.unanchored
private val clockRemoveRegex = """\[\%clk[\s\r\n]++[\d:\.]++\]""".r
private val emtRegex = """(?s)\[\%emt[\s\r\n]++([\d:\.]++)\]""".r.unanchored
private val emtRemoveRegex = """\[\%emt[\s\r\n]++[\d:\.]++\]""".r
private val tcecClockRegex = """(?s)tl=([\d:\.]++)""".r.unanchored
private val tcecClockRemoveRegex = """tl=[\d:\.]++""".r

case class ParsedComment(
shapes: Shapes,
clock: Option[Centis],
emt: Option[Centis],
comment: String
)

def apply(comment: ChessComment): ParsedComment =
parseShapes(comment.value) match
case (shapes, c2) =>
parseClock(c2) match
case (clock, c3) => ParsedComment(shapes, clock, c3)
val (shapes, c1) = parseShapes(comment.value)
val (clock, c2) = parseClock(c1)
val (emt, c3) = parseEmt(c2)
ParsedComment(shapes, clock, emt, c3)

private type ClockAndComment = (Option[Centis], String)
private type CentisAndComment = (Option[Centis], String)

private def readCentis(hours: String, minutes: String, seconds: String): Option[Centis] =
for
Expand All @@ -52,12 +55,17 @@ private[study] object CommentParser:
readCentis(hours, minutes, seconds)
case _ => none

private def parseClock(comment: String): ClockAndComment =
private def parseClock(comment: String): CentisAndComment =
comment match
case clockRegex(str) => readCentis(str) -> clockRemoveRegex.replaceAllIn(comment, "").trim
case tcecClockRegex(str) => readCentis(str) -> tcecClockRemoveRegex.replaceAllIn(comment, "").trim
case _ => None -> comment

private def parseEmt(comment: String): CentisAndComment =
comment match
case emtRegex(str) => readCentis(str) -> emtRemoveRegex.replaceAllIn(comment, "").trim
case _ => None -> comment

private type ShapesAndComment = (Shapes, String)

private def parseShapes(comment: String): ShapesAndComment =
Expand Down
86 changes: 47 additions & 39 deletions modules/study/src/main/NewPgnImport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import lila.core.LightUser
import lila.importer.{ ImportData, Preprocessed }
import lila.tree.{ NewRoot, NewTree, NewBranch, Metas }

case class Context(
currentGame: chess.Game,
currentClock: Option[Centis],
previousClock: Option[Centis]
)

object NewPgnImport:

case class Result(
Expand All @@ -33,7 +39,9 @@ object NewPgnImport:
case Preprocessed(game, replay, initialFen, parsedPgn) =>
val annotator = PgnImport.findAnnotator(parsedPgn, contributors)
PgnImport.parseComments(parsedPgn.initialPosition.comments, annotator) match
case (shapes, _, comments) =>
case (shapes, _, _, comments) =>
val clock = parsedPgn.tags.clockConfig.map(_.limit)
val setup = Context(replay.setup, clock, clock)
val root: NewRoot =
NewRoot(
Metas(
Expand All @@ -49,9 +57,9 @@ object NewPgnImport:
glyphs = Glyphs.empty,
opening = None,
crazyData = replay.setup.situation.board.crazyData,
clock = parsedPgn.tags.clockConfig.map(_.limit)
clock = clock
),
parsedPgn.tree.flatMap(makeTree(replay.setup, _, annotator))
parsedPgn.tree.flatMap(makeTree(setup, _, annotator))
)
val end: Option[PgnImport.End] = (game.finished.option(game.status)).map { status =>
PgnImport.End(
Expand All @@ -78,52 +86,52 @@ object NewPgnImport:
}

private def makeTree(
setup: chess.Game,
context: Context,
node: ParsedPgnTree,
annotator: Option[Comment.Author]
): Option[PgnNode[NewBranch]] =
node.mapAccumlOption_(setup): (setup, data) =>
transform(setup, data, annotator)
node.mapAccumlOption_(context): (context, data) =>
transform(context, data, annotator)

private def transform(
context: chess.Game,
context: Context,
data: PgnNodeData,
annotator: Option[Comment.Author]
): (chess.Game, Option[NewBranch]) =
): (Context, Option[NewBranch]) =
data
.san(context.situation)
.san(context.currentGame.situation)
.map(moveOrDrop =>
val game = moveOrDrop.applyGame(context)
val game = moveOrDrop.applyGame(context.currentGame)
val uci = moveOrDrop.toUci
val id = UciCharPair(uci)
val sanStr = moveOrDrop.toSanStr
(
game,
PgnImport.parseComments(data.metas.comments, annotator) match
case (shapes, clock, comments) =>
NewBranch(
id = id,
move = Uci.WithSan(uci, sanStr),
comp = false,
forceVariation = false,
Metas(
ply = game.ply,
fen = Fen.write(game),
check = game.situation.check,
dests = None,
drops = None,
eval = None,
shapes = shapes,
comments = comments,
gamebook = None,
glyphs = data.metas.glyphs,
opening = None,
clock = clock,
crazyData = game.situation.board.crazyData
)
).some
)
val newBranch = PgnImport.parseComments(data.metas.comments, annotator) match
case (shapes, clock, emt, comments) =>
NewBranch(
id = id,
move = Uci.WithSan(uci, sanStr),
comp = false,
forceVariation = false,
Metas(
ply = game.ply,
fen = Fen.write(game),
check = game.situation.check,
dests = None,
drops = None,
eval = None,
shapes = shapes,
comments = comments,
gamebook = None,
glyphs = data.metas.glyphs,
opening = None,
clock = clock.orElse((context.previousClock, emt).mapN(_ - _)),
crazyData = game.situation.board.crazyData
)
)

(Context(game, newBranch.metas.clock, context.currentClock), newBranch.some)
)
.toOption match
case Some(branch) => branch
case None => (context, None)
.toOption
.match
case Some(branch) => branch
case None => (context, None)
57 changes: 33 additions & 24 deletions modules/study/src/main/PgnImport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,21 @@ object PgnImport:
ImportData(pgn, analyse = none).preprocess(user = none).map {
case Preprocessed(game, replay, initialFen, parsedPgn) =>
val annotator = findAnnotator(parsedPgn, contributors)

val clock = parsedPgn.tags.clockConfig.map(_.limit)
parseComments(parsedPgn.initialPosition.comments, annotator) match
case (shapes, _, comments) =>
case (shapes, _, _, comments) =>
val root = Root(
ply = replay.setup.ply,
fen = initialFen | game.variant.initialFen,
check = replay.setup.situation.check,
shapes = shapes,
comments = comments,
glyphs = Glyphs.empty,
clock = parsedPgn.tags.clockConfig.map(_.limit),
clock = clock,
crazyData = replay.setup.situation.board.crazyData,
children = parsedPgn.tree.fold(Branches.empty)(makeBranches(replay.setup, _, annotator))
children = parsedPgn.tree
.fold(Branches.empty)(makeBranches(Context(replay.setup, clock, clock), _, annotator))
)
val end: Option[End] = (game.finished
.option(game.status))
Expand Down Expand Up @@ -88,47 +91,51 @@ object PgnImport:
def parseComments(
comments: List[ChessComment],
annotator: Option[Comment.Author]
): (Shapes, Option[Centis], Comments) =
comments.foldLeft((Shapes(Nil), none[Centis], Comments(Nil))) { case ((shapes, clock, comments), txt) =>
CommentParser(txt) match
case CommentParser.ParsedComment(s, c, str) =>
(
(shapes ++ s),
c.orElse(clock),
(str.trim match
case "" => comments
case com =>
comments + Comment(Comment.Id.make, Comment.Text(com), annotator | Comment.Author.Lichess)
): (Shapes, Option[Centis], Option[Centis], Comments) =
comments.foldLeft((Shapes(Nil), none[Centis], none[Centis], Comments(Nil))) {
case ((shapes, clock, emt, comments), txt) =>
CommentParser(txt) match
case CommentParser.ParsedComment(s, c, e, str) =>
(
(shapes ++ s),
c.orElse(clock),
e.orElse(emt),
(str.trim match
case "" => comments
case com =>
comments + Comment(Comment.Id.make, Comment.Text(com), annotator | Comment.Author.Lichess)
)
)
)
}

private def makeBranches(
prev: chess.Game,
context: Context,
node: PgnNode[PgnNodeData],
annotator: Option[Comment.Author]
): Branches =
val variations = node.take(Node.MAX_PLIES).variations.flatMap(x => makeBranch(prev, x.toNode, annotator))
val variations =
node.take(Node.MAX_PLIES).variations.flatMap(x => makeBranch(context, x.toNode, annotator))
removeDuplicatedChildrenFirstNode(
Branches(makeBranch(prev, node, annotator).fold(variations)(_ +: variations))
Branches(makeBranch(context, node, annotator).fold(variations)(_ +: variations))
)

private def makeBranch(
prev: chess.Game,
context: Context,
node: PgnNode[PgnNodeData],
annotator: Option[Comment.Author]
): Option[Branch] =
try
node.value
.san(prev.situation)
.san(context.currentGame.situation)
.fold(
_ => none, // illegal move; stop here.
moveOrDrop =>
val game = moveOrDrop.applyGame(prev)
val game = moveOrDrop.applyGame(context.currentGame)
val uci = moveOrDrop.toUci
val sanStr = moveOrDrop.toSanStr
parseComments(node.value.metas.comments, annotator) match
case (shapes, clock, comments) =>
case (shapes, clock, emt, comments) =>
val computedClock = clock.orElse((context.previousClock, emt).mapN(_ - _))
Branch(
id = UciCharPair(uci),
ply = game.ply,
Expand All @@ -138,9 +145,11 @@ object PgnImport:
shapes = shapes,
comments = comments,
glyphs = node.value.metas.glyphs,
clock = clock,
clock = computedClock,
crazyData = game.situation.board.crazyData,
children = node.child.fold(Branches.empty)(makeBranches(game, _, annotator))
children = node.child.fold(Branches.empty)(
makeBranches(Context(game, computedClock, context.currentClock), _, annotator)
)
).some
)
catch
Expand Down
Loading

0 comments on commit a2cab41

Please sign in to comment.