rewrite puzzle history - WIP

This commit is contained in:
Thibault Duplessis 2016-09-29 18:52:20 +02:00
parent 47861bc852
commit 67a9b06589
9 changed files with 67 additions and 130 deletions

View file

@ -37,7 +37,7 @@ object Puzzle extends LilaController {
}
def home = Open { implicit ctx =>
selectPuzzle(ctx.me) flatMap { puzzle =>
selectPuzzle(ctx.me) flatMap { puzzle =>
renderShow(puzzle, ctx.isAuth.fold("play", "try")) map { Ok(_) }
}
}
@ -59,22 +59,6 @@ object Puzzle extends LilaController {
infos => JsData(puzzle, infos, "play", animationDuration = env.AnimationDuration)
}
def history = Auth { implicit ctx =>
me =>
env userInfos me flatMap { ui =>
negotiate(
html = XhrOnly {
fuccess { Ok(views.html.puzzle.history(ui)) }
},
api = _ => fuccess {
Ok(JsData history ui)
}
)
}
}
private val noMorePuzzleJson = jsonError("No more puzzles for you!")
// XHR load next play puzzle
def newPuzzle = Open { implicit ctx =>
XhrOnly {
@ -84,22 +68,21 @@ object Puzzle extends LilaController {
}
}
def difficulty = AuthBody { implicit ctx =>
me =>
implicit val req = ctx.body
env.forms.difficulty.bindFromRequest.fold(
err => fuccess(BadRequest(errorsAsJson(err))),
value => Env.pref.api.setPref(
me,
(p: lila.pref.Pref) => p.copy(puzzleDifficulty = value),
notifyChange = false) >> {
reqToCtx(ctx.req) flatMap { newCtx =>
selectPuzzle(newCtx.me) zip env.userInfos(newCtx.me) map {
case (puzzle, infos) => Ok(JsData(puzzle, infos, ctx.isAuth.fold("play", "try"), animationDuration = env.AnimationDuration)(newCtx))
}
def difficulty = AuthBody { implicit ctx => me =>
implicit val req = ctx.body
env.forms.difficulty.bindFromRequest.fold(
err => fuccess(BadRequest(errorsAsJson(err))),
value => Env.pref.api.setPref(
me,
(p: lila.pref.Pref) => p.copy(puzzleDifficulty = value),
notifyChange = false) >> {
reqToCtx(ctx.req) flatMap { newCtx =>
selectPuzzle(newCtx.me) zip env.userInfos(newCtx.me) map {
case (puzzle, infos) => Ok(JsData(puzzle, infos, ctx.isAuth.fold("play", "try"), animationDuration = env.AnimationDuration)(newCtx))
}
}
) map (_ as JSON)
}
) map (_ as JSON)
}
private def selectPuzzle(user: Option[UserModel]) =
@ -125,9 +108,9 @@ object Puzzle extends LilaController {
case ((p2, infos), vote) => Ok {
JsData(p2 | puzzle, infos, "view",
voted = (vote match {
case Some(_) => true
case _ => false
}) some,
case Some(_) => true
case _ => false
}) some,
round = newAttempt.some,
animationDuration = env.AnimationDuration)
}
@ -151,20 +134,19 @@ object Puzzle extends LilaController {
}
}
def vote(id: PuzzleId) = AuthBody { implicit ctx =>
me =>
implicit val req = ctx.body
env.forms.vote.bindFromRequest.fold(
err => fuccess(BadRequest(errorsAsJson(err))),
vote => env.api.vote.find(id, me) flatMap {
v => env.api.vote.update(id, me, v, vote == 1)
} map {
case (p, a) =>
if (vote == 1) lila.mon.puzzle.vote.up()
else lila.mon.puzzle.vote.down()
Ok(play.api.libs.json.Json.arr(a.vote, p.vote.sum))
}
) map (_ as JSON)
def vote(id: PuzzleId) = AuthBody { implicit ctx => me =>
implicit val req = ctx.body
env.forms.vote.bindFromRequest.fold(
err => fuccess(BadRequest(errorsAsJson(err))),
vote => env.api.vote.find(id, me) flatMap {
v => env.api.vote.update(id, me, v, vote == 1)
} map {
case (p, a) =>
if (vote == 1) lila.mon.puzzle.vote.up()
else lila.mon.puzzle.vote.down()
Ok(play.api.libs.json.Json.arr(a.vote, p.vote.sum))
}
) map (_ as JSON)
}
def recentGame = Action.async { req =>

View file

@ -10,19 +10,9 @@ import lila.puzzle._
object JsData extends lila.Steroids {
def history(infos: UserInfos) = Json.obj(
"attempts" -> infos.history.map { a =>
Json.obj(
"puzzleId" -> a.puzzleId,
"date" -> a.date,
"win" -> a.win,
"rating" -> a.rating,
"ratingDiff" -> a.ratingDiff)
})
def apply(
puzzle: Puzzle,
userInfos: Option[lila.puzzle.UserInfos],
userInfos: Option[UserInfos],
mode: String,
animationDuration: scala.concurrent.duration.Duration,
round: Option[Round] = None,
@ -75,7 +65,9 @@ object JsData extends lila.Steroids {
"user" -> userInfos.map { i =>
Json.obj(
"rating" -> i.user.perfs.puzzle.intRating,
"history" -> i.history.nonEmpty.option(Json.toJson(i.chart))
"history" -> i.history.map { r =>
Json.arr(r.puzzleId, r.ratingDiff)
}
)
},
"difficulty" -> ctx.isAuth.option {

View file

@ -1,29 +0,0 @@
@(userInfos: lila.puzzle.UserInfos)(implicit ctx: Context)
<div class="undertable">
<div class="undertable_top">
<span class="title">@trans.recentlyPlayedPuzzles()</span>
</div>
<div class="undertable_inner scroll-shadow-soft">
<div class="content">
<table>
<tbody>
@userInfos.history.map { attempt =>
<tr>
<td>
<a class="user_link ulpt" href="@routes.User.show(userInfos.user.username)">
@userInfos.user.username (@attempt.rating) @showRatingDiff(attempt.ratingDiff)
</a>
</td>
<td>
<a class="user_link" href="@routes.Puzzle.show(attempt.puzzleId)">
@trans.puzzleId(attempt.puzzleId)
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>

View file

@ -104,7 +104,6 @@ POST /training/coordinate/color controllers.Coordinate.color
GET /training controllers.Puzzle.home
POST /training/difficulty controllers.Puzzle.difficulty
GET /training/new controllers.Puzzle.newPuzzle
GET /training/history controllers.Puzzle.history
GET /training/daily controllers.Puzzle.daily
GET /training/embed controllers.Puzzle.embed
GET /training/frame controllers.Puzzle.frame

View file

@ -271,7 +271,6 @@ findTheBestMoveForWhite=Find the best move for white.
findTheBestMoveForBlack=Find the best move for black.
toTrackYourProgress=To track your progress:
trainingSignupExplanation=Lichess will provide puzzles that match your ability, making for better training sessions.
recentlyPlayedPuzzles=Recently played puzzles
puzzleId=Puzzle %s
puzzleOfTheDay=Puzzle of the day
clickToSolve=Click to solve

View file

@ -19,10 +19,12 @@ case class Round(
object Round {
case class Mini(puzzleId: Int, ratingDiff: Int)
object BSONFields {
val puzzleId = "p"
val userId = "u"
val date = "d"
val date = "a"
val win = "w"
val rating = "r"
val ratingDiff = "d"
@ -30,8 +32,9 @@ object Round {
import reactivemongo.bson._
import lila.db.BSON
import lila.db.dsl._
import BSON.BSONJodaDateTimeHandler
implicit val roundBSONHandler = new BSON[Round] {
implicit val RoundBSONHandler = new BSON[Round] {
import BSONFields._
@ -51,4 +54,11 @@ object Round {
rating -> w.int(o.rating),
ratingDiff -> w.int(o.ratingDiff))
}
private[puzzle] implicit val RoundMiniBSONReader = new BSONDocumentReader[Mini] {
import BSONFields._
def read(doc: Bdoc): Mini = Mini(
puzzleId = doc.getAs[Int](puzzleId) err "RoundMini no puzzleId",
ratingDiff = doc.getAs[Int](ratingDiff) err "RoundMini no ratingDiff")
}
}

View file

@ -1,52 +1,37 @@
package lila.puzzle
import play.api.libs.json._
import reactivemongo.bson._
import lila.db.dsl._
import lila.rating.Glicko
import lila.user.User
case class UserInfos(user: User, history: List[Round], chart: JsArray)
case class UserInfos(user: User, history: List[Round.Mini])
object UserInfos {
private def historySize = 20
private def historySize = 12
private def chartSize = 12
import Round.roundBSONHandler
lazy val defaultChart = JsArray {
List.fill(chartSize)(Glicko.default.intRating) map { JsNumber(_) }
}
import Round.RoundMiniBSONReader
def apply(roundColl: Coll) = new {
def apply(user: User): Fu[UserInfos] = fetchRounds(user.id) map { rounds =>
new UserInfos(user, makeHistory(rounds), makeChart(rounds))
} recover {
case e: Exception =>
logger.error("user infos", e)
new UserInfos(user, Nil, JsArray())
def apply(user: User): Fu[UserInfos] = fetchRoundMinis(user.id) map {
new UserInfos(user, _)
}
def apply(user: Option[User]): Fu[Option[UserInfos]] =
user ?? { apply(_) map (_.some) }
private def fetchRounds(userId: String): Fu[List[Round]] =
roundColl.find(BSONDocument(
Round.BSONFields.userId -> userId
)).sort(BSONDocument(
Round.BSONFields.date -> -1
)).cursor[Round]()
.gather[List](math.max(historySize, chartSize))
}
private def makeHistory(rounds: List[Round]) = rounds.take(historySize)
private def makeChart(rounds: List[Round]) = JsArray {
val ratings = rounds.take(chartSize).reverse map (_.userPostRating)
val filled = List.fill(chartSize - ratings.size)(Glicko.default.intRating) ::: ratings
filled map { JsNumber(_) }
private def fetchRoundMinis(userId: String): Fu[List[Round.Mini]] =
roundColl.find(
$doc(Round.BSONFields.userId -> userId),
$doc(
"_id" -> false,
Round.BSONFields.puzzleId -> true,
Round.BSONFields.ratingDiff -> true
)).sort($sort desc Round.BSONFields.date)
.cursor[Round.Mini]()
.gather[List](historySize atLeast chartSize)
}
}

View file

@ -17,6 +17,8 @@ module.exports = function(cfg, router, i18n) {
this.data = data(cfg);
console.log(this.data);
var userMove = function(orig, dest) {
var res = puzzle.tryMove(this.data, [orig, dest]);
var newProgress = res[0];

View file

@ -272,15 +272,12 @@ function renderFooter(ctrl) {
function renderHistory(ctrl) {
return m('div.history', {
config: function(el, isUpdate, context) {
var hash = ctrl.data.user.history.join('');
var hash = ctrl.data.user.history.reduce(function(h, r) {
return h + r.puzzleId;
}, '');
if (hash == context.hash) return;
context.hash = hash;
$.ajax({
url: '/training/history',
success: function(html) {
el.innerHTML = html;
}
});
el.innerHTML = JSON.stringify(ctrl.data.user.history);
}
});
}