Skip to content

Commit

Permalink
More tournament wip
Browse files Browse the repository at this point in the history
  • Loading branch information
WandererXII committed Jan 9, 2025
1 parent ea64be0 commit 59d91d8
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 148 deletions.
28 changes: 13 additions & 15 deletions modules/tournament/src/main/ArrangementRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ final class ArrangementRepo(coll: Coll)(implicit
def byId(id: Arrangement.ID): Fu[Option[Arrangement]] = coll.byId[Arrangement](id)

def byLookup(lookup: Arrangement.Lookup): Fu[Option[Arrangement]] =
coll.one[Arrangement](selectTourUsers(lookup.tourId, lookup.users._1, lookup.users._2) ++ {
lookup.order.filter(_ > 0) ?? { case o => $doc("o" -> o) }
})
coll.one[Arrangement]((lookup.id ?? { id =>
$id(id)
}) ++ selectTourUsers(lookup.tourId, lookup.users._1, lookup.users._2))

def byGame(tourId: Tournament.ID, gameId: Game.ID): Fu[Option[Arrangement]] =
coll.one[Arrangement](selectTourGame(tourId, gameId))
Expand All @@ -60,34 +60,30 @@ final class ArrangementRepo(coll: Coll)(implicit

def removePlaying(tourId: Tournament.ID) = coll.delete.one(selectTour(tourId) ++ selectPlaying).void

def find(tourId: Tournament.ID, userId: User.ID): Fu[List[Arrangement]] =
coll.list[Arrangement](selectTourUser(tourId, userId))

def findPlaying(tourId: Tournament.ID, userId: User.ID): Fu[List[Arrangement]] =
coll.list[Arrangement](selectTourUser(tourId, userId) ++ selectPlaying)

def isPlaying(tourId: Tournament.ID, userId: User.ID): Fu[Boolean] =
coll.exists(selectTourUser(tourId, userId) ++ selectPlaying)

def countByTour(tourId: Tournament.ID): Fu[Int] =
coll.countSel(selectTour(tourId))

def countWithGame(tourId: Tournament.ID): Fu[Int] =
coll.countSel(selectTour(tourId) ++ selectWithGame)

def update(arrangement: Arrangement): Funit =
coll.update.one($id(arrangement.id), arrangement, upsert = true).void

def withGame(id: Arrangement.ID, gid: lila.game.Game.ID) =
coll.update
.one(
$id(id),
$set(
"g" -> gid
) ++ $unset("r1", "r2", "d1", "d2", "ua")
)
.void

def finish(g: lila.game.Game, arr: Arrangement) =
if (g.aborted)
coll.update
.one(
$id(arr.id),
$unset("g")
$unset("g", "st")
)
.void
else
Expand All @@ -98,10 +94,12 @@ final class ArrangementRepo(coll: Coll)(implicit
"s" -> g.status.id,
"w" -> g.winnerUserId.map(_ == arr.user1.id),
"p" -> g.plies
)
) ++ $unset("l")
)
.void

def delete(id: Arrangement.ID) = coll.delete.one($id(id)).void

def removeByTour(tourId: Tournament.ID) = coll.delete.one(selectTour(tourId)).void

private[tournament] def allUpcomingByUserIdChronological(
Expand Down
10 changes: 7 additions & 3 deletions modules/tournament/src/main/BSONHandlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object BSONHandlers {
Arrangement.Points(p(0), p(1), p(2))
}
},
points => BSONArray(points.lose, points.draw, points.win)
points => BSONArray(points.loss, points.draw, points.win)
)
}

