puzzle WIP
parent
5f54e07d7e
commit
dd9abd4532
|
@ -7,7 +7,7 @@ import views._
|
|||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.common.config.MaxPerSecond
|
||||
import lila.puzzle.{ Result, Puzzle => Puz }
|
||||
import lila.puzzle.{ Result, PuzzleRound, Puzzle => Puz }
|
||||
|
||||
final class Puzzle(
|
||||
env: Env,
|
||||
|
@ -16,7 +16,7 @@ final class Puzzle(
|
|||
|
||||
private def renderJson(
|
||||
puzzle: Puz,
|
||||
round: Option[lila.puzzle.Round] = None
|
||||
round: Option[PuzzleRound] = None
|
||||
)(implicit ctx: Context): Fu[JsObject] =
|
||||
env.puzzle.jsonView(
|
||||
puzzle = puzzle,
|
||||
|
@ -50,10 +50,9 @@ final class Puzzle(
|
|||
def home =
|
||||
Open { implicit ctx =>
|
||||
NoBot {
|
||||
???
|
||||
// env.puzzle.selector(ctx.me) flatMap { puzzle =>
|
||||
// renderShow(puzzle, if (ctx.isAuth) "play" else "try")
|
||||
// }
|
||||
env.puzzle.cursorApi.nextPuzzleFor(ctx.me.get) flatMap { puzzle =>
|
||||
renderShow(puzzle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,10 +124,9 @@ puzzle {
|
|||
uri = "mongodb://127.0.0.1:27017/lichess"
|
||||
}
|
||||
collection {
|
||||
puzzle = puzzle
|
||||
round = puzzle_round2
|
||||
vote = puzzle_vote
|
||||
head = puzzle_head
|
||||
puzzle = puzzle2_puzzle
|
||||
round = puzzle2_round
|
||||
path = puzzle2_path
|
||||
}
|
||||
api.token = ${api.token}
|
||||
animation.duration = ${chessground.animation.duration}
|
||||
|
|
|
@ -42,7 +42,7 @@ case class StudentProgress(
|
|||
final class ClasProgressApi(
|
||||
gameRepo: GameRepo,
|
||||
historyApi: lila.history.HistoryApi,
|
||||
puzzleRoundRepo: lila.puzzle.RoundRepo,
|
||||
puzzleColls: lila.puzzle.PuzzleColls,
|
||||
getStudentIds: () => Fu[Set[User.ID]]
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
|
@ -76,7 +76,7 @@ final class ClasProgressApi(
|
|||
}
|
||||
|
||||
private def getPuzzleStats(userIds: List[User.ID], days: Int): Fu[Map[User.ID, PlayStats]] =
|
||||
puzzleRoundRepo.coll.get.flatMap {
|
||||
puzzleColls.round {
|
||||
_.aggregateList(
|
||||
maxDocs = Int.MaxValue,
|
||||
ReadPreference.secondaryPreferred
|
||||
|
|
|
@ -10,7 +10,7 @@ final class Env(
|
|||
userRepo: lila.user.UserRepo,
|
||||
gameRepo: lila.game.GameRepo,
|
||||
historyApi: lila.history.HistoryApi,
|
||||
puzzleRoundRepo: lila.puzzle.RoundRepo,
|
||||
puzzleColls: lila.puzzle.PuzzleColls,
|
||||
msgApi: lila.msg.MsgApi,
|
||||
lightUserAsync: lila.common.LightUser.Getter,
|
||||
securityForms: lila.security.SecurityForm,
|
||||
|
|
|
@ -2,7 +2,11 @@ package lila.db
|
|||
|
||||
import dsl._
|
||||
|
||||
final class AsyncColl(resolve: () => Fu[Coll])(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
import lila.common.config.CollName
|
||||
|
||||
final class AsyncColl(val name: CollName, resolve: () => Fu[Coll])(implicit
|
||||
ec: scala.concurrent.ExecutionContext
|
||||
) {
|
||||
|
||||
def get: Fu[Coll] = resolve()
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ final class AsyncDb(
|
|||
conn database dbName.getOrElse("lichess")
|
||||
}
|
||||
|
||||
def apply(name: CollName) = new AsyncColl(() => db.dmap(_(name.value)))
|
||||
def apply(name: CollName) = new AsyncColl(name, () => db.dmap(_(name.value)))
|
||||
}
|
||||
|
||||
final class Db(
|
||||
|
|
|
@ -27,9 +27,8 @@ final class CacheApi(
|
|||
}
|
||||
|
||||
// AsyncLoadingCache for a single entry
|
||||
def unit[V](build: Builder => AsyncLoadingCache[Unit, V]): AsyncLoadingCache[Unit, V] = {
|
||||
def unit[V](build: Builder => AsyncLoadingCache[Unit, V]): AsyncLoadingCache[Unit, V] =
|
||||
build(scaffeine initialCapacity 1)
|
||||
}
|
||||
|
||||
// AsyncLoadingCache with monitoring and a synchronous getter
|
||||
def sync[K, V](
|
||||
|
|
|
@ -2,7 +2,6 @@ package lila.puzzle
|
|||
|
||||
import chess.format.{ FEN, Uci }
|
||||
import reactivemongo.api.bson._
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
import lila.db.BSON
|
||||
|
@ -37,15 +36,34 @@ private[puzzle] object BsonHandlers {
|
|||
)
|
||||
}
|
||||
|
||||
implicit val RoundIdHandler = tryHandler[Round.Id](
|
||||
implicit val RoundIdHandler = tryHandler[PuzzleRound.Id](
|
||||
{ case BSONString(v) =>
|
||||
v split Round.idSep match {
|
||||
case Array(userId, puzzleId) => Success(Round.Id(userId, Puzzle.Id(puzzleId)))
|
||||
v split PuzzleRound.idSep match {
|
||||
case Array(userId, puzzleId) => Success(PuzzleRound.Id(userId, Puzzle.Id(puzzleId)))
|
||||
case _ => handlerBadValue(s"Invalid puzzle round id $v")
|
||||
}
|
||||
},
|
||||
id => BSONString(id.toString)
|
||||
)
|
||||
|
||||
implicit val RoundBSONHandler = Macros.handler[Round]
|
||||
implicit val RoundHandler = new BSON[PuzzleRound] {
|
||||
import PuzzleRound.BSONFields._
|
||||
def reads(r: BSON.Reader) = PuzzleRound(
|
||||
id = r.get[PuzzleRound.Id](id),
|
||||
date = r.date(date),
|
||||
win = r.bool(win),
|
||||
vote = r.boolO(vote),
|
||||
weight = r.intO(weight)
|
||||
)
|
||||
def writes(w: BSON.Writer, r: PuzzleRound) =
|
||||
$doc(
|
||||
id -> r.id,
|
||||
date -> r.date,
|
||||
win -> r.win,
|
||||
vote -> r.vote,
|
||||
weight -> r.weight
|
||||
)
|
||||
}
|
||||
|
||||
implicit val PathIdBSONHandler: BSONHandler[Puzzle.PathId] = stringIsoHandler(Puzzle.pathIdIso)
|
||||
}
|
||||
|
|
|
@ -5,12 +5,11 @@ import org.joda.time.DateTime
|
|||
import Puzzle.{ BSONFields => F }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.AsyncColl
|
||||
import lila.db.dsl._
|
||||
import lila.memo.CacheApi._
|
||||
|
||||
final private[puzzle] class Daily(
|
||||
coll: AsyncColl,
|
||||
colls: PuzzleColls,
|
||||
renderer: lila.hub.actors.Renderer,
|
||||
cacheApi: lila.memo.CacheApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
@ -45,7 +44,7 @@ final private[puzzle] class Daily(
|
|||
}
|
||||
|
||||
private def findCurrent =
|
||||
coll {
|
||||
colls.puzzle {
|
||||
_.find(
|
||||
$doc(F.day $gt DateTime.now.minusMinutes(24 * 60 - 15))
|
||||
)
|
||||
|
@ -53,7 +52,7 @@ final private[puzzle] class Daily(
|
|||
}
|
||||
|
||||
private def findNew =
|
||||
coll { c =>
|
||||
colls.puzzle { c =>
|
||||
c.find($doc(F.day $exists false))
|
||||
.sort($doc(F.vote -> -1))
|
||||
.one[Puzzle] flatMap {
|
||||
|
|
|
@ -7,19 +7,23 @@ import play.api.Configuration
|
|||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
import lila.common.config._
|
||||
import lila.db.AsyncColl
|
||||
|
||||
@Module
|
||||
private class PuzzleConfig(
|
||||
@ConfigName("mongodb.uri") val mongoUri: String,
|
||||
@ConfigName("collection.puzzle") val puzzleColl: CollName,
|
||||
@ConfigName("collection.round") val roundColl: CollName,
|
||||
@ConfigName("collection.vote") val voteColl: CollName,
|
||||
@ConfigName("collection.head") val headColl: CollName,
|
||||
@ConfigName("collection.path") val pathColl: CollName,
|
||||
@ConfigName("api.token") val apiToken: Secret,
|
||||
@ConfigName("animation.duration") val animationDuration: FiniteDuration
|
||||
)
|
||||
|
||||
case class RoundRepo(coll: lila.db.AsyncColl)
|
||||
case class PuzzleColls(
|
||||
puzzle: AsyncColl,
|
||||
round: AsyncColl,
|
||||
path: AsyncColl
|
||||
)
|
||||
|
||||
@Module
|
||||
final class Env(
|
||||
|
@ -38,45 +42,29 @@ final class Env(
|
|||
|
||||
private val config = appConfig.get[PuzzleConfig]("puzzle")(AutoConfig.loader)
|
||||
|
||||
private lazy val db = mongo.asyncDb("puzzle", config.mongoUri)
|
||||
private def puzzleColl = db(config.puzzleColl)
|
||||
private def roundColl = db(config.roundColl)
|
||||
private def voteColl = db(config.voteColl)
|
||||
private def headColl = db(config.headColl)
|
||||
private lazy val db = mongo.asyncDb("puzzle", config.mongoUri)
|
||||
|
||||
lazy val colls = PuzzleColls(
|
||||
puzzle = db(config.puzzleColl),
|
||||
round = db(config.roundColl),
|
||||
path = db(config.pathColl)
|
||||
)
|
||||
|
||||
private lazy val gameJson: GameJson = wire[GameJson]
|
||||
|
||||
lazy val jsonView = wire[JsonView]
|
||||
|
||||
lazy val api = new PuzzleApi(
|
||||
puzzleColl = puzzleColl,
|
||||
roundColl = roundColl,
|
||||
voteColl = voteColl,
|
||||
headColl = headColl,
|
||||
cacheApi = cacheApi
|
||||
)
|
||||
lazy val api: PuzzleApi = wire[PuzzleApi]
|
||||
|
||||
lazy val roundRepo = RoundRepo(roundColl)
|
||||
lazy val cursorApi: PuzzleCursorApi = wire[PuzzleCursorApi]
|
||||
|
||||
lazy val finisher = new Finisher(
|
||||
historyApi = historyApi,
|
||||
userRepo = userRepo,
|
||||
api = api,
|
||||
puzzleColl = puzzleColl
|
||||
)
|
||||
lazy val finisher = wire[Finisher]
|
||||
|
||||
lazy val forms = PuzzleForm
|
||||
|
||||
lazy val daily = new Daily(
|
||||
puzzleColl,
|
||||
renderer,
|
||||
cacheApi = cacheApi
|
||||
)
|
||||
lazy val daily = wire[Daily]
|
||||
|
||||
lazy val activity = new PuzzleActivity(
|
||||
puzzleColl = puzzleColl,
|
||||
roundColl = roundColl
|
||||
)
|
||||
lazy val activity = wire[PuzzleActivity]
|
||||
|
||||
def cli =
|
||||
new lila.common.Cli {
|
||||
|
|
|
@ -13,10 +13,10 @@ final private[puzzle] class Finisher(
|
|||
api: PuzzleApi,
|
||||
userRepo: UserRepo,
|
||||
historyApi: lila.history.HistoryApi,
|
||||
puzzleColl: AsyncColl
|
||||
colls: PuzzleColls
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
def apply(puzzle: Puzzle, user: User, result: Result, isStudent: Boolean): Fu[Round] =
|
||||
def apply(puzzle: Puzzle, user: User, result: Result, isStudent: Boolean): Fu[PuzzleRound] =
|
||||
api.round.find(user, puzzle) flatMap { prevRound =>
|
||||
val now = DateTime.now
|
||||
val formerUserRating = user.perfs.puzzle.intRating
|
||||
|
@ -29,8 +29,8 @@ final private[puzzle] class Finisher(
|
|||
user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, now)
|
||||
val round = prevRound
|
||||
.fold(
|
||||
Round(
|
||||
id = Round.Id(user.id, puzzle.id),
|
||||
PuzzleRound(
|
||||
id = PuzzleRound.Id(user.id, puzzle.id),
|
||||
date = now,
|
||||
win = result.win,
|
||||
vote = none,
|
||||
|
@ -62,7 +62,7 @@ final private[puzzle] class Finisher(
|
|||
private val system = new RatingCalculator(VOLATILITY, TAU)
|
||||
|
||||
def incPuzzlePlays(puzzle: Puzzle): Funit =
|
||||
puzzleColl.map(_.incFieldUnchecked($id(puzzle.id.value), Puzzle.BSONFields.plays))
|
||||
colls.puzzle.map(_.incFieldUnchecked($id(puzzle.id.value), Puzzle.BSONFields.plays))
|
||||
|
||||
private def updateRatings(u1: Rating, u2: Rating, result: Glicko.Result): Unit = {
|
||||
val results = new RatingPeriodResults()
|
||||
|
|
|
@ -19,7 +19,7 @@ final class JsonView(
|
|||
def apply(
|
||||
puzzle: Puzzle,
|
||||
user: Option[User],
|
||||
round: Option[Round] = None
|
||||
round: Option[PuzzleRound] = None
|
||||
): Fu[JsObject] = {
|
||||
gameJson(
|
||||
gameId = puzzle.gameId,
|
||||
|
|
|
@ -35,7 +35,7 @@ object Puzzle {
|
|||
|
||||
case class Id(value: String) extends AnyVal with StringValue
|
||||
|
||||
case class Line(initial: Uci.Move, first: Uci.Move, more: List[(Uci.Move, Uci.Move)])
|
||||
case class PathId(value: String) extends AnyVal with StringValue
|
||||
|
||||
case class UserResult(
|
||||
puzzleId: Id,
|
||||
|
@ -55,5 +55,6 @@ object Puzzle {
|
|||
val plays = "plays"
|
||||
}
|
||||
|
||||
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
|
||||
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
|
||||
implicit val pathIdIso = lila.common.Iso.string[PathId](PathId.apply, _.value)
|
||||
}
|
||||
|
|
|
@ -13,8 +13,7 @@ import lila.db.dsl._
|
|||
import lila.user.User
|
||||
|
||||
final class PuzzleActivity(
|
||||
puzzleColl: AsyncColl,
|
||||
roundColl: AsyncColl
|
||||
colls: PuzzleColls
|
||||
)(implicit
|
||||
ec: scala.concurrent.ExecutionContext,
|
||||
system: akka.actor.ActorSystem
|
||||
|
@ -26,11 +25,11 @@ final class PuzzleActivity(
|
|||
|
||||
def stream(config: Config): Source[String, _] =
|
||||
Source futureSource {
|
||||
roundColl.map {
|
||||
_.find($doc("_id" $startsWith s"${config.user.id}${Round.idSep}"))
|
||||
colls.round.map {
|
||||
_.find($doc("_id" $startsWith s"${config.user.id}${PuzzleRound.idSep}"))
|
||||
.sort($sort desc "_id")
|
||||
.batchSize(config.perSecond.value)
|
||||
.cursor[Round](ReadPreference.secondaryPreferred)
|
||||
.cursor[PuzzleRound](ReadPreference.secondaryPreferred)
|
||||
.documentSource()
|
||||
.take(config.max | Int.MaxValue)
|
||||
.grouped(config.perSecond.value)
|
||||
|
@ -43,8 +42,8 @@ final class PuzzleActivity(
|
|||
}
|
||||
}
|
||||
|
||||
private def enrich(rounds: Seq[Round]): Fu[Seq[JsObject]] =
|
||||
puzzleColl {
|
||||
private def enrich(rounds: Seq[PuzzleRound]): Fu[Seq[JsObject]] =
|
||||
colls.puzzle {
|
||||
_.primitiveMap[Puzzle.Id, Double](
|
||||
ids = rounds.map(_.id.puzzleId),
|
||||
field = "perf.gl.r",
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
package lila.puzzle
|
||||
|
||||
import Puzzle.{ BSONFields => F }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.AsyncColl
|
||||
import lila.db.dsl._
|
||||
import lila.user.User
|
||||
import Puzzle.{ BSONFields => F }
|
||||
import lila.memo.CacheApi
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final private[puzzle] class PuzzleApi(
|
||||
puzzleColl: AsyncColl,
|
||||
roundColl: AsyncColl,
|
||||
voteColl: AsyncColl,
|
||||
headColl: AsyncColl,
|
||||
cacheApi: lila.memo.CacheApi
|
||||
colls: PuzzleColls
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import Puzzle.BSONFields._
|
||||
|
@ -21,32 +18,22 @@ final private[puzzle] class PuzzleApi(
|
|||
object puzzle {
|
||||
|
||||
def find(id: Puzzle.Id): Fu[Option[Puzzle]] =
|
||||
puzzleColl(_.byId[Puzzle](id.value))
|
||||
colls.puzzle(_.byId[Puzzle](id.value))
|
||||
|
||||
def delete(id: Puzzle.Id): Funit =
|
||||
puzzleColl(_.delete.one($id(id.value))).void
|
||||
colls.puzzle(_.delete.one($id(id.value))).void
|
||||
}
|
||||
|
||||
object round {
|
||||
|
||||
def find(user: User, puzzle: Puzzle): Fu[Option[Round]] =
|
||||
roundColl(_.byId[Round](Round.Id(user.id, puzzle.id).toString))
|
||||
def find(user: User, puzzle: Puzzle): Fu[Option[PuzzleRound]] =
|
||||
colls.round(_.byId[PuzzleRound](PuzzleRound.Id(user.id, puzzle.id).toString))
|
||||
|
||||
def upsert(a: Round) = roundColl(_.update.one($id(a.id), a, upsert = true))
|
||||
def upsert(a: PuzzleRound) = colls.round(_.update.one($id(a.id), a, upsert = true))
|
||||
|
||||
def addDenormalizedUser(a: Round, user: User) = roundColl(
|
||||
_.updateField($id(a.id), Round.BSONFields.user, user.id).void
|
||||
def addDenormalizedUser(a: PuzzleRound, user: User): Funit = colls.round(
|
||||
_.updateField($id(a.id), PuzzleRound.BSONFields.user, user.id).void
|
||||
)
|
||||
|
||||
// def reset(user: User) =
|
||||
// roundColl {
|
||||
// _.delete.one(
|
||||
// $doc(
|
||||
// Round.BSONFields.id $startsWith s"${user.id}:"
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// object vote {
|
||||
|
@ -91,25 +78,4 @@ final private[puzzle] class PuzzleApi(
|
|||
// } inject (p2 -> v2)
|
||||
// }
|
||||
// }
|
||||
|
||||
// object head {
|
||||
|
||||
// def find(user: User): Fu[Option[PuzzleHead]] = headColl(_.byId[PuzzleHead](user.id))
|
||||
|
||||
// def set(h: PuzzleHead) = headColl(_.update.one($id(h.id), h, upsert = true).void)
|
||||
|
||||
// def addNew(user: User, puzzleId: PuzzleId) = set(PuzzleHead(user.id, puzzleId.some, puzzleId))
|
||||
|
||||
// def currentPuzzleId(user: User): Fu[Option[PuzzleId]] =
|
||||
// find(user) dmap2 { (h: PuzzleHead) =>
|
||||
// h.current | h.last
|
||||
// }
|
||||
|
||||
// private[puzzle] def solved(user: User, id: PuzzleId): Funit =
|
||||
// head find user flatMap { headOption =>
|
||||
// set {
|
||||
// PuzzleHead(user.id, none, headOption.fold(id)(head => id atLeast head.last))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package lila.puzzle
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.memo.CacheApi
|
||||
import lila.rating.{ Perf, PerfType }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private case class PuzzleCursor(
|
||||
path: Puzzle.PathId,
|
||||
previousPaths: Set[Puzzle.PathId],
|
||||
positionInPath: Int
|
||||
) {
|
||||
def switchTo(pathId: Puzzle.PathId) = copy(
|
||||
path = pathId,
|
||||
previousPaths = previousPaths + pathId,
|
||||
positionInPath = 0
|
||||
)
|
||||
def next = copy(positionInPath = positionInPath + 1)
|
||||
}
|
||||
|
||||
final class PuzzleCursorApi(colls: PuzzleColls, cacheApi: CacheApi, userRepo: UserRepo)(implicit
|
||||
ec: ExecutionContext
|
||||
) {
|
||||
|
||||
import BsonHandlers._
|
||||
import Puzzle.PathId
|
||||
|
||||
private[puzzle] def cursorOf(user: User): Fu[PuzzleCursor] =
|
||||
cursors.get(user.id)
|
||||
|
||||
sealed private trait NextPuzzleResult
|
||||
private object NextPuzzleResult {
|
||||
case object PathMissing extends NextPuzzleResult
|
||||
case object PathEnded extends NextPuzzleResult
|
||||
case class PuzzleMissing(id: Puzzle.Id) extends NextPuzzleResult
|
||||
case class PuzzleAlreadyPlayed(puzzle: Puzzle) extends NextPuzzleResult
|
||||
case class PuzzleFound(puzzle: Puzzle) extends NextPuzzleResult
|
||||
}
|
||||
|
||||
def nextPuzzleFor(user: User, isRetry: Boolean = false): Fu[Puzzle] =
|
||||
cursorOf(user) flatMap { cursor =>
|
||||
import NextPuzzleResult._
|
||||
nextPuzzleResult(user, cursor).thenPp flatMap {
|
||||
case PathMissing | PathEnded if !isRetry =>
|
||||
nextPathIdFor(user.id, cursor.previousPaths) flatMap {
|
||||
case None => fufail(s"No remaining puzzle path for ${user.id}")
|
||||
case Some(pathId) =>
|
||||
val newCursor = cursor switchTo pathId
|
||||
cursors.put(user.id, fuccess(newCursor))
|
||||
nextPuzzleFor(user, isRetry = true)
|
||||
}
|
||||
case PathMissing | PathEnded => fufail(s"Puzzle patth missing or ended for ${user.id}")
|
||||
case PuzzleMissing(id) =>
|
||||
logger.warn(s"Puzzle missing: $id")
|
||||
cursors.put(user.id, fuccess(cursor.next))
|
||||
nextPuzzleFor(user, isRetry = isRetry)
|
||||
case PuzzleAlreadyPlayed(_) =>
|
||||
cursors.put(user.id, fuccess(cursor.next))
|
||||
nextPuzzleFor(user, isRetry = isRetry)
|
||||
case PuzzleFound(puzzle) => fuccess(puzzle)
|
||||
}
|
||||
}
|
||||
|
||||
private def nextPuzzleResult(user: User, cursor: PuzzleCursor): Fu[NextPuzzleResult] =
|
||||
colls.path {
|
||||
_.aggregateOne() { framework =>
|
||||
import framework._
|
||||
Match($id(cursor.path)) -> List(
|
||||
Project($doc("puzzleId" -> $doc("$arrayElemAt" -> $arr("$ids", 0)))),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> colls.puzzle.name.value,
|
||||
"localField" -> "puzzleId",
|
||||
"foreignField" -> "_id",
|
||||
"as" -> "puzzle"
|
||||
)
|
||||
)
|
||||
),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> colls.round.name.value,
|
||||
"let" -> $doc(
|
||||
"roundId" -> $doc("$concat" -> $arr(s"${user.id}${PuzzleRound.idSep}", "$puzzleId"))
|
||||
),
|
||||
"pipeline" -> $arr(
|
||||
$doc("$match" -> $id("$$roundId")),
|
||||
// $doc("$match" -> $id("thibault:313og")),
|
||||
$doc("$project" -> $doc("i" -> "$$roundId"))
|
||||
),
|
||||
"as" -> "round"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}.map { docOpt =>
|
||||
import NextPuzzleResult._
|
||||
println(docOpt map lila.db.BSON.debug)
|
||||
docOpt.fold[NextPuzzleResult](PathMissing) { doc =>
|
||||
doc.getAsOpt[Puzzle.Id]("puzzleId").fold[NextPuzzleResult](PathEnded) { puzzleId =>
|
||||
doc
|
||||
.getAsOpt[List[Puzzle]]("puzzle")
|
||||
.flatMap(_.headOption)
|
||||
.fold[NextPuzzleResult](PuzzleMissing(puzzleId)) { puzzle =>
|
||||
if (doc.getAsOpt[List[Bdoc]]("round").exists(_.nonEmpty)) PuzzleAlreadyPlayed(puzzle)
|
||||
else PuzzleFound(puzzle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val cursors = cacheApi[User.ID, PuzzleCursor](32768, "puzzle.cursor")(
|
||||
_.expireAfterWrite(1 hour)
|
||||
.buildAsyncFuture { userId =>
|
||||
nextPathIdFor(userId, Set.empty)
|
||||
.orFail(s"No puzzle path found for $userId")
|
||||
.dmap(pathId => PuzzleCursor(pathId, Set.empty, 0))
|
||||
}
|
||||
)
|
||||
|
||||
private def nextPathIdFor(userId: User.ID, previousPaths: Set[PathId]): Fu[Option[PathId]] =
|
||||
userRepo.perfOf(userId, PerfType.Puzzle).dmap(_ | Perf.default) flatMap { perf =>
|
||||
colls.path {
|
||||
_.aggregateOne() { framework =>
|
||||
import framework._
|
||||
Match(
|
||||
$doc(
|
||||
"tier" -> "top",
|
||||
"min" $lte perf.glicko.rating,
|
||||
"max" $gt perf.glicko.rating,
|
||||
"_id" $nin previousPaths
|
||||
)
|
||||
) -> List(
|
||||
Project($id(true)),
|
||||
Sample(1)
|
||||
)
|
||||
}.dmap(_.flatMap(_.getAsOpt[PathId]("_id")))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@ import org.joda.time.DateTime
|
|||
|
||||
import lila.user.User
|
||||
|
||||
case class Round(
|
||||
id: Round.Id,
|
||||
case class PuzzleRound(
|
||||
id: PuzzleRound.Id,
|
||||
date: DateTime,
|
||||
win: Boolean,
|
||||
vote: Option[Boolean],
|
||||
|
@ -13,7 +13,7 @@ case class Round(
|
|||
weight: Option[Int]
|
||||
) {}
|
||||
|
||||
object Round {
|
||||
object PuzzleRound {
|
||||
|
||||
val idSep = ':'
|
||||
|
Loading…
Reference in New Issue