store practice progress

This commit is contained in:
Thibault Duplessis 2017-01-22 11:52:18 +01:00
parent 0eb0f1fb39
commit 6baae906ed
11 changed files with 77 additions and 31 deletions

View file

@ -44,6 +44,10 @@ object Practice extends LilaController {
} map NoCache
}
def complete(chapterId: String, nbMoves: Int) = Auth { implicit ctx => me =>
env.api.progress.setNbMoves(me, chapterId, lila.practice.PracticeProgress.NbMoves(nbMoves))
}
def config = Auth { implicit ctx => me =>
for {
struct <- env.api.structure.get

View file

@ -158,10 +158,11 @@ POST /patron/ipn controllers.Plan.payPalIpn
GET /features controllers.Plan.features
# Practice
GET /practice controllers.Practice.index
GET /practice/:sectionId/:studySlug/:studyId controllers.Practice.show(sectionId: String, studySlug: String, studyId: String)
GET /practice/config controllers.Practice.config
POST /practice/config controllers.Practice.configSave
GET /practice controllers.Practice.index
GET /practice/:sectionId/:studySlug/:studyId controllers.Practice.show(sectionId: String, studySlug: String, studyId: String)
POST /practice/complete/:chapterId/:moves controllers.Practice.complete(chapterId: String, moves: Int)
GET /practice/config controllers.Practice.config
POST /practice/config controllers.Practice.configSave
# Round
GET /$gameId<\w{8}> controllers.Round.watcher(gameId: String, color: String = "white")

View file

@ -49,13 +49,15 @@ final class PracticeApi(
object progress {
import PracticeProgress.NbMoves
def get(user: User): Fu[PracticeProgress] =
coll.uno[PracticeProgress]($id(user.id)) map { _ | PracticeProgress.empty(PracticeProgress.Id(user.id)) }
private def save(p: PracticeProgress): Funit =
coll.update($id(p.id), p, upsert = true).void
def setNbMoves(user: User, chapterId: Chapter.Id, score: PracticeProgress.NbMoves) =
def setNbMoves(user: User, chapterId: Chapter.Id, score: NbMoves) =
get(user) flatMap { prog =>
save(prog.withNbMoves(chapterId, score))
}

View file

@ -10,13 +10,17 @@ case class PracticeProgress(
createdAt: DateTime,
updatedAt: DateTime) {
import PracticeProgress.NbMoves
def id = _id
def apply(chapterId: Chapter.Id): Option[PracticeProgress.NbMoves] =
def apply(chapterId: Chapter.Id): Option[NbMoves] =
chapters get chapterId
def withNbMoves(chapterId: Chapter.Id, nbMoves: PracticeProgress.NbMoves) = copy(
chapters = chapters - chapterId + (chapterId -> nbMoves),
chapters = chapters - chapterId + {
chapterId -> NbMoves(math.min(chapters.get(chapterId).fold(999)(_.value), nbMoves.value))
},
updatedAt = DateTime.now)
def countDone(chapterIds: List[Chapter.Id]): Int =

View file

@ -115,6 +115,10 @@ module.exports = function(opts) {
return this.data.orientation;
}.bind(this);
this.turnColor = function() {
return this.vm.node.ply % 2 === 0 ? 'white' : 'black';
}.bind(this);
this.togglePlay = function(delay) {
this.autoplay.toggle(delay);
this.actionMenu.open = false;
@ -130,7 +134,7 @@ module.exports = function(opts) {
var showGround = function() {
var node = this.vm.node;
var color = node.ply % 2 === 0 ? 'white' : 'black';
var color = this.turnColor();
var dests = readDests(node.dests);
var drops = readDrops(node.drops);
var movableColor = this.practice ? this.bottomColor() : (
@ -227,6 +231,7 @@ module.exports = function(opts) {
if (pathChanged) {
if (this.retro) this.retro.onJump();
if (this.practice) this.practice.onJump();
if (this.study) this.study.onJump();
}
if (this.music) this.music.jump(this.vm.node);
}.bind(this);

View file

@ -1,7 +1,7 @@
var ctrl = require('./ctrl');
var view = require('./view');
var studyView = require('./study/studyView');
var practiceView = require('./study/practiceView');
var studyPractice = require('./study/studyPractice');
var legacy = require('./legacy');
var m = require('mithril');
@ -22,7 +22,7 @@ module.exports = {
m.redraw.strategy("diff"); // prevents double full redraw on page load
return controller.study;
},
view: controller.study.practice ? practiceView.main : studyView.main
view: controller.study.practice ? studyPractice.view : studyView.main
});
return {

View file

@ -56,12 +56,8 @@ module.exports = function(root) {
};
}
var turnColor = function() {
return root.vm.node.ply % 2 === 0 ? 'white' : 'black';
};
var isMyTurn = function() {
return turnColor() === root.bottomColor();
return root.turnColor() === root.bottomColor();
};
var checkCeval = function() {
@ -102,7 +98,6 @@ module.exports = function(root) {
// because running(false) is called after the jump
setTimeout(checkCeval, 50)
},
turnColor: turnColor,
isMyTurn: isMyTurn,
comment: comment,
running: running,

View file

@ -61,8 +61,8 @@ function renderOffTrack(ctrl) {
];
}
function renderEnd(ctrl, end) {
var color = ctrl.turnColor();
function renderEnd(root, end) {
var color = root.turnColor();
if (end === 'checkmate') color = opposite(color);
return m('div.player', [
color ? m('div.no-square', m('piece.king.' + color)) : m('div.icon.off', '!'),
@ -92,7 +92,7 @@ function renderRunning(root) {
var ctrl = root.practice;
var hint = ctrl.hinting();
return m('div.player', [
m('div.no-square', m('piece.king.' + ctrl.turnColor())),
m('div.no-square', m('piece.king.' + root.turnColor())),
m('div.instruction', [
ctrl.isMyTurn() ? m('strong', 'Your move') : [
m('strong', 'Computer thinking...'),
@ -117,7 +117,7 @@ module.exports = function(root) {
class: 'practice_box ' + (comment ? comment.verdict : '')
}, [
renderTitle(root.togglePractice),
m('div.feedback', !running ? renderOffTrack(ctrl) : (end ? renderEnd(ctrl, end) : renderRunning(root))),
m('div.feedback', !running ? renderOffTrack(ctrl) : (end ? renderEnd(root, end) : renderRunning(root))),
ctrl.running() ? m('div.comment', comment ? [
m('span.verdict', commentText[comment.verdict]),
' ',

View file

@ -3,6 +3,7 @@ var partial = require('chessground').util.partial;
var throttle = require('common').throttle;
var memberCtrl = require('./studyMembers').ctrl;
var chapterCtrl = require('./studyChapters').ctrl;
var practiceCtrl = require('./studyPractice').ctrl;
var commentFormCtrl = require('./commentForm').ctrl;
var glyphFormCtrl = require('./studyGlyph').ctrl;
var studyFormCtrl = require('./studyForm').ctrl;
@ -14,7 +15,7 @@ var xhr = require('./studyXhr');
// data.position.path represents the server state
// ctrl.vm.path is the client state
module.exports = function(data, ctrl, tagTypes, practice) {
module.exports = function(data, ctrl, tagTypes, practiceData) {
var send = ctrl.socket.send;
@ -26,12 +27,14 @@ module.exports = function(data, ctrl, tagTypes, practice) {
loading: false,
nextChapterId: false,
tab: m.prop(data.chapters.length > 1 ? 'chapters' : 'members'),
behind: (isManualChapter || practice) ? 0 : false, // false if syncing, else incremental number of missed event
behind: (isManualChapter || practiceData) ? 0 : false, // false if syncing, else incremental number of missed event
catchingUp: false, // was behind, is syncing back
chapterId: isManualChapter ? data.chapter.id : null // only useful when not synchronized
};
})();
var practice = practiceData && practiceCtrl(ctrl, practiceData);
var notif = notifCtrl();
var form = studyFormCtrl(function(data, isNew) {
send("editStudy", data);
@ -204,6 +207,7 @@ module.exports = function(data, ctrl, tagTypes, practice) {
ctrl.tree.lastMainlineNode(path).ply <= data.chapter.conceal
);
},
onJump: practice ? practice.onJump : function() {},
withPosition: function(obj) {
obj.chapterId = currentChapterId();
obj.path = ctrl.vm.path;

View file

@ -1,9 +1,10 @@
var m = require('mithril');
var classSet = require('common').classSet;
var xhr = require('./studyXhr');
var firstRender = true;
function selector(practice) {
function selector(data) {
if (!firstRender && m.redraw.strategy() === 'diff') return {
subtree: 'retain'
};
@ -14,7 +15,7 @@ function selector(practice) {
}
}, [
m('option[disabled][selected]', 'Practice list'),
practice.structure.map(function(section) {
data.structure.map(function(section) {
return m('optgroup', {
label: section.name
}, section.studies.map(function(study) {
@ -28,17 +29,38 @@ function selector(practice) {
module.exports = {
main: function(ctrl) {
ctrl: function(root, data) {
var complete = function(chapterId, nbMoves) {
xhr.practiceComplete(chapterId, nbMoves);
data.completion[chapterId] = Math.min(data.completion[chapterId] || 999, nbMoves);
};
var isVictory = function() {
return root.gameOver() === 'checkmate' && root.turnColor() !== root.bottomColor();
};
var onJump = function() {
if (isVictory()) complete(root.study.currentChapter().id, root.vm.node.ply);
};
return {
onJump: onJump,
data: data
}
},
view: function(ctrl) {
var current = ctrl.currentChapter();
var practice = ctrl.practice;
var data = ctrl.practice.data;
return [
m('div.title', [
m('i.practice.icon.' + practice.study.id),
m('i.practice.icon.' + data.study.id),
m('div.text', [
m('h1', practice.study.name),
m('em', practice.study.desc)
m('h1', data.study.name),
m('em', data.study.desc)
])
]),
m('div', {
@ -66,7 +88,7 @@ module.exports = {
}, [
m('span', {
'data-icon': 'E',
class: 'status ' + (practice.completion[chapter.id] ? 'done' : 'ongoing')
class: 'status ' + (data.completion[chapter.id] ? 'done' : 'ongoing')
}),
m('h3', chapter.name)
])
@ -79,7 +101,7 @@ module.exports = {
href: '/practice',
title: 'More practice'
}),
selector(ctrl.practice)
selector(data)
])
];
}

View file

@ -47,5 +47,14 @@ module.exports = {
config: xhrConfig,
background: true
});
},
practiceComplete: function(chapterId, nbMoves) {
return m.request({
method: 'POST',
url: ['/practice/complete', chapterId, nbMoves].join('/'),
config: xhrConfig,
background: true
});
}
};