2013-03-22 11:53:13 -06:00
|
|
|
package lila.game
|
|
|
|
|
2021-11-11 03:48:17 -07:00
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
import chess.format.{ FEN, Forsyth }
|
2015-01-11 07:23:25 -07:00
|
|
|
import chess.{ Color, Status }
|
2013-12-04 13:22:54 -07:00
|
|
|
import org.joda.time.DateTime
|
2019-12-13 07:30:20 -07:00
|
|
|
import reactivemongo.akkastream.{ cursorProducer, AkkaStreamCursor }
|
2018-08-25 10:02:16 -06:00
|
|
|
import reactivemongo.api.commands.WriteResult
|
2021-10-04 03:21:16 -06:00
|
|
|
import reactivemongo.api.{ Cursor, ReadPreference, WriteConcern }
|
2013-08-02 11:43:26 -06:00
|
|
|
|
2021-04-11 03:14:17 -06:00
|
|
|
import lila.common.ThreadLocalRandom
|
2013-12-26 16:31:09 -07:00
|
|
|
import lila.db.BSON.BSONJodaDateTimeHandler
|
2016-04-01 06:23:56 -06:00
|
|
|
import lila.db.dsl._
|
2019-12-08 01:02:12 -07:00
|
|
|
import lila.db.isDuplicateKey
|
2017-10-21 14:01:50 -06:00
|
|
|
import lila.user.User
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2019-12-13 18:17:43 -07:00
|
|
|
final class GameRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionContext) {
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2015-06-13 09:26:58 -06:00
|
|
|
import BSONHandlers._
|
2018-04-07 10:56:24 -06:00
|
|
|
import Game.{ ID, BSONFields => F }
|
2019-08-19 11:30:58 -06:00
|
|
|
import Player.holdAlertBSONHandler
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2021-11-11 03:48:17 -07:00
|
|
|
val fixedColorLobbyCache = new lila.memo.ExpireSetMemo(2 hours)
|
|
|
|
|
2021-06-11 00:00:18 -06:00
|
|
|
def game(gameId: ID): Fu[Option[Game]] = coll.byId[Game](gameId)
|
|
|
|
def gameFromSecondary(gameId: ID): Fu[Option[Game]] = coll.secondaryPreferred.byId[Game](gameId)
|
2013-05-07 17:44:26 -06:00
|
|
|
|
2016-07-23 05:31:11 -06:00
|
|
|
def gamesFromSecondary(gameIds: Seq[ID]): Fu[List[Game]] =
|
2018-04-07 10:56:24 -06:00
|
|
|
coll.byOrderedIds[Game, ID](gameIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
|
2016-07-23 05:31:11 -06:00
|
|
|
|
2017-11-23 15:17:01 -07:00
|
|
|
def gameOptionsFromSecondary(gameIds: Seq[ID]): Fu[List[Option[Game]]] =
|
2020-01-18 20:20:16 -07:00
|
|
|
coll.optionsByOrderedIds[Game, ID](gameIds, none, ReadPreference.secondaryPreferred)(_.id)
|
2015-01-17 02:43:39 -07:00
|
|
|
|
2018-04-05 09:18:11 -06:00
|
|
|
object light {
|
2018-04-05 09:52:55 -06:00
|
|
|
|
|
|
|
def game(gameId: ID): Fu[Option[LightGame]] = coll.byId[LightGame](gameId, LightGame.projection)
|
|
|
|
|
2018-04-05 10:03:52 -06:00
|
|
|
def pov(gameId: ID, color: Color): Fu[Option[LightPov]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
game(gameId) dmap2 { (game: LightGame) =>
|
2019-12-13 07:30:20 -07:00
|
|
|
LightPov(game, game player color)
|
|
|
|
}
|
2018-04-05 10:03:52 -06:00
|
|
|
|
|
|
|
def pov(ref: PovRef): Fu[Option[LightPov]] = pov(ref.gameId, ref.color)
|
|
|
|
|
2018-04-05 09:18:11 -06:00
|
|
|
def gamesFromPrimary(gameIds: Seq[ID]): Fu[List[LightGame]] =
|
2018-04-07 10:56:24 -06:00
|
|
|
coll.byOrderedIds[LightGame, ID](gameIds, projection = LightGame.projection.some)(_.id)
|
|
|
|
|
|
|
|
def gamesFromSecondary(gameIds: Seq[ID]): Fu[List[LightGame]] =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll.byOrderedIds[LightGame, ID](
|
|
|
|
gameIds,
|
|
|
|
projection = LightGame.projection.some,
|
|
|
|
readPreference = ReadPreference.secondaryPreferred
|
|
|
|
)(_.id)
|
2018-04-05 09:18:11 -06:00
|
|
|
}
|
|
|
|
|
2013-05-14 21:52:27 -06:00
|
|
|
def finished(gameId: ID): Fu[Option[Game]] =
|
2019-12-07 21:49:02 -07:00
|
|
|
coll.one[Game]($id(gameId) ++ Query.finished)
|
2013-05-12 05:27:05 -06:00
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def player(gameId: ID, color: Color): Fu[Option[Player]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
game(gameId) dmap2 { _ player color }
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2013-05-17 18:08:08 -06:00
|
|
|
def player(gameId: ID, playerId: ID): Fu[Option[Player]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
game(gameId) dmap { gameOption =>
|
2013-05-17 18:08:08 -06:00
|
|
|
gameOption flatMap { _ player playerId }
|
|
|
|
}
|
|
|
|
|
|
|
|
def player(playerRef: PlayerRef): Fu[Option[Player]] =
|
|
|
|
player(playerRef.gameId, playerRef.playerId)
|
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def pov(gameId: ID, color: Color): Fu[Option[Pov]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
game(gameId) dmap2 { (game: Game) =>
|
2019-12-13 07:30:20 -07:00
|
|
|
Pov(game, game player color)
|
|
|
|
}
|
2013-03-22 11:53:13 -06:00
|
|
|
|
|
|
|
def pov(gameId: ID, color: String): Fu[Option[Pov]] =
|
2020-09-21 03:31:16 -06:00
|
|
|
Color.fromName(color) ?? (pov(gameId, _))
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2013-05-17 18:08:08 -06:00
|
|
|
def pov(playerRef: PlayerRef): Fu[Option[Pov]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
game(playerRef.gameId) dmap { _ flatMap { _ playerIdPov playerRef.playerId } }
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2013-05-17 18:08:08 -06:00
|
|
|
def pov(fullId: ID): Fu[Option[Pov]] = pov(PlayerRef(fullId))
|
|
|
|
|
2013-03-22 12:52:13 -06:00
|
|
|
def pov(ref: PovRef): Fu[Option[Pov]] = pov(ref.gameId, ref.color)
|
|
|
|
|
2019-11-30 11:06:50 -07:00
|
|
|
def remove(id: ID) = coll.delete.one($id(id)).void
|
2013-03-25 10:00:28 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def userPovsByGameIds(
|
|
|
|
gameIds: List[String],
|
|
|
|
user: User,
|
|
|
|
readPreference: ReadPreference = ReadPreference.secondaryPreferred
|
|
|
|
): Fu[List[Pov]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
coll.byOrderedIds[Game, ID](gameIds, readPreference = readPreference)(_.id) dmap {
|
2019-12-13 07:30:20 -07:00
|
|
|
_.flatMap(g => Pov(g, user))
|
|
|
|
}
|
2015-11-29 05:59:11 -07:00
|
|
|
|
2021-02-25 01:39:29 -07:00
|
|
|
def recentPovsByUserFromSecondary(user: User, nb: Int, select: Bdoc = $empty): Fu[List[Pov]] =
|
2021-04-11 03:14:17 -06:00
|
|
|
recentGamesByUserFromSecondaryCursor(user, select)
|
|
|
|
.list(nb)
|
|
|
|
.map { _.flatMap(Pov(_, user)) }
|
|
|
|
|
|
|
|
def recentGamesByUserFromSecondaryCursor(user: User, select: Bdoc = $empty) =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2021-02-25 01:39:29 -07:00
|
|
|
.find(Query.user(user) ++ select)
|
2017-03-26 04:49:34 -06:00
|
|
|
.sort(Query.sortCreated)
|
|
|
|
.cursor[Game](ReadPreference.secondaryPreferred)
|
2014-05-22 13:01:54 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def gamesForAssessment(userId: String, nb: Int): Fu[List[Game]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(
|
|
|
|
Query.finished
|
|
|
|
++ Query.rated
|
|
|
|
++ Query.user(userId)
|
|
|
|
++ Query.analysed(true)
|
|
|
|
++ Query.turnsGt(20)
|
|
|
|
++ Query.clockHistory(true)
|
|
|
|
)
|
|
|
|
.sort($sort asc F.createdAt)
|
2020-07-19 07:48:22 -06:00
|
|
|
.cursor[Game](ReadPreference.secondaryPreferred)
|
|
|
|
.list(nb)
|
2019-12-13 07:30:20 -07:00
|
|
|
|
|
|
|
def extraGamesForIrwin(userId: String, nb: Int): Fu[List[Game]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(
|
|
|
|
Query.finished
|
|
|
|
++ Query.rated
|
|
|
|
++ Query.user(userId)
|
|
|
|
++ Query.turnsGt(22)
|
|
|
|
++ Query.variantStandard
|
|
|
|
++ Query.clock(true)
|
|
|
|
)
|
|
|
|
.sort($sort asc F.createdAt)
|
2020-07-19 07:48:22 -06:00
|
|
|
.cursor[Game](ReadPreference.secondaryPreferred)
|
|
|
|
.list(nb)
|
2015-02-23 06:08:44 -07:00
|
|
|
|
2021-02-20 04:37:15 -07:00
|
|
|
def unanalysedGames(gameIds: Seq[ID]): Fu[List[Game]] =
|
|
|
|
coll
|
|
|
|
.find($inIds(gameIds) ++ Query.analysed(false))
|
|
|
|
.cursor[Game](ReadPreference.secondaryPreferred)
|
|
|
|
.list(100)
|
|
|
|
|
2016-04-01 22:57:54 -06:00
|
|
|
def cursor(
|
2019-12-13 07:30:20 -07:00
|
|
|
selector: Bdoc,
|
|
|
|
readPreference: ReadPreference = ReadPreference.secondaryPreferred
|
2019-12-02 09:41:05 -07:00
|
|
|
): AkkaStreamCursor[Game] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll.find(selector).cursor[Game](readPreference)
|
2016-04-01 10:54:24 -06:00
|
|
|
|
2020-01-16 08:10:27 -07:00
|
|
|
def docCursor(
|
|
|
|
selector: Bdoc,
|
|
|
|
readPreference: ReadPreference = ReadPreference.secondaryPreferred
|
|
|
|
): AkkaStreamCursor[Bdoc] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll.find(selector).cursor[Bdoc](readPreference)
|
2020-01-16 08:10:27 -07:00
|
|
|
|
2016-04-01 22:57:54 -06:00
|
|
|
def sortedCursor(
|
2019-12-13 07:30:20 -07:00
|
|
|
selector: Bdoc,
|
|
|
|
sort: Bdoc,
|
|
|
|
batchSize: Int = 0,
|
|
|
|
readPreference: ReadPreference = ReadPreference.secondaryPreferred
|
2019-12-02 09:41:05 -07:00
|
|
|
): AkkaStreamCursor[Game] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll.find(selector).sort(sort).batchSize(batchSize).cursor[Game](readPreference)
|
2016-04-01 22:57:54 -06:00
|
|
|
|
2021-10-04 03:21:16 -06:00
|
|
|
def byIdsCursor(ids: Iterable[Game.ID]): Cursor[Game] = coll.find($inIds(ids)).cursor[Game]()
|
|
|
|
|
2015-08-18 04:47:18 -06:00
|
|
|
def goBerserk(pov: Pov): Funit =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll.update
|
|
|
|
.one(
|
|
|
|
$id(pov.gameId),
|
|
|
|
$set(
|
|
|
|
s"${pov.color.fold(F.whitePlayer, F.blackPlayer)}.${Player.BSONFields.berserk}" -> true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.void
|
2015-08-18 04:47:18 -06:00
|
|
|
|
2019-08-19 12:37:06 -06:00
|
|
|
def update(progress: Progress): Funit =
|
|
|
|
saveDiff(progress.origin, GameDiff(progress.origin, progress.game))
|
2018-12-16 19:55:14 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
private def saveDiff(origin: Game, diff: GameDiff.Diff): Funit =
|
|
|
|
diff match {
|
|
|
|
case (Nil, Nil) => funit
|
|
|
|
case (sets, unsets) =>
|
|
|
|
coll.update
|
|
|
|
.one(
|
|
|
|
$id(origin.id),
|
|
|
|
nonEmptyMod("$set", $doc(sets)) ++ nonEmptyMod("$unset", $doc(unsets))
|
|
|
|
)
|
|
|
|
.void
|
|
|
|
}
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2016-04-01 10:43:50 -06:00
|
|
|
private def nonEmptyMod(mod: String, doc: Bdoc) =
|
2016-04-01 06:23:56 -06:00
|
|
|
if (doc.isEmpty) $empty else $doc(mod -> doc)
|
2014-04-20 14:26:13 -06:00
|
|
|
|
2017-07-07 06:59:54 -06:00
|
|
|
def setRatingDiffs(id: ID, diffs: RatingDiffs) =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll.update.one(
|
|
|
|
$id(id),
|
|
|
|
$set(
|
|
|
|
s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> diffs.white,
|
|
|
|
s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> diffs.black
|
|
|
|
)
|
|
|
|
)
|
2014-07-27 11:31:33 -06:00
|
|
|
|
2019-08-20 07:51:40 -06:00
|
|
|
// Use Env.round.proxy.urgentGames to get in-heap states!
|
|
|
|
def urgentPovsUnsorted(user: User): Fu[List[Pov]] =
|
2021-04-20 04:22:21 -06:00
|
|
|
coll.list[Game](Query nowPlaying user.id, Game.maxPlayingRealtime) dmap {
|
|
|
|
_ flatMap { Pov(_, user) }
|
2014-12-16 12:35:18 -07:00
|
|
|
}
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2021-04-20 04:22:21 -06:00
|
|
|
def countWhereUserTurn(userId: User.ID): Fu[Int] =
|
|
|
|
coll
|
|
|
|
.countSel(
|
|
|
|
// important, hits the index!
|
|
|
|
Query.nowPlaying(userId) ++ $doc(
|
|
|
|
"$or" ->
|
|
|
|
List(0, 1).map { rem =>
|
|
|
|
$doc(
|
|
|
|
s"${Game.BSONFields.playingUids}.$rem" -> userId,
|
|
|
|
Game.BSONFields.turns -> $doc("$mod" -> $arr(2, rem))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.dmap(_.toInt)
|
|
|
|
|
2019-12-08 01:02:12 -07:00
|
|
|
def playingRealtimeNoAi(user: User): Fu[List[Game.ID]] =
|
2020-04-30 10:38:58 -06:00
|
|
|
coll.distinctEasy[Game.ID, List](
|
|
|
|
F.id,
|
|
|
|
Query.nowPlaying(user.id) ++ Query.noAi ++ Query.clock(true),
|
|
|
|
ReadPreference.secondaryPreferred
|
|
|
|
)
|
2018-11-11 05:01:33 -07:00
|
|
|
|
2019-08-19 04:42:21 -06:00
|
|
|
def lastPlayedPlayingId(userId: User.ID): Fu[Option[Game.ID]] =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll
|
|
|
|
.find(Query recentlyPlaying userId, $id(true).some)
|
2017-04-29 08:13:48 -06:00
|
|
|
.sort(Query.sortMovedAtNoIndex)
|
2019-12-11 22:06:48 -07:00
|
|
|
.one[Bdoc](readPreference = ReadPreference.primary)
|
2020-04-15 12:11:35 -06:00
|
|
|
.dmap { _.flatMap(_.getAsOpt[Game.ID](F.id)) }
|
2014-12-23 17:34:13 -07:00
|
|
|
|
2018-09-08 02:53:43 -06:00
|
|
|
def allPlaying(userId: User.ID): Fu[List[Pov]] =
|
2020-07-19 07:48:22 -06:00
|
|
|
coll
|
|
|
|
.list[Game](Query nowPlaying userId)
|
2020-04-15 12:11:35 -06:00
|
|
|
.dmap { _ flatMap { Pov.ofUserId(_, userId) } }
|
2018-09-08 02:53:43 -06:00
|
|
|
|
2014-12-26 14:18:32 -07:00
|
|
|
def lastPlayed(user: User): Fu[Option[Pov]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(Query user user.id)
|
2016-04-01 10:43:50 -06:00
|
|
|
.sort($sort desc F.createdAt)
|
2020-07-19 07:48:22 -06:00
|
|
|
.cursor[Game]()
|
|
|
|
.list(2)
|
2019-12-13 07:30:20 -07:00
|
|
|
.dmap {
|
2017-04-29 08:13:48 -06:00
|
|
|
_.sortBy(_.movedAt).lastOption flatMap { Pov(_, user) }
|
2016-04-01 10:43:50 -06:00
|
|
|
}
|
2014-12-26 14:18:32 -07:00
|
|
|
|
2020-04-15 12:11:35 -06:00
|
|
|
def quickLastPlayedId(userId: User.ID): Fu[Option[Game.ID]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
|
|
|
.find(Query user userId, $id(true).some)
|
2020-04-15 12:11:35 -06:00
|
|
|
.sort($sort desc F.createdAt)
|
|
|
|
.one[Bdoc]
|
|
|
|
.dmap { _.flatMap(_.getAsOpt[Game.ID](F.id)) }
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def lastFinishedRatedNotFromPosition(user: User): Fu[Option[Game]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(
|
|
|
|
Query.user(user.id) ++
|
|
|
|
Query.rated ++
|
|
|
|
Query.finished ++
|
|
|
|
Query.turnsGt(2) ++
|
|
|
|
Query.notFromPosition
|
|
|
|
)
|
|
|
|
.sort(Query.sortAntiChronological)
|
|
|
|
.one[Game]
|
2015-11-26 12:12:34 -07:00
|
|
|
|
2018-03-31 11:43:44 -06:00
|
|
|
def setTv(id: ID) = coll.updateFieldUnchecked($id(id), F.tvAt, DateTime.now)
|
|
|
|
|
2020-04-30 10:38:58 -06:00
|
|
|
def setAnalysed(id: ID): Unit = coll.updateFieldUnchecked($id(id), F.analysed, true)
|
|
|
|
def setUnanalysed(id: ID): Unit = coll.updateFieldUnchecked($id(id), F.analysed, false)
|
2014-06-06 03:43:00 -06:00
|
|
|
|
2014-06-06 03:50:59 -06:00
|
|
|
def isAnalysed(id: ID): Fu[Boolean] =
|
2016-04-01 10:43:50 -06:00
|
|
|
coll.exists($id(id) ++ Query.analysed(true))
|
2014-06-06 03:50:59 -06:00
|
|
|
|
2018-04-07 10:56:24 -06:00
|
|
|
def exists(id: ID) = coll.exists($id(id))
|
2018-04-05 10:03:52 -06:00
|
|
|
|
2018-04-07 10:56:24 -06:00
|
|
|
def tournamentId(id: ID): Fu[Option[String]] = coll.primitiveOne[String]($id(id), F.tournamentId)
|
2016-02-09 02:57:23 -07:00
|
|
|
|
2013-05-11 04:48:55 -06:00
|
|
|
def incBookmarks(id: ID, value: Int) =
|
2019-11-30 11:06:50 -07:00
|
|
|
coll.update.one($id(id), $inc(F.bookmarks -> value)).void
|
2014-06-12 13:13:19 -06:00
|
|
|
|
2020-07-19 07:48:22 -06:00
|
|
|
def setHoldAlert(pov: Pov, alert: Player.HoldAlert): Funit =
|
|
|
|
coll
|
|
|
|
.updateField(
|
|
|
|
$id(pov.gameId),
|
|
|
|
holdAlertField(pov.color),
|
|
|
|
alert
|
|
|
|
)
|
|
|
|
.void
|
2017-02-15 17:53:15 -07:00
|
|
|
|
2019-08-19 11:30:58 -06:00
|
|
|
def setBorderAlert(pov: Pov) = setHoldAlert(pov, Player.HoldAlert(0, 0, 20))
|
2014-03-12 13:35:22 -06:00
|
|
|
|
2021-02-25 14:55:21 -07:00
|
|
|
object holdAlert {
|
|
|
|
private val holdAlertSelector = $or(
|
|
|
|
holdAlertField(chess.White) $exists true,
|
|
|
|
holdAlertField(chess.Black) $exists true
|
|
|
|
)
|
|
|
|
private val holdAlertProjection = $doc(
|
|
|
|
holdAlertField(chess.White) -> true,
|
|
|
|
holdAlertField(chess.Black) -> true
|
|
|
|
)
|
|
|
|
private def holdAlertOf(doc: Bdoc, color: Color): Option[Player.HoldAlert] =
|
|
|
|
doc.child(color.fold("p0", "p1")).flatMap(_.getAsOpt[Player.HoldAlert](Player.BSONFields.holdAlert))
|
|
|
|
|
|
|
|
def game(game: Game): Fu[Player.HoldAlert.Map] =
|
|
|
|
coll.one[Bdoc](
|
|
|
|
$doc(F.id -> game.id, holdAlertSelector),
|
|
|
|
holdAlertProjection
|
|
|
|
) map {
|
|
|
|
_.fold(Player.HoldAlert.emptyMap) { doc =>
|
|
|
|
Color.Map(white = holdAlertOf(doc, chess.White), black = holdAlertOf(doc, chess.Black))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def povs(povs: Seq[Pov]): Fu[Map[Game.ID, Player.HoldAlert]] =
|
|
|
|
coll
|
|
|
|
.find(
|
|
|
|
$doc($inIds(povs.map(_.gameId)), holdAlertSelector),
|
|
|
|
holdAlertProjection.some
|
2019-12-13 07:30:20 -07:00
|
|
|
)
|
2021-02-25 14:55:21 -07:00
|
|
|
.cursor[Bdoc](ReadPreference.secondaryPreferred)
|
|
|
|
.list() map { docs =>
|
|
|
|
val idColors = povs.view.map { p =>
|
|
|
|
p.gameId -> p.color
|
|
|
|
}.toMap
|
|
|
|
val holds = for {
|
|
|
|
doc <- docs
|
|
|
|
id <- doc string "_id"
|
|
|
|
color <- idColors get id
|
|
|
|
holds <- holdAlertOf(doc, color)
|
|
|
|
} yield id -> holds
|
|
|
|
holds.toMap
|
2019-08-25 10:03:58 -06:00
|
|
|
}
|
2021-02-25 14:55:21 -07:00
|
|
|
}
|
2019-08-25 10:03:58 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def hasHoldAlert(pov: Pov): Fu[Boolean] =
|
|
|
|
coll.exists(
|
|
|
|
$doc(
|
|
|
|
$id(pov.gameId),
|
|
|
|
holdAlertField(pov.color) $exists true
|
|
|
|
)
|
|
|
|
)
|
2019-08-25 10:03:58 -06:00
|
|
|
|
|
|
|
private def holdAlertField(color: Color) = s"p${color.fold(0, 1)}.${Player.BSONFields.holdAlert}"
|
|
|
|
|
2018-01-22 13:58:33 -07:00
|
|
|
private val finishUnsets = $doc(
|
2019-12-13 07:30:20 -07:00
|
|
|
F.positionHashes -> true,
|
|
|
|
F.playingUids -> true,
|
|
|
|
F.unmovedRooks -> true,
|
|
|
|
("p0." + Player.BSONFields.isOfferingDraw) -> true,
|
|
|
|
("p1." + Player.BSONFields.isOfferingDraw) -> true,
|
2018-01-22 13:58:33 -07:00
|
|
|
("p0." + Player.BSONFields.proposeTakebackAt) -> true,
|
|
|
|
("p1." + Player.BSONFields.proposeTakebackAt) -> true
|
|
|
|
)
|
|
|
|
|
2015-04-27 04:20:07 -06:00
|
|
|
def finish(
|
2019-12-13 07:30:20 -07:00
|
|
|
id: ID,
|
|
|
|
winnerColor: Option[Color],
|
|
|
|
winnerId: Option[String],
|
|
|
|
status: Status
|
2020-05-05 22:11:15 -06:00
|
|
|
) =
|
|
|
|
coll.update.one(
|
|
|
|
$id(id),
|
|
|
|
nonEmptyMod(
|
|
|
|
"$set",
|
|
|
|
$doc(
|
|
|
|
F.winnerId -> winnerId,
|
|
|
|
F.winnerColor -> winnerColor.map(_.white),
|
|
|
|
F.status -> status
|
|
|
|
)
|
|
|
|
) ++ $doc(
|
|
|
|
"$unset" -> finishUnsets.++ {
|
|
|
|
// keep the checkAt field when game is aborted,
|
|
|
|
// so it gets deleted in 24h
|
|
|
|
(status >= Status.Mate) ?? $doc(F.checkAt -> true)
|
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
)
|
2015-04-27 04:20:07 -06:00
|
|
|
)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(
|
|
|
|
Query.mate ++ Query.variantStandard
|
|
|
|
)
|
|
|
|
.sort(Query.sortCreated)
|
2020-08-18 13:31:32 -06:00
|
|
|
.skip(ThreadLocalRandom nextInt distribution)
|
2019-12-13 07:30:20 -07:00
|
|
|
.one[Game]
|
2013-03-23 17:10:46 -06:00
|
|
|
|
2018-06-07 06:56:34 -06:00
|
|
|
def insertDenormalized(g: Game, initialFen: Option[chess.format.FEN] = None): Funit = {
|
2019-12-13 07:30:20 -07:00
|
|
|
val g2 =
|
|
|
|
if (g.rated && (g.userIds.distinct.size != 2 || !Game.allowRated(g.variant, g.clock.map(_.config))))
|
|
|
|
g.copy(mode = chess.Mode.Casual)
|
|
|
|
else g
|
2014-12-22 03:38:21 -07:00
|
|
|
val userIds = g2.userIds.distinct
|
2020-10-18 12:21:34 -06:00
|
|
|
val fen: Option[FEN] = initialFen orElse {
|
2021-05-24 08:49:39 -06:00
|
|
|
(g2.variant.fromPosition || g2.variant.chess960)
|
2018-01-23 08:41:42 -07:00
|
|
|
.option(Forsyth >> g2.chess)
|
2020-10-18 12:21:34 -06:00
|
|
|
.filterNot(_.initial)
|
2016-03-26 02:06:38 -06:00
|
|
|
}
|
2017-03-26 06:10:16 -06:00
|
|
|
val checkInHours =
|
|
|
|
if (g2.isPgnImport) none
|
|
|
|
else if (g2.hasClock) 1.some
|
|
|
|
else if (g2.hasAi) (Game.aiAbandonedHours + 1).some
|
|
|
|
else (24 * 10).some
|
2016-04-01 10:43:50 -06:00
|
|
|
val bson = (gameBSONHandler write g2) ++ $doc(
|
2019-12-13 07:30:20 -07:00
|
|
|
F.initialFen -> fen,
|
|
|
|
F.checkAt -> checkInHours.map(DateTime.now.plusHours),
|
2019-07-13 12:12:42 -06:00
|
|
|
F.playingUids -> (g2.started && userIds.nonEmpty).option(userIds)
|
2013-12-03 13:31:31 -07:00
|
|
|
)
|
2019-11-30 11:06:50 -07:00
|
|
|
coll.insert.one(bson) addFailureEffect {
|
2020-09-29 01:51:32 -06:00
|
|
|
case wr: WriteResult if isDuplicateKey(wr) => lila.mon.game.idCollision.increment().unit
|
2018-08-25 10:02:16 -06:00
|
|
|
} void
|
2019-12-11 11:18:12 -07:00
|
|
|
}
|
2013-05-11 04:48:55 -06:00
|
|
|
|
2016-01-23 11:21:26 -07:00
|
|
|
def removeRecentChallengesOf(userId: String) =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll.delete.one(
|
|
|
|
Query.created ++ Query.friend ++ Query.user(userId) ++
|
|
|
|
Query.createdSince(DateTime.now minusHours 1)
|
|
|
|
)
|
2015-07-18 07:52:18 -06:00
|
|
|
|
2014-11-30 07:53:39 -07:00
|
|
|
def setCheckAt(g: Game, at: DateTime) =
|
2020-01-16 08:10:27 -07:00
|
|
|
coll.update.one($id(g.id), $set(F.checkAt -> at))
|
2014-11-30 07:53:39 -07:00
|
|
|
|
2020-01-16 08:10:27 -07:00
|
|
|
def unsetCheckAt(id: Game.ID): Funit =
|
|
|
|
coll.update.one($id(id), $unset(F.checkAt)).void
|
2014-11-30 07:53:39 -07:00
|
|
|
|
2019-07-13 12:12:42 -06:00
|
|
|
def unsetPlayingUids(g: Game): Unit =
|
2020-09-29 01:51:32 -06:00
|
|
|
coll.update(ordered = false, WriteConcern.Unacknowledged).one($id(g.id), $unset(F.playingUids)).unit
|
2015-11-16 20:11:23 -07:00
|
|
|
|
2014-11-17 18:54:16 -07:00
|
|
|
// used to make a compound sparse index
|
|
|
|
def setImportCreatedAt(g: Game) =
|
2019-11-30 11:06:50 -07:00
|
|
|
coll.update.one($id(g.id), $set("pgni.ca" -> g.createdAt)).void
|
2014-11-17 18:54:16 -07:00
|
|
|
|
2018-05-06 16:40:17 -06:00
|
|
|
def initialFen(gameId: ID): Fu[Option[FEN]] =
|
|
|
|
coll.primitiveOne[FEN]($id(gameId), F.initialFen)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2018-05-06 16:40:17 -06:00
|
|
|
def initialFen(game: Game): Fu[Option[FEN]] =
|
2019-12-13 18:17:43 -07:00
|
|
|
if (game.imported || !game.variant.standardInitialPosition) initialFen(game.id) dmap {
|
2020-10-18 12:21:34 -06:00
|
|
|
case None if game.variant == chess.variant.Chess960 => Forsyth.initial.some
|
2019-12-13 07:30:20 -07:00
|
|
|
case fen => fen
|
2016-02-20 08:14:11 -07:00
|
|
|
}
|
2014-08-30 03:03:55 -06:00
|
|
|
else fuccess(none)
|
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def gameWithInitialFen(gameId: ID): Fu[Option[(Game, Option[FEN])]] =
|
|
|
|
game(gameId) flatMap {
|
|
|
|
_ ?? { game =>
|
|
|
|
initialFen(game) dmap { fen =>
|
|
|
|
Option(game -> fen)
|
|
|
|
}
|
2016-04-26 01:03:49 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-30 17:21:48 -06:00
|
|
|
def withInitialFen(game: Game): Fu[Game.WithInitialFen] =
|
2019-12-13 18:17:43 -07:00
|
|
|
initialFen(game) dmap { Game.WithInitialFen(game, _) }
|
2016-10-30 17:21:48 -06:00
|
|
|
|
2019-11-30 19:29:40 -07:00
|
|
|
def withInitialFens(games: List[Game]): Fu[List[(Game, Option[FEN])]] =
|
2019-11-30 11:06:50 -07:00
|
|
|
games.map { game =>
|
2019-12-13 18:17:43 -07:00
|
|
|
initialFen(game) dmap { game -> _ }
|
2019-11-30 19:29:40 -07:00
|
|
|
}.sequenceFu
|
2016-10-17 04:01:00 -06:00
|
|
|
|
2016-04-01 10:43:50 -06:00
|
|
|
def count(query: Query.type => Bdoc): Fu[Int] = coll countSel query(Query)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2020-05-07 10:38:02 -06:00
|
|
|
private[game] def favoriteOpponents(
|
|
|
|
userId: String,
|
|
|
|
opponentLimit: Int,
|
|
|
|
gameLimit: Int
|
|
|
|
): Fu[List[(User.ID, Int)]] = {
|
2019-12-13 07:30:20 -07:00
|
|
|
coll
|
|
|
|
.aggregateList(
|
2020-05-07 10:38:02 -06:00
|
|
|
maxDocs = opponentLimit,
|
2019-12-13 07:30:20 -07:00
|
|
|
ReadPreference.secondaryPreferred
|
|
|
|
) { framework =>
|
2019-11-30 11:06:50 -07:00
|
|
|
import framework._
|
|
|
|
Match($doc(F.playerUids -> userId)) -> List(
|
|
|
|
Match($doc(F.playerUids -> $doc("$size" -> 2))),
|
|
|
|
Sort(Descending(F.createdAt)),
|
2020-05-07 10:38:02 -06:00
|
|
|
Limit(gameLimit), // only look in the last n games
|
2019-12-13 07:30:20 -07:00
|
|
|
Project(
|
|
|
|
$doc(
|
|
|
|
F.playerUids -> true,
|
|
|
|
F.id -> false
|
|
|
|
)
|
|
|
|
),
|
2019-11-30 11:06:50 -07:00
|
|
|
UnwindField(F.playerUids),
|
2020-01-18 21:04:06 -07:00
|
|
|
Match($doc(F.playerUids $ne userId)),
|
2019-11-30 11:06:50 -07:00
|
|
|
GroupField(F.playerUids)("gs" -> SumAll),
|
|
|
|
Sort(Descending("gs")),
|
2020-05-07 10:38:02 -06:00
|
|
|
Limit(opponentLimit)
|
2019-11-30 11:06:50 -07:00
|
|
|
)
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
.map(_.flatMap { obj =>
|
2020-01-18 21:04:06 -07:00
|
|
|
obj.string(F.id) flatMap { id =>
|
2019-11-30 11:06:50 -07:00
|
|
|
obj.int("gs") map { id -> _ }
|
2018-03-31 14:04:57 -06:00
|
|
|
}
|
|
|
|
})
|
2013-05-19 18:01:20 -06:00
|
|
|
}
|
2013-07-21 15:13:36 -06:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def random: Fu[Option[Game]] =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find($empty)
|
|
|
|
.sort(Query.sortCreated)
|
2020-08-18 13:31:32 -06:00
|
|
|
.skip(ThreadLocalRandom nextInt 1000)
|
2019-12-13 07:30:20 -07:00
|
|
|
.one[Game]
|
2016-04-01 10:43:50 -06:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def findPgnImport(pgn: String): Fu[Option[Game]] =
|
|
|
|
coll.one[Game](
|
|
|
|
$doc(s"${F.pgnImport}.h" -> PgnImport.hash(pgn))
|
|
|
|
)
|
2015-05-28 09:40:49 -06:00
|
|
|
|
2019-12-13 18:17:43 -07:00
|
|
|
def getOptionPgn(id: ID): Fu[Option[PgnMoves]] = game(id) dmap2 { _.pgnMoves }
|
2013-12-05 16:53:18 -07:00
|
|
|
|
2016-04-01 10:43:50 -06:00
|
|
|
def lastGameBetween(u1: String, u2: String, since: DateTime): Fu[Option[Game]] =
|
2019-12-13 07:30:20 -07:00
|
|
|
coll.one[Game](
|
|
|
|
$doc(
|
|
|
|
F.playerUids $all List(u1, u2),
|
|
|
|
F.createdAt $gt since
|
|
|
|
)
|
|
|
|
)
|
2013-12-26 16:31:09 -07:00
|
|
|
|
2017-11-30 21:18:44 -07:00
|
|
|
def lastGamesBetween(u1: User, u2: User, since: DateTime, nb: Int): Fu[List[Game]] =
|
|
|
|
List(u1, u2).forall(_.count.game > 0) ??
|
2020-07-19 07:48:22 -06:00
|
|
|
coll.secondaryPreferred.list[Game](
|
|
|
|
$doc(
|
|
|
|
F.playerUids $all List(u1.id, u2.id),
|
|
|
|
F.createdAt $gt since
|
|
|
|
),
|
|
|
|
nb
|
|
|
|
)
|
2015-04-29 09:04:55 -06:00
|
|
|
|
2018-03-07 16:22:15 -07:00
|
|
|
def getSourceAndUserIds(id: ID): Fu[(Option[Source], List[User.ID])] =
|
2019-12-13 18:17:43 -07:00
|
|
|
coll.one[Bdoc]($id(id), $doc(F.playerUids -> true, F.source -> true)) dmap {
|
2018-03-07 16:22:15 -07:00
|
|
|
_.fold(none[Source] -> List.empty[User.ID]) { doc =>
|
2019-12-13 07:30:20 -07:00
|
|
|
(doc.int(F.source) flatMap Source.apply, ~doc.getAsOpt[List[User.ID]](F.playerUids))
|
2018-03-07 16:22:15 -07:00
|
|
|
}
|
|
|
|
}
|
2017-08-08 17:35:16 -06:00
|
|
|
|
|
|
|
def recentAnalysableGamesByUserId(userId: User.ID, nb: Int) =
|
2020-08-21 09:18:23 -06:00
|
|
|
coll
|
2019-12-13 07:30:20 -07:00
|
|
|
.find(
|
|
|
|
Query.finished
|
|
|
|
++ Query.rated
|
|
|
|
++ Query.user(userId)
|
|
|
|
++ Query.turnsGt(20)
|
|
|
|
)
|
|
|
|
.sort(Query.sortCreated)
|
2017-08-08 17:35:16 -06:00
|
|
|
.cursor[Game](ReadPreference.secondaryPreferred)
|
|
|
|
.list(nb)
|
2020-01-19 09:31:28 -07:00
|
|
|
|
|
|
|
// only for student games, for aggregation
|
2021-02-10 13:38:10 -07:00
|
|
|
def denormalizePerfType(game: Game): Unit =
|
2020-01-19 09:31:28 -07:00
|
|
|
game.perfType ?? { pt =>
|
2021-02-10 13:38:10 -07:00
|
|
|
coll.updateFieldUnchecked($id(game.id), F.perfType, pt.id)
|
2020-01-19 09:31:28 -07:00
|
|
|
}
|
2013-03-22 11:53:13 -06:00
|
|
|
}
|