moar progress on opening trainer, with score perf

This commit is contained in:
Thibault Duplessis 2015-01-08 13:03:45 +01:00
parent df8f601eb9
commit ba650ee339
22 changed files with 174 additions and 37 deletions

View file

@ -17,13 +17,15 @@ object JsData extends lila.Steroids {
Html(Json.stringify(Json.obj(
"opening" -> Json.obj(
"id" -> opening.id,
"score" -> opening.score.toInt,
"attempts" -> opening.attempts,
"goal" -> opening.goal,
"fen" -> opening.fen,
"color" -> opening.color.name,
"moves" -> JsArray(opening.qualityMoves.map {
case QualityMove(move, quality) => Json.obj(
"first" -> move.first,
"uci" -> move.first,
"san" -> move.line.headOption,
"cp" -> move.cp,
"line" -> move.line.mkString(" "),
"quality" -> quality.name)

View file

@ -7,6 +7,7 @@
}
@moreJs = {
@jsTag("vendor/sparkline.min.js")
@jsAt(s"compiled/lichess.opening${isProd??(".min")}.js")
@evenMoreJs
}

View file

@ -14,7 +14,13 @@ trans.toTrackYourProgress,
trans.signUp,
trans.trainingSignupExplanation,
trans.giveUp,
trans.continueTraining
trans.yourTurn,
trans.continueTraining,
trans.openingId,
trans.scoreX,
trans.playedXTimes,
trans.yourOpeningScoreX,
trans.findNbStrongMoves
)))
);
}

View file

@ -48,6 +48,14 @@
<br />
}
@showPerf(u.perfs.puzzle, PerfType.Puzzle)
<div data-icon="]">
<h3 class="hint--top" data-hint="Opening trainer">OPENING TRAINER</h3>
<div class="rating">
<strong class="hint--bottom" data-hint="Percentage score">@u.perfs.opening.averageScore.getOrElse("?")</strong>
<span class="hint--bottom">/ @u.perfs.opening.nb.localize Openings</span>
@showProgress(u.perfs.opening.progress)
</div>
</div>
</div>
}

View file

@ -309,3 +309,7 @@ youHaveNbSecondsToMakeYourFirstMove=You have %s seconds to make your first move!
nbGamesInPlay=%s games in play
automaticallyProceedToNextGameAfterMoving=Automatically proceed to next game after moving
autoSwitch=Auto switch
openingId=Opening %s
scoreX=Score: %s
yourOpeningScoreX=Your opening score: %s
findNbStrongMoves=Find %s strong moves

File diff suppressed because one or more lines are too long

View file

