do all takeback negociation in websockets

pull/83/head
Thibault Duplessis 2013-05-18 12:26:37 -03:00
parent 407c4b621a
commit 9de59615d2
14 changed files with 181 additions and 206 deletions

View File

@ -7,7 +7,9 @@ import lila.game.{ Pov, PlayerRef, GameRepo, Game ⇒ GameModel }
import lila.round.{ RoomRepo, WatcherRoomRepo }
import lila.round.actorApi.round._
import lila.socket.actorApi.{ Forward, GetVersion }
import lila.hub.actorApi.map.{ Tell, Ask }
import lila.tournament.{ TournamentRepo, Tournament Tourney }
import makeTimeout.large
import akka.pattern.ask
import play.api.mvc._
@ -15,7 +17,7 @@ import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.templates.Html
object Round extends LilaController with TheftPrevention with RoundEventPerformer {
object Round extends LilaController with TheftPrevention {
private def env = Env.round
private def bookmarkApi = Env.bookmark.api
@ -96,16 +98,6 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
def abort(fullId: String) = performAndRedirect(fullId, Abort(_))
def resign(fullId: String) = performAndRedirect(fullId, Resign(_))
def resignForce(fullId: String) = performAndRedirect(fullId, ResignForce(_))
def drawClaim(fullId: String) = performAndRedirect(fullId, DrawClaim(_))
def drawAccept(fullId: String) = performAndRedirect(fullId, DrawAccept(_))
def drawOffer(fullId: String) = performAndRedirect(fullId, DrawOffer(_))
def drawCancel(fullId: String) = performAndRedirect(fullId, DrawCancel(_))
def drawDecline(fullId: String) = performAndRedirect(fullId, DrawDecline(_))
def takebackAccept(fullId: String) = performAndRedirect(fullId, TakebackAccept(_))
def takebackOffer(fullId: String) = performAndRedirect(fullId, TakebackOffer(_))
def takebackCancel(fullId: String) = performAndRedirect(fullId, TakebackCancel(_))
def takebackDecline(fullId: String) = performAndRedirect(fullId, TakebackDecline(_))
def tableWatcher(gameId: String, color: String) = Open { implicit ctx
OptionOk(GameRepo.pov(gameId, color)) { html.round.table.watch(_) }
@ -129,4 +121,18 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
} toMap) ++ ctx.me.??(me Map("me" -> me.usernameWithElo))
})
}
protected def performAndRedirect(fullId: String, makeMessage: String Any) = Action {
Async {
perform(fullId, makeMessage) inject Redirect(routes.Round.player(fullId))
}
}
protected def perform(fullId: String, makeMessage: String Any): Funit = {
Env.round.roundMap ! Tell(
GameModel takeGameId fullId,
makeMessage(GameModel takePlayerId(fullId))
)
Env.round.roundMap ? Ask(GameModel takeGameId fullId, Await) void
}
}

View File

@ -1,29 +0,0 @@
package controllers
import lila.app._
import lila.game.Event
import lila.socket.actorApi.Forward
import lila.hub.actorApi.map.{ Tell, Ask }
import lila.round.actorApi.round.Await
import lila.game.Game.{ takeGameId, takePlayerId }
import makeTimeout.large
import akka.pattern.ask
import play.api.mvc._, Results._
trait RoundEventPerformer {
protected def performAndRedirect(fullId: String, makeMessage: String Any) = Action {
Async {
perform(fullId, makeMessage) inject Redirect(routes.Round.player(fullId))
}
}
protected def perform(fullId: String, makeMessage: String Any): Funit = {
Env.round.roundMap ! Tell(
takeGameId(fullId),
makeMessage(takePlayerId(fullId))
)
Env.round.roundMap ? Ask(takeGameId(fullId), Await) void
}
}

View File

