Implement lobby hook matching

This commit is contained in:
Thibault Duplessis 2012-05-26 01:32:37 +02:00
parent 2ad61ad857
commit 97b92844b3
19 changed files with 185 additions and 68 deletions

View file

@ -48,7 +48,7 @@ final class Preload(
entries entryRepo.recent entries entryRepo.recent
} yield Right(Map( } yield Right(Map(
"version" -> history.version, "version" -> history.version,
"pool" -> renderHooks(hooks, myHook).pp, "pool" -> renderHooks(hooks, myHook),
"chat" -> (messages.reverse map (_.render)), "chat" -> (messages.reverse map (_.render)),
"timeline" -> (entries.reverse map (_.render)) "timeline" -> (entries.reverse map (_.render))
)) ))

View file

@ -18,6 +18,7 @@ object Lobby extends LilaController {
def preloader = env.preloader def preloader = env.preloader
def hookRepo = env.lobby.hookRepo def hookRepo = env.lobby.hookRepo
def fisherman = env.lobby.fisherman def fisherman = env.lobby.fisherman
def joiner = env.setup.hookJoiner
val home = Open { implicit ctx val home = Open { implicit ctx
renderHome(none).fold(identity, Ok(_)) renderHome(none).fold(identity, Ok(_))
@ -35,7 +36,7 @@ object Lobby extends LilaController {
myHook = myHook myHook = myHook
).unsafePerformIO.bimap( ).unsafePerformIO.bimap(
url Redirect(url), url Redirect(url),
preload html.lobby.home(toJson(preload)) preload html.lobby.home(toJson(preload), myHook)
) )
def socket = WebSocket.async[JsValue] { implicit req def socket = WebSocket.async[JsValue] { implicit req
@ -49,11 +50,22 @@ object Lobby extends LilaController {
} }
def hook(ownerId: String) = Open { implicit ctx 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(_)), hook renderHome(hook.some).fold(identity, Ok(_)),
Redirect(routes.Lobby.home)) 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 def cancel(ownerId: String) = Open { implicit ctx
IORedirect { IORedirect {
for { for {
@ -63,8 +75,6 @@ object Lobby extends LilaController {
} }
} }
def join(hookId: String) = TODO
//def join(gameId: String, color: String) = Action { implicit req //def join(gameId: String, color: String) = Action { implicit req
//FormValidIOk[LobbyJoinData](lobbyJoinForm)(join //FormValidIOk[LobbyJoinData](lobbyJoinForm)(join
//api.join(gameId, color, join._1, join._2, join._3, join._4) //api.join(gameId, color, join._1, join._2, join._3, join._4)

View file

@ -20,7 +20,7 @@ object Round extends LilaController {
private val hand = env.round.hand private val hand = env.round.hand
private val messenger = env.round.messenger private val messenger = env.round.messenger
private val rematcher = env.setup.rematcher 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 def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req) implicit val ctx = reqToCtx(req)

View file

@ -38,12 +38,13 @@ final class CoreEnv private (application: Application, val settings: Settings) {
settings = settings, settings = settings,
mongodb = mongodb.apply _, mongodb = mongodb.apply _,
gameRepo = game.gameRepo, gameRepo = game.gameRepo,
hookRepo = lobby.hookRepo,
fisherman = lobby.fisherman, fisherman = lobby.fisherman,
userRepo = user.userRepo, userRepo = user.userRepo,
timelinePush = timeline.push.apply, timelinePush = timeline.push.apply,
roundMessenger = round.messenger, roundMessenger = round.messenger,
ai = ai.ai, ai = ai.ai,
dbRef = user.userRepo.dbRef) userDbRef = user.userRepo.dbRef)
lazy val timeline = new lila.timeline.TimelineEnv( lazy val timeline = new lila.timeline.TimelineEnv(
settings = settings, settings = settings,

View file

@ -28,9 +28,9 @@ object Cron {
} }
} }
message(1 seconds) { //message(1 seconds) {
env.monitor.reporting -> monitor.Update(env) //env.monitor.reporting -> monitor.Update(env)
} //}
message(1 second) { message(1 second) {
env.lobby.hub -> lobby.WithHooks(env.lobby.hookMemo.putAll) env.lobby.hub -> lobby.WithHooks(env.lobby.hookMemo.putAll)
@ -52,28 +52,28 @@ object Cron {
env.lobby.hookRepo.cleanupOld env.lobby.hookRepo.cleanupOld
} }
unsafe(3 seconds) { //unsafe(3 seconds) {
Future.traverse(hubs) { hub //Future.traverse(hubs) { hub
hub ? socket.GetUsernames mapTo manifest[Iterable[String]] //hub ? socket.GetUsernames mapTo manifest[Iterable[String]]
} map (_.flatten) onSuccess { //} map (_.flatten) onSuccess {
case xs (env.user.usernameMemo putAll xs).unsafePerformIO //case xs (env.user.usernameMemo putAll xs).unsafePerformIO
} //}
} //}
effect(4.1 hours) { //effect(4.1 hours) {
env.game.gameRepo.cleanupUnplayed flatMap { _ //env.game.gameRepo.cleanupUnplayed flatMap { _
env.gameCleanNextCommand.apply //env.gameCleanNextCommand.apply
} //}
} //}
effect(1 hour) { //effect(1 hour) {
env.gameFinishCommand.apply //env.gameFinishCommand.apply
} //}
effect(1 minute) { //effect(1 minute) {
env.ai.remoteAi.diagnose //env.ai.remoteAi.diagnose
} //}
env.ai.remoteAi.diagnose.unsafePerformIO //env.ai.remoteAi.diagnose.unsafePerformIO
lazy val hubs: List[ActorRef] = lazy val hubs: List[ActorRef] =
List(env.site.hub, env.lobby.hub, env.round.hubMaster) List(env.site.hub, env.lobby.hub, env.round.hubMaster)

View file

@ -15,11 +15,12 @@ object Global extends GlobalSettings {
coreEnv = CoreEnv(app) coreEnv = CoreEnv(app)
//if (env.ai.isServer) println("Running as AI server") if (env.ai.isServer) println("Running as AI server")
//else core.Cron start env else core.Cron start env
} }
override def onRouteRequest(req: RequestHeader): Option[Handler] = { override def onRouteRequest(req: RequestHeader): Option[Handler] = {
println(req)
env.monitor.rpsProvider.countRequest() env.monitor.rpsProvider.countRequest()
env.i18n.requestHandler(req) orElse super.onRouteRequest(req) env.i18n.requestHandler(req) orElse super.onRouteRequest(req)
} }

View file

@ -3,6 +3,8 @@ package elo
case class EloRange(min: Int, max: Int) { case class EloRange(min: Int, max: Int) {
def contains(elo: Int) = elo >= min && elo <= max
override def toString = "%d-%d".format(min, max) override def toString = "%d-%d".format(min, max)
} }
@ -11,7 +13,8 @@ object EloRange {
val min = 800 val min = 800
val max = 2200 val max = 2200
val default = EloRange(min, max) val broad = EloRange(min, max)
val default = broad
// ^\d{3,4}\-\d{3,4}$ // ^\d{3,4}\-\d{3,4}$
def apply(from: String): Option[EloRange] = for { def apply(from: String): Option[EloRange] = for {
@ -28,5 +31,5 @@ object EloRange {
def valid(from: String) = apply(from).isDefined def valid(from: String) = apply(from).isDefined
private def acceptable(v: Int) = v >= min && v <= max private def acceptable(elo: Int) = broad contains elo
} }

View file

@ -14,7 +14,7 @@ final class I18nRequestHandler(pool: I18nPool) {
else pool.domainLang(req).isDefined.fold( else pool.domainLang(req).isDefined.fold(
None, None,
Action { Action {
Redirect(redirectUrl(req).pp) Redirect(redirectUrl(req))
} some } some
) )

View file

@ -24,6 +24,7 @@ final class Fisherman(
def bite(hook: Hook, game: DbGame): IO[Unit] = for { def bite(hook: Hook, game: DbGame): IO[Unit] = for {
_ socket removeHook hook _ socket removeHook hook
_ socket.biteHook(hook, game) _ socket.biteHook(hook, game)
_ hookRepo.setGame(hook, game)
} yield () } yield ()
// mark the hook as active, once // mark the hook as active, once

View file

@ -1,7 +1,8 @@
package lila package lila
package lobby package lobby
import chess.{ Variant, Mode, Color, Clock } import chess.{ Variant, Mode, Clock }
import setup.Color
import elo.EloRange import elo.EloRange
import user.User import user.User
@ -18,6 +19,7 @@ case class Hook(
increment: Option[Int], increment: Option[Int],
mode: Int, mode: Int,
color: String, color: String,
userId: Option[String],
username: String, username: String,
elo: Option[Int], elo: Option[Int],
eloRange: String, eloRange: String,
@ -25,6 +27,8 @@ case class Hook(
`match`: Boolean = false, `match`: Boolean = false,
game: Option[DBRef] = None) { game: Option[DBRef] = None) {
def realColor = Color orDefault color
def gameId: Option[String] = game map (_.getId.toString) def gameId: Option[String] = game map (_.getId.toString)
def realVariant = Variant orDefault variant def realVariant = Variant orDefault variant
@ -71,6 +75,7 @@ object Hook {
increment = clock map (_.increment), increment = clock map (_.increment),
mode = mode.id, mode = mode.id,
color = color, color = color,
userId = user map (_.idString),
username = user.fold(_.username, User.anonymous), username = user.fold(_.username, User.anonymous),
elo = user map (_.elo), elo = user map (_.elo),
eloRange = eloRange.toString, eloRange = eloRange.toString,

View file

@ -1,6 +1,8 @@
package lila package lila
package lobby package lobby
import game.DbGame
import com.novus.salat._ import com.novus.salat._
import com.novus.salat.dao._ import com.novus.salat.dao._
import com.mongodb.casbah.MongoCollection import com.mongodb.casbah.MongoCollection
@ -33,8 +35,12 @@ class HookRepo(collection: MongoCollection)
find(query) sort DBObject("createdAt" -> 1) toList 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 { def removeId(id: String): IO[Unit] = io {
remove(DBObject("_id" -> id)) remove(idSelector(id))
} }
def removeOwnerId(ownerId: String): IO[Unit] = io { def removeOwnerId(ownerId: String): IO[Unit] = io {
@ -50,4 +56,7 @@ class HookRepo(collection: MongoCollection)
def cleanupOld: IO[Unit] = io { def cleanupOld: IO[Unit] = io {
remove("createdAt" $lt (DateTime.now - 1.hour)) 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)
} }

View file

@ -1,19 +1,18 @@
package lila package lila
package lobby package lobby
import com.mongodb.casbah.MongoCollection
import akka.actor._ import akka.actor._
import play.api.libs.concurrent._ import play.api.libs.concurrent._
import play.api.Application import play.api.Application
import play.api.i18n.Lang import play.api.i18n.Lang
import play.api.i18n.MessagesPlugin import play.api.i18n.MessagesPlugin
import scalaz.effects._
import com.mongodb.casbah.MongoCollection
import com.mongodb.DBRef
import user.UserRepo import user.{ User, UserRepo }
import game.GameRepo import game.{ GameRepo, DbGame }
import round.{ Socket RoundSocket, Messenger RoundMessenger } import round.{ Socket RoundSocket, Messenger RoundMessenger }
import ai.Ai
import core.Settings import core.Settings
final class LobbyEnv( final class LobbyEnv(
@ -50,14 +49,6 @@ final class LobbyEnv(
collection = mongodb(MongoCollectionMessage), collection = mongodb(MongoCollectionMessage),
max = LobbyMessageMax) 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 hookRepo = new HookRepo(mongodb(MongoCollectionHook))
lazy val hookMemo = new HookMemo(timeout = MemoHookTimeout) lazy val hookMemo = new HookMemo(timeout = MemoHookTimeout)

View file

@ -27,11 +27,13 @@ object Color {
def apply(name: String): Option[Color] = all find (_.name == name) def apply(name: String): Option[Color] = all find (_.name == name)
def orDefault(name: String) = apply(name) | default
val all = List(White, Black, Random) val all = List(White, Black, Random)
val names = all map (_.name) val names = all map (_.name)
val choices = names zip names val choices = names zip names
val default = White val default = Random
} }

View file

@ -1,7 +1,7 @@
package lila package lila
package setup package setup
import chess.{ Game, Board, Variant, Mode, Color ChessColor } import chess.{ Game, Board, Variant, Mode, PausedClock, Color ChessColor }
import elo.EloRange import elo.EloRange
import game.{ DbGame, DbPlayer } import game.{ DbGame, DbPlayer }
@ -16,7 +16,13 @@ case class FriendConfig(
def >> = (variant.id, clock, time, increment, mode.id, color.name).some def >> = (variant.id, clock, time, increment, mode.id, color.name).some
def game = DbGame( 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, ai = None,
whitePlayer = DbPlayer.white, whitePlayer = DbPlayer.white,
blackPlayer = DbPlayer.black, blackPlayer = DbPlayer.black,

View file

@ -10,19 +10,19 @@ import controllers.routes
import com.mongodb.DBRef import com.mongodb.DBRef
import scalaz.effects._ import scalaz.effects._
final class Joiner( final class FriendJoiner(
gameRepo: GameRepo, gameRepo: GameRepo,
messenger: Messenger, messenger: Messenger,
timelinePush: DbGame IO[Unit], timelinePush: DbGame IO[Unit],
dbRef: User DBRef) { userDbRef: User DBRef) {
def apply(game: DbGame, user: Option[User]): Valid[IO[(Pov, List[Event])]] = def apply(game: DbGame, user: Option[User]): Valid[IO[(Pov, List[Event])]] =
game.notStarted option { game.notStarted option {
val color = game.invitedColor val color = game.invitedColor
for { for {
p1 user.fold( p1 user.fold(
u gameRepo.setUser(game.id, color, dbRef(u), u.elo) map { _ u gameRepo.setUser(game.id, color, userDbRef(u), u.elo) map { _
Progress(game, game.updatePlayer(color, _.withUser(u, dbRef(u)))) Progress(game, game.updatePlayer(color, _.withUser(u, userDbRef(u))))
}, },
io(Progress(game))) io(Progress(game)))
p2 = p1 withGame game.start p2 = p1 withGame game.start

View file

@ -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) }
)
}

View file

@ -18,7 +18,7 @@ final class Processor(
fisherman: Fisherman, fisherman: Fisherman,
timelinePush: DbGame IO[Unit], timelinePush: DbGame IO[Unit],
ai: () Ai, ai: () Ai,
dbRef: User DBRef) { userDbRef: User DBRef) {
def ai(config: AiConfig)(implicit ctx: Context): IO[Pov] = for { def ai(config: AiConfig)(implicit ctx: Context): IO[Pov] = for {
_ ctx.me.fold( _ ctx.me.fold(
@ -27,7 +27,7 @@ final class Processor(
) )
pov = config.pov pov = config.pov
game = ctx.me.fold( 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) pov.game)
_ gameRepo insert game _ gameRepo insert game
_ game.variant.standard.fold(io(), gameRepo saveInitialFen game) _ game.variant.standard.fold(io(), gameRepo saveInitialFen game)
@ -50,7 +50,7 @@ final class Processor(
) )
pov = config.pov pov = config.pov
game = ctx.me.fold( 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) pov.game)
_ gameRepo insert game _ gameRepo insert game
_ game.variant.standard.fold(io(), gameRepo saveInitialFen game) _ game.variant.standard.fold(io(), gameRepo saveInitialFen game)

View file

@ -3,7 +3,7 @@ package setup
import core.Settings import core.Settings
import game.{ DbGame, GameRepo } import game.{ DbGame, GameRepo }
import lobby.Fisherman import lobby.{ HookRepo, Fisherman }
import round.Messenger import round.Messenger
import ai.Ai import ai.Ai
import user.{ User, UserRepo } import user.{ User, UserRepo }
@ -16,12 +16,13 @@ final class SetupEnv(
settings: Settings, settings: Settings,
mongodb: String MongoCollection, mongodb: String MongoCollection,
gameRepo: GameRepo, gameRepo: GameRepo,
hookRepo: HookRepo,
fisherman: Fisherman, fisherman: Fisherman,
userRepo: UserRepo, userRepo: UserRepo,
timelinePush: DbGame IO[Unit], timelinePush: DbGame IO[Unit],
roundMessenger: Messenger, roundMessenger: Messenger,
ai: () Ai, ai: () Ai,
dbRef: User DBRef) { userDbRef: User DBRef) {
import settings._ import settings._
@ -37,7 +38,7 @@ final class SetupEnv(
fisherman = fisherman, fisherman = fisherman,
timelinePush = timelinePush, timelinePush = timelinePush,
ai = ai, ai = ai,
dbRef = dbRef) userDbRef = userDbRef)
lazy val friendConfigMemo = new FriendConfigMemo( lazy val friendConfigMemo = new FriendConfigMemo(
ttl = SetupFriendConfigMemoTtl) ttl = SetupFriendConfigMemoTtl)
@ -48,9 +49,18 @@ final class SetupEnv(
messenger = roundMessenger, messenger = roundMessenger,
timelinePush = timelinePush) timelinePush = timelinePush)
lazy val joiner = new Joiner( lazy val friendJoiner = new FriendJoiner(
gameRepo = gameRepo, gameRepo = gameRepo,
messenger = roundMessenger, messenger = roundMessenger,
timelinePush = timelinePush, 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)
} }

View file

@ -1,4 +1,4 @@
@(preload: String, hookId: Option[String] = None)(implicit ctx: Context) @(preload: String, myHook: Option[lila.lobby.Hook])(implicit ctx: Context)
@chat = { @chat = {
@ctx.me.map { m => @ctx.me.map { m =>
@ -25,9 +25,9 @@
@widget.connection() @widget.connection()
<div class="hooks_wrap"> <div class="hooks_wrap">
<div class="hooks" <div class="hooks"
data-my-hook="@hookId" data-my-hook="@myHook.map(_.ownerId)"
data-cancel-url="@routes.Lobby.cancel("000000000000")" data-cancel-url="@routes.Lobby.cancel("000000000000")"
data-join-url="@routes.Lobby.join("000000000000")" data-join-url="@routes.Lobby.join("00000000")"
> >
<table></table> <table></table>
<textarea class="hooks_preload" style="display: none">@Html(preload)</textarea> <textarea class="hooks_preload" style="display: none">@Html(preload)</textarea>