handle rematch negociation through websockets
parent
10c6109377
commit
27e24b7da6
|
@ -102,19 +102,6 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
|
|||
def drawCancel(fullId: String) = performAndRedirect(fullId, DrawCancel(_))
|
||||
def drawDecline(fullId: String) = performAndRedirect(fullId, DrawDecline(_))
|
||||
|
||||
def rematch(fullId: String) = Open { implicit ctx ⇒
|
||||
Env.setup.rematcher offerOrAccept PlayerRef(fullId) fold (
|
||||
_ ⇒ Redirect(routes.Round.player(fullId)), {
|
||||
case (nextFullId, events) ⇒ {
|
||||
sendEvents(fullId)(events)
|
||||
Redirect(routes.Round.player(nextFullId))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
def rematchCancel(fullId: String) = performAndRedirect(fullId, RematchCancel(_))
|
||||
def rematchDecline(fullId: String) = performAndRedirect(fullId, RematchDecline(_))
|
||||
|
||||
def takebackAccept(fullId: String) = performAndRedirect(fullId, TakebackAccept(_))
|
||||
def takebackOffer(fullId: String) = performAndRedirect(fullId, TakebackOffer(_))
|
||||
def takebackCancel(fullId: String) = performAndRedirect(fullId, TakebackCancel(_))
|
||||
|
|
|
@ -3,29 +3,27 @@ package controllers
|
|||
import lila.app._
|
||||
import lila.game.Event
|
||||
import lila.socket.actorApi.Forward
|
||||
import lila.hub.actorApi.map.Ask
|
||||
import lila.hub.actorApi.map.Tell
|
||||
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 performAndRedirect(fullId: String, makeMessage: String ⇒ Any) = Action {
|
||||
perform(fullId, makeMessage)
|
||||
Redirect(routes.Round.player(fullId))
|
||||
}
|
||||
|
||||
protected def perform(fullId: String, makeMessage: String ⇒ Any): Fu[List[Event]] =
|
||||
Env.round.roundMap ?
|
||||
Ask(takeGameId(fullId), makeMessage(takePlayerId(fullId))) mapTo
|
||||
manifest[List[Event]] logFailure
|
||||
"[round] fail to perform on game %s".format(fullId)
|
||||
protected def perform(fullId: String, makeMessage: String ⇒ Any) {
|
||||
Env.round.roundMap ! Tell(
|
||||
takeGameId(fullId),
|
||||
makeMessage(takePlayerId(fullId))
|
||||
)
|
||||
}
|
||||
|
||||
protected def sendEvents(gameId: String)(events: List[Event]) {
|
||||
Env.round.socketHub ! Forward(gameId, events)
|
||||
Env.round.roundMap ! Tell(gameId, events)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ object Setup extends LilaController with TheftPrevention with RoundEventPerforme
|
|||
Redirect(routes.Round.watcher(id, game.creatorColor.name)),
|
||||
_ map {
|
||||
case (p, events) ⇒ {
|
||||
sendEvents(p.gameId)(events)
|
||||
Env.round.roundMap ! lila.hub.actorApi.map.Tell(p.gameId, events)
|
||||
Redirect(routes.Round.player(p.fullId))
|
||||
}
|
||||
})
|
||||
|
|
|
@ -16,18 +16,18 @@
|
|||
@if(opponent.isOfferingRematch) {
|
||||
<div class="lichess_play_again_join rematch_alert">
|
||||
@trans.yourOpponentWantsToPlayANewGameWithYou().
|
||||
<a class="lichess_play_again lichess_rematch" title="@trans.playWithTheSameOpponentAgain()" href="@routes.Round.rematch(fullId)">@trans.joinTheGame()</a><br />
|
||||
<a class="lichess_rematch_decline" href="@routes.Round.rematchDecline(fullId)">@trans.declineInvitation()</a>
|
||||
<a class="lichess_play_again lichess_rematch socket-link" title="@trans.playWithTheSameOpponentAgain()" data-msg="rematch-yes">@trans.joinTheGame()</a><br />
|
||||
<a class="lichess_rematch_decline socket-link" data-msg="rematch-no">@trans.declineInvitation()</a>
|
||||
</div>
|
||||
} else {
|
||||
@if(player.isOfferingRematch) {
|
||||
<div class="lichess_play_again_join rematch_wait">
|
||||
@trans.rematchOfferSent().<br />
|
||||
@trans.waitingForOpponent()...<br /><br />
|
||||
<a class="lichess_rematch_cancel" href="@routes.Round.rematchCancel(fullId)">@trans.cancelRematchOffer()</a>
|
||||
<a class="lichess_rematch_cancel socket-link" data-msg="rematch-no">@trans.cancelRematchOffer()</a>
|
||||
</div>
|
||||
} else {
|
||||
<a class="lichess_rematch button" title="@trans.playWithTheSameOpponentAgain()" href="@routes.Round.rematch(fullId)">@trans.rematch()</a>
|
||||
<a class="lichess_rematch button socket-link" title="@trans.playWithTheSameOpponentAgain()" data-msg="rematch-yes">@trans.rematch()</a>
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -51,9 +51,6 @@ GET /$fullId<[\w\-]{12}>/draw/accept controllers.Round.drawAccep
|
|||
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}>/rematch controllers.Round.rematch(fullId: String)
|
||||
GET /$fullId<[\w\-]{12}>/rematch/cancel controllers.Round.rematchCancel(fullId: String)
|
||||
GET /$fullId<[\w\-]{12}>/rematch/decline controllers.Round.rematchDecline(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)
|
||||
|
|
|
@ -148,7 +148,7 @@ trait WithPlay extends Zeros { self: PackageObject ⇒
|
|||
case e ⇒ throw e
|
||||
})
|
||||
|
||||
def addEffect(effect: A => Unit) = fua ~ (_ foreach effect)
|
||||
def addEffect(effect: A ⇒ Unit) = fua ~ (_ foreach effect)
|
||||
|
||||
def thenPp: Fu[A] = fua ~ {
|
||||
_.effectFold(
|
||||
|
|
|
@ -140,6 +140,11 @@ object Event {
|
|||
override def only = Some(color)
|
||||
}
|
||||
|
||||
case object ReloadTables extends Event {
|
||||
def typ = "reload_table"
|
||||
def data = JsNull
|
||||
}
|
||||
|
||||
case class Premove(color: Color) extends Empty {
|
||||
def typ = "premove"
|
||||
override def only = Some(color)
|
||||
|
|
|
@ -22,7 +22,7 @@ final class ActorMap[A <: Actor](mkActor: String ⇒ A) extends Actor {
|
|||
|
||||
case Count ⇒ sender ! actors.size
|
||||
|
||||
case Ask(id, msg) ⇒ get(id) flatMap { _ ? msg } pipeTo sender
|
||||
// case Ask(id, msg) ⇒ get(id.pp) flatMap { _ ? msg.pp } pipeTo sender
|
||||
|
||||
case Tell(id, msg, _) ⇒ get(id) foreach { _ forward msg }
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ final class Env(
|
|||
takeback = takeback,
|
||||
ai = ai,
|
||||
finisher = finisher,
|
||||
rematcher = rematcher,
|
||||
notifyMove = notifyMove,
|
||||
socketHub = socketHub,
|
||||
moretimeDuration = Moretime)
|
||||
|
@ -73,6 +74,11 @@ final class Env(
|
|||
indexer = hub.actor.gameIndexer,
|
||||
tournamentOrganizer = hub.actor.tournamentOrganizer)
|
||||
|
||||
private lazy val rematcher = new Rematcher(
|
||||
messenger = messenger,
|
||||
router = hub.actor.router,
|
||||
timeline = hub.actor.timeline)
|
||||
|
||||
lazy val meddler = new Meddler(
|
||||
roundMap = roundMap,
|
||||
socketHub = socketHub)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package lila.setup
|
||||
package lila.round
|
||||
|
||||
import chess.{ Game ⇒ ChessGame, Board, Clock, Variant, Color ⇒ ChessColor }
|
||||
import ChessColor.{ White, Black }
|
||||
import chess.format.Forsyth
|
||||
import lila.game.{ GameRepo, Game, Event, Progress, Pov, PlayerRef, Namer, Source }
|
||||
import lila.round.Messenger
|
||||
import lila.user.User
|
||||
import lila.hub.actorApi.router.Player
|
||||
import makeTimeout.short
|
||||
|
@ -13,31 +12,44 @@ import lila.game.tube.gameTube
|
|||
import lila.user.tube.userTube
|
||||
import lila.db.api._
|
||||
|
||||
import play.api.libs.json.{ Json, JsObject }
|
||||
import akka.pattern.ask
|
||||
|
||||
private[setup] final class Rematcher(
|
||||
private[round] final class Rematcher(
|
||||
messenger: Messenger,
|
||||
router: lila.hub.ActorLazyRef,
|
||||
timeline: lila.hub.ActorLazyRef) {
|
||||
|
||||
private type Result = (String, List[Event])
|
||||
def yes(pov: Pov): Fu[Events] = pov match {
|
||||
case Pov(game, color) ⇒ (game playerCanRematch color) ??
|
||||
game.opponent(color).isOfferingRematch.fold(
|
||||
game.next.fold(rematchJoin(pov))(rematchExists(pov)),
|
||||
rematchCreate(pov)
|
||||
)
|
||||
}
|
||||
|
||||
def offerOrAccept(playerRef: PlayerRef): Fu[Result] =
|
||||
GameRepo pov playerRef flatten "No such game" flatMap {
|
||||
case pov @ Pov(game, color) ⇒ (game playerCanRematch color) ??
|
||||
game.opponent(color).isOfferingRematch.fold(
|
||||
game.next.fold(rematchJoin(pov))(rematchExists(pov)),
|
||||
rematchCreate(pov)
|
||||
)
|
||||
def no(pov: Pov): Fu[Events] = pov match {
|
||||
case Pov(g1, color) if (pov.player.isOfferingRematch) ⇒ for {
|
||||
p1 ← messenger.systemMessage(g1, _.rematchOfferCanceled) map { es ⇒
|
||||
Progress(g1, Event.ReloadTables :: 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 {
|
||||
p1 ← messenger.systemMessage(g1, _.rematchOfferDeclined) map { es ⇒
|
||||
Progress(g1, Event.ReloadTables :: es)
|
||||
}
|
||||
p2 = p1 map { g ⇒ g.updatePlayer(!color, _.removeRematchOffer) }
|
||||
_ ← GameRepo save p2
|
||||
} yield p2.events
|
||||
}
|
||||
|
||||
private def rematchExists(pov: Pov)(nextId: String): Fu[Events] =
|
||||
GameRepo game nextId flatMap {
|
||||
_.fold(rematchJoin(pov))(redirectEvents)
|
||||
}
|
||||
|
||||
private def rematchExists(pov: Pov)(nextId: String): Fu[Result] =
|
||||
GameRepo.pov(nextId, !pov.color) map {
|
||||
_.fold(pov.fullId -> Nil)(_.fullId -> Nil)
|
||||
}
|
||||
|
||||
private def rematchJoin(pov: Pov): Fu[Result] = for {
|
||||
private def rematchJoin(pov: Pov): Fu[Events] = for {
|
||||
nextGame ← returnGame(pov) map (_.start)
|
||||
nextId = nextGame.id
|
||||
_ ← (GameRepo insertDenormalized nextGame) >>
|
||||
|
@ -46,25 +58,16 @@ private[setup] final class Rematcher(
|
|||
// messenges are not sent to the next game socket
|
||||
// as nobody is there to see them yet
|
||||
messenger.rematch(pov.game, nextGame)
|
||||
result ← router ? Player(nextGame fullIdOf White) zip
|
||||
router ? Player(nextGame fullIdOf Black) collect {
|
||||
case (whiteUrl: String, blackUrl: String) ⇒
|
||||
(nextGame fullIdOf !pov.color) -> List(
|
||||
Event.RedirectOwner(White, blackUrl),
|
||||
Event.RedirectOwner(Black, whiteUrl),
|
||||
// tell spectators to reload the table
|
||||
Event.ReloadTable(White),
|
||||
Event.ReloadTable(Black))
|
||||
}
|
||||
} yield result
|
||||
events ← redirectEvents(nextGame)
|
||||
} yield events
|
||||
|
||||
private def rematchCreate(pov: Pov): Fu[Result] = for {
|
||||
private def rematchCreate(pov: Pov): Fu[Events] = for {
|
||||
p1 ← messenger.systemMessage(pov.game, _.rematchOfferSent) map { es ⇒
|
||||
Progress(pov.game, Event.ReloadTable(!pov.color) :: es)
|
||||
Progress(pov.game, Event.ReloadTables :: es)
|
||||
}
|
||||
p2 = p1 map { g ⇒ g.updatePlayer(pov.color, _ offerRematch) }
|
||||
_ ← GameRepo save p2
|
||||
} yield pov.fullId -> p2.events
|
||||
} yield p2.events
|
||||
|
||||
private def returnGame(pov: Pov): Fu[Game] = for {
|
||||
pieces ← pov.game.variant.standard.fold(
|
||||
|
@ -99,4 +102,15 @@ private[setup] final class Rematcher(
|
|||
$find.byId[User](userId) map { _.fold(player)(player.withUser) }
|
||||
}
|
||||
}
|
||||
|
||||
private def redirectEvents(nextGame: Game): Fu[Events] =
|
||||
router ? Player(nextGame fullIdOf White) zip
|
||||
router ? Player(nextGame fullIdOf Black) collect {
|
||||
case (whiteUrl: String, blackUrl: String) ⇒ List(
|
||||
Event.RedirectOwner(White, blackUrl),
|
||||
Event.RedirectOwner(Black, whiteUrl),
|
||||
// tell spectators to reload the table
|
||||
Event.ReloadTable(White),
|
||||
Event.ReloadTable(Black))
|
||||
}
|
||||
}
|
|
@ -20,19 +20,21 @@ private[round] final class Round(
|
|||
takeback: Takeback,
|
||||
ai: Ai,
|
||||
finisher: Finisher,
|
||||
rematcher: Rematcher,
|
||||
notifyMove: (String, String, Option[String]) ⇒ Unit,
|
||||
socketHub: ActorRef,
|
||||
moretimeDuration: Duration) extends Handler(gameId) with Actor {
|
||||
|
||||
// TODO 30 seconds sounds good
|
||||
context setReceiveTimeout 5.seconds
|
||||
context setReceiveTimeout 30.seconds
|
||||
|
||||
def receive = {
|
||||
|
||||
case ReceiveTimeout ⇒ self ! PoisonPill
|
||||
|
||||
// useful to get the game after all messages have been processed
|
||||
case GetGame ⇒ GameRepo game gameId pipeTo sender
|
||||
// guaranty that all previous blocking events were performed
|
||||
case Await ⇒ sender ! ()
|
||||
|
||||
case Send(events) ⇒ sendEvents(events)
|
||||
|
||||
case Play(playerId, origS, destS, promS, blur, lag) ⇒ blocking[PlayResult](playerId) {
|
||||
case Pov(g1, color) ⇒ PgnRepo get g1.id flatMap { pgnString ⇒
|
||||
|
@ -161,25 +163,8 @@ private[round] final class Round(
|
|||
} yield p2.events)
|
||||
}
|
||||
|
||||
case RematchCancel(playerRef) ⇒ sender ! blocking(playerRef) {
|
||||
case pov @ Pov(g1, color) ⇒ (pov.player.isOfferingRematch) ?? (for {
|
||||
p1 ← messenger.systemMessage(g1, _.rematchOfferCanceled) map { es ⇒
|
||||
Progress(g1, Event.ReloadTable(!color) :: es)
|
||||
}
|
||||
p2 = p1 map { g ⇒ g.updatePlayer(color, _.removeRematchOffer) }
|
||||
_ ← GameRepo save p2
|
||||
} yield p2.events)
|
||||
}
|
||||
|
||||
case RematchDecline(playerRef) ⇒ sender ! blocking(playerRef) {
|
||||
case pov @ Pov(g1, color) ⇒ (g1.player(!color).isOfferingRematch) ?? (for {
|
||||
p1 ← messenger.systemMessage(g1, _.rematchOfferDeclined) map { es ⇒
|
||||
Progress(g1, Event.ReloadTable(!color) :: es)
|
||||
}
|
||||
p2 = p1 map { g ⇒ g.updatePlayer(!color, _.removeRematchOffer) }
|
||||
_ ← GameRepo save p2
|
||||
} yield p2.events)
|
||||
}
|
||||
case RematchYes(playerRef) ⇒ blocking(playerRef)(rematcher.yes) ~ sendEvents
|
||||
case RematchNo(playerRef) ⇒ blocking(playerRef)(rematcher.no) ~ sendEvents
|
||||
|
||||
case TakebackAccept(playerRef) ⇒ sender ! blocking(playerRef) { pov ⇒
|
||||
(pov.opponent.isProposingTakeback && pov.game.nonTournament) ?? (for {
|
||||
|
|
|
@ -33,8 +33,6 @@ private[round] final class SocketHandler(
|
|||
case ("p", o) ⇒ o int "v" foreach { v ⇒ socket ! PingVersion(uid, v) }
|
||||
case ("talk", o) ⇒ for {
|
||||
txt ← o str "d"
|
||||
// TODO troll
|
||||
// if member.canChat
|
||||
if flood.allowMessage(uid, txt)
|
||||
} messenger.watcherMessage(
|
||||
ref.gameId,
|
||||
|
@ -45,10 +43,10 @@ private[round] final class SocketHandler(
|
|||
case ("p", o) ⇒ o int "v" foreach { v ⇒ socket ! PingVersion(uid, v) }
|
||||
case ("talk", o) ⇒ for {
|
||||
txt ← o str "d"
|
||||
// TODO troll
|
||||
// if member.canChat
|
||||
if flood.allowMessage(uid, txt)
|
||||
} messenger.playerMessage(ref, txt) pipeTo socket
|
||||
case ("rematch-yes", o) ⇒ roundMap ! Tell(gameId, RematchYes(playerId))
|
||||
case ("rematch-no", o) ⇒ roundMap ! Tell(gameId, RematchNo(playerId))
|
||||
case ("move", o) ⇒ parseMove(o) foreach {
|
||||
case (orig, dest, prom, blur, lag) ⇒ {
|
||||
socket ! Ack(uid)
|
||||
|
|
|
@ -76,7 +76,8 @@ package round {
|
|||
|
||||
case class PlayResult(events: Events, fen: String, lastMove: Option[String])
|
||||
|
||||
case object GetGame
|
||||
case object Await
|
||||
case class Send(events: Events)
|
||||
case class Abort(playerId: String)
|
||||
case object AbortForce
|
||||
case class Resign(playerId: String)
|
||||
|
@ -88,8 +89,8 @@ package round {
|
|||
case class DrawCancel(playerId: String)
|
||||
case class DrawDecline(playerId: String)
|
||||
case object DrawForce
|
||||
case class RematchCancel(playerId: String)
|
||||
case class RematchDecline(playerId: String)
|
||||
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)
|
||||
|
|
|
@ -31,11 +31,6 @@ final class Env(
|
|||
router = hub.actor.router,
|
||||
ai = ai)
|
||||
|
||||
lazy val rematcher = new Rematcher(
|
||||
messenger = messenger,
|
||||
router = hub.actor.router,
|
||||
timeline = hub.actor.timeline)
|
||||
|
||||
lazy val friendJoiner = new FriendJoiner(
|
||||
messenger = messenger,
|
||||
router = hub.actor.router,
|
||||
|
|
|
@ -89,7 +89,7 @@ var lichess_translations = [];
|
|||
t: t,
|
||||
d: data
|
||||
});
|
||||
self.debug(message);
|
||||
self.debug("send " + message);
|
||||
self.ws.send(message);
|
||||
},
|
||||
scheduleConnect: function(delay) {
|
||||
|
@ -586,6 +586,10 @@ var lichess_translations = [];
|
|||
$('body').data('tournament-id', self.options.tournament_id);
|
||||
}
|
||||
|
||||
self.$tableInner.on('click', 'a.socket-link', function() {
|
||||
lichess.socket.send($(this).data('msg'));
|
||||
});
|
||||
|
||||
if (self.options.game.started) {
|
||||
self.indicateTurn();
|
||||
self.initSquaresAndPieces();
|
||||
|
|
Loading…
Reference in New Issue