lila/modules/game/src/main/GameRepo.scala

559 lines
18 KiB
Scala
Raw Normal View History

2013-03-22 11:53:13 -06:00
package lila.game
import scala.concurrent.duration._
2019-12-13 07:30:20 -07:00
import chess.format.{ FEN, Forsyth }
import chess.{ Color, Status }
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
import reactivemongo.api.{ Cursor, ReadPreference, WriteConcern }
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
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._
import Game.{ ID, BSONFields => F }
import Player.holdAlertBSONHandler
2013-03-22 11:53:13 -06: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]] =
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)
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]] =
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]] =
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]] =
game(gameId) dmap2 { _ player color }
2013-03-22 11:53:13 -06:00
def player(gameId: ID, playerId: ID): Fu[Option[Player]] =
game(gameId) dmap { gameOption =>
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]] =
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
def pov(playerRef: PlayerRef): Fu[Option[Pov]] =
game(playerRef.gameId) dmap { _ flatMap { _ playerIdPov playerRef.playerId } }
2013-03-22 11:53:13 -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
2019-12-13 07:30:20 -07:00
def userPovsByGameIds(
gameIds: List[String],
user: User,
readPreference: ReadPreference = ReadPreference.secondaryPreferred
): Fu[List[Pov]] =
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
def recentPovsByUserFromSecondary(user: User, nb: Int, select: Bdoc = $empty): Fu[List[Pov]] =
recentGamesByUserFromSecondaryCursor(user, select)
.list(nb)
.map { _.flatMap(Pov(_, user)) }
def recentGamesByUserFromSecondaryCursor(user: User, select: Bdoc = $empty) =
coll
.find(Query.user(user) ++ select)
2017-03-26 04:49:34 -06:00
.sort(Query.sortCreated)
.cursor[Game](ReadPreference.secondaryPreferred)
2019-12-13 07:30:20 -07:00
def gamesForAssessment(userId: String, nb: Int): Fu[List[Game]] =
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]] =
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] =
coll.find(selector).cursor[Game](readPreference)
2016-04-01 10:54:24 -06:00
def docCursor(
selector: Bdoc,
readPreference: ReadPreference = ReadPreference.secondaryPreferred
): AkkaStreamCursor[Bdoc] =
coll.find(selector).cursor[Bdoc](readPreference)
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] =
coll.find(selector).sort(sort).batchSize(batchSize).cursor[Game](readPreference)
2016-04-01 22:57:54 -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
def update(progress: Progress): Funit =
saveDiff(progress.origin, GameDiff(progress.origin, progress.game))
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
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]] =
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
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]] =
coll.distinctEasy[Game.ID, List](
F.id,
Query.nowPlaying(user.id) ++ Query.noAi ++ Query.clock(true),
ReadPreference.secondaryPreferred
)
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)
.sort(Query.sortMovedAtNoIndex)
.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
def lastPlayed(user: User): Fu[Option[Pov]] =
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 {
_.sortBy(_.movedAt).lastOption flatMap { Pov(_, user) }
2016-04-01 10:43:50 -06:00
}
2020-04-15 12:11:35 -06:00
def quickLastPlayedId(userId: User.ID): Fu[Option[Game.ID]] =
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]] =
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
def setTv(id: ID) = coll.updateFieldUnchecked($id(id), F.tvAt, DateTime.now)
def setAnalysed(id: ID): Unit = coll.updateFieldUnchecked($id(id), F.analysed, true)
def setUnanalysed(id: ID): Unit = coll.updateFieldUnchecked($id(id), F.analysed, false)
def isAnalysed(id: ID): Fu[Boolean] =
2016-04-01 10:43:50 -06:00
coll.exists($id(id) ++ Query.analysed(true))
def exists(id: ID) = coll.exists($id(id))
2018-04-05 10:03:52 -06:00
def tournamentId(id: ID): Fu[Option[String]] = coll.primitiveOne[String]($id(id), F.tournamentId)
def incBookmarks(id: ID, value: Int) =
2019-11-30 11:06:50 -07:00
coll.update.one($id(id), $inc(F.bookmarks -> value)).void
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
def setBorderAlert(pov: Pov) = setHoldAlert(pov, Player.HoldAlert(0, 0, 20))
2014-03-12 13:35:22 -06: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
)
.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-12-13 07:30:20 -07:00
def hasHoldAlert(pov: Pov): Fu[Boolean] =
coll.exists(
$doc(
$id(pov.gameId),
holdAlertField(pov.color) $exists true
)
)
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
)
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
)
)
2013-03-22 11:53:13 -06:00
2019-12-13 07:30:20 -07:00
def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] =
coll
2019-12-13 07:30:20 -07:00
.find(
Query.mate ++ Query.variantStandard
)
.sort(Query.sortCreated)
.skip(ThreadLocalRandom nextInt distribution)
2019-12-13 07:30:20 -07:00
.one[Game]
2013-03-23 17:10:46 -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
val fen: Option[FEN] = initialFen orElse {
2021-05-24 08:49:39 -06:00
(g2.variant.fromPosition || g2.variant.chess960)
.option(Forsyth >> g2.chess)
.filterNot(_.initial)
}
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 {
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
}
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
def setCheckAt(g: Game, at: DateTime) =
coll.update.one($id(g.id), $set(F.checkAt -> at))
def unsetCheckAt(id: Game.ID): Funit =
coll.update.one($id(id), $unset(F.checkAt)).void
2019-07-13 12:12:42 -06:00
def unsetPlayingUids(g: Game): Unit =
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]] =
if (game.imported || !game.variant.standardInitialPosition) initialFen(game.id) dmap {
case None if game.variant == chess.variant.Chess960 => Forsyth.initial.some
2019-12-13 07:30:20 -07:00
case fen => fen
}
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-10-30 17:21:48 -06:00
def withInitialFen(game: Game): Fu[Game.WithInitialFen] =
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 =>
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
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(
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)),
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")),
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 -> _ }
}
})
}
2019-12-13 07:30:20 -07:00
def random: Fu[Option[Game]] =
coll
2019-12-13 07:30:20 -07:00
.find($empty)
.sort(Query.sortCreated)
.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))
)
def getOptionPgn(id: ID): Fu[Option[PgnMoves]] = game(id) dmap2 { _.pgnMoves }
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
def getSourceAndUserIds(id: ID): Fu[(Option[Source], List[User.ID])] =
coll.one[Bdoc]($id(id), $doc(F.playerUids -> true, F.source -> true)) dmap {
_.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))
}
}
2017-08-08 17:35:16 -06:00
def recentAnalysableGamesByUserId(userId: User.ID, nb: Int) =
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
def denormalizePerfType(game: Game): Unit =
2020-01-19 09:31:28 -07:00
game.perfType ?? { pt =>
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
}