@ -8,7 +8,7 @@ import views._
import play.api.mvc.{ Result, Call }
import play.api.data.Form
object Setup extends LilaController with TheftPrevention with RoundEventPerformer {
object Setup extends LilaController with TheftPrevention {
private def env = Env.setup

View File

@ -21,7 +21,7 @@
<a class="offer_draw" href="@routes.Round.drawOffer(fullId)">@trans.offerDraw()</a>
}
@if(game.playerCanProposeTakeback(color)) {
<a class="propose_takeback" title="@trans.proposeATakeback()"href="@routes.Round.takebackOffer(fullId)">@trans.takeback()</a>
<a class="propose_takeback socket-link" data-msg="takeback-yes" title="@trans.proposeATakeback()">@trans.takeback()</a>
}
}
</div>
@ -53,14 +53,14 @@
@if(player.isProposingTakeback) {
<div class="proposed_takeback">
@trans.takebackPropositionSent().&nbsp;
<a href="@routes.Round.takebackCancel(fullId)">@trans.cancel()</a>
<a class="socket-link" data-msg="takeback-no">@trans.cancel()</a>
</div>
} else {
@if(opponent.isProposingTakeback) {
<div class="offered_draw">
@trans.yourOpponentProposesATakeback().<br />
<a href="@routes.Round.takebackAccept(fullId)">@trans.accept()</a>&nbsp;
<a href="@routes.Round.takebackDecline(fullId)">@trans.decline()</a>
<a class="socket-link" data-msg="takeback-yes">@trans.accept()</a>&nbsp;
<a class="socket-link" data-msg="takeback-no">@trans.decline()</a>
</div>
}
}

View File

