Checkpoint before multisocketing
This commit is contained in:
parent
b98154109c
commit
8f1677e198
|
@ -12,6 +12,7 @@ import scalaz.effects._
|
|||
import socket._
|
||||
import lobby._
|
||||
import game._
|
||||
import RichDuration._
|
||||
|
||||
final class Cron(env: SystemEnv)(implicit app: Application) {
|
||||
|
||||
|
@ -22,10 +23,12 @@ final class Cron(env: SystemEnv)(implicit app: Application) {
|
|||
env.lobbyHub -> WithHooks(env.hookMemo.putAll)
|
||||
}
|
||||
|
||||
spawn("nb_players", 5 seconds) {
|
||||
Future.traverse(env.lobbyHub :: env.gameHubMaster :: Nil)(a ⇒
|
||||
(a ? GetNbMembers).mapTo[Int]
|
||||
) map (xs ⇒ NbPlayers(xs.sum)) pipeTo env.lobbyHub pipeTo env.gameHubMaster
|
||||
spawn("nb_players", 1 seconds) {
|
||||
pipeToHubs {
|
||||
Future.traverse(hubs)(a ⇒
|
||||
(a ? GetNbMembers).mapTo[Int]
|
||||
) map (xs ⇒ NbPlayers(xs.sum))
|
||||
}
|
||||
}
|
||||
|
||||
spawnIO("hook_cleanup_dead", 2 seconds) {
|
||||
|
@ -36,8 +39,14 @@ final class Cron(env: SystemEnv)(implicit app: Application) {
|
|||
env.hookRepo.cleanupOld
|
||||
}
|
||||
|
||||
spawnMessage("online_username", 3 seconds) {
|
||||
env.lobbyHub -> WithUsernames(env.userRepo.updateOnlineUsernames)
|
||||
spawn("online_username", 3 seconds) {
|
||||
Future.traverse(hubs) { a ⇒
|
||||
(a ? GetUsernames).mapTo[Iterable[String]]
|
||||
} map (_.flatten) onComplete {
|
||||
case Right(usernames) ⇒
|
||||
(env.userRepo updateOnlineUsernames usernames).unsafePerformIO
|
||||
case Left(e) ⇒ println(e)
|
||||
}
|
||||
}
|
||||
|
||||
spawnIO("game_cleanup_unplayed", 2 hours) {
|
||||
|
@ -55,14 +64,20 @@ final class Cron(env: SystemEnv)(implicit app: Application) {
|
|||
}
|
||||
|
||||
def spawn(name: String, freq: Duration)(op: ⇒ Unit) = {
|
||||
Akka.system.scheduler.schedule(freq, freq)(op)
|
||||
Akka.system.scheduler.schedule(freq, freq.randomize())(op)
|
||||
}
|
||||
|
||||
def spawnIO(name: String, freq: Duration)(op: IO[Unit]) = {
|
||||
Akka.system.scheduler.schedule(freq, freq) { op.unsafePerformIO }
|
||||
Akka.system.scheduler.schedule(freq, freq.randomize()) { op.unsafePerformIO }
|
||||
}
|
||||
|
||||
def spawnMessage(name: String, freq: Duration)(to: (ActorRef, Any)) = {
|
||||
Akka.system.scheduler.schedule(freq, freq, to._1, to._2)
|
||||
Akka.system.scheduler.schedule(freq, freq.randomize(), to._1, to._2)
|
||||
}
|
||||
|
||||
def hubs = env.siteHub :: env.lobbyHub :: env.gameHubMaster :: Nil
|
||||
|
||||
def pipeToHubs(future: Future[_]) {
|
||||
hubs foreach (future pipeTo _)
|
||||
}
|
||||
}
|
||||
|
|
18
app/Duration.scala
Normal file
18
app/Duration.scala
Normal file
|
@ -0,0 +1,18 @@
|
|||
package lila
|
||||
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
import scala.util.Random
|
||||
import scala.math.round
|
||||
|
||||
object RichDuration {
|
||||
|
||||
implicit def richDuration(d: Duration) = new {
|
||||
|
||||
def randomize(ratio: Float = 0.1f): Duration = {
|
||||
val m = d.toMillis
|
||||
val m2 = round(m + (ratio * m * 2 * Random.nextFloat) - (ratio * m))
|
||||
m2 millis
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ final class Finisher(
|
|||
def outoftimes(games: List[DbGame]): List[IO[Unit]] =
|
||||
games map { g ⇒
|
||||
outoftime(g).fold(
|
||||
msgs ⇒ putStrLn(g.id + " " + (msgs.list mkString "\n")),
|
||||
msgs ⇒ putStrLn(g.id + " " + msgs.shows),
|
||||
_ map (_ ⇒ Unit) // events are lost
|
||||
): IO[Unit]
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ final class Hand(
|
|||
|
||||
def resign(fullId: String): IOValidEvents = attempt(fullId, finisher.resign)
|
||||
|
||||
def outoftime(fullId: String): IOValidEvents = attempt(fullId, finisher outoftime _.game)
|
||||
def outoftime(ref: PovRef): IOValidEvents = attemptRef(ref, finisher outoftime _.game)
|
||||
|
||||
def drawClaim(fullId: String): IOValidEvents = attempt(fullId, finisher.drawClaim)
|
||||
|
||||
|
@ -108,15 +108,14 @@ final class Hand(
|
|||
pov.game.clock filter (_ ⇒ pov.game.playable) map { clock ⇒
|
||||
val color = !pov.color
|
||||
val newClock = clock.giveTime(color, moretimeSeconds)
|
||||
val g2 = pov.game withClock newClock
|
||||
val progress = pov.game withClock newClock
|
||||
for {
|
||||
progress ← messenger.systemMessage(
|
||||
g2, "%s + %d seconds".format(color, moretimeSeconds)
|
||||
) map { es ⇒
|
||||
Progress(pov.game, ClockEvent(newClock).pp :: es)
|
||||
}
|
||||
_ ← gameRepo save progress
|
||||
} yield progress.events
|
||||
events ← messenger.systemMessage(
|
||||
progress.game, "%s + %d seconds".format(color, moretimeSeconds)
|
||||
)
|
||||
progress2 = progress ++ (ClockEvent(newClock) :: events)
|
||||
_ ← gameRepo save progress2
|
||||
} yield progress2.events
|
||||
} toValid "cannot add moretime"
|
||||
)
|
||||
|
||||
|
|
|
@ -16,7 +16,12 @@ import command._
|
|||
|
||||
final class SystemEnv(config: Config) {
|
||||
|
||||
lazy val gameHistory = () ⇒ new socket.History(
|
||||
lazy val siteHub = Akka.system.actorOf(Props(new site.Hub), name = "site_hub")
|
||||
|
||||
lazy val siteSocket = new site.Socket(
|
||||
hub = siteHub)
|
||||
|
||||
lazy val gameHistory = () ⇒ new game.History(
|
||||
timeout = getMilliseconds("game.message.lifetime"))
|
||||
|
||||
lazy val gameHubMemo = new game.HubMemo(
|
||||
|
@ -32,7 +37,7 @@ final class SystemEnv(config: Config) {
|
|||
hubMemo = gameHubMemo,
|
||||
messenger = messenger)
|
||||
|
||||
lazy val lobbyHistory = new socket.History(
|
||||
lazy val lobbyHistory = new lobby.History(
|
||||
timeout = getMilliseconds("lobby.message.lifetime"))
|
||||
|
||||
lazy val lobbyHub = Akka.system.actorOf(Props(new lobby.Hub(
|
||||
|
|
|
@ -11,7 +11,7 @@ trait FenBased {
|
|||
newSituation ← Forsyth << fen toValid "Cannot parse engine FEN: " + fen
|
||||
reverseEngineer = new ReverseEngineering(game, newSituation.board)
|
||||
poss = reverseEngineer.move.mapFail(msgs ⇒
|
||||
("ReverseEngineering failure: " + (msgs.list mkString "\n") + "\n--------\n" + game.board + "\n" + newSituation.board + "\n" + fen).wrapNel
|
||||
("ReverseEngineering failure: " + msgs.shows + "\n--------\n" + game.board + "\n" + newSituation.board + "\n" + fen).wrapNel
|
||||
).err
|
||||
(orig, dest) = poss
|
||||
newGameAndMove ← game(orig, dest)
|
||||
|
|
|
@ -17,7 +17,7 @@ object AiC extends LilaController {
|
|||
craftyServer(fen = getOr("fen", ""), level = getIntOr("level", 1))
|
||||
} map { res ⇒
|
||||
res.fold(
|
||||
err ⇒ BadRequest(err.list mkString "\n"),
|
||||
err ⇒ BadRequest(err.shows),
|
||||
op ⇒ Ok(op.unsafePerformIO)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,11 +14,17 @@ import libs.iteratee._
|
|||
|
||||
import scalaz.effects._
|
||||
|
||||
object AppXhrC extends LilaController {
|
||||
object AppC extends LilaController {
|
||||
|
||||
private val hand = env.hand
|
||||
|
||||
def socket(gameId: String, color: String) =
|
||||
def socket = WebSocket.async[JsValue] { implicit request ⇒
|
||||
env.siteSocket.join(
|
||||
uid = get("uid") err "Socket UID missing",
|
||||
username = get("username"))
|
||||
}
|
||||
|
||||
def gameSocket(gameId: String, color: String) =
|
||||
WebSocket.async[JsValue] { implicit request ⇒
|
||||
env.gameSocket.join(
|
||||
gameId = gameId ~ { i ⇒ println("Attempt to connect to " + i) },
|
||||
|
@ -29,10 +35,6 @@ object AppXhrC extends LilaController {
|
|||
username = get("username")).unsafePerformIO
|
||||
}
|
||||
|
||||
def outoftime(fullId: String) = Action {
|
||||
IOk(perform(fullId, hand.outoftime))
|
||||
}
|
||||
|
||||
def abort(fullId: String) = performAndRedirect(fullId, hand.abort)
|
||||
|
||||
def resign(fullId: String) = performAndRedirect(fullId, hand.resign)
|
||||
|
@ -47,16 +49,12 @@ object AppXhrC extends LilaController {
|
|||
|
||||
def drawDecline(fullId: String) = performAndRedirect(fullId, hand.drawDecline)
|
||||
|
||||
def nbPlayers = Action { Ok(0) }
|
||||
|
||||
def nbGames = Action { Ok(env.gameRepo.countPlaying.unsafePerformIO) }
|
||||
|
||||
type IOValidEvents = IO[Valid[List[Event]]]
|
||||
|
||||
private def perform(fullId: String, op: String ⇒ IOValidEvents): IO[Unit] =
|
||||
op(fullId) flatMap { res ⇒
|
||||
res.fold(
|
||||
failures ⇒ putStrLn(failures.list mkString "\n"),
|
||||
putFailures,
|
||||
events ⇒ env.gameSocket.send(DbGame takeGameId fullId, events)
|
||||
)
|
||||
}
|
|
@ -19,12 +19,12 @@ trait LilaController extends Controller with ContentTypes with RequestGetter {
|
|||
def JsonIOk(map: IO[Map[String, Any]]) = JsonOk(map.unsafePerformIO)
|
||||
|
||||
def ValidOk(valid: Valid[Unit]) = valid.fold(
|
||||
e ⇒ BadRequest(e.list mkString "\n"),
|
||||
e ⇒ BadRequest(e.shows),
|
||||
_ ⇒ Ok("ok")
|
||||
)
|
||||
|
||||
def ValidIOk(valid: IO[Valid[Unit]]) = valid.unsafePerformIO.fold(
|
||||
e ⇒ BadRequest(e.list mkString "\n"),
|
||||
e ⇒ BadRequest(e.shows),
|
||||
_ ⇒ Ok("ok")
|
||||
)
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class GameRepo(collection: MongoCollection)
|
|||
d(name + "lastDrawOffer", _.players(i).lastDrawOffer)
|
||||
d(name + "isOfferingDraw", _.players(i).isOfferingDraw)
|
||||
}
|
||||
a.clock.pp foreach { c ⇒
|
||||
a.clock foreach { c ⇒
|
||||
d("clock.c", _.clock.get.c)
|
||||
d("clock.w", _.clock.get.w)
|
||||
d("clock.b", _.clock.get.b)
|
||||
|
|
|
@ -32,7 +32,7 @@ class UserRepo(collection: MongoCollection)
|
|||
}
|
||||
|
||||
def updateOnlineUsernames(usernames: Iterable[String]): IO[Unit] = io {
|
||||
val names = usernames map (_.toLowerCase)
|
||||
val names = usernames.toList.map(_.toLowerCase).distinct
|
||||
collection.update(
|
||||
("usernameCanonical" $nin names) ++ ("isOnline" -> true),
|
||||
$set ("isOnline" -> false),
|
||||
|
|
45
app/game/History.scala
Normal file
45
app/game/History.scala
Normal file
|
@ -0,0 +1,45 @@
|
|||
package lila
|
||||
package game
|
||||
|
||||
import scala.math.max
|
||||
import play.api.libs.json._
|
||||
import scalaz.effects._
|
||||
|
||||
import chess.Color
|
||||
import model.Event
|
||||
import memo.Builder
|
||||
|
||||
final class History(timeout: Int) {
|
||||
|
||||
case class VersionedEvent(js: JsObject, only: Option[Color], own: Boolean) {
|
||||
|
||||
def visible(color: Color, owner: Boolean): Boolean =
|
||||
if (own && !owner) false else only.fold(_ == color, true)
|
||||
|
||||
def visible(member: Member): Boolean = visible(member.color, member.owner)
|
||||
}
|
||||
|
||||
private var privateVersion = 0
|
||||
private val events = memo.Builder.expiry[Int, VersionedEvent](timeout)
|
||||
|
||||
def version = privateVersion
|
||||
|
||||
def since(v: Int): List[VersionedEvent] =
|
||||
(v + 1 to version).toList map event flatten
|
||||
|
||||
private def event(v: Int) = Option(events getIfPresent v)
|
||||
|
||||
def +=(event: Event): VersionedEvent = {
|
||||
privateVersion = privateVersion + 1
|
||||
val vevent = VersionedEvent(
|
||||
js = JsObject(Seq(
|
||||
"v" -> JsNumber(privateVersion),
|
||||
"t" -> JsString(event.typ),
|
||||
"d" -> event.data
|
||||
)),
|
||||
only = event.only,
|
||||
own = event.owner)
|
||||
events.put(privateVersion, vevent)
|
||||
vevent
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package lila
|
||||
package game
|
||||
|
||||
import socket.{ History, LilaEnumerator }
|
||||
import model._
|
||||
import socket._
|
||||
import chess.{ Color, White, Black }
|
||||
|
||||
import akka.actor._
|
||||
import akka.event.Logging
|
||||
import play.api.libs.json._
|
||||
import play.api.libs.iteratee._
|
||||
import play.api.Play.current
|
||||
|
@ -16,11 +14,12 @@ import scalaz.effects._
|
|||
final class Hub(gameId: String, history: History) extends Actor {
|
||||
|
||||
private var members = Map.empty[String, Member]
|
||||
private val log = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
|
||||
case WithMembers(op) ⇒ op(members.values.pp).unsafePerformIO
|
||||
case WithMembers(op) ⇒ op(members.values).unsafePerformIO
|
||||
|
||||
case GetUsernames ⇒ sender ! usernames
|
||||
|
||||
case IfEmpty(op) ⇒ members.isEmpty.fold(op, io()).unsafePerformIO
|
||||
|
||||
|
@ -33,51 +32,48 @@ final class Hub(gameId: String, history: History) extends Actor {
|
|||
case IsConnected(color) ⇒ sender ! member(color).isDefined
|
||||
|
||||
case Join(uid, version, color, owner, username) ⇒ {
|
||||
val channel = new LilaEnumerator[JsValue](history since version)
|
||||
val msgs = history since version filter (_.visible(color, owner)) map (_.js)
|
||||
val channel = new LilaEnumerator[JsValue](msgs)
|
||||
val member = Member(channel, PovRef(gameId, color), owner, username)
|
||||
members = members + (uid -> member)
|
||||
sender ! Connected(member)
|
||||
notifyCrowd()
|
||||
notify(crowdEvent)
|
||||
}
|
||||
|
||||
case Events(events) ⇒ events foreach notifyVersion
|
||||
case Events(events) ⇒ events match {
|
||||
case Nil ⇒
|
||||
case single :: Nil ⇒ notify(single)
|
||||
case multi ⇒ notify(multi)
|
||||
}
|
||||
|
||||
case Quit(uid) ⇒ {
|
||||
members = members - uid
|
||||
notifyCrowd()
|
||||
notify(crowdEvent)
|
||||
}
|
||||
|
||||
case Close ⇒ {
|
||||
members.values foreach { _.channel.close() }
|
||||
self ! PoisonPill
|
||||
}
|
||||
|
||||
case msg ⇒ log.info("GameHub unknown message: " + msg)
|
||||
}
|
||||
|
||||
private def notifyCrowd() {
|
||||
notifyVersion("crowd", JsObject(Seq(
|
||||
"white" -> JsBoolean(member(White).isDefined),
|
||||
"black" -> JsBoolean(member(Black).isDefined),
|
||||
"watchers" -> JsNumber(members.values count (_.watcher))
|
||||
)))
|
||||
private def crowdEvent = CrowdEvent(
|
||||
white = member(White).isDefined,
|
||||
black = member(Black).isDefined,
|
||||
watchers = members.values count (_.watcher))
|
||||
|
||||
private def notify(e: Event) {
|
||||
val vevent = history += e
|
||||
members.values filter vevent.visible foreach (_.channel push vevent.js)
|
||||
}
|
||||
|
||||
private def member(color: Color): Option[Member] =
|
||||
members.values find { m ⇒ m.owner && m.color == color }
|
||||
|
||||
private def notifyVersion(e: Event) {
|
||||
val vmsg = history += makeMessage(e.typ, e.data)
|
||||
val m1 = if (e.owner) members.values filter (_.owner) else members.values
|
||||
val m2 = e.only.fold(color ⇒ m1 filter (_.color == color), m1)
|
||||
|
||||
m2 foreach (_.channel push vmsg)
|
||||
}
|
||||
private def notifyVersion(t: String, d: JsValue) {
|
||||
notifyVersion(new Event {
|
||||
val typ = t
|
||||
val data = d
|
||||
})
|
||||
private def notify(events: List[Event]) {
|
||||
val vevents = events map history.+=
|
||||
members.values foreach { member ⇒
|
||||
member.channel push JsObject(Seq(
|
||||
"t" -> JsString("batch"),
|
||||
"d" -> JsArray(vevents filter (_ visible member) map (_.js))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private def notifyAll(t: String, data: JsValue) {
|
||||
|
@ -85,6 +81,14 @@ final class Hub(gameId: String, history: History) extends Actor {
|
|||
members.values.foreach(_.channel push msg)
|
||||
}
|
||||
|
||||
private def member(color: Color): Option[Member] =
|
||||
members.values find { m ⇒ m.owner && m.color == color }
|
||||
|
||||
private def usernames: Iterable[String] = members.values collect {
|
||||
case Owner(_, _, Some(username)) ⇒ username
|
||||
case Watcher(_, _, Some(username)) ⇒ username
|
||||
}
|
||||
|
||||
private def makeMessage(t: String, data: JsValue) =
|
||||
JsObject(Seq("t" -> JsString(t), "d" -> data))
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import akka.pattern.{ ask, pipe }
|
|||
import akka.util.duration._
|
||||
import akka.util.{ Duration, Timeout }
|
||||
import akka.dispatch.{ Future }
|
||||
import akka.event.Logging
|
||||
import scalaz.effects._
|
||||
import socket._
|
||||
|
||||
|
@ -16,7 +15,6 @@ final class HubMaster(hubMemo: HubMemo) extends Actor {
|
|||
|
||||
private implicit val timeout = Timeout(200 millis)
|
||||
private implicit val executor = Akka.system.dispatcher
|
||||
private val log = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
|
||||
|
@ -24,11 +22,13 @@ final class HubMaster(hubMemo: HubMemo) extends Actor {
|
|||
(a ? GetNbMembers).mapTo[Int]
|
||||
) map (_.sum) pipeTo sender
|
||||
|
||||
case GetUsernames ⇒ Future.traverse(hubActors)(a ⇒
|
||||
(a ? GetUsernames).mapTo[Iterable[String]]
|
||||
) map (_.flatten) pipeTo sender
|
||||
|
||||
case WithHubs(op) => op(hubMemo.all).unsafePerformIO
|
||||
|
||||
case msg @ NbPlayers(nb) ⇒ hubActors foreach (_ ! msg)
|
||||
|
||||
case msg ⇒ log.info("HubMaster unknown message: " + msg)
|
||||
}
|
||||
|
||||
def hubActors = hubMemo.all.values
|
||||
|
|
|
@ -56,16 +56,16 @@ final class Socket(
|
|||
promotion = d str "promotion"
|
||||
op = for {
|
||||
events ← hand.play(povRef, orig, dest, promotion)
|
||||
_ ← events.fold(
|
||||
errors ⇒ putStrLn(errors.list mkString "\n"),
|
||||
events ⇒ send(povRef.gameId, events))
|
||||
_ ← events.fold(putFailures, events ⇒ send(povRef.gameId, events))
|
||||
} yield ()
|
||||
} op.unsafePerformIO
|
||||
case "moretime" ⇒ (for {
|
||||
res ← hand moretime povRef
|
||||
op ← io {
|
||||
res.fold(println, events ⇒ hub ! Events(events))
|
||||
}
|
||||
op ← res.fold(putFailures, events ⇒ io(hub ! Events(events)))
|
||||
} yield op).unsafePerformIO
|
||||
case "outoftime" ⇒ (for {
|
||||
res ← hand outoftime povRef
|
||||
op ← res.fold(putFailures, events ⇒ io(hub ! Events(events)))
|
||||
} yield op).unsafePerformIO
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ sealed trait Member {
|
|||
def gameId = ref.gameId
|
||||
def color = ref.color
|
||||
def className = owner.fold("Owner", "Watcher")
|
||||
def show = username | "Anonymous"
|
||||
override def toString = "%s(%s-%s,%s)".format(className, gameId, color, username)
|
||||
}
|
||||
object Member {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
package lila
|
||||
package socket
|
||||
package lobby
|
||||
|
||||
import scala.math.max
|
||||
import play.api.libs.json._
|
|
@ -5,20 +5,18 @@ import db.MessageRepo
|
|||
import socket._
|
||||
|
||||
import akka.actor._
|
||||
import akka.event.Logging
|
||||
import play.api.libs.json._
|
||||
import play.api.libs.iteratee._
|
||||
|
||||
final class Hub(messageRepo: MessageRepo, history: History) extends Actor {
|
||||
|
||||
private var members = Map.empty[String, Member]
|
||||
private val log = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
|
||||
case WithHooks(op) ⇒ op(hookOwnerIds).unsafePerformIO
|
||||
case WithHooks(op) ⇒ op(hookOwnerIds).unsafePerformIO
|
||||
|
||||
case WithUsernames(op) ⇒ op(usernames).unsafePerformIO
|
||||
case GetUsernames ⇒ sender ! usernames
|
||||
|
||||
case Join(uid, version, username, hookOwnerId) ⇒ {
|
||||
val channel = new LilaEnumerator[JsValue](history since version)
|
||||
|
@ -62,8 +60,6 @@ final class Hub(messageRepo: MessageRepo, history: History) extends Actor {
|
|||
case NbPlayers(nb) ⇒ notifyAll("nbp", JsNumber(nb))
|
||||
|
||||
case Quit(uid) ⇒ { members = members - uid }
|
||||
|
||||
case msg ⇒ log.info("LobbyHub unknown message: " + msg)
|
||||
}
|
||||
|
||||
private def notifyMember(t: String, data: JsValue)(member: Member) {
|
||||
|
|
|
@ -4,7 +4,6 @@ package lobby
|
|||
import model._
|
||||
import memo._
|
||||
import db._
|
||||
import socket.History
|
||||
import scalaz.effects._
|
||||
|
||||
final class Preload(
|
||||
|
|
|
@ -12,7 +12,6 @@ case class Member(
|
|||
}
|
||||
|
||||
case class WithHooks(op: Iterable[String] => IO[Unit])
|
||||
case class WithUsernames(op: Iterable[String] => IO[Unit])
|
||||
case class AddHook(hook: model.Hook)
|
||||
case class RemoveHook(hook: model.Hook)
|
||||
case class BiteHook(hook: model.Hook, game: model.DbGame)
|
||||
|
|
|
@ -101,6 +101,7 @@ case class DbGame(
|
|||
val events =
|
||||
Event.possibleMoves(game.situation, White) ::
|
||||
Event.possibleMoves(game.situation, Black) ::
|
||||
StateEvent(game.situation.color, game.turns) ::
|
||||
(Event fromMove move) :::
|
||||
(Event fromSituation game.situation)
|
||||
|
||||
|
@ -190,7 +191,7 @@ case class DbGame(
|
|||
if !c.isRunning || (c outoftime player.color)
|
||||
} yield player
|
||||
|
||||
def withClock(c: Clock) = copy(clock = Some(c))
|
||||
def withClock(c: Clock) = Progress(this, copy(clock = Some(c)))
|
||||
|
||||
def creator = player(creatorColor)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import play.api.libs.json._
|
|||
import chess._
|
||||
import Pos.{ piotr, allPiotrs }
|
||||
|
||||
trait Event {
|
||||
sealed trait Event {
|
||||
def typ: String
|
||||
def data: JsValue
|
||||
def only: Option[Color] = None
|
||||
|
@ -142,3 +142,23 @@ object ClockEvent {
|
|||
clock remainingTime White,
|
||||
clock remainingTime Black)
|
||||
}
|
||||
|
||||
case class StateEvent(color: Color, turns: Int) extends Event {
|
||||
def typ = "state"
|
||||
def data = JsObject(Seq(
|
||||
"color" -> JsString(color.name),
|
||||
"turns" -> JsNumber(turns)
|
||||
))
|
||||
}
|
||||
|
||||
case class CrowdEvent(
|
||||
white: Boolean,
|
||||
black: Boolean,
|
||||
watchers: Int) extends Event {
|
||||
def typ = "crowd"
|
||||
def data = JsObject(Seq(
|
||||
"white" -> JsBoolean(white),
|
||||
"black" -> JsBoolean(black),
|
||||
"watchers" -> JsNumber(watchers)
|
||||
))
|
||||
}
|
||||
|
|
47
app/site/Hub.scala
Normal file
47
app/site/Hub.scala
Normal file
|
@ -0,0 +1,47 @@
|
|||
package lila
|
||||
package site
|
||||
|
||||
import socket._
|
||||
|
||||
import akka.actor._
|
||||
import play.api.libs.json._
|
||||
import play.api.libs.iteratee._
|
||||
|
||||
final class Hub extends Actor {
|
||||
|
||||
private var members = Map.empty[String, Member]
|
||||
|
||||
def receive = {
|
||||
|
||||
case GetUsernames ⇒ sender ! usernames
|
||||
|
||||
case Join(uid, username) ⇒ {
|
||||
val channel = new LilaEnumerator[JsValue](Nil)
|
||||
members = members + (uid -> Member(channel, username))
|
||||
sender ! Connected(channel)
|
||||
}
|
||||
|
||||
case GetNbMembers ⇒ sender ! members.size
|
||||
|
||||
case NbPlayers(nb) ⇒ notifyAll("nbp", JsNumber(nb))
|
||||
|
||||
case Quit(uid) ⇒ { members = members - uid }
|
||||
}
|
||||
|
||||
private def notifyMember(t: String, data: JsValue)(member: Member) {
|
||||
val msg = JsObject(Seq("t" -> JsString(t), "d" -> data))
|
||||
member.channel push msg
|
||||
}
|
||||
|
||||
private def notifyAll(t: String, data: JsValue) {
|
||||
val msg = makeMessage(t, data)
|
||||
members.values.foreach(_.channel push msg)
|
||||
}
|
||||
|
||||
private def usernames: Iterable[String] = members.values collect {
|
||||
case Member(_, Some(username)) ⇒ username
|
||||
}
|
||||
|
||||
private def makeMessage(t: String, data: JsValue) =
|
||||
JsObject(Seq("t" -> JsString(t), "d" -> data))
|
||||
}
|
30
app/site/Socket.scala
Normal file
30
app/site/Socket.scala
Normal file
|
@ -0,0 +1,30 @@
|
|||
package lila
|
||||
package site
|
||||
|
||||
import akka.actor._
|
||||
import akka.pattern.ask
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
|
||||
import play.api._
|
||||
import play.api.libs.json._
|
||||
import play.api.libs.iteratee._
|
||||
import play.api.libs.concurrent._
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
final class Socket(hub: ActorRef) {
|
||||
|
||||
implicit val timeout = Timeout(1 second)
|
||||
|
||||
def join(uid: String, username: Option[String]): SocketPromise =
|
||||
(hub ? Join(uid, username)).asPromise map {
|
||||
case Connected(channel) ⇒
|
||||
val iteratee = Iteratee.foreach[JsValue] { event ⇒
|
||||
Unit
|
||||
} mapDone { _ ⇒
|
||||
hub ! Quit(uid)
|
||||
}
|
||||
(iteratee, channel)
|
||||
}
|
||||
}
|
15
app/site/model.scala
Normal file
15
app/site/model.scala
Normal file
|
@ -0,0 +1,15 @@
|
|||
package lila
|
||||
package site
|
||||
|
||||
import scalaz.effects.IO
|
||||
|
||||
case class Member(
|
||||
channel: Channel,
|
||||
username: Option[String]) {
|
||||
}
|
||||
|
||||
case class Join(
|
||||
uid: String,
|
||||
username: Option[String])
|
||||
case class Quit(uid: String)
|
||||
case class Connected(channel: Channel)
|
|
@ -3,4 +3,5 @@ package socket
|
|||
|
||||
case object GetNbMembers
|
||||
case class NbPlayers(nb: Int)
|
||||
case object GetUsernames
|
||||
case object Close
|
||||
|
|
22
conf/routes
22
conf/routes
|
@ -1,15 +1,13 @@
|
|||
# App Public API
|
||||
GET /socket/:gameId/:color lila.controllers.AppXhrC.socket(gameId: String, color: String)
|
||||
GET /how-many-players-now lila.controllers.AppXhrC.nbPlayers
|
||||
GET /how-many-games-now lila.controllers.AppXhrC.nbGames
|
||||
GET /abort/:fullId lila.controllers.AppXhrC.abort(fullId: String)
|
||||
GET /resign/:fullId lila.controllers.AppXhrC.resign(fullId: String)
|
||||
GET /draw-claim/:fullId lila.controllers.AppXhrC.drawClaim(fullId: String)
|
||||
POST /outoftime/:fullId lila.controllers.AppXhrC.outoftime(fullId: String)
|
||||
GET /draw-accept/:fullId lila.controllers.AppXhrC.drawAccept(fullId: String)
|
||||
GET /draw-offer/:fullId lila.controllers.AppXhrC.drawOffer(fullId: String)
|
||||
GET /draw-cancel/:fullId lila.controllers.AppXhrC.drawCancel(fullId: String)
|
||||
GET /draw-decline/:fullId lila.controllers.AppXhrC.drawDecline(fullId: String)
|
||||
GET /socket lila.controllers.AppC.socket
|
||||
GET /socket/:gameId/:color lila.controllers.AppC.gameSocket(gameId: String, color: String)
|
||||
GET /abort/:fullId lila.controllers.AppC.abort(fullId: String)
|
||||
GET /resign/:fullId lila.controllers.AppC.resign(fullId: String)
|
||||
GET /draw-claim/:fullId lila.controllers.AppC.drawClaim(fullId: String)
|
||||
GET /draw-accept/:fullId lila.controllers.AppC.drawAccept(fullId: String)
|
||||
GET /draw-offer/:fullId lila.controllers.AppC.drawOffer(fullId: String)
|
||||
GET /draw-cancel/:fullId lila.controllers.AppC.drawCancel(fullId: String)
|
||||
GET /draw-decline/:fullId lila.controllers.AppC.drawDecline(fullId: String)
|
||||
|
||||
GET /ai lila.controllers.AiC.run
|
||||
|
||||
|
@ -21,8 +19,6 @@ POST /api/reload-table/:gameId lila.controllers.AppApiC.reloadTable(gameId:
|
|||
POST /api/rematch-accept/:gameId/:color/:newGameId lila.controllers.AppApiC.rematchAccept(gameId: String, color: String, newGameId: String)
|
||||
GET /api/activity/:gameId/:color lila.controllers.AppApiC.activity(gameId: String, color: String)
|
||||
GET /api/game-version/:gameId lila.controllers.AppApiC.gameVersion(gameId: String)
|
||||
GET /api/nb-players lila.controllers.AppXhrC.nbPlayers
|
||||
POST /api/outoftime/:fullId lila.controllers.AppXhrC.outoftime(fullId: String)
|
||||
|
||||
# Lobby Public API
|
||||
GET /lobby/cancel/:ownerId lila.controllers.LobbyC.cancel(ownerId: String)
|
||||
|
|
|
@ -14,7 +14,7 @@ trait Dependencies {
|
|||
val specs2 = "org.specs2" %% "specs2" % "1.8.2"
|
||||
val casbah = "com.mongodb.casbah" %% "casbah" % "2.1.5-1"
|
||||
val salat = "com.novus" %% "salat-core" % "0.0.8-SNAPSHOT"
|
||||
val scalalib = "com.github.ornicar" %% "scalalib" % "1.24"
|
||||
val scalalib = "com.github.ornicar" %% "scalalib" % "1.25"
|
||||
val hasher = "com.roundeights" % "hasher" % "0.3" from "http://cloud.github.com/downloads/Nycto/Hasher/hasher_2.9.1-0.3.jar"
|
||||
val config = "com.typesafe.config" % "config" % "0.3.0"
|
||||
val json = "com.codahale" %% "jerkson" % "0.5.0"
|
||||
|
|
Loading…
Reference in a new issue