Start lobby implementation, fix tests, and more
parent
59c8081005
commit
e1fb905119
|
@ -11,7 +11,7 @@ import scala.io.Codec
|
|||
import com.codahale.jerkson.Json
|
||||
import scalaz.effects.IO
|
||||
|
||||
trait LilaController extends Controller with ContentTypes {
|
||||
trait LilaController extends Controller with ContentTypes with RequestGetter {
|
||||
|
||||
lazy val env = Global.env
|
||||
|
||||
|
@ -30,12 +30,6 @@ trait LilaController extends Controller with ContentTypes {
|
|||
|
||||
def IOk(op: IO[Unit]) = Ok(op.unsafePerformIO)
|
||||
|
||||
def get(name: String)(implicit request: Request[_]) =
|
||||
request.queryString get name flatMap (_.headOption)
|
||||
|
||||
def getInt(name: String)(implicit request: Request[_]) =
|
||||
get(name)(request) map (_.toInt)
|
||||
|
||||
// I like Unit requests.
|
||||
implicit def wUnit: Writeable[Unit] =
|
||||
Writeable[Unit](_ ⇒ Codec toUTF8 "ok")
|
||||
|
|
|
@ -10,7 +10,15 @@ object LobbyXhrC extends LilaController {
|
|||
|
||||
private val xhr = env.lobbyXhr
|
||||
|
||||
def sync() = Action {
|
||||
Ok("")
|
||||
def syncWithHook(hookId: String) = sync(Some(hookId))
|
||||
|
||||
def syncWithoutHook() = sync(None)
|
||||
|
||||
private def sync(hookId: Option[String]) = Action { implicit request =>
|
||||
JsonOk(xhr.sync(
|
||||
getIntOr("auth", 0) == 1,
|
||||
getOr("l", "en"),
|
||||
getIntOr("state", 0)
|
||||
).unsafePerformIO)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package controllers
|
||||
|
||||
import play.api.mvc.Request
|
||||
|
||||
trait RequestGetter {
|
||||
|
||||
def get(name: String)(implicit request: Request[_]) =
|
||||
request.queryString get name flatMap (_.headOption)
|
||||
|
||||
def getInt(name: String)(implicit request: Request[_]) =
|
||||
get(name)(request) map (_.toInt)
|
||||
|
||||
def getOr(name: String, default: String)(implicit request: Request[_]) =
|
||||
get(name) getOrElse default
|
||||
|
||||
def getIntOr(name: String, default: Int)(implicit request: Request[_]) =
|
||||
getInt(name) getOrElse default
|
||||
}
|
|
@ -5,6 +5,7 @@ mongo {
|
|||
collection {
|
||||
game = game2
|
||||
user = user
|
||||
hook = hook
|
||||
}
|
||||
}
|
||||
redis {
|
||||
|
@ -15,6 +16,12 @@ sync {
|
|||
duration = 7 seconds
|
||||
sleep = 200 milliseconds
|
||||
}
|
||||
lobby {
|
||||
poll {
|
||||
duration = 10 seconds
|
||||
sleep = 300 milliseconds
|
||||
}
|
||||
}
|
||||
memo {
|
||||
version.timeout = 30 minutes
|
||||
alive.hard_timeout = 100 seconds
|
||||
|
|
27
conf/routes
27
conf/routes
|
@ -10,23 +10,24 @@ GET /ping controllers.AppXhrC.ping()
|
|||
GET /how-many-players-now controllers.AppXhrC.nbPlayers()
|
||||
|
||||
# App Private API
|
||||
POST /internal/update-version/:gameId controllers.AppApiC.updateVersion(gameId: String)
|
||||
POST /internal/end/:gameId controllers.AppApiC.end(gameId: String)
|
||||
POST /internal/talk/:fullId controllers.AppApiC.talk(fullId: String)
|
||||
POST /internal/join/:fullId controllers.AppApiC.join(fullId: String)
|
||||
POST /internal/reload-table/:gameId controllers.AppApiC.reloadTable(gameId: String)
|
||||
POST /internal/accept-rematch/:gameId/:color/:newGameId controllers.AppApiC.acceptRematch(gameId: String, color: String, newGameId: String)
|
||||
POST /internal/alive/:gameId/:color controllers.AppApiC.alive(gameId: String, color: String)
|
||||
POST /internal/draw/:gameId/:color controllers.AppApiC.draw(gameId: String, color: String)
|
||||
POST /internal/draw-accept/:gameId/:color controllers.AppApiC.drawAccept(gameId: String, color: String)
|
||||
GET /internal/activity/:gameId/:color controllers.AppApiC.activity(gameId: String, color: String)
|
||||
GET /internal/nb-players controllers.AppXhrC.nbPlayers()
|
||||
POST /api/update-version/:gameId controllers.AppApiC.updateVersion(gameId: String)
|
||||
POST /api/end/:gameId controllers.AppApiC.end(gameId: String)
|
||||
POST /api/talk/:fullId controllers.AppApiC.talk(fullId: String)
|
||||
POST /api/join/:fullId controllers.AppApiC.join(fullId: String)
|
||||
POST /api/reload-table/:gameId controllers.AppApiC.reloadTable(gameId: String)
|
||||
POST /api/accept-rematch/:gameId/:color/:newGameId controllers.AppApiC.acceptRematch(gameId: String, color: String, newGameId: String)
|
||||
POST /api/alive/:gameId/:color controllers.AppApiC.alive(gameId: String, color: String)
|
||||
POST /api/draw/:gameId/:color controllers.AppApiC.draw(gameId: String, color: String)
|
||||
POST /api/draw-accept/:gameId/:color controllers.AppApiC.drawAccept(gameId: String, color: String)
|
||||
GET /api/activity/:gameId/:color controllers.AppApiC.activity(gameId: String, color: String)
|
||||
GET /api/nb-players controllers.AppXhrC.nbPlayers()
|
||||
|
||||
# Lobby XHR
|
||||
GET /lobby/sync controllers.LobbyXhrC.sync()
|
||||
GET /lobby/sync/:hookId controllers.LobbyXhrC.syncWithHook(hookId: String)
|
||||
GET /lobby/sync controllers.LobbyXhrC.syncWithoutHook()
|
||||
|
||||
# Lobby Private API
|
||||
POST /internal/lobby/join/:gameId/:color controllers.LobbyApiC.join(gameId: String, color: String)
|
||||
POST /api/lobby/join/:gameId/:color controllers.LobbyApiC.join(gameId: String, color: String)
|
||||
|
||||
# Useless, but play2 needs it
|
||||
GET /assets/*file controllers.Assets.at(path="/public", file)
|
||||
|
|
|
@ -2,12 +2,44 @@ package lila.system
|
|||
|
||||
import model._
|
||||
import memo._
|
||||
import db.GameRepo
|
||||
import lila.chess.{ Color, White, Black }
|
||||
import db._
|
||||
import scalaz.effects._
|
||||
import scala.annotation.tailrec
|
||||
import scala.math.max
|
||||
|
||||
final class LobbyXhr(
|
||||
gameRepo: GameRepo,
|
||||
versionMemo: VersionMemo,
|
||||
aliveMemo: AliveMemo) {
|
||||
hookRepo: HookRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
duration: Int,
|
||||
sleep: Int) {
|
||||
|
||||
def sync(
|
||||
auth: Boolean,
|
||||
lang: String,
|
||||
version: Int): IO[Map[String, Any]] = for {
|
||||
newVersion ← versionWait(version)
|
||||
hooks ← if (auth) hookRepo.allOpen else hookRepo.allOpenCasual
|
||||
} yield Map(
|
||||
"state" -> newVersion,
|
||||
"pool" -> {
|
||||
if (hooks.nonEmpty) Map("hooks" -> renderHooks(hooks, None))
|
||||
else Map("message" -> "No game available right now, create one!")
|
||||
}
|
||||
)
|
||||
|
||||
private def renderHooks(hooks: List[Hook], myHookId: Option[String]) = for {
|
||||
hook ← hooks
|
||||
} yield hook.render ++ {
|
||||
if (myHookId == hook.ownerId) Map("action" -> "cancel", "id" -> myHookId)
|
||||
else Map("action" -> "join", "id" -> hook.id)
|
||||
}
|
||||
|
||||
private def versionWait(version: Int): IO[Int] = io {
|
||||
@tailrec
|
||||
def wait(loop: Int): Int = {
|
||||
if (loop == 0 || lobbyMemo.version != version) lobbyMemo.version
|
||||
else { Thread sleep sleep; wait(loop - 1) }
|
||||
}
|
||||
wait(max(1, duration / sleep))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ final class Syncer(
|
|||
fullId: Option[String]): IO[Map[String, Any]] = {
|
||||
for {
|
||||
color ← io { Color(colorString) err "Invalid color" }
|
||||
_ ← io { versionWait(gameId, color, version) }
|
||||
_ ← versionWait(gameId, color, version)
|
||||
gameAndPlayer ← gameRepo.player(gameId, color)
|
||||
(game, player) = gameAndPlayer
|
||||
isPrivate = fullId some { game.isPlayerFullId(player, _) } none false
|
||||
|
@ -60,7 +60,7 @@ final class Syncer(
|
|||
"html" -> """<li class="%s">%s</li>""".format(author, escapeXml(message))
|
||||
)
|
||||
|
||||
private def versionWait(gameId: String, color: Color, version: Int) {
|
||||
private def versionWait(gameId: String, color: Color, version: Int) = io {
|
||||
@tailrec
|
||||
def wait(loop: Int): Unit = {
|
||||
if (loop == 0 || versionMemo.get(gameId, color) != version) ()
|
||||
|
|
|
@ -21,9 +21,10 @@ final class SystemEnv(config: Config) {
|
|||
aliveMemo = aliveMemo)
|
||||
|
||||
lazy val lobbyXhr = new LobbyXhr(
|
||||
gameRepo = gameRepo,
|
||||
versionMemo = versionMemo,
|
||||
aliveMemo = aliveMemo)
|
||||
hookRepo = hookRepo,
|
||||
lobbyMemo = lobbyMemo,
|
||||
duration = getMilliseconds("lobby.poll.duration"),
|
||||
sleep = getMilliseconds("lobby.poll.sleep"))
|
||||
|
||||
lazy val lobbyApi = new LobbyApi(
|
||||
gameRepo = gameRepo,
|
||||
|
@ -55,6 +56,9 @@ final class SystemEnv(config: Config) {
|
|||
lazy val userRepo = new UserRepo(
|
||||
mongodb(config getString "mongo.collection.user"))
|
||||
|
||||
lazy val hookRepo = new HookRepo(
|
||||
mongodb(config getString "mongo.collection.hook"))
|
||||
|
||||
lazy val mongodb = MongoConnection(
|
||||
config getString "mongo.host",
|
||||
config getInt "mongo.port"
|
||||
|
@ -74,6 +78,8 @@ final class SystemEnv(config: Config) {
|
|||
lazy val watcherMemo = new WatcherMemo(
|
||||
timeout = getMilliseconds("memo.watcher.timeout"))
|
||||
|
||||
lazy val lobbyMemo = new LobbyMemo
|
||||
|
||||
def getMilliseconds(name: String): Int = (config getMilliseconds name).toInt
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import model.Hook
|
||||
|
||||
import com.novus.salat._
|
||||
import com.novus.salat.dao._
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
class HookRepo(collection: MongoCollection)
|
||||
extends SalatDAO[Hook, String](collection) {
|
||||
|
||||
def allOpen = hookList(DBObject(
|
||||
"match" -> false
|
||||
))
|
||||
|
||||
def allOpenCasual = hookList(DBObject(
|
||||
"match" -> false,
|
||||
"mode" -> 0
|
||||
))
|
||||
|
||||
def hookList(query: MongoDBObject): IO[List[Hook]] = io {
|
||||
find(query) sort DBObject("createdAt" -> 1) toList
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
final class LobbyMemo {
|
||||
|
||||
val version: Int = 0
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package lila.system
|
||||
package model
|
||||
|
||||
case class Hook(
|
||||
id: String,
|
||||
ownerId: String,
|
||||
variant: Int,
|
||||
hasClock: Boolean,
|
||||
time: Option[Int],
|
||||
increment: Option[Int],
|
||||
mode: Int,
|
||||
color: String,
|
||||
username: String,
|
||||
elo: Option[Int],
|
||||
`match`: Boolean,
|
||||
eloRange: Option[String],
|
||||
engine: Boolean) {
|
||||
|
||||
def realVariant = Variant(variant) | Standard
|
||||
|
||||
def realMode = Mode(mode) | Casual
|
||||
|
||||
def eloMin = eloRange map (_ takeWhile ('-' !=))
|
||||
|
||||
def eloMax = eloRange map (_ dropWhile ('-' !=) tail)
|
||||
|
||||
def render = Map(
|
||||
"username" -> username,
|
||||
"elo" -> elo,
|
||||
"variant" -> realVariant.toString,
|
||||
"mode" -> realMode.toString,
|
||||
"color" -> color,
|
||||
"clock" -> (if (hasClock) time + " + " + increment else "Unlimited"),
|
||||
"emin" -> eloMin,
|
||||
"emax" -> eloMax
|
||||
) +? (engine, "engine" -> true)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package lila.system
|
||||
package model
|
||||
|
||||
sealed abstract class Mode(val id: Int)
|
||||
|
||||
case object Casual extends Mode(0)
|
||||
case object Rated extends Mode(1)
|
||||
|
||||
object Mode {
|
||||
|
||||
val all = List(Casual, Rated)
|
||||
|
||||
val byId = all map { v ⇒ (v.id, v) } toMap
|
||||
|
||||
def apply(id: Int): Option[Mode] = byId get id
|
||||
}
|
|
@ -21,6 +21,10 @@ package object system
|
|||
def pp[A] = a ~ println
|
||||
}
|
||||
|
||||
implicit def richerMap[A, B](m: Map[A, B]) = new {
|
||||
def +?(bp: (Boolean, (A, B))): Map[A, B] = if (bp._1) m + bp._2 else m
|
||||
}
|
||||
|
||||
def parseIntOption(str: String): Option[Int] = try {
|
||||
Some(java.lang.Integer.parseInt(str))
|
||||
}
|
||||
|
|
|
@ -5,18 +5,18 @@ import model._
|
|||
import scalaz.effects._
|
||||
import scalaz.{ Success, Failure }
|
||||
|
||||
class ServerTest extends SystemTest {
|
||||
class AppXhrTest extends SystemTest {
|
||||
|
||||
val env = SystemEnv()
|
||||
val repo = env.gameRepo
|
||||
val server = env.server
|
||||
val xhr = env.appXhr
|
||||
|
||||
def insert(dbGame: DbGame = newDbGameWithRandomIds()): IO[DbGame] = for {
|
||||
_ ← repo insert dbGame
|
||||
} yield dbGame
|
||||
|
||||
def move(game: DbGame, m: String = "d2 d4"): IO[Valid[Unit]] =
|
||||
server.playMove(game fullIdOf White, m)
|
||||
xhr.playMove(game fullIdOf White, m)
|
||||
|
||||
def updated(
|
||||
game: DbGame = newDbGameWithRandomIds,
|
Loading…
Reference in New Issue