Communication with lichess

pull/1/merge
Thibault Duplessis 2012-03-17 21:18:52 +01:00
parent cab58c9f98
commit 13101a42c1
11 changed files with 72 additions and 17 deletions

View File

@ -13,4 +13,7 @@ object Global extends GlobalSettings {
override def onBadRequest(request: RequestHeader, error: String) = {
BadRequest("Bad Request: " + error)
}
override def onError(request: RequestHeader, ex: Throwable): Result =
InternalServerError(ex.getMessage)
}

View File

@ -12,11 +12,12 @@ 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(jsonify(
env.syncer.sync(id, color, version, fullId).unsafePerformIO
)) as json
}
def sync(id: String, color: String, version: Int, fullId: Option[String]) =
Action {
Ok(jsonify(
env.syncer.sync(id, color, version, fullId).unsafePerformIO
)) as json
}
def move(fullId: String) = Action { implicit request
(moveForm.bindFromRequest.value toValid "Invalid move" flatMap { move =>
@ -27,5 +28,15 @@ object Application extends Controller {
)
}
def updateVersion(gameId: String) = Action {
env.server.updateVersion(gameId).unsafePerformIO
Ok("ok")
}
def endGame(gameId: String) = Action {
env.server.endGame(gameId).unsafePerformIO
Ok("ok")
}
def index = TODO
}

View File

@ -5,7 +5,9 @@
# Home page
GET / controllers.Application.index
POST /move/:fullId controllers.Application.move(fullId: String)
POST /sync/:id/:color/:version/:fullId controllers.Application.sync(id: String, color: String, version: Int, fullId: String)
GET /sync/:id/:color/:version/:fullId controllers.Application.sync(id: String, color: String, version: Int, fullId: Option[String])
POST /update-version/:gameId controllers.Application.updateVersion(gameId: String)
POST /end-game/:gameId controllers.Application.endGame(gameId: String)
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)

View File

@ -21,6 +21,7 @@ trait Dependencies {
val config = "com.typesafe.config" % "config" % "0.3.0"
val json = "com.codahale" %% "jerkson" % "0.5.0"
val guava = "com.google.guava" % "guava" % "11.0.2"
val apache = "org.apache.commons" % "commons-lang3" % "3.1"
// benchmark
val instrumenter = "com.google.code.java-allocation-instrumenter" % "java-allocation-instrumenter" % "2.0"
@ -51,7 +52,7 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
) dependsOn (system)
lazy val system = Project("system", file("system"), settings = buildSettings).settings(
libraryDependencies ++= Seq(scalaz, config, json, casbah, salat, guava, slf4j)
libraryDependencies ++= Seq(scalaz, config, json, casbah, salat, guava, slf4j, apache)
) dependsOn (chess)
lazy val chess = Project("chess", file("chess"), settings = buildSettings).settings(

4
routes
View File

@ -1,4 +1,4 @@
lichess_move ANY /move/{id}
/*lichess_move ANY /move/{id}*/
lichess_say ANY /talk/{id}
lichess_resign ANY /resign/{id}
lichess_abort ANY /abort/{id}
@ -13,4 +13,4 @@ lichess_decline_draw_offer ANY /decline-draw-offer/{id}
lichess_moretime ANY /moretime/{id}
lichess_status ANY /status
lichess_ping ANY /ping
lichess_sync ANY /sync/{id}/{color}/{version}/{playerFullId}
/*lichess_sync ANY /sync/{id}/{color}/{version}/{playerFullId}*/

View File

@ -32,6 +32,15 @@ final class Server(repo: GameRepo, ai: Ai, versionMemo: VersionMemo) {
)
}
def updateVersion(gameId: String): IO[Unit] =
repo game gameId flatMap versionMemo.put
def endGame(gameId: String): IO[Unit] = for {
g1 repo game gameId
g2 = g1 withEvents List(EndEvent())
_ repo.applyDiff(g1, g2)
} yield ()
private def purePlay(
g1: DbGame,
origString: String,

View File

@ -6,6 +6,7 @@ import memo._
import scalaz.effects._
import scala.annotation.tailrec
import scala.math.max
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
final class Syncer(
repo: GameRepo,
@ -17,18 +18,19 @@ final class Syncer(
gameId: String,
colorString: String,
version: Int,
fullId: String): IO[Map[String, Any]] = {
fullId: Option[String]): IO[Map[String, Any]] = {
for {
color io { Color(colorString) err "Invalid color" }
_ io { versionWait(gameId, color, version) }
gameAndPlayer repo.player(gameId, color)
(game, player) = gameAndPlayer
isPrivate = fullId some { game.isPlayerFullId(player, _) } none false
_ versionMemo put game
} yield {
player.eventStack eventsSince version map { events
Map(
"v" -> player.eventStack.lastVersion,
"e" -> (events map (_.export)),
"e" -> renderEvents(events, isPrivate),
"p" -> game.player.color.name,
"t" -> game.turns
)
@ -36,6 +38,22 @@ final class Syncer(
}
} except (e io(failMap))
private def renderEvents(events: List[Event], isPrivate: Boolean) =
if (isPrivate) events map {
case MessageEvent(author, message) renderMessage(author, message)
case e e.export
}
else events filter {
case MessageEvent(_, _) | RedirectEvent(_) false
case _ true
} map (_.export)
// TODO author=system messages should be translated!!
private def renderMessage(author: String, message: String) = Map(
"type" -> "message",
"html" -> """<li class="%s">%s</li>""".format(author, escapeXml(message))
)
private def versionWait(gameId: String, color: Color, version: Int) {
@tailrec
def wait(loop: Int): Unit = {

View File

@ -14,7 +14,7 @@ final class VersionMemo(repo: GameRepo) {
}
def put(gameId: String, color: Color, version: Int): IO[Unit] = io {
cache.put((gameId.pp, color.pp == White), version.pp)
cache.put((gameId, color == White), version)
}
def put(game: DbGame): IO[Unit] = for {

View File

@ -31,7 +31,11 @@ case class DbGame(
case Black blackPlayer
}
def player(id: String): Option[DbPlayer] = players find (_.id == id)
def player(playerId: String): Option[DbPlayer] =
players find (_.id == playerId)
def isPlayerFullId(player: DbPlayer, fullId: String): Boolean =
(fullId.size == DbGame.fullIdSize) && player.id == (fullId drop 8)
def player: DbPlayer = player(if (0 == turns % 2) White else Black)
@ -112,11 +116,16 @@ case class DbGame(
)
}
def withEvents(events: List[Event]): DbGame = copy(
whitePlayer = whitePlayer withEvents events,
blackPlayer = blackPlayer withEvents events
)
def playable = status < Aborted
def aiLevel: Option[Int] = players find (_.isAi) flatMap (_.aiLevel)
def mapPlayers(f: DbPlayer => DbPlayer) = copy(
def mapPlayers(f: DbPlayer DbPlayer) = copy(
whitePlayer = f(whitePlayer),
blackPlayer = f(blackPlayer)
)

View File

@ -17,6 +17,10 @@ case class DbPlayer(
def newEvts(events: List[Event]): String =
(eventStack withEvents events).optimize.encode
def withEvents(events: List[Event]) = copy(
evts = newEvts(events)
)
def encodePieces(allPieces: Iterable[(Pos, Piece, Boolean)]): String =
allPieces withFilter (_._2.color == color) map {
case (pos, piece, dead) pos.piotr.toString + {

View File

@ -179,9 +179,7 @@ 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))
def export = Map.empty
}
object MessageEvent extends EventDecoder {
def decode(str: String) = str.split(' ').toList match {