diff --git a/app/Preload.scala b/app/Preload.scala index ef4bb95c08..a333fe96ce 100644 --- a/app/Preload.scala +++ b/app/Preload.scala @@ -48,7 +48,7 @@ final class Preload( entries ← entryRepo.recent } yield Right(Map( "version" -> history.version, - "pool" -> renderHooks(hooks, myHook).pp, + "pool" -> renderHooks(hooks, myHook), "chat" -> (messages.reverse map (_.render)), "timeline" -> (entries.reverse map (_.render)) )) diff --git a/app/controllers/Lobby.scala b/app/controllers/Lobby.scala index 7cb5421927..d91ce8325b 100644 --- a/app/controllers/Lobby.scala +++ b/app/controllers/Lobby.scala @@ -18,6 +18,7 @@ object Lobby extends LilaController { def preloader = env.preloader def hookRepo = env.lobby.hookRepo def fisherman = env.lobby.fisherman + def joiner = env.setup.hookJoiner val home = Open { implicit ctx ⇒ renderHome(none).fold(identity, Ok(_)) @@ -35,7 +36,7 @@ object Lobby extends LilaController { myHook = myHook ).unsafePerformIO.bimap( url ⇒ Redirect(url), - preload ⇒ html.lobby.home(toJson(preload)) + preload ⇒ html.lobby.home(toJson(preload), myHook) ) def socket = WebSocket.async[JsValue] { implicit req ⇒ @@ -49,11 +50,22 @@ object Lobby extends LilaController { } def hook(ownerId: String) = Open { implicit ctx ⇒ - hookRepo.ownedHook(ownerId.pp).unsafePerformIO.pp.fold( + hookRepo.ownedHook(ownerId).unsafePerformIO.fold( hook ⇒ renderHome(hook.some).fold(identity, Ok(_)), Redirect(routes.Lobby.home)) } + def join(hookId: String) = Open { implicit ctx ⇒ + IORedirect { + val myHookId = get("cancel") + joiner(hookId, myHookId)(ctx.me) map { result ⇒ + result.fold( + _ ⇒ myHookId.fold(routes.Lobby.hook(_), routes.Lobby.home), + pov ⇒ routes.Round.player(pov.fullId)) + } + } + } + def cancel(ownerId: String) = Open { implicit ctx ⇒ IORedirect { for { @@ -63,8 +75,6 @@ object Lobby extends LilaController { } } - def join(hookId: String) = TODO - //def join(gameId: String, color: String) = Action { implicit req ⇒ //FormValidIOk[LobbyJoinData](lobbyJoinForm)(join ⇒ //api.join(gameId, color, join._1, join._2, join._3, join._4) diff --git a/app/controllers/Round.scala b/app/controllers/Round.scala index 1367b36c70..1188fdc77d 100644 --- a/app/controllers/Round.scala +++ b/app/controllers/Round.scala @@ -20,7 +20,7 @@ object Round extends LilaController { private val hand = env.round.hand private val messenger = env.round.messenger private val rematcher = env.setup.rematcher - private val joiner = env.setup.joiner + private val joiner = env.setup.friendJoiner def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req ⇒ implicit val ctx = reqToCtx(req) diff --git a/app/core/CoreEnv.scala b/app/core/CoreEnv.scala index 9f6af20d1f..a74221b221 100644 --- a/app/core/CoreEnv.scala +++ b/app/core/CoreEnv.scala @@ -38,12 +38,13 @@ final class CoreEnv private (application: Application, val settings: Settings) { settings = settings, mongodb = mongodb.apply _, gameRepo = game.gameRepo, + hookRepo = lobby.hookRepo, fisherman = lobby.fisherman, userRepo = user.userRepo, timelinePush = timeline.push.apply, roundMessenger = round.messenger, ai = ai.ai, - dbRef = user.userRepo.dbRef) + userDbRef = user.userRepo.dbRef) lazy val timeline = new lila.timeline.TimelineEnv( settings = settings, diff --git a/app/core/Cron.scala b/app/core/Cron.scala index bcde385605..131a01395c 100644 --- a/app/core/Cron.scala +++ b/app/core/Cron.scala @@ -28,9 +28,9 @@ object Cron { } } - message(1 seconds) { - env.monitor.reporting -> monitor.Update(env) - } + //message(1 seconds) { + //env.monitor.reporting -> monitor.Update(env) + //} message(1 second) { env.lobby.hub -> lobby.WithHooks(env.lobby.hookMemo.putAll) @@ -52,28 +52,28 @@ object Cron { env.lobby.hookRepo.cleanupOld } - unsafe(3 seconds) { - Future.traverse(hubs) { hub ⇒ - hub ? socket.GetUsernames mapTo manifest[Iterable[String]] - } map (_.flatten) onSuccess { - case xs ⇒ (env.user.usernameMemo putAll xs).unsafePerformIO - } - } + //unsafe(3 seconds) { + //Future.traverse(hubs) { hub ⇒ + //hub ? socket.GetUsernames mapTo manifest[Iterable[String]] + //} map (_.flatten) onSuccess { + //case xs ⇒ (env.user.usernameMemo putAll xs).unsafePerformIO + //} + //} - effect(4.1 hours) { - env.game.gameRepo.cleanupUnplayed flatMap { _ ⇒ - env.gameCleanNextCommand.apply - } - } + //effect(4.1 hours) { + //env.game.gameRepo.cleanupUnplayed flatMap { _ ⇒ + //env.gameCleanNextCommand.apply + //} + //} - effect(1 hour) { - env.gameFinishCommand.apply - } + //effect(1 hour) { + //env.gameFinishCommand.apply + //} - effect(1 minute) { - env.ai.remoteAi.diagnose - } - env.ai.remoteAi.diagnose.unsafePerformIO + //effect(1 minute) { + //env.ai.remoteAi.diagnose + //} + //env.ai.remoteAi.diagnose.unsafePerformIO lazy val hubs: List[ActorRef] = List(env.site.hub, env.lobby.hub, env.round.hubMaster) diff --git a/app/core/Global.scala b/app/core/Global.scala index c912bfdd9c..1474a4f715 100644 --- a/app/core/Global.scala +++ b/app/core/Global.scala @@ -15,11 +15,12 @@ object Global extends GlobalSettings { coreEnv = CoreEnv(app) - //if (env.ai.isServer) println("Running as AI server") - //else core.Cron start env + if (env.ai.isServer) println("Running as AI server") + else core.Cron start env } override def onRouteRequest(req: RequestHeader): Option[Handler] = { + println(req) env.monitor.rpsProvider.countRequest() env.i18n.requestHandler(req) orElse super.onRouteRequest(req) } diff --git a/app/elo/EloRange.scala b/app/elo/EloRange.scala index c61d8f175e..fbea741b4e 100644 --- a/app/elo/EloRange.scala +++ b/app/elo/EloRange.scala @@ -3,6 +3,8 @@ package elo case class EloRange(min: Int, max: Int) { + def contains(elo: Int) = elo >= min && elo <= max + override def toString = "%d-%d".format(min, max) } @@ -11,7 +13,8 @@ object EloRange { val min = 800 val max = 2200 - val default = EloRange(min, max) + val broad = EloRange(min, max) + val default = broad // ^\d{3,4}\-\d{3,4}$ def apply(from: String): Option[EloRange] = for { @@ -28,5 +31,5 @@ object EloRange { def valid(from: String) = apply(from).isDefined - private def acceptable(v: Int) = v >= min && v <= max + private def acceptable(elo: Int) = broad contains elo } diff --git a/app/i18n/I18nRequestHandler.scala b/app/i18n/I18nRequestHandler.scala index 07f93d8098..287e5c30c4 100644 --- a/app/i18n/I18nRequestHandler.scala +++ b/app/i18n/I18nRequestHandler.scala @@ -14,7 +14,7 @@ final class I18nRequestHandler(pool: I18nPool) { else pool.domainLang(req).isDefined.fold( None, Action { - Redirect(redirectUrl(req).pp) + Redirect(redirectUrl(req)) } some ) diff --git a/app/lobby/Fisherman.scala b/app/lobby/Fisherman.scala index a63b728251..8d5256810b 100644 --- a/app/lobby/Fisherman.scala +++ b/app/lobby/Fisherman.scala @@ -24,6 +24,7 @@ final class Fisherman( def bite(hook: Hook, game: DbGame): IO[Unit] = for { _ ← socket removeHook hook _ ← socket.biteHook(hook, game) + _ ← hookRepo.setGame(hook, game) } yield () // mark the hook as active, once diff --git a/app/lobby/Hook.scala b/app/lobby/Hook.scala index 041784b269..4a4dc23b41 100644 --- a/app/lobby/Hook.scala +++ b/app/lobby/Hook.scala @@ -1,7 +1,8 @@ package lila package lobby -import chess.{ Variant, Mode, Color, Clock } +import chess.{ Variant, Mode, Clock } +import setup.Color import elo.EloRange import user.User @@ -18,6 +19,7 @@ case class Hook( increment: Option[Int], mode: Int, color: String, + userId: Option[String], username: String, elo: Option[Int], eloRange: String, @@ -25,6 +27,8 @@ case class Hook( `match`: Boolean = false, game: Option[DBRef] = None) { + def realColor = Color orDefault color + def gameId: Option[String] = game map (_.getId.toString) def realVariant = Variant orDefault variant @@ -71,6 +75,7 @@ object Hook { increment = clock map (_.increment), mode = mode.id, color = color, + userId = user map (_.idString), username = user.fold(_.username, User.anonymous), elo = user map (_.elo), eloRange = eloRange.toString, diff --git a/app/lobby/HookRepo.scala b/app/lobby/HookRepo.scala index 12bac7ce4e..1e6b6da8c9 100644 --- a/app/lobby/HookRepo.scala +++ b/app/lobby/HookRepo.scala @@ -1,6 +1,8 @@ package lila package lobby +import game.DbGame + import com.novus.salat._ import com.novus.salat.dao._ import com.mongodb.casbah.MongoCollection @@ -33,8 +35,12 @@ class HookRepo(collection: MongoCollection) find(query) sort DBObject("createdAt" -> 1) toList } + def setGame(hook: Hook, game: DbGame) = io { + update(idSelector(hook), $set("match" -> true) ++ $set("gameId" -> game.id)) + } + def removeId(id: String): IO[Unit] = io { - remove(DBObject("_id" -> id)) + remove(idSelector(id)) } def removeOwnerId(ownerId: String): IO[Unit] = io { @@ -50,4 +56,7 @@ class HookRepo(collection: MongoCollection) def cleanupOld: IO[Unit] = io { remove("createdAt" $lt (DateTime.now - 1.hour)) } + + private def idSelector(id: String): DBObject = DBObject("_id" -> id) + private def idSelector(hook: Hook): DBObject = idSelector(hook.id) } diff --git a/app/lobby/LobbyEnv.scala b/app/lobby/LobbyEnv.scala index ce92d8e5c8..f2432081a6 100644 --- a/app/lobby/LobbyEnv.scala +++ b/app/lobby/LobbyEnv.scala @@ -1,19 +1,18 @@ package lila package lobby -import com.mongodb.casbah.MongoCollection - import akka.actor._ - import play.api.libs.concurrent._ import play.api.Application import play.api.i18n.Lang import play.api.i18n.MessagesPlugin +import scalaz.effects._ +import com.mongodb.casbah.MongoCollection +import com.mongodb.DBRef -import user.UserRepo -import game.GameRepo +import user.{ User, UserRepo } +import game.{ GameRepo, DbGame } import round.{ Socket ⇒ RoundSocket, Messenger ⇒ RoundMessenger } -import ai.Ai import core.Settings final class LobbyEnv( @@ -50,14 +49,6 @@ final class LobbyEnv( collection = mongodb(MongoCollectionMessage), max = LobbyMessageMax) - //lazy val api = new Api( - //hookRepo = hookRepo, - //fisherman = fisherman, - //gameRepo = gameRepo, - //roundSocket = roundSocket, - //roundMessenger = roundMessenger, - //starter = starter) - lazy val hookRepo = new HookRepo(mongodb(MongoCollectionHook)) lazy val hookMemo = new HookMemo(timeout = MemoHookTimeout) diff --git a/app/setup/Color.scala b/app/setup/Color.scala index 9b09bd6cac..70c07b305e 100644 --- a/app/setup/Color.scala +++ b/app/setup/Color.scala @@ -27,11 +27,13 @@ object Color { def apply(name: String): Option[Color] = all find (_.name == name) + def orDefault(name: String) = apply(name) | default + val all = List(White, Black, Random) val names = all map (_.name) val choices = names zip names - val default = White + val default = Random } diff --git a/app/setup/FriendConfig.scala b/app/setup/FriendConfig.scala index 261e785b42..fcf0e4f849 100644 --- a/app/setup/FriendConfig.scala +++ b/app/setup/FriendConfig.scala @@ -1,7 +1,7 @@ package lila package setup -import chess.{ Game, Board, Variant, Mode, Color ⇒ ChessColor } +import chess.{ Game, Board, Variant, Mode, PausedClock, Color ⇒ ChessColor } import elo.EloRange import game.{ DbGame, DbPlayer } @@ -16,7 +16,13 @@ case class FriendConfig( def >> = (variant.id, clock, time, increment, mode.id, color.name).some def game = DbGame( - game = Game(board = Board(pieces = variant.pieces)), + game = Game( + board = Board(pieces = variant.pieces), + clock = clock option PausedClock( + limit = time, + increment = increment + ) + ), ai = None, whitePlayer = DbPlayer.white, blackPlayer = DbPlayer.black, diff --git a/app/setup/Joiner.scala b/app/setup/FriendJoiner.scala similarity index 86% rename from app/setup/Joiner.scala rename to app/setup/FriendJoiner.scala index 8674ac6081..2b84e9fd24 100644 --- a/app/setup/Joiner.scala +++ b/app/setup/FriendJoiner.scala @@ -10,19 +10,19 @@ import controllers.routes import com.mongodb.DBRef import scalaz.effects._ -final class Joiner( +final class FriendJoiner( gameRepo: GameRepo, messenger: Messenger, timelinePush: DbGame ⇒ IO[Unit], - dbRef: User ⇒ DBRef) { + userDbRef: User ⇒ DBRef) { def apply(game: DbGame, user: Option[User]): Valid[IO[(Pov, List[Event])]] = game.notStarted option { val color = game.invitedColor for { p1 ← user.fold( - u ⇒ gameRepo.setUser(game.id, color, dbRef(u), u.elo) map { _ ⇒ - Progress(game, game.updatePlayer(color, _.withUser(u, dbRef(u)))) + u ⇒ gameRepo.setUser(game.id, color, userDbRef(u), u.elo) map { _ ⇒ + Progress(game, game.updatePlayer(color, _.withUser(u, userDbRef(u)))) }, io(Progress(game))) p2 = p1 withGame game.start diff --git a/app/setup/HookJoiner.scala b/app/setup/HookJoiner.scala new file mode 100644 index 0000000000..ab758907eb --- /dev/null +++ b/app/setup/HookJoiner.scala @@ -0,0 +1,78 @@ +package lila +package setup + +import lobby.{ HookRepo, Hook, Fisherman } +import user.{ User, UserRepo } +import chess.{ Game, Board, Variant, Mode, PausedClock, Color ⇒ ChessColor } +import game.{ GameRepo, DbGame, DbPlayer, Pov } +import round.{ Messenger, Progress } + +import scalaz.effects._ +import com.mongodb.DBRef + +final class HookJoiner( + hookRepo: HookRepo, + fisherman: Fisherman, + gameRepo: GameRepo, + userRepo: UserRepo, + userDbRef: User ⇒ DBRef, + timelinePush: DbGame ⇒ IO[Unit], + messenger: Messenger) { + + def apply(hookId: String, myHookId: Option[String])(me: Option[User]): IO[Valid[Pov]] = + for { + hookOption ← hookRepo hook hookId + myHookOption ← myHookId.fold(hookRepo.ownedHook, io(none)) + result ← hookOption.fold( + hook ⇒ canJoin(hook, me).fold( + join(hook, myHookOption)(me) map success, + io(!!("Can not join hook")) + ), + io(!!("No such hook")) + ) + + } yield result + + private def join(hook: Hook, myHook: Option[Hook])(me: Option[User]): IO[Pov] = for { + _ ← myHook.fold(fisherman.delete, io()) + ownerOption ← hook.userId.fold(userRepo.user, io(none)) + game = blame( + _.invitedColor, me, + blame(_.creatorColor, ownerOption, makeGame(hook)) + ).start + _ ← gameRepo insert game + _ ← game.variant.standard.fold(io(), gameRepo saveInitialFen game) + _ ← timelinePush(game) + // messenges are not sent to the game socket + // as nobody is there to see them yet + _ ← messenger init game + _ ← fisherman.bite(hook, game) + } yield Pov(game, game.invitedColor) + + def blame(color: DbGame ⇒ ChessColor, userOption: Option[User], game: DbGame) = + userOption.fold( + user ⇒ game.updatePlayer(color(game), _.withUser(user, userDbRef(user))), + game) + + def makeGame(hook: Hook) = DbGame( + game = Game( + board = Board(pieces = hook.realVariant.pieces), + clock = hook.hasClock.fold( + hook.time |@| hook.increment apply { (limit, inc) ⇒ + PausedClock(limit = limit, increment = inc) + }, + none) + ), + ai = None, + whitePlayer = DbPlayer.white, + blackPlayer = DbPlayer.black, + creatorColor = hook.realColor.resolve, + mode = hook.realMode, + variant = hook.realVariant) + + private def canJoin(hook: Hook, me: Option[User]) = + hook.realMode.fold( + true, + me exists { u ⇒ hook.realEloRange.fold(_ contains u.elo, true) } + ) +} diff --git a/app/setup/Processor.scala b/app/setup/Processor.scala index 868dedf6ac..7f535b0648 100644 --- a/app/setup/Processor.scala +++ b/app/setup/Processor.scala @@ -18,7 +18,7 @@ final class Processor( fisherman: Fisherman, timelinePush: DbGame ⇒ IO[Unit], ai: () ⇒ Ai, - dbRef: User ⇒ DBRef) { + userDbRef: User ⇒ DBRef) { def ai(config: AiConfig)(implicit ctx: Context): IO[Pov] = for { _ ← ctx.me.fold( @@ -27,7 +27,7 @@ final class Processor( ) pov = config.pov game = ctx.me.fold( - user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, dbRef(user))), + user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, userDbRef(user))), pov.game) _ ← gameRepo insert game _ ← game.variant.standard.fold(io(), gameRepo saveInitialFen game) @@ -50,7 +50,7 @@ final class Processor( ) pov = config.pov game = ctx.me.fold( - user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, dbRef(user))), + user ⇒ pov.game.updatePlayer(pov.color, _.withUser(user, userDbRef(user))), pov.game) _ ← gameRepo insert game _ ← game.variant.standard.fold(io(), gameRepo saveInitialFen game) diff --git a/app/setup/SetupEnv.scala b/app/setup/SetupEnv.scala index 41ec056585..7f14428cd4 100644 --- a/app/setup/SetupEnv.scala +++ b/app/setup/SetupEnv.scala @@ -3,7 +3,7 @@ package setup import core.Settings import game.{ DbGame, GameRepo } -import lobby.Fisherman +import lobby.{ HookRepo, Fisherman } import round.Messenger import ai.Ai import user.{ User, UserRepo } @@ -16,12 +16,13 @@ final class SetupEnv( settings: Settings, mongodb: String ⇒ MongoCollection, gameRepo: GameRepo, + hookRepo: HookRepo, fisherman: Fisherman, userRepo: UserRepo, timelinePush: DbGame ⇒ IO[Unit], roundMessenger: Messenger, ai: () ⇒ Ai, - dbRef: User ⇒ DBRef) { + userDbRef: User ⇒ DBRef) { import settings._ @@ -37,7 +38,7 @@ final class SetupEnv( fisherman = fisherman, timelinePush = timelinePush, ai = ai, - dbRef = dbRef) + userDbRef = userDbRef) lazy val friendConfigMemo = new FriendConfigMemo( ttl = SetupFriendConfigMemoTtl) @@ -48,9 +49,18 @@ final class SetupEnv( messenger = roundMessenger, timelinePush = timelinePush) - lazy val joiner = new Joiner( + lazy val friendJoiner = new FriendJoiner( gameRepo = gameRepo, messenger = roundMessenger, timelinePush = timelinePush, - dbRef = dbRef) + userDbRef = userDbRef) + + lazy val hookJoiner = new HookJoiner( + hookRepo = hookRepo, + fisherman = fisherman, + gameRepo = gameRepo, + userRepo = userRepo, + userDbRef = userDbRef, + timelinePush = timelinePush, + messenger = roundMessenger) } diff --git a/app/views/lobby/home.scala.html b/app/views/lobby/home.scala.html index 32249692eb..77a6d243c0 100644 --- a/app/views/lobby/home.scala.html +++ b/app/views/lobby/home.scala.html @@ -1,4 +1,4 @@ -@(preload: String, hookId: Option[String] = None)(implicit ctx: Context) +@(preload: String, myHook: Option[lila.lobby.Hook])(implicit ctx: Context) @chat = { @ctx.me.map { m => @@ -25,9 +25,9 @@ @widget.connection()