record storm runs
parent
d87749357e
commit
f4a19ff2cd
|
@ -16,4 +16,15 @@ final class Storm(env: Env)(implicit mat: akka.stream.Materializer) extends Lila
|
|||
Ok(views.html.storm.home(env.storm.json(puzzles), env.storm.json.pref(ctx.pref)))
|
||||
}
|
||||
}
|
||||
|
||||
def record =
|
||||
OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
env.storm.forms.run
|
||||
.bindFromRequest()
|
||||
.fold(
|
||||
_ => funit,
|
||||
data => env.storm.runApi.record(data, ctx.me)
|
||||
) inject jsonOkResult
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ POST /training/difficulty/:theme controllers.Puzzle.setDifficulty(theme: S
|
|||
|
||||
# Puzzle Storm
|
||||
GET /storm controllers.Storm.home
|
||||
POST /storm controllers.Storm.record
|
||||
|
||||
# User Analysis
|
||||
GET /analysis/help controllers.UserAnalysis.help
|
||||
|
|
|
@ -15,7 +15,13 @@ final class Env(
|
|||
ec: scala.concurrent.ExecutionContext
|
||||
) {
|
||||
|
||||
private lazy val runColl = db(CollName("storm_run"))
|
||||
|
||||
lazy val selector = wire[StormSelector]
|
||||
|
||||
lazy val json = new StormJson
|
||||
lazy val json = wire[StormJson]
|
||||
|
||||
lazy val runApi = wire[StormRunApi]
|
||||
|
||||
val forms = StormForm
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package lila.storm
|
||||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
import lila.common.Form.{ numberIn, stringIn }
|
||||
|
||||
object StormForm {
|
||||
|
||||
case class RunData(
|
||||
puzzles: Int,
|
||||
wins: Int,
|
||||
moves: Int,
|
||||
combo: Int,
|
||||
time: Int,
|
||||
highest: Int
|
||||
)
|
||||
|
||||
val run = Form(
|
||||
mapping(
|
||||
"puzzles" -> number(min = 1, max = 200),
|
||||
"wins" -> number(min = 1, max = 200),
|
||||
"moves" -> number(min = 1, max = 900),
|
||||
"combo" -> number(min = 1, max = 900),
|
||||
"time" -> number(min = 1, max = 900),
|
||||
"highest" -> number(min = lila.rating.Glicko.minRating, max = 4000)
|
||||
)(RunData.apply)(RunData.unapply)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package lila.storm
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.user.User
|
||||
import lila.common.ThreadLocalRandom
|
||||
|
||||
case class StormRun(
|
||||
_id: String,
|
||||
user: User.ID,
|
||||
date: DateTime,
|
||||
puzzles: Int,
|
||||
wins: Int,
|
||||
moves: Int,
|
||||
combo: Int,
|
||||
time: Int,
|
||||
highest: Int
|
||||
)
|
||||
|
||||
object StormRun {
|
||||
import lila.db.dsl._
|
||||
import reactivemongo.api.bson._
|
||||
implicit val stormGameBSONHandler = Macros.handler[StormRun]
|
||||
|
||||
def randomId = ThreadLocalRandom nextString 8
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package lila.storm
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
|
||||
final class StormRunApi(coll: Coll)(implicit ctx: ExecutionContext) {
|
||||
|
||||
def record(data: StormForm.RunData, user: Option[User]): Funit = {
|
||||
monitor(data)
|
||||
user ?? { u =>
|
||||
coll.insert
|
||||
.one(
|
||||
StormRun(
|
||||
_id = StormRun.randomId,
|
||||
user = u.id,
|
||||
date = DateTime.now,
|
||||
puzzles = data.puzzles,
|
||||
wins = data.wins,
|
||||
moves = data.moves,
|
||||
combo = data.combo,
|
||||
time = data.time,
|
||||
highest = data.highest
|
||||
)
|
||||
)
|
||||
.void
|
||||
}
|
||||
}
|
||||
|
||||
private def monitor(run: StormForm.RunData): Unit = {}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import * as xhr from './xhr';
|
||||
import config from './config';
|
||||
import makePromotion from './promotion';
|
||||
import { Api as CgApi } from 'chessground/api';
|
||||
|
@ -10,7 +11,7 @@ import { parseUci, opposite } from 'chessops/util';
|
|||
import { prop, Prop } from 'common';
|
||||
import { Role } from 'chessground/types';
|
||||
import { set as dailyBestSet } from './best';
|
||||
import { StormOpts, StormData, StormPuzzle, StormVm, Promotion, TimeMod } from './interfaces';
|
||||
import { StormOpts, StormData, StormPuzzle, StormVm, Promotion, TimeMod, StormRun } from './interfaces';
|
||||
|
||||
export default class StormCtrl {
|
||||
|
||||
|
@ -51,6 +52,7 @@ export default class StormCtrl {
|
|||
dailyBestSet(this.countWins());
|
||||
this.redraw();
|
||||
this.sound.end();
|
||||
xhr.record(this.runStats());
|
||||
}
|
||||
|
||||
naturalFlag = () => {
|
||||
|
@ -196,6 +198,15 @@ export default class StormCtrl {
|
|||
return g && f(g);
|
||||
}
|
||||
|
||||
runStats = (): StormRun => ({
|
||||
puzzles: this.vm.history.length,
|
||||
wins: this.countWins(),
|
||||
moves: this.vm.run.moves,
|
||||
combo: this.vm.comboBest,
|
||||
time: (this.vm.run.endAt! - this.vm.run.startAt) / 1000,
|
||||
highest: this.vm.history.reduce((h, r) => r.win && r.puzzle.rating > h ? r.puzzle.rating : h, 0)
|
||||
});
|
||||
|
||||
private showGround = (g: CgApi): void => g.set(this.makeCgOpts());
|
||||
|
||||
private uciToLastMove = (uci: string): [Key, Key] => [uci.substr(0, 2) as Key, uci.substr(2, 2) as Key];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Role} from 'chessground/types';
|
||||
import { VNode } from 'snabbdom/vnode'
|
||||
import { Role } from 'chessground/types';
|
||||
|
||||
export type MaybeVNode = VNode | string | null | undefined;
|
||||
export type MaybeVNodes = MaybeVNode[];
|
||||
|
@ -75,3 +75,12 @@ export interface DailyBest {
|
|||
prev?: number;
|
||||
at: number;
|
||||
}
|
||||
|
||||
export interface StormRun {
|
||||
puzzles: number;
|
||||
score: number;
|
||||
moves: number;
|
||||
combo: number;
|
||||
time: number;
|
||||
highest: number;
|
||||
}
|
||||
|
|
|
@ -15,12 +15,10 @@ const renderEnd = (ctrl: StormCtrl): VNode[] => [
|
|||
];
|
||||
|
||||
const renderSummary = (ctrl: StormCtrl): VNode[] => {
|
||||
const score = ctrl.countWins();
|
||||
const best = dailyBestGet();
|
||||
const run = ctrl.vm.run;
|
||||
const seconds = (run.endAt! - run.startAt) / 1000;
|
||||
const run = ctrl.runStats();
|
||||
return [
|
||||
...(score > (best.prev || 0) ? [
|
||||
...(run.score > (best.prev || 0) ? [
|
||||
h('div.storm--end__high.storm--end__high-daily.bar-glider',
|
||||
h('div.storm--end__high__content', [
|
||||
h('div.storm--end__high__text', [
|
||||
|
@ -31,7 +29,7 @@ const renderSummary = (ctrl: StormCtrl): VNode[] => {
|
|||
)] : []),
|
||||
h('div.storm--end__score', [
|
||||
h('span.storm--end__score__number', {
|
||||
hook: onInsert(el => numberSpread(el, score, Math.round(score * 50), 0)(score))
|
||||
hook: onInsert(el => numberSpread(el, run.score, Math.round(run.score * 50), 0)(run.score))
|
||||
}, '0'),
|
||||
h('p', ctrl.trans('puzzlesSolved'))
|
||||
]),
|
||||
|
@ -44,7 +42,7 @@ const renderSummary = (ctrl: StormCtrl): VNode[] => {
|
|||
]),
|
||||
h('tr', [
|
||||
h('th', 'Accuracy'),
|
||||
h('td', [h('number', Number(100 * (run.moves - (ctrl.vm.history.length - score)) / run.moves).toFixed(1)), '%'])
|
||||
h('td', [h('number', Number(100 * (run.moves - (run.puzzles - run.score - 1)) / run.moves).toFixed(1)), '%'])
|
||||
]),
|
||||
h('tr', [
|
||||
h('th', 'Best combo'),
|
||||
|
@ -52,22 +50,21 @@ const renderSummary = (ctrl: StormCtrl): VNode[] => {
|
|||
]),
|
||||
h('tr', [
|
||||
h('th', 'Total time'),
|
||||
h('td', [h('number', Math.round(seconds)), 's'])
|
||||
h('td', [h('number', Math.round(run.time)), 's'])
|
||||
]),
|
||||
h('tr', [
|
||||
h('th', 'Time per move'),
|
||||
h('td', [h('number', Number(seconds / run.moves).toFixed(2)), 's'])
|
||||
h('td', [h('number', Number(run.time / run.moves).toFixed(2)), 's'])
|
||||
]),
|
||||
h('tr', [
|
||||
h('th', 'Highest solved'),
|
||||
h('td', h('number', ctrl.vm.history.reduce((h, r) => r.win && r.puzzle.rating > h ? r.puzzle.rating : h, 0)))
|
||||
h('td', h('number', run.highest))
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
h('a.storm--end__play.button', {
|
||||
attrs: { href: '/storm' }
|
||||
// hook: onInsert(e => e.addEventListener('click', ctrl.restart))
|
||||
}, ctrl.trans('playAgain'))
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import * as xhr from 'common/xhr';
|
||||
import { StormRun } from './interfaces';
|
||||
|
||||
export function record(run: StormRun): Promise<void> {
|
||||
return xhr.json('/storm', {
|
||||
method: 'POST',
|
||||
body: xhr.form({
|
||||
...run,
|
||||
time: Math.round(run.time)
|
||||
})
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue