From 96189f053ed4abfa25aac8f7665265ce221ecac9 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 29 Dec 2024 12:37:20 +0100 Subject: [PATCH] parse broadcast time control and use increment in %emt computations --- modules/relay/src/main/RelayFetch.scala | 6 ++-- modules/relay/src/main/RelayPush.scala | 2 +- modules/relay/src/main/RelayTour.scala | 13 ++++++-- modules/relay/src/main/ui/RelayFormUi.scala | 2 +- modules/study/src/main/PgnTags.scala | 2 +- modules/study/src/main/StudyPgnImport.scala | 32 +++++++++++++++---- .../study/src/main/StudyPgnImportNew.scala | 15 ++++----- project/Dependencies.scala | 2 +- 8 files changed, 50 insertions(+), 24 deletions(-) diff --git a/modules/relay/src/main/RelayFetch.scala b/modules/relay/src/main/RelayFetch.scala index 43fa4b3454e5c..0d4c0737b9f13 100644 --- a/modules/relay/src/main/RelayFetch.scala +++ b/modules/relay/src/main/RelayFetch.scala @@ -89,10 +89,10 @@ final private class RelayFetch( sliced = RelayGame.Slices.filter(~rt.round.sync.slices)(filtered) limited = sliced.take(RelayFetch.maxChaptersToShow.value) withPlayers <- playerEnrich.enrichAndReportAmbiguous(rt)(limited) - enriched <- fidePlayers.enrichGames(rt.tour)(withPlayers) - withTeams = rt.tour.teams.fold(enriched)(_.update(enriched)) + withFide <- fidePlayers.enrichGames(rt.tour)(withPlayers) + enriched = rt.tour.enrichGames(withFide) res <- sync - .updateStudyChapters(rt, withTeams) + .updateStudyChapters(rt, enriched) .withTimeoutError(7 seconds, SyncResult.Timeout) .mon(_.relay.syncTime(rt.tour.official, rt.tour.id, rt.tour.slug)) games = res.plan.input.games diff --git a/modules/relay/src/main/RelayPush.scala b/modules/relay/src/main/RelayPush.scala index 248f883800954..9112604eb2b78 100644 --- a/modules/relay/src/main/RelayPush.scala +++ b/modules/relay/src/main/RelayPush.scala @@ -53,7 +53,7 @@ final class RelayPush( for withPlayers <- playerEnrich.enrichAndReportAmbiguous(rt)(rawGames) enriched <- fidePlayers.enrichGames(rt.tour)(withPlayers) - games = rt.tour.teams.fold(enriched)(_.update(enriched)) + games = rt.tour.enrichGames(enriched) event <- sync .updateStudyChapters(rt, games) .map: res => diff --git a/modules/relay/src/main/RelayTour.scala b/modules/relay/src/main/RelayTour.scala index 4e96c67d030a2..0907a4c7d64f1 100644 --- a/modules/relay/src/main/RelayTour.scala +++ b/modules/relay/src/main/RelayTour.scala @@ -9,6 +9,8 @@ import lila.core.id.ImageId import lila.core.misc.PicfitUrl import lila.core.fide.FideTC import lila.core.study.Visibility +import chess.TournamentClock +import chess.format.pgn.Tag case class RelayTour( @Key("_id") id: RelayTourId, @@ -52,6 +54,12 @@ case class RelayTour( then Visibility.`private` else Visibility.public + def enrichGames(games: RelayGames): RelayGames = + val withTeams = teams.fold(games)(_.update(games)) + info.clock.fold(withTeams): clock => + val tag = Tag.timeControl(clock) + withTeams.map(g => g.copy(tags = g.tags + tag)) + object RelayTour: val maxRelays = Max(64) @@ -94,8 +102,9 @@ object RelayTour: ): def nonEmpty = List(format, tc, fideTc, location, players, website, standings).flatten.nonEmpty override def toString = List(format, tc, fideTc, location, players).flatten.mkString(" | ") - lazy val fideTcOrGuess: FideTC = fideTc | FideTC.standard - def timeZoneOrDefault: ZoneId = timeZone | ZoneId.systemDefault + lazy val fideTcOrGuess: FideTC = fideTc | FideTC.standard + def timeZoneOrDefault: ZoneId = timeZone | ZoneId.systemDefault + def clock: Option[TournamentClock] = tc.flatMap(TournamentClock.parse.apply) case class Dates(start: Instant, end: Option[Instant]) diff --git a/modules/relay/src/main/ui/RelayFormUi.scala b/modules/relay/src/main/ui/RelayFormUi.scala index 489bcaf690649..0199c4036e4a0 100644 --- a/modules/relay/src/main/ui/RelayFormUi.scala +++ b/modules/relay/src/main/ui/RelayFormUi.scala @@ -468,7 +468,7 @@ final class RelayFormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): form3.group( form("info.tc"), trs.timeControl(), - help = frag(""""Classical" or "Rapid" or "Rapid & Blitz"""").some, + help = frag("""e.g. "15 min + 10 sec" or "15+10"""").some, half = true )(form3.input(_)), form3.group( diff --git a/modules/study/src/main/PgnTags.scala b/modules/study/src/main/PgnTags.scala index af3cad076af00..201d5e9590d5c 100644 --- a/modules/study/src/main/PgnTags.scala +++ b/modules/study/src/main/PgnTags.scala @@ -12,7 +12,7 @@ object PgnTags: tags.pipe(filterRelevant(types)).pipe(removeContradictingTermination).pipe(sort) def setRootClockFromTags(c: Chapter): Option[Chapter] = - c.updateRoot { _.setClockAt(c.tags.clockConfig.map(_.limit), UciPath.root) }.filter(c !=) + c.updateRoot { _.setClockAt(c.tags.timeControl.map(_.limit), UciPath.root) }.filter(c !=) // clean up tags before exposing them def cleanUpForPublication(tags: Tags) = tags.copy( diff --git a/modules/study/src/main/StudyPgnImport.scala b/modules/study/src/main/StudyPgnImport.scala index b0c56a22a54a5..bd81ad1bab161 100644 --- a/modules/study/src/main/StudyPgnImport.scala +++ b/modules/study/src/main/StudyPgnImport.scala @@ -3,7 +3,7 @@ package lila.study import chess.MoveOrDrop.* import chess.format.pgn.{ Comment as ChessComment, Glyphs, ParsedPgn, PgnNodeData, PgnStr, Tags, Tag } import chess.format.{ Fen, Uci, UciCharPair } -import chess.{ Centis, ErrorStr, Node as PgnNode, Outcome, Status } +import chess.{ Centis, ErrorStr, Node as PgnNode, Outcome, Status, TournamentClock, Ply } import lila.core.LightUser import lila.tree.Node.{ Comment, Comments, Shapes } @@ -11,11 +11,19 @@ import lila.tree.{ Branch, Branches, ImportResult, Root } object StudyPgnImport: + case class Context( + currentGame: chess.Game, + timeControl: Option[TournamentClock], + currentClock: Option[Centis], + previousClock: Option[Centis] + ) + def apply(pgn: PgnStr, contributors: List[LightUser]): Either[ErrorStr, Result] = lila.tree.parseImport(pgn).map { case ImportResult(game, result, replay, initialFen, parsedPgn) => val annotator = findAnnotator(parsedPgn, contributors) - val clock = parsedPgn.tags.clockConfig.map(_.limit) + val timeControl = parsedPgn.tags.timeControl + val clock = timeControl.map(_.limit) parseComments(parsedPgn.initialPosition.comments, annotator) match case (shapes, _, _, comments) => val root = Root( @@ -27,8 +35,8 @@ object StudyPgnImport: glyphs = Glyphs.empty, clock = clock, crazyData = replay.setup.board.crazyData, - children = parsedPgn.tree - .fold(Branches.empty)(makeBranches(Context(replay.setup, clock, clock), _, annotator)) + children = parsedPgn.tree.fold(Branches.empty): + makeBranches(Context(replay.setup, timeControl, clock, clock), _, annotator) ) val end = result.map: res => @@ -133,7 +141,7 @@ object StudyPgnImport: val sanStr = moveOrDrop.toSanStr val (shapes, clock, emt, comments) = parseComments(node.value.metas.comments, annotator) val computedClock = clock - .orElse((context.previousClock, emt).mapN(_ - _)) + .orElse(context.previousClock.map(guessNewClockState(_, game.ply, context.timeControl, emt))) .filter(_ > Centis(0)) Branch( id = UciCharPair(uci), @@ -147,7 +155,11 @@ object StudyPgnImport: clock = computedClock, crazyData = game.situation.board.crazyData, children = node.child.fold(Branches.empty): - makeBranches(Context(game, computedClock, context.currentClock), _, annotator) + makeBranches( + Context(game, context.timeControl, computedClock, context.currentClock), + _, + annotator + ) ).some ) catch @@ -155,6 +167,14 @@ object StudyPgnImport: logger.warn(s"study PgnImport.makeNode StackOverflowError") None + private def guessNewClockState( + prev: Centis, + ply: Ply, + tc: Option[TournamentClock], + emt: Option[Centis] + ): Centis = + prev - ~emt + ~tc.map(_.incrementAtPly(ply)) + /* * Fix bad PGN like this one found on reddit: * 7. c4 (7. c4 Nf6) (7. c4 dxc4) 7... cxd4 diff --git a/modules/study/src/main/StudyPgnImportNew.scala b/modules/study/src/main/StudyPgnImportNew.scala index 136e39d91f862..ce0058bda904d 100644 --- a/modules/study/src/main/StudyPgnImportNew.scala +++ b/modules/study/src/main/StudyPgnImportNew.scala @@ -10,17 +10,13 @@ import lila.core.LightUser import lila.tree.Node.{ Comment, Comments } import lila.tree.{ ImportResult, Metas, NewBranch, NewRoot, NewTree } -case class Context( - currentGame: chess.Game, - currentClock: Option[Centis], - previousClock: Option[Centis] -) - /** This code is still unused, and is now out of sync with the StudyPgnImport it's supposed to replace. Some * features are missing that are present in StudyPgnImport, such as the ability to replay clock states. */ object StudyPgnImportNew: + import StudyPgnImport.Context + case class Result( root: NewRoot, variant: chess.variant.Variant, @@ -33,8 +29,9 @@ object StudyPgnImportNew: val annotator = StudyPgnImport.findAnnotator(parsedPgn, contributors) StudyPgnImport.parseComments(parsedPgn.initialPosition.comments, annotator) match case (shapes, _, _, comments) => - val clock = parsedPgn.tags.clockConfig.map(_.limit) - val setup = Context(replay.setup, clock, clock) + val tc = parsedPgn.tags.timeControl + val clock = tc.map(_.limit) + val setup = Context(replay.setup, tc, clock, clock) val root: NewRoot = NewRoot( Metas( @@ -124,7 +121,7 @@ object StudyPgnImportNew: ) ) - (Context(game, newBranch.metas.clock, context.currentClock), newBranch.some) + (Context(game, context.timeControl, newBranch.metas.clock, context.currentClock), newBranch.some) ) .toOption .match diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 4263d9840f041..d0b8fb6eb63e5 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -42,7 +42,7 @@ object Dependencies { } object chess { - val version = "16.6.0" + val version = "17.0.0" val core = "org.lichess" %% "scalachess" % version val testKit = "org.lichess" %% "scalachess-test-kit" % version % Test val playJson = "org.lichess" %% "scalachess-play-json" % version