Skip to content
2 changes: 1 addition & 1 deletion app/controllers/Challenge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ final class Challenge(
env.challenge.msg.onApiPair(challenge)(managedBy, message) inject Ok(
Json.obj(
"game" -> {
env.game.jsonView(g, challenge.initialFen) ++ Json.obj(
env.game.jsonView.base(g, challenge.initialFen) ++ Json.obj(
"url" -> s"${env.net.baseUrl}${routes.Round.watcher(g.id, "white")}"
)
}
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ final class Setup(
jsonFormError,
config =>
processor.apiAi(config, me) map { pov =>
Created(env.game.jsonView(pov.game, config.fen)) as JSON
Created(env.game.jsonView.base(pov.game, config.fen)) as JSON
}
)
}(rateLimitedFu)
Expand Down
2 changes: 1 addition & 1 deletion modules/api/src/main/EventStream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ final class EventStream(
"type" -> tpe,
"game" -> {
gameJsonView
.ownerPreview(pov)(lightUserApi.sync)
.ownerPreview(pov)(using lightUserApi.sync)
.add("source" -> game.source.map(_.name)) ++ compatJson(
bot = me.isBot && Game.isBotCompatible(game),
board = Game.isBoardCompatible(game)
Expand Down
11 changes: 3 additions & 8 deletions modules/api/src/main/GameApiV2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import lila.round.GameProxyRepo
final class GameApiV2(
pgnDump: PgnDump,
gameRepo: lila.game.GameRepo,
gameJsonView: lila.game.JsonView,
tournamentRepo: lila.tournament.TournamentRepo,
pairingRepo: lila.tournament.PairingRepo,
playerRepo: lila.tournament.PlayerRepo,
Expand Down Expand Up @@ -289,14 +290,8 @@ final class GameApiV2(
"lastMoveAt" -> g.movedAt,
"status" -> g.status.name,
"players" -> JsObject(g.players zip lightUsers map { (p, user) =>
p.color.name -> Json
.obj()
.add("user", user)
.add("rating", p.rating)
.add("ratingDiff", p.ratingDiff)
.add("name", p.name)
.add("provisional" -> p.provisional)
.add("aiLevel" -> p.aiLevel)
p.color.name -> gameJsonView
.player(p, user)
.add(
"analysis" -> analysisOption.flatMap(
analysisJson.player(g.pov(p.color).sideAndStart)(_, accuracy = none)
Expand Down
3 changes: 2 additions & 1 deletion modules/api/src/main/LobbyApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import play.api.libs.json.{ JsArray, JsObject, Json }
import lila.game.Pov
import lila.lobby.{ LobbySocket, SeekApi }
import lila.common.Json.given
import lila.common.LightUser

final class LobbyApi(
lightUserApi: lila.user.LightUserApi,
Expand Down Expand Up @@ -39,4 +40,4 @@ final class LobbyApi(
}
}

def nowPlaying(pov: Pov) = gameJson.ownerPreview(pov)(lightUserApi.sync)
def nowPlaying(pov: Pov) = gameJson.ownerPreview(pov)(using lightUserApi.sync)
10 changes: 2 additions & 8 deletions modules/bot/src/main/BotJsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ final class BotJsonView(
.obj(
"type" -> "gameState",
"moves" -> uciMoves.mkString(" "),
"wtime" -> millisOf(game.whitePov),
"btime" -> millisOf(game.blackPov),
"wtime" -> game.whitePov.millisRemaining,
"btime" -> game.blackPov.millisRemaining,
"winc" -> (game.clock.??(_.config.increment.millis): Long),
"binc" -> (game.clock.??(_.config.increment.millis): Long),
"status" -> game.status.name
Expand Down Expand Up @@ -93,12 +93,6 @@ final class BotJsonView(
.add("rating" -> pov.player.rating)
.add("provisional" -> pov.player.provisional)

private def millisOf(pov: Pov): Int =
pov.game.clock
.map(_.remainingTime(pov.color).millis.toInt)
.orElse(pov.game.correspondenceClock.map(_.remainingTime(pov.color).toInt * 1000))
.getOrElse(Int.MaxValue)

implicit private val clockConfigWriter: OWrites[chess.Clock.Config] = OWrites { c =>
Json.obj(
"initial" -> c.limit.millis,
Expand Down
16 changes: 13 additions & 3 deletions modules/game/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class JsonView(rematches: Rematches):

import JsonView.{ *, given }

def apply(game: Game, initialFen: Option[Fen.Epd]) =
def base(game: Game, initialFen: Option[Fen.Epd]) =
Json
.obj(
"id" -> game.id,
Expand Down Expand Up @@ -40,7 +40,7 @@ final class JsonView(rematches: Rematches):
.add("drawOffers" -> (!game.drawOffers.isEmpty).option(game.drawOffers.normalizedPlies))
.add("rules" -> game.metadata.nonEmptyRules)

def ownerPreview(pov: Pov)(lightUserSync: LightUser.GetterSync) =
def ownerPreview(pov: Pov)(using LightUser.GetterSync) =
Json
.obj(
"fullId" -> pov.fullId,
Expand All @@ -61,7 +61,7 @@ final class JsonView(rematches: Rematches):
.obj(
"id" -> pov.opponent.userId,
"username" -> lila.game.Namer
.playerTextBlocking(pov.opponent, withRating = false)(using lightUserSync)
.playerTextBlocking(pov.opponent, withRating = false)
)
.add("rating" -> pov.opponent.rating)
.add("ai" -> pov.opponent.aiLevel),
Expand All @@ -71,6 +71,16 @@ final class JsonView(rematches: Rematches):
.add("tournamentId" -> pov.game.tournamentId)
.add("swissId" -> pov.game.swissId)

def player(p: Player, user: Option[LightUser]) =
Json
.obj()
.add("user", user)
.add("rating", p.rating)
.add("ratingDiff", p.ratingDiff)
.add("name", p.name)
.add("provisional" -> p.provisional)
.add("aiLevel" -> p.aiLevel)

object JsonView:

given OWrites[chess.Status] = OWrites { s =>
Expand Down
6 changes: 3 additions & 3 deletions modules/game/src/main/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Namer:
playerTextUser(player, _, withRating)
}

private def playerTextUser(player: Player, user: Option[LightUser], withRating: Boolean = false): String =
def playerTextUser(player: Player, user: Option[LightUser], withRating: Boolean = false): String =
player.aiLevel.fold(
user.fold(player.name | "Anon.") { u =>
player.rating.ifTrue(withRating).fold(u.titleName) { r =>
Expand All @@ -30,9 +30,9 @@ object Namer:
): String =
s"${playerTextBlocking(game.whitePlayer, withRatings)} - ${playerTextBlocking(game.blackPlayer, withRatings)}"

def gameVsText(game: Game, withRatings: Boolean = false)(implicit lightUser: LightUser.Getter): Fu[String] =
def gameVsText(game: Game, withRatings: Boolean = false)(using lightUser: LightUser.Getter): Fu[String] =
game.whitePlayer.userId.??(lightUser) zip
game.blackPlayer.userId.??(lightUser) dmap { case (wu, bu) =>
game.blackPlayer.userId.??(lightUser) dmap { (wu, bu) =>
s"${playerTextUser(game.whitePlayer, wu, withRatings)} - ${playerTextUser(game.blackPlayer, bu, withRatings)}"
}

Expand Down
6 changes: 6 additions & 0 deletions modules/game/src/main/Pov.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ case class Pov(game: Game, color: Color):
game.playableCorrespondenceClock.map(_.remainingTime(color).toInt)
}

def millisRemaining: Int =
game.clock
.map(_.remainingTime(color).millis.toInt)
.orElse(game.correspondenceClock.map(_.remainingTime(color).toInt * 1000))
.getOrElse(Int.MaxValue)

def hasMoved = game playerHasMoved color

def moves = game playerMoves color
Expand Down
40 changes: 26 additions & 14 deletions modules/round/src/main/ApiMoveStream.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package lila.round

import akka.stream.OverflowStrategy
import akka.stream.scaladsl.*
import chess.Color
import chess.format.{ BoardFen, Fen }
Expand All @@ -12,17 +11,30 @@ import lila.common.Json.given
import lila.game.actorApi.{ FinishGame, MoveGameEvent }
import lila.game.{ Game, GameRepo }

final class ApiMoveStream(gameRepo: GameRepo, gameJsonView: lila.game.JsonView)(using Executor):
final class ApiMoveStream(
gameRepo: GameRepo,
gameJsonView: lila.game.JsonView,
lightUserApi: lila.user.LightUserApi
)(using Executor):

def apply(game: Game, delayMoves: Boolean): Source[JsObject, ?] =
Source futureSource {
val hasMoveDelay = delayMoves && game.hasClock
val delayMovesBy = hasMoveDelay ?? 3
val delayKeepsFirstMoves = hasMoveDelay ?? 5
gameRepo.initialFen(game) map { initialFen =>
for
initialFen <- gameRepo.initialFen(game)
lightUsers <- lightUserApi.asyncMany(game.userIds)
yield
val buffer = scala.collection.mutable.Queue.empty[JsObject]
var moves = 0
Source(List(gameJsonView(game, initialFen))) concat
def makeGameJson(g: Game) =
gameJsonView.base(g, initialFen) ++ Json.obj(
"players" -> JsObject(g.players zip lightUsers map { (p, user) =>
p.color.name -> gameJsonView.player(p, user)
})
)
Source(List(makeGameJson(game))) concat
Source
.queue[JsObject]((game.ply.value + 3) atLeast 16, akka.stream.OverflowStrategy.dropHead)
.statefulMapConcat { () => js =>
Expand All @@ -33,21 +45,21 @@ final class ApiMoveStream(gameRepo: GameRepo, gameJsonView: lila.game.JsonView)(
(buffer.size > delayMovesBy) ?? List(buffer.dequeue())
}
.mapMaterializedValue { queue =>
val clocks = for {
val clocks = for
clk <- game.clock
clkHistory <- game.clockHistory
} yield (
yield (
Vector(clk.config.initTime) ++ clkHistory.white,
Vector(clk.config.initTime) ++ clkHistory.black
)
val clockOffset = game.startColor.fold(0, 1)
Replay.situations(game.sans, initialFen, game.variant) foreach {
_.zipWithIndex foreach { case (s, index) =>
val clk = for {
_.zipWithIndex foreach { (s, index) =>
val clk = for
(clkWhite, clkBlack) <- clocks
white <- clkWhite.lift((index + 1 - clockOffset) >> 1)
black <- clkBlack.lift((index + clockOffset) >> 1)
} yield (white, black)
yield (white, black)
queue offer toJson(
Fen writeBoard s.board,
s.color,
Expand All @@ -57,24 +69,24 @@ final class ApiMoveStream(gameRepo: GameRepo, gameJsonView: lila.game.JsonView)(
}
}
if (game.finished)
queue offer gameJsonView(game, initialFen)
queue offer makeGameJson(game)
queue.complete()
else
val chans = List(MoveGameEvent makeChan game.id, "finishGame")
val sub = Bus.subscribeFun(chans*) {
case MoveGameEvent(g, fen, move) =>
queue.offer(toJson(g, fen, move.some)).unit
case FinishGame(g, _, _) if g.id == game.id =>
queue offer gameJsonView(g, initialFen)
queue offer makeGameJson(g)
(1 to buffer.size) foreach { _ => queue.offer(Json.obj()) } // push buffer content out
queue.complete()
}
queue.watchCompletion() addEffectAnyway {
Bus.unsubscribe(sub, chans)
}
}
}
}
end apply

private def toJson(game: Game, fen: BoardFen, lastMoveUci: Option[String]): JsObject =
toJson(
Expand All @@ -90,12 +102,12 @@ final class ApiMoveStream(gameRepo: GameRepo, gameJsonView: lila.game.JsonView)(
fen: BoardFen,
turnColor: Color,
lastMoveUci: Option[String],
clock: Option[(Centis, Centis)]
clock: Option[PairOf[Centis]]
): JsObject =
clock.foldLeft(
Json
.obj("fen" -> fen.andColor(turnColor))
.add("lm" -> lastMoveUci)
) { case (js, clk) =>
) { (js, clk) =>
js ++ Json.obj("wc" -> clk._1.roundSeconds, "bc" -> clk._2.roundSeconds)
}
7 changes: 4 additions & 3 deletions modules/round/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ final class JsonView(
import pov.*
Json
.obj(
"game" -> gameJsonView(game, initialFen),
"game" -> gameJsonView.base(game, initialFen),
"player" -> {
commonPlayerJson(game, player, playerUser, withFlags) ++ Json.obj(
"id" -> playerId,
Expand Down Expand Up @@ -167,7 +167,8 @@ final class JsonView(
import pov.*
Json
.obj(
"game" -> gameJsonView(game, initialFen)
"game" -> gameJsonView
.base(game, initialFen)
.add("moveCentis" -> (withFlags.movetimes ?? game.moveTimes.map(_.map(_.centis))))
.add("division" -> withFlags.division.option(divider(game, initialFen)))
.add("opening" -> game.opening)
Expand Down Expand Up @@ -220,7 +221,7 @@ final class JsonView(
Json
.obj(
"game" -> {
gameJsonView(game, initialFen) ++ Json.obj(
gameJsonView.base(game, initialFen) ++ Json.obj(
"pgn" -> pov.game.sans.mkString(" ")
)
},
Expand Down
2 changes: 1 addition & 1 deletion modules/study/src/main/ChapterMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ final private class ChapterMaker(
tags <- pgnDump.tags(game, initialFen, none, withOpening = true, withRatings)
name <-
if (data.isDefaultName)
Namer.gameVsText(game, withRatings)(lightUser.async) dmap { StudyChapterName(_) }
StudyChapterName from Namer.gameVsText(game, withRatings)(using lightUser.async)
else fuccess(data.name)
_ = notifyChat(study, game, userId)
} yield Chapter.make(
Expand Down
2 changes: 1 addition & 1 deletion modules/study/src/main/StudyMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final private class StudyMaker(
for {
root <- chapterMaker.getBestRoot(pov.game, data.form.pgnStr, initialFen)
tags <- pgnDump.tags(pov.game, initialFen, none, withOpening = true, withRatings)
name <- Namer.gameVsText(pov.game, withRatings)(lightUserApi.async) dmap { StudyChapterName(_) }
name <- StudyChapterName from Namer.gameVsText(pov.game, withRatings)(using lightUserApi.async)
study = Study.make(user, Study.From.Game(pov.gameId), data.id, StudyName("Game study").some)
chapter = Chapter.make(
studyId = study.id,
Expand Down
5 changes: 1 addition & 4 deletions modules/user/src/main/LightUserApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import lila.db.dsl.{ given, * }
import lila.memo.{ CacheApi, Syncache }
import User.BSONFields as F

final class LightUserApi(
repo: UserRepo,
cacheApi: CacheApi
)(using Executor):
final class LightUserApi(repo: UserRepo, cacheApi: CacheApi)(using Executor):

val async = LightUser.Getter(id => if (User isGhost id) fuccess(LightUser.ghost.some) else cache.async(id))
val asyncFallback = LightUser.GetterFallback(id =>
Expand Down