team battle WIP

team-tournament
Thibault Duplessis 2019-10-03 19:49:44 +02:00
parent 90640772c4
commit d32ec59c29
6 changed files with 73 additions and 34 deletions

View File

@ -215,8 +215,8 @@ GET /tournament/featured controllers.Tournament.featured
GET /tournament/new controllers.Tournament.form
POST /tournament/new controllers.Tournament.create
GET /tournament/team-battle/new/:teamId controllers.Tournament.teamBattleForm(teamId: String)
GET /tournament/team-battle/edit controllers.Tournament.teamBattleEdit(id: String)
POST /tournament/team-battle/edit controllers.Tournament.teamBattleUpdate(id: String)
GET /tournament/team-battle/edit/:id controllers.Tournament.teamBattleEdit(id: String)
POST /tournament/team-battle/edit/:id controllers.Tournament.teamBattleUpdate(id: String)
GET /tournament/calendar controllers.Tournament.calendar
GET /tournament/$id<\w{8}> controllers.Tournament.show(id: String)
GET /tournament/$id<\w{8}>/standing/:page controllers.Tournament.standing(id: String, page: Int)

View File

@ -14,9 +14,9 @@ object PlayerRepo {
private lazy val coll = Env.current.playerColl
private def selectId(id: String) = $doc("_id" -> id)
private def selectTour(tourId: String) = $doc("tid" -> tourId)
private def selectTourUser(tourId: String, userId: String) = $doc(
private def selectId(id: Tournament.ID) = $doc("_id" -> id)
private def selectTour(tourId: Tournament.ID) = $doc("tid" -> tourId)
private def selectTourUser(tourId: Tournament.ID, userId: User.ID) = $doc(
"tid" -> tourId,
"uid" -> userId
)
@ -24,82 +24,110 @@ object PlayerRepo {
private val selectWithdraw = $doc("w" -> true)
private val bestSort = $doc("m" -> -1)
def byId(id: String): Fu[Option[Player]] = coll.uno[Player](selectId(id))
def byId(id: Tournament.ID): Fu[Option[Player]] = coll.uno[Player](selectId(id))
private[tournament] def bestByTour(tourId: String, nb: Int, skip: Int = 0): Fu[List[Player]] =
private[tournament] def bestByTour(tourId: Tournament.ID, nb: Int, skip: Int = 0): Fu[List[Player]] =
coll.find(selectTour(tourId)).sort(bestSort).skip(skip).list[Player](nb)
private[tournament] def bestByTourWithRank(tourId: String, nb: Int, skip: Int = 0): Fu[RankedPlayers] =
private[tournament] def bestByTourWithRank(tourId: Tournament.ID, nb: Int, skip: Int = 0): Fu[RankedPlayers] =
bestByTour(tourId, nb, skip).map { res =>
res.foldRight(List.empty[RankedPlayer] -> (res.size + skip)) {
case (p, (res, rank)) => (RankedPlayer(rank, p) :: res, rank - 1)
}._1
}
private[tournament] def bestByTourWithRankByPage(tourId: String, nb: Int, page: Int): Fu[RankedPlayers] =
private[tournament] def bestByTourWithRankByPage(tourId: Tournament.ID, nb: Int, page: Int): Fu[RankedPlayers] =
bestByTourWithRank(tourId, nb, (page - 1) * nb)
def countActive(tourId: String): Fu[Int] =
// very expensive
private[tournament] def bestTeamIdsByTour(tourId: Tournament.ID, topPlayers: Int): Fu[List[TeamBattle.RankedTeamId]] = {
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
coll.aggregateList(
Match(selectTour(tourId)),
List(
GroupField("t")("m" -> PushField("m")),
Project($doc(
"m" -> $doc(
"$sum" -> $doc(
"$slice" -> $arr("m", topPlayers)
)
)
)),
Sort(Descending("m"))
),
maxDocs = 10
).map {
_.flatMap { doc =>
doc.getAs[TeamId]("_id") flatMap { teamId =>
doc.getAs[Int]("m") map { teamId -> _ }
}
}.zipWithIndex map {
case ((teamId, magic), rank) => TeamBattle.RankedTeamId(rank, teamId, magic)
}
}
}
def countActive(tourId: Tournament.ID): Fu[Int] =
coll.countSel(selectTour(tourId) ++ selectActive)
def count(tourId: String): Fu[Int] = coll.countSel(selectTour(tourId))
def count(tourId: Tournament.ID): Fu[Int] = coll.countSel(selectTour(tourId))
def removeByTour(tourId: String) = coll.remove(selectTour(tourId)).void
def removeByTour(tourId: Tournament.ID) = coll.remove(selectTour(tourId)).void
def remove(tourId: String, userId: String) =
def remove(tourId: Tournament.ID, userId: User.ID) =
coll.remove(selectTourUser(tourId, userId)).void
def filterExists(tourIds: List[Tournament.ID], userId: String): Fu[List[Tournament.ID]] =
def filterExists(tourIds: List[Tournament.ID], userId: User.ID): Fu[List[Tournament.ID]] =
coll.primitive[Tournament.ID]($doc(
"tid" $in tourIds,
"uid" -> userId
), "tid")
def existsActive(tourId: String, userId: String) =
def existsActive(tourId: Tournament.ID, userId: User.ID) =
coll.exists(selectTourUser(tourId, userId) ++ selectActive)
def unWithdraw(tourId: String) = coll.update(
def unWithdraw(tourId: Tournament.ID) = coll.update(
selectTour(tourId) ++ selectWithdraw,
$doc("$unset" -> $doc("w" -> true)),
multi = true
).void
def find(tourId: String, userId: String): Fu[Option[Player]] =
def find(tourId: Tournament.ID, userId: User.ID): Fu[Option[Player]] =
coll.find(selectTourUser(tourId, userId)).uno[Player]
def update(tourId: String, userId: String)(f: Player => Fu[Player]) =
def update(tourId: Tournament.ID, userId: User.ID)(f: Player => Fu[Player]) =
find(tourId, userId) flatten s"No such player: $tourId/$userId" flatMap f flatMap { player =>
coll.update(selectId(player._id), player).void
}
def join(tourId: String, user: User, perfLens: Perfs => Perf, team: Option[TeamId]) =
def join(tourId: Tournament.ID, user: User, perfLens: Perfs => Perf, team: Option[TeamId]) =
find(tourId, user.id) flatMap {
case Some(p) if p.withdraw => coll.update(selectId(p._id), $unset("w"))
case Some(p) => funit
case None => coll.insert(Player.make(tourId, user, perfLens, team))
} void
def withdraw(tourId: String, userId: String) =
def withdraw(tourId: Tournament.ID, userId: User.ID) =
coll.update(selectTourUser(tourId, userId), $set("w" -> true)).void
private[tournament] def withPoints(tourId: String): Fu[List[Player]] =
private[tournament] def withPoints(tourId: Tournament.ID): Fu[List[Player]] =
coll.find(
selectTour(tourId) ++ $doc("m" $gt 0)
).list[Player]()
private[tournament] def userIds(tourId: String): Fu[List[String]] =
coll.distinct[String, List]("uid", selectTour(tourId).some)
private[tournament] def userIds(tourId: Tournament.ID): Fu[List[User.ID]] =
coll.distinct[User.ID, List]("uid", selectTour(tourId).some)
private[tournament] def activeUserIds(tourId: String): Fu[List[String]] =
coll.distinct[String, List](
private[tournament] def activeUserIds(tourId: Tournament.ID): Fu[List[User.ID]] =
coll.distinct[User.ID, List](
"uid", (selectTour(tourId) ++ selectActive).some
)
def winner(tourId: String): Fu[Option[Player]] =
def winner(tourId: Tournament.ID): Fu[Option[Player]] =
coll.find(selectTour(tourId)).sort(bestSort).uno[Player]
// freaking expensive (marathons)
private[tournament] def computeRanking(tourId: String): Fu[Ranking] =
private[tournament] def computeRanking(tourId: Tournament.ID): Fu[Ranking] =
coll.aggregateOne(Match(selectTour(tourId)), List(
Sort(Descending("m")),
Group(BSONNull)("uids" -> PushField("uid"))
@ -108,7 +136,7 @@ object PlayerRepo {
_ get "uids" match {
case Some(BSONArray(uids)) =>
// mutable optimized implementation
val b = Map.newBuilder[String, Int]
val b = Map.newBuilder[User.ID, Int]
var r = 0
for (u <- uids) {
b += (u.get.asInstanceOf[BSONString].value -> r)
@ -124,21 +152,21 @@ object PlayerRepo {
coll.countSel(selectTour(player.tourId) ++ $doc("m" $gt player.magicScore))
// expensive, cache it
private[tournament] def averageRating(tourId: String): Fu[Int] =
private[tournament] def averageRating(tourId: Tournament.ID): Fu[Int] =
coll.aggregateOne(Match(selectTour(tourId)), List(
Group(BSONNull)("rating" -> AvgField("r"))
)) map {
~_.flatMap(_.getAs[Double]("rating").map(_.toInt))
}
def byTourAndUserIds(tourId: String, userIds: Iterable[String]): Fu[List[Player]] =
def byTourAndUserIds(tourId: Tournament.ID, userIds: Iterable[User.ID]): Fu[List[Player]] =
coll.find(selectTour(tourId) ++ $doc("uid" $in userIds))
.list[Player]()
.chronometer.logIfSlow(200, logger) { players =>
s"PlayerRepo.byTourAndUserIds $tourId ${userIds.size} user IDs, ${players.size} players"
}.result
def pairByTourAndUserIds(tourId: String, id1: String, id2: String): Fu[Option[(Player, Player)]] =
def pairByTourAndUserIds(tourId: Tournament.ID, id1: User.ID, id2: User.ID): Fu[Option[(Player, Player)]] =
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)
@ -153,7 +181,7 @@ object PlayerRepo {
ranking get p.userId map { RankedPlayer(_, p) }
}.sortBy(_.rank)
def rankedByTourAndUserIds(tourId: String, userIds: Iterable[String], ranking: Ranking): Fu[RankedPlayers] =
def rankedByTourAndUserIds(tourId: Tournament.ID, userIds: Iterable[User.ID], ranking: Ranking): Fu[RankedPlayers] =
byTourAndUserIds(tourId, userIds).map { rankPlayers(_, ranking) }
.chronometer
.logIfSlow(200, logger) { players =>

View File

@ -13,6 +13,10 @@ case class TeamBattle(
object TeamBattle {
case class RankedTeamId(rank: Int, teamId: TeamId, magicScore: Int) {
def score: Int = magicScore / 10000
}
object DataForm {
import play.api.data.Forms._
import lila.common.Form._

View File

@ -236,7 +236,12 @@ final class TournamentApi(
case None if tour.isTeamBattle => fuccess(false)
case None => proceedWithTeam(none)
case Some(team) => tour.teamBattle match {
case Some(battle) if battle.teams contains team => proceedWithTeam(team.some)
case Some(battle) if battle.teams contains team =>
getUserTeamIds(me) flatMap { myTeams =>
if (myTeams has team) proceedWithTeam(team.some)
else proceedWithTeam(team.some) // listress
// else fuccess(false)
}
case _ => fuccess(false)
}
}

View File

@ -17,6 +17,7 @@ private object AntmaPairing {
def pairScore(a: RankedPlayer, b: RankedPlayer): Option[Int] =
if (justPlayedTogether(a.player.userId, b.player.userId)) None
else if (a.player.team == b.player.team) None
else Some {
Math.abs(a.rank - b.rank) * rankFactor(a, b) +
Math.abs(a.player.rating - b.player.rating)

View File

@ -17,7 +17,8 @@
.tour__standing team {
@extend %box-radius;
@include metal;
padding: 2px 5px;
padding: 1px 5px;
font-size: .8em;
border: 1px solid $c-brag;
color: c-clearer($c-brag);
}