puzzle streak improvements
parent
1f70b71033
commit
34905daa89
|
@ -206,7 +206,7 @@ final class Puzzle(
|
|||
env.puzzle.streak.apply flatMap {
|
||||
_ ?? { case (streak, puzzle) =>
|
||||
env.puzzle.jsonView(puzzle = puzzle, PuzzleTheme.mix.some, none, user = ctx.me) map { preJson =>
|
||||
val json = preJson ++ Json.obj("streak" -> streak.ids.map(_.value))
|
||||
val json = preJson ++ Json.obj("streak" -> streak.ids.mkString(" "))
|
||||
EnableSharedArrayBuffer(
|
||||
Ok(
|
||||
views.html.puzzle
|
||||
|
|
|
@ -17,10 +17,12 @@ object bits {
|
|||
def daily(p: lila.puzzle.Puzzle, fen: chess.format.FEN, lastMove: String) =
|
||||
views.html.board.bits.mini(fen, p.color, lastMove)(span)
|
||||
|
||||
def jsI18n(streak: Boolean)(implicit lang: Lang) = i18nJsObject {
|
||||
i18nKeys ++ streak.??(streakI18nKeys)
|
||||
} +
|
||||
(PuzzleTheme.enPassant.key.value -> JsString(PuzzleTheme.enPassant.name.txt()(lila.i18n.defaultLang)))
|
||||
def jsI18n(streak: Boolean)(implicit lang: Lang) =
|
||||
if (streak) i18nJsObject(streakI18nKeys)
|
||||
else
|
||||
i18nJsObject(trainingI18nKeys) + (PuzzleTheme.enPassant.key.value -> JsString(
|
||||
PuzzleTheme.enPassant.name.txt()(lila.i18n.defaultLang)
|
||||
))
|
||||
|
||||
lazy val jsonThemes = PuzzleTheme.all
|
||||
.collect {
|
||||
|
@ -59,9 +61,8 @@ object bits {
|
|||
)
|
||||
)
|
||||
|
||||
private val i18nKeys: List[MessageKey] = {
|
||||
private val baseI18nKeys: List[MessageKey] =
|
||||
List(
|
||||
trans.puzzle.yourPuzzleRatingX,
|
||||
trans.puzzle.bestMove,
|
||||
trans.puzzle.keepGoing,
|
||||
trans.puzzle.notTheMove,
|
||||
|
@ -75,17 +76,10 @@ object bits {
|
|||
trans.puzzle.hidden,
|
||||
trans.puzzle.jumpToNextPuzzleImmediately,
|
||||
trans.puzzle.fromGameLink,
|
||||
trans.puzzle.didYouLikeThisPuzzle,
|
||||
trans.puzzle.voteToLoadNextOne,
|
||||
trans.puzzle.puzzleId,
|
||||
trans.puzzle.ratingX,
|
||||
trans.puzzle.playedXTimes,
|
||||
trans.puzzle.continueTraining,
|
||||
trans.puzzle.difficultyLevel,
|
||||
trans.puzzle.example,
|
||||
trans.puzzle.toGetPersonalizedPuzzles,
|
||||
trans.puzzle.addAnotherTheme,
|
||||
trans.signUp,
|
||||
trans.analysis,
|
||||
trans.playWithTheMachine,
|
||||
// ceval
|
||||
|
@ -98,13 +92,25 @@ object bits {
|
|||
trans.gameOver,
|
||||
trans.inLocalBrowser,
|
||||
trans.toggleLocalEvaluation
|
||||
) ::: PuzzleTheme.all.map(_.name) :::
|
||||
PuzzleTheme.all.map(_.description) :::
|
||||
PuzzleDifficulty.all.map(_.name)
|
||||
}.map(_.key)
|
||||
).map(_.key)
|
||||
|
||||
private val trainingI18nKeys: List[MessageKey] =
|
||||
baseI18nKeys ::: List(
|
||||
trans.puzzle.example,
|
||||
trans.puzzle.addAnotherTheme,
|
||||
trans.puzzle.yourPuzzleRatingX,
|
||||
trans.puzzle.difficultyLevel,
|
||||
trans.puzzle.didYouLikeThisPuzzle,
|
||||
trans.puzzle.voteToLoadNextOne,
|
||||
trans.signUp,
|
||||
trans.puzzle.toGetPersonalizedPuzzles
|
||||
).map(_.key) :::
|
||||
PuzzleTheme.all.map(_.name.key) :::
|
||||
PuzzleTheme.all.map(_.description.key) :::
|
||||
PuzzleDifficulty.all.map(_.name.key)
|
||||
|
||||
private val streakI18nKeys: List[MessageKey] =
|
||||
List(
|
||||
baseI18nKeys ::: List(
|
||||
trans.storm.skip,
|
||||
trans.puzzle.streakDescription,
|
||||
trans.puzzle.yourStreakX,
|
||||
|
|
|
@ -43,10 +43,8 @@ final class PuzzleStreakApi(colls: PuzzleColls, cacheApi: CacheApi)(implicit ec:
|
|||
2799 -> 23,
|
||||
highestBoundary -> 25
|
||||
)
|
||||
private val poolSize = buckets.map(_._2).sum
|
||||
private val theme = lila.puzzle.PuzzleTheme.mix.key.value
|
||||
private val tier = lila.puzzle.PuzzleTier.Good.key
|
||||
private val maxDeviation = 110
|
||||
private val poolSize = buckets.map(_._2).sum
|
||||
private val theme = lila.puzzle.PuzzleTheme.mix.key.value
|
||||
|
||||
private val current = cacheApi.unit[List[Puzzle.Id]] {
|
||||
_.refreshAfterWrite(30 seconds)
|
||||
|
@ -57,6 +55,8 @@ final class PuzzleStreakApi(colls: PuzzleColls, cacheApi: CacheApi)(implicit ec:
|
|||
import framework._
|
||||
Facet(
|
||||
buckets.map { case (rating, nbPuzzles) =>
|
||||
val (tier, samples, deviation) =
|
||||
if (rating > 2300) (PuzzleTier.Good, 5, 110) else (PuzzleTier.Top, 1, 85)
|
||||
rating.toString -> List(
|
||||
Match(
|
||||
$doc(
|
||||
|
@ -64,7 +64,7 @@ final class PuzzleStreakApi(colls: PuzzleColls, cacheApi: CacheApi)(implicit ec:
|
|||
"max" $gte f"${theme}_${tier}_${if (rating == highestBoundary) 9999 else rating}%04d"
|
||||
)
|
||||
),
|
||||
Sample(if (rating > 2300) 5 else 1),
|
||||
Sample(samples),
|
||||
Project($doc("_id" -> false, "ids" -> true)),
|
||||
UnwindField("ids"),
|
||||
// ensure we have enough after filtering deviation
|
||||
|
@ -81,7 +81,7 @@ final class PuzzleStreakApi(colls: PuzzleColls, cacheApi: CacheApi)(implicit ec:
|
|||
"$expr" -> $doc(
|
||||
"$and" -> $arr(
|
||||
$doc("$eq" -> $arr("$_id", "$$id")),
|
||||
$doc("$lte" -> $arr("$glicko.d", maxDeviation))
|
||||
$doc("$lte" -> $arr("$glicko.d", deviation))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function (opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
const ground = prop<CgApi | undefined>(undefined) as Prop<CgApi>;
|
||||
const threatMode = prop(false);
|
||||
const session = new PuzzleSession(opts.data.theme.key, opts.data.user?.id, hasStreak);
|
||||
const streak = opts.data.streak && new PuzzleStreak(opts.data.streak);
|
||||
const streak = opts.data.streak ? new PuzzleStreak(opts.data.streak.split(' ')) : undefined;
|
||||
|
||||
// required by ceval
|
||||
vm.showComputer = () => vm.mode === 'view';
|
||||
|
|
|
@ -133,7 +133,7 @@ export interface PuzzleData {
|
|||
game: PuzzleGame;
|
||||
user: PuzzleUser | undefined;
|
||||
replay?: PuzzleReplay;
|
||||
streak?: PuzzleId[];
|
||||
streak?: string;
|
||||
}
|
||||
|
||||
export interface PuzzleReplay {
|
||||
|
|
|
@ -102,7 +102,7 @@ export default function (ctrl: Controller): VNode {
|
|||
h('aside.puzzle__side', [
|
||||
side.replay(ctrl),
|
||||
side.puzzleBox(ctrl),
|
||||
side.userBox(ctrl),
|
||||
ctrl.streak ? side.streakBox(ctrl) : side.userBox(ctrl),
|
||||
side.config(ctrl),
|
||||
theme(ctrl),
|
||||
]),
|
||||
|
@ -155,12 +155,14 @@ function session(ctrl: Controller) {
|
|||
);
|
||||
}),
|
||||
rounds.find(r => r.id == current)
|
||||
? h('a.session-new', {
|
||||
key: 'new',
|
||||
attrs: {
|
||||
href: `/training/${ctrl.session.theme}`,
|
||||
},
|
||||
})
|
||||
? ctrl.streak
|
||||
? null
|
||||
: h('a.session-new', {
|
||||
key: 'new',
|
||||
attrs: {
|
||||
href: `/training/${ctrl.session.theme}`,
|
||||
},
|
||||
})
|
||||
: h('a.result-cursor.current', {
|
||||
key: current,
|
||||
attrs: ctrl.streak
|
||||
|
|
|
@ -18,21 +18,24 @@ function puzzleInfos(ctrl: Controller, puzzle: Puzzle): VNode {
|
|||
},
|
||||
[
|
||||
h('div', [
|
||||
h('p', [
|
||||
...ctrl.trans.vdom(
|
||||
'puzzleId',
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
attrs: {
|
||||
href: `/training/${puzzle.id}`,
|
||||
...(ctrl.streak ? { target: '_blank' } : {}),
|
||||
},
|
||||
},
|
||||
'#' + puzzle.id
|
||||
)
|
||||
),
|
||||
]),
|
||||
ctrl.streak
|
||||
? null
|
||||
: h(
|
||||
'p',
|
||||
ctrl.trans.vdom(
|
||||
'puzzleId',
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
attrs: {
|
||||
href: `/training/${puzzle.id}`,
|
||||
...(ctrl.streak ? { target: '_blank' } : {}),
|
||||
},
|
||||
},
|
||||
'#' + puzzle.id
|
||||
)
|
||||
)
|
||||
),
|
||||
h(
|
||||
'p',
|
||||
ctrl.trans.vdom(
|
||||
|
@ -117,7 +120,7 @@ const renderStreak = (streak: PuzzleStreak, noarg: TransNoArg) =>
|
|||
)
|
||||
);
|
||||
|
||||
export function userBox(ctrl: Controller): VNode {
|
||||
export const userBox = (ctrl: Controller): VNode => {
|
||||
const data = ctrl.getData();
|
||||
if (!data.user)
|
||||
return h('div.puzzle__side__user', [
|
||||
|
@ -126,21 +129,22 @@ export function userBox(ctrl: Controller): VNode {
|
|||
]);
|
||||
const diff = ctrl.vm.round?.ratingDiff;
|
||||
return h('div.puzzle__side__user', [
|
||||
ctrl.streak
|
||||
? renderStreak(ctrl.streak, ctrl.trans.noarg)
|
||||
: h(
|
||||
'div.puzzle__side__user__rating',
|
||||
ctrl.trans.vdom(
|
||||
'yourPuzzleRatingX',
|
||||
h('strong', [
|
||||
data.user.rating - (diff || 0),
|
||||
...(diff && diff > 0 ? [' ', h('good.rp', '+' + diff)] : []),
|
||||
...(diff && diff < 0 ? [' ', h('bad.rp', '−' + -diff)] : []),
|
||||
])
|
||||
)
|
||||
),
|
||||
h(
|
||||
'div.puzzle__side__user__rating',
|
||||
ctrl.trans.vdom(
|
||||
'yourPuzzleRatingX',
|
||||
h('strong', [
|
||||
data.user.rating - (diff || 0),
|
||||
...(diff && diff > 0 ? [' ', h('good.rp', '+' + diff)] : []),
|
||||
...(diff && diff < 0 ? [' ', h('bad.rp', '−' + -diff)] : []),
|
||||
])
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
export const streakBox = (ctrl: Controller) =>
|
||||
h('div.puzzle__side__user', renderStreak(ctrl.streak!, ctrl.trans.noarg));
|
||||
|
||||
const difficulties: PuzzleDifficulty[] = ['easiest', 'easier', 'normal', 'harder', 'hardest'];
|
||||
|
||||
|
|
Loading…
Reference in New Issue