This commit is contained in:
Thibault Duplessis 2012-05-18 00:35:31 +02:00
parent 464e7b7d81
commit 502235f08a
18 changed files with 306 additions and 290 deletions

View file

@ -20,7 +20,6 @@ final class AppApi(
gameRepo: GameRepo,
roundSocket: round.Socket,
messenger: round.Messenger,
starter: lobby.Starter,
eloUpdater: EloUpdater,
gameInfo: DbGame IO[GameInfo]) {
@ -50,85 +49,85 @@ final class AppApi(
} yield gameInfo
}
def join(
fullId: String,
url: String,
messages: String,
entryData: String): IO[Valid[Unit]] = for {
povOption gameRepo pov fullId
op povOption.fold(
pov for {
p1 starter.start(pov.game, entryData)
p2 messenger.systemMessages(p1.game, messages) map { evts
p1 + Event.RedirectOwner(!pov.color, url) ++ evts
}
_ gameRepo save p2
_ roundSocket send p2
} yield success(),
io(GameNotFound)
)
} yield op
//def join(
//fullId: String,
//url: String,
//messages: String,
//entryData: String): IO[Valid[Unit]] = for {
//povOption gameRepo pov fullId
//op povOption.fold(
//pov for {
//p1 starter.start(pov.game, entryData)
//p2 messenger.systemMessages(p1.game, messages) map { evts
//p1 + Event.RedirectOwner(!pov.color, url) ++ evts
//}
//_ gameRepo save p2
//_ roundSocket send p2
//} yield success(),
//io(GameNotFound)
//)
//} yield op
def start(gameId: String, entryData: String): IO[Valid[Unit]] =
gameRepo game gameId flatMap { gameOption
gameOption.fold(
g1 for {
progress starter.start(g1, entryData)
_ gameRepo save progress
_ roundSocket send progress
} yield success(Unit),
io { !!("No such game") }
)
}
//def start(gameId: String, entryData: String): IO[Valid[Unit]] =
//gameRepo game gameId flatMap { gameOption
//gameOption.fold(
//g1 for {
//progress starter.start(g1, entryData)
//_ gameRepo save progress
//_ roundSocket send progress
//} yield success(Unit),
//io { !!("No such game") }
//)
//}
def rematchAccept(
gameId: String,
newGameId: String,
colorName: String,
whiteRedirect: String,
blackRedirect: String,
entryData: String,
messageString: String): IO[Valid[Unit]] = Color(colorName).fold(
color for {
newGameOption gameRepo game newGameId
g1Option gameRepo game gameId
result (newGameOption |@| g1Option).apply(
(newGame, g1) {
val progress = Progress(g1, List(
Event.RedirectOwner(White, whiteRedirect),
Event.RedirectOwner(Black, blackRedirect),
// tell spectators to reload the table
Event.ReloadTable(White),
Event.ReloadTable(Black)))
for {
_ gameRepo save progress
_ roundSocket send progress
newProgress starter.start(newGame, entryData)
newProgress2 messenger.systemMessages(
newProgress.game, messageString
) map newProgress.++
_ gameRepo save newProgress2
_ roundSocket send newProgress2
} yield success()
}
).fold(identity, io(GameNotFound))
} yield result,
io { !!("Wrong color name") }
)
//def rematchAccept(
//gameId: String,
//newGameId: String,
//colorName: String,
//whiteRedirect: String,
//blackRedirect: String,
//entryData: String,
//messageString: String): IO[Valid[Unit]] = Color(colorName).fold(
//color for {
//newGameOption gameRepo game newGameId
//g1Option gameRepo game gameId
//result (newGameOption |@| g1Option).apply(
//(newGame, g1) {
//val progress = Progress(g1, List(
//Event.RedirectOwner(White, whiteRedirect),
//Event.RedirectOwner(Black, blackRedirect),
//// tell spectators to reload the table
//Event.ReloadTable(White),
//Event.ReloadTable(Black)))
//for {
//_ gameRepo save progress
//_ roundSocket send progress
//newProgress starter.start(newGame, entryData)
//newProgress2 messenger.systemMessages(
//newProgress.game, messageString
//) map newProgress.++
//_ gameRepo save newProgress2
//_ roundSocket send newProgress2
//} yield success()
//}
//).fold(identity, io(GameNotFound))
//} yield result,
//io { !!("Wrong color name") }
//)
def reloadTable(gameId: String): IO[Valid[Unit]] = for {
g1Option gameRepo game gameId
result g1Option.fold(
g1 {
val progress = Progress(g1, Color.all map Event.ReloadTable)
for {
_ gameRepo save progress
_ roundSocket send progress
} yield success()
},
io(GameNotFound)
)
} yield result
//def reloadTable(gameId: String): IO[Valid[Unit]] = for {
//g1Option gameRepo game gameId
//result g1Option.fold(
//g1 {
//val progress = Progress(g1, Color.all map Event.ReloadTable)
//for {
//_ gameRepo save progress
//_ roundSocket send progress
//} yield success()
//},
//io(GameNotFound)
//)
//} yield result
def gameVersion(gameId: String): Future[Int] = futureVersion(gameId)

