Skip to content

Commit

Permalink
Merge branch 'master' into issue-16951
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar authored Feb 12, 2025
2 parents 8a97fe3 + 3e5ac24 commit 808c76b
Show file tree
Hide file tree
Showing 21 changed files with 51 additions and 38 deletions.
14 changes: 10 additions & 4 deletions app/controllers/PlayApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package controllers
import play.api.i18n.Lang
import play.api.mvc.*

import lila.app.*
import lila.app.{ *, given }
import lila.core.id.GameAnyId
import lila.core.perf.UserWithPerfs

Expand Down Expand Up @@ -131,9 +131,15 @@ final class PlayApi(env: Env)(using akka.stream.Materializer) extends LilaContro
BadRequest:
jsonError:
"This endpoint can only be used with a Bot account. See https://lichess.org/api#operation/botAccountUpgrade"
else if !lila.game.Game.isBotCompatible(pov.game) then
BadRequest(jsonError("This game cannot be played with the Bot API."))
else f(pov)
else
isReallyBotCompatible(pov.game).flatMap:
if _ then f(pov)
else BadRequest(jsonError("This game cannot be played with the Bot API."))

private def isReallyBotCompatible(game: lila.core.game.Game): Fu[Boolean] =
lila.game.Game.isBotCompatible(game) match
case Some(known) => fuccess(known)
case None => game.tournamentId.so(env.tournament.api.isForBots)

