puzzle of the day on homepage

This commit is contained in:
Thibault Duplessis 2014-03-15 21:19:36 +01:00
parent 015a98c238
commit 88dbc9f003
12 changed files with 120 additions and 14 deletions

View file

@ -26,7 +26,8 @@ final class Env(
relations = Env.relation.api,
leaderboard = Env.user.cached.topRatingDay.apply,
progress = Env.user.cached.topProgressDay.apply,
timelineEntries = Env.timeline.getter.userEntries _)
timelineEntries = Env.timeline.getter.userEntries _,
dailyPuzzle = Env.puzzle.daily)
lazy val userInfo = mashup.UserInfo(
countUsers = () => Env.user.countEnabled,

View file

@ -33,5 +33,8 @@ private[app] final class Renderer extends Actor {
case lila.tournament.actorApi.TournamentTable(tours) =>
sender ! V.tournament.createdTable(tours)
case lila.puzzle.RenderDaily(puzzle, fen, lastMove) =>
sender ! V.puzzle.daily(puzzle, fen, lastMove)
}
}

View file

@ -32,9 +32,9 @@ object Lobby extends LilaController {
tours = TournamentRepo.createdUnprotected,
filter = Env.setup.filter
).map(_.fold(Redirect(_), {
case (preload, entries, posts, tours, featured, leaderboard, progress) =>
case (preload, entries, posts, tours, featured, leaderboard, progress, puzzle) =>
val response = status(html.lobby.home(
Json stringify preload, entries, posts, tours, featured, leaderboard, progress
Json stringify preload, entries, posts, tours, featured, leaderboard, progress, puzzle
))
// the session cookie is required for anon lobby filter storage
ctx.req.session.data.contains(LilaCookie.sessionId).fold(

View file

@ -7,17 +7,17 @@ import play.api.libs.json.{ Json, JsObject, JsArray }
import play.api.mvc.Call
import controllers.routes
import lila.api.Context
import lila.forum.PostLiteView
import lila.game.{ Game, GameRepo, Featured }
import lila.lobby.actorApi.GetOpen
import lila.lobby.{ Hook, HookRepo }
import lila.relation.RelationApi
import lila.setup.FilterConfig
import lila.socket.History
import lila.timeline.Entry
import lila.tournament.Created
import lila.user.User
import lila.api.Context
import lila.relation.RelationApi
import makeTimeout.large
final class Preload(
@ -27,9 +27,10 @@ final class Preload(
relations: RelationApi,
leaderboard: Int => Fu[List[User]],
progress: Int => Fu[List[User]],
timelineEntries: String => Fu[List[Entry]]) {
timelineEntries: String => Fu[List[Entry]],
dailyPuzzle: () => Fu[Option[lila.puzzle.DailyPuzzle]]) {
private type RightResponse = (JsObject, List[Entry], List[PostLiteView], List[Created], Option[Game], List[User], List[User])
private type RightResponse = (JsObject, List[Entry], List[PostLiteView], List[Created], Option[Game], List[User], List[User], Option[lila.puzzle.DailyPuzzle])
private type Response = Either[Call, RightResponse]
def apply(
@ -44,13 +45,14 @@ final class Preload(
(ctx.userId ?? timelineEntries) zip
leaderboard(10) zip
progress(10) zip
dailyPuzzle() zip
filter map {
case ((((((((hooks, posts), tours), feat), blocks), entries), leaderboard), progress), filter) =>
case (((((((((hooks, posts), tours), feat), blocks), entries), leaderboard), progress), puzzle), filter) =>
(Right((Json.obj(
"version" -> history.version,
"pool" -> JsArray(hooks map (_.render)),
"filter" -> filter.render,
"blocks" -> blocks
), entries, posts, tours, feat, leaderboard, progress)))
), entries, posts, tours, feat, leaderboard, progress, puzzle)))
}
}

View file

@ -1,4 +1,4 @@
@(preload: String, userTimeline: List[lila.timeline.Entry], forumRecent: List[lila.forum.PostLiteView], tours: List[lila.tournament.Created], featured: Option[Game], leaderboard: List[User], progress: List[User])(implicit ctx: Context)
@(preload: String, userTimeline: List[lila.timeline.Entry], forumRecent: List[lila.forum.PostLiteView], tours: List[lila.tournament.Created], featured: Option[Game], leaderboard: List[User], progress: List[User], puzzle: Option[lila.puzzle.DailyPuzzle])(implicit ctx: Context)
@underchat = {
<div id="featured_game" title="@trans.watchLichessTV()">
@ -46,7 +46,7 @@ themepicker = true) {
@translationCall.map(i18n.callBox(_))
</div>
<div class="clearfix lichess_homepage">
<div class="lichess_board_wrap lichess_player_white">
<div class="lichess_board_wrap">
<div id="hooks_wrap">
<div class="tabs">
<a data-tab="list" class="list">@trans.list()</a>
@ -78,6 +78,15 @@ themepicker = true) {
<a class="lichess_button button config_@b.code" href="@b.route" onclick="return false" title="@b.title()">@b.name()</a>
}
</div>
@puzzle.map { p =>
<div id="daily_puzzle">
@p.html
<div class="vstext">
@trans.puzzleOfTheDay()<br />
@trans.findTheBestMoveForColor(p.color.name)
</div>
</div>
}
</div>
@lobby.undertable(forumRecent, tours, leaderboard, progress)
</div>

View file

@ -0,0 +1,7 @@
@(p: lila.puzzle.Puzzle, fen: String, lastMove: String)
<a href="@routes.Puzzle.show(p.id)"
class="mini_board parse_fen"
data-color="@p.color"
data-fen="@fen"
data-lastmove="@lastMove"></a>

View file

@ -272,6 +272,7 @@ toTrackYourProgress=To track your progress:
trainingSignupExplanation=Lichess will provide puzzles that match your ability, making for better training sessions.
recentlyPlayedPuzzles=Recently played puzzles
puzzleId=Puzzle %s
puzzleOfTheDay=Puzzle of the day
goodMove=Good move
butYouCanDoBetter=But you can do better.
bestMove=Best move!

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,65 @@
package lila.puzzle
import scala.concurrent.duration._
import akka.actor.{ ActorSelection, Scheduler }
import akka.pattern.ask
import org.joda.time.DateTime
import reactivemongo.bson.BSONDocument
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.Types.Coll
private[puzzle] final class Daily(
coll: Coll,
renderer: ActorSelection,
scheduler: Scheduler) {
private val cache =
lila.memo.AsyncCache.single[Option[DailyPuzzle]](f = find, timeToLive = 30 minutes)
def apply(): Fu[Option[DailyPuzzle]] = cache apply true
private def find: Fu[Option[DailyPuzzle]] = findCurrent orElse findNew flatMap {
case Some(puzzle) => makeDaily(puzzle)
case None =>
scheduler.scheduleOnce(10.seconds)(cache.clear)
fuccess(none)
}
private def makeDaily(puzzle: Puzzle): Fu[Option[DailyPuzzle]] = {
import chess.format.{ UciMove, Forsyth }
import makeTimeout.short
~(for {
sit1 <- Forsyth << puzzle.fen
move <- puzzle.history.lastOption
uci <- UciMove(move)
sit2 <- sit1.move(uci.orig, uci.dest, uci.promotion).toOption map (_.situationAfter)
fen = Forsyth >> sit2
} yield renderer ? RenderDaily(puzzle, fen, move) map {
case html: play.api.templates.Html => DailyPuzzle(html, puzzle.color).some
})
} recover {
case e: Exception =>
play.api.Logger("daily puzzle").warn(e.getMessage)
none
}
private def findCurrent = coll.find(
BSONDocument("day" -> BSONDocument("$gt" -> DateTime.now.minusDays(1)))
).one[Puzzle]
private def findNew = coll.find(
BSONDocument("day" -> BSONDocument("$exists" -> false))
).sort(BSONDocument("vote.sum" -> -1)).one[Puzzle] flatMap {
case Some(puzzle) => coll.update(
BSONDocument("_id" -> puzzle.id),
BSONDocument("$set" -> BSONDocument("day" -> DateTime.now))
) inject puzzle.some
case None => fuccess(none)
}
}
case class DailyPuzzle(html: play.api.templates.Html, color: chess.Color)
case class RenderDaily(puzzle: Puzzle, fen: String, lastMove: String)

View file

@ -1,6 +1,6 @@
package lila.puzzle
import akka.actor.{ ActorSystem, Props }
import akka.actor.{ ActorSelection, ActorSystem, Props }
import com.typesafe.config.Config
import lila.common.PimpedConfig._
@ -8,6 +8,7 @@ import lila.common.PimpedConfig._
final class Env(
config: Config,
db: lila.db.Env,
renderer: ActorSelection,
system: ActorSystem) {
private val settings = new {
@ -32,6 +33,12 @@ final class Env(
lazy val forms = DataForm
lazy val daily = new Daily(
puzzleColl,
renderer,
system.scheduler
).apply _
private[puzzle] lazy val puzzleColl = db(CollectionPuzzle)
private[puzzle] lazy val attemptColl = db(CollectionAttempt)
}
@ -41,5 +48,6 @@ object Env {
lazy val current: Env = "[boot] puzzle" describes new Env(
config = lila.common.PlayApp loadConfig "puzzle",
db = lila.db.Env.current,
renderer = lila.hub.Env.current.actor.renderer,
system = lila.common.PlayApp.system)
}

View file

@ -513,7 +513,7 @@ div.under_chat {
width: 224px;
margin-left: -28px;
position: absolute;
top: 530px;
top: 529px;
left: 0;
}
div.under_chat .watchtv {

View file

@ -1250,6 +1250,15 @@ div.vstext .right {
float: right;
text-align: right;
}
#daily_puzzle {
width: 224px;
position: absolute;
top: 529px;
left: 0;
}
#daily_puzzle > div.vstext {
text-align: center;
}
div.extra_top {
float: right;
margin: 20px 20px 0 0;