Skip to content

Commit

Permalink
Merge pull request #605 from lichess-org/tournament-clock
Browse files Browse the repository at this point in the history
Tournament clock
  • Loading branch information
ornicar authored Dec 29, 2024
2 parents 7eaf501 + d394e12 commit a642b7d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 14 deletions.
11 changes: 0 additions & 11 deletions core/src/main/scala/Clock.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package chess

import cats.syntax.option.none
import chess.Clock.Config

import java.text.DecimalFormat
Expand Down Expand Up @@ -207,16 +206,6 @@ object Clock:
if limitSeconds == 0 then increment.atLeast(Centis(300))
else limit

// [TimeControl "600+2"] -> 10+2
def readPgnConfig(str: String): Option[Config] =
str.split('+') match
case Array(initStr, incStr) =>
for
init <- initStr.toIntOption
inc <- incStr.toIntOption
yield Config(init, inc)
case _ => none

def apply(limit: LimitSeconds, increment: IncrementSeconds): Clock = apply(Config(limit, increment))

def apply(config: Config): Clock =
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/scala/TournamentClock.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package chess

import cats.syntax.all.*

import Clock.{ LimitSeconds, IncrementSeconds }

case class TournamentClock(limitSeconds: LimitSeconds, incrementSeconds: IncrementSeconds):

def toClockConfig: Option[Clock.Config] =
Clock.Config(limitSeconds, incrementSeconds).some

object TournamentClock:

object parse:

private val cleanRegex = "(move|minutes|minute|min|m|seconds|second|sec|s|'|\"|/)".r

private def make(a: Int, b: Int) =
val limit = LimitSeconds(if a > 180 then a else a * 60)
TournamentClock(limit, IncrementSeconds(b))

def apply(str: String): Option[TournamentClock] =
cleanRegex
.replaceAllIn(str.toLowerCase, "")
.replace(" ", "")
.split('+') match
case Array(a) => a.toIntOption.map(make(_, 0))
case Array(a, b) => (a.toIntOption, b.toIntOption).mapN(make)
case _ => none
2 changes: 1 addition & 1 deletion core/src/main/scala/format/pgn/Reader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ object Reader:

private def makeGame(tags: Tags) =
val g = Game(variantOption = tags.variant, fen = tags.fen)
g.copy(startedAtPly = g.ply, clock = tags.clockConfig.map(Clock.apply))
g.copy(startedAtPly = g.ply, clock = tags.clockConfig.flatMap(_.toClockConfig).map(Clock.apply))
4 changes: 2 additions & 2 deletions core/src/main/scala/format/pgn/Tag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ case class Tags(value: List[Tag]) extends AnyVal:
val name = which(Tag)
value.find(_.name == name).map(_.value)

def clockConfig: Option[Clock.Config] =
def clockConfig: Option[TournamentClock] =
value
.collectFirst { case Tag(Tag.TimeControl, str) => str }
.flatMap(Clock.readPgnConfig)
.flatMap(TournamentClock.parse.apply)

def variant: Option[chess.variant.Variant] =
apply(_.Variant)
Expand Down
47 changes: 47 additions & 0 deletions test-kit/src/test/scala/TournamentClockTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package chess

import Clock.*

class TournamentClockTest extends ChessTest:

import TournamentClock.parse

def someClock(minutes: Int, seconds: Int) = Some:
TournamentClock(LimitSeconds(minutes * 60), IncrementSeconds(seconds))

test("parse"):
assertEquals(parse(""), None)
assertEquals(parse("nope"), None)

assertEquals(parse("5+5"), someClock(5, 5))
assertEquals(parse("5+0"), someClock(5, 0))
assertEquals(parse("30+40"), someClock(30, 40))

assertEquals(parse("15m + 10s"), someClock(15, 10))
assertEquals(parse("15 m + 10 s"), someClock(15, 10))
assertEquals(parse("15min + 10sec"), someClock(15, 10))
assertEquals(parse("15m + 10 sec"), someClock(15, 10))
assertEquals(parse("15 min + 10 sec"), someClock(15, 10))
assertEquals(parse("15 min + 10 s"), someClock(15, 10))
assertEquals(parse("15 minutes + 10 seconds"), someClock(15, 10))
assertEquals(parse(" 15 MiNUTes+10SECOnds "), someClock(15, 10))

assertEquals(parse("15 min + 10 sec / move"), someClock(15, 10))
assertEquals(parse("15 min + 10 s / move"), someClock(15, 10))
assertEquals(parse("15 min + 10 seconds / move"), someClock(15, 10))
assertEquals(parse("15 minutes + 10 seconds / move"), someClock(15, 10))

assertEquals(parse("90 min + 30 sec / move"), someClock(90, 30))

assertEquals(parse("120' + 12\""), someClock(120, 12))
assertEquals(parse("120' + 12\"/move"), someClock(120, 12))
assertEquals(parse("120' + 12\" / move"), someClock(120, 12))
assertEquals(parse("7200+12"), someClock(120, 12))

assertEquals(parse("3600"), someClock(60, 0))
assertEquals(parse("60"), someClock(60, 0))
assertEquals(parse("180"), someClock(180, 0))
assertEquals(parse("240"), someClock(4, 0))

// we're not there yet
// assertEquals(parse("90 min / 40 moves + 30 min + 30 sec / move"), ???)

0 comments on commit a642b7d

Please sign in to comment.