more on mithril play

pull/132/head
Thibault Duplessis 2014-10-03 22:13:59 +02:00
parent 51b740de51
commit cf0fbf5b00
12 changed files with 262 additions and 155 deletions

View File

@ -58,14 +58,14 @@ object Round extends LilaController with TheftPrevention {
(pov.game.tournamentId ?? TournamentRepo.byId) zip
Env.game.crosstableApi(pov.game) flatMap {
case (tour, crosstable) =>
env.jsonView.playerJson(pov, ctx.pref, Env.api.version, ctx.me) map { data =>
Env.api.roundApi.player(pov, Env.api.version) map { data =>
Ok(html.round.player(pov, data, tour = tour, cross = crosstable))
}
}
},
Redirect(routes.Setup.await(fullId)).fuccess
),
api = apiVersion => env.jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me) map { Ok(_) }
api = apiVersion => Env.api.roundApi.player(pov, apiVersion) map { Ok(_) }
)
}
}

View File

@ -11,7 +11,10 @@
@jsAt(s"compiled/lichess.round.js")
@helper.javascriptRouter("roundRoutes")(
routes.javascript.Auth.signup,
routes.javascript.User.show
routes.javascript.User.show,
routes.javascript.Tournament.show,
routes.javascript.Pool.show,
routes.javascript.Pool.leave
)(ctx.req)
@embedJs {
lichess = lichess || {};
@ -53,7 +56,15 @@ trans.timeOut,
trans.premoveEnabledClickAnywhereToCancel,
trans.playingRightNow,
trans.whiteIsVictorious,
trans.blackIsVictorious
trans.blackIsVictorious,
trans.backToTournament,
trans.joinTheGame,
trans.playWithTheSameOpponentAgain,
trans.declineInvitation,
trans.rematch,
trans.rematchOfferSent,
trans.waitingForOpponent,
trans.cancelRematchOffer
)))
};
}

View File

@ -10,6 +10,7 @@ final class Env(
renderer: akka.actor.ActorSelection,
router: akka.actor.ActorSelection,
bus: lila.common.Bus,
roundJsonView: lila.round.JsonView,
pgnDump: lila.game.PgnDump,
userEnv: lila.user.Env,
analyseEnv: lila.analyse.Env,
@ -56,6 +57,9 @@ final class Env(
apiToken = apiToken,
pgnDump = pgnDump)
val roundApi = new RoundApi(
jsonView = roundJsonView)
val puzzleApi = new PuzzleApi(
env = puzzleEnv,
makeUrl = apiUrl)
@ -78,6 +82,7 @@ object Env {
userEnv = lila.user.Env.current,
analyseEnv = lila.analyse.Env.current,
puzzleEnv = lila.puzzle.Env.current,
roundJsonView = lila.round.Env.current.jsonView,
pgnDump = lila.game.Env.current.pgnDump,
userIdsSharingIp = lila.security.Env.current.api.userIdsSharingIp,
bus = lila.common.PlayApp.system.lilaBus,

View File

@ -106,7 +106,7 @@ private[api] final class GameApi(
"perf" -> PerfPicker.key(g),
"timestamp" -> g.createdAt.getDate,
"turns" -> g.turns,
"status" -> g.status.name.toLowerCase,
"status" -> g.status.name,
"clock" -> g.clock.map { clock =>
Json.obj(
"initial" -> clock.limit,

View File

@ -0,0 +1,26 @@
package lila.api
import play.api.libs.json._
import lila.game.Pov
import lila.pref.Pref
import lila.round.JsonView
import lila.tournament.TournamentRepo
import lila.user.User
private[api] final class RoundApi(jsonView: JsonView) {
def player(pov: Pov, apiVersion: Int)(implicit ctx: Context): Fu[JsObject] =
jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me) zip
(pov.game.tournamentId ?? TournamentRepo.byId) map {
case (json, tourOption) => tourOption.fold(json) { tour =>
json + (
"tournament" -> Json.obj(
"id" -> tour.id,
"name" -> tour.name,
"running" -> tour.isRunning
)
)
}
}
}

@ -1 +1 @@
Subproject commit 8a5223f54661801520d5a28c78f6354163b67a65
Subproject commit 4ef85368489ffac650233d32057882195d4ac7a8

View File

@ -117,6 +117,6 @@ object Query {
val statuses =
Status.finishedNotCheated filterNot (_.is(_.Timeout)) map { s =>
s.id -> s.is(_.Outoftime).fold("Clock Flag", s.name)
s.id -> s.is(_.Outoftime).fold("Clock Flag", s.toString)
}
}

View File

@ -24,7 +24,6 @@ final class JsonView(
pov: Pov,
pref: Pref,
apiVersion: Int,
tourOption: Option[lila.tournament.Tournament],
playerUser: Option[User]): Fu[JsObject] =
getVersion(pov.game.id) zip
(pov.opponent.userId ?? UserRepo.byId) zip
@ -54,7 +53,7 @@ final class JsonView(
"lastMove" -> game.castleLastMoveTime.lastMoveString,
"status" -> Json.obj(
"id" -> pov.game.status.id,
"name" -> pov.game.status.lowerName)),
"name" -> pov.game.status.name)),
"clock" -> game.clock.map(clockJson),
"player" -> Json.obj(
"id" -> playerId,
@ -98,13 +97,12 @@ final class JsonView(
"t" -> text)
})
},
"tournament" -> tourOption.map { tour =>
Json.obj(
"id" ->game.tournamentId
)
},
"possibleMoves" -> possibleMoves(pov),
"poolId" -> game.poolId,
"pool" -> game.poolId.map { pid =>
Json.obj(
"id" -> pid
)
},
"takebackable" -> takebackable)
}