Expand Down Expand Up @@ -176,6 +176,7 @@ object BSONHandlers {
rating = r int "r",
provisional = r boolD "pr",
withdraw = r boolD "w",
kicked = r boolD "k",
score = r intD "s",
fire = r boolD "f",
performance = r intD "e",
Expand All @@ -190,6 +191,7 @@ object BSONHandlers {
"r" -> o.rating,
"pr" -> w.boolO(o.provisional),
"w" -> w.boolO(o.withdraw),
"k" -> w.boolO(o.kicked),
"s" -> w.intO(o.score),
"m" -> o.magicScore,
"f" -> w.boolO(o.fire),
Expand Down Expand Up @@ -238,7 +240,6 @@ object BSONHandlers {
val user2Id = users lift 1 err "tournament arrangement second user"
Arrangement(
id = r str "_id",
order = r intD "o",
tourId = r str "t",
user1 = Arrangement.User(
id = user1Id,
Expand All @@ -254,20 +255,21 @@ object BSONHandlers {
color = r.getO[shogi.Color]("c"),
points = r.getO[Arrangement.Points]("pt"),
gameId = r strO "g",
startedAt = r dateO "st",
status = r.intO("s") flatMap shogi.Status.apply,
winner = r boolO "w" map {
case true => user1Id
case _ => user2Id
},
plies = r intO "p",
scheduledAt = r dateO "d",
lockedScheduledAt = r boolD "l",
history = Arrangement.History(r strsD "h")
)
}
def writes(w: BSON.Writer, o: Arrangement) =
$doc(
"_id" -> o.id,
"o" -> o.order.some.filter(_ > 0),
"t" -> o.tourId,
"u" -> BSONArray(o.user1.id, o.user2.id),
"r1" -> o.user1.readyAt,
Expand All @@ -278,11 +280,13 @@ object BSONHandlers {
"c" -> o.color,
"pt" -> o.points.filterNot(_ == Arrangement.Points.default),
"g" -> o.gameId,
"st" -> o.startedAt,
"s" -> o.status.map(_.id),
"w" -> o.winner.map(o.user1 ==),
"p" -> o.plies,
"d" -> o.scheduledAt,
"h" -> o.history.list,
"l" -> w.boolO(o.lockedScheduledAt),
"ua" -> o.gameId.isEmpty ?? DateTime.now.some // updated at
)
}
Expand Down
24 changes: 14 additions & 10 deletions modules/tournament/src/main/Cached.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,18 @@ final private[tournament] class Cached(
}
}

private[tournament] object robin {
private[tournament] object arrangement {

private val playersCache = cacheApi[Tournament.ID, List[JsObject]](32, "tournament.robin.players") {
_.expireAfterWrite(5 minutes)
.buildAsyncFuture(computePlayers)
}
private val arrangementsCache = cacheApi[Tournament.ID, List[JsObject]](32, "tournament.robin.players") {
_.expireAfterWrite(3 minutes)
.buildAsyncFuture(computeArrangements)
}
private val playersCache =
cacheApi[Tournament.ID, List[JsObject]](32, "tournament.robin.players") {
_.expireAfterWrite(5 minutes)
.buildAsyncFuture(computePlayers)
}
private val arrangementsCache =
cacheApi[Tournament.ID, List[JsObject]](32, "tournament.arrangement.players") {
_.expireAfterWrite(3 minutes)
.buildAsyncFuture(computeArrangements)
}

def apply(tourId: Tournament.ID) =
for {
Expand All @@ -145,7 +147,9 @@ final private[tournament] class Cached(
playerRepo
.allByTour(tourId)
.flatMap(
_.sortBy(_.order.getOrElse(Int.MaxValue)).map(JsonView.tablePlayerJson(lightUserApi, _)).sequenceFu
_.sortBy(p => p.order.getOrElse(p.magicScore))
.map(JsonView.arrangementPlayerJson(lightUserApi, _))
.sequenceFu
)

def computeArrangements(tourId: Tournament.ID): Fu[List[JsObject]] =
Expand Down
13 changes: 8 additions & 5 deletions modules/tournament/src/main/Condition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,14 @@ object Condition {
def apply(x: TeamMember): TeamMemberSetup = TeamMemberSetup(x.teamId.some)
}
val all = mapping(
"nbRatedGame" -> optional(nbRatedGame),
"maxRating" -> optional(maxRating),
"minRating" -> optional(minRating),
"titled" -> optional(boolean),
"teamMember" -> optional(teamMember)
"nbRatedGame" -> optional(nbRatedGame).transform[Option[lila.tournament.Condition.NbRatedGame]](
_.filter(_.nb > 0),
identity
),
"maxRating" -> optional(maxRating),
"minRating" -> optional(minRating),
"titled" -> optional(boolean),
"teamMember" -> optional(teamMember)
)(AllSetup.apply)(AllSetup.unapply)
.verifying("Invalid ratings", _.validRatings)

Expand Down
47 changes: 25 additions & 22 deletions modules/tournament/src/main/DataForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class DataForm {
name = teamBattleId.isEmpty option user.titleUsername,
format = (if (teamBattleId.isDefined) Format.Arena.key else Format.Robin.key).some,
timeControlSetup = TimeControl.DataForm.Setup.default,
minutes = minuteDefault,
minutes = minutesDefault.some,
startDate = none,
finishDate = none,
variant = shogi.variant.Standard.id.toString.some,
Expand All @@ -45,7 +45,7 @@ final class DataForm {
name = tour.name.some,
format = tour.format.key.some,
timeControlSetup = TimeControl.DataForm.Setup(tour.timeControl),
minutes = tour.minutes,
minutes = tour.minutes.some,
startDate = tour.startsAt.some,
finishDate = tour.finishesAt.some,
variant = tour.variant.id.toString.some,
Expand Down Expand Up @@ -104,9 +104,9 @@ final class DataForm {
private def makeMapping(user: User) =
mapping(
"name" -> optional(nameType),
"format" -> optional(stringIn(DataForm.formats.toSet)),
"format" -> optional(stringIn(Format.all.map(_.key).toSet)),
"timeControlSetup" -> TimeControl.DataForm.setup,
"minutes" -> {
"minutes" -> optional {
if (lila.security.Granter(_.ManageTournament)(user)) number
else numberIn(minutes)
},
Expand All @@ -126,6 +126,7 @@ final class DataForm {
"hasChat" -> optional(boolean)
)(TournamentSetup.apply)(TournamentSetup.unapply)
.verifying("Invalid starting position", _.validPosition)
.verifying("Provide valid duration", _.validMinutes)
.verifying("End date needs to come at least 20 minutes after start date", _.validFinishDate)
.verifying("Games with this time control cannot be rated", _.validRatedVariant)
.verifying("Cannot have correspondence in arena format", _.validTimeControl)
Expand All @@ -139,10 +140,8 @@ object DataForm {

import shogi.variant._

val formats = Format.all.map(_.key)

val minutes = (20 to 60 by 5) ++ (70 to 120 by 10) ++ (150 to 360 by 30) ++ (420 to 600 by 60) :+ 720
val minuteDefault = 45
val minutes = (20 to 60 by 5) ++ (70 to 120 by 10) ++ (150 to 360 by 30) ++ (420 to 600 by 60) :+ 720
val minutesDefault = 60

val validVariants =
List(Standard, Minishogi, Chushogi, Annanshogi, Kyotoshogi, Checkshogi)
Expand All @@ -157,7 +156,7 @@ private[tournament] case class TournamentSetup(
name: Option[String],
format: Option[String],
timeControlSetup: TimeControl.DataForm.Setup,
minutes: Int,
minutes: Option[Int],
startDate: Option[DateTime],
finishDate: Option[DateTime],
variant: Option[String],
Expand All @@ -174,16 +173,6 @@ private[tournament] case class TournamentSetup(
hasChat: Option[Boolean]
) {

def validPosition = position.fold(true) { sfen =>
sfen.toSituation(realVariant).exists(_.playable(strict = true, withImpasse = true))
}

def validFinishDate = finishDate.fold(true) { d =>
d.minusMinutes(20) isAfter (realStartDate)
}

def validTimeControl = timeControlSetup.isRealTime || format != Format.Arena

def realMode =
if (position.filterNot(_.initialOf(realVariant)).isDefined) Mode.Casual
else Mode(rated.orElse(mode.map(Mode.Rated.id ===)) | true)
Expand All @@ -194,12 +183,26 @@ private[tournament] case class TournamentSetup(

def realStartDate = startDate.filter(_ isAfter DateTime.now).getOrElse(DateTime.now)

def realMinutes = finishDate.ifTrue(format != Format.Arena).map { fd =>
((fd.getMillis - realStartDate.getMillis) / 60000).toInt
} getOrElse minutes
def realMinutes = finishDate
.ifTrue(format != Format.Arena)
.map { fd =>
((fd.getMillis - realStartDate.getMillis) / 60000).toInt
}
.orElse(minutes)
.getOrElse(DataForm.minutesDefault)

def speed = timeControlSetup.clock.fold[shogi.Speed](shogi.Speed.Correspondence)(shogi.Speed.apply)

def validPosition = position.fold(true) { sfen =>
sfen.toSituation(realVariant).exists(_.playable(strict = true, withImpasse = true))
}

def validMinutes = minutes.isDefined || realFormat != Format.Arena

def validFinishDate = finishDate.fold(realFormat == Format.Arena)(_.minusMinutes(20) isAfter realStartDate)

def validTimeControl = timeControlSetup.isRealTime || format != Format.Arena

def validRatedVariant =
realMode == Mode.Casual ||
lila.game.Game.allowRated(position, timeControlSetup.clock, realVariant)
Expand Down
1 change: 1 addition & 0 deletions modules/tournament/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ final class Env(
chatApi: lila.chat.ChatApi,
tellRound: lila.round.TellRound,
roundSocket: lila.round.RoundSocket,
// notifyApi: lila.notify.NotifyApi,
lightUserApi: lila.user.LightUserApi,
onStart: lila.round.OnStart,
historyApi: lila.history.HistoryApi,
Expand Down
Loading

0 comments on commit 59d91d8

Please sign in to comment.