Implement rematches (and refactor stuff)
This commit is contained in:
parent
4dbb6ca5fd
commit
8777401a55
|
@ -17,6 +17,7 @@ object Round extends LilaController {
|
||||||
private val gameRepo = env.game.gameRepo
|
private val gameRepo = env.game.gameRepo
|
||||||
private val socket = env.round.socket
|
private val socket = env.round.socket
|
||||||
private val hand = env.round.hand
|
private val hand = env.round.hand
|
||||||
|
private val rematcher = env.setup.rematcher
|
||||||
|
|
||||||
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req ⇒
|
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req ⇒
|
||||||
implicit val ctx = reqToCtx(req)
|
implicit val ctx = reqToCtx(req)
|
||||||
|
@ -54,8 +55,19 @@ object Round extends LilaController {
|
||||||
def drawOffer(fullId: String) = performAndRedirect(fullId, hand.drawOffer)
|
def drawOffer(fullId: String) = performAndRedirect(fullId, hand.drawOffer)
|
||||||
def drawCancel(fullId: String) = performAndRedirect(fullId, hand.drawCancel)
|
def drawCancel(fullId: String) = performAndRedirect(fullId, hand.drawCancel)
|
||||||
def drawDecline(fullId: String) = performAndRedirect(fullId, hand.drawDecline)
|
def drawDecline(fullId: String) = performAndRedirect(fullId, hand.drawDecline)
|
||||||
def rematchOffer(fullId: String) = TODO
|
def rematch(fullId: String) = Action {
|
||||||
def rematchAccept(fullId: String) = TODO
|
rematcher offerOrAccept fullId flatMap { validResult ⇒
|
||||||
|
validResult.fold(
|
||||||
|
err ⇒ putFailures(err) map { _ ⇒
|
||||||
|
Redirect(routes.Round.player(fullId))
|
||||||
|
}, {
|
||||||
|
case (nextFullId, events) ⇒ performEvents(fullId)(events) map { _ ⇒
|
||||||
|
Redirect(routes.Round.player(nextFullId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} unsafePerformIO
|
||||||
|
}
|
||||||
def rematchCancel(fullId: String) = TODO
|
def rematchCancel(fullId: String) = TODO
|
||||||
def rematchDecline(fullId: String) = TODO
|
def rematchDecline(fullId: String) = TODO
|
||||||
def takebackAccept(fullId: String) = performAndRedirect(fullId, hand.takebackAccept)
|
def takebackAccept(fullId: String) = performAndRedirect(fullId, hand.takebackAccept)
|
||||||
|
@ -70,7 +82,7 @@ object Round extends LilaController {
|
||||||
def tablePlayer(fullId: String) = Open { implicit ctx ⇒
|
def tablePlayer(fullId: String) = Open { implicit ctx ⇒
|
||||||
IOption(gameRepo pov fullId) { pov ⇒
|
IOption(gameRepo pov fullId) { pov ⇒
|
||||||
pov.game.playable.fold(
|
pov.game.playable.fold(
|
||||||
html.round.table.playing(pov),
|
html.round.table.playing(pov),
|
||||||
html.round.table.end(pov))
|
html.round.table.end(pov))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,9 @@ final class CoreEnv private (application: Application, val settings: Settings) {
|
||||||
settings = settings,
|
settings = settings,
|
||||||
mongodb = mongodb.apply _,
|
mongodb = mongodb.apply _,
|
||||||
gameRepo = game.gameRepo,
|
gameRepo = game.gameRepo,
|
||||||
|
userRepo = user.userRepo,
|
||||||
timelinePush = timeline.push.apply,
|
timelinePush = timeline.push.apply,
|
||||||
|
roundMessenger = round.messenger,
|
||||||
ai = ai.ai,
|
ai = ai.ai,
|
||||||
dbRef = user.userRepo.dbRef)
|
dbRef = user.userRepo.dbRef)
|
||||||
|
|
||||||
|
@ -81,15 +83,6 @@ final class CoreEnv private (application: Application, val settings: Settings) {
|
||||||
messageRepo = lobby.messageRepo,
|
messageRepo = lobby.messageRepo,
|
||||||
entryRepo = timeline.entryRepo)
|
entryRepo = timeline.entryRepo)
|
||||||
|
|
||||||
//lazy val appApi = new AppApi(
|
|
||||||
//userRepo = user.userRepo,
|
|
||||||
//gameRepo = game.gameRepo,
|
|
||||||
//roundSocket = round.socket,
|
|
||||||
//messenger = round.messenger,
|
|
||||||
//starter = lobby.starter,
|
|
||||||
//eloUpdater = user.eloUpdater,
|
|
||||||
//gameInfo = analyse.gameInfo)
|
|
||||||
|
|
||||||
lazy val mongodb = MongoConnection(
|
lazy val mongodb = MongoConnection(
|
||||||
new MongoServer(MongoHost, MongoPort),
|
new MongoServer(MongoHost, MongoPort),
|
||||||
mongoOptions
|
mongoOptions
|
||||||
|
|
|
@ -55,10 +55,12 @@ case class DbGame(
|
||||||
def isPlayerFullId(player: DbPlayer, fullId: String): Boolean =
|
def isPlayerFullId(player: DbPlayer, fullId: String): Boolean =
|
||||||
(fullId.size == DbGame.fullIdSize) && player.id == (fullId drop 8)
|
(fullId.size == DbGame.fullIdSize) && player.id == (fullId drop 8)
|
||||||
|
|
||||||
def opponent(p: DbPlayer): DbPlayer = player(!(p.color))
|
|
||||||
|
|
||||||
def player: DbPlayer = player(turnColor)
|
def player: DbPlayer = player(turnColor)
|
||||||
|
|
||||||
|
def opponent(p: DbPlayer): DbPlayer = opponent(p.color)
|
||||||
|
|
||||||
|
def opponent(c: Color): DbPlayer = player(!c)
|
||||||
|
|
||||||
def turnColor = Color(0 == turns % 2)
|
def turnColor = Color(0 == turns % 2)
|
||||||
|
|
||||||
def turnOf(p: DbPlayer) = p == player
|
def turnOf(p: DbPlayer) = p == player
|
||||||
|
@ -203,7 +205,8 @@ case class DbGame(
|
||||||
|
|
||||||
def start = started.fold(this, copy(
|
def start = started.fold(this, copy(
|
||||||
status = Status.Started,
|
status = Status.Started,
|
||||||
isRated = isRated && (players forall (_.hasUser))
|
isRated = isRated && (players forall (_.hasUser)),
|
||||||
|
updatedAt = DateTime.now.some
|
||||||
))
|
))
|
||||||
|
|
||||||
def recordMoveTimes = !hasAi
|
def recordMoveTimes = !hasAi
|
||||||
|
@ -231,16 +234,19 @@ case class DbGame(
|
||||||
started && playable &&
|
started && playable &&
|
||||||
turns >= 2 &&
|
turns >= 2 &&
|
||||||
!player(color).isOfferingDraw &&
|
!player(color).isOfferingDraw &&
|
||||||
!(player(!color).isAi) &&
|
!(opponent(color).isAi) &&
|
||||||
!(playerHasOfferedDraw(color))
|
!(playerHasOfferedDraw(color))
|
||||||
|
|
||||||
def playerHasOfferedDraw(color: Color) =
|
def playerHasOfferedDraw(color: Color) =
|
||||||
player(color).lastDrawOffer some (_ >= turns - 1) none false
|
player(color).lastDrawOffer.fold(_ >= turns - 1, false)
|
||||||
|
|
||||||
|
def playerCanRematch(color: Color) =
|
||||||
|
finishedOrAborted && opponent(color).isHuman
|
||||||
|
|
||||||
def playerCanProposeTakeback(color: Color) =
|
def playerCanProposeTakeback(color: Color) =
|
||||||
started && playable &&
|
started && playable &&
|
||||||
turns >= 2 &&
|
turns >= 2 &&
|
||||||
!player(color).isProposingTakeback
|
opponent(color).isProposingTakeback
|
||||||
|
|
||||||
def abortable = status == Status.Started && turns < 2
|
def abortable = status == Status.Started && turns < 2
|
||||||
|
|
||||||
|
@ -286,7 +292,9 @@ case class DbGame(
|
||||||
|
|
||||||
def creator = player(creatorColor)
|
def creator = player(creatorColor)
|
||||||
|
|
||||||
def invited = player(!creatorColor)
|
def invitedColor = !creatorColor
|
||||||
|
|
||||||
|
def invited = opponent(invitedColor)
|
||||||
|
|
||||||
def pgnList = pgn.split(' ').toList
|
def pgnList = pgn.split(' ').toList
|
||||||
|
|
||||||
|
@ -345,8 +353,7 @@ object DbGame {
|
||||||
ai: Option[(Color, Int)],
|
ai: Option[(Color, Int)],
|
||||||
creatorColor: Color,
|
creatorColor: Color,
|
||||||
isRated: Boolean,
|
isRated: Boolean,
|
||||||
variant: Variant,
|
variant: Variant): DbGame = DbGame(
|
||||||
createdAt: DateTime): DbGame = DbGame(
|
|
||||||
id = IdGenerator.game,
|
id = IdGenerator.game,
|
||||||
whitePlayer = whitePlayer withEncodedPieces game.allPieces,
|
whitePlayer = whitePlayer withEncodedPieces game.allPieces,
|
||||||
blackPlayer = blackPlayer withEncodedPieces game.allPieces,
|
blackPlayer = blackPlayer withEncodedPieces game.allPieces,
|
||||||
|
@ -362,7 +369,7 @@ object DbGame {
|
||||||
isRated = isRated,
|
isRated = isRated,
|
||||||
variant = variant,
|
variant = variant,
|
||||||
lastMoveTime = None,
|
lastMoveTime = None,
|
||||||
createdAt = createdAt.some)
|
createdAt = DateTime.now.some)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class RawDbGame(
|
case class RawDbGame(
|
||||||
|
|
|
@ -67,6 +67,10 @@ case class DbPlayer(
|
||||||
|
|
||||||
def removeDrawOffer = copy(isOfferingDraw = false)
|
def removeDrawOffer = copy(isOfferingDraw = false)
|
||||||
|
|
||||||
|
def offerRematch = copy(isOfferingRematch = true)
|
||||||
|
|
||||||
|
def removeRematchOffer = copy(isOfferingRematch = false)
|
||||||
|
|
||||||
def proposeTakeback = copy(isProposingTakeback = true)
|
def proposeTakeback = copy(isProposingTakeback = true)
|
||||||
|
|
||||||
def removeTakebackProposition = copy(isProposingTakeback = false)
|
def removeTakebackProposition = copy(isProposingTakeback = false)
|
||||||
|
@ -97,6 +101,10 @@ object DbPlayer {
|
||||||
id = IdGenerator.player,
|
id = IdGenerator.player,
|
||||||
color = color,
|
color = color,
|
||||||
aiLevel = aiLevel)
|
aiLevel = aiLevel)
|
||||||
|
|
||||||
|
def white = apply(Color.White, None)
|
||||||
|
|
||||||
|
def black = apply(Color.Black, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class RawDbPlayer(
|
case class RawDbPlayer(
|
||||||
|
|
|
@ -25,6 +25,7 @@ final class GameDiff(a: RawDbGame, b: RawDbGame) {
|
||||||
d(name + "w", _.players(i).w)
|
d(name + "w", _.players(i).w)
|
||||||
d(name + "lastDrawOffer", _.players(i).lastDrawOffer)
|
d(name + "lastDrawOffer", _.players(i).lastDrawOffer)
|
||||||
d(name + "isOfferingDraw", _.players(i).isOfferingDraw)
|
d(name + "isOfferingDraw", _.players(i).isOfferingDraw)
|
||||||
|
d(name + "isOfferingRematch", _.players(i).isOfferingRematch)
|
||||||
d(name + "isProposingTakeback", _.players(i).isProposingTakeback)
|
d(name + "isProposingTakeback", _.players(i).isProposingTakeback)
|
||||||
d(name + "blurs", _.players(i).blurs)
|
d(name + "blurs", _.players(i).blurs)
|
||||||
d(name + "mts", _.players(i).mts)
|
d(name + "mts", _.players(i).mts)
|
||||||
|
|
|
@ -24,10 +24,9 @@ trait GameHelper { self: I18nHelper with UserHelper ⇒
|
||||||
def clockName(clock: Option[Clock])(implicit ctx: Context): String =
|
def clockName(clock: Option[Clock])(implicit ctx: Context): String =
|
||||||
clock.fold(clockName, trans.unlimited.str())
|
clock.fold(clockName, trans.unlimited.str())
|
||||||
|
|
||||||
def clockName(clock: Clock): String = "%d minutes/side + %d seconds/move".format(
|
def clockName(clock: Clock): String = Namer clock clock
|
||||||
clock.limitInMinutes, clock.increment)
|
|
||||||
|
|
||||||
def usernameWithElo(player: DbPlayer) = PlayerNamer(player)(userIdToUsername)
|
def usernameWithElo(player: DbPlayer) = Namer.player(player)(userIdToUsername)
|
||||||
|
|
||||||
def playerLink(player: DbPlayer, cssClass: Option[String] = None) = Html {
|
def playerLink(player: DbPlayer, cssClass: Option[String] = None) = Html {
|
||||||
player.userId.fold(
|
player.userId.fold(
|
||||||
|
|
31
app/game/Handler.scala
Normal file
31
app/game/Handler.scala
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package lila
|
||||||
|
package game
|
||||||
|
|
||||||
|
import scalaz.effects._
|
||||||
|
|
||||||
|
abstract class Handler(gameRepo: GameRepo) {
|
||||||
|
|
||||||
|
protected def attempt[A](
|
||||||
|
fullId: String,
|
||||||
|
action: Pov ⇒ Valid[IO[A]]): IO[Valid[A]] =
|
||||||
|
fromPov(fullId) { pov ⇒ action(pov).sequence }
|
||||||
|
|
||||||
|
protected def attemptRef[A](
|
||||||
|
ref: PovRef,
|
||||||
|
action: Pov ⇒ Valid[IO[A]]): IO[Valid[A]] =
|
||||||
|
fromPov(ref) { pov ⇒ action(pov).sequence }
|
||||||
|
|
||||||
|
protected def fromPov[A](ref: PovRef)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
||||||
|
fromPov(gameRepo pov ref)(op)
|
||||||
|
|
||||||
|
protected def fromPov[A](fullId: String)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
||||||
|
fromPov(gameRepo pov fullId)(op)
|
||||||
|
|
||||||
|
protected def fromPov[A](povIO: IO[Option[Pov]])(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
||||||
|
povIO flatMap { povOption ⇒
|
||||||
|
povOption.fold(
|
||||||
|
pov ⇒ op(pov),
|
||||||
|
io { "No such game".failNel }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,20 @@
|
||||||
package lila
|
package lila
|
||||||
package game
|
package game
|
||||||
|
|
||||||
object PlayerNamer {
|
import chess.Clock
|
||||||
|
|
||||||
|
object Namer {
|
||||||
|
|
||||||
val anonPlayerName = "Anonymous"
|
val anonPlayerName = "Anonymous"
|
||||||
|
|
||||||
def apply(player: DbPlayer)(getUsername: String ⇒ String) =
|
def player(player: DbPlayer)(getUsername: String ⇒ String) =
|
||||||
player.aiLevel.fold(
|
player.aiLevel.fold(
|
||||||
level ⇒ "A.I. level " + level,
|
level ⇒ "A.I. level " + level,
|
||||||
(player.userId map getUsername).fold(
|
(player.userId map getUsername).fold(
|
||||||
username ⇒ "%s (%s)".format(username, player.elo getOrElse "?"),
|
username ⇒ "%s (%s)".format(username, player.elo getOrElse "?"),
|
||||||
anonPlayerName)
|
anonPlayerName)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clock(clock: Clock): String = "%d minutes/side + %d seconds/move".format(
|
||||||
|
clock.limitInMinutes, clock.increment)
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ package lila
|
||||||
package round
|
package round
|
||||||
|
|
||||||
import ai.Ai
|
import ai.Ai
|
||||||
import game.{ GameRepo, Pov, PovRef }
|
import game.{ GameRepo, Pov, PovRef, Handler }
|
||||||
import chess.Role
|
import chess.Role
|
||||||
import chess.Pos.posAt
|
import chess.Pos.posAt
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ final class Hand(
|
||||||
ai: () ⇒ Ai,
|
ai: () ⇒ Ai,
|
||||||
finisher: Finisher,
|
finisher: Finisher,
|
||||||
hubMaster: ActorRef,
|
hubMaster: ActorRef,
|
||||||
moretimeSeconds: Int) {
|
moretimeSeconds: Int) extends Handler(gameRepo) {
|
||||||
|
|
||||||
type IOValidEvents = IO[Valid[List[Event]]]
|
type IOValidEvents = IO[Valid[List[Event]]]
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ final class Hand(
|
||||||
g2 ← (g1.playable).fold(success(g1), failure("Game not playable" wrapNel))
|
g2 ← (g1.playable).fold(success(g1), failure("Game not playable" wrapNel))
|
||||||
orig ← posAt(origString) toValid "Wrong orig " + origString
|
orig ← posAt(origString) toValid "Wrong orig " + origString
|
||||||
dest ← posAt(destString) toValid "Wrong dest " + destString
|
dest ← posAt(destString) toValid "Wrong dest " + destString
|
||||||
promotion = Role promotable promString
|
promotion = Role promotable promString
|
||||||
newChessGameAndMove ← g2.toChess(orig, dest, promotion)
|
newChessGameAndMove ← g2.toChess(orig, dest, promotion)
|
||||||
(newChessGame, move) = newChessGameAndMove
|
(newChessGame, move) = newChessGameAndMove
|
||||||
} yield g2.update(newChessGame, move, blur)).fold(
|
} yield g2.update(newChessGame, move, blur)).fold(
|
||||||
|
@ -127,6 +127,29 @@ final class Hand(
|
||||||
else !!("no draw offer to decline " + fullId)
|
else !!("no draw offer to decline " + fullId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def rematch(fullId: String): IO[Valid[(String, List[Event])]] =
|
||||||
|
attempt(fullId, {
|
||||||
|
case pov @ Pov(game, color) if game playerCanRematch color ⇒
|
||||||
|
if (game.opponent(color).isOfferingRematch) success {
|
||||||
|
game.nextId.fold(
|
||||||
|
nextId ⇒ io(nextId -> Nil),
|
||||||
|
io(fullId -> Nil) // accept
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//$nextOpponent = $this->gameGenerator->createReturnGame($opponent);
|
||||||
|
//$nextPlayer = $nextOpponent->getOpponent();
|
||||||
|
//$nextGame = $nextOpponent->getGame();
|
||||||
|
//$messages = $this->starter->start($nextGame);
|
||||||
|
//$this->objectManager->persist($nextGame);
|
||||||
|
else success {
|
||||||
|
val progress = Progress(game, Event.ReloadTable(!color)) map { g ⇒
|
||||||
|
g.updatePlayer(color, _.offerRematch)
|
||||||
|
}
|
||||||
|
gameRepo save progress map { _ ⇒ fullId -> progress.events }
|
||||||
|
}
|
||||||
|
case _ ⇒ !!("invalid rematch offer " + fullId)
|
||||||
|
})
|
||||||
|
|
||||||
def takebackAccept(fullId: String): IOValidEvents = fromPov(fullId) { pov ⇒
|
def takebackAccept(fullId: String): IOValidEvents = fromPov(fullId) { pov ⇒
|
||||||
if (pov.opponent.isProposingTakeback) for {
|
if (pov.opponent.isProposingTakeback) for {
|
||||||
fen ← gameRepo initialFen pov.game.id
|
fen ← gameRepo initialFen pov.game.id
|
||||||
|
@ -201,28 +224,4 @@ final class Hand(
|
||||||
} yield progress2.events
|
} yield progress2.events
|
||||||
} toValid "cannot add moretime"
|
} toValid "cannot add moretime"
|
||||||
)
|
)
|
||||||
|
|
||||||
private def attempt[A](
|
|
||||||
fullId: String,
|
|
||||||
action: Pov ⇒ Valid[IO[A]]): IO[Valid[A]] =
|
|
||||||
fromPov(fullId) { pov ⇒ action(pov).sequence }
|
|
||||||
|
|
||||||
private def attemptRef[A](
|
|
||||||
ref: PovRef,
|
|
||||||
action: Pov ⇒ Valid[IO[A]]): IO[Valid[A]] =
|
|
||||||
fromPov(ref) { pov ⇒ action(pov).sequence }
|
|
||||||
|
|
||||||
private def fromPov[A](ref: PovRef)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
|
||||||
fromPov(gameRepo pov ref)(op)
|
|
||||||
|
|
||||||
private def fromPov[A](fullId: String)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
|
||||||
fromPov(gameRepo pov fullId)(op)
|
|
||||||
|
|
||||||
private def fromPov[A](povIO: IO[Option[Pov]])(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] =
|
|
||||||
povIO flatMap { povOption ⇒
|
|
||||||
povOption.fold(
|
|
||||||
pov ⇒ op(pov),
|
|
||||||
io { "No such game".failNel }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,15 @@ final class Messenger(roomRepo: RoomRepo) {
|
||||||
else io(Nil)
|
else io(Nil)
|
||||||
|
|
||||||
def systemMessages(game: DbGame, encodedMessages: String): IO[List[Event]] =
|
def systemMessages(game: DbGame, encodedMessages: String): IO[List[Event]] =
|
||||||
if (game.invited.isHuman) {
|
systemMessages(game, (encodedMessages split '$').toList)
|
||||||
val messages = (encodedMessages split '$').toList
|
|
||||||
|
def systemMessages(game: DbGame, messages: List[String]): IO[List[Event]] =
|
||||||
|
game.invited.isHuman.fold(
|
||||||
roomRepo.addSystemMessages(game.id, messages) map { _ ⇒
|
roomRepo.addSystemMessages(game.id, messages) map { _ ⇒
|
||||||
messages map { Message("system", _) }
|
messages map { Message("system", _) }
|
||||||
}
|
},
|
||||||
}
|
io(Nil)
|
||||||
else io(Nil)
|
)
|
||||||
|
|
||||||
def systemMessage(game: DbGame, message: String): IO[List[Event]] =
|
def systemMessage(game: DbGame, message: String): IO[List[Event]] =
|
||||||
if (game.invited.isHuman)
|
if (game.invited.isHuman)
|
||||||
|
|
|
@ -26,4 +26,7 @@ object Progress {
|
||||||
|
|
||||||
def apply(game: DbGame, events: List[Event]): Progress =
|
def apply(game: DbGame, events: List[Event]): Progress =
|
||||||
new Progress(game, game, events)
|
new Progress(game, game, events)
|
||||||
|
|
||||||
|
def apply(game: DbGame, events: Event): Progress =
|
||||||
|
new Progress(game, game, events :: Nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package lila
|
package lila
|
||||||
package round
|
package round
|
||||||
|
|
||||||
|
import scalaz.effects._
|
||||||
import com.mongodb.casbah.MongoCollection
|
import com.mongodb.casbah.MongoCollection
|
||||||
|
import akka.actor.Props
|
||||||
import akka.actor._
|
|
||||||
|
|
||||||
import play.api.libs.concurrent._
|
import play.api.libs.concurrent._
|
||||||
import play.api.Application
|
import play.api.Application
|
||||||
|
|
||||||
import game.{ GameRepo }
|
import game.{ GameRepo, DbGame }
|
||||||
import user.{ UserRepo, EloUpdater }
|
import user.{ UserRepo, EloUpdater }
|
||||||
import ai.Ai
|
import ai.Ai
|
||||||
import core.Settings
|
import core.Settings
|
||||||
|
@ -20,7 +19,7 @@ final class RoundEnv(
|
||||||
gameRepo: GameRepo,
|
gameRepo: GameRepo,
|
||||||
userRepo: UserRepo,
|
userRepo: UserRepo,
|
||||||
eloUpdater: EloUpdater,
|
eloUpdater: EloUpdater,
|
||||||
ai: () => Ai) {
|
ai: () ⇒ Ai) {
|
||||||
|
|
||||||
implicit val ctx = app
|
implicit val ctx = app
|
||||||
import settings._
|
import settings._
|
||||||
|
@ -63,7 +62,7 @@ final class RoundEnv(
|
||||||
lazy val finisherLock = new FinisherLock(timeout = FinisherLockTimeout)
|
lazy val finisherLock = new FinisherLock(timeout = FinisherLockTimeout)
|
||||||
|
|
||||||
lazy val takeback = new Takeback(
|
lazy val takeback = new Takeback(
|
||||||
gameRepo = gameRepo,
|
gameRepo = gameRepo,
|
||||||
messenger = messenger)
|
messenger = messenger)
|
||||||
|
|
||||||
lazy val messenger = new Messenger(roomRepo = roomRepo)
|
lazy val messenger = new Messenger(roomRepo = roomRepo)
|
||||||
|
|
|
@ -5,8 +5,6 @@ import chess.{ Game, Board, Variant, Color ⇒ ChessColor }
|
||||||
import elo.EloRange
|
import elo.EloRange
|
||||||
import game.{ DbGame, DbPlayer }
|
import game.{ DbGame, DbPlayer }
|
||||||
|
|
||||||
import org.joda.time.DateTime
|
|
||||||
|
|
||||||
case class AiConfig(
|
case class AiConfig(
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
level: Int,
|
level: Int,
|
||||||
|
@ -25,8 +23,7 @@ case class AiConfig(
|
||||||
aiLevel = creatorColor.white option level),
|
aiLevel = creatorColor.white option level),
|
||||||
creatorColor = creatorColor,
|
creatorColor = creatorColor,
|
||||||
isRated = false,
|
isRated = false,
|
||||||
variant = variant,
|
variant = variant).start
|
||||||
createdAt = DateTime.now).start
|
|
||||||
|
|
||||||
def encode = RawAiConfig(
|
def encode = RawAiConfig(
|
||||||
v = variant.id,
|
v = variant.id,
|
||||||
|
|
93
app/setup/Rematcher.scala
Normal file
93
app/setup/Rematcher.scala
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package lila
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import chess.{ Game, Board, Clock, Color ⇒ ChessColor }
|
||||||
|
import ChessColor.{ White, Black }
|
||||||
|
import chess.format.Forsyth
|
||||||
|
import game.{ GameRepo, DbGame, DbPlayer, Pov, Handler, Namer }
|
||||||
|
import round.{ Event, Progress, Messenger }
|
||||||
|
import user.UserRepo
|
||||||
|
import controllers.routes
|
||||||
|
|
||||||
|
import scalaz.effects._
|
||||||
|
|
||||||
|
final class Rematcher(
|
||||||
|
gameRepo: GameRepo,
|
||||||
|
userRepo: UserRepo,
|
||||||
|
messenger: Messenger,
|
||||||
|
timelinePush: DbGame ⇒ IO[Unit]) extends Handler(gameRepo) {
|
||||||
|
|
||||||
|
def offerOrAccept(fullId: String): IO[Valid[(String, List[Event])]] =
|
||||||
|
attempt(fullId, {
|
||||||
|
case pov @ Pov(game, color) if game playerCanRematch color ⇒
|
||||||
|
if (game.opponent(color).isOfferingRematch) success {
|
||||||
|
game.nextId.fold(
|
||||||
|
nextId ⇒ io(nextId -> Nil),
|
||||||
|
for {
|
||||||
|
nextGame ← returnGame(pov) map (_.start)
|
||||||
|
_ ← gameRepo insert nextGame
|
||||||
|
nextId = nextGame.id
|
||||||
|
_ ← game.variant.standard.fold(io(), gameRepo saveInitialFen game)
|
||||||
|
_ ← timelinePush(game)
|
||||||
|
// messenges are not sent to the next game socket
|
||||||
|
// as nobody is there to see them yet
|
||||||
|
_ ← messenger.systemMessages(nextGame, List(
|
||||||
|
Some(nextGame.creatorColor + " creates the game"),
|
||||||
|
Some(nextGame.invitedColor + " joins the game"),
|
||||||
|
nextGame.clock map Namer.clock,
|
||||||
|
nextGame.isRated option "This game is rated").flatten)
|
||||||
|
} yield nextId -> List(
|
||||||
|
Event.RedirectOwner(White, playerUrl(nextGame, White)),
|
||||||
|
Event.RedirectOwner(Black, playerUrl(nextGame, Black)),
|
||||||
|
// tell spectators to reload the table
|
||||||
|
Event.ReloadTable(White),
|
||||||
|
Event.ReloadTable(Black))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else success {
|
||||||
|
val progress = Progress(game, Event.ReloadTable(!color)) map { g ⇒
|
||||||
|
g.updatePlayer(color, _.offerRematch)
|
||||||
|
}
|
||||||
|
gameRepo save progress map { _ ⇒ fullId -> progress.events }
|
||||||
|
}
|
||||||
|
case _ ⇒ !!("invalid rematch offer " + fullId)
|
||||||
|
})
|
||||||
|
|
||||||
|
private def returnGame(pov: Pov): IO[DbGame] = for {
|
||||||
|
board ← pov.game.variant.standard.fold(
|
||||||
|
io(pov.game.variant.pieces),
|
||||||
|
gameRepo initialFen pov.game.id map { fenOption ⇒
|
||||||
|
(fenOption flatMap Forsyth.<< map { situation ⇒
|
||||||
|
situation.board.pieces
|
||||||
|
}) | pov.game.variant.pieces
|
||||||
|
})
|
||||||
|
whitePlayer ← returnPlayer(pov.game, White)
|
||||||
|
blackPlayer ← returnPlayer(pov.game, Black)
|
||||||
|
} yield DbGame(
|
||||||
|
game = Game(
|
||||||
|
board = Board(board),
|
||||||
|
clock = pov.game.clock map (_.reset)),
|
||||||
|
whitePlayer = whitePlayer,
|
||||||
|
blackPlayer = blackPlayer,
|
||||||
|
ai = None,
|
||||||
|
creatorColor = !pov.color,
|
||||||
|
isRated = pov.game.isRated,
|
||||||
|
variant = pov.game.variant)
|
||||||
|
|
||||||
|
private def returnPlayer(game: DbGame, color: ChessColor): IO[DbPlayer] =
|
||||||
|
DbPlayer(color = color, aiLevel = None) |> { player ⇒
|
||||||
|
game.player(color).userId.fold(
|
||||||
|
userId ⇒ userRepo user userId map { userOption ⇒
|
||||||
|
userOption.fold(
|
||||||
|
user ⇒ player.withUser(user)(userRepo.dbRef),
|
||||||
|
player)
|
||||||
|
},
|
||||||
|
io(player))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def clockName(clock: Option[Clock]): String =
|
||||||
|
clock.fold(Namer.clock, "Unlimited")
|
||||||
|
|
||||||
|
private def playerUrl(game: DbGame, color: ChessColor): String =
|
||||||
|
routes.Round.player(game fullIdOf color).url
|
||||||
|
}
|
|
@ -3,8 +3,9 @@ package setup
|
||||||
|
|
||||||
import core.Settings
|
import core.Settings
|
||||||
import game.{ DbGame, GameRepo }
|
import game.{ DbGame, GameRepo }
|
||||||
|
import round.Messenger
|
||||||
import ai.Ai
|
import ai.Ai
|
||||||
import user.User
|
import user.{ User, UserRepo }
|
||||||
|
|
||||||
import com.mongodb.casbah.MongoCollection
|
import com.mongodb.casbah.MongoCollection
|
||||||
import scalaz.effects._
|
import scalaz.effects._
|
||||||
|
@ -14,7 +15,9 @@ final class SetupEnv(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
mongodb: String ⇒ MongoCollection,
|
mongodb: String ⇒ MongoCollection,
|
||||||
gameRepo: GameRepo,
|
gameRepo: GameRepo,
|
||||||
|
userRepo: UserRepo,
|
||||||
timelinePush: DbGame ⇒ IO[Unit],
|
timelinePush: DbGame ⇒ IO[Unit],
|
||||||
|
roundMessenger: Messenger,
|
||||||
ai: () ⇒ Ai,
|
ai: () ⇒ Ai,
|
||||||
dbRef: User ⇒ DBRef) {
|
dbRef: User ⇒ DBRef) {
|
||||||
|
|
||||||
|
@ -31,4 +34,10 @@ final class SetupEnv(
|
||||||
timelinePush = timelinePush,
|
timelinePush = timelinePush,
|
||||||
ai = ai,
|
ai = ai,
|
||||||
dbRef = dbRef)
|
dbRef = dbRef)
|
||||||
|
|
||||||
|
lazy val rematcher = new Rematcher(
|
||||||
|
gameRepo = gameRepo,
|
||||||
|
userRepo = userRepo,
|
||||||
|
messenger = roundMessenger,
|
||||||
|
timelinePush = timelinePush)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package lila
|
||||||
package timeline
|
package timeline
|
||||||
|
|
||||||
import chess.Color
|
import chess.Color
|
||||||
import game.{ DbGame, PlayerNamer }
|
import game.{ DbGame, Namer }
|
||||||
|
|
||||||
import scalaz.effects._
|
import scalaz.effects._
|
||||||
|
|
||||||
|
@ -32,5 +32,5 @@ final class Push(
|
||||||
(game player color).userId
|
(game player color).userId
|
||||||
|
|
||||||
private def usernameElo(game: DbGame, color: Color): String =
|
private def usernameElo(game: DbGame, color: Color): String =
|
||||||
PlayerNamer(game player color)(getUsername)
|
Namer.player(game player color)(getUsername)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ final class TimelineEnv(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
mongodb: String ⇒ MongoCollection,
|
mongodb: String ⇒ MongoCollection,
|
||||||
lobbyNotify: Entry ⇒ IO[Unit],
|
lobbyNotify: Entry ⇒ IO[Unit],
|
||||||
getUsername: String => String) {
|
getUsername: String ⇒ String) {
|
||||||
|
|
||||||
import settings._
|
import settings._
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
@if(opponent.isOfferingRematch) {
|
@if(opponent.isOfferingRematch) {
|
||||||
<div class="lichess_play_again_join rematch_alert">
|
<div class="lichess_play_again_join rematch_alert">
|
||||||
@trans.yourOpponentWantsToPlayANewGameWithYou().
|
@trans.yourOpponentWantsToPlayANewGameWithYou().
|
||||||
<a class="lichess_play_again lichess_rematch" title="@trans.playWithTheSameOpponentAgain()" href="@routes.Round.rematchAccept(fullId)">@trans.joinTheGame()</a><br />
|
<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_rematch_decline" href="@routes.Round.rematchDecline(fullId)">@trans.declineInvitation()</a>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
<a class="lichess_rematch_cancel" href="@routes.Round.rematchCancel(fullId)">@trans.cancelRematchOffer()</a>
|
<a class="lichess_rematch_cancel" href="@routes.Round.rematchCancel(fullId)">@trans.cancelRematchOffer()</a>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<a class="lichess_rematch button" title="@trans.playWithTheSameOpponentAgain()" href="@routes.Round.rematchOffer(fullId)">@trans.rematch()</a>
|
<a class="lichess_rematch button" title="@trans.playWithTheSameOpponentAgain()" href="@routes.Round.rematch(fullId)">@trans.rematch()</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,8 +18,7 @@ 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/offer controllers.Round.drawOffer(fullId: String)
|
||||||
GET /$fullId<[\w\-]{12}>/draw/cancel controllers.Round.drawCancel(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}>/draw/decline controllers.Round.drawDecline(fullId: String)
|
||||||
GET /$fullId<[\w\-]{12}>/rematch/offer controllers.Round.rematchOffer(fullId: String)
|
GET /$fullId<[\w\-]{12}>/rematch controllers.Round.rematch(fullId: String)
|
||||||
GET /$fullId<[\w\-]{12}>/rematch/accept controllers.Round.rematchAccept(fullId: String)
|
|
||||||
GET /$fullId<[\w\-]{12}>/rematch/cancel controllers.Round.rematchCancel(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}>/rematch/decline controllers.Round.rematchDecline(fullId: String)
|
||||||
GET /$fullId<[\w\-]{12}>/takeback/accept controllers.Round.takebackAccept(fullId: String)
|
GET /$fullId<[\w\-]{12}>/takeback/accept controllers.Round.takebackAccept(fullId: String)
|
||||||
|
|
|
@ -12,7 +12,7 @@ trait Resolvers {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Dependencies {
|
trait Dependencies {
|
||||||
val scalachess = "com.github.ornicar" %% "scalachess" % "1.1"
|
val scalachess = "com.github.ornicar" %% "scalachess" % "1.2"
|
||||||
val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.4"
|
val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.4"
|
||||||
val specs2 = "org.specs2" %% "specs2" % "1.8.2"
|
val specs2 = "org.specs2" %% "specs2" % "1.8.2"
|
||||||
val casbah = "com.mongodb.casbah" %% "casbah" % "2.1.5-1"
|
val casbah = "com.mongodb.casbah" %% "casbah" % "2.1.5-1"
|
||||||
|
|
Loading…
Reference in a new issue