Lobby hook creation

pull/1/merge
Thibault Duplessis 2012-05-25 23:05:19 +02:00
parent c8aba03734
commit 81da8c6b52
22 changed files with 155 additions and 75 deletions

View File

@ -19,49 +19,47 @@ final class Preload(
def apply(
auth: Boolean,
chat: Boolean,
myHookId: Option[String]): IO[Response] = for {
myHook: Option[Hook]): IO[Response] = for {
hooks auth.fold(hookRepo.allOpen, hookRepo.allOpenCasual)
std = () stdResponse(chat, hooks, myHookId)
res myHookId.fold(
id hookRepo ownedHook id flatMap { hookResponse(_, std) },
std()
)
std = () stdResponse(chat, hooks, myHook)
res myHook.fold(
h hookResponse(h, std),
std())
} yield res
private def hookResponse(
myHook: Option[Hook],
std: () IO[Response]): IO[Response] = myHook.fold(
h h.gameId.fold(
myHook: Hook,
std: () IO[Response]): IO[Response] =
myHook.gameId.fold(
ref gameRepo game ref map { game
game.fold(
g redirect(g fullIdOf g.creatorColor),
redirect()
)
},
fisherman shake h flatMap { _ std() }
),
io(redirect())
)
fisherman shake myHook flatMap { _ std() }
)
private def stdResponse(
chat: Boolean,
hooks: List[Hook],
myHookId: Option[String]): IO[Response] = for {
myHook: Option[Hook]): IO[Response] = for {
messages if (chat) messageRepo.recent else io(Nil)
entries entryRepo.recent
} yield Right(Map(
"version" -> history.version,
"pool" -> renderHooks(hooks, myHookId),
"pool" -> renderHooks(hooks, myHook).pp,
"chat" -> (messages.reverse map (_.render)),
"timeline" -> (entries.reverse map (_.render))
))
private def renderHooks(
hooks: List[Hook],
myHookId: Option[String]) = hooks map { h
if (myHookId == Some(h.ownerId)) h.render ++ Map("ownerId" -> h.ownerId)
else h.render
myHook: Option[Hook]) = hooks map { h
myHook.exists(_.id == h.id).fold(
h.render ++ Map("ownerId" -> h.ownerId),
h.render)
}
private def redirect(url: String = "" ) = Left("/" + url)
private def redirect(url: String = "") = Left("/" + url)
}

View File