View File

@ -0,0 +1,15 @@
package lila.tournament
case class LightTournament(
id: String,
name: String,
status: Int) {
def running = status == Status.Started.id
}
object LightTournament {
import reactivemongo.bson._
private[tournament] implicit val lightTournamentBSONHandler = Macros.handler[LightTournament]
}

View File

@ -10,6 +10,8 @@ import tube.tournamentTube
object TournamentRepo {
import LightTournament.lightTournamentBSONHandler
private def asCreated(tour: Tournament): Option[Created] = tour.some collect {
case t: Created => t
}
@ -26,6 +28,9 @@ object TournamentRepo {
def byId(id: String): Fu[Option[Tournament]] = $find byId id
def lightById(id: String): Fu[Option[LightTournament]] =
tournamentTube.coll.find(BSONDocument("_id" -> id)).one[LightTournament]
def nameById(id: String): Fu[Option[String]] =
$primitive.one($select(id), "name")(_.asOpt[String])

View File

@ -1,129 +1,8 @@
var map = require('lodash-node/modern/collections/map');
var chessground = require('chessground');
var round = require('../round');
var status = require('../status');
var opposite = chessground.util.opposite;
var classSet = chessground.util.classSet;
var partial = chessground.util.partial;
var renderClock = require('../clock/view');
var renderStatus = require('./status');
var m = require('mithril');
function renderOpponent(ctrl) {
var op = ctrl.data.opponent;
return op.ai ? m('div.username.connected.statused',
ctrl.trans('aiNameLevelAiLevel', 'Stockfish', op.ai)
) : m('div', {
class: 'username ' + op.color + ' ' + classSet({
'statused': op.statused,
'connected': op.connected,
'offline': !op.connected
})
},
op.user ? [
m('a', {
class: 'user_link ulpt',
href: ctrl.router.User.show(op.user.username).url,
target: round.playable(ctrl.data) ? '_blank' : null,
'data-icon': 'r',
}, [
(op.user.title ? op.user.title + ' ' : '') + op.user.username,
op.engine ? m('span[data-icon=j]', {
title: ctrl.trans('thisPlayerUsesChessComputerAssistance')
}) : null
]),
m('span.status')
] : m('span.user_link', [
'Anonymous',
m('span.status')
])
);
}
function renderResult(ctrl) {
var winner = round.getPlayer(ctrl.data, ctrl.data.game.winner);
return winner ? m('div.lichess_player.' + winner.color, [
m('div.cg-piece.king.' + winner.color),
m('p', [
renderStatus(ctrl),
m('br'),
ctrl.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious')
])
]) :
m('div.lichess_player', renderStatus(ctrl));
}
function renderTableEnd(ctrl) {
var d = ctrl.data;
return [
m('div.lichess_current_player', renderResult(ctrl))
];
}
function renderButton(ctrl, condition, icon, hint, socketMsg) {
return condition(ctrl.data) ? m('button', {
class: 'button hint--bottom',
'data-hint': ctrl.trans(hint),
onclick: partial(ctrl.socket.send, socketMsg, null)
}, m('span[data-icon=' + icon + ']')) : null;
}
function renderTablePlay(ctrl) {
var d = ctrl.data;
return [
m('div.lichess_current_player',
m('div.lichess_player', [
m('div.cg-piece.king.' + d.game.player),
m('p', ctrl.trans(d.game.player == d.player.color ? 'yourTurn' : 'waiting'))
])
),
m('div.lichess_control.icons', [
renderButton(ctrl, round.abortable, 'L', 'abortGame', 'abort'),
renderButton(ctrl, round.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
renderButton(ctrl, round.drawable, '2', 'offerDraw', 'draw-yes'),
renderButton(ctrl, round.resignable, 'b', 'resign', 'resign')
]),
d.player.isOfferingDraw ? m('div.negociation', [
ctrl.trans('drawOfferSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isOfferingDraw ? m('div.negociation', [
ctrl.trans('yourOpponentOffersADraw'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'draw-yes', null)
}, ctrl.trans('accept')),
m.trust(' '),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('decline')),
]) : null,
d.player.isProposingTakeback ? m('div.negociation', [
ctrl.trans('takebackPropositionSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isProposingTakeback ? m('div.negociation', [
ctrl.trans('yourOpponentProposesATakeback'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'takeback-yes', null)
}, ctrl.trans('accept')),
m.trust(' '),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('decline')),
]) : null, (round.mandatory(d) && round.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
ctrl.trans('youHaveNbSecondsToMakeYourFirstMove')
) : null
];
}
var chessground = require('chessground');
var renderTable = require('./table');
module.exports = function(ctrl) {
var clockRunningColor = ctrl.isClockRunning() ? ctrl.data.game.player : null;
return m('div', {
config: function(el, isUpdate, context) {
if (isUpdate) return;
@ -136,22 +15,6 @@ module.exports = function(ctrl) {
m('div.lichess_board.' + ctrl.data.game.variant.key, chessground.view(ctrl.chessground)),
m('div#premove_alert', ctrl.trans('premoveEnabledClickAnywhereToCancel'))
]),
m('div.lichess_ground',
m('div.lichess_table_wrap', [
(ctrl.clock && !ctrl.data.blindMode) ? renderClock(ctrl.clock, opposite(ctrl.data.player.color), "top", clockRunningColor) : null,
m('div', {
class: 'lichess_table onbg ' + classSet({
'table_with_clock': ctrl.clock,
'finished': status.finished(ctrl.data)
})
}, [
m('div.lichess_opponent', renderOpponent(ctrl)),
m('div.lichess_separator'),
m('div.table_inner',
round.playable(ctrl.data) ? renderTablePlay(ctrl) : renderTableEnd(ctrl)
)
]), (ctrl.clock && !ctrl.data.blindMode) ? renderClock(ctrl.clock, ctrl.data.player.color, "bottom", clockRunningColor) : null,
])
)
m('div.lichess_ground', renderTable(ctrl))
]);
};