View file

@ -1,6 +1,6 @@
package lila
package lobby
import lobby.{ Fisherman, History, HookRepo, Hook, MessageRepo }
import timeline.EntryRepo
import game.GameRepo

View file

@ -8,68 +8,68 @@ import play.api.libs.concurrent._
object AppApi extends LilaController {
private val api = env.appApi
//private val api = env.appApi
def show(fullId: String) = Action {
Async {
(api show fullId).asPromise map { op
op.unsafePerformIO.fold(e BadRequest(e.shows), JsonOk)
}
}
}
//def show(fullId: String) = Action {
//Async {
//(api show fullId).asPromise map { op
//op.unsafePerformIO.fold(e BadRequest(e.shows), JsonOk)
//}
//}
//}
def reloadTable(gameId: String) = Action {
ValidIOk(api reloadTable gameId)
}
//def reloadTable(gameId: String) = Action {
//ValidIOk(api reloadTable gameId)
//}
def start(gameId: String) = Action { implicit req
FormValidIOk[EntryData](entryForm)(entryData api.start(gameId, entryData))
}
//def start(gameId: String) = Action { implicit req
//FormValidIOk[EntryData](entryForm)(entryData api.start(gameId, entryData))
//}
def join(fullId: String) = Action { implicit req
FormValidIOk[JoinData](joinForm) { join
api.join(fullId, join._1, join._2, join._3)
}
}
//def join(fullId: String) = Action { implicit req
//FormValidIOk[JoinData](joinForm) { join
//api.join(fullId, join._1, join._2, join._3)
//}
//}
def activity(gameId: String, color: String) = Action {
Async {
api.isConnected(gameId, color).asPromise map { bool
Ok(bool.fold(1, 0))
}
}
}
//def activity(gameId: String, color: String) = Action {
//Async {
//api.isConnected(gameId, color).asPromise map { bool
//Ok(bool.fold(1, 0))
//}
//}
//}
def gameVersion(gameId: String) = Action {
Async {
(api gameVersion gameId).asPromise map { Ok(_) }
}
}
//def gameVersion(gameId: String) = Action {
//Async {
//(api gameVersion gameId).asPromise map { Ok(_) }
//}
//}
def gameInfo(gameId: String) = Action {
(api gameInfo gameId).unsafePerformIO.fold(
info JsonOk(info.toMap),
BadRequest("No such game")
)
}
//def gameInfo(gameId: String) = Action {
//(api gameInfo gameId).unsafePerformIO.fold(
//info JsonOk(info.toMap),
//BadRequest("No such game")
//)
//}
def rematchAccept(gameId: String, color: String, newGameId: String) = Action { implicit req
FormValidIOk[RematchData](rematchForm)(r
api.rematchAccept(gameId, newGameId, color, r._1, r._2, r._3, r._4))
}
//def rematchAccept(gameId: String, color: String, newGameId: String) = Action { implicit req
//FormValidIOk[RematchData](rematchForm)(r
//api.rematchAccept(gameId, newGameId, color, r._1, r._2, r._3, r._4))
//}
def adjust(username: String) = Action {
IOk(api adjust username)
}
//def adjust(username: String) = Action {
//IOk(api adjust username)
//}
def captcha = Action {
env.site.captcha.create.unsafePerformIO.fold(
err BadRequest(err.shows),
data JsonOk(Map(
"id" -> data._1,
"fen" -> data._2,
"color" -> data._3.toString
))
)
}
//def captcha = Action {
//env.site.captcha.create.unsafePerformIO.fold(
//err BadRequest(err.shows),
//data JsonOk(Map(
//"id" -> data._1,
//"fen" -> data._2,
//"color" -> data._3.toString
//))
//)
//}
}

