Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions modules/relay/src/main/RelayFetch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ final private class RelayFetch(
.mon(_.relay.syncTime(rt.tour.official, rt.tour.id, rt.tour.slug))
games = res.plan.input.games
_ <- notifyAdmin.orphanBoards.inspectPlan(rt, res.plan)
nbGamesFinished = games.count(_.outcome.isDefined)
nbGamesFinished = games.count(_.points.isDefined)
nbGamesUnstarted = games.count(!_.hasMoves)
allGamesFinishedOrUnstarted = games.nonEmpty &&
nbGamesFinished + nbGamesUnstarted >= games.size &&
Expand Down Expand Up @@ -434,16 +434,23 @@ private object RelayFetch:
StudyPgnImport(pgn, Nil)
.leftMap(err => LilaInvalid(err.value))
.map: res =>
val fixedTags = // remove wrong ongoing result tag if the board has a mate on it
val fixedTags = Tags:
// remove wrong ongoing result tag if the board has a mate on it
if res.end.isDefined && res.tags(_.Result).has("*") then
Tags(res.tags.value.filter(_ != Tag(_.Result, "*")))
else res.tags
res.tags.value.filter(_ != Tag(_.Result, "*"))
// normalize result tag (e.g. 0.5-0 -> 1/2-0)
else
res.tags.value.map: tag =>
if tag.name == Tag.Result
then tag.copy(value = Outcome.showPoints(Outcome.pointsFromResult(tag.value)))
else tag

RelayGame(
tags = fixedTags,
variant = res.variant,
root = res.root.copy(
comments = Comments.empty,
children = res.root.children.updateMainline(_.copy(comments = Comments.empty))
),
outcome = res.end.map(_.outcome)
points = res.end.map(_.points)
)
13 changes: 8 additions & 5 deletions modules/relay/src/main/RelayGame.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lila.relay

import chess.Outcome
import chess.{ Outcome, Color }
import chess.Outcome.GamePoints
import chess.format.pgn.{ Tag, TagType, Tags }

import lila.study.{ MultiPgn, PgnDump }
Expand All @@ -10,7 +11,7 @@ case class RelayGame(
tags: Tags,
variant: chess.variant.Variant,
root: Root,
outcome: Option[Outcome]
points: Option[Outcome.GamePoints]
):
override def toString =
s"RelayGame ${root.mainlineNodeList.size} ${tags.outcome} ${tags.names} ${tags.fideIds}"
Expand Down Expand Up @@ -40,7 +41,7 @@ case class RelayGame(

def resetToSetup = withoutMoves.copy(
tags = tags.copy(value = tags.value.filter(_.name != Tag.Result)),
outcome = None
points = None
)

def fideIdsPair: Option[PairOf[Option[chess.FideId]]] =
Expand All @@ -50,7 +51,9 @@ case class RelayGame(
List(RelayGame.whiteTags, RelayGame.blackTags).exists:
_.forall(tag => tags(tag).isEmpty)

def showResult = Outcome.showResult(outcome)
private def outcome = points.flatMap(Outcome.fromPoints)

def showResult = Outcome.showPoints(points)

private object RelayGame:

Expand All @@ -67,7 +70,7 @@ private object RelayGame:
tags = c.tags,
variant = c.setup.variant,
root = c.root,
outcome = c.tags.outcome
points = c.tags.points
)

import scalalib.Iso
Expand Down
29 changes: 17 additions & 12 deletions modules/relay/src/main/RelayPlayer.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lila.relay

import scalalib.Json.writeAs
import chess.{ ByColor, Color, Elo, FideId, Outcome, PlayerName }
import lila.db.dsl.*
import lila.study.{ ChapterPreviewApi, StudyPlayer }
Expand All @@ -15,7 +16,7 @@ import lila.core.fide.FideTC
// Player in a tournament with current performance rating and list of games
case class RelayPlayer(
player: StudyPlayer.WithFed,
score: Option[Double],
score: Option[Float],
ratingDiff: Option[Int],
performance: Option[Elo],
games: Vector[RelayPlayer.Game]
Expand All @@ -30,22 +31,27 @@ object RelayPlayer:
id: StudyChapterId,
opponent: StudyPlayer.WithFed,
color: Color,
outcome: Option[Outcome]
points: Option[Outcome.GamePoints]
):
def playerOutcome: Option[Option[Boolean]] = outcome.map(_.winner.map(_ == color))
def playerPoints = points.map(_(color))
// only rate draws and victories, not exotic results
def isRated = points.exists(_.mapReduce(_.value)(_ + _) == 1)
def eloGame = for
o <- outcome
pp <- playerPoints
if isRated
opRating <- opponent.rating
yield Elo.Game(o.winner.map(_ == color), opRating)
yield Elo.Game(pp, opRating)

object json:
given Writes[Outcome] = Json.writes
given Writes[RelayPlayer.Game] = Json.writes
given Writes[Outcome] = Json.writes
given Writes[Outcome.Points] = writeAs(_.show)
given Writes[Outcome.GamePoints] = writeAs(points => Outcome.showPoints(points.some))
given Writes[RelayPlayer.Game] = Json.writes
given OWrites[RelayPlayer] = OWrites: p =>
Json.toJsObject(p.player) ++ Json
.obj(
"score" -> p.score,
"played" -> p.games.count(_.outcome.isDefined)
"played" -> p.games.count(_.points.isDefined)
)
.add("ratingDiff" -> p.ratingDiff)
.add("performance" -> p.performance)
Expand All @@ -65,7 +71,7 @@ object RelayPlayer:
"id" -> g.id,
"opponent" -> g.opponent,
"color" -> g.color,
"outcome" -> g.outcome
"points" -> g.playerPoints
)
.add("ratingDiff" -> rd)
Json.toJsObject(p).add("fide", fidePlayer) ++ Json.obj("games" -> gamesJson)
Expand Down Expand Up @@ -136,7 +142,7 @@ private final class RelayPlayerApi(
gamePlayers.zipColor.foldLeft(players):
case (players, (color, (playerId, player))) =>
val (_, opponent) = gamePlayers(!color)
val game = RelayPlayer.Game(roundId, chapterId, opponent, color, tags.outcome)
val game = RelayPlayer.Game(roundId, chapterId, opponent, color, tags.points)
players.updated(
playerId,
players
Expand Down Expand Up @@ -166,8 +172,7 @@ private final class RelayPlayerApi(
.mapValues: p =>
p.copy(
score = p.games
.foldLeft(0d): (score, game) =>
score + game.playerOutcome.so(_.fold(0.5)(_.so(1d)))
.foldMap(_.playerPoints.so(_.value))
.some,
performance = Elo.computePerformanceRating(p.eloGames)
)
Expand Down
4 changes: 2 additions & 2 deletions modules/relay/src/main/RelayPush.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ final class RelayPush(
_ = if !rt.round.hasStarted && !rt.tour.official && event.hasMoves then
irc.broadcastStart(rt.round.id, rt.fullName)
_ = stats.setActive(rt.round.id)
allGamesFinished <- (games.nonEmpty && games.forall(_.outcome.isDefined)).so:
allGamesFinished <- (games.nonEmpty && games.forall(_.points.isDefined)).so:
chapterPreview.dataList(rt.round.studyId).map(_.forall(_.finished))
round <- api.update(rt.round): r1 =>
val r2 = r1.withSync(_.addLog(event))
Expand All @@ -89,7 +89,7 @@ final class RelayPush(
children = game.root.children
.updateMainline(_.copy(comments = lila.tree.Node.Comments.empty))
),
outcome = game.end.map(_.outcome)
points = game.end.map(_.points)
)
)

Expand Down
2 changes: 1 addition & 1 deletion modules/relay/src/main/RelaySync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ final private class RelaySync(
if !chapter.tags.value.has(tag) then newTags + tag
else newTags
val newEndTag = (
game.outcome.isDefined &&
game.points.isDefined &&
gameTags(_.Result).isEmpty &&
!chapter.tags(_.Result).has(game.showResult)
).option(Tag(_.Result, game.showResult))
Expand Down
19 changes: 7 additions & 12 deletions modules/relay/src/main/RelayTeams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,8 @@ final class RelayTeamTable(
JsonStr(Json.stringify(Json.obj("table" -> ordered)))

case class TeamWithPoints(name: String, points: Float = 0):
def add(o: Option[Outcome], as: Color) =
copy(points = points + o.so(_.winner match
case Some(w) if w == as => 1
case None => 0.5f
case _ => 0
))
def add(result: Option[Outcome.GamePoints], as: Color) =
copy(points = points + result.so(_(as).value))
case class Pair[A](a: A, b: A):
def is(p: Pair[A]) = (a == p.a && b == p.b) || (a == p.b && b == p.a)
def map[B](f: A => B) = Pair(f(a), f(b))
Expand All @@ -104,24 +100,23 @@ final class RelayTeamTable(
def add(
chap: ChapterPreview,
playerAndTeam: Pair[(StudyPlayer, TeamName)],
outcome: Option[Outcome]
) =
points: Option[Outcome.GamePoints]
): TeamMatch =
val t0Color = Color.fromWhite(playerAndTeam.a._2 == teams.a.name)
if t0Color.white then playerAndTeam else playerAndTeam.reverse
copy(
games = TeamGame(chap.id, t0Color) :: games,
teams = teams.bimap(_.add(outcome, t0Color), _.add(outcome, !t0Color))
teams = teams.bimap(_.add(points, t0Color), _.add(points, !t0Color))
)
def swap = copy(teams = teams.reverse, games = games.map(_.swap))

def makeTable(chapters: List[ChapterPreview]): List[TeamMatch] =
chapters.reverse.foldLeft(List.empty[TeamMatch]): (table, chap) =>
(for
outcome <- chap.result
points <- chap.points
players <- chap.players
teams <- players.traverse(_.team).map(_.toPair).map(Pair.apply)
m0 = table.find(_.is(teams)) | TeamMatch(teams.map(TeamWithPoints(_)), Nil)
m1 = m0.add(chap, Pair(players.white.player -> teams.a, players.black.player -> teams.b), outcome)
m1 = m0.add(chap, Pair(players.white.player -> teams.a, players.black.player -> teams.b), points)
newTable = m1 :: table.filterNot(_.is(teams))
yield newTable) | table

Expand Down
12 changes: 6 additions & 6 deletions modules/study/src/main/StudyChapterPreview.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ case class ChapterPreview(
check: Option[Chapter.Check],
/* None = No Result PGN tag, the chapter may not be a game
* Some(None) = Result PGN tag is "*", the game is ongoing
* Some(Some(Outcome)) = Game is over with a result
* Some(Some(GamePoints)) = Game is over with a result
*/
result: Option[Option[Outcome]]
points: Option[Option[Outcome.GamePoints]]
):
def finished = result.exists(_.isDefined)
def finished = points.exists(_.isDefined)
def thinkTime = (!finished).so(lastMoveAt.map(at => (nowSeconds - at.toSeconds).toInt))
def fideIds: List[FideId] = players.so(_.mapList(_.fideId)).flatten

Expand Down Expand Up @@ -109,7 +109,7 @@ final class ChapterPreviewApi(
lastMove = denorm.flatMap(_.uci),
lastMoveAt = relay.map(_.lastMoveAt),
check = denorm.flatMap(_.check),
result = tags.outcome.isDefined.option(tags.outcome)
points = tags.points.isDefined.option(tags.points)
)

object federations:
Expand Down Expand Up @@ -155,7 +155,7 @@ object ChapterPreview:
.add("lastMove", c.lastMove)
.add("check", c.check)
.add("thinkTime", c.thinkTime)
.add("status", c.result.map(o => Outcome.showResult(o).replace("1/2", "½")))
.add("status", c.points.map(o => Outcome.showPoints(o).replace("1/2", "½")))

object bson:
import BSONHandlers.given
Expand Down Expand Up @@ -187,5 +187,5 @@ object ChapterPreview:
lastMove = lastPos.flatMap(_.uci),
lastMoveAt = lastMoveAt,
check = lastPos.flatMap(_.check),
result = tags.flatMap(_(_.Result)).map(Outcome.fromResult)
points = tags.map(_.points)
)
7 changes: 3 additions & 4 deletions modules/study/src/main/StudyPgnImport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ object StudyPgnImport:
)

val end = result.map: res =>
val outcome = Outcome(res.winner)
End(
status = res.status,
outcome = outcome,
resultText = chess.Outcome.showResult(outcome.some),
points = res.points,
resultText = chess.Outcome.showPoints(res.points.some),
statusText = lila.tree.StatusText(res.status, res.winner, game.board.variant)
)

Expand Down Expand Up @@ -64,7 +63,7 @@ object StudyPgnImport:

case class End(
status: Status,
outcome: Outcome,
points: Outcome.GamePoints,
resultText: String,
statusText: String
)
Expand Down
5 changes: 2 additions & 3 deletions modules/study/src/main/StudyPgnImportNew.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ object StudyPgnImportNew:
)

val end = result.map: res =>
val outcome = Outcome(res.winner)
StudyPgnImport.End(
status = res.status,
outcome = outcome,
resultText = chess.Outcome.showResult(outcome.some),
points = res.points,
resultText = chess.Outcome.showPoints(res.points.some),
statusText = lila.tree.StatusText(res.status, res.winner, game.board.variant)
)

Expand Down
6 changes: 2 additions & 4 deletions modules/tournament/src/main/Schedule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,8 @@ object Schedule:
case (_, _, Blitz) => TC(5 * 60, 0)
case (_, _, Rapid) => TC(10 * 60, 0)
case (_, _, Classical) => TC(20 * 60, 10)
private[tournament] def withConditions(s: Schedule) =
val newConditions = conditionFor(s)
if newConditions == s.conditions then s
else s.copy(conditions = conditionFor(s))

private[tournament] def withConditions(s: Schedule) = s.copy(conditions = conditionFor(s))

private[tournament] def conditionFor(s: Schedule) =
if s.conditions.nonEmpty then s.conditions
Expand Down
20 changes: 11 additions & 9 deletions modules/tree/src/main/ParseImport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import chess.format.pgn.{ ParsedPgn, Parser, PgnStr, Reader, Sans }
import chess.variant.*
import chess.{ Game as ChessGame, * }

case class TagResult(status: Status, winner: Option[Color]):
case class TagResult(status: Status, points: Outcome.GamePoints):
// duplicated from Game.finish
def finished = status >= Status.Mate
def finished = status >= Status.Mate
def winner: Option[Color] = Outcome.fromPoints(points).flatMap(_.winner)

case class ImportResult(
game: ChessGame,
Expand Down Expand Up @@ -54,20 +55,21 @@ val parseImport: PgnStr => Either[ErrorStr, ImportResult] = pgn =>
case Some(txt) if txt.contains("won on time") => Status.Outoftime
case _ => Status.UnknownFinish

val result = parsed.tags.outcome
.map:
case Outcome(Some(winner)) => TagResult(status, winner.some)
case _ if status == Status.Outoftime => TagResult(status, none)
case _ => TagResult(Status.Draw, none)
val result = parsed.tags.points
.map(points => TagResult(status, points))
.filter(_.status > Status.Started)
.orElse { game.situation.status.map(TagResult(_, game.situation.winner)) }
.orElse:
game.situation.status.flatMap: status =>
Outcome
.guessPointsFromStatusAndPosition(status, game.situation.winner)
.map(TagResult(status, _))

ImportResult(game, result, replay.copy(state = game), initialFen, parsed)
}
}

private def isChess960StartPosition(sit: Situation) =
import _root_.chess.*
import chess.*
val strict =
def rankMatches(f: Option[Piece] => Boolean)(rank: Rank) =
File.all.forall: file =>
Expand Down
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object Dependencies {
}

object chess {
val version = "16.2.10"
val version = "16.2.14"
val core = "org.lichess" %% "scalachess" % version
val testKit = "org.lichess" %% "scalachess-test-kit" % version % Test
val playJson = "org.lichess" %% "scalachess-play-json" % version
Expand Down
5 changes: 3 additions & 2 deletions ui/analyse/src/study/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export type ToolTab = 'tags' | 'comments' | 'glyphs' | 'serverEval' | 'share' |
export type Visibility = 'public' | 'unlisted' | 'private';
export type ChapterId = string;
export type TeamName = string;
export type OutcomeStr = '1-0' | '0-1' | '½-½';
export type StatusStr = OutcomeStr | '*';
export type PointsStr = '1' | '0' | '½';
export type GamePointsStr = '1-0' | '0-1' | '½-½' | '0-0' | '½-0' | '0-½';
export type StatusStr = GamePointsStr | '*';
export type ClockCentis = number;
export type BothClocks = [ClockCentis?, ClockCentis?];
export type FideId = number;
Expand Down
Loading