Progress on round http

pull/1/merge
Thibault Duplessis 2012-05-17 12:30:41 +02:00
parent 5c1005d1c2
commit eb781921ab
20 changed files with 228 additions and 46 deletions

View File

@ -8,8 +8,27 @@ import play.api.mvc._
object Round extends LilaController {
val gameRepo = env.game.gameRepo
val socket = env.round.socket
def player(id: String) = Open { implicit ctx
IOption(gameRepo pov id) { html.round.player(_) }
IOption(gameRepo pov id) { pov
html.round.player(pov, socket blockingVersion pov.gameId)
}
}
def abort(fullId: String) = TODO
def resign(fullId: String) = TODO
def resignForce(fullId: String) = TODO
def drawClaim(fullId: String) = TODO
def drawAccept(fullId: String) = TODO
def drawOffer(fullId: String) = TODO
def drawCancel(fullId: String) = TODO
def drawDecline(fullId: String) = TODO
def takebackAccept(fullId: String) = TODO
def takebackOffer(fullId: String) = TODO
def takebackCancel(fullId: String) = TODO
def takebackDecline(fullId: String) = TODO
def table(gameId: String, color: String, fullId: String) = TODO
def players(gameId: String) = TODO
}

View File

@ -19,7 +19,7 @@ object Setup extends LilaController {
_ Redirect(routes.Lobby.home),
config IORedirect(
processor ai config map { pov
routes.Round.player(pov.playerFullId)
routes.Round.player(pov.fullId)
}
)
)

View File