private def WithPovAsBoard(id: GameId)(f: Pov => Fu[Result])(using ctx: Context)(using Me) =
WithPov(id): pov =>
Expand Down
2 changes: 1 addition & 1 deletion modules/api/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class Env(
cacheApi: lila.memo.CacheApi,
webConfig: lila.web.WebConfig,
realPlayerApi: lila.web.RealPlayerApi,
bookmarkExists: lila.core.bookmark.BookmarkExists,
bookmarkExists: lila.core.misc.BookmarkExists,
manifest: lila.web.AssetManifest,
tokenApi: lila.oauth.AccessTokenApi
)(using val mode: Mode, scheduler: Scheduler)(using
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 @@ -141,7 +141,7 @@ final class EventStream(
gameJsonView
.ownerPreview(pov)(using lightUserApi.sync)
.add("source" -> game.source.map(_.name)) ++ compatJson(
bot = me.isBot && lila.game.Game.isBotCompatible(game),
bot = me.isBot && lila.game.Game.isBotCompatible(game).|(true),
board = lila.game.Game.isBoardCompatible(game)
) ++ Json.obj(
"id" -> game.id // API BC
Expand Down
2 changes: 1 addition & 1 deletion modules/bookmark/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class Env(

lazy val api = wire[BookmarkApi]

def exists: lila.core.bookmark.BookmarkExists = api.exists
def exists: lila.core.misc.BookmarkExists = api.exists

lila.common.Bus.subscribeFun("roundUnplayed"):
case lila.core.round.DeleteUnplayed(gameId) => api.removeByGameId(gameId)
2 changes: 1 addition & 1 deletion modules/challenge/src/main/ChallengeApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ final class ChallengeApi(
then fuccess(Left("The challenge has been canceled."))
else if c.declined
then fuccess(Left("The challenge has been declined."))
else if me.exists(_.isBot) && !lila.game.Game.isBotCompatible(chess.Speed(c.clock.map(_.config)))
else if me.exists(_.isBot) && !c.clock.map(_.config).forall(lila.core.game.isBotCompatible)
then fuccess(Left("Game incompatible with a BOT account"))
else if c.open.exists(!_.canJoin)
then fuccess(Left("The challenge is not for you to accept."))
Expand Down
4 changes: 0 additions & 4 deletions modules/core/src/main/bookmark.scala

This file was deleted.

6 changes: 3 additions & 3 deletions modules/core/src/main/game/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ case class Game(
def playableCorrespondenceClock: Option[CorrespondenceClock] =
if playable then correspondenceClock else none

def speed = Speed(chess.clock.map(_.config))

def perfKey: PerfKey = PerfKey(variant, speed)

def ratingVariant: Variant =
Expand Down Expand Up @@ -213,7 +211,9 @@ case class Game(
def isCorrespondence = speed == Speed.Correspondence
def isSpeed(s: Speed) = speed == s

def hasClock = clock.isDefined
def hasClock = clock.isDefined
def clockConfig = clock.map(_.config)
def speed = Speed(clockConfig)

def hasCorrespondenceClock = daysPerTurn.isDefined

Expand Down
1 change: 1 addition & 0 deletions modules/core/src/main/game/misc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def allowRated(variant: Variant, clock: Option[Clock.Config]) =
c.limitSeconds > 0 || c.incrementSeconds > 1

def isBoardCompatible(clock: Clock.Config): Boolean = Speed(clock) >= Speed.Rapid
def isBotCompatible(clock: Clock.Config): Boolean = Speed(clock) >= Speed.Bullet

def interleave[A](a: Seq[A], b: Seq[A]): Vector[A] =
val iterA = a.iterator
Expand Down
2 changes: 2 additions & 0 deletions modules/core/src/main/misc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ trait PicfitUrl:
def thumbnail(id: lila.core.id.ImageId, width: Int, height: Int): String
def resize(id: lila.core.id.ImageId, size: Either[Int, Int]): String
def raw(id: lila.core.id.ImageId): String

type BookmarkExists = (game.Game, Option[userId.UserId]) => Fu[Boolean]
19 changes: 10 additions & 9 deletions modules/game/src/main/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,20 @@ object Game:
def abandonedDate = nowInstant.minusDays(abandonedDays.value)

def isBoardCompatible(game: Game): Boolean =
game.clock.forall: c =>
lila.core.game.isBoardCompatible(c.config) || {
game.clockConfig.forall: c =>
lila.core.game.isBoardCompatible(c) || {
(game.hasAi || game.sourceIs(_.Friend) || game.sourceIs(_.Api)) &&
chess.Speed(c.config) >= Speed.Blitz
chess.Speed(c) >= Speed.Blitz
}

def isBotCompatible(game: Game): Boolean = {
game.hasAi || game.sourceIs(_.Friend) || game.sourceIs(_.Api)
} && isBotCompatible(game.speed)
// if source is Arena, we will also need to check if the arena accepts bots!
def isBotCompatible(game: Game): Option[Boolean] =
if !game.clockConfig.forall(lila.core.game.isBotCompatible) then false.some
else if game.hasAi || game.sourceIs(_.Friend) || game.sourceIs(_.Api) then true.some
else if game.sourceIs(_.Arena) then none
else false.some

def isBotCompatible(speed: Speed): Boolean = speed >= Speed.Bullet

def isBoardOrBotCompatible(game: Game) = isBoardCompatible(game) || isBotCompatible(game)
def mightBeBoardOrBotCompatible(game: Game) = isBoardCompatible(game) || isBotCompatible(game).|(true)

object BSONFields:
export lila.core.game.BSONFields.*
Expand Down
1 change: 0 additions & 1 deletion modules/game/src/test/EventTest.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package lila.game

import cats.syntax.all.*
import chess.*
import chess.bitboard.Bitboard
import chess.variant.Crazyhouse
Expand Down
1 change: 0 additions & 1 deletion modules/plan/src/main/PlanApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package lila.plan

import play.api.i18n.Lang
import reactivemongo.api.*
import cats.syntax.all.*

import lila.common.Bus
import lila.core.config.Secret
Expand Down
2 changes: 1 addition & 1 deletion modules/round/src/main/Drawer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ final private[round] class Drawer(
lila.core.round.CorresDrawOfferEvent(game.id),
"offerEventCorres"
)
if lila.game.Game.isBoardOrBotCompatible(game) then
if lila.game.Game.mightBeBoardOrBotCompatible(game) then
Bus.publish(
lila.game.actorApi.BoardDrawOffer(game),
lila.game.actorApi.BoardDrawOffer.makeChan(game.id)
Expand Down
2 changes: 1 addition & 1 deletion modules/round/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ final class Env(
socketKit: lila.core.socket.ParallelSocketKit,
userLagPut: lila.core.socket.userLag.Put,
lightUserApi: lila.user.LightUserApi,
bookmarkExists: lila.core.bookmark.BookmarkExists,
bookmarkExists: lila.core.misc.BookmarkExists,
simulApiCircularDep: => lila.core.simul.SimulApi,
settingStore: lila.memo.SettingStore.Builder,
shutdown: akka.actor.CoordinatedShutdown
Expand Down
2 changes: 1 addition & 1 deletion modules/round/src/main/RoundAsyncActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ final private class RoundAsyncActor(
publishBoardBotGone(pov, millis.some)

private def publishBoardBotGone(pov: Pov, millis: Option[Long]) =
if lila.game.Game.isBoardOrBotCompatible(pov.game) then
if lila.game.Game.mightBeBoardOrBotCompatible(pov.game) then
lila.common.Bus.publish(
lila.game.actorApi.BoardGone(pov, millis.map(m => (m.atLeast(0) / 1000).toInt)),
lila.game.actorApi.BoardGone.makeChan(gameId)
Expand Down
2 changes: 1 addition & 1 deletion modules/round/src/main/RoundMobile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class RoundMobile(
moretimer: Moretimer,
isOfferingRematch: lila.core.round.IsOfferingRematch,
chatApi: lila.chat.ChatApi,
bookmarkExists: lila.core.bookmark.BookmarkExists
bookmarkExists: lila.core.misc.BookmarkExists
)(using Executor, lila.core.user.FlairGetMap):

import RoundMobile.*
Expand Down
4 changes: 2 additions & 2 deletions modules/round/src/main/Takebacker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ final private class Takebacker(
proxy.save(p2).inject(p2.events)

private def publishTakebackOffer(game: Game): Unit =
if lila.game.Game.isBoardOrBotCompatible(game) then
if lila.game.Game.mightBeBoardOrBotCompatible(game) then
Bus.publish(
lila.game.actorApi.BoardTakebackOffer(game),
lila.game.actorApi.BoardTakebackOffer.makeChan(game.id)
)

private def publishTakeback(prevPov: Pov)(using proxy: GameProxy): Unit =
if lila.game.Game.isBoardOrBotCompatible(prevPov.game) then
if lila.game.Game.mightBeBoardOrBotCompatible(prevPov.game) then
proxy.withPov(prevPov.color): p =>
fuccess:
Bus.publish(
Expand Down
4 changes: 4 additions & 0 deletions modules/tournament/src/main/TournamentApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@ final class TournamentApi(
_.sequentiallyVoid(playerRepo.withdraw(_, userId))
}

def isForBots(tourId: TourId): Fu[Boolean] =
for tour <- cached.tourCache.byId(tourId)
yield tour.exists(_.conditions.bots.so(_.allowed))

private[tournament] def kickFromTeam(teamId: TeamId, userId: UserId): Funit =
tournamentRepo.withdrawableIds(userId, teamId = teamId.some, reason = "kickFromTeam").flatMap {
_.sequentiallyVoid: tourId =>
Expand Down
11 changes: 6 additions & 5 deletions modules/tournament/src/main/TournamentCache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class TournamentCache(
object tourCache:
private val cache = cacheApi[TourId, Option[Tournament]](128, "tournament.tournament"):
_.expireAfterWrite(1.second)
.maximumSize(256)
.buildAsyncFuture(tournamentRepo.byId)
export cache.get as byId
def clear(id: TourId) = cache.invalidate(id)
Expand Down Expand Up @@ -45,13 +46,13 @@ final class TournamentCache(
.buildAsyncFuture(playerRepo.computeRanking)

// only applies to finished tournaments
private val finishedRanking = cacheApi[TourId, FullRanking](1024, "tournament.finishedRanking"):
private val finishedRanking = cacheApi[TourId, FullRanking](2_048, "tournament.finishedRanking"):
_.expireAfterAccess(1.hour)
.maximumSize(2048)
.maximumSize(2_048)
.buildAsyncFuture(playerRepo.computeRanking)

private[tournament] val teamInfo =
cacheApi[(TourId, TeamId), TeamBattle.TeamInfo](16, "tournament.teamInfo"):
cacheApi[(TourId, TeamId), TeamBattle.TeamInfo](32, "tournament.teamInfo"):
_.expireAfterWrite(5.seconds)
.maximumSize(64)
.buildAsyncFuture: (tourId, teamId) =>
Expand Down Expand Up @@ -113,9 +114,9 @@ final class TournamentCache(
.map:
arena.Sheet.buildFromScratch(key.userId, _, key.version, key.streakable, key.variant)

private val cache = cacheApi[SheetKey, Sheet](32768, "tournament.sheet"):
private val cache = cacheApi[SheetKey, Sheet](32_768, "tournament.sheet"):
_.expireAfterAccess(4.minutes)
.maximumSize(65536)
.maximumSize(65_536)
.buildAsyncFuture(compute)

private[tournament] val notableFinishedCache = cacheApi.unit[List[Tournament]]:
Expand Down
4 changes: 4 additions & 0 deletions modules/tournament/src/main/TournamentForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ final class TournamentForm:
"hasChat" -> optional(boolean)
)(TournamentSetup.apply)(unapply)
.verifying("Invalid clock", _.validClock)
.verifying("Invalid clock for bot games", _.validClockForBots)
.verifying("15s and 0+1 variant games cannot be rated", _.validRatedVariant)
.verifying("Increase tournament duration, or decrease game clock", _.sufficientDuration)
.verifying("Reduce tournament duration, or increase game clock", _.excessiveDuration)
Expand Down Expand Up @@ -161,6 +162,9 @@ private[tournament] case class TournamentSetup(

def validClock = (clockTime + clockIncrement.value) > 0

def validClockForBots = !conditions.bots.exists(_.allowed) ||
lila.core.game.isBotCompatible(clockConfig)

def realMode =
if realPosition.isDefined && !thematicPosition then Mode.Casual
else Mode(rated | true)
Expand Down
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Dependencies {
val scalatags = "com.lihaoyi" %% "scalatags" % "0.13.1"
val lettuce = "io.lettuce" % "lettuce-core" % "6.5.3.RELEASE"
val nettyTransport =
("io.netty" % s"netty-transport-native-$notifier" % "4.1.117.Final").classifier(s"$os-$arch")
("io.netty" % s"netty-transport-native-$notifier" % "4.1.118.Final").classifier(s"$os-$arch")
val lilaSearch = "org.lichess.search" %% "client" % "3.1.0"
val munit = "org.scalameta" %% "munit" % "1.1.0" % Test
val uaparser = "org.uaparser" %% "uap-scala" % "0.18.0"
Expand Down

0 comments on commit 808c76b

Please sign in to comment.