lila/modules/tournament/src/main/PlayerRepo.scala

356 lines
12 KiB
Scala
Raw Normal View History

2015-06-11 03:45:49 -06:00
package lila.tournament
2019-12-08 09:58:50 -07:00
import com.github.ghik.silencer.silent
2019-12-13 07:30:20 -07:00
import reactivemongo.akkastream.{ cursorProducer, AkkaStreamCursor }
2019-12-02 17:42:57 -07:00
import reactivemongo.api._
2019-11-29 19:16:11 -07:00
import reactivemongo.api.bson._
2015-06-11 03:45:49 -06:00
import BSONHandlers._
2016-04-01 11:50:57 -06:00
import lila.db.dsl._
2019-12-08 09:58:50 -07:00
import lila.hub.LightTeam.TeamID
2015-06-11 09:03:45 -06:00
import lila.rating.Perf
2019-12-13 07:30:20 -07:00
import lila.user.{ Perfs, User }
2015-06-11 03:45:49 -06:00
final class PlayerRepo(coll: Coll)(implicit ec: scala.concurrent.ExecutionContext) {
2015-06-11 03:45:49 -06:00
2019-12-13 07:30:20 -07:00
private def selectId(id: Tournament.ID) = $doc("_id" -> id)
2019-10-03 11:49:44 -06:00
private def selectTour(tourId: Tournament.ID) = $doc("tid" -> tourId)
private def selectTourUser(tourId: Tournament.ID, userId: User.ID) = $doc(
2015-06-11 16:54:51 -06:00
"tid" -> tourId,
2019-07-13 15:38:21 -06:00
"uid" -> userId
)
2019-12-13 07:30:20 -07:00
private val selectActive = $doc("w" $ne true)
2016-06-18 05:21:30 -06:00
private val selectWithdraw = $doc("w" -> true)
2019-12-13 07:30:20 -07:00
private val bestSort = $doc("m" -> -1)
2015-06-11 03:45:49 -06:00
2019-12-07 21:49:02 -07:00
def byId(id: Tournament.ID): Fu[Option[Player]] = coll.one[Player](selectId(id))
2015-06-11 03:45:49 -06:00
2019-10-03 11:49:44 -06:00
private[tournament] def bestByTour(tourId: Tournament.ID, nb: Int, skip: Int = 0): Fu[List[Player]] =
2019-12-03 09:52:11 -07:00
coll.ext.find(selectTour(tourId)).sort(bestSort).skip(skip).list[Player](nb)
2015-06-11 03:45:49 -06:00
2019-12-13 07:30:20 -07:00
private[tournament] def bestByTourWithRank(
tourId: Tournament.ID,
nb: Int,
skip: Int = 0
): Fu[RankedPlayers] =
bestByTour(tourId, nb, skip).map { res =>
2019-12-13 07:30:20 -07:00
res
.foldRight(List.empty[RankedPlayer] -> (res.size + skip)) {
case (p, (res, rank)) => (RankedPlayer(rank, p) :: res, rank - 1)
}
._1
2015-06-11 03:45:49 -06:00
}
2019-12-13 07:30:20 -07:00
private[tournament] def bestByTourWithRankByPage(
tourId: Tournament.ID,
nb: Int,
page: Int
): Fu[RankedPlayers] =
bestByTourWithRank(tourId, nb, (page - 1) * nb)
2019-10-03 11:49:44 -06:00
// very expensive
2019-12-13 07:30:20 -07:00
private[tournament] def bestTeamIdsByTour(
tourId: Tournament.ID,
battle: TeamBattle
): Fu[List[TeamBattle.RankedTeam]] = {
2019-10-05 13:52:28 -06:00
import TeamBattle.{ RankedTeam, TeamLeader }
2019-12-13 07:30:20 -07:00
coll
.aggregateList(maxDocs = 10) { framework =>
import framework._
Match(selectTour(tourId)) -> List(
Sort(Descending("m")),
GroupField("t")(
"m" -> Push(
$doc(
"u" -> "$uid",
"m" -> "$m"
)
)
),
Project(
$doc(
"p" -> $doc(
"$slice" -> $arr("$m", battle.nbLeaders)
)
)
2019-10-03 11:49:44 -06:00
)
2019-12-13 07:30:20 -07:00
)
2019-10-03 11:49:44 -06:00
}
2019-12-13 07:30:20 -07:00
.map {
_.flatMap { doc =>
for {
teamId <- doc.getAsOpt[TeamID]("_id")
leadersBson <- doc.getAsOpt[List[Bdoc]]("p")
leaders = leadersBson.flatMap {
case p: Bdoc =>
for {
id <- p.getAsOpt[User.ID]("u")
magic <- p.int("m")
} yield TeamLeader(id, magic)
}
} yield RankedTeam(0, teamId, leaders)
}.sortBy(-_.magicScore).zipWithIndex map {
case (rt, pos) => rt.copy(rank = pos + 1)
}
} map { ranked =>
2019-12-02 17:42:57 -07:00
if (ranked.size == battle.teams.size) ranked
2019-12-13 07:30:20 -07:00
else
ranked ::: battle.teams
.foldLeft(List.empty[RankedTeam]) {
case (missing, team) if !ranked.exists(_.teamId == team) =>
RankedTeam(missing.headOption.fold(ranked.size)(_.rank) + 1, team, Nil) :: missing
case (acc, _) => acc
}
.reverse
2019-12-02 17:42:57 -07:00
}
2019-10-03 11:49:44 -06:00
}
2019-12-02 17:42:57 -07:00
2019-10-05 13:52:28 -06:00
// very expensive
2019-12-13 07:30:20 -07:00
private[tournament] def teamInfo(
tourId: Tournament.ID,
teamId: TeamID,
@silent battle: TeamBattle
): Fu[TeamBattle.TeamInfo] = {
coll
.aggregateWith[Bdoc]() { framework =>
import framework._
Match(selectTour(tourId) ++ $doc("t" -> teamId)) -> List(
Sort(Descending("m")),
Facet(
List(
"agg" -> {
Group(BSONNull)(
"nb" -> SumAll,
"rating" -> AvgField("r"),
"perf" -> AvgField("e"),
"score" -> AvgField("s")
) -> Nil
},
"topPlayers" -> {
Limit(50) -> Nil
}
)
)
)
}
.headOption
.map {
_.flatMap { doc =>
for {
aggs <- doc.getAsOpt[List[Bdoc]]("agg")
agg <- aggs.headOption
nbPlayers <- agg.int("nb")
rating = agg.double("rating").??(math.round)
perf = agg.double("perf").??(math.round)
score = agg.double("score").??(math.round)
topPlayers <- doc.getAsOpt[List[Player]]("topPlayers")
} yield TeamBattle.TeamInfo(teamId, nbPlayers, rating.toInt, perf.toInt, score.toInt, topPlayers)
} | TeamBattle.TeamInfo(teamId, 0, 0, 0, 0, Nil)
}
2019-10-05 13:52:28 -06:00
}
2019-12-08 09:58:50 -07:00
def bestTeamPlayers(tourId: Tournament.ID, teamId: TeamID, nb: Int): Fu[List[Player]] =
2019-12-03 09:52:11 -07:00
coll.ext.find($doc("tid" -> tourId, "t" -> teamId)).sort($sort desc "m").list[Player](nb)
2019-10-05 13:52:28 -06:00
2019-12-08 09:58:50 -07:00
def countTeamPlayers(tourId: Tournament.ID, teamId: TeamID): Fu[Int] =
2019-10-05 13:52:28 -06:00
coll.countSel($doc("tid" -> tourId, "t" -> teamId))
2019-10-03 11:49:44 -06:00
2019-12-08 09:58:50 -07:00
def teamsOfPlayers(tourId: Tournament.ID, userIds: List[User.ID]): Fu[List[(User.ID, TeamID)]] =
2019-12-13 07:30:20 -07:00
coll.ext
.find($doc("tid" -> tourId, "uid" $in userIds), $doc("_id" -> false, "uid" -> true, "t" -> true))
2019-10-06 10:58:42 -06:00
.list[Bdoc]()
.map {
_.flatMap { doc =>
2019-12-02 17:42:57 -07:00
doc.getAsOpt[User.ID]("uid") flatMap { userId =>
2019-12-08 09:58:50 -07:00
doc.getAsOpt[TeamID]("t") map { (userId, _) }
2019-10-06 10:58:42 -06:00
}
}
}
def teamVs(tourId: Tournament.ID, game: lila.game.Game): Fu[Option[TeamBattle.TeamVs]] =
game.twoUserIds ?? {
case (w, b) =>
teamsOfPlayers(tourId, List(w, b)).dmap(_.toMap) map { m =>
(m.get(w) |@| m.get(b)).tupled ?? {
case (wt, bt) => TeamBattle.TeamVs(chess.Color.Map(wt, bt)).some
}
}
}
2019-10-03 11:49:44 -06:00
def countActive(tourId: Tournament.ID): Fu[Int] =
2017-04-05 03:37:16 -06:00
coll.countSel(selectTour(tourId) ++ selectActive)
2015-06-11 03:45:49 -06:00
2019-10-03 11:49:44 -06:00
def count(tourId: Tournament.ID): Fu[Int] = coll.countSel(selectTour(tourId))
2015-06-11 09:03:45 -06:00
2019-12-03 09:52:11 -07:00
def removeByTour(tourId: Tournament.ID) = coll.delete.one(selectTour(tourId)).void
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
def remove(tourId: Tournament.ID, userId: User.ID) =
2019-12-03 09:52:11 -07:00
coll.delete.one(selectTourUser(tourId, userId)).void
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
def filterExists(tourIds: List[Tournament.ID], userId: User.ID): Fu[List[Tournament.ID]] =
2019-12-13 07:30:20 -07:00
coll.primitive[Tournament.ID](
$doc(
"tid" $in tourIds,
"uid" -> userId
),
"tid"
)
2015-06-11 16:54:51 -06:00
2019-10-03 11:49:44 -06:00
def existsActive(tourId: Tournament.ID, userId: User.ID) =
2017-04-05 03:37:16 -06:00
coll.exists(selectTourUser(tourId, userId) ++ selectActive)
2015-06-11 09:03:45 -06:00
2019-10-05 13:52:28 -06:00
def exists(tourId: Tournament.ID, userId: User.ID) =
coll.exists(selectTourUser(tourId, userId))
2019-12-13 07:30:20 -07:00
def unWithdraw(tourId: Tournament.ID) =
coll.update
.one(
selectTour(tourId) ++ selectWithdraw,
$doc("$unset" -> $doc("w" -> true)),
multi = true
)
.void
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
def find(tourId: Tournament.ID, userId: User.ID): Fu[Option[Player]] =
2019-12-07 21:49:02 -07:00
coll.ext.find(selectTourUser(tourId, userId)).one[Player]
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
def update(tourId: Tournament.ID, userId: User.ID)(f: Player => Fu[Player]) =
2019-12-02 17:42:57 -07:00
find(tourId, userId) orFail s"No such player: $tourId/$userId" flatMap f flatMap { player =>
coll.update.one(selectId(player._id), player).void
2015-06-11 16:54:51 -06:00
}
2019-12-08 09:58:50 -07:00
def join(tourId: Tournament.ID, user: User, perfLens: Perfs => Perf, team: Option[TeamID]) =
2015-06-11 09:03:45 -06:00
find(tourId, user.id) flatMap {
2019-12-05 22:35:55 -07:00
case Some(p) if p.withdraw => coll.update.one(selectId(p._id), $unset("w"))
2019-12-13 07:30:20 -07:00
case Some(_) => funit
case None => coll.insert.one(Player.make(tourId, user, perfLens, team))
2015-06-11 09:03:45 -06:00
} void
2019-10-03 11:49:44 -06:00
def withdraw(tourId: Tournament.ID, userId: User.ID) =
2019-12-05 22:35:55 -07:00
coll.update.one(selectTourUser(tourId, userId), $set("w" -> true)).void
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
private[tournament] def withPoints(tourId: Tournament.ID): Fu[List[Player]] =
2019-12-13 07:30:20 -07:00
coll.ext
.find(
selectTour(tourId) ++ $doc("m" $gt 0)
)
.list[Player]()
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
private[tournament] def userIds(tourId: Tournament.ID): Fu[List[User.ID]] =
2019-12-02 17:42:57 -07:00
coll.distinctEasy[User.ID, List]("uid", selectTour(tourId))
2015-06-11 09:03:45 -06:00
private[tournament] def nbActiveUserIds(tourId: Tournament.ID): Fu[Int] =
coll.countSel(selectTour(tourId) ++ selectActive)
2015-06-11 09:03:45 -06:00
2019-10-03 11:49:44 -06:00
def winner(tourId: Tournament.ID): Fu[Option[Player]] =
2019-12-07 21:56:53 -07:00
coll.ext.find(selectTour(tourId)).sort(bestSort).one[Player]
2015-06-11 09:03:45 -06:00
2015-10-05 06:40:42 -06:00
// freaking expensive (marathons)
2019-10-03 11:49:44 -06:00
private[tournament] def computeRanking(tourId: Tournament.ID): Fu[Ranking] =
2019-12-13 07:30:20 -07:00
coll
.aggregateWith[Bdoc]() { framework =>
import framework._
Match(selectTour(tourId)) -> List(
Sort(Descending("m")),
Group(BSONNull)("uids" -> PushField("uid"))
)
}
.headOption map {
_ ?? {
2019-07-13 15:38:21 -06:00
_ get "uids" match {
case Some(BSONArray(uids)) =>
2016-01-22 07:33:25 -07:00
// mutable optimized implementation
2019-10-03 11:49:44 -06:00
val b = Map.newBuilder[User.ID, Int]
2016-01-22 07:33:25 -07:00
var r = 0
2019-07-13 15:38:21 -06:00
for (u <- uids) {
2019-12-02 17:42:57 -07:00
b += (u.asInstanceOf[BSONString].value -> r)
2016-01-22 07:33:25 -07:00
r = r + 1
}
b.result
2016-01-22 07:33:25 -07:00
case _ => Map.empty
}
}
}
2015-06-11 09:03:45 -06:00
def computeRankOf(player: Player): Fu[Int] =
coll.countSel(selectTour(player.tourId) ++ $doc("m" $gt player.magicScore))
2017-01-15 12:27:45 -07:00
// expensive, cache it
2019-10-03 11:49:44 -06:00
private[tournament] def averageRating(tourId: Tournament.ID): Fu[Int] =
2019-12-13 07:30:20 -07:00
coll
.aggregateWith[Bdoc]() { framework =>
import framework._
Match(selectTour(tourId)) -> List(
Group(BSONNull)("rating" -> AvgField("r"))
)
}
.headOption map {
2019-12-02 17:42:57 -07:00
~_.flatMap(_.double("rating").map(_.toInt))
2017-01-15 12:27:45 -07:00
}
2019-10-03 11:49:44 -06:00
def byTourAndUserIds(tourId: Tournament.ID, userIds: Iterable[User.ID]): Fu[List[Player]] =
2019-12-13 07:30:20 -07:00
coll.ext
.find(selectTour(tourId) ++ $doc("uid" $in userIds))
2016-04-02 05:35:06 -06:00
.list[Player]()
2019-12-13 07:30:20 -07:00
.chronometer
.logIfSlow(200, logger) { players =>
s"PlayerRepo.byTourAndUserIds $tourId ${userIds.size} user IDs, ${players.size} players"
2019-12-13 07:30:20 -07:00
}
.result
2015-06-11 03:45:49 -06:00
2019-10-03 11:49:44 -06:00
def pairByTourAndUserIds(tourId: Tournament.ID, id1: User.ID, id2: User.ID): Fu[Option[(Player, Player)]] =
2016-01-25 05:28:42 -07:00
byTourAndUserIds(tourId, List(id1, id2)) map {
case List(p1, p2) if p1.is(id1) && p2.is(id2) => Some(p1 -> p2)
case List(p1, p2) if p1.is(id2) && p2.is(id1) => Some(p2 -> p1)
2019-12-13 07:30:20 -07:00
case _ => none
2016-01-25 05:28:42 -07:00
}
def setPerformance(player: Player, performance: Int) =
2019-12-07 21:56:53 -07:00
coll.update.one(selectId(player.id), $doc("$set" -> $doc("e" -> performance))).void
private def rankPlayers(players: List[Player], ranking: Ranking): RankedPlayers =
2019-12-13 07:30:20 -07:00
players
.flatMap { p =>
ranking get p.userId map { RankedPlayer(_, p) }
}
.sortBy(_.rank)
def rankedByTourAndUserIds(
tourId: Tournament.ID,
userIds: Iterable[User.ID],
ranking: Ranking
): Fu[RankedPlayers] =
byTourAndUserIds(tourId, userIds)
.map { rankPlayers(_, ranking) }
.chronometer
2016-03-20 03:31:09 -06:00
.logIfSlow(200, logger) { players =>
s"PlayerRepo.rankedByTourAndUserIds $tourId ${userIds.size} user IDs, ${ranking.size} ranking, ${players.size} players"
2019-12-13 07:30:20 -07:00
}
.result
2018-01-08 11:03:17 -07:00
def searchPlayers(tourId: Tournament.ID, term: String, nb: Int): Fu[List[User.ID]] =
2018-04-05 14:21:47 -06:00
User.couldBeUsername(term) ?? {
term.nonEmpty ?? coll.primitive[User.ID](
selector = $doc(
"tid" -> tourId,
2019-07-13 15:38:21 -06:00
"uid" $startsWith term.toLowerCase
2018-04-05 14:21:47 -06:00
),
sort = $sort desc "m",
nb = nb,
2019-07-13 15:38:21 -06:00
field = "uid"
2018-04-05 14:21:47 -06:00
)
}
2019-12-02 17:42:57 -07:00
private[tournament] def sortedCursor(
2019-12-13 07:30:20 -07:00
tournamentId: Tournament.ID,
batchSize: Int,
readPreference: ReadPreference = ReadPreference.secondaryPreferred
2019-12-02 17:42:57 -07:00
): AkkaStreamCursor[Player] =
2019-12-13 07:30:20 -07:00
coll.ext
.find(selectTour(tournamentId))
.sort($sort desc "m")
2019-12-02 20:19:13 -07:00
.batchSize(batchSize)
2019-12-02 17:42:57 -07:00
.cursor[Player](readPreference)
2015-06-11 03:45:49 -06:00
}