View File

@ -0,0 +1,184 @@
var m = require('mithril');
var chessground = require('chessground');
var round = require('../round');
var status = require('../status');
var opposite = chessground.util.opposite;
var classSet = chessground.util.classSet;
var partial = chessground.util.partial;
var renderClock = require('../clock/view');
var renderStatus = require('./status');
function renderOpponent(ctrl) {
var op = ctrl.data.opponent;
return op.ai ? m('div.username.connected.statused',
ctrl.trans('aiNameLevelAiLevel', 'Stockfish', op.ai)
) : m('div', {
class: 'username ' + op.color + ' ' + classSet({
'statused': op.statused,
'connected': op.connected,
'offline': !op.connected
})
},
op.user ? [
m('a', {
class: 'user_link ulpt',
href: ctrl.router.User.show(op.user.username).url,
target: round.playable(ctrl.data) ? '_blank' : null,
'data-icon': 'r',
}, [
(op.user.title ? op.user.title + ' ' : '') + op.user.username,
op.engine ? m('span[data-icon=j]', {
title: ctrl.trans('thisPlayerUsesChessComputerAssistance')
}) : null
]),
m('span.status')
] : m('span.user_link', [
'Anonymous',
m('span.status')
])
);
}
function renderResult(ctrl) {
var winner = round.getPlayer(ctrl.data, ctrl.data.game.winner);
return winner ? m('div.lichess_player.' + winner.color, [
m('div.cg-piece.king.' + winner.color),
m('p', [
renderStatus(ctrl),
m('br'),
ctrl.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious')
])
]) :
m('div.lichess_player', renderStatus(ctrl));
}
function renderRematchButton(ctrl) {
return m('a.lichess_rematch.offer.button.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null)
}, ctrl.trans('rematch'));
}
function renderTableEnd(ctrl) {
var d = ctrl.data;
return [
m('div.lichess_current_player', renderResult(ctrl)),
m('div.lichess_control.buttons', [
d.game.pool ? [
m('a.button[data-icon=,]', {
href: router.Pool.show(d.game.pool.id),
}, 'Return to pool'),
m('form[method=post]', {
action: router.Pool.leave(d.game.pool.id)
}, m('button.button[type=submit]', 'Leave the pool'))
] : (d.game.tournament ? m('a[data-icon=G]', {
class: classSet({
button: true,
strong: d.game.tournament.running
}),
href: ctrl.router.Tournament.show(d.game.tournament.id)
}, ctrl.trans('backToTournament')) : (d.opponent.ai ? renderRematchButton(ctrl) : [
m('div.lichess_separator'),
d.opponent.isOfferingRematch ? m('div.lichess_play_again_join.rematch_alert', [
ctrl.trans('yourOpponentWantsToPlayANewGameWithYou'),
m('a.glowing.button.lichess_play_again.lichess_rematch.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null),
}, ctrl.trans('joinTheGame')),
m('a', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('declineInvitation'))
]) : (d.player.isOfferingRematch ? m('div.lichess_play_again_join.rematch_wait', [
ctrl.trans('rematchOfferSent'),
m('br'),
ctrl.trans('waitingForOpponent'),
m('br'), m('br'),
m('a.lichess_rematch_cancel', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('cancelRematchOffer'))
]) : renderRematchButton(ctrl))
]))
])
];
}
function renderButton(ctrl, condition, icon, hint, socketMsg) {
return condition(ctrl.data) ? m('button', {
class: 'button hint--bottom',
'data-hint': ctrl.trans(hint),
onclick: partial(ctrl.socket.send, socketMsg, null)
}, m('span[data-icon=' + icon + ']')) : null;
}
function renderTablePlay(ctrl) {
var d = ctrl.data;
return [
m('div.lichess_current_player',
m('div.lichess_player', [
m('div.cg-piece.king.' + d.game.player),
m('p', ctrl.trans(d.game.player == d.player.color ? 'yourTurn' : 'waiting'))
])
),
m('div.lichess_control.icons', [
renderButton(ctrl, round.abortable, 'L', 'abortGame', 'abort'),
renderButton(ctrl, round.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
renderButton(ctrl, round.drawable, '2', 'offerDraw', 'draw-yes'),
renderButton(ctrl, round.resignable, 'b', 'resign', 'resign')
]),
d.player.isOfferingDraw ? m('div.negociation', [
ctrl.trans('drawOfferSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isOfferingDraw ? m('div.negociation', [
ctrl.trans('yourOpponentOffersADraw'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'draw-yes', null)
}, ctrl.trans('accept')),
m.trust(' '),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('decline')),
]) : null,
d.player.isProposingTakeback ? m('div.negociation', [
ctrl.trans('takebackPropositionSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isProposingTakeback ? m('div.negociation', [
ctrl.trans('yourOpponentProposesATakeback'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'takeback-yes', null)
}, ctrl.trans('accept')),
m.trust(' '),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('decline')),
]) : null, (round.mandatory(d) && round.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
ctrl.trans('youHaveNbSecondsToMakeYourFirstMove')
) : null
];
}
module.exports = function(ctrl) {
var clockRunningColor = ctrl.isClockRunning() ? ctrl.data.game.player : null;
return m('div.lichess_table_wrap', [
(ctrl.clock && !ctrl.data.blindMode) ? renderClock(ctrl.clock, opposite(ctrl.data.player.color), "top", clockRunningColor) : null,
m('div', {
class: 'lichess_table onbg ' + classSet({
'table_with_clock': ctrl.clock,
'finished': status.finished(ctrl.data)
})
}, [
m('div.lichess_opponent', renderOpponent(ctrl)),
m('div.lichess_separator'),
m('div.table_inner',
round.playable(ctrl.data) ? renderTablePlay(ctrl) : renderTableEnd(ctrl)
)
]), (ctrl.clock && !ctrl.data.blindMode) ? renderClock(ctrl.clock, ctrl.data.player.color, "bottom", clockRunningColor) : null,
])
}