let us join started tournaments
This commit is contained in:
parent
f59273f9d0
commit
d835734d93
|
@ -29,7 +29,7 @@ object Lobby extends LilaController {
|
|||
def renderHome[A](status: Results.Status)(implicit ctx: Context): Fu[SimpleResult] =
|
||||
Env.current.preloader(
|
||||
posts = Env.forum.recent(ctx.me, Env.team.cached.teamIds.apply),
|
||||
tours = TournamentRepo.enterable,
|
||||
tours = Env.tournament.allCreatedSorted(true),
|
||||
filter = Env.setup.filter
|
||||
).map(_.fold(Redirect(_), {
|
||||
case (preload, entries, posts, tours, featured, leaderboard, progress, puzzle) =>
|
||||
|
|
|
@ -40,7 +40,7 @@ object Tournament extends LilaController {
|
|||
}
|
||||
|
||||
private def fetchTournaments =
|
||||
repo.enterable zip repo.started zip repo.finished(20)
|
||||
env allCreatedSorted true zip repo.started zip repo.finished(20)
|
||||
|
||||
def show(id: String) = Open { implicit ctx =>
|
||||
repo byId id flatMap {
|
||||
|
@ -78,7 +78,7 @@ object Tournament extends LilaController {
|
|||
def join(id: String) = AuthBody { implicit ctx =>
|
||||
implicit me =>
|
||||
NoEngine {
|
||||
TourOptionFuRedirect(repo createdById id) { tour =>
|
||||
TourOptionFuRedirect(repo enterableById id) { tour =>
|
||||
tour.hasPassword.fold(
|
||||
fuccess(routes.Tournament.joinPassword(id)),
|
||||
env.api.join(tour, me, none).fold(
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
<br />
|
||||
@tour.map { t =>
|
||||
@tournament.linkTo(t)
|
||||
@tournamentLink(t)
|
||||
<br /><br />
|
||||
}
|
||||
@if(game.finishedOrAborted) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@scheduled.map { tour =>
|
||||
@tour.schedule.map { s =>
|
||||
<li>
|
||||
@linkTo(tour)<br />
|
||||
@tournamentLink(tour)<br />
|
||||
@momentFormat(s.at, "calendar")
|
||||
</li>
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@(createds: List[lila.tournament.Created], starteds: List[lila.tournament.Started], finisheds: List[lila.tournament.Finished])(implicit ctx: Context)
|
||||
|
||||
@joinButton(tour: lila.tournament.Created) = {
|
||||
@joinButton(tour: lila.tournament.Enterable) = {
|
||||
@ctx.me.map { me =>
|
||||
@if(tour contains me) {
|
||||
@if(tour isActive me) {
|
||||
<span class="joined label" data-icon="E"> JOINED</span>
|
||||
} else {
|
||||
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
|
||||
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button"> @trans.join()</button>
|
||||
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button @if(!tour.isOpen) { shy }"> @trans.join()</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@
|
|||
<tbody>
|
||||
@finisheds.map { tour =>
|
||||
<tr>
|
||||
<td>@linkTo(tour)</td>
|
||||
<td>@tournamentLink(tour)</td>
|
||||
<td class="small">@tourMode(tour)</td>
|
||||
<td data-icon="p"> @tour.clock.show | @tour.durationString</td>
|
||||
<td data-icon="r"> @tour.playerRatio</td>
|
||||
|
|
|
@ -25,11 +25,17 @@
|
|||
@{ tour.rated.fold(trans.rated(), trans.casual()) }
|
||||
<br /><br />
|
||||
@trans.duration(): @tour.minutes minutes
|
||||
@if(tour.isRunning && (tour isActive ctx.me)) {
|
||||
@if(tour.isRunning) {
|
||||
<br /><br />
|
||||
@if(tour isActive ctx.me) {
|
||||
<form action="@routes.Tournament.withdraw(tour.id)" method="POST">
|
||||
<button type="submit" class="submit button strong" data-icon="b"> @trans.withdraw()</button>
|
||||
</form>
|
||||
} else {
|
||||
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
|
||||
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button @if(!tour.isOpen) { shy }"> @trans.join()</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@tour.winner.filter(_ => tour.isFinished).map { winner =>
|
||||
<br /><br />
|
||||
|
|
|
@ -25,7 +25,7 @@ final class Env(
|
|||
private val settings = new {
|
||||
val CollectionTournament = config getString "collection.tournament"
|
||||
val MessageTtl = config duration "message.ttl"
|
||||
val EnterableCacheTtl = config duration "enterable.cache.ttl"
|
||||
val CreatedCacheTtl = config duration "created.cache.ttl"
|
||||
val UidTimeout = config duration "uid.timeout"
|
||||
val SocketTimeout = config duration "socket.timeout"
|
||||
val SocketName = config getString "socket.name"
|
||||
|
@ -76,8 +76,8 @@ final class Env(
|
|||
def version(tourId: String): Fu[Int] =
|
||||
socketHub ? Ask(tourId, GetVersion) mapTo manifest[Int]
|
||||
|
||||
private val enterable =
|
||||
lila.memo.AsyncCache.single(TournamentRepo.enterable, timeToLive = EnterableCacheTtl)
|
||||
val allCreatedSorted =
|
||||
lila.memo.AsyncCache.single(TournamentRepo.allCreatedSorted, timeToLive = CreatedCacheTtl)
|
||||
|
||||
def cli = new lila.common.Cli {
|
||||
import tube.tournamentTube
|
||||
|
@ -92,7 +92,7 @@ final class Env(
|
|||
import scala.concurrent.duration._
|
||||
|
||||
scheduler.message(2 seconds) {
|
||||
organizer -> actorApi.EnterableTournaments
|
||||
organizer -> actorApi.AllCreatedTournaments
|
||||
}
|
||||
|
||||
scheduler.message(3 seconds) {
|
||||
|
|
|
@ -19,7 +19,7 @@ private[tournament] final class Organizer(
|
|||
|
||||
def receive = {
|
||||
|
||||
case EnterableTournaments => TournamentRepo.unsortedEnterable foreach {
|
||||
case AllCreatedTournaments => TournamentRepo.allCreated foreach {
|
||||
_ foreach { tour =>
|
||||
tour schedule match {
|
||||
case None =>
|
||||
|
|
|
@ -16,8 +16,10 @@ private[tournament] case class Player(
|
|||
|
||||
def is(userId: String): Boolean = id == userId
|
||||
def is(user: User): Boolean = is(user.id)
|
||||
def is(other: Player): Boolean = is(other.id)
|
||||
|
||||
def doWithdraw = copy(withdraw = true)
|
||||
def unWithdraw = copy(withdraw = false)
|
||||
}
|
||||
|
||||
private[tournament] object Player {
|
||||
|
|
|
@ -51,6 +51,7 @@ sealed trait Tournament {
|
|||
def userIds = players map (_.id)
|
||||
def activeUserIds = players filter (_.active) map (_.id)
|
||||
def nbActiveUsers = players count (_.active)
|
||||
def withdrawnPlayers = players filterNot (_.active)
|
||||
def nbPlayers = players.size
|
||||
def minPlayers = data.minPlayers
|
||||
def playerRatio = if (scheduled) nbPlayers.toString else s"$nbPlayers/$minPlayers"
|
||||
|
@ -71,6 +72,20 @@ sealed trait Tournament {
|
|||
def isCreator(userId: String) = data.createdBy == userId
|
||||
}
|
||||
|
||||
sealed trait Enterable extends Tournament {
|
||||
|
||||
def withPlayers(s: Players): Enterable
|
||||
|
||||
def join(user: User, pass: Option[String]): Valid[Enterable]
|
||||
|
||||
def joinNew(user: User, pass: Option[String]): Valid[Enterable] = contains(user).fold(
|
||||
!!("User %s is already part of the tournament" format user.id),
|
||||
(pass != password).fold(
|
||||
!!("Invalid tournament password"),
|
||||
withPlayers(players :+ Player.make(user)).success
|
||||
))
|
||||
}
|
||||
|
||||
sealed trait StartedOrFinished extends Tournament {
|
||||
|
||||
def startedAt: DateTime
|
||||
|
@ -106,7 +121,7 @@ sealed trait StartedOrFinished extends Tournament {
|
|||
case class Created(
|
||||
id: String,
|
||||
data: Data,
|
||||
players: Players) extends Tournament {
|
||||
players: Players) extends Tournament with Enterable {
|
||||
|
||||
import data._
|
||||
|
||||
|
@ -137,26 +152,20 @@ case class Created(
|
|||
createdBy = data.createdBy,
|
||||
players = players)
|
||||
|
||||
def join(user: User, pass: Option[String]): Valid[Created] = contains(user).fold(
|
||||
!!("User %s is already part of the tournament" format user.id),
|
||||
(pass != password).fold(
|
||||
!!("Invalid tournament password"),
|
||||
withPlayers(players :+ Player.make(user)).success
|
||||
)
|
||||
)
|
||||
|
||||
def withdraw(userId: String): Valid[Created] = contains(userId).fold(
|
||||
withPlayers(players filterNot (_ is userId)).success,
|
||||
!!("User %s is not part of the tournament" format userId)
|
||||
)
|
||||
|
||||
private def withPlayers(s: Players) = copy(players = s)
|
||||
def withPlayers(s: Players) = copy(players = s)
|
||||
|
||||
def startIfReady = enoughPlayersToStart option start
|
||||
|
||||
def start = Started(id, data, DateTime.now, players, Nil)
|
||||
|
||||
def asScheduled = schedule map { Scheduled(this, _) }
|
||||
|
||||
def join(user: User, pass: Option[String]) = joinNew(user, pass)
|
||||
}
|
||||
|
||||
case class Scheduled(tour: Created, schedule: Schedule) {
|
||||
|
@ -166,14 +175,14 @@ case class Scheduled(tour: Created, schedule: Schedule) {
|
|||
def overlaps(other: Scheduled) = interval overlaps other.interval
|
||||
}
|
||||
|
||||
case class Enterable(tours: List[Created], scheduled: List[Created])
|
||||
case class EnterableTournaments(tours: List[Created], scheduled: List[Created])
|
||||
|
||||
case class Started(
|
||||
id: String,
|
||||
data: Data,
|
||||
startedAt: DateTime,
|
||||
players: Players,
|
||||
pairings: List[Pairing]) extends StartedOrFinished {
|
||||
pairings: List[Pairing]) extends StartedOrFinished with Enterable {
|
||||
|
||||
override def isRunning = true
|
||||
|
||||
|
@ -218,17 +227,29 @@ case class Started(
|
|||
!!("User %s is not part of the tournament" format userId)
|
||||
)
|
||||
|
||||
def withPlayers(s: Players) = copy(players = s)
|
||||
|
||||
def quickLossStreak(user: String): Boolean = {
|
||||
userPairings(user) takeWhile { pair => (pair lostBy user) && pair.quickLoss }
|
||||
}.size >= 3
|
||||
|
||||
private def userPairings(user: String) = pairings filter (_ contains user)
|
||||
|
||||
private def withPlayers(s: Players) = copy(players = s)
|
||||
|
||||
private def refreshPlayers = withPlayers(Player refresh this)
|
||||
|
||||
def encode = refreshPlayers.encode(Status.Started)
|
||||
|
||||
def join(user: User, pass: Option[String]) = joinNew(user, pass) orElse joinBack(user, pass)
|
||||
|
||||
private def joinBack(user: User, pass: Option[String]) = withdrawnPlayers.find(_ is user) match {
|
||||
case None => !!("User %s is already part of the tournament" format user.id)
|
||||
case Some(player) => (pass != password).fold(
|
||||
!!("Invalid tournament password"),
|
||||
withPlayers(players map {
|
||||
case p if p is player => p.unWithdraw
|
||||
case p => p
|
||||
}).success)
|
||||
}
|
||||
}
|
||||
|
||||
case class Finished(
|
||||
|
@ -263,6 +284,9 @@ object Tournament {
|
|||
private[tournament] val tube: JsTube[Tournament] =
|
||||
JsTube(Reads(reader(_.decode)), writer)
|
||||
|
||||
private[tournament] val enterableTube: JsTube[Enterable] =
|
||||
JsTube(Reads(reader(_.enterable)), writer)
|
||||
|
||||
private[tournament] val createdTube: JsTube[Created] =
|
||||
JsTube(Reads(reader(_.created)), writer)
|
||||
|
||||
|
@ -354,6 +378,8 @@ private[tournament] case class RawTournament(
|
|||
players = players,
|
||||
pairings = decodePairings)
|
||||
|
||||
def enterable: Option[Enterable] = created orElse started
|
||||
|
||||
private def data = Data(
|
||||
name,
|
||||
clock,
|
||||
|
|
|
@ -97,7 +97,7 @@ private[tournament] final class TournamentApi(
|
|||
finished.players.filter(_.score > 0).map(p => UserRepo.incToints(p.id)(p.score)).sequenceFu inject finished
|
||||
}, fuccess(started))
|
||||
|
||||
def join(tour: Created, me: User, password: Option[String]): Funit =
|
||||
def join(tour: Enterable, me: User, password: Option[String]): Funit =
|
||||
(tour.join(me, password)).future flatMap { tour2 =>
|
||||
TournamentRepo withdraw me.id flatMap { withdrawIds =>
|
||||
$update(tour2) >>-
|
||||
|
@ -144,7 +144,7 @@ private[tournament] final class TournamentApi(
|
|||
.filter(_ => List(chess.Status.Timeout, chess.Status.Outoftime) contains game.status)
|
||||
|
||||
private def lobbyReload {
|
||||
TournamentRepo.enterable foreach { tours =>
|
||||
TournamentRepo.allCreatedSorted foreach { tours =>
|
||||
renderer ? TournamentTable(tours) map {
|
||||
case view: play.api.templates.Html => ReloadTournaments(view.body)
|
||||
} pipeToSelection lobby
|
||||
|
|
|
@ -19,6 +19,10 @@ object TournamentRepo {
|
|||
private def asFinished(tour: Tournament): Option[Finished] = tour.some collect {
|
||||
case t: Finished => t
|
||||
}
|
||||
private def asEnterable(tour: Tournament): Option[Enterable] = tour.some collect {
|
||||
case t: Created => t
|
||||
case t: Started => t
|
||||
}
|
||||
|
||||
def byId(id: String): Fu[Option[Tournament]] = $find byId id
|
||||
|
||||
|
@ -27,6 +31,8 @@ object TournamentRepo {
|
|||
|
||||
def createdById(id: String): Fu[Option[Created]] = byIdAs(id, asCreated)
|
||||
|
||||
def enterableById(id: String): Fu[Option[Enterable]] = byIdAs(id, asEnterable)
|
||||
|
||||
def startedById(id: String): Fu[Option[Started]] = byIdAs(id, asStarted)
|
||||
|
||||
def createdByIdAndCreator(id: String, userId: String): Fu[Option[Created]] =
|
||||
|
@ -51,7 +57,7 @@ object TournamentRepo {
|
|||
limit
|
||||
) map { _.map(asFinished).flatten }
|
||||
|
||||
private val enterableQuery = $query(Json.obj(
|
||||
private val allCreatedQuery = $query(Json.obj(
|
||||
"status" -> Status.Created.id,
|
||||
"password" -> $exists(false)
|
||||
) ++ $or(Seq(
|
||||
|
@ -59,11 +65,11 @@ object TournamentRepo {
|
|||
Json.obj("schedule.at" -> $lt($date(DateTime.now plusMinutes 30)))
|
||||
)))
|
||||
|
||||
def enterable: Fu[List[Created]] = $find(
|
||||
enterableQuery sort BSONDocument("schedule.at" -> 1, "createdAt" -> 1)
|
||||
def allCreatedSorted: Fu[List[Created]] = $find(
|
||||
allCreatedQuery sort BSONDocument("schedule.at" -> 1, "createdAt" -> 1)
|
||||
) map { _.map(asCreated).flatten }
|
||||
|
||||
def unsortedEnterable: Fu[List[Created]] = $find(enterableQuery) map { _.map(asCreated).flatten }
|
||||
def allCreated: Fu[List[Created]] = $find(allCreatedQuery) map { _.map(asCreated).flatten }
|
||||
|
||||
def scheduled: Fu[List[Created]] = $find(
|
||||
$query(Json.obj(
|
||||
|
|
|
@ -31,7 +31,7 @@ case class Joining(userId: String)
|
|||
case class Connected(enumerator: JsEnumerator, member: Member)
|
||||
|
||||
// organizer
|
||||
private[tournament] case object EnterableTournaments
|
||||
private[tournament] case object AllCreatedTournaments
|
||||
private[tournament] case object StartedTournaments
|
||||
case class RemindTournaments(tours: List[Started])
|
||||
case class RemindTournament(tour: Started)
|
||||
|
|
|
@ -13,6 +13,7 @@ package object tournament extends PackageObject with WithPlay with WithSocket{
|
|||
implicit lazy val anyTube = tournamentTube
|
||||
implicit lazy val createdTube = Tournament.createdTube inColl Env.current.tournamentColl
|
||||
implicit lazy val startedTube = Tournament.startedTube inColl Env.current.tournamentColl
|
||||
implicit lazy val enterableTube = Tournament.enterableTube inColl Env.current.tournamentColl
|
||||
implicit lazy val finishedTube = Tournament.finishedTube inColl Env.current.tournamentColl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1106,9 +1106,16 @@ a.button,
|
|||
.ui-widget-content {
|
||||
border-radius: 2px;
|
||||
}
|
||||
.button.shy,
|
||||
a.button.shy {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
font-weight: normal;
|
||||
}
|
||||
.button:hover,
|
||||
a.button:hover,
|
||||
.ui-state-hover {
|
||||
border-color: #e2e2e2;
|
||||
background: rgb(253, 253, 253);
|
||||
background: -moz-linear-gradient(top, rgba(253, 253, 253, 1) 0%, rgba(248, 248, 248, 1) 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(253, 253, 253, 1)), color-stop(100%, rgba(248, 248, 248, 1)));
|
||||
|
|
Loading…
Reference in a new issue