work on tournament api
parent
f54a24c70f
commit
f94e9a8115
|
@ -3,7 +3,7 @@ package actor
|
|||
|
||||
import akka.actor._
|
||||
|
||||
import play.api.templates._
|
||||
import play.api.templates.Html
|
||||
import views.{ html => V }
|
||||
|
||||
private[app] final class Renderer extends Actor {
|
||||
|
@ -15,5 +15,13 @@ private[app] final class Renderer extends Actor {
|
|||
|
||||
case lila.notification.actorApi.RenderNotification(id, from, body) =>
|
||||
V.notification.view(id, from)(Html(body))
|
||||
|
||||
case lila.tournament.actorApi.RemindTournament(tournament) =>
|
||||
// TODO
|
||||
// V.notification.view(id, from)(Html(body))
|
||||
|
||||
case lila.tournament.actorApi.TournamentTable(tour) =>
|
||||
// TODO
|
||||
// V.tournament.createdTable(tours)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ private[app] final class Router(
|
|||
case Watcher(gameId, color) ⇒ sender ! R.Round.watcher(gameId, color).url
|
||||
case Replay(gameId, color) ⇒ sender ! R.Analyse.replay(gameId, color).url
|
||||
case Pgn(gameId) ⇒ sender ! R.Analyse.pgn(gameId)
|
||||
case Tourney(tourId) ⇒ // TODO sender ! R.tournament.show(tourId)
|
||||
}
|
||||
|
||||
private lazy val noLangBaseUrl = protocol + I18nDomain(domain).commonDomain
|
||||
|
|
|
@ -26,6 +26,10 @@ package captcha {
|
|||
package lobby {
|
||||
case class TimelineEntry(rendered: String)
|
||||
case class Censor(username: String)
|
||||
case class Talk(u: String, txt: String)
|
||||
case class SysTalk(txt: String)
|
||||
case class UnTalk(r: scala.util.matching.Regex)
|
||||
case class ReloadTournaments(html: String)
|
||||
}
|
||||
|
||||
package game {
|
||||
|
@ -44,6 +48,7 @@ package router {
|
|||
case class Watcher(gameId: String, color: String)
|
||||
case class Replay(gameId: String, color: String)
|
||||
case class Pgn(gameId: String)
|
||||
case class Tourney(tourId: String)
|
||||
}
|
||||
|
||||
package forum {
|
||||
|
|
|
@ -9,6 +9,7 @@ import actorApi._
|
|||
import lila.common.PimpedJson._
|
||||
import lila.socket.Handler
|
||||
import lila.socket.actorApi.{ Connected ⇒ _, _ }
|
||||
import lila.hub.actorApi.lobby._
|
||||
import lila.user.{ User, Context }
|
||||
import lila.security.Flood
|
||||
import makeTimeout.short
|
||||
|
|
|
@ -26,7 +26,6 @@ object Member {
|
|||
}
|
||||
|
||||
case class Connected(enumerator: JsEnumerator, member: Member)
|
||||
case class ReloadTournaments(html: String)
|
||||
case class WithHooks(op: Iterable[String] ⇒ Unit)
|
||||
case class AddHook(hook: Hook)
|
||||
case class RemoveHook(hook: Hook)
|
||||
|
@ -36,6 +35,3 @@ case class Join(
|
|||
uid: String,
|
||||
user: Option[User],
|
||||
hookOwnerId: Option[String])
|
||||
case class Talk(u: String, txt: String)
|
||||
case class SysTalk(txt: String)
|
||||
case class UnTalk(r: util.matching.Regex)
|
||||
|
|
|
@ -7,9 +7,7 @@ import lila.db.api._
|
|||
|
||||
import akka.actor.ActorRef
|
||||
|
||||
private[round] final class Meddler(
|
||||
finisher: Finisher,
|
||||
socketHub: ActorRef) {
|
||||
final class Meddler(finisher: Finisher, socketHub: ActorRef) {
|
||||
|
||||
def forceAbort(id: String) {
|
||||
$find.byId(id) foreach {
|
||||
|
|
|
@ -2,7 +2,7 @@ package lila.site
|
|||
|
||||
import actorApi._
|
||||
import lila.socket._
|
||||
import lila.socket.actorApi.Connected
|
||||
import lila.socket.actorApi.{ Connected, SendToFlag }
|
||||
|
||||
import akka.actor._
|
||||
import play.api.libs.json._
|
||||
|
|
|
@ -13,6 +13,4 @@ case class Member(
|
|||
def hasFlag(f: String) = flag zmap (f ==)
|
||||
}
|
||||
|
||||
case class SendToFlag(flag: String, message: JsObject)
|
||||
|
||||
case class Join(uid: String, userId: Option[String], flag: Option[String])
|
||||
|
|
|
@ -23,3 +23,5 @@ case class Resync(uid: String)
|
|||
case class GetSocket(id: String)
|
||||
case object GetNbSockets
|
||||
case object SocketTimeout
|
||||
|
||||
case class SendToFlag(flag: String, message: JsObject)
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package lila.tournament
|
||||
|
||||
import chess.Color
|
||||
import lila.game.{ Game, Player ⇒ GamePlayer, GameRepo, Pov, PovRef, Source }
|
||||
import lila.user.{ User, UserRepo }
|
||||
import lila.round.Meddler
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.{ ActorRef, ActorSystem }
|
||||
|
||||
final class GameJoiner(
|
||||
roundMeddler: Meddler,
|
||||
timelinePush: ActorRef,
|
||||
system: ActorSystem) {
|
||||
|
||||
private val secondsToMove = 20
|
||||
|
||||
def apply(tour: Started)(pairing: Pairing): Fu[Game] = for {
|
||||
user1 ← getUser(pairing.user1)
|
||||
user2 ← getUser(pairing.user2)
|
||||
game = Game.make(
|
||||
game = chess.Game(
|
||||
board = chess.Board init tour.variant,
|
||||
clock = tour.clock.chessClock.some
|
||||
),
|
||||
ai = None,
|
||||
whitePlayer = GamePlayer.white withUser user1,
|
||||
blackPlayer = GamePlayer.black withUser user2,
|
||||
creatorColor = chess.Color.White,
|
||||
mode = tour.mode,
|
||||
variant = tour.variant,
|
||||
source = Source.Tournament,
|
||||
pgnImport = None
|
||||
).withTournamentId(tour.id)
|
||||
.withId(pairing.gameId)
|
||||
.start
|
||||
.startClock(2)
|
||||
_ ← (GameRepo insertDenormalized game) >>-
|
||||
(timelinePush ! game) >>-
|
||||
scheduleIdleCheck(PovRef(game.id, Color.White), secondsToMove)
|
||||
} yield game
|
||||
|
||||
private def getUser(username: String): Fu[User] =
|
||||
UserRepo named username flatMap {
|
||||
_.fold(fufail[User]("No user named " + username))(fuccess)
|
||||
}
|
||||
|
||||
private def scheduleIdleCheck(povRef: PovRef, in: Int) {
|
||||
system.scheduler.scheduleOnce(in seconds)(idleCheck(povRef))
|
||||
}
|
||||
|
||||
private def idleCheck(povRef: PovRef) {
|
||||
GameRepo pov povRef foreach {
|
||||
_.filter(_.game.playable) foreach { pov ⇒
|
||||
pov.game.playerHasMoved(pov.color).fold(
|
||||
(pov.color.white && !pov.game.playerHasMoved(Color.Black)) ?? {
|
||||
scheduleIdleCheck(!pov.ref, pov.game.lastMoveTime.fold(secondsToMove) { lmt ⇒
|
||||
lmt - nowSeconds + secondsToMove
|
||||
})
|
||||
},
|
||||
roundMeddler resign pov)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package lila.tournament
|
||||
|
||||
import actorApi._
|
||||
import lila.hub.actorApi.SendTos
|
||||
import makeTimeout.short
|
||||
|
||||
import akka.actor._
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import play.api.libs.json.Json
|
||||
import play.api.templates.Html
|
||||
|
||||
private[tournament] final class Reminder(
|
||||
sockets: List[ActorRef],
|
||||
renderer: ActorRef) extends Actor {
|
||||
|
||||
def receive = {
|
||||
|
||||
case RemindTournaments(tours) ⇒ tours foreach { tour ⇒
|
||||
renderer ? RemindTournament(tour) foreach {
|
||||
case html: Html ⇒ {
|
||||
val msg = SendTos(tour.activeUserIds.toSet, Json.obj(
|
||||
"t" -> "tournamentReminder",
|
||||
"d" -> Json.obj(
|
||||
"id" -> tour.id,
|
||||
"html" -> html.toString
|
||||
)))
|
||||
sockets foreach { _ ! msg }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -222,20 +222,29 @@ object Tournament {
|
|||
import lila.db.Tube
|
||||
import play.api.libs.json._
|
||||
|
||||
private[tournament] lazy val tube = Tube(
|
||||
reader = Reads[Tournament](js ⇒
|
||||
~(for {
|
||||
private def reader[T <: Tournament](decode: RawTournament ⇒ Option[T])(js: JsValue): JsResult[T] = ~(for {
|
||||
obj ← js.asOpt[JsObject]
|
||||
rawTour ← RawTournament.tube.read(obj).asOpt
|
||||
tour ← rawTour.decode
|
||||
} yield JsSuccess(tour): JsResult[Tournament])
|
||||
),
|
||||
writer = Writes[Tournament](tour ⇒
|
||||
tour ← decode(rawTour)
|
||||
} yield JsSuccess(tour): JsResult[T])
|
||||
|
||||
private lazy val writer = Writes[Tournament](tour ⇒
|
||||
RawTournament.tube.write(tour.encode) getOrElse JsUndefined("[db] Can't write tournament " + tour.id)
|
||||
)
|
||||
)
|
||||
|
||||
def apply(
|
||||
private[tournament] lazy val tube: Tube[Tournament] =
|
||||
Tube(Reads(reader(_.decode)), writer)
|
||||
|
||||
private[tournament] lazy val createdTube: Tube[Created] =
|
||||
Tube(Reads(reader(_.created)), writer)
|
||||
|
||||
private[tournament] lazy val startedTube: Tube[Started] =
|
||||
Tube(Reads(reader(_.started)), writer)
|
||||
|
||||
private[tournament] lazy val finishedTube: Tube[Finished] =
|
||||
Tube(Reads(reader(_.finished)), writer)
|
||||
|
||||
def make(
|
||||
createdBy: User,
|
||||
clock: TournamentClock,
|
||||
minutes: Int,
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package lila.tournament
|
||||
|
||||
import actorApi._
|
||||
import tube.roomTube
|
||||
import tube.tournamentTubes._
|
||||
import lila.db.api._
|
||||
import chess.{ Mode, Variant }
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
import lila.hub.actorApi.lobby.{ SysTalk, UnTalk, ReloadTournaments }
|
||||
import lila.hub.actorApi.router.Tourney
|
||||
import lila.socket.actorApi.SendToFlag
|
||||
import makeTimeout.short
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
import scalaz.NonEmptyList
|
||||
import akka.actor.ActorRef
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import play.api.libs.json._
|
||||
|
||||
private[tournament] final class TournamentApi(
|
||||
joiner: GameJoiner,
|
||||
router: ActorRef,
|
||||
renderer: ActorRef,
|
||||
socketHub: ActorRef,
|
||||
site: ActorRef,
|
||||
lobby: ActorRef,
|
||||
roundMeddler: lila.round.Meddler,
|
||||
incToints: String ⇒ Int ⇒ Funit) {
|
||||
|
||||
def makePairings(tour: Started, pairings: NonEmptyList[Pairing]): Funit =
|
||||
(tour addPairings pairings) |> { tour2 ⇒
|
||||
$update(tour2) >> (pairings map joiner(tour2)).sequence
|
||||
} map {
|
||||
_.list foreach { game ⇒
|
||||
game.tournamentId foreach { tid ⇒
|
||||
socketHub ! Forward(tid, StartGame(game))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createTournament(setup: TournamentSetup, me: User): Fu[Created] =
|
||||
TournamentRepo withdraw me.id flatMap { withdrawIds ⇒
|
||||
val created = Tournament.make(
|
||||
createdBy = me,
|
||||
clock = TournamentClock(setup.clockTime * 60, setup.clockIncrement),
|
||||
minutes = setup.minutes,
|
||||
minPlayers = setup.minPlayers,
|
||||
mode = Mode orDefault ~setup.mode,
|
||||
variant = Variant orDefault setup.variant)
|
||||
$insert(created) >>
|
||||
(withdrawIds map socket.reload).sequence >>
|
||||
reloadSiteSocket >>
|
||||
lobbyReload >>
|
||||
sendLobbyMessage(created) inject created
|
||||
}
|
||||
|
||||
def startIfReady(created: Created): Option[Fu[Unit]] = created.startIfReady map doStart
|
||||
|
||||
def earlyStart(created: Created): Option[Fu[Unit]] =
|
||||
created.readyToEarlyStart option doStart(created.start)
|
||||
|
||||
private def doStart(started: Started): Fu[Unit] =
|
||||
$update(started) >> (socket start started.id) >> reloadSiteSocket >> lobbyReload
|
||||
|
||||
def wipeEmpty(created: Created): Fu[Unit] = (for {
|
||||
_ ← $remove(created)
|
||||
_ ← $remove[Room](created.id)
|
||||
_ ← reloadSiteSocket
|
||||
_ ← lobbyReload
|
||||
_ ← removeLobbyMessage(created)
|
||||
} yield ()) doIf created.isEmpty
|
||||
|
||||
def finish(started: Started): Fu[Tournament] = started.readyToFinish.fold({
|
||||
val pairingsToAbort = started.playingPairings
|
||||
val finished = started.finish
|
||||
for {
|
||||
_ ← $update(finished)
|
||||
_ ← socket reloadPage finished.id
|
||||
_ ← reloadSiteSocket
|
||||
_ ← (pairingsToAbort map (_.gameId) map roundMeddler.forceAbort).sequence
|
||||
_ ← finished.players.filter(_.score > 0).map(p ⇒ incToints(p.id)(p.score)).sequence
|
||||
} yield finished
|
||||
}, fuccess(started))
|
||||
|
||||
def join(tour: Created, me: User): Funit =
|
||||
(tour join me).future flatMap { tour2 ⇒
|
||||
TournamentRepo withdraw me.id flatMap { withdrawIds ⇒
|
||||
$update(tour2) >>-
|
||||
(socketHub ! Forward(tour.id, Joining(me.id))) >>-
|
||||
((tour.id :: withdrawIds) foreach { tourId ⇒
|
||||
socketHub ! Forward(tourId, Reload)
|
||||
}) >>-
|
||||
reloadSiteSocket >>-
|
||||
lobbyReload
|
||||
}
|
||||
}
|
||||
|
||||
def withdraw(tour: Tournament, userId: String): Funit = tour match {
|
||||
case created: Created ⇒ (created withdraw userId).fold(
|
||||
err ⇒ fufail(err.shows),
|
||||
tour2 ⇒ $update(tour2) >> (socket reload tour2.id) >> reloadSiteSocket >> lobbyReload
|
||||
)
|
||||
case started: Started ⇒ (started withdraw userId).fold(
|
||||
err ⇒ fufail(err.shows),
|
||||
tour2 ⇒ $update(tour2) >>
|
||||
~(tour2 userCurrentPov userId map roundMeddler.resign) >>
|
||||
(socket reload tour2.id) >>
|
||||
reloadSiteSocket
|
||||
)
|
||||
case finished: Finished ⇒ fufail("Cannot withdraw from finished tournament " + finished.id)
|
||||
}
|
||||
|
||||
def finishGame(game: Game): Fu[Option[Tournament]] = for {
|
||||
tourOption ← ~(game.tournamentId map TournamentRepo.startedById)
|
||||
result ← ~(tourOption.filter(_ ⇒ game.finished).map(tour ⇒ {
|
||||
val tour2 = tour.updatePairing(game.id, _.finish(game.status, game.winnerUserId, game.turns))
|
||||
$update(tour2) >> tripleQuickLossWithdraw(tour2, game.loserUserId) inject tour2.some
|
||||
}))
|
||||
} yield result
|
||||
|
||||
private def tripleQuickLossWithdraw(tour: Started, loser: Option[String]): Funit =
|
||||
loser.filter(tour.quickLossStreak).zmap(withdraw(tour, _))
|
||||
|
||||
private def userIdWhoLostOnTimeWithoutMoving(game: Game): Option[String] =
|
||||
game.playerWhoDidNotMove
|
||||
.flatMap(_.userId)
|
||||
.filter(_ ⇒ List(chess.Status.Timeout, chess.Status.Outoftime) contains game.status)
|
||||
|
||||
private def lobbyReload {
|
||||
TournamentRepo.created foreach { tours ⇒
|
||||
renderer ? TournamentTable(tours) map {
|
||||
case view: play.api.templates.Html ⇒ ReloadTournaments(view)
|
||||
} pipeTo lobby
|
||||
}
|
||||
}
|
||||
|
||||
private val reloadMessage = Json.obj("t" -> "reload", "d" -> JsNull)
|
||||
private def reloadSiteSocket {
|
||||
site ! SendToFlag("tournament", reloadMessage)
|
||||
}
|
||||
|
||||
private def sendLobbyMessage(tour: Created) {
|
||||
router ? Tourney(tour.id) map {
|
||||
case url: String ⇒ SysTalk(
|
||||
"""<a href="%s">%s tournament created</a>""".format(url, tour.name)
|
||||
)
|
||||
} pipeTo lobby
|
||||
}
|
||||
|
||||
private def removeLobbyMessage(tour: Created) {
|
||||
lobby ! UnTalk("%s tournament created".format(tour.name).r)
|
||||
}
|
||||
|
||||
}
|
|
@ -48,3 +48,5 @@ case object StartPairings
|
|||
case class StartPairing(tour: Started)
|
||||
case class GetTournamentUserIds(tournamentId: String)
|
||||
case class RemindTournaments(tours: List[Started])
|
||||
case class RemindTournament(tour: Started)
|
||||
case class TournamentTable(tours: List[Created])
|
||||
|
|
|
@ -9,6 +9,13 @@ package object tournament extends PackageObject with WithPlay with WithSocket{
|
|||
|
||||
private[tournament] implicit lazy val tournamentTube = Tournament.tube inColl Env.current.tournamentColl
|
||||
|
||||
private[tournament] object tournamentTubes {
|
||||
implicit lazy val any = tournamentTube
|
||||
implicit lazy val created = Tournament.createdTube inColl Env.current.tournamentColl
|
||||
implicit lazy val started = Tournament.startedTube inColl Env.current.tournamentColl
|
||||
implicit lazy val finished = Tournament.finishedTube inColl Env.current.tournamentColl
|
||||
}
|
||||
|
||||
private[tournament] implicit lazy val roomTube = Room.tube inColl Env.current.roomColl
|
||||
}
|
||||
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
package lila.app
|
||||
package tournament
|
||||
|
||||
import chess.Color
|
||||
import game.{ DbGame, DbPlayer, GameRepo, Pov, PovRef, Source }
|
||||
import user.User
|
||||
import round.Meddler
|
||||
|
||||
import scalaz.effects._
|
||||
import play.api.libs.concurrent._
|
||||
import play.api.Play.current
|
||||
import play.api.libs.concurrent.Execution.Implicits._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
final class GameJoiner(
|
||||
gameRepo: GameRepo,
|
||||
roundMeddler: Meddler,
|
||||
timelinePush: DbGame ⇒ IO[Unit],
|
||||
getUser: String ⇒ IO[Option[User]]) {
|
||||
|
||||
private val secondsToMove = 20
|
||||
|
||||
def apply(tour: Started)(pairing: Pairing): IO[DbGame] = for {
|
||||
user1 ← getUser(pairing.user1) map (_ err "No such user " + pairing)
|
||||
user2 ← getUser(pairing.user2) map (_ err "No such user " + pairing)
|
||||
game = DbGame(
|
||||
game = chess.Game(
|
||||
board = chess.Board init tour.variant,
|
||||
clock = tour.clock.chessClock.some
|
||||
),
|
||||
ai = None,
|
||||
whitePlayer = DbPlayer.white withUser user1,
|
||||
blackPlayer = DbPlayer.black withUser user2,
|
||||
creatorColor = chess.Color.White,
|
||||
mode = tour.mode,
|
||||
variant = tour.variant,
|
||||
source = Source.Tournament,
|
||||
pgnImport = None
|
||||
).withTournamentId(tour.id)
|
||||
.withId(pairing.gameId)
|
||||
.start
|
||||
.startClock(2)
|
||||
_ ← gameRepo insert game
|
||||
_ ← gameRepo denormalize game
|
||||
_ ← timelinePush(game)
|
||||
_ ← scheduleIdleCheck(PovRef(game.id, Color.White), secondsToMove)
|
||||
} yield game
|
||||
|
||||
private def scheduleIdleCheck(povRef: PovRef, in: Int) = io {
|
||||
Akka.system.scheduler.scheduleOnce(in seconds)(idleCheck(povRef))
|
||||
} map (_ ⇒ ())
|
||||
|
||||
private def idleCheck(povRef: PovRef) {
|
||||
(for {
|
||||
povOption ← gameRepo pov povRef
|
||||
_ ← ~(povOption filter (_.game.playable) map idleResult)
|
||||
} yield ()).unsafePerformIO
|
||||
}
|
||||
|
||||
private def idleResult(pov: Pov): IO[Unit] = {
|
||||
val idle = !pov.game.playerHasMoved(pov.color)
|
||||
idle.fold(
|
||||
roundMeddler resign pov,
|
||||
(pov.color.white && !pov.game.playerHasMoved(Color.Black)).fold(
|
||||
scheduleIdleCheck(!pov.ref, pov.game.lastMoveTime.fold(secondsToMove) { lmt ⇒
|
||||
lmt - nowSeconds + secondsToMove
|
||||
}),
|
||||
io()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package lila.app
|
||||
package tournament
|
||||
|
||||
import akka.actor._
|
||||
import akka.actor.ReceiveTimeout
|
||||
import scala.concurrent.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import scala.concurrent.{ Future, Promise }
|
||||
import play.api.libs.concurrent._
|
||||
import play.api.Play.current
|
||||
import play.api.libs.json._
|
||||
|
||||
import socket.SendTos
|
||||
|
||||
private[tournament] final class Reminder(hubNames: List[String]) extends Actor {
|
||||
|
||||
lazy val hubRefs = hubNames map { name ⇒ Akka.system.actorFor("/user/" + name) }
|
||||
|
||||
implicit val timeout = Timeout(1 second)
|
||||
|
||||
def receive = {
|
||||
|
||||
case RemindTournaments(tours) ⇒ tours foreach { tour =>
|
||||
val msg = SendTos(tour.activeUserIds.toSet, JsObject(Seq(
|
||||
"t" -> JsString("tournamentReminder"),
|
||||
"d" -> JsObject(Seq(
|
||||
"id" -> JsString(tour.id),
|
||||
"html" -> JsString(views.html.tournament.reminder(tour).toString)
|
||||
))
|
||||
)))
|
||||
hubRefs foreach { _ ! msg }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package lila.app
|
||||
package tournament
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
import scalaz.effects._
|
||||
import scalaz.{ NonEmptyList, Success, Failure }
|
||||
import play.api.libs.json._
|
||||
|
||||
import chess.{ Mode, Variant }
|
||||
import controllers.routes
|
||||
import game.DbGame
|
||||
import user.User
|
||||
import lobby.{ Socket ⇒ LobbySocket }
|
||||
|
||||
private[tournament] final class TournamentApi(
|
||||
repo: TournamentRepo,
|
||||
roomRepo: RoomRepo,
|
||||
joiner: GameJoiner,
|
||||
socket: Socket,
|
||||
siteSocket: site.Socket,
|
||||
lobbySocket: LobbySocket,
|
||||
roundMeddler: round.Meddler,
|
||||
incToints: String ⇒ Int ⇒ IO[Unit]) {
|
||||
|
||||
def makePairings(tour: Started, pairings: NonEmptyList[Pairing]): IO[Unit] =
|
||||
(tour addPairings pairings) |> { tour2 ⇒
|
||||
for {
|
||||
_ ← repo saveIO tour2
|
||||
games ← (pairings map joiner(tour)).sequence
|
||||
_ ← (games map socket.notifyPairing).sequence
|
||||
} yield ()
|
||||
}
|
||||
|
||||
def createTournament(setup: TournamentSetup, me: User): IO[Created] = for {
|
||||
withdrawIds ← repo withdraw me.id
|
||||
created = Tournament(
|
||||
createdBy = me,
|
||||
clock = TournamentClock(setup.clockTime * 60, setup.clockIncrement),
|
||||
minutes = setup.minutes,
|
||||
minPlayers = setup.minPlayers,
|
||||
mode = Mode orDefault ~setup.mode,
|
||||
variant = Variant orDefault setup.variant)
|
||||
_ ← repo saveIO created
|
||||
_ ← (withdrawIds map socket.reload).sequence
|
||||
_ ← reloadSiteSocket
|
||||
_ ← lobbyReload
|
||||
_ ← sendLobbyMessage(created)
|
||||
} yield created
|
||||
|
||||
def startIfReady(created: Created): Option[IO[Unit]] = created.startIfReady map doStart
|
||||
|
||||
def earlyStart(created: Created): Option[IO[Unit]] =
|
||||
created.readyToEarlyStart option doStart(created.start)
|
||||
|
||||
private def doStart(started: Started): IO[Unit] =
|
||||
(repo saveIO started) >> (socket start started.id) >> reloadSiteSocket >> lobbyReload
|
||||
|
||||
def wipeEmpty(created: Created): IO[Unit] = (for {
|
||||
_ ← repo removeIO created
|
||||
_ ← roomRepo removeIO created.id
|
||||
_ ← reloadSiteSocket
|
||||
_ ← lobbyReload
|
||||
_ ← removeLobbyMessage(created)
|
||||
} yield ()) doIf created.isEmpty
|
||||
|
||||
def finish(started: Started): IO[Tournament] = started.readyToFinish.fold({
|
||||
val pairingsToAbort = started.playingPairings
|
||||
val finished = started.finish
|
||||
for {
|
||||
_ ← repo saveIO finished
|
||||
_ ← socket reloadPage finished.id
|
||||
_ ← reloadSiteSocket
|
||||
_ ← (pairingsToAbort map (_.gameId) map roundMeddler.forceAbort).sequence
|
||||
_ ← finished.players.filter(_.score > 0).map(p ⇒ incToints(p.id)(p.score)).sequence
|
||||
} yield finished
|
||||
}, io(started))
|
||||
|
||||
def join(tour: Created, me: User): Valid[IO[Unit]] = for {
|
||||
tour2 ← tour join me
|
||||
} yield for {
|
||||
withdrawIds ← repo withdraw me.id
|
||||
_ ← repo saveIO tour2
|
||||
_ ← socket.notifyJoining(tour.id, me.id)
|
||||
_ ← ((tour.id :: withdrawIds) map socket.reload).sequence
|
||||
_ ← reloadSiteSocket
|
||||
_ ← lobbyReload
|
||||
} yield ()
|
||||
|
||||
def withdraw(tour: Tournament, userId: String): IO[Unit] = tour match {
|
||||
case created: Created ⇒ (created withdraw userId).fold(
|
||||
err ⇒ putStrLn(err.shows),
|
||||
tour2 ⇒ (repo saveIO tour2) >> (socket reload tour2.id) >> reloadSiteSocket >> lobbyReload
|
||||
)
|
||||
case started: Started ⇒ (started withdraw userId).fold(
|
||||
err ⇒ putStrLn(err.shows),
|
||||
tour2 ⇒ (repo saveIO tour2) >>
|
||||
~(tour2 userCurrentPov userId map roundMeddler.resign) >>
|
||||
(socket reload tour2.id) >>
|
||||
reloadSiteSocket
|
||||
)
|
||||
case finished: Finished ⇒ putStrLn("Cannot withdraw from finished tournament " + finished.id)
|
||||
}
|
||||
|
||||
def finishGame(game: DbGame): IO[Option[Tournament]] = for {
|
||||
tourOption ← ~(game.tournamentId map repo.startedById)
|
||||
result ← ~(tourOption.filter(_ ⇒ game.finished).map(tour ⇒ {
|
||||
val tour2 = tour.updatePairing(game.id, _.finish(game.status, game.winnerUserId, game.turns))
|
||||
repo saveIO tour2 inject tour2.some
|
||||
(repo saveIO tour2) >>
|
||||
tripleQuickLossWithdraw(tour2, game.loserUserId) inject tour2.some
|
||||
}))
|
||||
} yield result
|
||||
|
||||
private def tripleQuickLossWithdraw(tour: Started, loser: Option[String]): IO[Unit] =
|
||||
~loser.filter(tour.quickLossStreak).map(withdraw(tour, _))
|
||||
|
||||
private def userIdWhoLostOnTimeWithoutMoving(game: DbGame): Option[String] =
|
||||
game.playerWhoDidNotMove
|
||||
.flatMap(_.userId)
|
||||
.filter(_ ⇒ List(chess.Status.Timeout, chess.Status.Outoftime) contains game.status)
|
||||
|
||||
private def lobbyReload = repo.created flatMap { tours ⇒
|
||||
lobbySocket reloadTournaments views.html.tournament.createdTable(tours).toString
|
||||
}
|
||||
|
||||
private val reloadMessage = JsObject(Seq("t" -> JsString("reload"), "d" -> JsNull))
|
||||
private def sendToSiteSocket(message: JsObject) = io {
|
||||
siteSocket.sendToFlag("tournament", message)
|
||||
}
|
||||
private val reloadSiteSocket = sendToSiteSocket(reloadMessage)
|
||||
|
||||
private def sendLobbyMessage(tour: Created) = lobbySocket sysTalk {
|
||||
"""<a href="%s">%s tournament created</a>""".format(routes.Tournament.show(tour.id), tour.name)
|
||||
}
|
||||
|
||||
private def removeLobbyMessage(tour: Created) = lobbySocket unTalk {
|
||||
("%s tournament created" format tour.name).r
|
||||
}
|
||||
|
||||
}
|
1
todo
1
todo
|
@ -79,6 +79,7 @@ FIX TOUCHPAD SIGN IN http://en.lichess.org/forum/lichess-feedback/cant-sign-in#6
|
|||
chess variants https://github.com/ornicar/lila/issues/2://github.com/ornicar/lila/issues/25
|
||||
publish scalastic 0.90.0-thib
|
||||
stream game export
|
||||
show fen only after game is finished http://en.lichess.org/forum/lichess-feedback/please-disable-live-fen-notation?page=1
|
||||
|
||||
DEPLOY p21
|
||||
----------
|
||||
|
|
Loading…
Reference in New Issue