@ -13,6 +13,7 @@ final class Settings(config: Config) {
val GameUidTimeout = millis("game.uid.timeout")
val GameHubTimeout = millis("game.hub.timeout")
val GamePlayerTimeout = millis("game.player.timeout")
val GameAnimationDelay = millis("game.animation.delay")
val LobbyEntryMax = getInt("lobby.entry.max")
val LobbyMessageMax = getInt("lobby.message.max")

View File

@ -49,7 +49,11 @@ case class DbGame(
def opponent(p: DbPlayer): DbPlayer = player(!(p.color))
def player: DbPlayer = player(if (0 == turns % 2) White else Black)
def player: DbPlayer = player(turnColor)
def turnColor = Color(0 == turns % 2)
def turnOf(p: DbPlayer) = p == player
def fullIdOf(player: DbPlayer): Option[String] =
(players contains player) option id + player.id
@ -87,7 +91,7 @@ case class DbGame(
)
}
def toChessHistory = ChessHistory(
lazy val toChessHistory = ChessHistory(
lastMove = lastMove,
castles = castles,
positionHashes = positionHashes)
@ -189,13 +193,20 @@ case class DbGame(
blackPlayer = f(blackPlayer)
)
def start = started.fold(this, copy(
status = Status.Started,
isRated = isRated && (players forall (_.hasUser))
))
def recordMoveTimes = !hasAi
def hasMoveTimes = players forall (_.hasMoveTimes)
def started = status >= Status.Started
def playable = status < Status.Aborted
def playableBy(p: DbPlayer) = playable && p == player
def playableBy(p: DbPlayer) = playable && turnOf(p)
def aiLevel: Option[Int] = players find (_.isAi) flatMap (_.aiLevel)
@ -207,8 +218,7 @@ case class DbGame(
)
def playerCanOfferDraw(color: Color) =
status >= Status.Started &&
status < Status.Aborted &&
started && playable &&
turns >= 2 &&
!player(color).isOfferingDraw &&
!(player(!color).isAi) &&
@ -217,6 +227,11 @@ case class DbGame(
def playerHasOfferedDraw(color: Color) =
player(color).lastDrawOffer some (_ >= turns - 1) none false
def playerCanProposeTakeback(color: Color) =
started && playable &&
turns >= 2 &&
!player(color).isProposingTakeback
def abortable = status == Status.Started && turns < 2
def resignable = playable && !abortable
@ -248,6 +263,11 @@ case class DbGame(
def withClock(c: Clock) = Progress(this, copy(clock = Some(c)))
def estimateTotalTime = clock.fold(
c c.limit + 30 * c.increment,
1200 // default to 20 minutes
)
def creator = player(creatorColor)
def invited = player(!creatorColor)
@ -266,7 +286,7 @@ object DbGame {
def takeGameId(fullId: String) = fullId take gameIdSize
def apply(
game: Game,
game: Game,
whitePlayer: DbPlayer,
blackPlayer: DbPlayer,
ai: Option[(Color, Int)],
@ -289,5 +309,5 @@ object DbGame {
isRated = isRated,
variant = variant,
lastMoveTime = None,
createdAt = createdAt.some)
createdAt = createdAt.some)
}

View File

@ -42,6 +42,8 @@ case class DbPlayer(
def userId: Option[String] = user map (_.getId.toString)
def hasUser = user.isDefined
def wins = isWinner getOrElse false
def hasMoveTimes = moveTimes.size > 10

View File

@ -9,7 +9,7 @@ case class Pov(game: DbGame, color: Color) {
def playerId = player.id
def playerFullId = game fullIdOf color
def fullId = game fullIdOf color
def gameId = game.id

View File

@ -9,7 +9,6 @@ import akka.util.duration._
import akka.util.Timeout
import akka.pattern.{ ask, pipe }
import akka.dispatch.{ Future, Promise }
import akka.event.Logging
import play.api.libs.json._
import play.api.libs.concurrent._
import play.api.Play.current
@ -21,7 +20,6 @@ final class HubMaster(
playerTimeout: Int) extends Actor {
implicit val timeout = Timeout(1 second)
val log = Logging(context.system, this)
implicit val executor = Akka.system.dispatcher
var hubs = Map.empty[String, ActorRef]

View File

@ -0,0 +1,51 @@
package lila
package round
import http.Context
import game.Pov
import templating.ConfigHelper
import com.codahale.jerkson.Json
import scala.math.{ min, max, round }
trait RoundHelper { self: ConfigHelper
def roundJsData(pov: Pov, version: Int) = Json generate {
import pov._
Map(
"game" -> Map(
"id" -> gameId,
"started" -> game.started,
"finished" -> game.finished,
"clock" -> game.hasClock,
"player" -> game.turnColor.name,
"turns" -> game.turns,
"lastMove" -> game.lastMove
),
"player" -> Map(
"id" -> player.id,
"color" -> player.color.name,
"version" -> version,
"spectator" -> false
),
"opponent" -> Map(
"color" -> opponent.color.name,
"ai" -> opponent.isAi
),
"possible_moves" -> possibleMoves(pov),
"animation_delay" -> animationDelay(pov)
)
}
private def possibleMoves(pov: Pov) = (pov.game playableBy pov.player) option {
pov.game.toChess.situation.destinations map {
case (from, dests) from.key -> (dests.mkString)
} toMap
}
private def animationDelay(pov: Pov) = round {
gameAnimationDelay * max(0, min(1.2, ((pov.game.estimateTotalTime - 60) / 60) * 0.2))
}
}

View File

@ -5,6 +5,7 @@ import akka.actor._
import akka.pattern.ask
import akka.util.duration._
import akka.util.Timeout
import akka.dispatch.Await
import play.api.libs.json._
import play.api.libs.iteratee._
@ -24,7 +25,12 @@ final class Socket(
val hubMaster: ActorRef,
messenger: Messenger) {
implicit val timeout = Timeout(1 second)
private val timeoutDuration = 1 second
implicit private val timeout = Timeout(timeoutDuration)
def blockingVersion(gameId: String): Int = Await.result(
hubMaster ? GetGameVersion(gameId) mapTo manifest[Int],
timeoutDuration)
def send(progress: Progress): IO[Unit] =
send(progress.game.id, progress.events)
@ -33,7 +39,7 @@ final class Socket(
hubMaster ! GameEvents(gameId, events)
}
def controller(
private def controller(
hub: ActorRef,
uid: String,
member: Member,

View File

@ -23,7 +23,7 @@ case class AiConfig(variant: Variant, level: Int, color: Color) extends Config {
creatorColor = creatorColor,
isRated = false,
variant = variant,
createdAt = DateTime.now)
createdAt = DateTime.now).start
}
object AiConfig extends BaseConfig {

View File

@ -8,4 +8,6 @@ trait ConfigHelper {
protected def env: CoreEnv
def moretimeSeconds = env.settings.MoretimeSeconds
def gameAnimationDelay = env.settings.GameAnimationDelay
}

View File

@ -2,11 +2,14 @@ package lila
package templating
import core.Global.{ env coreEnv } // OMG
import round.RoundHelper
import http.{ HttpEnvironment, Setting }
object Environment
extends HttpEnvironment
with scalaz.Identitys
with scalaz.Options
with scalaz.Booleans
with StringHelper
with AssetHelper
with I18nHelper
@ -14,7 +17,8 @@ object Environment
with RequestHelper
with SettingHelper
with UserHelper
with ConfigHelper {
with ConfigHelper
with RoundHelper {
protected def env = coreEnv
}

View File

@ -17,13 +17,13 @@ object Board {
s.pos.key,
s.top,
s.left) ++
"""<div class="lcsi">""" ++ {
"""<div class="lcsi"></div>""" ++ {
board(s.pos) map { piece
"""<div class="lichess_piece %s %s"></div>""".format(
piece.role.name, piece.color.name)
} getOrElse ""
} ++
"</div></div>"
"</div>"
} mkString
}

View File

@ -1,4 +1,4 @@
@(pov: Pov)(implicit ctx: Context)
@(pov: Pov, version: Int)(implicit ctx: Context)
@import pov._
@ -18,7 +18,9 @@
}
@round.layout(title = title, goodies = goodies) {
<div class="lichess_game clearfix lichess_player_@color not_spectator">
<div class="lichess_game clearfix lichess_player_@color not_spectator"
data-table-url="@routes.Round.table(gameId, color.name, fullId)"
data-players-url="@routes.Round.players(gameId)">
<div class="lichess_board_wrap">
@widget.connection()
<div class="lichess_board grey">@Html(ui.Board.render(pov))</div>
@ -55,4 +57,5 @@
@round.cemetery(pov, "bottom")
</div>
</div>
<script type="text/javascript">var lichess_data = @Html(roundJsData(pov, version))</script>
}

View File

@ -1,3 +1,69 @@
@(pov: Pov)(implicit ctx: Context)
table
@import pov._
<div class="lichess_current_player">
<div class="lichess_player white @game.turnColor.white.fold("", " none")">
<div class="lichess_piece king white"></div>
<p>@player.color.white.fold(trans.yourTurn(), trans.waiting())</p>
</div>
<div class="lichess_player black @game.turnColor.black.fold("", " none")">
<div class="lichess_piece king black"></div>
<p>@player.color.black.fold(trans.yourTurn(), trans.waiting())</p>
</div>
</div>
<div class="lichess_control clearfix">
@if(game.abortable) {
<a href="@routes.Round.abort(fullId)" class="lichess_abort">@trans.abortGame()</a>
} else {
<a href="@routes.Round.resign(fullId)" class="lichess_resign" title="@trans.giveUp()">@trans.resign()</a>
@if(game.playerCanOfferDraw(color)) {
<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>
}
}
</div>
@if(game.resignable && !game.hasAi) {
<div class="force_resign_zone">
@trans.theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim()<br />
<a class="force_resign" href="@routes.Round.resignForce(fullId)">@trans.forceResignation()</a>
</div>
}
@if(game.turnOf(player) && game.toChessHistory.threefoldRepetition) {
<div class="lichess_claim_draw_zone">
@trans.threefoldRepetition().&nbsp;
<a class="lichess_claim_draw" href="@routes.Round.drawClaim(fullId)">@trans.claimADraw()</a>
</div>
} else {
@if(player.isOfferingDraw) {
<div class="offered_draw">
@trans.drawOfferSent().&nbsp;
<a href="@routes.Round.drawCancel(fullId)">@trans.cancel()</a>
</div>
} else {
@if(opponent.isOfferingDraw) {
<div class="offered_draw">
@trans.yourOpponentOffersADraw().<br />
<a href="@routes.Round.drawAccept(fullId)">@trans.accept()</a>&nbsp;
<a href="@routes.Round.drawDecline(fullId)">@trans.decline()</a>
</div>
} else {
@if(player.isProposingTakeback) {
<div class="proposed_takeback">
@trans.takebackPropositionSent().&nbsp;
<a href="@routes.Round.takebackCancel(fullId)">@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>
</div>
}
}
}
}
}

View File

@ -1,11 +1,15 @@
@(player: DbPlayer)(implicit ctx: Context)
@if(player.isAi) {
@ai(level: Int) = {
<div class="username connected">
@trans.aiNameLevelAiLevel("Crafty A.I.", player.aiLevel)
@trans.aiNameLevelAiLevel("Crafty A.I.", level)
</div>
} else {
}
@human = {
<div class="username @player.color.name">
@playerLink(player, "blank_if_play")
</div>
}
@player.aiLevel.fold(ai, human)

View File

@ -28,6 +28,7 @@ game {
uid.timeout = 10 seconds
hub.timeout = 2 minutes
player.timeout = 1 minute
animation.delay = 200 ms
}
site {
uid.timeout = 10 seconds

View File

@ -2,12 +2,27 @@
GET /games controllers.Game.list
# Round
GET /$id<[\w\-]{12}> controllers.Round.player(id: String)
GET /$fullId<[\w\-]{12}> controllers.Round.player(fullId: String)
GET /abort/$fullId<[\w\-]{12}> controllers.Round.abort(fullId: String)
GET /resign/$fullId<[\w\-]{12}> controllers.Round.resign(fullId: String)
GET /resign-force/$fullId<[\w\-]{12}> controllers.Round.resignForce(fullId: String)
GET /draw-claim/$fullId<[\w\-]{12}> controllers.Round.drawClaim(fullId: String)
GET /draw-accept/$fullId<[\w\-]{12}> controllers.Round.drawAccept(fullId: String)
GET /draw-offer/$fullId<[\w\-]{12}> controllers.Round.drawOffer(fullId: String)
GET /draw-cancel/$fullId<[\w\-]{12}> controllers.Round.drawCancel(fullId: String)
GET /draw-decline/$fullId<[\w\-]{12}> controllers.Round.drawDecline(fullId: String)
GET /takeback-accept/$fullId<[\w\-]{12}> controllers.Round.takebackAccept(fullId: String)
GET /takeback-offer/$fullId<[\w\-]{12}> controllers.Round.takebackOffer(fullId: String)
GET /takeback-cancel/$fullId<[\w\-]{12}> controllers.Round.takebackCancel(fullId: String)
GET /takeback-decline/$fullId<[\w\-]{12}> controllers.Round.takebackDecline(fullId: String)
GET /table/$gameId<[\w\-]{8}>/$color<[white|black]> controllers.Round.table(gameId: String, color: String, fullId: String = "")
GET /table/$gameId<[\w\-]{8}>/$color<[white|black]>/$fullId<[\w\-]{12}> controllers.Round.table(gameId: String, color: String, fullId: String)
GET /players/$gameId<[\w\-]{8}> controllers.Round.players(gameId: String)
# Analyse
GET /analyse/$id<[\w\-]{8}> controllers.Analyse.replay(id: String, color: String = "white")
GET /analyse/$id<[\w\-]{8}>/$color<[white|black]> controllers.Analyse.replay(id: String, color: String)
GET /$id<[\w\-]{8}>/stats controllers.Analyse.stats(id: String)
GET /analyse/$gameId<[\w\-]{8}> controllers.Analyse.replay(gameId: String, color: String = "white")
GET /analyse/$gameId<[\w\-]{8}>/$color<[white|black]> controllers.Analyse.replay(gameId: String, color: String)
GET /$gameId<[\w\-]{8}>/stats controllers.Analyse.stats(gameId: String)
# Setting
POST /setting/color controllers.Setting.color
@ -39,25 +54,12 @@ GET /wiki controllers.Wiki.home
# App Public API
GET /socket controllers.App.socket
GET /socket/:gameId/:color controllers.App.gameSocket(gameId: String, color: String)
GET /abort/:fullId controllers.App.abort(fullId: String)
GET /resign/:fullId controllers.App.resign(fullId: String)
GET /resign-force/:fullId controllers.App.resignForce(fullId: String)
GET /draw-claim/:fullId controllers.App.drawClaim(fullId: String)
GET /draw-accept/:fullId controllers.App.drawAccept(fullId: String)
GET /draw-offer/:fullId controllers.App.drawOffer(fullId: String)
GET /draw-cancel/:fullId controllers.App.drawCancel(fullId: String)
GET /draw-decline/:fullId controllers.App.drawDecline(fullId: String)
GET /takeback-accept/:fullId controllers.App.takebackAccept(fullId: String)
GET /takeback-offer/:fullId controllers.App.takebackOffer(fullId: String)
GET /takeback-cancel/:fullId controllers.App.takebackCancel(fullId: String)
GET /takeback-decline/:fullId controllers.App.takebackDecline(fullId: String)
GET /ai controllers.Ai.run
# App Private API
GET /api/show/:fullId controllers.AppApi.show(fullId: String)
POST /api/start/:gameId controllers.AppApi.start(gameId: String)
POST /api/join/:fullId controllers.AppApi.join(fullId: String)
POST /api/join/$fullId<[\w\-]{12}> controllers.AppApi.join(fullId: String)
POST /api/reload-table/:gameId controllers.AppApi.reloadTable(gameId: String)
POST /api/adjust/:username controllers.AppApi.adjust(username: String)
GET /api/activity/:gameId/:color controllers.AppApi.activity(gameId: String, color: String)

View File

@ -11,6 +11,8 @@ $.widget("lichess.game", {
self.initialTitle = document.title;
self.hasMovedOnce = false;
self.premove = null;
self.options.tableUrl = self.element.data('table-url');
self.options.playersUrl = self.element.data('players-url');
if (self.options.game.started) {
self.indicateTurn();
@ -514,7 +516,7 @@ $.widget("lichess.game", {
},
reloadTable: function(callback) {
var self = this;
self.get(self.options.url.table, {
self.get(self.options.tableUrl, {
success: function(html) {
$('body > div.tipsy').remove();
self.$tableInner.html(html);
@ -526,7 +528,7 @@ $.widget("lichess.game", {
},
reloadPlayers: function(callback) {
var self = this;
$.getJSON(self.options.url.players, function(data) {
$.getJSON(self.options.playersUrl, function(data) {
$(['white', 'black']).each(function() {
if (data[this]) self.$table.find('div.username.' + this).html(data[this]);
});

View File

@ -56,10 +56,11 @@ $.websocket.prototype = {
})
.bind('message', function(e){
var m = JSON.parse(e.originalEvent.data);
self._debug(m);
if (m.t == "n") {
self.keepAlive();
}
} else {
self._debug(m);
}
if (m.t == "batch") {
$(m.d || []).each(function() { self._handle(this); });
} else {