puzzle WIP

pull/7680/head
Thibault Duplessis 2020-12-03 22:31:42 +01:00
parent 982e35841d
commit 27ef01c7a4
8 changed files with 69 additions and 15 deletions

View File

@ -139,6 +139,24 @@ final class Puzzle(
}
}
def voteTheme(id: String, themeStr: String) =
AuthBody { implicit ctx => me =>
NoBot {
PuzzleTheme.find(themeStr) ?? { theme =>
implicit val req = ctx.body
env.puzzle.forms.themeVote
.bindFromRequest()
.fold(
jsonFormError,
vote => {
vote foreach { v => lila.mon.puzzle.voteTheme(theme.key.value, v).increment() }
env.puzzle.api.theme.vote(Puz.Id(id), me, theme, vote) inject jsonOkResult
}
)
}
}
}
/* Mobile API: select a bunch of puzzles for offline use */
def batchSelect =
Auth { implicit ctx => me =>

View File

@ -87,6 +87,7 @@ GET /training/themes controllers.Puzzle.themes
GET /training/:themeOrId controllers.Puzzle.show(themeOrId: String)
GET /training/:theme/$id<\w{5}> controllers.Puzzle.showWithTheme(theme: String, id: String)
POST /training/$id<\w{5}>/vote controllers.Puzzle.vote(id: String)
POST /training/$id<\w{5}>/vote/:theme controllers.Puzzle.voteTheme(id: String, theme: String)
POST /training/complete/:theme/$id<\w{5}> controllers.Puzzle.complete(theme: String, id: String)
# User Analysis

View File

@ -420,7 +420,6 @@ object mon {
object puzzle {
object selector {
val time = timer("puzzle.selector.time").withoutTags()
val vote = histogram("puzzle.selector.vote").withoutTags()
}
object batch {
object selector {
@ -433,8 +432,14 @@ object mon {
def attempt(user: Boolean, theme: String) =
counter("puzzle.attempt.count").withTags(Map("user" -> user, "theme" -> theme))
}
def vote(up: Boolean) = counter("puzzle.vote.count").withTag("dir", if (up) "up" else "down")
val crazyGlicko = counter("puzzle.crazyGlicko").withoutTags()
def vote(up: Boolean) = counter("puzzle.vote.count").withTag("up", up)
def voteTheme(key: String, up: Boolean) = counter("puzzle.selector.voteTheme").withTags(
Map(
"up" -> up,
"theme" -> key
)
)
val crazyGlicko = counter("puzzle.crazyGlicko").withoutTags()
}
object game {
def finish(variant: String, speed: String, source: String, mode: String, status: String) =

View File

@ -8,6 +8,7 @@ import lila.db.BSON
import lila.db.dsl._
import lila.game.Game
import lila.rating.Glicko
import scala.util.Try
private[puzzle] object BsonHandlers {
@ -48,6 +49,17 @@ private[puzzle] object BsonHandlers {
id => BSONString(id.toString)
)
implicit val RoundThemeHandler = tryHandler[PuzzleRound.Theme](
{ case BSONString(v) =>
PuzzleTheme
.find(v.tail)
.fold[Try[PuzzleRound.Theme]](handlerBadValue(s"Invalid puzzle round theme $v")) { theme =>
Success(PuzzleRound.Theme(theme.key, v.head == '+'))
}
},
rt => BSONString(s"${if (rt.vote) "+" else "-"}${rt.theme}")
)
implicit val RoundHandler = new BSON[PuzzleRound] {
import PuzzleRound.BSONFields._
def reads(r: BSON.Reader) = PuzzleRound(
@ -55,6 +67,7 @@ private[puzzle] object BsonHandlers {
date = r.date(date),
win = r.bool(win),
vote = r.boolO(vote),
themes = r.get[List[PuzzleRound.Theme]](themes),
weight = r.intO(weight)
)
def writes(w: BSON.Writer, r: PuzzleRound) =

View File

@ -29,8 +29,8 @@ final private[puzzle] class PuzzleApi(
object round {
def find(user: User, puzzle: Puzzle): Fu[Option[PuzzleRound]] =
colls.round(_.byId[PuzzleRound](PuzzleRound.Id(user.id, puzzle.id).toString))
def find(user: User, puzzleId: Puzzle.Id): Fu[Option[PuzzleRound]] =
colls.round(_.byId[PuzzleRound](PuzzleRound.Id(user.id, puzzleId).toString))
def upsert(a: PuzzleRound) = colls.round(_.update.one($id(a.id), a, upsert = true))
@ -68,5 +68,18 @@ final private[puzzle] class PuzzleApi(
}
}
}
def vote(user: User, id: Puzzle.Id, theme: PuzzleTheme, vote: Option[Boolean]): Funit =
round.find(user, id) flatMap {
_ ?? { round =>
???
}
}
// colls.round {
// _.byId[
// .findAndUpdate[PuzzleRound](
// $id(PuzzleRound.Id(user.id, id)),
// $set($doc(PuzzleRound.BSONFields.vote -> vote))
}
}

View File

@ -61,13 +61,7 @@ final private[puzzle] class PuzzleFinisher(
if (theme == PuzzleTheme.any.key) g else g.average(puzzle.glicko)
}
.filter(_.sanityCheck)
val round = PuzzleRound(
id = PuzzleRound.Id(user.id, puzzle.id),
date = now,
win = result.win,
vote = none,
weight = none
)
val round = PuzzleRound(id = PuzzleRound.Id(user.id, puzzle.id), date = now, win = result.win)
val userPerf =
user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, now) pipe {
p =>

View File

@ -12,4 +12,8 @@ object PuzzleForm {
val vote = Form(
single("vote" -> boolean)
)
val themeVote = Form(
single("vote" -> optional(boolean))
)
}

View File

@ -8,12 +8,15 @@ case class PuzzleRound(
id: PuzzleRound.Id,
date: DateTime,
win: Boolean,
vote: Option[Boolean],
// tags: List[RoundTag],
weight: Option[Int]
vote: Option[Boolean] = None,
themes: List[PuzzleRound.Theme] = Nil,
weight: Option[Int] = None
) {
def userId = id.userId
def themeVote(theme: PuzzleTheme.Key, vote: Option[Boolean]): Option[Boolean] =
themes.find(_.theme == theme)
}
object PuzzleRound {
@ -25,11 +28,14 @@ object PuzzleRound {
override def toString = s"${userId}$idSep${puzzleId}"
}
case class Theme(theme: PuzzleTheme.Key, vote: Boolean)
object BSONFields {
val id = "_id"
val date = "d"
val win = "w"
val vote = "v"
val themes = "t"
val weight = "w"
val user = "u" // student denormalization
}