@ -11,12 +11,17 @@ private[opening] final class Finisher(
api: OpeningApi,
openingColl: Coll) {
private def computeScore(opening: Opening, found: Int, failed: Int): Int = {
val base = 100d * found / goal
}
def apply(opening: Opening, user: User, found: Int, failed: Int): Fu[Attempt] =
api.attempt.find(opening.id, user.id) flatMap {
case Some(a) => fuccess(a)
case None =>
val date = DateTime.now
val score = 50
val score = computeScore(opening, found, failed)
val userScorePerf = user.perfs.opening.add(score, DateTime.now)
val openingScore = 0.1 * (score - opening.score) + opening.score
val a = new Attempt(

View file

@ -17,7 +17,7 @@ case class Opening(
attempts: Int,
score: Double) {
def goal = qualityMoves.size min 5
lazy val goal = qualityMoves.count(_.quality == Quality.Good) min 5
lazy val qualityMoves: List[QualityMove] = {
val bestCp = moves.foldLeft(Int.MaxValue) {

View file

@ -46,7 +46,7 @@ object UserInfos {
private def makeChart(attempts: List[Attempt]) = JsArray {
val scores = attempts.take(chartSize).reverse map (_.score)
val filled = List.fill(chartSize - scores.size)(Glicko.default.intRating) ::: scores
val filled = List.fill(chartSize - scores.size)(0) ::: scores
filled map { JsNumber(_) }
}
}

Binary file not shown.

View file

@ -96,4 +96,7 @@
<glyph unicode="&#91;" d="M416 160c44 0 80-36 80-80 0-44-36-80-80-80-44 0-80 36-80 80 0 5 0 10 1 14l-186 104c-14-13-34-22-55-22-44 0-80 36-80 80 0 44 36 80 80 80 21 0 41-9 55-22l186 104c-1 5-1 9-1 14 0 44 36 80 80 80 44 0 80-36 80-80 0-44-36-80-80-80-22 0-42 9-56 23l-186-102c1-5 2-11 2-17 0-6-1-12-2-17l185-102c14 14 35 23 57 23z"/>
<glyph unicode="&#62;" d="M256 320c-35 0-64-29-64-64 0-35 29-64 64-64 35 0 64 29 64 64 0 35-29 64-64 64z m197-8c-7 2-13 6-19 11-8-16-19-32-30-49-18 23-38 46-61 69-23 22-46 43-69 61 55 39 108 62 145 62 16 0 27-4 35-12 9-8 12-22 12-38 1 0 2 0 3 0 6 0 12-1 18-3 1 24-5 43-18 56-12 12-28 18-50 18-43 0-102-26-163-70-61 44-120 70-163 70-5 0-9 0-13-1 6-5 10-12 13-20 37 0 90-23 145-62-23-18-46-39-69-61-22-23-43-46-61-69-18 26-33 51-44 76-7 16-12 32-15 45-2 0-4 0-6 0-6 0-11 1-16 2 3-17 9-35 18-56 12-27 29-56 50-85-21-29-38-58-50-85-26-58-27-104-2-128 12-12 28-18 50-18 64 0 162 56 250 144 22 23 43 46 61 69 59-83 76-154 50-180-8-8-19-12-35-12-20 0-44 7-71 19-3-7-7-13-12-18 31-14 59-22 83-22 22 0 38 6 50 18 37 37 14 122-52 213 14 19 26 38 36 56z m-360-266c-16 0-27 4-35 12-18 17-15 55 6 104 11 25 26 50 44 76 18-23 39-47 61-69 23-23 46-43 69-61-55-39-108-62-145-62z m235 138c-23-23-48-44-72-63-24 19-48 40-72 63-23 24-44 48-63 72 19 24 40 48 63 72 24 23 48 44 72 63 24-19 48-40 72-63 24-24 45-48 63-72-19-24-40-48-63-72z m141 200c-11 0-21-10-21-21 0-12 10-22 21-22 12 0 22 10 22 22 0 11-10 21-22 21z m-170-277c-12 0-22-10-22-22 0-11 10-21 22-21 11 0 21 10 21 21 0 12-10 22-21 22z m-256 320c11 0 21 9 21 21 0 12-10 21-21 21-12 0-22-9-22-21 0-12 10-21 22-21z"/>
<glyph unicode="&#123;" d="M371 470l-230 0-141-139 256-289 256 289z m90-151l-1 2-190 0 92 98z m-239-178l-150 158 99 0-10-4z m-40 158l148 0-74-184z m159 0l99 0-150-158 61 154z m-85 37l-83 91 166 0z m-106 82l92-97-190 0-1-1z"/>
<glyph unicode="&#64;" d="M256 480c-141 0-256-115-256-256 0 0 0-96 0-128 0-32 32-64 64-64 32 0 352 0 384 0 32 0 64 32 64 64 0 32 0 128 0 128 0 141-114 256-256 256z m48-384l-96 0c-9 0-16 7-16 16 0 9 7 16 16 16l96 0c9 0 16-7 16-16 0-9-7-16-16-16z m144 64c0-16-16-32-32-32-16 0-64 0-64 0 0 16-16 32-32 32-16 0-112 0-128 0-16 0-32-16-32-32 0 0-48 0-64 0-16 0-32 16-32 32 0 16 0 180 0 180 39 64 110 108 192 108 82 0 153-44 192-108 0 0 0-164 0-180z m-64 192c-16 0-240 0-256 0-16 0-32-16-32-32 0-16 0-48 0-64 0-16 16-32 32-32 16 0 240 0 256 0 16 0 32 16 32 32 0 16 0 48 0 64 0 16-16 32-32 32z m0-64l-32-32-64 0-32 32-32-32-64 0-32 32 0 32 32 0 32-32 32 32 64 0 32-32 32 32 32 0z"/>
<glyph unicode="&#93;" d="M487 375c7-10 9-23 5-36l-79-259c-3-12-11-23-22-31-11-8-22-12-35-12l-263 0c-15 0-29 5-43 15-13 10-23 23-28 37-5 13-5 25-1 37 0 0 0 3 1 7 1 5 1 8 1 11 0 2 0 4-1 6 0 3-1 5-1 6 1 2 2 4 3 6 1 2 2 4 4 6 2 3 4 5 5 7 5 7 9 16 13 26 4 10 7 19 9 26 0 2 0 5 0 9-1 4-1 6 0 8 0 2 2 5 4 8 3 3 5 5 5 7 4 6 8 15 12 26 4 11 7 19 7 26 1 1 0 4 0 9-1 4-1 7 0 8 1 2 3 5 6 8 4 4 6 6 6 7 4 5 8 13 13 24 4 11 7 20 7 28 1 1 0 4 0 7-1 3-1 6-1 7 0 2 1 4 3 6 1 1 3 4 5 6 2 3 3 5 5 6 1 2 3 5 4 9 2 3 3 7 5 10 1 3 2 6 4 10 2 4 4 7 6 9 2 3 4 5 7 7 3 2 7 3 11 3 3 0 8 0 13-1l0-1c7 2 12 2 14 2l218 0c14 0 25-5 32-16 8-10 10-23 6-37l-79-259c-7-22-13-37-20-43-7-7-19-10-37-10l-248 0c-5 0-9-2-11-5-2-3-2-7 0-12 4-13 18-20 41-20l264 0c5 0 10 2 16 5 5 3 8 6 10 11l85 282c2 5 2 10 2 17 7-3 13-7 17-13z m-304 0c-1-3-1-5 0-7 1-1 3-2 6-2l174 0c2 0 4 1 7 2 2 2 4 4 5 7l6 18c0 3 0 5-1 7-1 1-3 2-6 2l-173 0c-3 0-5-1-8-2-2-2-4-4-4-7z m-24-73c-1-3-1-5 0-7 2-2 3-2 6-2l174 0c2 0 5 0 7 2 3 2 4 4 5 7l6 18c1 2 0 5-1 6-1 2-3 3-5 3l-174 0c-3 0-5-1-7-3-3-1-4-4-5-6z"/>
<glyph unicode="&#94;" d="M252 511c-55-1-106-15-143-36-6-4-8-5-9-8l-1-2 0-49 0-49-6-4c-12-7-26-16-37-26-7-5-22-20-27-26-14-18-23-36-26-54 0-6 0-21 0-27 2-8 5-19 9-27 3-6 11-17 19-25 30-29 68-40 110-31 20 4 38 12 55 23l1 1 0-1c-1-2-1-13-1-18 1-10 3-17 8-26 9-19 23-33 38-37l2-1 0-37-13 0c-15 0-16 0-19-3-7-6-5-16 3-19 2 0 5 0 16 0l13 0 0-10c0-11 0-12 3-15 0-1 1-2 2-3 2-1 3-1 6-1 4 0 4 0 6 1 1 1 2 2 3 3 2 3 2 4 2 15l0 10 14 0c15 0 16 0 19 2 0 1 2 2 2 3 3 5 2 10-2 14-3 3-4 3-19 3l-14 0 0 37 3 1c9 3 20 10 27 20 6 7 12 18 15 25 3 8 4 19 3 29 0 5-1 13-2 15 0 1 2 0 4-2 29-20 61-31 92-31 8 0 14 0 22 2 18 3 34 12 47 25 11 11 19 23 25 39 7 18 9 37 5 55-3 15-13 34-25 48-12 15-30 30-48 41-3 2-6 4-6 4l-1 0 0 53c0 40-1 53-1 54-1 2-4 5-9 8-7 4-22 12-30 15-30 12-63 19-99 22-9 0-28 1-36 0z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

View file

@ -401,6 +401,18 @@ h2{font-size:18px;padding:0 0 21px 5px;margin:45px 0 0 0;text-transform:uppercas
<div class="icon icon-diamond"></div>
<input type="text" readonly="readonly" value="diamond">
</li>
<li>
<div class="icon icon-hubot"></div>
<input type="text" readonly="readonly" value="hubot">
</li>
<li>
<div class="icon icon-book"></div>
<input type="text" readonly="readonly" value="book">
</li>
<li>
<div class="icon icon-invertedking"></div>
<input type="text" readonly="readonly" value="invertedking">
</li>
</ul>
<h2>Character mapping</h2>
<ul class="glyphs character-mapping">
@ -760,6 +772,18 @@ h2{font-size:18px;padding:0 0 21px 5px;margin:45px 0 0 0;text-transform:uppercas
<div data-icon="{" class="icon"></div>
<input type="text" readonly="readonly" value="{">
</li>
<li>
<div data-icon="@" class="icon"></div>
<input type="text" readonly="readonly" value="@">
</li>
<li>
<div data-icon="]" class="icon"></div>
<input type="text" readonly="readonly" value="]">
</li>
<li>
<div data-icon="^" class="icon"></div>
<input type="text" readonly="readonly" value="^">
</li>
</ul>
</div><script type="text/javascript">
(function() {

View file

@ -305,3 +305,12 @@
.icon-diamond:before {
content: "{";
}
.icon-hubot:before {
content: "@";
}
.icon-book:before {
content: "]";
}
.icon-invertedking:before {
content: "^";
}

View file

@ -70,8 +70,8 @@ time {
}
@font-face {
font-family: "lichess";
src: url("../font28/fonts/lichess.eot");
src: url("../font28/fonts/lichess.eot?#iefix") format("embedded-opentype"), url("../font28/fonts/lichess.woff") format("woff"), url("../font28/fonts/lichess.ttf") format("truetype"), url("../font28/fonts/lichess.svg#lichess") format("svg");
src: url("../font29/fonts/lichess.eot");
src: url("../font29/fonts/lichess.eot?#iefix") format("embedded-opentype"), url("../font29/fonts/lichess.woff") format("woff"), url("../font29/fonts/lichess.ttf") format("truetype"), url("../font29/fonts/lichess.svg#lichess") format("svg");
font-weight: normal;
font-style: normal;
}

View file

@ -155,9 +155,6 @@ body.dark #themepicker div.theme:hover {
body.dark #opening .meter .step {
border-color: #505050;
}
body.dark #opening .meter li.next .step {
box-shadow: 0 0 15px #000;
}
body.dark #opening .meter .step,
body.dark #hooks_wrap div.tabs a,
body.dark #top div.auth .links a:hover,

View file

@ -7,6 +7,27 @@
font-size: 1.3em;
margin-bottom: 10px;
}
#opening .right .box input {
width: 214px;
}
#opening .right h2 {
font-size: 1.4em;
margin-bottom: 7px;
}
#opening .right .continue_wrap {
width: 100%;
text-align: center;
margin-top: 20px;
}
#opening .right .continue {
display: inline-block;
white-space: nowrap;
font-size: 1.3em;
font-weight: bold;
}
#opening .right .loader {
font-size: 10px;
}
#opening .meter {
width: 494px;
height: 47px;
@ -21,7 +42,6 @@
padding: 0;
display: block;
height: 19px;
width: 99%;
border-radius: 10px;
background-color: #ddd;
position: relative;
@ -30,11 +50,10 @@
#opening .meter li {
float: left;
height: 19px;
font-weight: bold;
position: relative;
border-radius: 10px;
position: relative;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.25) inset;
transition: 2s;
transition: background-color 0.8s;
}
#opening .meter span {
right: -9px;
@ -50,6 +69,7 @@
font-size: 1.1em;
position: absolute;
background: #fff;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.25) inset;
border-radius: 50%;
white-space: nowrap;
z-index: 1;
@ -69,9 +89,6 @@
#opening .meter li.good .step {
box-shadow: 0 0 25px 10px #759900;
}
#opening .meter li.next .step {
box-shadow: 0 0 15px #888;
}
#opening .meter li.already .step {
background: #759900!important;
color: #fff!important;