@ -110,6 +110,9 @@ trait LilaController
def IOk[A](op: IO[A])(implicit writer: Writeable[A], ctype: ContentTypeOf[A]) =
Ok(op.unsafePerformIO)
def IOResult[A](op: IO[Result]) =
op.unsafePerformIO
def IORedirect(op: IO[Call]) = Redirect(op.unsafePerformIO)
def IOptionOk[A, B](ioa: IO[Option[A]])(op: A B)(

View File

@ -2,6 +2,7 @@ package controllers
import lila._
import http.Context
import lobby.Hook
import views._
import play.api._
@ -13,22 +14,23 @@ import play.api.libs.iteratee._
object Lobby extends LilaController {
private val preloader = env.preloader
def preloader = env.preloader
def hookRepo = env.lobby.hookRepo
val home = Open { implicit ctx
renderHome(ctx).fold(identity, Ok(_))
renderHome(none).fold(identity, Ok(_))
}
def handleNotFound(req: RequestHeader): Result =
handleNotFound(reqToCtx(req))
def handleNotFound(ctx: Context): Result =
renderHome(ctx).fold(identity, NotFound(_))
def handleNotFound(implicit ctx: Context): Result =
renderHome(none).fold(identity, NotFound(_))
private def renderHome(implicit ctx: Context) = preloader(
private def renderHome(myHook: Option[Hook])(implicit ctx: Context) = preloader(
auth = ctx.isAuth,
chat = ctx.canSeeChat,
myHookId = get("hook")
myHook = myHook
).unsafePerformIO.bimap(
url Redirect(url),
preload html.lobby.home(toJson(preload))
@ -44,9 +46,15 @@ object Lobby extends LilaController {
)
}
def cancel(ownerId: String) = TODO
//api.cancel(ownerId).unsafePerformIO
//Redirect("/")
def hook(ownerId: String) = Open { implicit ctx
hookRepo.ownedHook(ownerId.pp).unsafePerformIO.pp.fold(
hook renderHome(hook.some).fold(identity, Ok(_)),
Redirect(routes.Lobby.home))
}
def cancel(fullId: String) = TODO
//api.cancel(ownerId).unsafePerformIO
//Redirect("/")
//}
def join(hookId: String) = TODO
@ -58,10 +66,10 @@ object Lobby extends LilaController {
//}
//def create(hookOwnerId: String) = Action {
//IOk(api create hookOwnerId)
//IOk(api create hookOwnerId)
//}
//def chatBan(username: String) = Action {
//IOk(env.lobby.messenger ban username)
//IOk(env.lobby.messenger ban username)
//}
}

View File

@ -39,6 +39,17 @@ object Setup extends LilaController {
}
}
val hookForm = Open { implicit ctx
IOk(forms.hookFilled map { html.setup.hook(_) })
}
val hook = process(forms.hook) { config
implicit ctx
processor hook config map { hook
routes.Lobby.hook(hook.ownerId)
}
}
def await(fullId: String) = Open { implicit ctx
IOptionResult(gameRepo pov fullId) { pov
pov.game.started.fold(
@ -60,12 +71,6 @@ object Setup extends LilaController {
}
}
val hookForm = Open { implicit ctx
IOk(forms.hookFilled map { html.setup.hook(_) })
}
val hook = TODO
private def process[A](form: Form[A])(op: A BodyContext IO[Call]) =
OpenBody { ctx
implicit val req = ctx.body

View File

@ -38,6 +38,7 @@ final class CoreEnv private (application: Application, val settings: Settings) {
settings = settings,
mongodb = mongodb.apply _,
gameRepo = game.gameRepo,
fisherman = lobby.fisherman,
userRepo = user.userRepo,
timelinePush = timeline.push.apply,
roundMessenger = round.messenger,

View File

@ -3,9 +3,6 @@ package elo
case class EloRange(min: Int, max: Int) {
def userMin = (min > EloRange.min) option min
def userMax = (max > EloRange.max) option max
override def toString = "%d-%d".format(min, max)
}
@ -27,6 +24,8 @@ object EloRange {
def orDefault(from: String) = apply(from) | default
def noneIfDefault(from: String) = apply(from) filter (_ != default)
def valid(from: String) = apply(from).isDefined
private def acceptable(v: Int) = v >= min && v <= max

View File

@ -2,17 +2,16 @@ package lila
package game
import chess.Clock
import user.User
object Namer {
val anonPlayerName = "Anonymous"
def player(player: DbPlayer)(getUsername: String String) =
player.aiLevel.fold(
level "A.I. level " + level,
(player.userId map getUsername).fold(
username "%s (%s)".format(username, player.elo getOrElse "?"),
anonPlayerName)
User.anonymous)
)
def clock(clock: Clock): String = "%d minutes/side + %d seconds/move".format(

View File

@ -45,9 +45,4 @@ final class Api(
//}
//).fold(identity, io(GameNotFound))
//} yield result
def create(hookOwnerId: String): IO[Unit] = for {
hook hookRepo ownedHook hookOwnerId
_ hook.fold(fisherman.+, io())
} yield ()
}

View File

@ -10,14 +10,13 @@ final class Fisherman(
hookMemo: HookMemo,
socket: Socket) {
// DO delete in db
def delete(hook: Hook): IO[Unit] = for {
_ socket removeHook hook
_ hookRepo removeId hook.id
} yield ()
// DO NOT insert in db (done on php side)
def +(hook: Hook): IO[Unit] = for {
def add(hook: Hook): IO[Unit] = for {
_ io(hookRepo insert hook)
_ socket addHook hook
_ shake(hook)
} yield ()

View File

@ -1,11 +1,13 @@
package lila
package lobby
import chess.{ Variant, Mode }
import chess.{ Variant, Mode, Color, Clock }
import elo.EloRange
import user.User
import com.novus.salat.annotations.Key
import com.mongodb.DBRef
import ornicar.scalalib.OrnicarRandom
case class Hook(
@Key("_id") id: String,
@ -18,18 +20,18 @@ case class Hook(
color: String,
username: String,
elo: Option[Int],
`match`: Boolean,
eloRange: String,
engine: Boolean,
game: Option[DBRef]) {
`match`: Boolean = false,
game: Option[DBRef] = None) {
def gameId: Option[String] = game map (_.getId.toString)
def realVariant = Variant orDefault variant
def realVariant = Variant orDefault variant
def realMode = Mode orDefault mode
lazy val realEloRange = EloRange orDefault eloRange
lazy val realEloRange: Option[EloRange] = EloRange noneIfDefault eloRange
def render = Map(
"id" -> id,
@ -39,11 +41,45 @@ case class Hook(
"mode" -> realMode.toString,
"color" -> color,
"clock" -> clockOrUnlimited,
"emin" -> realEloRange.userMin,
"emax" -> realEloRange.userMax
"emin" -> realEloRange.map(_.min),
"emax" -> realEloRange.map(_.max)
) +? (engine, "engine" -> true)
def clockOrUnlimited = ((time filter (_ hasClock)) |@| increment apply renderClock _) | "Unlimited"
def renderClock(time: Int, inc: Int) = "%d + %d".format(time, inc)
}
object Hook {
val idSize = 8
val ownerIdSize = 12
def apply(
variant: Variant,
clock: Option[Clock],
mode: Mode,
color: String,
user: Option[User],
eloRange: EloRange): Hook = generateId |> { id
new Hook(
id = id,
ownerId = id + generateOwnerId,
variant = variant.id,
hasClock = clock.isDefined,
time = clock map (_.limit),
increment = clock map (_.increment),
mode = mode.id,
color = color,
username = user.fold(_.username, User.anonymous),
elo = user map (_.elo),
eloRange = eloRange.toString,
engine = user.fold(_.engine, false))
}
private def generateId =
OrnicarRandom nextAsciiString idSize
private def generateOwnerId =
OrnicarRandom nextAsciiString (ownerIdSize - idSize)
}

View File

@ -39,8 +39,8 @@ final class Hub(
"variant" -> JsString(hook.realVariant.toString),
"color" -> JsString(hook.color),
"clock" -> JsString(hook.clockOrUnlimited),
"emin" -> hook.realEloRange.userMin.fold(JsNumber(_), JsNull),
"emax" -> hook.realEloRange.userMax.fold(JsNumber(_), JsNull),
"emin" -> hook.realEloRange.fold(range JsNumber(range.min), JsNull),
"emax" -> hook.realEloRange.fold(range JsNumber(range.max), JsNull),
"engine" -> JsBoolean(hook.engine))
)

View File

@ -7,7 +7,7 @@ import game.{ DbGame, DbPlayer }
case class AiConfig(
variant: Variant,
level: Int,
color: Color) extends Config {
color: Color) extends Config with GameGenerator {
def >> = (variant.id, level, color.name).some

View File

@ -13,6 +13,9 @@ trait Config {
val color: Color
lazy val creatorColor = color.resolve
}
trait GameGenerator { self: Config
def game: DbGame

View File

@ -1,7 +1,9 @@
package lila
package setup
import chess.{ Variant, Mode, Color ChessColor }
import chess.{ Game, Board, Variant, Mode, Color ChessColor }
import elo.EloRange
import game.{ DbGame, DbPlayer }
case class FriendConfig(
variant: Variant,
@ -9,10 +11,19 @@ case class FriendConfig(
time: Int,
increment: Int,
mode: Mode,
color: Color) extends HumanConfig {
color: Color) extends HumanConfig with GameGenerator {
def >> = (variant.id, clock, time, increment, mode.id, color.name).some
def game = DbGame(
game = Game(board = Board(pieces = variant.pieces)),
ai = None,
whitePlayer = DbPlayer.white,
blackPlayer = DbPlayer.black,
creatorColor = creatorColor,
mode = mode,
variant = variant)
def encode = RawFriendConfig(
v = variant.id,
k = clock,

View File

@ -3,6 +3,8 @@ package setup
import chess.{ Variant, Mode, Color ChessColor }
import elo.EloRange
import user.User
import lobby.Hook
case class HookConfig(
variant: Variant,
@ -15,6 +17,14 @@ case class HookConfig(
def >> = (variant.id, clock, time, increment, mode.id, eloRange.toString, color.name).some
def hook(user: Option[User]) = Hook(
variant = variant,
clock = makeClock,
mode = mode,
color = color.name,
user = user,
eloRange = eloRange)
def encode = RawHookConfig(
v = variant.id,
k = clock,

View File

@ -1,8 +1,7 @@
package lila
package setup
import chess.{ Mode, PausedClock, Game, Board }
import game.{ DbGame, DbPlayer }
import chess.{ Mode, PausedClock }
trait HumanConfig extends Config {
@ -21,15 +20,6 @@ trait HumanConfig extends Config {
def validClock = clock.fold(time + increment > 0, true)
def makeClock = clock option PausedClock(time * 60, increment)
def game = DbGame(
game = Game(board = Board(pieces = variant.pieces)),
ai = None,
whitePlayer = DbPlayer.white,
blackPlayer = DbPlayer.black,
creatorColor = creatorColor,
mode = mode,
variant = variant)
}
trait BaseHumanConfig extends BaseConfig {

View File

@ -6,6 +6,7 @@ import game.{ DbGame, GameRepo, Pov }
import user.User
import chess.{ Game, Board }
import ai.Ai
import lobby.{ Hook, Fisherman }
import com.mongodb.DBRef
import scalaz.effects._
@ -14,6 +15,7 @@ final class Processor(
configRepo: UserConfigRepo,
friendConfigMemo: FriendConfigMemo,
gameRepo: GameRepo,
fisherman: Fisherman,
timelinePush: DbGame IO[Unit],
ai: () Ai,
dbRef: User DBRef) {
@ -55,4 +57,13 @@ final class Processor(
_ timelinePush(game)
_ friendConfigMemo.set(pov.game.id, config)
} yield pov
def hook(config: HookConfig)(implicit ctx: Context): IO[Hook] = for {
_ ctx.me.fold(
user configRepo.update(user)(_ withHook config),
io()
)
hook = config hook ctx.me
_ fisherman add hook
} yield hook
}

View File

@ -3,6 +3,7 @@ package setup
import core.Settings
import game.{ DbGame, GameRepo }
import lobby.Fisherman
import round.Messenger
import ai.Ai
import user.{ User, UserRepo }
@ -15,6 +16,7 @@ final class SetupEnv(
settings: Settings,
mongodb: String MongoCollection,
gameRepo: GameRepo,
fisherman: Fisherman,
userRepo: UserRepo,
timelinePush: DbGame IO[Unit],
roundMessenger: Messenger,
@ -32,6 +34,7 @@ final class SetupEnv(
configRepo = configRepo,
friendConfigMemo = friendConfigMemo,
gameRepo = gameRepo,
fisherman = fisherman,
timelinePush = timelinePush,
ai = ai,
dbRef = dbRef)

View File

@ -37,4 +37,6 @@ case class User(
object User {
val STARTING_ELO = 1200
val anonymous = "Anonymous"
}

View File

@ -95,8 +95,9 @@ GET /ai controllers.Ai.run
# Lobby Public API
GET / controllers.Lobby.home
GET /lobby/cancel/:ownerId controllers.Lobby.cancel(ownerId: String)
GET /lobby/join/:hookId controllers.Lobby.join(hookId: String)
GET /new/$ownerId<[\w\-]{12}> controllers.Lobby.hook(ownerId: String)
GET /new/$ownerId<[\w\-]{12}>/cancel controllers.Lobby.cancel(ownerId: String)
GET /new/$hookId<[\w\-]{8}>/join controllers.Lobby.join(hookId: String)
GET /lobby/socket controllers.Lobby.socket
# Lobby Private API

View File

@ -0,0 +1,5 @@
User-agent: *
Allow: /
Disallow: /new
Disallow: /setting
Disallow: /account

1
todo
View File

@ -19,6 +19,7 @@ user info is expensive - cache it
chess960 confirmation http://fr.lichess.org/forum/lichess-feedback/separate-960-lobby?page=1#7
use play-navigator router case class MyRegexStr(value: String); implicit val MyRegexStrPathParam: PathParam[MyRegexStr] = new PathParam[MyRegexStr] { def apply(s: MyRegexStr) = s.value}; def unapply(s: String) = val Rx = "(\w+)".r; s match { case Rx(x) => Some(x); case _ => None } }
http://codetunes.com/2012/05/09/scala-dsl-tutorial-writing-web-framework-router
use POST instead of GET where it makes sense
next deploy:
upgrade jre (jdk?)