record storm runs

storm
Thibault Duplessis 2021-01-25 13:15:36 +01:00
parent d87749357e
commit f4a19ff2cd
10 changed files with 148 additions and 13 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
)
}

View File

@ -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
}

View File

@ -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 = {}
}

View File

@ -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];

View File

@ -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;
}

View File

@ -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'))
];
}

View File

@ -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)
})
});
}