View file

@ -1,4 +1,5 @@
var m = require('mithril');
var xhr = require('./xhr');
var chessground = require('chessground');
var Chess = require('chessli.js').Chess;
@ -29,7 +30,28 @@ module.exports = function(cfg, router, i18n) {
this.reload = function(data) {
this.data = data;
initialize();
if (this.data.play) {
initialize();
this.chessground.set({
fen: this.data.opening.fen,
orientation: this.data.opening.color,
lastMove: null,
turnColor: this.data.opening.color,
check: init.check,
movable: {
color: this.data.opening.color,
dests: init.dests
}
});
} else {
this.vm.loading = false;
this.chessground.cancelMove();
this.chessground.set({
movable: {
color: null
}
});
}
}.bind(this);
var onMove = function(orig, dest, meta) {
@ -41,13 +63,13 @@ module.exports = function(cfg, router, i18n) {
lastMove: null,
turnColor: this.data.opening.color,
check: init.check,
premovable: {
enabled: false
},
movable: {
dests: init.dests
}
});
if (this.vm.figuredOut.length >= this.data.opening.goal) {
xhr.attempt(this);
} else this.chessground.playPremove();
}.bind(this), 1000);
m.redraw();
}.bind(this);
@ -80,7 +102,7 @@ module.exports = function(cfg, router, i18n) {
san: chessMove.san
};
var known = this.data.opening.moves.filter(function(m) {
return m.first === move.uci;
return m.uci === move.uci;
})[0];
if (known && known.quality === 'good') {
var alreadyFound = this.vm.figuredOut.filter(function(f) {
@ -115,18 +137,21 @@ module.exports = function(cfg, router, i18n) {
}.bind(this), 1000);
}.bind(this);
this.notFiguredOut = function() {
return this.data.opening.moves.filter(function(m) {
return !this.vm.figuredOut.filter(function(fm) {
return fm.uci === m.uci;
}).length
}.bind(this));
}.bind(this);
this.router = router;
this.trans = function() {
var str = i18n[arguments[0]] || untranslated[arguments[0]] || arguments[0];
var str = i18n[arguments[0]] || arguments[0];
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
str = str.replace('%s', arg);
});
return str;
};
var untranslated = {
yourOpeningScoreX: 'Your opening score: %s',
findNbGoodMoves: 'Find %s good moves',
};
};

