puzzle WIP
parent
400b8d23c5
commit
11e3bde24e
|
@ -97,7 +97,6 @@ final class Puzzle(
|
||||||
def round3(id: String) =
|
def round3(id: String) =
|
||||||
OpenBody { implicit ctx =>
|
OpenBody { implicit ctx =>
|
||||||
NoBot {
|
NoBot {
|
||||||
fuccess(Ok(Json.obj()))
|
|
||||||
implicit val req = ctx.body
|
implicit val req = ctx.body
|
||||||
OptionFuResult(env.puzzle.api.puzzle find Puz.Id(id)) { puzzle =>
|
OptionFuResult(env.puzzle.api.puzzle find Puz.Id(id)) { puzzle =>
|
||||||
lila.mon.puzzle.round.attempt(ctx.isAuth).increment()
|
lila.mon.puzzle.round.attempt(ctx.isAuth).increment()
|
||||||
|
@ -110,24 +109,28 @@ final class Puzzle(
|
||||||
case Some(me) =>
|
case Some(me) =>
|
||||||
for {
|
for {
|
||||||
isStudent <- env.clas.api.student.isStudent(me.id)
|
isStudent <- env.clas.api.student.isStudent(me.id)
|
||||||
round <- env.puzzle.finisher(
|
(round, perf) <- env.puzzle.finisher(
|
||||||
puzzle = puzzle,
|
puzzle = puzzle,
|
||||||
user = me,
|
user = me,
|
||||||
result = Result(resultInt == 1),
|
result = Result(resultInt == 1),
|
||||||
isStudent = isStudent
|
isStudent = isStudent
|
||||||
)
|
)
|
||||||
_ = env.puzzle.cursor.onRound(round)
|
|
||||||
me2 <- env.user.repo.byId(me.id).dmap(_ | me)
|
|
||||||
// infos <- env.puzzle userInfos me2
|
|
||||||
} yield Ok(
|
} yield Ok(
|
||||||
Json.obj(
|
Json.obj(
|
||||||
"user" -> env.puzzle.jsonView.userJson(me)
|
"perf" -> Json
|
||||||
// "round" -> lila.puzzle.JsonView.round(round)
|
.obj("rating" -> perf.intRating)
|
||||||
|
.add("provisional" -> perf.provisional),
|
||||||
|
"round" -> Json
|
||||||
|
.obj(
|
||||||
|
"win" -> round.win,
|
||||||
|
"ratingDiff" -> (me.perfs.puzzle.intRating - perf.intRating)
|
||||||
|
)
|
||||||
|
.add("vote" -> round.vote)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case None =>
|
case None =>
|
||||||
env.puzzle.finisher.incPuzzlePlays(puzzle)
|
env.puzzle.finisher.incPuzzlePlays(puzzle)
|
||||||
Ok(Json.obj("user" -> false)).fuccess
|
fuccess(NoContent)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.dmap(_ as JSON)
|
.dmap(_ as JSON)
|
||||||
|
@ -138,21 +141,18 @@ final class Puzzle(
|
||||||
def vote(id: String) =
|
def vote(id: String) =
|
||||||
AuthBody { implicit ctx => me =>
|
AuthBody { implicit ctx => me =>
|
||||||
NoBot {
|
NoBot {
|
||||||
???
|
implicit val req = ctx.body
|
||||||
// implicit val req = ctx.body
|
env.puzzle.forms.vote
|
||||||
// env.puzzle.forms.vote
|
.bindFromRequest()
|
||||||
// .bindFromRequest()
|
.fold(
|
||||||
// .fold(
|
jsonFormError,
|
||||||
// jsonFormError,
|
vote =>
|
||||||
// vote =>
|
env.puzzle.api.vote.update(Puz.Id(id), me, vote == 1) map { newVote =>
|
||||||
// env.puzzle.api.vote.find(id, me) flatMap { v =>
|
if (vote == 1) lila.mon.puzzle.vote.up.increment()
|
||||||
// env.puzzle.api.vote.update(id, me, v, vote == 1)
|
else lila.mon.puzzle.vote.down.increment()
|
||||||
// } map { case (p, a) =>
|
jsonOkResult
|
||||||
// if (vote == 1) lila.mon.puzzle.vote.up.increment()
|
}
|
||||||
// else lila.mon.puzzle.vote.down.increment()
|
)
|
||||||
// Ok(Json.arr(a.value, p.vote.sum))
|
|
||||||
// }
|
|
||||||
// ) map (_ as JSON)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ package lila.puzzle
|
||||||
|
|
||||||
import org.goochjs.glicko2.{ Rating, RatingCalculator, RatingPeriodResults }
|
import org.goochjs.glicko2.{ Rating, RatingCalculator, RatingPeriodResults }
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
import scala.util.chaining._
|
||||||
|
|
||||||
import lila.common.Bus
|
import lila.common.Bus
|
||||||
import lila.db.AsyncColl
|
import lila.db.AsyncColl
|
||||||
import lila.db.dsl._
|
import lila.db.dsl._
|
||||||
import lila.rating.{ Glicko, PerfType }
|
import lila.rating.{ Glicko, PerfType }
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
|
import lila.rating.Perf
|
||||||
|
|
||||||
final private[puzzle] class Finisher(
|
final private[puzzle] class Finisher(
|
||||||
api: PuzzleApi,
|
api: PuzzleApi,
|
||||||
|
@ -16,15 +18,32 @@ final private[puzzle] class Finisher(
|
||||||
colls: PuzzleColls
|
colls: PuzzleColls
|
||||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||||
|
|
||||||
def apply(puzzle: Puzzle, user: User, result: Result, isStudent: Boolean): Fu[PuzzleRound] =
|
import BsonHandlers._
|
||||||
|
|
||||||
|
def apply(puzzle: Puzzle, user: User, result: Result, isStudent: Boolean): Fu[(PuzzleRound, Perf)] =
|
||||||
api.round.find(user, puzzle) flatMap { prevRound =>
|
api.round.find(user, puzzle) flatMap { prevRound =>
|
||||||
val now = DateTime.now
|
val now = DateTime.now
|
||||||
val formerUserRating = user.perfs.puzzle.intRating
|
val formerUserRating = user.perfs.puzzle.intRating
|
||||||
val userRating = user.perfs.puzzle.toRating
|
val userRating = user.perfs.puzzle.toRating
|
||||||
// val puzzleRating = puzzle.perf.toRating
|
val puzzleRating = new Rating(
|
||||||
// updateRatings(userRating, puzzleRating, result.glicko)
|
puzzle.glicko.rating atLeast Glicko.minRating,
|
||||||
// val puzzlePerf =
|
puzzle.glicko.deviation,
|
||||||
// puzzle.perf.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id} user")(puzzleRating)
|
puzzle.glicko.volatility,
|
||||||
|
puzzle.plays,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
updateRatings(userRating, puzzleRating, result.glicko)
|
||||||
|
val newPuzzleGlicko = user.perfs.puzzle.established
|
||||||
|
.option {
|
||||||
|
Glicko(
|
||||||
|
rating = puzzleRating.getRating
|
||||||
|
.atMost(puzzle.glicko.rating + Glicko.maxRatingDelta)
|
||||||
|
.atLeast(puzzle.glicko.rating - Glicko.maxRatingDelta),
|
||||||
|
deviation = puzzleRating.getRatingDeviation,
|
||||||
|
volatility = puzzleRating.getVolatility
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.filter(_.sanityCheck)
|
||||||
val userPerf =
|
val userPerf =
|
||||||
user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, now)
|
user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, now)
|
||||||
val round = prevRound
|
val round = prevRound
|
||||||
|
@ -37,24 +56,25 @@ final private[puzzle] class Finisher(
|
||||||
weight = none
|
weight = none
|
||||||
)
|
)
|
||||||
)(_.copy(win = result.win))
|
)(_.copy(win = result.win))
|
||||||
// historyApi.addPuzzle(user = user, completedAt = date, perf = userPerf)
|
api.round.upsert(round) zip
|
||||||
api.round.upsert(round) >> {
|
isStudent.??(api.round.addDenormalizedUser(round, user)) zip
|
||||||
isStudent ?? api.round.addDenormalizedUser(round, user)
|
prevRound.isEmpty.?? {
|
||||||
// } >> {
|
colls.puzzle {
|
||||||
// puzzleColl {
|
_.update
|
||||||
// _.update.one(
|
.one(
|
||||||
// $id(puzzle.id),
|
$id(puzzle.id),
|
||||||
// $inc(Puzzle.BSONFields.attempts -> $int(1)) ++
|
$inc(Puzzle.BSONFields.plays -> $int(1)) ++ newPuzzleGlicko ?? { glicko =>
|
||||||
// $set(Puzzle.BSONFields.perf -> PuzzlePerf.puzzlePerfBSONHandler.write(puzzlePerf))
|
$set(Puzzle.BSONFields.glicko -> Glicko.glickoBSONHandler.write(glicko))
|
||||||
// )
|
}
|
||||||
// } zip userRepo.setPerf(user.id, PerfType.Puzzle, userPerf)
|
)
|
||||||
} inject {
|
.void
|
||||||
|
}
|
||||||
|
} zip
|
||||||
|
userRepo.setPerf(user.id, PerfType.Puzzle, userPerf) >>-
|
||||||
Bus.publish(
|
Bus.publish(
|
||||||
Puzzle.UserResult(puzzle.id, user.id, result, formerUserRating -> userPerf.intRating),
|
Puzzle.UserResult(puzzle.id, user.id, result, formerUserRating -> userPerf.intRating),
|
||||||
"finishPuzzle"
|
"finishPuzzle"
|
||||||
)
|
) inject (round -> userPerf)
|
||||||
round
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val VOLATILITY = Glicko.default.volatility
|
private val VOLATILITY = Glicko.default.volatility
|
||||||
|
@ -62,7 +82,7 @@ final private[puzzle] class Finisher(
|
||||||
private val system = new RatingCalculator(VOLATILITY, TAU)
|
private val system = new RatingCalculator(VOLATILITY, TAU)
|
||||||
|
|
||||||
def incPuzzlePlays(puzzle: Puzzle): Funit =
|
def incPuzzlePlays(puzzle: Puzzle): Funit =
|
||||||
colls.puzzle.map(_.incFieldUnchecked($id(puzzle.id.value), Puzzle.BSONFields.plays))
|
colls.puzzle.map(_.incFieldUnchecked($id(puzzle.id), Puzzle.BSONFields.plays))
|
||||||
|
|
||||||
private def updateRatings(u1: Rating, u2: Rating, result: Glicko.Result): Unit = {
|
private def updateRatings(u1: Rating, u2: Rating, result: Glicko.Result): Unit = {
|
||||||
val results = new RatingPeriodResults()
|
val results = new RatingPeriodResults()
|
||||||
|
|
|
@ -35,10 +35,14 @@ final class JsonView(
|
||||||
}
|
}
|
||||||
|
|
||||||
def userJson(u: User) =
|
def userJson(u: User) =
|
||||||
Json.obj(
|
Json
|
||||||
"rating" -> u.perfs.puzzle.intRating,
|
.obj(
|
||||||
"recent" -> JsArray()
|
"rating" -> u.perfs.puzzle.intRating,
|
||||||
)
|
"recent" -> JsArray()
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
"provisional" -> u.perfs.puzzle.provisional
|
||||||
|
)
|
||||||
|
|
||||||
def pref(p: lila.pref.Pref) =
|
def pref(p: lila.pref.Pref) =
|
||||||
Json.obj(
|
Json.obj(
|
||||||
|
@ -55,16 +59,14 @@ final class JsonView(
|
||||||
"is3d" -> p.is3d
|
"is3d" -> p.is3d
|
||||||
)
|
)
|
||||||
|
|
||||||
private def puzzleJson(puzzle: Puzzle): JsObject =
|
private def puzzleJson(puzzle: Puzzle): JsObject = Json.obj(
|
||||||
Json
|
"id" -> puzzle.id,
|
||||||
.obj(
|
"rating" -> puzzle.glicko.intRating,
|
||||||
"id" -> puzzle.id,
|
"plays" -> puzzle.plays,
|
||||||
"rating" -> puzzle.glicko.intRating,
|
"initialPly" -> puzzle.initialPly,
|
||||||
"plays" -> puzzle.plays,
|
"solution" -> puzzle.line.tail.map(_.uci),
|
||||||
"initialPly" -> puzzle.initialPly,
|
"vote" -> puzzle.vote
|
||||||
"solution" -> puzzle.line.tail.map(_.uci),
|
)
|
||||||
"vote" -> puzzle.vote
|
|
||||||
)
|
|
||||||
|
|
||||||
private def makeSolution(puzzle: Puzzle): Option[tree.Branch] = {
|
private def makeSolution(puzzle: Puzzle): Option[tree.Branch] = {
|
||||||
import chess.format._
|
import chess.format._
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package lila.puzzle
|
package lila.puzzle
|
||||||
|
|
||||||
import Puzzle.{ BSONFields => F }
|
import cats.implicits._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.db.AsyncColl
|
import lila.db.AsyncColl
|
||||||
|
@ -12,7 +12,7 @@ final private[puzzle] class PuzzleApi(
|
||||||
colls: PuzzleColls
|
colls: PuzzleColls
|
||||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||||
|
|
||||||
import Puzzle.BSONFields._
|
import Puzzle.{ BSONFields => F }
|
||||||
import BsonHandlers._
|
import BsonHandlers._
|
||||||
|
|
||||||
object puzzle {
|
object puzzle {
|
||||||
|
@ -36,46 +36,61 @@ final private[puzzle] class PuzzleApi(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// object vote {
|
object vote {
|
||||||
|
|
||||||
// def value(id: PuzzleId, user: User): Fu[Option[Boolean]] =
|
// def value(id: Puzzle.Id, user: User): Fu[Option[Boolean]] =
|
||||||
// voteColl {
|
// colls.round {
|
||||||
// _.primitiveOne[Boolean]($id(Vote.makeId(id, user.id)), "v")
|
// _.primitiveOne[Boolean]($id(Vote.makeId(id, user.id)), "v")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// def find(id: PuzzleId, user: User): Fu[Option[Vote]] =
|
// def find(id: PuzzleId, user: User): Fu[Option[Vote]] =
|
||||||
// voteColl {
|
// voteColl {
|
||||||
// _.byId[Vote](Vote.makeId(id, user.id))
|
// _.byId[Vote](Vote.makeId(id, user.id))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// def update(id: PuzzleId, user: User, v1: Option[Vote], v: Boolean): Fu[(Puzzle, Vote)] =
|
def update(id: Puzzle.Id, user: User, v: Boolean): Funit =
|
||||||
// puzzle find id orFail s"Can't vote for non existing puzzle $id" flatMap { p1 =>
|
colls.round {
|
||||||
// val (p2, v2) = v1 match {
|
_.ext
|
||||||
// case Some(from) =>
|
.findAndUpdate[PuzzleRound](
|
||||||
// (
|
$id(PuzzleRound.Id(user.id, id)),
|
||||||
// (p1 withVote (_.change(from.value, v))),
|
$set($doc(PuzzleRound.BSONFields.vote -> v))
|
||||||
// from.copy(v = v)
|
)
|
||||||
// )
|
} flatMap {
|
||||||
// case None =>
|
case Some(prevRound) if prevRound.vote.fold(true)(v !=) =>
|
||||||
// (
|
val dir = if (v) 1 else -1
|
||||||
// (p1 withVote (_ add v)),
|
val inc = dir * (if (prevRound.vote.isDefined) 2 else 1)
|
||||||
// Vote(Vote.makeId(id, user.id), v)
|
colls.puzzle {
|
||||||
// )
|
_.incField($id(id), F.vote, inc).void
|
||||||
// }
|
}
|
||||||
// voteColl {
|
case _ => funit
|
||||||
// _.update
|
}
|
||||||
// .one(
|
// puzzle find id orFail s"Can't vote for non existing puzzle $id" flatMap { p1 =>
|
||||||
// $id(v2.id),
|
// val (p2, v2) = v1 match {
|
||||||
// $set("v" -> v),
|
// case Some(from) =>
|
||||||
// upsert = true
|
// (
|
||||||
// )
|
// (p1 withVote (_.change(from.value, v))),
|
||||||
// .void
|
// from.copy(v = v)
|
||||||
// .recover(lila.db.recoverDuplicateKey { _ => () })
|
// )
|
||||||
// } zip
|
// case None =>
|
||||||
// puzzleColl {
|
// (
|
||||||
// _.update
|
// (p1 withVote (_ add v)),
|
||||||
// .one($id(p2.id), $set(F.vote -> p2.vote))
|
// Vote(Vote.makeId(id, user.id), v)
|
||||||
// } inject (p2 -> v2)
|
// )
|
||||||
// }
|
// }
|
||||||
// }
|
// voteColl {
|
||||||
|
// _.update
|
||||||
|
// .one(
|
||||||
|
// $id(v2.id),
|
||||||
|
// $set("v" -> v),
|
||||||
|
// upsert = true
|
||||||
|
// )
|
||||||
|
// .void
|
||||||
|
// .recover(lila.db.recoverDuplicateKey { _ => () })
|
||||||
|
// } zip
|
||||||
|
// puzzleColl {
|
||||||
|
// _.update
|
||||||
|
// .one($id(p2.id), $set(F.vote -> p2.vote))
|
||||||
|
// } inject (p2 -> v2)
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ final class PuzzleCursorApi(colls: PuzzleColls, cacheApi: CacheApi, userRepo: Us
|
||||||
)
|
)
|
||||||
}.map { docOpt =>
|
}.map { docOpt =>
|
||||||
import NextPuzzleResult._
|
import NextPuzzleResult._
|
||||||
println(docOpt map lila.db.BSON.debug)
|
// println(docOpt map lila.db.BSON.debug)
|
||||||
docOpt.fold[NextPuzzleResult](PathMissing) { doc =>
|
docOpt.fold[NextPuzzleResult](PathMissing) { doc =>
|
||||||
doc.getAsOpt[Puzzle.Id]("puzzleId").fold[NextPuzzleResult](PathEnded) { puzzleId =>
|
doc.getAsOpt[Puzzle.Id]("puzzleId").fold[NextPuzzleResult](PathEnded) { puzzleId =>
|
||||||
doc
|
doc
|
||||||
|
|
|
@ -460,7 +460,6 @@ computer analysis, game chat and shareable URL.</string>
|
||||||
<string name="findTheBestMoveForWhite">Find the best move for white.</string>
|
<string name="findTheBestMoveForWhite">Find the best move for white.</string>
|
||||||
<string name="findTheBestMoveForBlack">Find the best move for black.</string>
|
<string name="findTheBestMoveForBlack">Find the best move for black.</string>
|
||||||
<string name="toTrackYourProgress">To track your progress:</string>
|
<string name="toTrackYourProgress">To track your progress:</string>
|
||||||
<string name="puzzleId">Puzzle %s</string>
|
|
||||||
<string name="puzzleOfTheDay">Puzzle of the day</string>
|
<string name="puzzleOfTheDay">Puzzle of the day</string>
|
||||||
<string name="clickToSolve">Click to solve</string>
|
<string name="clickToSolve">Click to solve</string>
|
||||||
<string name="goodMove">Good move</string>
|
<string name="goodMove">Good move</string>
|
||||||
|
|
|
@ -30,10 +30,6 @@
|
||||||
border-bottom: $border;
|
border-bottom: $border;
|
||||||
margin-bottom: 2vh;
|
margin-bottom: 2vh;
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { moveTestBuild, MoveTestFn } from './moveTest';
|
||||||
import { parseFen, makeFen } from 'chessops/fen';
|
import { parseFen, makeFen } from 'chessops/fen';
|
||||||
import { parseSquare, parseUci, makeSquare, makeUci } from 'chessops/util';
|
import { parseSquare, parseUci, makeSquare, makeUci } from 'chessops/util';
|
||||||
import { pgnToTree, mergeSolution } from './moveTree';
|
import { pgnToTree, mergeSolution } from './moveTree';
|
||||||
import { Redraw, Vm, Controller, PuzzleOpts, PuzzleData, PuzzleRound, MoveTest } from './interfaces';
|
import { Redraw, Vm, Controller, PuzzleOpts, PuzzleData, PuzzleResult, MoveTest } from './interfaces';
|
||||||
import { Role, Move, Outcome } from 'chessops/types';
|
import { Role, Move, Outcome } from 'chessops/types';
|
||||||
import { storedProp } from 'common/storage';
|
import { storedProp } from 'common/storage';
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||||
vm.mode = 'play';
|
vm.mode = 'play';
|
||||||
vm.loading = false;
|
vm.loading = false;
|
||||||
vm.round = undefined;
|
vm.round = undefined;
|
||||||
vm.voted = undefined;
|
|
||||||
vm.justPlayed = undefined;
|
vm.justPlayed = undefined;
|
||||||
vm.resultSent = false;
|
vm.resultSent = false;
|
||||||
vm.lastFeedback = 'init';
|
vm.lastFeedback = 'init';
|
||||||
|
@ -193,7 +192,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||||
if (p == 'good' || p == 'win') return -1;
|
if (p == 'good' || p == 'win') return -1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
if (recursive) node.children.forEach(child =>
|
if (recursive) node.children.forEach(child =>
|
||||||
reorderChildren(path + child.id, true)
|
reorderChildren(path + child.id, true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -236,12 +235,14 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||||
if (vm.resultSent) return;
|
if (vm.resultSent) return;
|
||||||
vm.resultSent = true;
|
vm.resultSent = true;
|
||||||
nbToVoteCall(Math.max(0, parseInt(nbToVoteCall()) - 1));
|
nbToVoteCall(Math.max(0, parseInt(nbToVoteCall()) - 1));
|
||||||
xhr.round(data.puzzle.id, win).then((res: PuzzleRound) => {
|
xhr.round(data.puzzle.id, win).then((res: PuzzleResult | undefined) => {
|
||||||
data.user = res.user;
|
if (res && data.user) {
|
||||||
vm.round = res.round;
|
data.user.rating = res.perf.rating;
|
||||||
vm.voted = res.voted;
|
data.user.provisional = res.perf.provisional;
|
||||||
redraw();
|
vm.round = res.round;
|
||||||
|
}
|
||||||
if (win) speech.success();
|
if (win) speech.success();
|
||||||
|
redraw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +251,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||||
vm.loading = true;
|
vm.loading = true;
|
||||||
redraw();
|
redraw();
|
||||||
xhr.nextPuzzle().then((d: PuzzleData) => {
|
xhr.nextPuzzle().then((d: PuzzleData) => {
|
||||||
vm.round = null;
|
vm.round = undefined;
|
||||||
vm.loading = false;
|
vm.loading = false;
|
||||||
initiate(d);
|
initiate(d);
|
||||||
redraw();
|
redraw();
|
||||||
|
@ -402,7 +403,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||||
const vote = throttle(1000, function(v) {
|
const vote = throttle(1000, function(v) {
|
||||||
if (callToVote()) thanksUntil = Date.now() + 2000;
|
if (callToVote()) thanksUntil = Date.now() + 2000;
|
||||||
nbToVoteCall(5);
|
nbToVoteCall(5);
|
||||||
vm.voted = v;
|
vm.round!.vote = v;
|
||||||
xhr.vote(data.puzzle.id, v);
|
xhr.vote(data.puzzle.id, v);
|
||||||
redraw();
|
redraw();
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,8 +61,7 @@ export interface Vm {
|
||||||
pov: Color;
|
pov: Color;
|
||||||
mode: 'play' | 'view' | 'try';
|
mode: 'play' | 'view' | 'try';
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
round: any;
|
round?: PuzzleRound;
|
||||||
voted?: boolean | null;
|
|
||||||
justPlayed?: Key;
|
justPlayed?: Key;
|
||||||
resultSent: boolean;
|
resultSent: boolean;
|
||||||
lastFeedback: 'init' | 'fail' | 'win' | 'good' | 'retry';
|
lastFeedback: 'init' | 'fail' | 'win' | 'good' | 'retry';
|
||||||
|
@ -117,6 +116,7 @@ export interface PuzzleGame {
|
||||||
|
|
||||||
export interface PuzzleUser {
|
export interface PuzzleUser {
|
||||||
rating: number;
|
rating: number;
|
||||||
|
provisional?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Puzzle {
|
export interface Puzzle {
|
||||||
|
@ -127,13 +127,18 @@ export interface Puzzle {
|
||||||
initialPly: number;
|
initialPly: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PuzzleResult {
|
||||||
|
perf: {
|
||||||
|
rating: number;
|
||||||
|
provisional?: boolean;
|
||||||
|
}
|
||||||
|
round?: PuzzleRound;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PuzzleRound {
|
export interface PuzzleRound {
|
||||||
user: PuzzleUser;
|
win: boolean;
|
||||||
round?: {
|
ratingDiff: number;
|
||||||
ratingDiff: number;
|
vote?: boolean;
|
||||||
win: boolean;
|
|
||||||
};
|
|
||||||
voted?: null | true | false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Promotion {
|
export interface Promotion {
|
||||||
|
|
|
@ -16,9 +16,6 @@ function puzzleInfos(ctrl: Controller, puzzle: Puzzle): VNode {
|
||||||
return h('div.infos.puzzle', {
|
return h('div.infos.puzzle', {
|
||||||
attrs: dataIcon('-')
|
attrs: dataIcon('-')
|
||||||
}, [h('div', [
|
}, [h('div', [
|
||||||
h('a.title', {
|
|
||||||
attrs: { href: '/training/' + puzzle.id }
|
|
||||||
}, ctrl.trans('puzzleId', puzzle.id)),
|
|
||||||
h('p', ctrl.trans.vdom('ratingX', ctrl.vm.mode === 'play' ? h('span.hidden', ctrl.trans.noarg('hidden')) : h('strong', puzzle.rating))),
|
h('p', ctrl.trans.vdom('ratingX', ctrl.vm.mode === 'play' ? h('span.hidden', ctrl.trans.noarg('hidden')) : h('strong', puzzle.rating))),
|
||||||
h('p', ctrl.trans.vdom('playedXTimes', h('strong', numberFormat(puzzle.plays))))
|
h('p', ctrl.trans.vdom('playedXTimes', h('strong', numberFormat(puzzle.plays))))
|
||||||
])]);
|
])]);
|
||||||
|
@ -49,12 +46,12 @@ function gameInfos(ctrl: Controller, game: PuzzleGame, puzzle: Puzzle): VNode {
|
||||||
export function userBox(ctrl: Controller): MaybeVNode {
|
export function userBox(ctrl: Controller): MaybeVNode {
|
||||||
const data = ctrl.getData();
|
const data = ctrl.getData();
|
||||||
if (!data.user) return;
|
if (!data.user) return;
|
||||||
const diff = ctrl.vm.round && ctrl.vm.round.ratingDiff;
|
const diff = ctrl.vm.round?.ratingDiff;
|
||||||
return h('div.puzzle__side__user', [
|
return h('div.puzzle__side__user', [
|
||||||
h('h2', ctrl.trans.vdom('yourPuzzleRatingX', h('strong', [
|
h('h2', ctrl.trans.vdom('yourPuzzleRatingX', h('strong', [
|
||||||
data.user.rating,
|
data.user.rating,
|
||||||
...(diff >= 0 ? [' ', h('good.rp', '+' + diff)] : []),
|
...(diff && diff > 0 ? [' ', h('good.rp', '+' + diff)] : []),
|
||||||
...(diff < 0 ? [' ', h('bad.rp', '−' + (-diff))] : [])
|
...(diff && diff < 0 ? [' ', h('bad.rp', '−' + (-diff))] : [])
|
||||||
])))
|
])))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PuzzleRound, PuzzleData } from './interfaces';
|
import { PuzzleData, PuzzleResult } from './interfaces';
|
||||||
import * as xhr from 'common/xhr';
|
import * as xhr from 'common/xhr';
|
||||||
|
|
||||||
export function round(puzzleId: string, win: boolean): Promise<PuzzleRound> {
|
export function round(puzzleId: string, win: boolean): Promise<PuzzleResult | undefined> {
|
||||||
return xhr.json(`/training/${puzzleId}/round3`, {
|
return xhr.json(`/training/${puzzleId}/round3`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: xhr.form({ win: win ? 1 : 0 })
|
body: xhr.form({ win: win ? 1 : 0 })
|
||||||
|
|
Loading…
Reference in New Issue