Stuff. Lot of.

This commit is contained in:
Thibault Duplessis 2012-04-11 01:12:07 +02:00
parent da9d5e79f0
commit b3ea12c381
10 changed files with 88 additions and 57 deletions

View file

@ -15,13 +15,13 @@ final class AppXhr(
aliveMemo: AliveMemo,
moretimeSeconds: Int) {
type IOValid = IO[Valid[Unit]]
type IOValidEvents = IO[Valid[List[Event]]]
def play(
fullId: String,
povRef: PovRef,
origString: String,
destString: String,
promString: Option[String] = None): List[Event] = fromPov(fullId) {
promString: Option[String] = None): IOValidEvents = fromPov(povRef) {
case Pov(g1, color) (for {
g2 (g1.playable).fold(success(g1), failure("Game not playable" wrapNel))
orig posAt(origString) toValid "Wrong orig " + origString
@ -51,19 +51,19 @@ final class AppXhr(
)
}
def abort(fullId: String): IOValid = attempt(fullId, finisher.abort)
def abort(fullId: String): IOValidEvents = attempt(fullId, finisher.abort)
def resign(fullId: String): IOValid = attempt(fullId, finisher.resign)
def resign(fullId: String): IOValidEvents = attempt(fullId, finisher.resign)
def forceResign(fullId: String): IOValid = attempt(fullId, finisher.forceResign)
def forceResign(fullId: String): IOValidEvents = attempt(fullId, finisher.forceResign)
def outoftime(fullId: String): IOValid = attempt(fullId, finisher outoftime _.game)
def outoftime(fullId: String): IOValidEvents = attempt(fullId, finisher outoftime _.game)
def drawClaim(fullId: String): IOValid = attempt(fullId, finisher.drawClaim)
def drawClaim(fullId: String): IOValidEvents = attempt(fullId, finisher.drawClaim)
def drawAccept(fullId: String): IOValid = attempt(fullId, finisher.drawAccept)
def drawAccept(fullId: String): IOValidEvents = attempt(fullId, finisher.drawAccept)
def drawOffer(fullId: String): IO[Valid[List[Event]]] = attempt(fullId, {
def drawOffer(fullId: String): IOValidEvents = attempt(fullId, {
case pov @ Pov(g1, color)
if (g1 playerCanOfferDraw color) {
if (g1.player(!color).isOfferingDraw) finisher drawAccept pov
@ -132,5 +132,8 @@ final class AppXhr(
private def fromPov[A](fullId: String)(op: Pov IO[A]): IO[A] =
gameRepo pov fullId flatMap op
private def fromPov[A](ref: PovRef)(op: Pov IO[A]): IO[A] =
gameRepo pov ref flatMap op
private def !!(msg: String) = failure(msg.wrapNel)
}

View file

@ -16,32 +16,32 @@ final class Finisher(
eloCalculator: EloCalculator,
finisherLock: FinisherLock) {
type ValidIO = Valid[IO[Unit]]
type ValidIOEvents = Valid[IO[List[Event]]]
def abort(pov: Pov): ValidIO =
def abort(pov: Pov): ValidIOEvents =
if (pov.game.abortable) finish(pov.game, Aborted)
else !!("game is not abortable")
def resign(pov: Pov): ValidIO =
def resign(pov: Pov): ValidIOEvents =
if (pov.game.resignable) finish(pov.game, Resign, Some(!pov.color))
else !!("game is not resignable")
def forceResign(pov: Pov): ValidIO =
def forceResign(pov: Pov): ValidIOEvents =
if (pov.game.playable && aliveMemo.inactive(pov.game.id, !pov.color))
finish(pov.game, Timeout, Some(pov.color))
else !!("game is not force-resignable")
def drawClaim(pov: Pov): ValidIO = pov match {
def drawClaim(pov: Pov): ValidIOEvents = pov match {
case Pov(game, color) if game.playable && game.player.color == color && game.toChessHistory.threefoldRepetition finish(game, Draw)
case Pov(game, color) !!("game is not threefold repetition")
}
def drawAccept(pov: Pov): ValidIO =
def drawAccept(pov: Pov): ValidIOEvents =
if (pov.opponent.isOfferingDraw)
finish(pov.game, Draw, None, Some("Draw offer accepted"))
else !!("opponent is not proposing a draw")
def outoftime(game: DbGame): ValidIO =
def outoftime(game: DbGame): ValidIOEvents =
game.outoftimePlayer some { player
finish(game, Outoftime,
Some(!player.color) filter game.toChess.board.hasEnoughMaterialToMate)
@ -49,14 +49,17 @@ 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")), identity)
outoftime(g).fold(
msgs putStrLn(g.id + " " + (msgs.list mkString "\n")),
_ map (_ Unit) // events are lost
): IO[Unit]
}
def moveFinish(game: DbGame, color: Color): IO[List[Event]] =
(game.status match {
case Mate finish(game, Mate, Some(color))
case status @ (Stalemate | Draw) finish(game, status)
case _ success(io(Nil))
case _ success(io(Nil)): ValidIOEvents
}) | io(Nil)
private def finish(

View file

@ -8,12 +8,11 @@ import scalaz.effects._
final class Messenger(roomRepo: RoomRepo) {
def playerMessage(
gameId: String,
color: Color,
ref: PovRef,
message: String): IO[List[Event]] =
if (message.size <= 140 && message.nonEmpty)
roomRepo.addMessage(gameId, color.name, message) map { _
List(MessageEvent(color.name, message))
roomRepo.addMessage(ref.gameId, ref.color.name, message) map { _
List(MessageEvent(ref.color.name, message))
}
else io(Nil)

View file

@ -25,6 +25,7 @@ final class SystemEnv(config: Config) {
lazy val gameSocket = new game.Socket(
gameRepo = gameRepo,
xhr = appXhr,
hubMemo = gameHubMemo,
messenger = messenger)

View file

@ -3,6 +3,7 @@ package controllers
import DataForm._
import chess.Color
import model.{ Event, DbGame }
import play.api._
import mvc._
@ -11,7 +12,7 @@ import Play.current
import libs.json._
import libs.iteratee._
import scalaz.effects.IO
import scalaz.effects._
object AppXhrC extends LilaController {
@ -28,23 +29,25 @@ object AppXhrC extends LilaController {
username = get("username")).unsafePerformIO
}
def outoftime(fullId: String) = Action { ValidIOk(xhr outoftime fullId) }
def outoftime(fullId: String) = Action {
IOk(perform(fullId, xhr.outoftime))
}
def abort(fullId: String) = validAndRedirect(fullId, xhr.abort)
def abort(fullId: String) = performAndRedirect(fullId, xhr.abort)
def resign(fullId: String) = validAndRedirect(fullId, xhr.resign)
def resign(fullId: String) = performAndRedirect(fullId, xhr.resign)
def forceResign(fullId: String) = validAndRedirect(fullId, xhr.forceResign)
def forceResign(fullId: String) = performAndRedirect(fullId, xhr.forceResign)
def drawClaim(fullId: String) = validAndRedirect(fullId, xhr.drawClaim)
def drawClaim(fullId: String) = performAndRedirect(fullId, xhr.drawClaim)
def drawAccept(fullId: String) = validAndRedirect(fullId, xhr.drawAccept)
def drawAccept(fullId: String) = performAndRedirect(fullId, xhr.drawAccept)
def drawOffer(fullId: String) = validAndRedirect(fullId, xhr.drawOffer)
def drawOffer(fullId: String) = performAndRedirect(fullId, xhr.drawOffer)
def drawCancel(fullId: String) = validAndRedirect(fullId, xhr.drawCancel)
def drawCancel(fullId: String) = performAndRedirect(fullId, xhr.drawCancel)
def drawDecline(fullId: String) = validAndRedirect(fullId, xhr.drawDecline)
def drawDecline(fullId: String) = performAndRedirect(fullId, xhr.drawDecline)
def moretime(fullId: String) = Action {
(xhr moretime fullId).unsafePerformIO.fold(
@ -57,8 +60,19 @@ object AppXhrC extends LilaController {
def nbGames = Action { Ok(env.gameRepo.countPlaying.unsafePerformIO) }
private def validAndRedirect(fullId: String, f: String IO[Valid[Unit]]) =
Action {
ValidIORedir(f(fullId), fullId)
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"),
events env.gameSocket.send(DbGame takeGameId fullId, events)
)
}
private def performAndRedirect(fullId: String, op: String IOValidEvents) =
Action {
perform(fullId, op).unsafePerformIO
Redirect("/" + fullId)
}
}

View file

@ -28,15 +28,6 @@ trait LilaController extends Controller with ContentTypes with RequestGetter {
_ Ok("ok")
)
def ValidIORedir(op: IO[Valid[Unit]], url: String) =
op.unsafePerformIO.fold(
failures {
println(failures.list mkString "\n")
Redirect("/" + url)
},
_ Redirect("/" + url)
)
def FormValidIOk[A](form: Form[A])(op: A IO[Unit])(implicit request: Request[_]) =
form.bindFromRequest.fold(
form BadRequest(form.errors mkString "\n"),

View file

@ -30,6 +30,8 @@ class GameRepo(collection: MongoCollection)
else findOneByID(gameId) flatMap decode
}
def pov(ref: PovRef): IO[Pov] = pov(ref.gameId, ref.color)
def pov(gameId: String, color: Color): IO[Pov] =
game(gameId) map { g Pov(g, g player color) }

View file

@ -14,7 +14,7 @@ import scalaz.effects._
import chess.Color
import db.GameRepo
import model.{ DbGame, Pov, Progress }
import model.{ DbGame, Pov, PovRef, Progress, Event }
final class Socket(
gameRepo: GameRepo,
@ -24,26 +24,38 @@ final class Socket(
implicit val timeout = Timeout(1 second)
def send(progress: Progress): IO[Unit] = io {
(hubMemo get progress.game.id) ! Events(progress.events)
implicit def richJsObject(js: JsObject) = new {
def str(key: String): Option[String] = js.value get key map (_.as[String])
def obj(key: String): Option[JsObject] = js.value get key map (_.as[JsObject])
}
def send(progress: Progress): IO[Unit] =
send(progress.game.id, progress.events)
def send(gameId: String, events: List[Event]): IO[Unit] = io {
(hubMemo get gameId) ! Events(events)
}
def listener(
hub: ActorRef,
member: Member,
gameId: String): JsValue Unit = member match {
povRef: PovRef): JsValue Unit = member match {
case Watcher(_, _, _) (_: JsValue) Unit
case Owner(_, _, _) (e: JsValue) (e \ "t").as[String] match {
case Owner(_, color, _) (e: JsValue) (e \ "t").as[String] match {
case "talk" (e \ "d").as[String] |> { txt
hub ! Events(
messenger.playerMessage(gameId, member.color, txt).unsafePerformIO
messenger.playerMessage(povRef, txt).unsafePerformIO
)
}
case "move" {
val orig = (e \ "d" \ "from").as[String]
val dest = (e \ "d" \ "to").as[String]
xhr.play(fullId, move._1, move._2, move._3).unsafePerformIO
}
case "move" for {
d e.as[JsObject] obj "d"
orig d str "from"
dest d str "to"
promotion = d str "promotion"
} xhr.play(povRef, orig, dest, promotion).unsafePerformIO.fold(
error println(error.list mkString "\n"),
events send(povRef.gameId, events)
)
}
}
@ -68,7 +80,7 @@ final class Socket(
)).asPromise map {
case Connected(member) (
Iteratee.foreach[JsValue](
listener(hub, member, gameId)
listener(hub, member, PovRef(gameId, member.color))
) mapDone { _
hub ! Quit(uid)
},

View file

@ -11,6 +11,8 @@ case class Pov(game: DbGame, color: Color) {
def isPlayerFullId(fullId: Option[String]): Boolean =
fullId some { game.isPlayerFullId(player, _) } none false
def ref = PovRef(game.id, color)
}
object Pov {
@ -20,3 +22,5 @@ object Pov {
def apply(game: DbGame, playerId: String): Option[Pov] =
game player playerId map { p new Pov(game, p.color) }
}
case class PovRef(gameId: String, color: Color)

View file

@ -23,6 +23,8 @@ package object lila
object Tick // standard actor tick
type ValidIOEvents = Valid[scalaz.effects.IO[List[model.Event]]]
// custom salat context
implicit val ctx = new Context {
val name = "Lila System Context"