View file

@ -14,8 +14,8 @@ import play.api.libs.iteratee._
object Lobby extends LilaController {
private val api = env.lobby.api
private val preloader = env.lobby.preloader
//private val api = env.lobby.api
private val preloader = env.preloader
val home = Open { implicit ctx
renderHome(ctx).fold(identity, Ok(_))
@ -46,10 +46,10 @@ object Lobby extends LilaController {
)
}
def cancel(ownerId: String) = Action {
api.cancel(ownerId).unsafePerformIO
Redirect("/")
}
def cancel(ownerId: String) = TODO
//api.cancel(ownerId).unsafePerformIO
//Redirect("/")
//}
def join(hookId: String) = TODO
@ -59,11 +59,11 @@ object Lobby extends LilaController {
//)
//}
def create(hookOwnerId: String) = Action {
IOk(api create hookOwnerId)
}
//def create(hookOwnerId: String) = Action {
//IOk(api create hookOwnerId)
//}
def chatBan(username: String) = Action {
IOk(env.lobby.messenger ban username)
}
//def chatBan(username: String) = Action {
//IOk(env.lobby.messenger ban username)
//}
}

View file

@ -30,20 +30,21 @@ final class CoreEnv private (application: Application, val settings: Settings) {
settings = settings,
mongodb = mongodb.apply _,
userRepo = user.userRepo,
gameRepo = game.gameRepo,
roundSocket = round.socket,
roundMessenger = round.messenger,
entryRepo = timeline.entryRepo,
ai = ai.ai)
roundMessenger = round.messenger)
lazy val setup = new lila.setup.SetupEnv(
settings = settings,
mongodb = mongodb.apply _,
gameRepo = game.gameRepo)
gameRepo = game.gameRepo,
timelinePush = timeline.push.apply,
ai = ai.ai)
lazy val timeline = new lila.timeline.TimelineEnv(
settings = settings,
mongodb = mongodb.apply _)
mongodb = mongodb.apply _,
lobbyNotify = lobby.socket.addEntry,
getUsername = user.cached.username)
lazy val ai = new lila.ai.AiEnv(
settings = settings)
@ -71,14 +72,22 @@ final class CoreEnv private (application: Application, val settings: Settings) {
settings = settings,
gameRepo = game.gameRepo)
lazy val appApi = new AppApi(
userRepo = user.userRepo,
lazy val preloader = new Preload(
fisherman = lobby.fisherman,
history = lobby.history,
hookRepo = lobby.hookRepo,
gameRepo = game.gameRepo,
roundSocket = round.socket,
messenger = round.messenger,
starter = lobby.starter,
eloUpdater = user.eloUpdater,
gameInfo = analyse.gameInfo)
messageRepo = lobby.messageRepo,
entryRepo = timeline.entryRepo)
//lazy val appApi = new AppApi(
//userRepo = user.userRepo,
//gameRepo = game.gameRepo,
//roundSocket = round.socket,
//messenger = round.messenger,
//starter = lobby.starter,
//eloUpdater = user.eloUpdater,
//gameInfo = analyse.gameInfo)
lazy val mongodb = MongoConnection(
new MongoServer(MongoHost, MongoPort),

View file

@ -14,7 +14,6 @@ import play.api.mvc.Call
trait GameHelper { self: I18nHelper with UserHelper
val anonPlayerName = "Anonymous"
val aiName = "Crafty A.I."
def variantName(variant: Variant)(implicit ctx: Context) = variant match {
@ -28,13 +27,7 @@ trait GameHelper { self: I18nHelper with UserHelper ⇒
def clockName(clock: Clock): String = "%d minutes/side + %d seconds/move".format(
clock.limitInMinutes, clock.increment)
def usernameWithElo(player: DbPlayer) =
player.aiLevel.fold(
level "A.I. level " + level,
(player.userId map userIdToUsername).fold(
username "%s (%s)".format(username, player.elo getOrElse "?"),
anonPlayerName)
)
def usernameWithElo(player: DbPlayer) = PlayerNamer(player)(userIdToUsername)
def playerLink(player: DbPlayer, cssClass: Option[String] = None) = Html {
player.userId.fold(

View file

@ -0,0 +1,15 @@
package lila
package game
object PlayerNamer {
val anonPlayerName = "Anonymous"
def apply(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)
)
}

View file

@ -21,6 +21,8 @@ case class Pov(game: DbGame, color: Color) {
fullId some { game.isPlayerFullId(player, _) } none false
def ref = PovRef(game.id, color)
def withGame(g: DbGame) = Pov(g, color)
}
object Pov {

View file

@ -12,40 +12,39 @@ final class Api(
fisherman: Fisherman,
gameRepo: GameRepo,
roundSocket: RoundSocket,
roundMessenger: RoundMessenger,
starter: Starter) {
roundMessenger: RoundMessenger) {
def cancel(ownerId: String): IO[Unit] = for {
hook hookRepo ownedHook ownerId
_ hook.fold(fisherman.delete, io())
} yield ()
def join(
gameId: String,
colorName: String,
entryData: String,
messageString: String,
hookOwnerId: String,
myHookOwnerId: Option[String]): IO[Valid[Unit]] = for {
hook hookRepo ownedHook hookOwnerId
gameOption gameRepo game gameId
result (Color(colorName) |@| gameOption).apply(
(color, game) {
for {
p1 starter.start(game, entryData)
p2 roundMessenger.systemMessages(game, messageString) map p1.++
_ gameRepo save p2
_ roundSocket send p2
_ hook.fold(h fisherman.bite(h, p2.game), io())
_ myHookOwnerId.fold(
ownerId hookRepo ownedHook ownerId flatMap { myHook
myHook.fold(fisherman.delete, io())
},
io())
} yield success()
}
).fold(identity, io(GameNotFound))
} yield result
//def join(
//gameId: String,
//colorName: String,
//entryData: String,
//messageString: String,
//hookOwnerId: String,
//myHookOwnerId: Option[String]): IO[Valid[Unit]] = for {
//hook hookRepo ownedHook hookOwnerId
//gameOption gameRepo game gameId
//result (Color(colorName) |@| gameOption).apply(
//(color, game) {
//for {
//p1 starter.start(game, entryData)
//p2 roundMessenger.systemMessages(game, messageString) map p1.++
//_ gameRepo save p2
//_ roundSocket send p2
//_ hook.fold(h fisherman.bite(h, p2.game), io())
//_ myHookOwnerId.fold(
//ownerId hookRepo ownedHook ownerId flatMap { myHook
//myHook.fold(fisherman.delete, io())
//},
//io())
//} yield success()
//}
//).fold(identity, io(GameNotFound))
//} yield result
def create(hookOwnerId: String): IO[Unit] = for {
hook hookRepo ownedHook hookOwnerId

View file

@ -13,7 +13,6 @@ import play.api.i18n.MessagesPlugin
import user.UserRepo
import game.GameRepo
import round.{ Socket RoundSocket, Messenger RoundMessenger }
import timeline.EntryRepo
import ai.Ai
import core.Settings
@ -22,21 +21,12 @@ final class LobbyEnv(
settings: Settings,
mongodb: String MongoCollection,
userRepo: UserRepo,
gameRepo: GameRepo,
roundSocket: RoundSocket,
roundMessenger: RoundMessenger,
entryRepo: EntryRepo,
ai: () Ai) {
roundMessenger: RoundMessenger) {
implicit val ctx = app
import settings._
lazy val starter = new Starter(
gameRepo = gameRepo,
entryRepo = entryRepo,
ai = ai,
socket = socket)
lazy val history = new History(timeout = LobbyMessageLifetime)
lazy val messenger = new Messenger(
@ -51,14 +41,6 @@ final class LobbyEnv(
lazy val socket = new Socket(hub = hub)
lazy val preloader = new Preload(
fisherman = fisherman,
history = history,
hookRepo = hookRepo,
gameRepo = gameRepo,
messageRepo = messageRepo,
entryRepo = entryRepo)
lazy val fisherman = new Fisherman(
hookRepo = hookRepo,
hookMemo = hookMemo,
@ -68,13 +50,13 @@ 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 api = new Api(
//hookRepo = hookRepo,
//fisherman = fisherman,
//gameRepo = gameRepo,
//roundSocket = roundSocket,
//roundMessenger = roundMessenger,
//starter = starter)
lazy val hookRepo = new HookRepo(mongodb(MongoCollectionHook))

View file

@ -1,27 +0,0 @@
package lila
package lobby
import timeline.{ EntryRepo, Entry }
import game.{ GameRepo, DbGame }
import round.{ Progress }
import ai.Ai
import scalaz.effects._
final class Starter(
gameRepo: GameRepo,
entryRepo: EntryRepo,
socket: Socket,
ai: () Ai) {
def start(game: DbGame, entryData: String): IO[Progress] = for {
_ if (game.variant.standard) io() else gameRepo saveInitialFen game
_ Entry(game, entryData).fold(
entry entryRepo add entry flatMap { _ socket addEntry entry },
io())
progress if (game.player.isHuman) io(Progress(game)) else for {
aiResult ai()(game) map (_.err)
(newChessGame, move) = aiResult
} yield game.update(newChessGame, move)
} yield progress
}

View file

@ -2,14 +2,17 @@ package lila
package setup
import http.Context
import game.{ GameRepo, Pov }
import game.{ DbGame, GameRepo, Pov }
import chess.{ Game, Board }
import ai.Ai
import scalaz.effects._
final class Processor(
configRepo: UserConfigRepo,
gameRepo: GameRepo) {
gameRepo: GameRepo,
timelinePush: DbGame IO[Unit],
ai: () Ai) {
def ai(config: AiConfig)(implicit ctx: Context): IO[Pov] = for {
_ ctx.me.fold(
@ -17,6 +20,18 @@ final class Processor(
io()
)
pov = config.pov
_ gameRepo insert pov.game
} yield pov
game = pov.game
_ gameRepo insert game
_ game.variant.standard.fold(io(), gameRepo saveInitialFen game)
_ timelinePush(game)
pov2 game.player.isHuman.fold(
io(pov),
for {
aiResult ai()(game) map (_.err)
(newChessGame, move) = aiResult
progress = game.update(newChessGame, move)
_ gameRepo save progress
} yield pov withGame progress.game
)
} yield pov2
}

View file

@ -2,14 +2,18 @@ package lila
package setup
import com.mongodb.casbah.MongoCollection
import scalaz.effects._
import core.Settings
import game.GameRepo
import game.{ DbGame, GameRepo }
import ai.Ai
final class SetupEnv(
settings: Settings,
mongodb: String MongoCollection,
gameRepo: GameRepo) {
gameRepo: GameRepo,
timelinePush: DbGame IO[Unit],
ai: () Ai) {
import settings._
@ -20,5 +24,7 @@ final class SetupEnv(
lazy val processor = new Processor(
configRepo = configRepo,
gameRepo = gameRepo)
gameRepo = gameRepo,
timelinePush = timelinePush,
ai = ai)
}

View file

@ -24,8 +24,8 @@ class UserConfigRepo(collection: MongoCollection)
def save(config: UserConfig): IO[Unit] = io {
update(
DBObject("_id" -> config.id).pp,
_grater asDBObject config.encode.pp,
DBObject("_id" -> config.id),
_grater asDBObject config.encode,
upsert = true,
wc = WriteConcern.Safe)
}

View file

@ -1,8 +1,6 @@
package lila
package timeline
import game.DbGame
import com.novus.salat.annotations._
import com.mongodb.BasicDBList
@ -32,22 +30,3 @@ case class Entry(
rated ? "Rated" | "Casual",
clock | "Unlimited")
}
object Entry {
def apply(game: DbGame, encodedData: String): Option[Entry] =
encodedData.split('$').toList match {
case wu :: wue :: bu :: bue :: Nil Some(
new Entry(
gameId = game.id,
whiteName = wue,
blackName = bue,
whiteId = Some(wu) filter (_.nonEmpty),
blackId = Some(bu) filter (_.nonEmpty),
variant = game.variant.name,
rated = game.isRated,
clock = game.clock map (_.show))
)
case _ None
}
}

36
app/timeline/Push.scala Normal file
View file

@ -0,0 +1,36 @@
package lila
package timeline
import chess.Color
import game.{ DbGame, PlayerNamer }
import scalaz.effects._
final class Push(
entryRepo: EntryRepo,
lobbyNotify: Entry IO[Unit],
getUsername: String String) {
def apply(game: DbGame): IO[Unit] = makeEntry(game) |> { entry
for {
_ entryRepo add entry
_ lobbyNotify(entry)
} yield ()
}
private def makeEntry(game: DbGame) = Entry(
gameId = game.id,
whiteName = usernameElo(game, Color.White),
blackName = usernameElo(game, Color.Black),
whiteId = userId(game, Color.White),
blackId = userId(game, Color.Black),
variant = game.variant.name,
rated = game.isRated,
clock = game.clock map (_.show))
private def userId(game: DbGame, color: Color): Option[String] =
(game player color).userId
private def usernameElo(game: DbGame, color: Color): String =
PlayerNamer(game player color)(getUsername)
}

View file

@ -2,16 +2,24 @@ package lila
package timeline
import com.mongodb.casbah.MongoCollection
import scalaz.effects._
import core.Settings
final class TimelineEnv(
settings: Settings,
mongodb: String MongoCollection) {
mongodb: String MongoCollection,
lobbyNotify: Entry IO[Unit],
getUsername: String => String) {
import settings._
lazy val entryRepo = new EntryRepo(
collection = mongodb(settings.MongoCollectionEntry),
max = LobbyEntryMax)
lazy val push = new Push(
entryRepo = entryRepo,
lobbyNotify = lobbyNotify,
getUsername = getUsername)
}

View file

@ -63,17 +63,17 @@ GET /socket/:gameId/:color controllers.App.gameSocket(gameId: String,
GET /ai controllers.Ai.run
# App Private API
POST /api/start/:gameId controllers.AppApi.start(gameId: String)
POST /api/join/$fullId<[\w\-]{12}> controllers.AppApi.join(fullId: String)
POST /api/reload-table/:gameId controllers.AppApi.reloadTable(gameId: String)
POST /api/adjust/:username controllers.AppApi.adjust(username: String)
GET /api/activity/:gameId/:color controllers.AppApi.activity(gameId: String, color: String)
GET /api/game-version/:gameId controllers.AppApi.gameVersion(gameId: String)
GET /api/game-info/:gameId controllers.AppApi.gameInfo(gameId: String)
POST /api/rematch-accept/:gameId/:color/:newGameId controllers.AppApi.rematchAccept(gameId: String, color: String, newGameId: String)
GET /api/captcha/create controllers.Captcha.create
GET /api/captcha/solve/:gameId controllers.Captcha.solve(gameId: String)
#POST /api/start/:gameId controllers.AppApi.start(gameId: String)
#POST /api/join/$fullId<[\w\-]{12}> controllers.AppApi.join(fullId: String)
#POST /api/reload-table/:gameId controllers.AppApi.reloadTable(gameId: String)
#POST /api/adjust/:username controllers.AppApi.adjust(username: String)
#GET /api/activity/:gameId/:color controllers.AppApi.activity(gameId: String, color: String)
#GET /api/game-version/:gameId controllers.AppApi.gameVersion(gameId: String)
#GET /api/game-info/:gameId controllers.AppApi.gameInfo(gameId: String)
#POST /api/rematch-accept/:gameId/:color/:newGameId controllers.AppApi.rematchAccept(gameId: String, color: String, newGameId: String)
#
#GET /api/captcha/create controllers.Captcha.create
#GET /api/captcha/solve/:gameId controllers.Captcha.solve(gameId: String)
# Lobby Public API
GET / controllers.Lobby.home
@ -84,8 +84,8 @@ GET /lobby/socket controllers.Lobby.socket
# Lobby Private API
#POST /api/lobby/join/:gameId/:color controllers.Lobby.join(gameId: String, color: String)
#GET /api/lobby/preload controllers.Lobby.preload
POST /api/lobby/create/:hookOwnerId controllers.Lobby.create(hookOwnerId: String)
POST /api/lobby/chat-ban/:username controllers.Lobby.chatBan(username: String)
#POST /api/lobby/create/:hookOwnerId controllers.Lobby.create(hookOwnerId: String)
#POST /api/lobby/chat-ban/:username controllers.Lobby.chatBan(username: String)
# Reporting API
GET /nb-players controllers.Report.nbPlayers