@ -47,14 +47,6 @@ GET /$fullId<[\w\-]{12}>/abort controllers.Round.abort(ful
GET /$fullId<[\w\-]{12}>/resign controllers.Round.resign(fullId: String)
GET /$fullId<[\w\-]{12}>/resign/force controllers.Round.resignForce(fullId: String)
GET /$fullId<[\w\-]{12}>/draw/claim controllers.Round.drawClaim(fullId: String)
GET /$fullId<[\w\-]{12}>/draw/accept controllers.Round.drawAccept(fullId: String)
GET /$fullId<[\w\-]{12}>/draw/offer controllers.Round.drawOffer(fullId: String)
GET /$fullId<[\w\-]{12}>/draw/cancel controllers.Round.drawCancel(fullId: String)
GET /$fullId<[\w\-]{12}>/draw/decline controllers.Round.drawDecline(fullId: String)
GET /$fullId<[\w\-]{12}>/takeback/accept controllers.Round.takebackAccept(fullId: String)
GET /$fullId<[\w\-]{12}>/takeback/offer controllers.Round.takebackOffer(fullId: String)
GET /$fullId<[\w\-]{12}>/takeback/cancel controllers.Round.takebackCancel(fullId: String)
GET /$fullId<[\w\-]{12}>/takeback/decline controllers.Round.takebackDecline(fullId: String)
GET /$gameId<[\w\-]{8}>/$color<white|black>/table controllers.Round.tableWatcher(gameId: String, color: String)
GET /$fullId<[\w\-]{12}>/table controllers.Round.tablePlayer(fullId: String)
GET /$gameId<[\w\-]{8}>/players controllers.Round.players(gameId: String)

View File

@ -144,6 +144,11 @@ object Event {
def typ = "reload_table"
def data = JsNull
}
case object ReloadTablesOwner extends Event {
def typ = "reload_table"
def data = JsNull
override def owner = true
}
case class Premove(color: Color) extends Empty {
def typ = "premove"

View File

@ -0,0 +1,57 @@
package lila.round
import chess.{ Game ChessGame, Board, Clock, Variant, Color ChessColor }
import ChessColor.{ White, Black }
import lila.game.{ GameRepo, Game, Event, Progress, Pov, PlayerRef, Namer, Source }
import lila.user.User
import makeTimeout.short
import lila.game.tube.gameTube
import lila.user.tube.userTube
import lila.db.api._
import akka.pattern.ask
private[round] final class Drawer(
messenger: Messenger,
finisher: Finisher) {
def yes(pov: Pov): Fu[Events] = pov match {
case pov if pov.opponent.isOfferingDraw
finisher(pov.game, _.Draw, None, Some(_.drawOfferAccepted))
case Pov(g1, color) if (g1 playerCanOfferDraw color) for {
p1 messenger.systemMessage(g1, _.drawOfferSent) map { es
Progress(g1, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(color, _ offerDraw g.turns) }
_ GameRepo save p2
} yield p2.events
case _ fufail("[drawer] invalid yes " + pov)
}
def no(pov: Pov): Fu[Events] = pov match {
case Pov(g1, color) if pov.player.isOfferingDraw for {
p1 messenger.systemMessage(g1, _.drawOfferCanceled) map { es
Progress(g1, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.removeDrawOffer) }
_ GameRepo save p2
} yield p2.events
case Pov(g1, color) if pov.opponent.isOfferingDraw for {
p1 messenger.systemMessage(g1, _.drawOfferDeclined) map { es
Progress(g1, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(!color, _.removeDrawOffer) }
_ GameRepo save p2
} yield p2.events
case _ fufail("[drawer] invalid no " + pov)
}
def claim(pov: Pov): Fu[Events] =
(pov.game.playable &&
pov.game.player.color == pov.color &&
pov.game.toChessHistory.threefoldRepetition
) ?? finisher(pov.game, _.Draw)
def force(game: Game): Fu[Events] = finisher(game, _.Draw, None, None)
}

View File

@ -43,10 +43,11 @@ final class Env(
new Round(
gameId = id,
messenger = messenger,
takeback = takeback,
takebacker = takebacker,
ai = ai,
finisher = finisher,
rematcher = rematcher,
drawer = drawer,
notifyMove = notifyMove,
socketHub = socketHub,
moretimeDuration = Moretime)
@ -79,6 +80,10 @@ final class Env(
router = hub.actor.router,
timeline = hub.actor.timeline)
private lazy val drawer = new Drawer(
messenger = messenger,
finisher = finisher)
lazy val meddler = new Meddler(
roundMap = roundMap,
socketHub = socketHub)
@ -109,7 +114,8 @@ final class Env(
private lazy val hijack = new Hijack(HijackTimeout)
private lazy val takeback = new Takeback(messenger)
private lazy val takebacker = new Takebacker(
messenger = messenger)
private def notifyMove(gameId: String, fen: String, lastMove: Option[String]) {
hub.socket.hub ! lila.socket.actorApi.Fen(gameId, fen, lastMove)

View File

@ -40,6 +40,7 @@ private[round] final class Finisher(
((g.status >= Status.Mate) ?? incNbGames(g, Black)) >>-
(indexer ! lila.game.actorApi.InsertGame(g)) >>-
(tournamentOrganizer ! FinishGame(g.id))
// TODO send redirection events
} yield p2.events
private def incNbGames(game: Game, color: Color): Funit =

View File

@ -20,28 +20,30 @@ private[round] final class Rematcher(
timeline: lila.hub.ActorLazyRef) {
def yes(pov: Pov): Fu[Events] = pov match {
case Pov(game, color) (game playerCanRematch color) ??
case Pov(game, color) if (game playerCanRematch color)
game.opponent(color).isOfferingRematch.fold(
game.next.fold(rematchJoin(pov))(rematchExists(pov)),
rematchCreate(pov)
)
case _ fufail("[rematcher] invalid yes " + pov)
}
def no(pov: Pov): Fu[Events] = pov match {
case Pov(g1, color) if (pov.player.isOfferingRematch) for {
case Pov(g1, color) if pov.player.isOfferingRematch for {
p1 messenger.systemMessage(g1, _.rematchOfferCanceled) map { es
Progress(g1, Event.ReloadTables :: es)
Progress(g1, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.removeRematchOffer) }
_ GameRepo save p2
} yield p2.events
case Pov(g1, color) if (g1.player(!color).isOfferingRematch) for {
case Pov(g1, color) if pov.opponent.isOfferingRematch for {
p1 messenger.systemMessage(g1, _.rematchOfferDeclined) map { es
Progress(g1, Event.ReloadTables :: es)
Progress(g1, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(!color, _.removeRematchOffer) }
_ GameRepo save p2
} yield p2.events
case _ fufail("[rematcher] invalid no " + pov)
}
private def rematchExists(pov: Pov)(nextId: String): Fu[Events] =
@ -63,7 +65,7 @@ private[round] final class Rematcher(
private def rematchCreate(pov: Pov): Fu[Events] = for {
p1 messenger.systemMessage(pov.game, _.rematchOfferSent) map { es
Progress(pov.game, Event.ReloadTables :: es)
Progress(pov.game, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(pov.color, _ offerRematch) }
_ GameRepo save p2

View File

@ -17,10 +17,11 @@ import akka.pattern.{ ask, pipe }
private[round] final class Round(
gameId: String,
messenger: Messenger,
takeback: Takeback,
takebacker: Takebacker,
ai: Ai,
finisher: Finisher,
rematcher: Rematcher,
drawer: Drawer,
notifyMove: (String, String, Option[String]) Unit,
socketHub: ActorRef,
moretimeDuration: Duration) extends Actor {
@ -114,106 +115,16 @@ private[round] final class Round(
}
}
case DrawClaim(playerId) publishing(playerId) { pov
(pov.game.playable &&
pov.game.player.color == pov.color &&
pov.game.toChessHistory.threefoldRepetition
) ?? finisher(pov.game, _.Draw)
}
case DrawYes(playerRef) publishing(playerRef)(drawer.yes)
case DrawNo(playerRef) publishing(playerRef)(drawer.no)
case DrawClaim(playerId) publishing(playerId)(drawer.claim)
case DrawForce publishing(drawer force _)
case DrawAccept(playerId) publishing(playerId) { pov
pov.opponent.isOfferingDraw ?? finisher(pov.game, _.Draw, None, Some(_.drawOfferAccepted))
}
case RematchYes(playerRef) publishing(playerRef)(rematcher.yes)
case RematchNo(playerRef) publishing(playerRef)(rematcher.no)
case DrawForce publishing { game
finisher(game, _.Draw, None, None)
}
case DrawOffer(playerId) publishing(playerId) {
case pov @ Pov(g1, color) (g1 playerCanOfferDraw color) ?? {
if (g1.player(!color).isOfferingDraw)
finisher(pov.game, _.Draw, None, Some(_.drawOfferAccepted))
else for {
p1 messenger.systemMessage(g1, _.drawOfferSent) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(color, _ offerDraw g.turns) }
_ GameRepo save p2
} yield p2.events
}
}
case DrawCancel(playerRef) publishing(playerRef) {
case pov @ Pov(g1, color) (pov.player.isOfferingDraw) ?? (for {
p1 messenger.systemMessage(g1, _.drawOfferCanceled) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.removeDrawOffer) }
_ GameRepo save p2
} yield p2.events)
}
case DrawDecline(playerRef) publishing(playerRef) {
case pov @ Pov(g1, color) (g1.player(!color).isOfferingDraw) ?? (for {
p1 messenger.systemMessage(g1, _.drawOfferDeclined) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(!color, _.removeDrawOffer) }
_ GameRepo save p2
} yield p2.events)
}
case RematchYes(playerRef) publishing(playerRef)(rematcher.yes)
case RematchNo(playerRef) publishing(playerRef)(rematcher.no)
case TakebackAccept(playerRef) publishing(playerRef) { pov
(pov.opponent.isProposingTakeback && pov.game.nonTournament) ?? (for {
fen GameRepo initialFen pov.game.id
pgn PgnRepo get pov.game.id
res takeback(pov.game, pgn, fen)
} yield res)
}
case TakebackOffer(playerRef) publishing(playerRef) {
case pov @ Pov(g1, color)
(g1.playable && g1.bothPlayersHaveMoved && g1.nonTournament) ?? (for {
fen GameRepo initialFen pov.game.id
pgn PgnRepo get pov.game.id
result if (g1.player(!color).isAi)
takeback.double(pov.game, pgn, fen)
else if (g1.player(!color).isProposingTakeback)
takeback(pov.game, pgn, fen)
else for {
p1 messenger.systemMessage(g1, _.takebackPropositionSent) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.proposeTakeback) }
_ GameRepo save p2
} yield p2.events
} yield result)
}
case TakebackCancel(playerRef) publishing(playerRef) {
case pov @ Pov(g1, color)
(pov.player.isProposingTakeback) ?? (for {
p1 messenger.systemMessage(g1, _.takebackPropositionCanceled) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.removeTakebackProposition) }
_ GameRepo save p2
} yield p2.events)
}
case TakebackDecline(playerRef) publishing(playerRef) {
case pov @ Pov(g1, color)
(g1.player(!color).isProposingTakeback) ?? (for {
p1 messenger.systemMessage(g1, _.takebackPropositionDeclined) map { es
Progress(g1, Event.ReloadTable(!color) :: es)
}
p2 = p1 map { g g.updatePlayer(!color, _.removeTakebackProposition) }
_ GameRepo save p2
} yield p2.events)
}
case TakebackYes(playerRef) publishing(playerRef)(takebacker.yes)
case TakebackNo(playerRef) publishing(playerRef)(takebacker.no)
case Moretime(playerRef) publishing(playerRef) { pov
pov.game.clock.filter(_ pov.game.moretimeable) ?? { clock
@ -226,7 +137,7 @@ private[round] final class Round(
GameRepo save progress2 inject progress2.events
}
}
}
}
}
private def publish(events: List[Event]) {

View File

@ -1,37 +0,0 @@
package lila.round
import lila.game.{ GameRepo, Game, Rewind, Event, Progress }
import lila.db.api._
private[round] final class Takeback(messenger: Messenger) {
def apply(game: Game, pgn: String, initialFen: Option[String]): Fu[Events] =
(Rewind(game, pgn, initialFen) map {
case (progress, newPgn) savePgn(game.id, newPgn) >> save(progress)
}) ||| fail(game)
def double(game: Game, pgn: String, initialFen: Option[String]): Fu[Events] = {
for {
first Rewind(game, pgn, initialFen)
(prog1, pgn1) = first
second Rewind(prog1.game, pgn1, initialFen) map {
case (progress, newPgn) (prog1 withGame progress.game, newPgn)
}
(prog2, pgn2) = second
} yield savePgn(game.id, pgn2) >> save(prog2)
} ||| fail(game)
def fail[A](game: Game)(err: Failures) =
fufail[A]("Takeback %s".format(game.id) <:: err)
private def savePgn(gameId: String, pgn: String): Funit = {
import lila.game.tube.pgnTube
$update.field(gameId, "p", pgn, upsert = true)
}
private def save(p1: Progress): Fu[Events] = {
val p2 = p1 + Event.Reload
messenger.systemMessage(p1.game, _.takebackPropositionAccepted) >>
(GameRepo save p2) inject p2.events
}
}

View File

@ -0,0 +1,65 @@
package lila.round
import lila.game.{ GameRepo, Game, PgnRepo, Pov, Rewind, Event, Progress }
import lila.db.api._
private[round] final class Takebacker(messenger: Messenger) {
def yes(pov: Pov): Fu[Events] = pov match {
case Pov(game, _) if pov.opponent.isProposingTakeback single(game)
case Pov(game, _) if pov.opponent.isAi double(game)
case Pov(game, color) if (game playerCanProposeTakeback color) for {
p1 messenger.systemMessage(game, _.takebackPropositionSent) map { es
Progress(game, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.proposeTakeback) }
_ GameRepo save p2
} yield p2.events
case _ fufail("[takebacker] invalid yes " + pov)
}
def no(pov: Pov): Fu[Events] = pov match {
case Pov(game, color) if pov.player.isProposingTakeback for {
p1 messenger.systemMessage(game, _.takebackPropositionCanceled) map { es
Progress(game, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(color, _.removeTakebackProposition) }
_ GameRepo save p2
} yield p2.events
case Pov(game, color) if pov.opponent.isProposingTakeback for {
p1 messenger.systemMessage(game, _.takebackPropositionDeclined) map { es
Progress(game, Event.ReloadTablesOwner :: es)
}
p2 = p1 map { g g.updatePlayer(!color, _.removeTakebackProposition) }
_ GameRepo save p2
} yield p2.events
case _ fufail("[takebacker] invalid no " + pov)
}
private def extras(gameId: String): Fu[(Option[String], String)] =
(GameRepo initialFen gameId) zip (PgnRepo get gameId)
private def single(game: Game): Fu[Events] = extras(game.id) flatMap {
case (fen, pgn) Rewind(game, pgn, fen).future flatMap {
case (progress, newPgn) PgnRepo.save(game.id, newPgn) >> save(progress)
}
}
private def double(game: Game): Fu[Events] = extras(game.id) flatMap {
case (fen, pgn) for {
first Rewind(game, pgn, fen).future
(prog1, pgn1) = first
second Rewind(prog1.game, pgn1, fen).future map {
case (progress, newPgn) (prog1 withGame progress.game, newPgn)
}
(prog2, pgn2) = second
_ PgnRepo.save(game.id, pgn2)
events save(prog2)
} yield events
}
private def save(p1: Progress): Fu[Events] = {
val p2 = p1 + Event.Reload
messenger.systemMessage(p1.game, _.takebackPropositionAccepted) >>
(GameRepo save p2) inject p2.events
}
}

View File

@ -84,17 +84,13 @@ package round {
case class ResignColor(color: Color)
case class ResignForce(playerId: String)
case class DrawClaim(playerId: String)
case class DrawAccept(playerId: String)
case class DrawOffer(playerId: String)
case class DrawCancel(playerId: String)
case class DrawDecline(playerId: String)
case class DrawYes(playerId: String)
case class DrawNo(playerId: String)
case object DrawForce
case class RematchYes(playerId: String)
case class RematchNo(playerId: String)
case class TakebackAccept(playerId: String)
case class TakebackOffer(playerId: String)
case class TakebackCancel(playerId: String)
case class TakebackDecline(playerId: String)
case class TakebackYes(playerId: String)
case class TakebackNo(playerId: String)
case class Moretime(playerId: String)
case object Outoftime
}