lila/modules/puzzle/src/main/DailyPuzzle.scala

114 lines
3.5 KiB
Scala

package lila.puzzle
import akka.pattern.ask
import org.joda.time.DateTime
import Puzzle.{ BSONFields => F }
import scala.concurrent.duration._
import lila.db.dsl._
import lila.memo.CacheApi._
import lila.common.ThreadLocalRandom
final private[puzzle] class DailyPuzzle(
colls: PuzzleColls,
pathApi: PuzzlePathApi,
renderer: lila.hub.actors.Renderer,
cacheApi: lila.memo.CacheApi
)(implicit ec: scala.concurrent.ExecutionContext) {
import BsonHandlers._
private val cache =
cacheApi.unit[Option[DailyPuzzle.WithHtml]] {
_.refreshAfterWrite(1 minutes)
.buildAsyncFuture(_ => find)
}
def get: Fu[Option[DailyPuzzle.WithHtml]] = cache.getUnit
private def find: Fu[Option[DailyPuzzle.WithHtml]] =
(findCurrent orElse findNewBiased()) recover { case e: Exception =>
logger.error("find daily", e)
none
} flatMap { _ ?? makeDaily }
private def makeDaily(puzzle: Puzzle): Fu[Option[DailyPuzzle.WithHtml]] = {
import makeTimeout.short
renderer.actor ? DailyPuzzle.Render(puzzle, puzzle.fenAfterInitialMove, puzzle.line.head.uci) map {
case html: String => DailyPuzzle.WithHtml(puzzle, html).some
}
} recover { case e: Exception =>
logger.warn("make daily", e)
none
}
private def findCurrent =
colls.puzzle {
_.find($doc(F.day $gt DateTime.now.minusDays(1)))
.sort($sort desc F.day)
.one[Puzzle]
}
private def findNewBiased(tries: Int = 0): Fu[Option[Puzzle]] = {
def tryAgainMaybe = (tries < 5) ?? findNewBiased(tries + 1)
import lila.common.ThreadLocalRandom.odds
import PuzzleTheme._
findNew flatMap {
case None => tryAgainMaybe
case Some(p) if p.hasTheme(anastasiaMate) && !odds(3) => tryAgainMaybe dmap (_ orElse p.some)
case Some(p) if p.hasTheme(arabianMate) && odds(2) => tryAgainMaybe dmap (_ orElse p.some)
case p => fuccess(p)
}
}
private def findNew: Fu[Option[Puzzle]] =
colls
.path {
_.aggregateOne() { framework =>
import framework._
Match(pathApi.select(PuzzleTheme.mix.key, PuzzleTier.Top, 2150 to 2300)) -> List(
Sample(3),
Project($doc("ids" -> true, "_id" -> false)),
UnwindField("ids"),
PipelineOperator(
$lookup.pipeline(
from = colls.puzzle,
as = "puzzle",
local = "ids",
foreign = "_id",
pipe = List(
$doc(
"$match" -> $doc(
Puzzle.BSONFields.plays $gt 5000,
Puzzle.BSONFields.day $exists false,
Puzzle.BSONFields.themes $ne PuzzleTheme.oneMove.key.value
)
)
)
)
),
UnwindField("puzzle"),
ReplaceRootField("puzzle"),
AddFields($doc("dayScore" -> $doc("$multiply" -> $arr("$plays", "$vote")))),
Sort(Descending("dayScore")),
Limit(1)
)
}
}
.flatMap { docOpt =>
docOpt.flatMap(PuzzleBSONReader.readOpt) ?? { puzzle =>
colls.puzzle {
_.update.one($id(puzzle.id), $set(F.day -> DateTime.now))
} inject puzzle.some
}
}
}
object DailyPuzzle {
type Try = () => Fu[Option[DailyPuzzle.WithHtml]]
case class WithHtml(puzzle: Puzzle, html: String)
case class Render(puzzle: Puzzle, fen: chess.format.FEN, lastMove: String)
}