From 8fc08dfc7f22aec3b58b9fdfba4fecb5f5642d5d Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Fri, 12 Apr 2024 17:28:07 +0200 Subject: [PATCH] remove more deps to user --- app/controllers/Game.scala | 2 +- build.sbt | 8 ++--- modules/api/src/main/RoundApi.scala | 2 +- modules/bot/src/main/BotJsonView.scala | 2 +- modules/bot/src/main/Env.scala | 2 +- modules/challenge/src/main/Challenge.scala | 2 +- .../challenge/src/main/ChallengeJoiner.scala | 2 +- .../challenge/src/main/ChallengeMaker.scala | 3 +- modules/chat/src/main/JsonView.scala | 8 +++-- modules/core/src/main/user.scala | 15 +++++++++- modules/lobby/src/main/AbortListener.scala | 8 ++--- modules/lobby/src/main/Biter.scala | 16 ++++------ modules/lobby/src/main/Env.scala | 4 +-- modules/lobby/src/main/LobbySocket.scala | 5 ++-- modules/lobby/src/main/SeekApi.scala | 6 ++-- modules/puzzle/src/main/Env.scala | 1 - modules/round/src/main/JsonView.scala | 2 +- modules/round/src/main/Rematcher.scala | 4 +-- modules/setup/src/main/AiConfig.scala | 2 +- modules/setup/src/main/ApiAiConfig.scala | 2 +- modules/timeline/src/main/TimelineApi.scala | 1 - modules/user/src/main/JsonView.scala | 5 ---- modules/user/src/main/UserApi.scala | 29 +++++++++++-------- modules/user/src/main/package.scala | 3 -- 24 files changed, 70 insertions(+), 64 deletions(-) diff --git a/app/controllers/Game.scala b/app/controllers/Game.scala index f91c9b5621017..c5daa1205d517 100644 --- a/app/controllers/Game.scala +++ b/app/controllers/Game.scala @@ -165,7 +165,7 @@ final class Game(env: Env, apiC: => Api) extends LilaController(env): private[controllers] def preloadUsers(game: lila.game.Game): Funit = env.user.lightUserApi.preloadMany(game.userIds) - private[controllers] def preloadUsers(users: lila.user.GameUsers): Unit = + private[controllers] def preloadUsers(users: lila.core.user.GameUsers): Unit = env.user.lightUserApi.preloadUsers(users.all.collect: case Some(lila.core.user.WithPerf(u, _)) => u ) diff --git a/build.sbt b/build.sbt index b46da2c9030a1..60bf48fe34866 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val puzzle = module("puzzle", ) lazy val storm = module("storm", - Seq(puzzle), + Seq(puzzle, user), reactivemongo.bundle ) @@ -255,17 +255,17 @@ lazy val pool = module("pool", ) lazy val activity = module("activity", - Seq(puzzle), + Seq(puzzle, user), reactivemongo.bundle ) lazy val lobby = module("lobby", - Seq(game, user), + Seq(game), Seq(lettuce) ++ reactivemongo.bundle ) lazy val setup = module("setup", - Seq(lobby), + Seq(lobby, user), reactivemongo.bundle ) diff --git a/modules/api/src/main/RoundApi.scala b/modules/api/src/main/RoundApi.scala index aa84a2248f0b5..aa69a07400151 100644 --- a/modules/api/src/main/RoundApi.scala +++ b/modules/api/src/main/RoundApi.scala @@ -17,7 +17,7 @@ import lila.simul.Simul import lila.swiss.GameView as SwissView import lila.tournament.GameView as TourView import lila.tree.Node.partitionTreeJsonWriter -import lila.user.{ GameUsers, User, Me } +import lila.core.user.GameUsers import lila.core.i18n.Translate import lila.core.data.Preload diff --git a/modules/bot/src/main/BotJsonView.scala b/modules/bot/src/main/BotJsonView.scala index 2afff99b5c5c7..48eeccd100944 100644 --- a/modules/bot/src/main/BotJsonView.scala +++ b/modules/bot/src/main/BotJsonView.scala @@ -8,7 +8,7 @@ import lila.game.{ Game, GameRepo, Pov } import lila.core.i18n.Translate final class BotJsonView( - lightUserApi: lila.user.LightUserApi, + lightUserApi: lila.core.user.LightUserApi, gameRepo: GameRepo, rematches: lila.game.Rematches )(using Executor): diff --git a/modules/bot/src/main/Env.scala b/modules/bot/src/main/Env.scala index eb8005558cc78..00b4e403120ce 100644 --- a/modules/bot/src/main/Env.scala +++ b/modules/bot/src/main/Env.scala @@ -8,7 +8,7 @@ import lila.core.socket.IsOnline final class Env( chatApi: lila.chat.ChatApi, gameRepo: lila.game.GameRepo, - lightUserApi: lila.user.LightUserApi, + lightUserApi: lila.core.user.LightUserApi, rematches: lila.game.Rematches, isOfferingRematch: lila.core.round.IsOfferingRematch, spam: lila.core.security.SpamApi, diff --git a/modules/challenge/src/main/Challenge.scala b/modules/challenge/src/main/Challenge.scala index bd49e3a889e55..911a80a0a0f31 100644 --- a/modules/challenge/src/main/Challenge.scala +++ b/modules/challenge/src/main/Challenge.scala @@ -13,8 +13,8 @@ import lila.core.i18n.I18nKey import lila.core.{ challenge as hub } import lila.core.game.GameRule import lila.rating.PerfType -import lila.user.{ GameUser, Me, User } import lila.core.user.WithPerf +import lila.core.user.GameUser case class Challenge( @Key("_id") id: Challenge.Id, diff --git a/modules/challenge/src/main/ChallengeJoiner.scala b/modules/challenge/src/main/ChallengeJoiner.scala index 15ebca3fb7f09..98efda1428fa9 100644 --- a/modules/challenge/src/main/ChallengeJoiner.scala +++ b/modules/challenge/src/main/ChallengeJoiner.scala @@ -7,7 +7,7 @@ import chess.{ ByColor, Mode, Situation } import scala.util.chaining.* import lila.game.{ Game, Player, Pov } -import lila.user.GameUser +import lila.core.user.GameUser final private class ChallengeJoiner( gameRepo: lila.game.GameRepo, diff --git a/modules/challenge/src/main/ChallengeMaker.scala b/modules/challenge/src/main/ChallengeMaker.scala index 87f7cc252af19..fadca5d19de9a 100644 --- a/modules/challenge/src/main/ChallengeMaker.scala +++ b/modules/challenge/src/main/ChallengeMaker.scala @@ -1,10 +1,11 @@ package lila.challenge import lila.game.{ Game, GameRepo, Player, Pov, Rematches } -import lila.user.{ GameUser, User, UserApi, UserPerfsRepo } +import lila.user.{ UserApi, UserPerfsRepo } import Challenge.TimeControl import lila.core.user.WithPerf +import lila.core.user.GameUser final class ChallengeMaker( userApi: UserApi, diff --git a/modules/chat/src/main/JsonView.scala b/modules/chat/src/main/JsonView.scala index a111b0f9c3aa0..ce434ec3b8ca2 100644 --- a/modules/chat/src/main/JsonView.scala +++ b/modules/chat/src/main/JsonView.scala @@ -33,8 +33,12 @@ object JsonView: lineWriter.writes(line) def userModInfo(using LightUser.GetterSync)(u: UserModInfo) = - lila.user.JsonView.modWrites.writes(u.user) ++ Json.obj: - "history" -> u.history + modView(u.user) ++ Json.obj("history" -> u.history) + + private def modView(u: User) = + Json.toJsObject(u.light) ++ Json + .obj("games" -> u.count.game) + .add("tos" -> u.marks.dirty) def mobile(chat: AnyChat, writeable: Boolean = true)(using FlairGetMap, Executor): Fu[JsObject] = asyncLines(chat).map: lines => diff --git a/modules/core/src/main/user.scala b/modules/core/src/main/user.scala index 11072ca943b5a..83a7e15e85128 100644 --- a/modules/core/src/main/user.scala +++ b/modules/core/src/main/user.scala @@ -4,7 +4,7 @@ import reactivemongo.api.bson.Macros.Annotations.Key import reactivemongo.api.bson.{ BSONDocument, BSONDocumentHandler, BSONDocumentReader } import reactivemongo.api.bson.collection.BSONCollection import play.api.i18n.Lang -import _root_.chess.PlayerTitle +import _root_.chess.{ ByColor, PlayerTitle } import lila.core.perf.Perf import lila.core.rating.data.{ IntRating, IntRatingDiff } @@ -12,6 +12,7 @@ import lila.core.perf.{ PerfKey, UserPerfs, UserWithPerfs } import lila.core.userId.* import lila.core.email.* import lila.core.id.Flair +import lila.core.rating.Glicko object user: @@ -210,6 +211,15 @@ object user: def dubiousPuzzle(id: UserId, puzzle: Perf): Fu[Boolean] def setPerf(userId: UserId, pk: PerfKey, perf: Perf): Funit def userIdsWithRoles(roles: List[String]): Fu[Set[UserId]] + def incColor(userId: UserId, value: Int): Unit + def firstGetsWhite(u1O: Option[UserId], u2O: Option[UserId]): Fu[Boolean] + def gamePlayersAny(userIds: ByColor[Option[UserId]], perf: PerfKey): Fu[GameUsers] + def gamePlayersLoggedIn( + ids: ByColor[UserId], + perf: PerfKey, + useCache: Boolean = true + ): Fu[Option[ByColor[WithPerf]]] + def glicko(userId: UserId, perf: PerfKey): Fu[Glicko] trait LightUserApiMinimal: val async: LightUser.Getter @@ -311,3 +321,6 @@ object user: trait FlagApi: val all: List[Flag] val nonCountries: List[Flag.Code] + + type GameUser = Option[WithPerf] + type GameUsers = ByColor[GameUser] diff --git a/modules/lobby/src/main/AbortListener.scala b/modules/lobby/src/main/AbortListener.scala index 90d83ea88e19b..130cdc26560e7 100644 --- a/modules/lobby/src/main/AbortListener.scala +++ b/modules/lobby/src/main/AbortListener.scala @@ -4,7 +4,7 @@ import lila.game.Pov import lila.core.game.Source final private class AbortListener( - userRepo: lila.user.UserRepo, + userApi: lila.core.user.UserApi, gameRepo: lila.game.GameRepo, seekApi: SeekApi, lobbyActor: LobbySyncActor @@ -22,8 +22,8 @@ final private class AbortListener( then pov.game.userIds match case List(u1, u2) => - userRepo.incColor(u1, -1) - userRepo.incColor(u2, 1) + userApi.incColor(u1, -1) + userApi.incColor(u2, 1) case _ => private def recreateSeek(pov: Pov): Funit = @@ -35,7 +35,7 @@ final private class AbortListener( } private def worthRecreating(seek: Seek): Fu[Boolean] = - userRepo.byId(seek.user.id).map { + userApi.byId(seek.user.id).map { _.exists: u => u.enabled.yes && !u.lame } diff --git a/modules/lobby/src/main/Biter.scala b/modules/lobby/src/main/Biter.scala index 8ed32f3d944ef..5b8ff96b43247 100644 --- a/modules/lobby/src/main/Biter.scala +++ b/modules/lobby/src/main/Biter.scala @@ -4,12 +4,11 @@ import chess.{ ByColor, Game as ChessGame, Situation } import lila.game.{ Game, Player } import lila.core.socket.Sri -import lila.user.{ GameUsers, User } +import lila.core.user.GameUsers import lila.core.user.WithPerf final private class Biter( - userRepo: lila.user.UserRepo, - userApi: lila.user.UserApi, + userApi: lila.core.user.UserApi, gameRepo: lila.game.GameRepo )(using Executor, lila.game.IdGenerator): @@ -25,7 +24,7 @@ final private class Biter( private def join(hook: Hook, sri: Sri, lobbyUserOption: Option[LobbyUser]): Fu[JoinHook] = for - users <- userApi.gamePlayers(ByColor(lobbyUserOption.map(_.id), hook.userId), hook.perfType) + users <- userApi.gamePlayersAny(ByColor(lobbyUserOption.map(_.id), hook.userId), hook.perfType) (joiner, owner) = users.toPair ownerColor <- assignCreatorColor(owner, joiner, hook.realColor) game <- makeGame( @@ -40,11 +39,8 @@ final private class Biter( private def join(seek: Seek, lobbyUser: LobbyUser): Fu[JoinSeek] = for - users <- userApi.gamePlayers - .loggedIn( - ByColor(lobbyUser.id, seek.user.id), - seek.perfType - ) + users <- userApi + .gamePlayersLoggedIn(ByColor(lobbyUser.id, seek.user.id), seek.perfType) .orFail(s"No such seek users: $seek") (joiner, owner) = users.toPair ownerColor <- assignCreatorColor(owner.some, joiner.some, seek.realColor) @@ -68,7 +64,7 @@ final private class Biter( ): Fu[chess.Color] = color match case Color.Random => - userRepo.firstGetsWhite(creatorUser.map(_.id), joinerUser.map(_.id)).map { chess.Color.fromWhite(_) } + userApi.firstGetsWhite(creatorUser.map(_.id), joinerUser.map(_.id)).map { chess.Color.fromWhite(_) } case Color.White => fuccess(chess.White) case Color.Black => fuccess(chess.Black) diff --git a/modules/lobby/src/main/Env.scala b/modules/lobby/src/main/Env.scala index cc7e4c6ba2773..9dad93feb99ab 100644 --- a/modules/lobby/src/main/Env.scala +++ b/modules/lobby/src/main/Env.scala @@ -14,9 +14,7 @@ final class Env( relationApi: lila.core.relation.RelationApi, hasCurrentPlayban: lila.core.playban.HasCurrentPlayban, gameCache: lila.game.Cached, - userRepo: lila.user.UserRepo, - perfsRepo: lila.user.UserPerfsRepo, - userApi: lila.user.UserApi, + userApi: lila.core.user.UserApi, gameRepo: lila.game.GameRepo, poolApi: lila.core.pool.PoolApi, cacheApi: lila.memo.CacheApi, diff --git a/modules/lobby/src/main/LobbySocket.scala b/modules/lobby/src/main/LobbySocket.scala index 6b37cee169f29..d84b382d365de 100644 --- a/modules/lobby/src/main/LobbySocket.scala +++ b/modules/lobby/src/main/LobbySocket.scala @@ -15,8 +15,7 @@ case class LobbyCounters(members: Int, rounds: Int) final class LobbySocket( biter: Biter, - perfsRepo: lila.user.UserPerfsRepo, - userApi: lila.user.UserApi, + userApi: lila.core.user.UserApi, socketKit: SocketKit, lobby: LobbySyncActor, relationApi: lila.core.relation.RelationApi, @@ -193,7 +192,7 @@ final class LobbySocket( blocking = d.get[UserId]("blocking") yield lobby ! CancelHook(member.sri) // in case there's one... - perfsRepo.glicko(user.id, perfType).foreach { glicko => + userApi.glicko(user.id, perfType).foreach { glicko => poolApi.join( PoolConfigId(id), lila.core.pool.Joiner( diff --git a/modules/lobby/src/main/SeekApi.scala b/modules/lobby/src/main/SeekApi.scala index ca91772e86683..ec70df19c1cce 100644 --- a/modules/lobby/src/main/SeekApi.scala +++ b/modules/lobby/src/main/SeekApi.scala @@ -6,10 +6,10 @@ import lila.memo.CacheApi.* import lila.core.perf.UserWithPerfs final class SeekApi( + userApi: lila.core.user.UserApi, config: SeekApi.Config, biter: Biter, relationApi: lila.core.relation.RelationApi, - perfsRepo: lila.user.UserPerfsRepo, cacheApi: lila.memo.CacheApi )(using Executor): import config.* @@ -41,7 +41,7 @@ final class SeekApi( def forMe(using me: User | UserWithPerfs): Fu[List[Seek]] = for user <- me match case u: UserWithPerfs => fuccess(u) - case u: User => perfsRepo.withPerfs(u) + case u: User => userApi.withPerfs(u) blocking <- relationApi.fetchBlocking(user.id) seeks <- forUser(LobbyUser.make(user, lila.core.pool.Blocking(blocking))) yield seeks @@ -106,7 +106,7 @@ final class SeekApi( .void .andDo(cacheClear()) - def removeByUser(user: lila.user.User) = + def removeByUser(user: User) = coll.delete.one($doc("user.id" -> user.id)).void.andDo(cacheClear()) private object SeekApi: diff --git a/modules/puzzle/src/main/Env.scala b/modules/puzzle/src/main/Env.scala index 0ac3aa1bf1c9c..1d3cae15f7e80 100644 --- a/modules/puzzle/src/main/Env.scala +++ b/modules/puzzle/src/main/Env.scala @@ -25,7 +25,6 @@ final class Env( cacheApi: lila.memo.CacheApi, mongoCacheApi: lila.memo.MongoCache.Api, gameRepo: lila.game.GameRepo, - perfsRepo: lila.user.UserPerfsRepo, mongo: lila.db.Env )(using Executor, akka.actor.ActorSystem, akka.stream.Materializer, lila.core.i18n.Translator)(using scheduler: Scheduler, diff --git a/modules/round/src/main/JsonView.scala b/modules/round/src/main/JsonView.scala index 24e9831610710..ac3e15b962863 100644 --- a/modules/round/src/main/JsonView.scala +++ b/modules/round/src/main/JsonView.scala @@ -13,7 +13,7 @@ import lila.game.JsonView.given import lila.game.{ Game, Player as GamePlayer, Pov } import lila.pref.Pref -import lila.user.{ GameUser, GameUsers } +import lila.core.user.{ GameUser, GameUsers } import lila.core.user.WithPerf import lila.core.net.ApiVersion import lila.core.perf.KeyedPerf diff --git a/modules/round/src/main/Rematcher.scala b/modules/round/src/main/Rematcher.scala index 567a7dc7d78e6..2d1c8e529a22f 100644 --- a/modules/round/src/main/Rematcher.scala +++ b/modules/round/src/main/Rematcher.scala @@ -8,7 +8,7 @@ import lila.common.Bus import lila.game.{ AnonCookie, Event, Game, GameRepo, Pov, Rematches } import lila.core.i18n.{ I18nKey as trans, defaultLang, Translator } import scalalib.cache.ExpireSetMemo -import lila.user.{ GameUsers, UserApi } +import lila.core.user.{ GameUsers, UserApi } import ChessColor.White @@ -99,7 +99,7 @@ final private class Rematcher( initialFen, !chess960.get(pov.gameId) ) - users <- userApi.gamePlayers(pov.game.userIdPair, pov.game.perfType) + users <- userApi.gamePlayersAny(pov.game.userIdPair, pov.game.perfType) sloppy = Game.make( chess = newGame, players = ByColor(returnPlayer(pov.game, _, users)), diff --git a/modules/setup/src/main/AiConfig.scala b/modules/setup/src/main/AiConfig.scala index defe32feafcc6..da427fc9f24e3 100644 --- a/modules/setup/src/main/AiConfig.scala +++ b/modules/setup/src/main/AiConfig.scala @@ -8,7 +8,7 @@ import scalalib.model.Days import lila.game.{ Game, IdGenerator, Player, Pov } import lila.lobby.Color import lila.rating.PerfType -import lila.user.GameUser +import lila.core.user.GameUser import lila.core.game.Source case class AiConfig( diff --git a/modules/setup/src/main/ApiAiConfig.scala b/modules/setup/src/main/ApiAiConfig.scala index b3cb221261203..0f3ca6639a056 100644 --- a/modules/setup/src/main/ApiAiConfig.scala +++ b/modules/setup/src/main/ApiAiConfig.scala @@ -8,7 +8,7 @@ import scalalib.model.Days import lila.game.{ Game, IdGenerator, Player, Pov } import lila.lobby.Color import lila.rating.PerfType -import lila.user.GameUser +import lila.core.user.GameUser import lila.core.game.Source final case class ApiAiConfig( diff --git a/modules/timeline/src/main/TimelineApi.scala b/modules/timeline/src/main/TimelineApi.scala index cc288c03c2e3e..f50a09623371c 100644 --- a/modules/timeline/src/main/TimelineApi.scala +++ b/modules/timeline/src/main/TimelineApi.scala @@ -4,7 +4,6 @@ import akka.actor.* import lila.core.timeline.{ Atom, Propagate, Propagation, ReloadTimelines } import lila.core.perm.Permission -import lila.user.UserRepo import lila.core.team.Access import lila.core.timeline.* diff --git a/modules/user/src/main/JsonView.scala b/modules/user/src/main/JsonView.scala index cc48b7e34395e..b3cbf9bb5feb2 100644 --- a/modules/user/src/main/JsonView.scala +++ b/modules/user/src/main/JsonView.scala @@ -83,11 +83,6 @@ object JsonView: .add("title" -> l.user.title) .add("patron" -> l.user.isPatron) - val modWrites = OWrites[User]: u => - Json.toJsObject(u.light) ++ Json - .obj("games" -> u.count.game) - .add("tos" -> u.marks.dirty) - given perfWrites: OWrites[Perf] = OWrites: o => Json .obj( diff --git a/modules/user/src/main/UserApi.scala b/modules/user/src/main/UserApi.scala index 527cf88e7d629..c3272d0c42f26 100644 --- a/modules/user/src/main/UserApi.scala +++ b/modules/user/src/main/UserApi.scala @@ -15,6 +15,7 @@ import lila.core.lilaism.LilaInvalid import lila.core.user.{ WithEmails, WithPerf } import lila.rating.PerfType +import lila.core.user.GameUsers final class UserApi(userRepo: UserRepo, perfsRepo: UserPerfsRepo, cacheApi: CacheApi)(using Executor, @@ -40,28 +41,32 @@ final class UserApi(userRepo: UserRepo, perfsRepo: UserPerfsRepo, cacheApi: Cach isTroll, isManaged, filterDisabled, - countEngines + countEngines, + firstGetsWhite, + incColor, + userIdsWithRoles } - export perfsRepo.{ perfsOf, setPerf, dubiousPuzzle } + export perfsRepo.{ perfsOf, setPerf, dubiousPuzzle, glicko } + export gamePlayers.{ apply as gamePlayersAny, loggedIn as gamePlayersLoggedIn } // hit by game rounds object gamePlayers: - private type PlayersKey = (PairOf[Option[UserId]], PerfType) + private type PlayersKey = (PairOf[Option[UserId]], PerfKey) - def apply(userIds: ByColor[Option[UserId]], perfType: PerfType): Fu[GameUsers] = - cache.get(userIds.toPair -> perfType) + def apply(userIds: ByColor[Option[UserId]], perf: PerfKey): Fu[GameUsers] = + cache.get(userIds.toPair -> perf) - def noCache(userIds: ByColor[Option[UserId]], perfType: PerfType): Fu[GameUsers] = - fetch(userIds.toPair, perfType) + def noCache(userIds: ByColor[Option[UserId]], perf: PerfKey): Fu[GameUsers] = + fetch(userIds.toPair, perf) def loggedIn( ids: ByColor[UserId], - perfType: PerfType, + perf: PerfKey, useCache: Boolean = true ): Fu[Option[ByColor[WithPerf]]] = val users = - if useCache then apply(ids.map(some), perfType) - else fetch(ids.map(some).toPair, perfType) + if useCache then apply(ids.map(some), perf) + else fetch(ids.map(some).toPair, perf) users.map: case ByColor(Some(x), Some(y)) => ByColor(x, y).some case _ => none @@ -69,9 +74,9 @@ final class UserApi(userRepo: UserRepo, perfsRepo: UserPerfsRepo, cacheApi: Cach private[UserApi] val cache = cacheApi[PlayersKey, GameUsers](1024, "user.perf.pair"): _.expireAfterWrite(3 seconds).buildAsyncFuture(fetch) - private def fetch(userIds: PairOf[Option[UserId]], perfType: PerfType): Fu[GameUsers] = + private def fetch(userIds: PairOf[Option[UserId]], perf: PerfKey): Fu[GameUsers] = val (x, y) = userIds - listWithPerf(List(x, y).flatten, perfType, _.pri).map: users => + listWithPerf(List(x, y).flatten, perf, _.pri).map: users => ByColor(x, y).map(_.flatMap(id => users.find(_.id == id))) def updatePerfs(ups: ByColor[(UserPerfs, UserPerfs)], gamePerfType: PerfType) = diff --git a/modules/user/src/main/package.scala b/modules/user/src/main/package.scala index bb2ae6a96e21a..3d9cca5728ffb 100644 --- a/modules/user/src/main/package.scala +++ b/modules/user/src/main/package.scala @@ -5,6 +5,3 @@ export lila.common.extensions.* export lila.rating.UserWithPerfs private val logger = lila.log("user") - -type GameUser = Option[lila.core.user.WithPerf] -type GameUsers = chess.ByColor[GameUser]