diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index 2d098785b4..1ece63edc9 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -5,14 +5,17 @@ import DataForm._ import play.api._ import play.api.mvc._ -import play.api.libs.json._ +import com.codahale.jerkson.Json.{ generate => jsonify } object Application extends Controller { val env = new HttpEnv(Play.unsafeApplication.configuration.underlying) + val json = "application/json" def sync(id: String, color: String, version: Int, fullId: String) = Action { - Ok(toJson(env.syncer.sync(id, color, version, fullId))) + Ok(jsonify( + env.syncer.sync(id, color, version, fullId).unsafePerformIO + )) as json } def move(fullId: String) = Action { implicit request ⇒ diff --git a/project/Build.scala b/project/Build.scala index d828b46285..d6862f10cd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -14,13 +14,13 @@ trait Dependencies { val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.4" val specs2 = "org.specs2" %% "specs2" % "1.8.2" val redis = "net.debasishg" %% "redisclient" % "2.4.2" - val json = "net.liftweb" %% "lift-json" % "2.4" val casbah = "com.mongodb.casbah" %% "casbah" % "2.1.5-1" val salat = "com.novus" %% "salat-core" % "0.0.8-SNAPSHOT" val slf4j = "org.slf4j" % "slf4j-nop" % "1.6.4" val scalalib = "com.github.ornicar" %% "scalalib" % "1.23" 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" // benchmark val instrumenter = "com.google.code.java-allocation-instrumenter" % "java-allocation-instrumenter" % "2.0" diff --git a/system/src/main/scala/Syncer.scala b/system/src/main/scala/Syncer.scala index 76751aad1a..ab090510cf 100644 --- a/system/src/main/scala/Syncer.scala +++ b/system/src/main/scala/Syncer.scala @@ -6,6 +6,8 @@ import scalaz.effects._ final class Syncer(repo: GameRepo) { + val reload = Map("reload" -> true) + def sync( id: String, colorString: String, @@ -15,16 +17,16 @@ final class Syncer(repo: GameRepo) { color ← io { Color(colorString) err "Invalid color" } gameAndPlayer ← repo.player(id, color) (g, p) = gameAndPlayer - //events ← io { eventsSince(p, version) } - } yield Map( - "v" -> p.eventStack.version, - //"e" -> events map (_.toMap), - "p" -> g.player, - "t" -> g.turns - ) - } except (e ⇒ io(Map("reload" -> true))) - - def eventsSince(player: DbPlayer, version: Int): List[Event] = - player.eventStack.eventsSince(version) | Nil + } yield { + p.eventStack eventsSince version map { events ⇒ + Map( + "v" -> p.eventStack.lastVersion, + "e" -> (events map (_.export)), + "p" -> g.player.color.name, + "t" -> g.turns + ) + } getOrElse reload + } + } except (e ⇒ io(reload)) } diff --git a/system/src/main/scala/SystemEnv.scala b/system/src/main/scala/SystemEnv.scala index 2958956b51..d45a28912b 100644 --- a/system/src/main/scala/SystemEnv.scala +++ b/system/src/main/scala/SystemEnv.scala @@ -15,6 +15,9 @@ trait SystemEnv { repo = gameRepo, ai = ai) + def syncer = new Syncer( + repo = gameRepo) + lazy val ai: Ai = new StupidAi def gameRepo = new GameRepo( diff --git a/system/src/main/scala/model/Event.scala b/system/src/main/scala/model/Event.scala index 24778e7706..7a1371b997 100644 --- a/system/src/main/scala/model/Event.scala +++ b/system/src/main/scala/model/Event.scala @@ -6,6 +6,7 @@ import Pos.{ piotr, allPiotrs } sealed trait Event { def encode: String + def export: Map[String, Any] } object Event { @@ -48,6 +49,8 @@ object EventDecoder { case class StartEvent() extends Event { def encode = "s" + def export = Map( + "type" -> "start") } object StartEvent extends EventDecoder { def decode(str: String) = Some(StartEvent()) @@ -55,6 +58,11 @@ object StartEvent extends EventDecoder { case class MoveEvent(orig: Pos, dest: Pos, color: Color) extends Event { def encode = "m" + orig.piotr + dest.piotr + color.letter + def export = Map( + "type" -> "move", + "from" -> orig.key, + "to" -> dest.key, + "color" -> color.name) } object MoveEvent extends EventDecoder { def apply(move: Move): MoveEvent = MoveEvent(move.orig, move.dest, move.piece.color) @@ -72,6 +80,11 @@ case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event { def encode = "p" + (moves map { case (orig, dests) ⇒ (orig :: dests) map (_.piotr) mkString } mkString ",") + def export = Map( + "type" -> "possible_moves", + "possible_moves" -> (moves map { + case (o, d) ⇒ o.key -> (d map (_.key) mkString) + })) } object PossibleMovesEvent extends EventDecoder { def decode(str: String) = Some(PossibleMovesEvent( @@ -90,6 +103,9 @@ object PossibleMovesEvent extends EventDecoder { case class EnpassantEvent(killed: Pos) extends Event { def encode = "E" + killed.piotr + def export = Map( + "type" -> "enpassant", + "killed" -> killed.key) } object EnpassantEvent extends EventDecoder { def decode(str: String) = for { @@ -100,7 +116,11 @@ object EnpassantEvent extends EventDecoder { case class CastlingEvent(king: (Pos, Pos), rook: (Pos, Pos), color: Color) extends Event { def encode = "c" + king._1.piotr + king._2.piotr + rook._1.piotr + rook._2.piotr + color.letter - + def export = Map( + "type" -> "castling", + "king" -> List(king._1.key, king._2.key), + "rook" -> List(rook._1.key, rook._2.key), + "color" -> color.name) } object CastlingEvent extends EventDecoder { def decode(str: String) = str.toList match { @@ -119,6 +139,9 @@ object CastlingEvent extends EventDecoder { case class RedirectEvent(url: String) extends Event { def encode = "r" + url + def export = Map( + "type" -> "redirect", + "url" -> url) } object RedirectEvent extends EventDecoder { def decode(str: String) = Some(RedirectEvent(str)) @@ -126,6 +149,10 @@ object RedirectEvent extends EventDecoder { case class PromotionEvent(role: PromotableRole, pos: Pos) extends Event { def encode = "P" + role.forsyth + pos.piotr + def export = Map( + "type" -> "promotion", + "key" -> pos.key, + "pieceClass" -> role.toString) } object PromotionEvent extends EventDecoder { def decode(str: String) = str.toList match { @@ -139,6 +166,9 @@ object PromotionEvent extends EventDecoder { case class CheckEvent(pos: Pos) extends Event { def encode = "C" + pos.piotr + def export = Map( + "type" -> "check", + "key" -> pos.key) } object CheckEvent extends EventDecoder { def decode(str: String) = for { @@ -149,6 +179,9 @@ object CheckEvent extends EventDecoder { case class MessageEvent(author: String, message: String) extends Event { def encode = "M" + author + " " + message.replace("|", "(pipe)") + def export = Map( + "type" -> "message", + "message" -> List(author, message)) } object MessageEvent extends EventDecoder { def decode(str: String) = str.split(' ').toList match { @@ -161,6 +194,8 @@ object MessageEvent extends EventDecoder { case class EndEvent() extends Event { def encode = "e" + def export = Map( + "type" -> "end") } object EndEvent extends EventDecoder { def decode(str: String) = Some(EndEvent()) @@ -168,6 +203,8 @@ object EndEvent extends EventDecoder { case class ThreefoldEvent() extends Event { def encode = "t" + def export = Map( + "type" -> "threefold_repetition") } object ThreefoldEvent extends EventDecoder { def decode(str: String) = Some(ThreefoldEvent()) @@ -175,6 +212,8 @@ object ThreefoldEvent extends EventDecoder { case class ReloadTableEvent() extends Event { def encode = "R" + def export = Map( + "type" -> "reload_table") } object ReloadTableEvent extends EventDecoder { def decode(str: String) = Some(ReloadTableEvent()) @@ -182,6 +221,10 @@ object ReloadTableEvent extends EventDecoder { case class MoretimeEvent(color: Color, seconds: Int) extends Event { def encode = "T" + color.letter + seconds + def export = Map( + "type" -> "moretime", + "color" -> color.name, + "seconds" -> seconds) } object MoretimeEvent extends EventDecoder { def decode(str: String) = for { diff --git a/system/src/main/scala/model/EventStack.scala b/system/src/main/scala/model/EventStack.scala index 27952038f8..ee064bb1c1 100644 --- a/system/src/main/scala/model/EventStack.scala +++ b/system/src/main/scala/model/EventStack.scala @@ -29,14 +29,12 @@ case class EventStack(events: List[(Int, Event)]) { ) } - def version: Int = events.lastOption map (_._1) getOrElse 0 - def eventsSince(version: Int): Option[List[Event]] = for { - first <- firstVersion + first ← firstVersion if version >= first - 1 - last <- lastVersion + last ← lastVersion if version <= last - } yield sortedEvents dropWhile { ve => ve._1 <= version } map (_._2) + } yield sortedEvents dropWhile { ve ⇒ ve._1 <= version } map (_._2) def withEvents(newEvents: List[Event]): EventStack = { @@ -45,7 +43,7 @@ case class EventStack(events: List[(Int, Event)]) { case event :: rest ⇒ (v + 1, event) :: versionEvents(v + 1, rest) } - copy(events = events ++ versionEvents(version, newEvents)) + copy(events = events ++ versionEvents(lastVersion getOrElse 0, newEvents)) } } diff --git a/system/src/test/scala/ServerTest.scala b/system/src/test/scala/ServerTest.scala index c0b25f031c..c153ff5096 100644 --- a/system/src/test/scala/ServerTest.scala +++ b/system/src/test/scala/ServerTest.scala @@ -123,7 +123,9 @@ B p p "high version number" in { found must beIO.like { case vg ⇒ vg must beSuccess.like { - case g ⇒ g.player(White).eventStack.version must be_>(20) + case g ⇒ g.player(White).eventStack.lastVersion must beSome.like { + case v => v must be_>(20) + } } } }