opening trainer is working
This commit is contained in:
parent
91a2c0d488
commit
82b80bbc79
|
@ -8,9 +8,9 @@ import play.twirl.api.Html
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.HTTPRequest
|
||||
import lila.opening.{ Generated, Opening => OpeningModel }
|
||||
import lila.user.{ User => UserModel, UserRepo }
|
||||
import lila.common.HTTPRequest
|
||||
import views._
|
||||
import views.html.opening.JsData
|
||||
|
||||
|
@ -20,12 +20,16 @@ object Opening extends LilaController {
|
|||
|
||||
private def renderShow(opening: OpeningModel)(implicit ctx: Context) =
|
||||
env userInfos ctx.me map { infos =>
|
||||
views.html.opening.show(opening, infos)
|
||||
views.html.opening.show(opening, infos, env.AnimationDuration)
|
||||
}
|
||||
|
||||
def home = Open { implicit ctx =>
|
||||
if (HTTPRequest isXhr ctx.req) env.selector(ctx.me) zip (env userInfos ctx.me) map {
|
||||
case (opening, infos) => Ok(JsData(opening, infos, true)) as JSON
|
||||
case (opening, infos) => Ok(JsData(opening, infos,
|
||||
play = true,
|
||||
attempt = none,
|
||||
win = none,
|
||||
animationDuration = env.AnimationDuration)) as JSON
|
||||
}
|
||||
else env.selector(ctx.me) flatMap { opening =>
|
||||
renderShow(opening) map { Ok(_) }
|
||||
|
@ -45,23 +49,42 @@ object Opening extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
OptionFuResult(env.api.opening find id) { opening =>
|
||||
attemptForm.bindFromRequest.fold(
|
||||
err => fuccess(BadRequest(err.errorsAsJson)), {
|
||||
case (found, failed) => ctx.me match {
|
||||
case Some(me) => env.finisher(opening, me, found, failed) flatMap { attempt =>
|
||||
UserRepo byId me.id map (_ | me) flatMap { me2 =>
|
||||
env.api.opening find id zip
|
||||
(env userInfos me2.some) map {
|
||||
err => fuccess(BadRequest(err.errorsAsJson)),
|
||||
data => {
|
||||
val (found, failed) = data
|
||||
val win = found == opening.goal && failed == 0
|
||||
ctx.me match {
|
||||
case Some(me) => env.finisher(opening, me, win) flatMap {
|
||||
case (newAttempt, None) =>
|
||||
UserRepo byId me.id map (_ | me) flatMap { me2 =>
|
||||
(env.api.opening find id) zip (env userInfos me2.some) map {
|
||||
case (o2, infos) => Ok {
|
||||
JsData(o2 | opening, infos, false)
|
||||
JsData(o2 | opening, infos,
|
||||
play = false,
|
||||
attempt = newAttempt.some,
|
||||
win = none,
|
||||
animationDuration = env.AnimationDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
case (oldAttempt, Some(win)) => env userInfos me.some map { infos =>
|
||||
Ok(JsData(opening, infos,
|
||||
play = false,
|
||||
attempt = oldAttempt.some,
|
||||
win = win.some,
|
||||
animationDuration = env.AnimationDuration))
|
||||
}
|
||||
}
|
||||
case None => fuccess {
|
||||
Ok(JsData(opening, none, false))
|
||||
Ok(JsData(opening, none,
|
||||
play = false,
|
||||
attempt = none,
|
||||
win = win.some,
|
||||
animationDuration = env.AnimationDuration))
|
||||
}
|
||||
}
|
||||
}) map (_ as JSON)
|
||||
}
|
||||
) map (_ as JSON)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,10 @@ object JsData extends lila.Steroids {
|
|||
def apply(
|
||||
opening: Opening,
|
||||
userInfos: Option[lila.opening.UserInfos],
|
||||
play: Boolean)(implicit ctx: Context) =
|
||||
play: Boolean,
|
||||
attempt: Option[Attempt],
|
||||
win: Option[Boolean],
|
||||
animationDuration: scala.concurrent.duration.Duration)(implicit ctx: Context) =
|
||||
Html(Json.stringify(Json.obj(
|
||||
"opening" -> Json.obj(
|
||||
"id" -> opening.id,
|
||||
|
@ -32,6 +35,15 @@ object JsData extends lila.Steroids {
|
|||
}),
|
||||
"url" -> s"$netBaseUrl${routes.Opening.show(opening.id)}"
|
||||
),
|
||||
"animation" -> Json.obj(
|
||||
"duration" -> ctx.pref.animationFactor * animationDuration.toMillis
|
||||
),
|
||||
"attempt" -> attempt.map { a =>
|
||||
Json.obj(
|
||||
"userRatingDiff" -> a.userRatingDiff,
|
||||
"win" -> a.win)
|
||||
},
|
||||
"win" -> win,
|
||||
"user" -> userInfos.map { i =>
|
||||
Json.obj(
|
||||
"rating" -> i.user.perfs.opening.intRating,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(opening: lila.opening.Opening, userInfos: Option[lila.opening.UserInfos])(implicit ctx: Context)
|
||||
@(opening: lila.opening.Opening, userInfos: Option[lila.opening.UserInfos], animationDuration: scala.concurrent.duration.Duration)(implicit ctx: Context)
|
||||
|
||||
@evenMoreJs = {
|
||||
@helper.javascriptRouter("openingRoutes")(
|
||||
|
@ -6,7 +6,7 @@
|
|||
@embedJs {
|
||||
LichessOpening(
|
||||
document.querySelector('#lichess .round'),
|
||||
@JsData(opening, userInfos, true),
|
||||
@JsData(opening, userInfos, play = true, attempt = none, win = none, animationDuration = animationDuration),
|
||||
openingRoutes.controllers,
|
||||
@Html(J.stringify(i18nJsObject(
|
||||
trans.training,
|
||||
|
@ -20,7 +20,16 @@ trans.openingId,
|
|||
trans.ratingX,
|
||||
trans.playedXTimes,
|
||||
trans.yourOpeningRatingX,
|
||||
trans.findNbStrongMoves
|
||||
trans.findNbStrongMoves,
|
||||
trans.openingFailed,
|
||||
trans.openingSolved,
|
||||
trans.butYouCanKeepTrying,
|
||||
trans.goodMove,
|
||||
trans.butYouCanDoBetter,
|
||||
trans.bestMove,
|
||||
trans.keepGoing,
|
||||
trans.victory,
|
||||
trans.thisMoveGivesYourOpponentTheAdvantage
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -312,3 +312,6 @@ autoSwitch=Auto switch
|
|||
openingId=Opening %s
|
||||
yourOpeningRatingX=Your opening rating: %s
|
||||
findNbStrongMoves=Find %s strong moves
|
||||
thisMoveGivesYourOpponentTheAdvantage=This move gives your opponent the advantage
|
||||
openingFailed=Opening failed
|
||||
openingSolved=Opening solved
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e0989842735e58803c2ac20b2548c4d8f8c03ac2
|
||||
Subproject commit 8e52881a9b77d371a1144a5814f594458ce52efe
|
File diff suppressed because one or more lines are too long
|
@ -16,6 +16,8 @@ final class Env(
|
|||
}
|
||||
import settings._
|
||||
|
||||
val AnimationDuration = config duration "animation.duration"
|
||||
|
||||
lazy val api = new OpeningApi(
|
||||
openingColl = openingColl,
|
||||
attemptColl = attemptColl,
|
||||
|
@ -23,7 +25,10 @@ final class Env(
|
|||
|
||||
lazy val selector = new Selector(
|
||||
openingColl = openingColl,
|
||||
api = api)
|
||||
api = api,
|
||||
toleranceStep = config getInt "selector.tolerance.step",
|
||||
toleranceMax = config getInt "selector.tolerance.max",
|
||||
modulo = config getInt "selector.modulo")
|
||||
|
||||
lazy val finisher = new Finisher(
|
||||
api = api,
|
||||
|
|
|
@ -12,11 +12,10 @@ private[opening] final class Finisher(
|
|||
api: OpeningApi,
|
||||
openingColl: Coll) {
|
||||
|
||||
def apply(opening: Opening, user: User, found: Int, failed: Int): Fu[Attempt] =
|
||||
def apply(opening: Opening, user: User, win: Boolean): Fu[(Attempt, Option[Boolean])] = {
|
||||
api.attempt.find(opening.id, user.id) flatMap {
|
||||
case Some(a) => fuccess(a)
|
||||
case Some(a) => fuccess(a -> win.some)
|
||||
case None =>
|
||||
val win = found == opening.goal && failed == 0
|
||||
val userRating = user.perfs.opening.toRating
|
||||
val openingRating = opening.perf.toRating
|
||||
updateRatings(userRating, openingRating, win.fold(Glicko.Result.Win, Glicko.Result.Loss))
|
||||
|
@ -44,8 +43,9 @@ private[opening] final class Finisher(
|
|||
))) zip UserRepo.setPerf(user.id, "opening", userPerf)
|
||||
}) recover {
|
||||
case e: reactivemongo.core.commands.LastError if e.getMessage.contains("duplicate key error") => ()
|
||||
} inject a
|
||||
} inject (a -> none)
|
||||
}
|
||||
}
|
||||
|
||||
private val VOLATILITY = Glicko.default.volatility
|
||||
private val TAU = 0.75d
|
||||
|
|
|
@ -97,6 +97,7 @@ object Opening {
|
|||
val attempts = "attempts"
|
||||
val wins = "wins"
|
||||
val perf = "perf"
|
||||
val rating = s"$perf.gl.r"
|
||||
}
|
||||
|
||||
implicit val openingBSONHandler = new BSON[Opening] {
|
||||
|
|
|
@ -58,9 +58,10 @@ private[opening] final class OpeningApi(
|
|||
|
||||
private val PlayedIdsGroup = Group(BSONBoolean(true))("ids" -> Push(Attempt.BSONFields.openingId))
|
||||
|
||||
def playedIds(user: User): Fu[BSONArray] = {
|
||||
def playedIds(user: User, max: Int): Fu[BSONArray] = {
|
||||
val command = Aggregate(attemptColl.name, Seq(
|
||||
Match(BSONDocument(Attempt.BSONFields.userId -> user.id)),
|
||||
Limit(max),
|
||||
PlayedIdsGroup
|
||||
))
|
||||
attemptColl.db.command(command) map {
|
||||
|
|
|
@ -4,7 +4,7 @@ import scala.concurrent.duration._
|
|||
import scala.util.Random
|
||||
|
||||
import reactivemongo.api.QueryOpts
|
||||
import reactivemongo.bson.BSONDocument
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONArray }
|
||||
import reactivemongo.core.commands.Count
|
||||
|
||||
import lila.db.Types.Coll
|
||||
|
@ -12,19 +12,36 @@ import lila.user.User
|
|||
|
||||
private[opening] final class Selector(
|
||||
openingColl: Coll,
|
||||
api: OpeningApi) {
|
||||
api: OpeningApi,
|
||||
toleranceStep: Int,
|
||||
toleranceMax: Int,
|
||||
modulo: Int) {
|
||||
|
||||
val anonSkipMax = 10000
|
||||
val anonSkipMax = 2000
|
||||
|
||||
def apply(me: Option[User]): Fu[Opening] = me match {
|
||||
case None =>
|
||||
openingColl.find(BSONDocument())
|
||||
.options(QueryOpts(skipN = Random nextInt anonSkipMax))
|
||||
.one[Opening] flatten "Can't find a opening for anon player!"
|
||||
case Some(user) => api.attempt playedIds user flatMap { ids =>
|
||||
openingColl.find(BSONDocument(
|
||||
Opening.BSONFields.id -> BSONDocument("$nin" -> ids)
|
||||
)).one[Opening] flatten s"Can't find a opening for user $user!"
|
||||
case Some(user) => api.attempt.playedIds(user, modulo) flatMap { ids =>
|
||||
tryRange(user, toleranceStep, ids)
|
||||
} recoverWith {
|
||||
case e: Exception => apply(none)
|
||||
}
|
||||
}
|
||||
|
||||
private def tryRange(user: User, tolerance: Int, ids: BSONArray): Fu[Opening] =
|
||||
openingColl.find(BSONDocument(
|
||||
Opening.BSONFields.id -> BSONDocument("$nin" -> ids),
|
||||
Opening.BSONFields.rating -> BSONDocument(
|
||||
"$gt" -> BSONInteger(user.perfs.opening.intRating - tolerance),
|
||||
"$lt" -> BSONInteger(user.perfs.opening.intRating + tolerance)
|
||||
)
|
||||
)).one[Opening] flatMap {
|
||||
case Some(opening) => fuccess(opening)
|
||||
case None => if ((tolerance + toleranceStep) <= toleranceMax)
|
||||
tryRange(user, tolerance + toleranceStep, ids)
|
||||
else fufail(s"Can't find a opening for user $user!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ object ApplicationBuild extends Build {
|
|||
import Dependencies._
|
||||
|
||||
lazy val root = Project("lila", file(".")) enablePlugins PlayScala settings (
|
||||
scalaVersion := "2.11.4",
|
||||
scalaVersion := globalScalaVersion,
|
||||
resolvers ++= Dependencies.Resolvers.commons,
|
||||
scalacOptions := compilerOptions,
|
||||
offline := true,
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import sbt._, Keys._
|
||||
import play.Play.autoImport._
|
||||
import sbt._, Keys._
|
||||
|
||||
object BuildSettings {
|
||||
|
||||
import Dependencies._
|
||||
|
||||
val globalScalaVersion = "2.11.5"
|
||||
|
||||
def buildSettings = Defaults.defaultSettings ++ Seq(
|
||||
organization := "org.lichess",
|
||||
scalaVersion := "2.11.4",
|
||||
scalaVersion := globalScalaVersion,
|
||||
resolvers ++= Dependencies.Resolvers.commons,
|
||||
parallelExecution in Test := false,
|
||||
scalacOptions := compilerOptions,
|
||||
|
|
|
@ -364,17 +364,17 @@ body.dark .loader:before,
|
|||
body.dark .loader:after {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
body.dark #puzzle > .side div.fail,
|
||||
body.dark #puzzle > .side div.loss {
|
||||
body.dark div.training > .side div.fail,
|
||||
body.dark div.training > .side div.loss {
|
||||
color: #e97472;
|
||||
background: #582b33;
|
||||
}
|
||||
body.dark #puzzle > .side div.retry {
|
||||
body.dark div.training > .side div.retry {
|
||||
color: #8482c9;
|
||||
background: #101034;
|
||||
}
|
||||
body.dark #puzzle > .side div.great,
|
||||
body.dark #puzzle > .side div.win,
|
||||
body.dark div.training > .side div.great,
|
||||
body.dark div.training > .side div.win,
|
||||
body.dark #puzzle > .right .please_vote {
|
||||
background: #103410;
|
||||
color: #74a962;
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
margin-top: 20px;
|
||||
}
|
||||
#opening .meter ul {
|
||||
width: 490px;
|
||||
margin-top: 13px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
|
|
|
@ -6,44 +6,6 @@
|
|||
#puzzle [data-icon]::before {
|
||||
margin-right: 3px;
|
||||
}
|
||||
#puzzle > .side div.comment {
|
||||
padding: 10px 5px;
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#puzzle > .side div.comment h3 {
|
||||
font-size: inherit;
|
||||
margin: 4px;
|
||||
display: block;
|
||||
}
|
||||
#puzzle > .side div.comment .rating {
|
||||
font-size: 2em;
|
||||
vertical-align: -3px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
#puzzle > .side div.retry {
|
||||
color: #31708f;
|
||||
background: #D9EDF7;
|
||||
}
|
||||
#puzzle.retry > .side div.retry {
|
||||
display: block;
|
||||
}
|
||||
#puzzle > .side div.great,
|
||||
#puzzle > .side div.win {
|
||||
background: #DFF0D8;
|
||||
color: #3c763d;
|
||||
}
|
||||
#puzzle.great > .side div.great {
|
||||
display: block;
|
||||
}
|
||||
#puzzle > .side div.loss,
|
||||
#puzzle > .side div.fail {
|
||||
color: #a94442;
|
||||
background: #ebccd1;
|
||||
}
|
||||
#puzzle.fail > .side div.fail {
|
||||
display: block;
|
||||
}
|
||||
#puzzle > .side .difficulty {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,44 @@ div.training > .side h1 {
|
|||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
div.training > .side div.comment {
|
||||
padding: 10px 5px;
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.training > .side div.comment h3 {
|
||||
font-size: inherit;
|
||||
margin: 4px;
|
||||
display: block;
|
||||
}
|
||||
div.training > .side div.retry {
|
||||
color: #31708f;
|
||||
background: #D9EDF7;
|
||||
}
|
||||
div.training > .side div.great,
|
||||
div.training > .side div.win {
|
||||
background: #DFF0D8;
|
||||
color: #3c763d;
|
||||
}
|
||||
div.training > .side div.loss,
|
||||
div.training > .side div.fail {
|
||||
color: #a94442;
|
||||
background: #ebccd1;
|
||||
}
|
||||
div.training > .side div.comment .rating {
|
||||
font-size: 2em;
|
||||
vertical-align: -3px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
div.training.retry > .side div.retry {
|
||||
display: block;
|
||||
}
|
||||
div.training.great > .side div.great {
|
||||
display: block;
|
||||
}
|
||||
div.training.fail > .side div.fail {
|
||||
display: block;
|
||||
}
|
||||
div.training > .side .buttonset {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
|
|
|
@ -24,6 +24,7 @@ module.exports = function(cfg, router, i18n) {
|
|||
loading: false,
|
||||
flash: {},
|
||||
flashFound: null,
|
||||
comment: null
|
||||
};
|
||||
}.bind(this);
|
||||
initialize();
|
||||
|
@ -80,6 +81,13 @@ module.exports = function(cfg, router, i18n) {
|
|||
turnColor: this.data.opening.color,
|
||||
check: init.check,
|
||||
autoCastle: true,
|
||||
animation: {
|
||||
enabled: true,
|
||||
duration: this.data.animation.duration
|
||||
},
|
||||
premovable: {
|
||||
enabled: true
|
||||
},
|
||||
movable: {
|
||||
color: this.data.opening.color,
|
||||
free: false,
|
||||
|
@ -104,6 +112,7 @@ module.exports = function(cfg, router, i18n) {
|
|||
var known = this.data.opening.moves.filter(function(m) {
|
||||
return m.uci === move.uci;
|
||||
})[0];
|
||||
this.comment = null;
|
||||
if (known && known.quality === 'good') {
|
||||
var alreadyFound = this.vm.figuredOut.filter(function(f) {
|
||||
return f.uci === move.uci;
|
||||
|
@ -112,12 +121,15 @@ module.exports = function(cfg, router, i18n) {
|
|||
else {
|
||||
flash(move, 'good');
|
||||
this.vm.figuredOut.push(move);
|
||||
this.vm.comment = 'good';
|
||||
}
|
||||
} else if (known && known.quality === 'dubious') {
|
||||
flash(move, 'dubious');
|
||||
this.vm.comment = 'dubious';
|
||||
} else {
|
||||
if (this.vm.messedUp.indexOf(move.uci) === -1) this.vm.messedUp.push(move);
|
||||
flash(move, 'bad');
|
||||
this.vm.comment = 'bad';
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
|
|
|
@ -32,11 +32,11 @@ function renderPlayTable(ctrl) {
|
|||
function renderViewTable(ctrl) {
|
||||
return [
|
||||
m('div.box', [
|
||||
m('h2',
|
||||
m('a', {
|
||||
href: '/training/opening/' + ctrl.data.opening.id
|
||||
}, ctrl.trans('openingId', ctrl.data.opening.id))
|
||||
),
|
||||
m('h2.text', {
|
||||
'data-icon': ']'
|
||||
}, m('a', {
|
||||
href: '/training/opening/' + ctrl.data.opening.id
|
||||
}, ctrl.trans('openingId', ctrl.data.opening.id))),
|
||||
m('p', m.trust(ctrl.trans('ratingX', strong(ctrl.data.opening.rating)))),
|
||||
m('p', m.trust(ctrl.trans('playedXTimes', strong(ctrl.data.opening.attempts)))),
|
||||
m('p',
|
||||
|
@ -74,6 +74,27 @@ function renderUserInfos(ctrl) {
|
|||
]);
|
||||
}
|
||||
|
||||
function renderCommentary(ctrl) {
|
||||
switch (ctrl.vm.comment) {
|
||||
case 'dubious':
|
||||
return m('div.comment.retry', [
|
||||
m('h3', m('strong', ctrl.trans('goodMove'))),
|
||||
m('span', ctrl.trans('butYouCanDoBetter'))
|
||||
]);
|
||||
case 'good':
|
||||
return m('div.comment.great', [
|
||||
m('h3.text[data-icon=E]', m('strong', ctrl.trans('bestMove'))),
|
||||
ctrl.vm.figuredOut.length < ctrl.data.goal ? m('span', ctrl.trans('keepGoing')) : null
|
||||
]);
|
||||
case 'bad':
|
||||
return m('div.comment.fail', [
|
||||
m('h3.text[data-icon=k]', m('strong', ctrl.trans('thisMoveGivesYourOpponentTheAdvantage')))
|
||||
]);
|
||||
default:
|
||||
return ctrl.vm.comment
|
||||
}
|
||||
}
|
||||
|
||||
function renderTrainingBox(ctrl) {
|
||||
return m('div.box', [
|
||||
m('h1', ctrl.trans('training')),
|
||||
|
@ -100,9 +121,50 @@ function renderTrainingBox(ctrl) {
|
|||
]);
|
||||
}
|
||||
|
||||
function renderRatingDiff(diff) {
|
||||
return m('strong.rating', diff > 0 ? '+' + diff : diff);
|
||||
}
|
||||
|
||||
function renderWin(ctrl, attempt) {
|
||||
return m('div.comment.win', [
|
||||
m('h3.text[data-icon=E]', [
|
||||
m('strong', ctrl.trans('victory')),
|
||||
attempt ? renderRatingDiff(attempt.userRatingDiff) : null
|
||||
]),
|
||||
attempt ? m('span', ctrl.trans('openingSolved')) : null
|
||||
]);
|
||||
}
|
||||
|
||||
function renderLoss(ctrl, attempt) {
|
||||
return m('div.comment.loss',
|
||||
m('h3.text[data-icon=k]', [
|
||||
m('strong', ctrl.trans('openingFailed')),
|
||||
attempt ? renderRatingDiff(attempt.userRatingDiff) : null
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function renderResult(ctrl) {
|
||||
switch (ctrl.data.win) {
|
||||
case true:
|
||||
return renderWin(ctrl, null);
|
||||
case false:
|
||||
return renderLoss(ctrl, null);
|
||||
default:
|
||||
switch (ctrl.data.attempt && ctrl.data.attempt.win) {
|
||||
case true:
|
||||
return renderWin(ctrl, ctrl.data.attempt);
|
||||
case false:
|
||||
return renderLoss(ctrl, ctrl.data.attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderSide(ctrl) {
|
||||
return m('div.side', [
|
||||
renderTrainingBox(ctrl)
|
||||
renderTrainingBox(ctrl),
|
||||
ctrl.data.play ? renderCommentary(ctrl) : null,
|
||||
renderResult(ctrl)
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ module.exports = function(cfg, router, i18n) {
|
|||
|
||||
this.data = data(cfg);
|
||||
|
||||
this.userMove = function(orig, dest) {
|
||||
var userMove = function(orig, dest) {
|
||||
var res = puzzle.tryMove(this.data, [orig, dest]);
|
||||
var newProgress = res[0];
|
||||
var newLines = res[1];
|
||||
|
@ -85,7 +85,7 @@ module.exports = function(cfg, router, i18n) {
|
|||
free: false,
|
||||
color: cfg.mode !== 'view' ? cfg.puzzle.color : null,
|
||||
events: {
|
||||
after: this.userMove
|
||||
after: userMove
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
|
|
|
@ -187,7 +187,7 @@ function renderViewTable(ctrl) {
|
|||
]) : null,
|
||||
m('div.box', [
|
||||
(ctrl.data.puzzle.enabled && ctrl.data.user) ? renderVote(ctrl) : null,
|
||||
m('h2',
|
||||
m('h2.text[data-icon="-"]',
|
||||
m('a', {
|
||||
href: ctrl.router.Puzzle.show(ctrl.data.puzzle.id).url
|
||||
}, ctrl.trans('puzzleId', ctrl.data.puzzle.id))
|
||||
|
|
Loading…
Reference in a new issue