View file

@ -17,7 +17,7 @@ function renderPlayTable(ctrl) {
m('p', ctrl.trans('yourTurn'))
])
),
m('div.findit', m.trust(ctrl.trans('findNbGoodMoves', strong(ctrl.data.opening.goal)))),
m('div.findit', m.trust(ctrl.trans('findNbStrongMoves', strong(ctrl.data.opening.goal)))),
m('div.control',
ctrl.data.play ? m('a.button', {
onclick: partial(xhr.attempt, ctrl)
@ -29,6 +29,30 @@ 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('p', m.trust(ctrl.trans('scoreX', strong(ctrl.data.opening.score)))),
m('p', m.trust(ctrl.trans('playedXTimes', strong(ctrl.data.opening.attempts)))),
m('p',
m('input.copyable[readonly][spellCheck=false]', {
value: ctrl.data.opening.url
})
)
]),
m('div.continue_wrap',
m('button.continue.button.text[data-icon=G]', {
onclick: partial(xhr.newOpening, ctrl)
}, ctrl.trans('continueTraining'))
)
];
}
function renderUserInfos(ctrl) {
return m('div.chart_container', [
m('p', m.trust(ctrl.trans('yourOpeningScoreX', strong(ctrl.data.user.score)))),
@ -84,12 +108,17 @@ function renderSide(ctrl) {
function progress(ctrl) {
var steps = [];
var nbFiguredOut = ctrl.vm.figuredOut.length;
var figuredOut = ctrl.vm.figuredOut.slice(0);
var nbFiguredOut = figuredOut.length;
var lastI = nbFiguredOut - 1;
var nextI = nbFiguredOut;
if (!ctrl.data.play) figuredOut = figuredOut.concat(
ctrl.notFiguredOut().slice(0, ctrl.data.opening.goal - figuredOut.length)
);
for (var i = 0; i < ctrl.data.opening.goal; i++) {
steps.push({
found: ctrl.vm.figuredOut[i],
found: i < nbFiguredOut,
move: figuredOut[i],
last: lastI === i,
next: nextI === i
});
@ -97,23 +126,24 @@ function progress(ctrl) {
var liWidth = Math.round(100 / ctrl.data.opening.goal) + '%';
return m('div.meter', [
m('ul',
steps.map(function(step) {
steps.map(function(step, i) {
var badSan = (step.next && ctrl.vm.flash.bad) ? ctrl.vm.flash.bad.san : null;
var dubiousSan = (step.next && ctrl.vm.flash.dubious) ? ctrl.vm.flash.dubious.san : null;
return m('li', {
key: i,
class: classSet({
found: step.found,
next: step.next,
bad: badSan,
dubious: dubiousSan,
good: step.last && ctrl.vm.flash.good,
already: step.found && ctrl.vm.flashFound && ctrl.vm.flashFound.uci === step.found.uci
already: step.move && ctrl.vm.flashFound && ctrl.vm.flashFound.uci === step.move.uci
}),
style: {
width: liWidth
}
}, [
m('span.step', step.found ? step.found.san : (
m('span.step', step.move ? step.move.san : (
badSan || dubiousSan || '?'
)),
m('span.stage')
@ -130,7 +160,9 @@ module.exports = function(ctrl) {
renderSide(ctrl),
m('div.board_and_ground', [
m('div', chessground.view(ctrl.chessground)),
m('div.right', ctrl.vm.loading ? loading : renderPlayTable(ctrl))
m('div.right', ctrl.vm.loading ? loading : (
ctrl.data.play ? renderPlayTable(ctrl) : renderViewTable(ctrl)
))
]),
m('div.center', [
progress(ctrl)

View file

@ -18,7 +18,7 @@ module.exports = {
showLoading(ctrl);
m.request({
method: 'POST',
url: ctrl.data.opening.url,
url: '/training/opening/' + ctrl.data.opening.id,
data: {
found: ctrl.vm.figuredOut.length,
failed: ctrl.vm.messedUp.length