Merge branch 'master' into antichess
* master: (116 commits) use Game.updatedAt when last move time is not available integrate analysis board with correspondence games improve analysis integration add screenshot fix analysis board highlights more cache tweaks don't show games older than 5 minutes improve current game detection hr "hrvatski" translation #11283. Author: gus_fring. update sl translation hu "Magyar" translation #11279. Author: OMMHOA. Couldn't translate perfectly "proceed" so it's "go" instead. ca "Català, valencià" translation #11278. Author: pedrolo. uk "українська" translation #11276. Author: IvTK. nl "Nederlands" translation #11274. Author: rokbe. correspondensie -> correspondentie sv "svenska" translation #11273. Author: nuwonga. sq "Shqip" translation #11271. Author: xhevati. pl "polski" translation #11268. Author: pirouetti. tr "Türkçe" translation #11265. Author: mabolek. sv "svenska" translation #11264. Author: Weckipecki. ca "Català, valencià" translation #11263. Author: Borchess. ... Conflicts: modules/chess ui/analyse/src/ctrl.jspull/185/head
commit
b96b982209
|
@ -1,6 +1,8 @@
|
|||
[lichess.org](http://lichess.org)
|
||||
---------------------------------
|
||||
|
||||
<img src="https://raw.githubusercontent.com/ornicar/lila/master/public/images/homepage_light.1200.png" alt="lichess.org" />
|
||||
|
||||
It's a free online chess game focused on [realtime](http://lichess.org/games) and ease of use
|
||||
|
||||
It has a [search engine](http://lichess.org/games/search),
|
||||
|
|
|
@ -20,7 +20,7 @@ final class Env(
|
|||
lobby = Env.lobby.lobby,
|
||||
lobbyVersion = () => Env.lobby.history.version,
|
||||
featured = Env.tv.featured,
|
||||
leaderboard = Env.user.cached.topToday.apply,
|
||||
leaderboard = Env.user.cached.topToday,
|
||||
tourneyWinners = Env.tournament.winners.scheduled,
|
||||
timelineEntries = Env.timeline.getter.userEntries _,
|
||||
dailyPuzzle = Env.puzzle.daily,
|
||||
|
|
|
@ -33,10 +33,10 @@ object Game extends LilaController with BaseGame {
|
|||
}
|
||||
}
|
||||
|
||||
def realtime = Open { implicit ctx =>
|
||||
def playing = Open { implicit ctx =>
|
||||
GameRepo.featuredCandidates map lila.tv.Featured.sort map (_ take 9) zip
|
||||
makeListMenu map {
|
||||
case (games, menu) => html.game.realtime(games, menu)
|
||||
case (games, menu) => html.game.playing(games, menu)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,10 +54,10 @@ object Round extends LilaController with TheftPrevention {
|
|||
PreventTheft(pov) {
|
||||
(pov.game.tournamentId ?? TournamentRepo.byId) zip
|
||||
Env.game.crosstableApi(pov.game) zip
|
||||
otherGames(pov.game) flatMap {
|
||||
case ((tour, crosstable), otherGames) =>
|
||||
otherPovs(pov.gameId) flatMap {
|
||||
case ((tour, crosstable), playing) =>
|
||||
Env.api.roundApi.player(pov, Env.api.version) map { data =>
|
||||
Ok(html.round.player(pov, data, tour = tour, cross = crosstable, otherGames = otherGames))
|
||||
Ok(html.round.player(pov, data, tour = tour, cross = crosstable, playing = playing))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -69,11 +69,30 @@ object Round extends LilaController with TheftPrevention {
|
|||
}
|
||||
}
|
||||
|
||||
private def otherGames(g: GameModel)(implicit ctx: Context) = ctx.me.ifFalse(g.hasClock) ?? { user =>
|
||||
private def otherPovs(gameId: String)(implicit ctx: Context) = ctx.me ?? { user =>
|
||||
GameRepo nowPlaying user map {
|
||||
_ filter { pov =>
|
||||
pov.isMyTurn && pov.game.id != g.id
|
||||
} sortBy Pov.priority
|
||||
_ filter { _.game.id != gameId }
|
||||
}
|
||||
}
|
||||
|
||||
private def getNext(currentGame: GameModel)(povs: List[Pov])(implicit ctx: Context) =
|
||||
povs find { pov =>
|
||||
pov.isMyTurn && (pov.game.hasClock || !currentGame.hasClock)
|
||||
} map (_.fullId)
|
||||
|
||||
def others(gameId: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo game gameId) { currentGame =>
|
||||
otherPovs(gameId) map { povs =>
|
||||
Ok(html.round.others(povs, nextId = getNext(currentGame)(povs)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def next(gameId: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo game gameId) { currentGame =>
|
||||
otherPovs(gameId) map getNext(currentGame) map { nextId =>
|
||||
Ok(Json.obj("next" -> nextId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ object User extends LilaController {
|
|||
|
||||
def tv(username: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
(GameRepo nowPlaying user.id) orElse
|
||||
(GameRepo lastPlayed user.id) flatMap {
|
||||
_.flatMap { Pov(_, user) }.fold(fuccess(Redirect(routes.User.show(username)))) { pov =>
|
||||
(GameRepo lastPlayed user) orElse
|
||||
(GameRepo lastPlayed user) flatMap {
|
||||
_.fold(fuccess(Redirect(routes.User.show(username)))) { pov =>
|
||||
Round.watch(pov, userTv = user.some)
|
||||
}
|
||||
}
|
||||
|
@ -38,12 +38,12 @@ object User extends LilaController {
|
|||
|
||||
def showMini(username: String) = Open { implicit ctx =>
|
||||
OptionFuResult(UserRepo named username) { user =>
|
||||
GameRepo nowPlaying user.id zip
|
||||
GameRepo lastPlayed user zip
|
||||
(ctx.userId ?? { relationApi.blocks(user.id, _) }) zip
|
||||
(ctx.isAuth ?? { Env.pref.api.followable(user.id) }) zip
|
||||
(ctx.userId ?? { relationApi.relation(_, user.id) }) map {
|
||||
case (((game, blocked), followable), relation) =>
|
||||
Ok(html.user.mini(user, game, blocked, followable, relation))
|
||||
case (((pov, blocked), followable), relation) =>
|
||||
Ok(html.user.mini(user, pov, blocked, followable, relation))
|
||||
.withHeaders(CACHE_CONTROL -> "max-age=5")
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ object User extends LilaController {
|
|||
user -> user.count.game
|
||||
}
|
||||
nbWeek ← Env.game.cached activePlayerUidsWeek nb flatMap { pairs =>
|
||||
UserRepo.byOrderedIds(pairs.map(_._1)) map (_ zip pairs.map(_._2))
|
||||
UserRepo.byOrderedIds(pairs.map(_.userId)) map (_ zip pairs.map(_.nb))
|
||||
}
|
||||
tourneyWinners ← Env.tournament.winners scheduled nb
|
||||
online ← env.cached topOnline 30
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package controllers
|
||||
|
||||
import chess.format.Forsyth
|
||||
import chess.{ Situation, Variant }
|
||||
import play.api.libs.json.Json
|
||||
|
||||
import lila.app._
|
||||
import lila.game.{ GameRepo, Pov }
|
||||
import views._
|
||||
|
||||
object UserAnalysis extends LilaController with BaseGame {
|
||||
|
||||
def index = load("")
|
||||
|
||||
def load(urlFen: String) = Open { implicit ctx =>
|
||||
val fenStr = Some(urlFen.trim.replace("_", " ")).filter(_.nonEmpty) orElse get("fen")
|
||||
val decodedFen = fenStr.map { java.net.URLDecoder.decode(_, "UTF-8").trim }.filter(_.nonEmpty)
|
||||
val situation = (decodedFen flatMap Forsyth.<<< map (_.situation)) | Situation(Variant.Standard)
|
||||
val pov = makePov(situation)
|
||||
val data = Env.round.jsonView.userAnalysisJson(pov, ctx.pref)
|
||||
makeListMenu map { listMenu =>
|
||||
Ok(html.board.userAnalysis(listMenu, data, none))
|
||||
}
|
||||
}
|
||||
|
||||
private def makePov(situation: Situation) = lila.game.Pov(
|
||||
lila.game.Game.make(
|
||||
game = chess.Game(situation.board, situation.color),
|
||||
whitePlayer = lila.game.Player.white,
|
||||
blackPlayer = lila.game.Player.black,
|
||||
mode = chess.Mode.Casual,
|
||||
variant = chess.Variant.Standard,
|
||||
source = lila.game.Source.Api,
|
||||
pgnImport = None,
|
||||
castles = situation.board.history.castles),
|
||||
situation.color)
|
||||
|
||||
def game(id: String, color: String) = Open { implicit ctx =>
|
||||
OptionFuOk(GameRepo game id) { game =>
|
||||
val pov = Pov(game, chess.Color(color == "white"))
|
||||
val data = Env.round.jsonView.userAnalysisJson(pov, ctx.pref)
|
||||
makeListMenu map { listMenu =>
|
||||
html.board.userAnalysis(listMenu, data, pov.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// def game(id: String) = Open { implicit ctx =>
|
||||
// OptionResult(GameRepo game id) { game =>
|
||||
// Redirect(routes.Editor.load(
|
||||
// get("fen") | (chess.format.Forsyth >> game.toChess)
|
||||
// ))
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -9,7 +9,7 @@ import controllers.routes
|
|||
import lila.api.Context
|
||||
import lila.forum.MiniForumPost
|
||||
import lila.game.{ Game, GameRepo, Pov }
|
||||
import lila.lobby.actorApi.GetOpen
|
||||
import lila.lobby.actorApi.HooksFor
|
||||
import lila.lobby.{ Hook, HookRepo, Seek, SeekApi }
|
||||
import lila.rating.PerfType
|
||||
import lila.setup.FilterConfig
|
||||
|
@ -38,7 +38,7 @@ final class Preload(
|
|||
posts: Fu[List[MiniForumPost]],
|
||||
tours: Fu[List[Enterable]],
|
||||
filter: Fu[FilterConfig])(implicit ctx: Context): Fu[Response] =
|
||||
(lobby ? GetOpen(ctx.me)).mapTo[List[Hook]] zip
|
||||
(lobby ? HooksFor(ctx.me)).mapTo[List[Hook]] zip
|
||||
posts zip
|
||||
tours zip
|
||||
featured.one zip
|
||||
|
|
|
@ -178,8 +178,9 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
ownerLink: Boolean = false,
|
||||
tv: Boolean = false,
|
||||
withTitle: Boolean = true,
|
||||
withLink: Boolean = true)(implicit ctx: UserContext) = Html {
|
||||
var isLive = game.isBeingPlayed
|
||||
withLink: Boolean = true,
|
||||
withLive: Boolean = true)(implicit ctx: UserContext) = Html {
|
||||
var isLive = withLive && game.isBeingPlayed
|
||||
val href = withLink ?? {
|
||||
val owner = ownerLink.fold(ctx.me flatMap game.player, none)
|
||||
val url = tv.fold(routes.Tv.index, owner.fold(routes.Round.watcher(game.id, color.name)) { o =>
|
||||
|
@ -194,13 +195,13 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
val lastMove = ~game.castleLastMoveTime.lastMoveString
|
||||
val variant = game.variant.key
|
||||
val tag = if (withLink) "a" else "span"
|
||||
s"""<$tag $href $title class="mini_board parse_fen is2d $cssClass $variant" data-live="$live" data-color="${color.name}" data-fen="$fen" data-lastmove="$lastMove">$miniBoardContent</$tag>"""
|
||||
s"""<$tag $href $title class="mini_board mini_board_${game.id} parse_fen is2d $cssClass $variant" data-live="$live" data-color="${color.name}" data-fen="$fen" data-lastmove="$lastMove">$miniBoardContent</$tag>"""
|
||||
}
|
||||
|
||||
def gameFenNoCtx(game: Game, color: Color, tv: Boolean = false, blank: Boolean = false) = Html {
|
||||
var isLive = game.isBeingPlayed
|
||||
val variant = game.variant.key
|
||||
s"""<a href="%s%s" title="%s" class="mini_board parse_fen is2d %s $variant" data-live="%s" data-color="%s" data-fen="%s" data-lastmove="%s"%s>$miniBoardContent</a>""".format(
|
||||
s"""<a href="%s%s" title="%s" class="mini_board mini_board_${game.id} parse_fen is2d %s $variant" data-live="%s" data-color="%s" data-fen="%s" data-lastmove="%s"%s>$miniBoardContent</a>""".format(
|
||||
blank ?? netBaseUrl,
|
||||
tv.fold(routes.Tv.index, routes.Round.watcher(game.id, color.name)),
|
||||
gameTitle(game, color),
|
||||
|
|
|
@ -12,7 +12,7 @@ final class SiteMenu(trans: I18nKeys) {
|
|||
import SiteMenu._
|
||||
|
||||
val play = new Elem("play", routes.Lobby.home, trans.play)
|
||||
val game = new Elem("game", routes.Game.realtime, trans.games)
|
||||
val game = new Elem("game", routes.Game.playing, trans.games)
|
||||
val puzzle = new Elem("puzzle", routes.Puzzle.home, trans.training)
|
||||
val tournament = new Elem("tournament", routes.Tournament.home, trans.tournaments)
|
||||
val user = new Elem("user", routes.User.list, trans.players)
|
||||
|
|
|
@ -11,7 +11,8 @@ robots: Boolean = true,
|
|||
moreCss: Html = Html(""),
|
||||
moreJs: Html = Html(""),
|
||||
zen: Boolean = false,
|
||||
openGraph: Map[Symbol, String] = Map.empty)(body: Html)(implicit ctx: Context)
|
||||
openGraph: Map[Symbol, String] = Map.empty,
|
||||
chessground: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
<!doctype html>
|
||||
<html lang="@lang.language">
|
||||
<head>
|
||||
|
@ -250,7 +251,7 @@ openGraph: Map[Symbol, String] = Map.empty)(body: Html)(implicit ctx: Context)
|
|||
<a href="@routes.Blog.index()">Blog</a> |
|
||||
<a href="@routes.QaQuestion.index()" title="Questions & Answers">Q&A</a> |
|
||||
<a href="@routes.Wiki.home" title="@trans.learnMoreAboutLichess()">Wiki</a><br />
|
||||
<a href="@routes.WorldMap.index" title="Realtime world map of chess moves">Map</a> |
|
||||
<a href="@routes.WorldMap.index" title="Real time world map of chess moves">Map</a> |
|
||||
<a href="@routes.Monitor.index">Monitor</a> |
|
||||
<a href="@routes.Page.helpLichess">Help lichess.org</a>
|
||||
</div>
|
||||
|
@ -271,12 +272,12 @@ openGraph: Map[Symbol, String] = Map.empty)(body: Html)(implicit ctx: Context)
|
|||
</div>
|
||||
}
|
||||
@jQueryTag
|
||||
@jsTag("vendor/chessground.min.js")
|
||||
@if(chessground) {@jsTag("vendor/chessground.min.js")}
|
||||
@jsTag("deps.min.js")
|
||||
@momentjsTag
|
||||
@powertipTag
|
||||
@jsTagCompiled("common.js")
|
||||
@jsTagCompiled("strongSocket.js")
|
||||
@jsTagCompiled("socket.js")
|
||||
@jsTagCompiled("big.js")
|
||||
@moreJs
|
||||
@jsAt(s"trans/${lang.language}.js")
|
||||
|
|
|
@ -30,7 +30,8 @@ LichessEditor(document.getElementById('board_editor'), {
|
|||
trans.whitePlays,
|
||||
trans.blackPlays,
|
||||
trans.playWithTheMachine,
|
||||
trans.playWithAFriend
|
||||
trans.playWithAFriend,
|
||||
trans.analysis
|
||||
)))
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
@(listMenu: lila.game.ListMenu, data: play.api.libs.json.JsObject, pov: Option[Pov])(implicit ctx: Context)
|
||||
|
||||
@moreCss = {
|
||||
@cssTag("analyse.css")
|
||||
}
|
||||
|
||||
@moreJs = {
|
||||
@jsAt(s"compiled/lichess.analyse${isProd??(".min")}.js")
|
||||
@round.jsRoutes()
|
||||
@embedJs {
|
||||
lichess = lichess || {};
|
||||
lichess.user_analysis = {
|
||||
data: @Html(play.api.libs.json.Json.stringify(data)),
|
||||
routes: roundRoutes.controllers,
|
||||
i18n: @round.jsI18n()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@game.layout(
|
||||
title = trans.analysis.str(),
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
menu = game.sideMenu(listMenu, "userAnalysis").some) {
|
||||
<div class="analyse cg-512">@miniBoardContent</div>
|
||||
@pov.map { p =>
|
||||
<div class="back_to_game">
|
||||
<a class="button" href="@routes.Round.watcher(p.gameId, p.color.name)">
|
||||
<span data-icon="i" class="text">@trans.backToGame()</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
@(g: lila.game.Game)
|
||||
@gameFenNoCtx(g, g.firstPlayer.color, tv = true)
|
||||
@game.vstext(g)
|
||||
@game.vstext(g)(none)
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
@game.layout(
|
||||
title = trans.gamesBeingPlayedRightNow.str(),
|
||||
menu = sideMenu(listMenu, "realtime").some) {
|
||||
<div style="padding-bottom: 0" class="content_box current_games_box">
|
||||
<div class="game_list realtime clearfix">
|
||||
menu = sideMenu(listMenu, "playing").some) {
|
||||
<div style="padding-bottom: 0" class="content_box">
|
||||
<div class="game_list playing">
|
||||
@games.map { g =>
|
||||
<div>
|
||||
@gameFen(g, g.firstPlayer.color)
|
||||
@game.vstext(g)
|
||||
@game.vstext(g)(ctx.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
@(listMenu: lila.game.ListMenu, active: String)(implicit ctx: Context)
|
||||
|
||||
<a class="@active.active("realtime")" href="@routes.Game.realtime()">
|
||||
<a class="@active.active("playing")" href="@routes.Game.playing()">
|
||||
@trans.gamesBeingPlayedRightNow()
|
||||
</a>
|
||||
<a class="@active.active("search")" href="@routes.Game.search()">
|
||||
|
@ -12,6 +12,9 @@
|
|||
<a class="@active.active("edit")" href="@routes.Editor.index">
|
||||
@trans.boardEditor()
|
||||
</a>
|
||||
<a class="@active.active("userAnalysis")" href="@routes.UserAnalysis.index">
|
||||
@trans.analysis()
|
||||
</a>
|
||||
<a class="@active.active("all")" href="@routes.Game.all()">
|
||||
@trans.viewAllNbGames(listMenu.nbGames.localize)
|
||||
</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(g: Game)
|
||||
@(g: Game)(ctxOption: Option[Context])
|
||||
<div class="vstext clearfix">
|
||||
<div class="left">
|
||||
@playerUsername(g.firstPlayer, withRating = false, withTitle = false)
|
||||
|
@ -14,5 +14,13 @@
|
|||
</div>
|
||||
@g.clock.map { c =>
|
||||
<div class="center"><span data-icon="p"> @shortClockName(c)</span></div>
|
||||
}.getOrElse {
|
||||
@ctxOption.map { ctx =>
|
||||
@g.daysPerTurn.map { days =>
|
||||
<div class="center">
|
||||
<span data-hint="@trans.correspondence()(ctx)" class="hint--top">@{(days == 1).fold(trans.oneDay()(ctx), trans.nbDays(days)(ctx))}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
|
||||
@games.map { g =>
|
||||
<div class="game_row paginated_element clearfix">
|
||||
<div class="game_row paginated_element">
|
||||
@defining(user flatMap g.player) { fromPlayer =>
|
||||
@defining(fromPlayer | g.firstPlayer ) { firstPlayer =>
|
||||
@gameFen(g, firstPlayer.color, ownerLink, withTitle = false)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div id="featured_game">
|
||||
@featured.map { g =>
|
||||
@gameFen(g, g.firstPlayer.color, tv = true)
|
||||
@game.vstext(g)
|
||||
@game.vstext(g)(ctx.some)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@povs.take(9).map { pov =>
|
||||
<a href="@routes.Round.player(pov.fullId)" class="@if(pov.isMyTurn){my_turn}">
|
||||
@gameFen(pov.game, pov.color, withLink = false, withTitle = false)
|
||||
@gameFen(pov.game, pov.color, withLink = false, withTitle = false, withLive = false)
|
||||
<span class="meta">
|
||||
@playerText(pov.opponent, withRating = false)
|
||||
<span class="indicator">
|
||||
|
|
|
@ -9,7 +9,9 @@ routes.javascript.Round.playerText,
|
|||
routes.javascript.Round.watcherText,
|
||||
routes.javascript.Round.sideWatcher,
|
||||
routes.javascript.Round.continue,
|
||||
routes.javascript.Round.next,
|
||||
/* routes.javascript.Tv.index */
|
||||
routes.javascript.Tv.side
|
||||
routes.javascript.Tv.side,
|
||||
routes.javascript.UserAnalysis.game
|
||||
/* routes.javascript.Editor.game */
|
||||
)(ctx.req)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(title: String, side: Html, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true, moreJs: Html = Html(""), active: Option[lila.app.ui.SiteMenu.Elem] = None, openGraph: Map[Symbol, String] = Map.empty, moreCss: Html = Html(""))(body: Html)(implicit ctx: Context)
|
||||
@(title: String, side: Html, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true, moreJs: Html = Html(""), active: Option[lila.app.ui.SiteMenu.Elem] = None, openGraph: Map[Symbol, String] = Map.empty, moreCss: Html = Html(""), chessground: Boolean = true)(body: Html)(implicit ctx: Context)
|
||||
|
||||
@base.layout(
|
||||
title = title,
|
||||
|
@ -9,4 +9,5 @@ underchat = underchat,
|
|||
robots = robots,
|
||||
openGraph = openGraph,
|
||||
moreJs = moreJs,
|
||||
moreCss = moreCss)(body)
|
||||
moreCss = moreCss,
|
||||
chessground = chessground)(body)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
@(playing: List[Pov], nextId: Option[String])(implicit ctx: Context)
|
||||
@nextId.map { id =>
|
||||
<input type="hidden" class="next_id" value="@id" />
|
||||
}
|
||||
<h3>
|
||||
<button class="move_on button hint--bottom" data-hint="@trans.automaticallyProceedToNextGameAfterMoving()">
|
||||
<i data-icon="E" class="is-green"></i><span>@trans.autoSwitch()</span>
|
||||
</button>
|
||||
@trans.gamesBeingPlayedRightNow()
|
||||
</h3>
|
||||
@defining(playing.partition(_.isMyTurn)) {
|
||||
case (myTurn, otherTurn) => {
|
||||
@lobby.playing(myTurn ++ otherTurn.take(6 - myTurn.size))
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.Tournament], cross: Option[lila.game.Crosstable], otherGames: List[Pov])(implicit ctx: Context)
|
||||
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.Tournament], cross: Option[lila.game.Crosstable], playing: List[Pov])(implicit ctx: Context)
|
||||
|
||||
@import pov._
|
||||
|
||||
|
@ -23,17 +23,17 @@ side = views.html.game.side(pov, tour, withTourStanding = true),
|
|||
chat = pov.game.hasChat.option(base.chatDom(trans.chatRoom.str(), ctx.isAuth)),
|
||||
underchat = views.html.game.watchers().some,
|
||||
moreJs = moreJs,
|
||||
openGraph = povOpenGraph(pov)) {
|
||||
openGraph = povOpenGraph(pov),
|
||||
chessground = false) {
|
||||
<div class="round cg-512">@miniBoardContent</div>
|
||||
<div class="crosstable" style="display:none">
|
||||
@cross.map { c =>
|
||||
@views.html.game.crosstable(ctx.userId.fold(c)(c.fromPov))
|
||||
}
|
||||
</div>
|
||||
@if(otherGames.nonEmpty) {
|
||||
<div id="now_playing" class="other_games">
|
||||
<h3>Other correspondence games</h3>
|
||||
@lobby.playing(otherGames take 6)
|
||||
@if(playing.nonEmpty) {
|
||||
<div id="now_playing" class="clearfix other_games" data-reload-url="@routes.Round.others(pov.gameId)">
|
||||
@others(playing, none)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.Tournament], cross: Option[lila.game.Crosstable], userTv: Option[User] = None)(implicit ctx: Context)
|
||||
|
||||
@import pov._
|
||||
|
||||
@title = @{ s"${playerText(pov.player)} vs ${playerText(pov.opponent)} in $gameId" }
|
||||
@title = @{ s"${playerText(pov.player)} vs ${playerText(pov.opponent)} in ${pov.gameId}" }
|
||||
|
||||
@moreJs = {
|
||||
@jsAt(s"compiled/lichess.round${isProd??(".min")}.js")
|
||||
|
@ -23,7 +21,8 @@ side = views.html.game.side(pov, tour, withTourStanding = false, userTv = userTv
|
|||
chat = base.chatDom(trans.spectatorRoom.str()).some,
|
||||
underchat = views.html.game.watchers().some,
|
||||
moreJs = moreJs,
|
||||
openGraph = povOpenGraph(pov)) {
|
||||
openGraph = povOpenGraph(pov),
|
||||
chessground = false) {
|
||||
<div class="round cg-512">@miniBoardContent</div>
|
||||
<div class="crosstable" style="display:none">
|
||||
@cross.map { c =>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
@(games: List[Game])(implicit ctx: Context)
|
||||
|
||||
<div class="game_list realtime">
|
||||
<div class="game_list playing">
|
||||
@games.map { g =>
|
||||
<div>
|
||||
@gameFen(g, g.firstPlayer.color)
|
||||
@game.vstext(g)
|
||||
@game.vstext(g)(ctx.some)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
data-stream-url="@routes.Tv.streamOut">
|
||||
<div id="featured_game" title="lichess.org TV">
|
||||
@gameFenNoCtx(g, g.firstPlayer.color, tv = true, blank = true)
|
||||
@game.vstext(g)
|
||||
@game.vstext(g)(none)
|
||||
</div>
|
||||
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
|
||||
@jsTag("vendor/chessground.min.js")
|
||||
|
|
|
@ -21,7 +21,8 @@ side = side(pov, games, streams),
|
|||
underchat = game.watchers().some,
|
||||
active = siteMenu.tv.some,
|
||||
moreJs = moreJs,
|
||||
moreCss = cssTag("tv.css")) {
|
||||
moreCss = cssTag("tv.css"),
|
||||
chessground = false) {
|
||||
<div class="round cg-512">
|
||||
@miniBoardContent
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
@(u: User, gs: Paginator[Game], filterName: String)(implicit ctx: Context)
|
||||
|
||||
<div class="games infinitescroll">
|
||||
<div class="games infinitescroll @if(filterName == "playing" && gs.nbResults > 2) {game_list playing center}">
|
||||
@gs.nextPage.map { np =>
|
||||
<div class="pager none"><a href="@routes.User.showFilter(u.username, filterName, np)">Next</a></div>
|
||||
}
|
||||
@if(filterName == "playing" && gs.nbResults > 2) {
|
||||
@gs.currentPageResults.flatMap{ Pov(_, u) }.map { p =>
|
||||
<div class="paginated_element">
|
||||
@gameFen(p.game, p.color)
|
||||
@game.vstext(p.game)(ctx.some)
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
@game.widgets(gs.currentPageResults, user = u.some, ownerLink = ctx is u)
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(u: User, playing: Option[Game], blocked: Boolean, followable: Boolean, rel: Option[lila.relation.Relation])(implicit ctx: Context)
|
||||
@(u: User, playing: Option[Pov], blocked: Boolean, followable: Boolean, rel: Option[lila.relation.Relation])(implicit ctx: Context)
|
||||
<div class="title">
|
||||
<div>
|
||||
@u.profileOrDefault.countryInfo.map {
|
||||
|
@ -22,8 +22,8 @@
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
@playing.map { g =>
|
||||
@gameFen(g, g.player(u).getOrElse(g.firstPlayer).color)
|
||||
@playing.map { pov =>
|
||||
@gameFen(pov.game, pov.color)
|
||||
}
|
||||
@ctx.userId.map { myId =>
|
||||
@if(myId != u.id) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
@(u: User, sugs: List[lila.relation.Related])(implicit ctx: Context)
|
||||
|
||||
@title = @{ "%s - %s".format(u.username, trans.favoriteOpponents()) }
|
||||
|
||||
@user.layout(title = title) {
|
||||
@user.layout(title = "%s - %s".format(u.username, trans.favoriteOpponents())) {
|
||||
<div class="content_box no_padding">
|
||||
<h1>@userLink(u, withOnline = false) @trans.favoriteOpponents()</h1>
|
||||
@user.relatedTable(u, sugs)
|
||||
|
|
|
@ -11,7 +11,7 @@ for app in editor puzzle round analyse; do
|
|||
cd -
|
||||
done
|
||||
|
||||
for file in strongSocket.js tv.js common.js big.js chart2.js user.js coordinate.js; do
|
||||
for file in socket.js tv.js common.js big.js chart2.js user.js coordinate.js; do
|
||||
orig=public/javascripts/$file
|
||||
comp=public/compiled/$file
|
||||
if [[ ! -f $comp || $orig -nt $comp ]]; then
|
||||
|
|
|
@ -169,6 +169,7 @@ tournaments=Tournaments
|
|||
tournamentPoints=Tournament points
|
||||
viewTournament=View tournament
|
||||
backToTournament=Return to tournament
|
||||
backToGame=Return to game
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Free online Chess game. Play Chess now in a clean interface. No registration, no ads, no plugin required. Play Chess with the computer, friends or random opponents.
|
||||
teams=Teams
|
||||
nbMembers=%s members
|
||||
|
@ -305,3 +306,5 @@ thisPuzzleIsCorrect=This puzzle is correct and interesting
|
|||
thisPuzzleIsWrong=This puzzle is wrong or boring
|
||||
youHaveNbSecondsToMakeYourFirstMove=You have %s seconds to make your first move!
|
||||
nbGamesInPlay=%s games in play
|
||||
automaticallyProceedToNextGameAfterMoving=Automatically proceed to next game after moving
|
||||
autoSwitch=Auto switch
|
||||
|
|
|
@ -47,9 +47,12 @@ computerAnalysisInProgress=Analise de computador em progresso
|
|||
theComputerAnalysisHasFailed=Die rekenaar analise het misluk
|
||||
viewTheComputerAnalysis=Sien die rekenaar analise
|
||||
requestAComputerAnalysis=Versoek 'n rekenaar analise
|
||||
computerAnalysis=Analisi del computer
|
||||
analysis=Analisi
|
||||
blunders=Flaters
|
||||
mistakes=Foute
|
||||
inaccuracies=Onakurate
|
||||
moveTimes=Tempo mossa
|
||||
flipBoard=Draai bord
|
||||
threefoldRepetition=Herhaal drie keer
|
||||
claimADraw=Kies gelykop
|
||||
|
|
|
@ -81,6 +81,7 @@ players=لاعبي الشطرنج
|
|||
minutesPerSide=دقائق لكل طرف
|
||||
variant=النوع
|
||||
timeControl=التحكم بالوقت
|
||||
realTime=الوقت الفعلي
|
||||
correspondence=طويل الأمد
|
||||
daysPerTurn=يوم لكل نقلة
|
||||
oneDay=يوم واحد
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Играчи
|
|||
minutesPerSide=Минути за страна
|
||||
variant=Вариант
|
||||
timeControl=Контрол на времето
|
||||
realTime=Засечено време
|
||||
correspondence=Кореспонденция
|
||||
daysPerTurn=Дни за ход
|
||||
oneDay=Един ден
|
||||
|
@ -92,6 +93,11 @@ password=Парола
|
|||
haveAnAccount=Имате акаунт?
|
||||
allYouNeedIsAUsernameAndAPassword=Всичко, което ви е необходимо е потребителско име и парола
|
||||
changePassword=Смяна на парола
|
||||
changeEmail=Смяна на имейла
|
||||
email=Електронна Поща
|
||||
emailIsOptional=Електронната поща не е задължителна. Lichess ще използва вашия адрес, за да възстанови паролата ви, ако я забравите
|
||||
passwordReset=Паролата е нулирана
|
||||
forgotPassword=Забравихте ли си паролаата ?
|
||||
learnMoreAboutLichess=Научи повече за Lichess
|
||||
rank=Ранг
|
||||
gamesPlayed=Играни игри
|
||||
|
@ -299,3 +305,5 @@ thisPuzzleIsCorrect=Тази задача е правилна и интерес
|
|||
thisPuzzleIsWrong=Тази задача е грешна или скучна
|
||||
youHaveNbSecondsToMakeYourFirstMove=Имате %s секунди да направите първия си ход!
|
||||
nbGamesInPlay=%s игри в процес
|
||||
automaticallyProceedToNextGameAfterMoving=Автоматично превключи на следващата игра след ход
|
||||
autoSwitch=Автоматично превключи
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Igrači
|
|||
minutesPerSide=Minuta po igraču
|
||||
variant=Varijanta
|
||||
timeControl=Vremenska kontrola
|
||||
realTime=Stvarno Vrijeme
|
||||
correspondence=Dopisni sah
|
||||
daysPerTurn=Dana po potezu
|
||||
oneDay=Jedan dan
|
||||
|
@ -92,6 +93,11 @@ password=Lozinka
|
|||
haveAnAccount=Već imate račun?
|
||||
allYouNeedIsAUsernameAndAPassword=Sve što Vam treba je korisničko ime i lozinka.
|
||||
changePassword=Promijeni lozinku
|
||||
changeEmail=Promijeni e-Mail
|
||||
email=e-Mail
|
||||
emailIsOptional=e-Mail je neoavezan. Lichess ce je koristiti da resetuje vasu Lozinku ako je zaboravite.
|
||||
passwordReset=resetuj Lozinku
|
||||
forgotPassword=Zaboravio si Lozinku ?
|
||||
learnMoreAboutLichess=Naučite više o Lichessu
|
||||
rank=Poredak
|
||||
gamesPlayed=Broj odigranih partija
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Jugadors d'escacs
|
|||
minutesPerSide=Minuts per jugador
|
||||
variant=Variant
|
||||
timeControl=Control de temps
|
||||
realTime=Temps real
|
||||
correspondence=Correspondència
|
||||
daysPerTurn=Dies per torn
|
||||
oneDay=Un dia
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Aquest trencaclosques és correcte i interessant
|
|||
thisPuzzleIsWrong=Aquest trencaclosques està malament o és avorrit
|
||||
youHaveNbSecondsToMakeYourFirstMove=Disposes de %s segons per realitzar el teu primer moviment!
|
||||
nbGamesInPlay=%s partides en joc
|
||||
automaticallyProceedToNextGameAfterMoving=Anar automàticament a la següent partida després de cada moviment
|
||||
autoSwitch=Canvi automàtic
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Hráči
|
|||
minutesPerSide=Minut pro každého hráče
|
||||
variant=Varianta
|
||||
timeControl=Tempo hry
|
||||
realTime=Skutečný čas
|
||||
correspondence=Korespondenčně
|
||||
daysPerTurn=Dnů na tah
|
||||
oneDay=Jeden den
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Tato úloha je správná a zajímavá
|
|||
thisPuzzleIsWrong=Tato úloha je špatná nebo nudná
|
||||
youHaveNbSecondsToMakeYourFirstMove=Máte %s sekund na provedení prvního tahu!
|
||||
nbGamesInPlay=%s rozehraných her
|
||||
automaticallyProceedToNextGameAfterMoving=Automaticky přejdi k další hře po tahu
|
||||
autoSwitch=Přepni automaticky
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Skakspillere
|
|||
minutesPerSide=Minutter per spiller
|
||||
variant=Variant
|
||||
timeControl=Tidskontrol
|
||||
realTime=Real time
|
||||
correspondence=Korrespondance
|
||||
daysPerTurn=Dage per træk
|
||||
oneDay=En dag
|
||||
|
@ -92,7 +93,10 @@ password=Password
|
|||
haveAnAccount=Har du en konto?
|
||||
allYouNeedIsAUsernameAndAPassword=Det eneste du skal bruge, er et brugernavn og en adgangskode.
|
||||
changePassword=Skift kodeord
|
||||
changeEmail=Ændr email
|
||||
email=Email
|
||||
emailIsOptional=Email er valgfri. Lichess vil bruge din email til at sende et nyt pasord, hvis du glemmer det eksisterende.
|
||||
passwordReset=Reset pasord
|
||||
forgotPassword=Glemt adgangskode?
|
||||
learnMoreAboutLichess=Få mere at vide om Lichess
|
||||
rank=Rang
|
||||
|
|
|
@ -86,7 +86,7 @@ correspondence=Fernschach
|
|||
daysPerTurn=Tage pro Zug
|
||||
oneDay=ein Tag
|
||||
nbDays=%s Tage
|
||||
nbHours=%s stunden
|
||||
nbHours=%s Stunden
|
||||
time=Zeit
|
||||
username=Benutzername
|
||||
password=Passwort
|
||||
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Dieses Rätsel ist korrekt und interessant.
|
|||
thisPuzzleIsWrong=Dieses Rätsel ist falsch oder langweilig.
|
||||
youHaveNbSecondsToMakeYourFirstMove=Du hast %s Sekunden, um deinen ersten Zug zu machen!
|
||||
nbGamesInPlay=%s laufende Spiele
|
||||
automaticallyProceedToNextGameAfterMoving=Nach dem Zug automatisch zur nächsten Partie gehen
|
||||
autoSwitch=Automatischer Wechsel
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Σκακιστές
|
|||
minutesPerSide=Λεπτά ανά πλευρά
|
||||
variant=Εκδοχή
|
||||
timeControl=Χρονόμετρο
|
||||
realTime=Πραγματικού χρόνου
|
||||
correspondence=Αλληλογραφία
|
||||
daysPerTurn=Μέρες ανά σειρά
|
||||
oneDay=Μία μέρα
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Este puzzle es correcto e interesante
|
|||
thisPuzzleIsWrong=Este puzzle es erróneo o aburrido
|
||||
youHaveNbSecondsToMakeYourFirstMove=¡Dispone de %s segundos para hacer su primer movimiento!
|
||||
nbGamesInPlay=%s partidas en juego
|
||||
automaticallyProceedToNextGameAfterMoving=Continuar automáticamente al siguiente juego al mover
|
||||
autoSwitch=Cambio automático
|
||||
|
|
|
@ -81,6 +81,7 @@ players=بازیکنان شطرنج
|
|||
minutesPerSide=دقیقه در هر طرف
|
||||
variant=شاخه
|
||||
timeControl=کنترل زمان
|
||||
realTime=بلادرنگ
|
||||
correspondence=مکاتبه ای
|
||||
daysPerTurn=روز برای هر حرکت
|
||||
oneDay=یک روز
|
||||
|
@ -92,6 +93,11 @@ password=رمزعبور
|
|||
haveAnAccount=شما صاحب حساب کاربری می باشید؟
|
||||
allYouNeedIsAUsernameAndAPassword=تمام چیزی که شما نیاز دارید نام کاربری و رمزعبور می باشد
|
||||
changePassword=تغییر کلمه عبور
|
||||
changeEmail=تغییر ایمیل
|
||||
email=ایمیل
|
||||
emailIsOptional=.ایمیل ضروری نیست. زمانیکه پسورد خود را فراموش کنید لایچس از این ایمیل استفاده میکند تا شما پسورد جدیدی دریافت کنید
|
||||
passwordReset=باز نشانی پسورد
|
||||
forgotPassword=آیا پسورد را فراموش کرده اید؟
|
||||
learnMoreAboutLichess=Lichess بیشتر بدانید درباره
|
||||
rank=رتبه
|
||||
gamesPlayed=بازی های انجام شده
|
||||
|
@ -299,3 +305,5 @@ thisPuzzleIsCorrect=این جدول صحیح و جالب است
|
|||
thisPuzzleIsWrong=این جدول اشتباه یا خسته کننده است
|
||||
youHaveNbSecondsToMakeYourFirstMove=! ثانیه فرصت دارید %s شما برای انجام اولین حرکت خود فقط
|
||||
nbGamesInPlay=بازی در حال انجام است %s
|
||||
automaticallyProceedToNextGameAfterMoving=حرکت کردن اتوماتیک برای بازی بعدی بعد از حرکت کردن
|
||||
autoSwitch=تعویض خودکار
|
||||
|
|
|
@ -81,7 +81,7 @@ players=Pelaajat
|
|||
minutesPerSide=Minuuttia per puoli
|
||||
variant=Variantti
|
||||
timeControl=Ajan hallinta
|
||||
realTime=Oikea aika
|
||||
realTime=Reaaliaikainen
|
||||
correspondence=Kirjeshakki
|
||||
daysPerTurn=Päivää per vuoro
|
||||
oneDay=Yksi päivä
|
||||
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Tämä tehtävä toimii ja on mielenkiintoinen
|
|||
thisPuzzleIsWrong=Tämä tehtävä on tylsä tai virheellinen
|
||||
youHaveNbSecondsToMakeYourFirstMove=Sinulla on %s sekuntia aikaa ensimmäisen siirtosi tekemiseen
|
||||
nbGamesInPlay=%s peliä meneillään
|
||||
automaticallyProceedToNextGameAfterMoving=Siirry automaattisesti seuraavaan peliin siirron jälkeen
|
||||
autoSwitch=Automaattinen siirtyminen
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Ce problème est correct et intéressant
|
|||
thisPuzzleIsWrong=Ce problème est erroné ou ennuyeux
|
||||
youHaveNbSecondsToMakeYourFirstMove=Vous avez %s secondes pour jouer votre premier coup !
|
||||
nbGamesInPlay=%s parties en cours
|
||||
automaticallyProceedToNextGameAfterMoving=Aller automatiquement à la prochaine partie après coup
|
||||
autoSwitch=Changement automatique
|
||||
|
|
|
@ -5,3 +5,14 @@ gameOver=રમત પુરી
|
|||
waitingForOpponent=વિરોધિ માટે રાહ જુએ છે
|
||||
waiting=રાહ જુએ છે
|
||||
yourTurn=તમરો વારો
|
||||
level=પાળવ
|
||||
chat=વાત ચીત
|
||||
resign=રાજીનામું
|
||||
white=સફેદ
|
||||
black=કાળુ
|
||||
createAGame=રમત બનાવો
|
||||
whiteIsVictorious=સફેદ વિજય રહ્યો
|
||||
blackIsVictorious=કાળો વિજય રહ્યો
|
||||
playWithTheSameOpponentAgain=આજ પ્રાતીસ્પર્ધી સાથે ફરી રમો
|
||||
newOpponent=નવો પ્રાતીસ્પર્ધી
|
||||
joinTheGame=રમત માં જોડવ
|
||||
|
|
|
@ -81,6 +81,7 @@ players=שחקנים
|
|||
minutesPerSide=דקות עבור כל צד
|
||||
variant=סוג משחק
|
||||
timeControl=כמות זמן
|
||||
realTime=זמן אמת
|
||||
correspondence=התכתבות
|
||||
daysPerTurn=ימים לצעד
|
||||
oneDay=יום אחד
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Igrači
|
|||
minutesPerSide=Minuta po igraču
|
||||
variant=Varijanta
|
||||
timeControl=Vremenska kontrola
|
||||
realTime=Stvarno vrijeme
|
||||
correspondence=Korespodencija
|
||||
daysPerTurn=Dana po potezu
|
||||
oneDay=Jedan dan
|
||||
|
@ -92,6 +93,11 @@ password=Zaporka
|
|||
haveAnAccount=Imate li otvoren račun?
|
||||
allYouNeedIsAUsernameAndAPassword=Sve što trebate je samo korisničko ime i zaporka.
|
||||
changePassword=Promijeni zaporku
|
||||
changeEmail=Promijeni email
|
||||
email=Email
|
||||
emailIsOptional=Email je neobavezan. Lichess će koristiti vaš mail kako bi vam povratio šifru ako ju zaboravite.
|
||||
passwordReset=Resetirajte lozinku
|
||||
forgotPassword=Zaboravili ste lozinku?
|
||||
learnMoreAboutLichess=Nauči više o Lichessu
|
||||
rank=Rang
|
||||
gamesPlayed=Broj odigranih igara
|
||||
|
@ -299,3 +305,5 @@ thisPuzzleIsCorrect=Ovaj problem je točan i zanimljiv
|
|||
thisPuzzleIsWrong=Ovaj problem je pogresan i dosadan
|
||||
youHaveNbSecondsToMakeYourFirstMove=Imate %s sekunti da napravite svoj prvi potez!
|
||||
nbGamesInPlay=%s partije koje se upravo igraju
|
||||
automaticallyProceedToNextGameAfterMoving=Automatski prebaci na sljedeću partiju nakon odigranog poteza
|
||||
autoSwitch=Prebaci automatski
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Játékosok
|
|||
minutesPerSide=Perc játékosonként
|
||||
variant=Változat
|
||||
timeControl=Játék időre
|
||||
realTime=Valós Idő
|
||||
correspondence=Levelezés
|
||||
daysPerTurn=Lépésenkénti napok száma
|
||||
oneDay=Egy nap
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Ez a feladvány helyes és érdekes
|
|||
thisPuzzleIsWrong=Ez a feladvány hibás vagy unalmas
|
||||
youHaveNbSecondsToMakeYourFirstMove=%s másodperced van hogy megtedd az első lépést!
|
||||
nbGamesInPlay=%s meccset játszanak éppen az oldalon
|
||||
automaticallyProceedToNextGameAfterMoving=Automatikusan menjen a következő játékhoz amiután lépett
|
||||
autoSwitch=Automatikus váltás
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Pemain catur
|
|||
minutesPerSide=Menit untuk tiap sisi
|
||||
variant=Variasi
|
||||
timeControl=Kontrol waktu
|
||||
realTime=Langsung
|
||||
correspondence=Korespondensi
|
||||
daysPerTurn=Hari per langkah
|
||||
oneDay=Satu hari
|
||||
|
@ -92,6 +93,11 @@ password=Kata kunci
|
|||
haveAnAccount=Punya akun?
|
||||
allYouNeedIsAUsernameAndAPassword=Semua yang anda butuhkan adalah nama pengguna dan kata kunci.
|
||||
changePassword=Ganti kata kunci
|
||||
changeEmail=Ubah email
|
||||
email=Email
|
||||
emailIsOptional=Email tidak diwajibkan. Lichess akan menggunakan email anda untuk me-reset password anda bila anda lupa.
|
||||
passwordReset=Reset password
|
||||
forgotPassword=Lupa password?
|
||||
learnMoreAboutLichess=Pelajari lebih lanjut tentang Lichess
|
||||
rank=Pangkat
|
||||
gamesPlayed=Permainan yang telah dimainkan
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Skákmenn
|
|||
minutesPerSide=Mínútur á lið
|
||||
variant=Afbrigði
|
||||
timeControl=Tímaskorður
|
||||
realTime=Rauntími
|
||||
daysPerTurn=Dagar á leik
|
||||
oneDay=Einn dagur
|
||||
nbDays=%s dagar
|
||||
|
@ -303,3 +304,5 @@ thisPuzzleIsCorrect=Þessi þraut er rétt og áhugaverð
|
|||
thisPuzzleIsWrong=Þessi þraut er röng eða leiðinleg
|
||||
youHaveNbSecondsToMakeYourFirstMove=Þú hefur %s sekúndur til þess að leika fyrsta leik!
|
||||
nbGamesInPlay=%s leikir í gangi
|
||||
automaticallyProceedToNextGameAfterMoving=Skipta sjálfkrafa um skák eftir leik
|
||||
autoSwitch=Skipta sjálfkrafa
|
||||
|
|
|
@ -70,9 +70,9 @@ viewInFullSize=Visualizza a schermo intero
|
|||
logOut=Esci
|
||||
signIn=Entra
|
||||
newToLichess=Nuovo su Lichess?
|
||||
youNeedAnAccountToDoThat=Ti servei un account per farlo
|
||||
youNeedAnAccountToDoThat=Ti serve un account per farlo
|
||||
signUp=Registrati
|
||||
computersAreNotAllowedToPlay=Non è permesso giocare a computer o a giocatori che si fanno aiutare dai computer. Mentre giochi, non farti aiutare da programmi di scacchi, da database o da altre persone.
|
||||
computersAreNotAllowedToPlay=Non è permesso giocare a computer o a giocatori che si fanno aiutare dai computer. Mentre giochi, non farti aiutare da programmi di scacchi, da database o da altre persone. Inoltre si sconsiglia vivamente di creare account multipli, pena la cancellazione dal sito.
|
||||
games=Partite
|
||||
forum=Forum
|
||||
xPostedInForumY=%s ha postato nel forum %s
|
||||
|
@ -81,7 +81,7 @@ players=Giocatori
|
|||
minutesPerSide=Minuti per lato
|
||||
variant=Variante
|
||||
timeControl=Controllo del tempo
|
||||
realTime=Diretta
|
||||
realTime=Partita a tempo
|
||||
correspondence=Corrispondenza
|
||||
daysPerTurn=Giorni per turno
|
||||
oneDay=Un giorno
|
||||
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Questo esercizio è corretto e interessante
|
|||
thisPuzzleIsWrong=Questo esercizio è sbagliato o noioso
|
||||
youHaveNbSecondsToMakeYourFirstMove=Hai %s secondi per fare la tua prima mossa
|
||||
nbGamesInPlay=%s partite in gioco
|
||||
automaticallyProceedToNextGameAfterMoving=Passa automaticamente alla partita successiva dopo aver mosso
|
||||
autoSwitch=Passaggio automatico
|
||||
|
|
|
@ -81,8 +81,10 @@ players=プレイヤー
|
|||
minutesPerSide=持ち時間
|
||||
variant=バリアント
|
||||
timeControl=持ち時間制限
|
||||
realTime=実時間
|
||||
correspondence=通信チェス
|
||||
daysPerTurn=制限日数
|
||||
oneDay=一日
|
||||
nbDays=%s日
|
||||
nbHours=%s時間
|
||||
time=時間
|
||||
|
|
|
@ -81,6 +81,7 @@ players=플레이어
|
|||
minutesPerSide=진영 당 주어진 시간(분)
|
||||
variant=모드
|
||||
timeControl=시간 제한
|
||||
realTime=시간 제한
|
||||
correspondence=긴 체스
|
||||
daysPerTurn=한 턴에 걸리는 날짜
|
||||
oneDay=하루
|
||||
|
@ -92,6 +93,11 @@ password=비밀번호
|
|||
haveAnAccount=계정이 있습니까?
|
||||
allYouNeedIsAUsernameAndAPassword=사용자 이름과 비밀번호만 입력하시면 됩니다
|
||||
changePassword=비밀번호 변경
|
||||
changeEmail=메일 주소 변경
|
||||
email=메일
|
||||
emailIsOptional=메일 주소는 선택 항목입니다. 비밀번호를 잊어버렸을 때 초기화하기 위해 메일 주소를 사용합니다.
|
||||
passwordReset=비밀번호 초기화
|
||||
forgotPassword=비밀번호를 잊어버리셨나요?
|
||||
learnMoreAboutLichess=Lichess에 대해 좀 더 알아보기
|
||||
rank=순위
|
||||
gamesPlayed=게임
|
||||
|
@ -299,3 +305,5 @@ thisPuzzleIsCorrect=이 퍼즐은 정확하고 재밌습니다
|
|||
thisPuzzleIsWrong=이 퍼즐은 오류가 있거나 지루합니다
|
||||
youHaveNbSecondsToMakeYourFirstMove=%s초 안에 첫 수를 놓으세요!
|
||||
nbGamesInPlay=%s개의 게임 플레이 중
|
||||
automaticallyProceedToNextGameAfterMoving=수를 둔 다음에 자동으로 다음 게임으로 이동
|
||||
autoSwitch=자동 전환
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Sjakkspillere
|
|||
minutesPerSide=Minutter per side
|
||||
variant=Variant
|
||||
timeControl=Tidskontroll
|
||||
realTime=Sanntid
|
||||
correspondence=Fjernsjakk
|
||||
daysPerTurn=Dager per trekk
|
||||
oneDay=Én dag
|
||||
|
|
|
@ -81,7 +81,8 @@ players=Geregistreerde spelers en spelers die online zijn
|
|||
minutesPerSide=Minuten per speler
|
||||
variant=Variant
|
||||
timeControl=Speelduur
|
||||
correspondence=Correspondensie
|
||||
realTime=Live
|
||||
correspondence=Correspondentie
|
||||
daysPerTurn=Dagen per zet
|
||||
oneDay=Eén dag
|
||||
nbDays=%s dagen
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=De puzzel is correct en interessant
|
|||
thisPuzzleIsWrong=De puzzel is fout of saai
|
||||
youHaveNbSecondsToMakeYourFirstMove=Je hebt %s seconden om je eerste zet te doen!
|
||||
nbGamesInPlay=%s partijen bezig
|
||||
automaticallyProceedToNextGameAfterMoving=Automatische doorgaan naar de volgende partij na uw zet
|
||||
autoSwitch=Automatische switch
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Sjakkspelarar
|
|||
minutesPerSide=Minutt per side
|
||||
variant=Variant
|
||||
timeControl=Tidskontroll
|
||||
realTime=Sanntid
|
||||
correspondence=Fjernsjakk
|
||||
daysPerTurn=Dagar per trekk
|
||||
oneDay=Ein dag
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=To zadanie jest poprawne i ciekawe
|
|||
thisPuzzleIsWrong=To zadanie jest niepoprawne lub nudne
|
||||
youHaveNbSecondsToMakeYourFirstMove=Masz %s sekund na pierwszy ruch!
|
||||
nbGamesInPlay=%s gier w trakcie
|
||||
automaticallyProceedToNextGameAfterMoving=Automatycznie przejdź do następnej gry po wykonaniu ruchu
|
||||
autoSwitch=Auto przełączanie
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=O quebra-cabeças está correcto e é interessante
|
|||
thisPuzzleIsWrong=O quebra-cabeças está errado ou é chato
|
||||
youHaveNbSecondsToMakeYourFirstMove=Tens %s segundos para fazeres a primeira jogada!
|
||||
nbGamesInPlay=%s partidas em andamento
|
||||
automaticallyProceedToNextGameAfterMoving=Automaticamente passa ao jogo seguinte após seu lance
|
||||
autoSwitch=Auto ciclo
|
||||
|
|
|
@ -81,7 +81,7 @@ players=Игроки
|
|||
minutesPerSide=Минут на партию
|
||||
variant=Вариант
|
||||
timeControl=Контроль времени
|
||||
realTime=Реальное время
|
||||
realTime=Время на ход
|
||||
correspondence=По переписке
|
||||
daysPerTurn=Дней на ход
|
||||
oneDay=Ежедневно
|
||||
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Задача правильная и интересная
|
|||
thisPuzzleIsWrong=Задача неправильная или неинтересная
|
||||
youHaveNbSecondsToMakeYourFirstMove=У Вас %s секунд, чтобы сделать свой первый ход!
|
||||
nbGamesInPlay=%s партий играются
|
||||
automaticallyProceedToNextGameAfterMoving=Автоматически переходить к следующей игре после хода
|
||||
autoSwitch=Автосмена
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Hráči
|
|||
minutesPerSide=Minút na stranu
|
||||
variant=Varianta
|
||||
timeControl=Nastavenie času
|
||||
realTime=Skutočný čas
|
||||
correspondence=Korešpondenčný šach
|
||||
daysPerTurn=Počet dní na ťah
|
||||
oneDay=Jeden deň
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Tento rébus je správny a pútavý
|
|||
thisPuzzleIsWrong=Tento rébus je nesprávny alebo nudný
|
||||
youHaveNbSecondsToMakeYourFirstMove=%s sekúnd na vykonanie prvého ťahu!
|
||||
nbGamesInPlay=%s hier sa hrá
|
||||
automaticallyProceedToNextGameAfterMoving=Automaticky prejdi k ďalšej hre po ťahu
|
||||
autoSwitch=Prepni automaticky
|
||||
|
|
|
@ -49,7 +49,7 @@ viewTheComputerAnalysis=Poglej računalniško analizo
|
|||
requestAComputerAnalysis=Zahtevaj računalniško analizo
|
||||
computerAnalysis=Računalniška analiza
|
||||
analysis=Analiza
|
||||
blunders=Hude napake
|
||||
blunders=Grobe napake
|
||||
mistakes=Napake
|
||||
inaccuracies=Nenatančnosti
|
||||
moveTimes=Čas za potezo
|
||||
|
@ -61,7 +61,7 @@ draw=Remi
|
|||
nbConnectedPlayers=%s igralcev
|
||||
gamesBeingPlayedRightNow=Število trenutnih iger
|
||||
viewAllNbGames=%s Iger
|
||||
viewNbCheckmates=Oglej si mat pozicije %s
|
||||
viewNbCheckmates=%s mat pozicij
|
||||
nbBookmarks=%s zaznamkov
|
||||
nbPopularGames=%s priljubljenih partij
|
||||
nbAnalysedGames=%s analiziranih Iger
|
||||
|
@ -81,6 +81,7 @@ players=Igralci
|
|||
minutesPerSide=Minut na igralca
|
||||
variant=Različica
|
||||
timeControl=Ura
|
||||
realTime=Standardno
|
||||
correspondence=Korespondenčno
|
||||
daysPerTurn=Število dni na potezo
|
||||
oneDay=En dan
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Ta problem je pravilen in zanimiv
|
|||
thisPuzzleIsWrong=Ta problem je napačen oz. dolgočasen
|
||||
youHaveNbSecondsToMakeYourFirstMove=Imaš še %s sekund za prvo potezo
|
||||
nbGamesInPlay=%s igranih partij
|
||||
automaticallyProceedToNextGameAfterMoving=Po vsaki potezi samodejno preklopi na naslednjo partijo
|
||||
autoSwitch=Samodejno
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Lojtarë
|
|||
minutesPerSide=Minuta për palë
|
||||
variant=Varianti
|
||||
timeControl=Kontolla e kohës
|
||||
realTime=Kha reale
|
||||
correspondence=korespondenca
|
||||
daysPerTurn=turne për ditë
|
||||
oneDay=një ditë
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Ky mister është korekt dhe interesant
|
|||
thisPuzzleIsWrong=Kjo levizje është e gabuar ose e bezëdisëshme
|
||||
youHaveNbSecondsToMakeYourFirstMove=Ju keni%s sekonda për të bërë lëvizje tuaj të parë
|
||||
nbGamesInPlay=%s lojëra duke u luajtur
|
||||
automaticallyProceedToNextGameAfterMoving=Procesimi Automatik i lojës ,më pas duke luajtur potezin
|
||||
autoSwitch=Mbyllje automatike
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Schackspelare
|
|||
minutesPerSide=Minuter per spelare
|
||||
variant=Variant
|
||||
timeControl=Tidskontroll
|
||||
realTime=Realtid
|
||||
correspondence=Korrespondens
|
||||
daysPerTurn=Dagar per speltur
|
||||
oneDay=En dag
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Det här schackproblemet är korrekt och intressant
|
|||
thisPuzzleIsWrong=Det här schackproblemet är fel eller tråkigt
|
||||
youHaveNbSecondsToMakeYourFirstMove=du har %s sekunder för att göra första draget
|
||||
nbGamesInPlay=%s partier spelas
|
||||
automaticallyProceedToNextGameAfterMoving=Gör att du automatiskt fortsätter till nästa parti efter du gjort ett drag
|
||||
autoSwitch=Automatiskt byte
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=ปริศนานี้ถูกต้อง และ
|
|||
thisPuzzleIsWrong=ปริศนานี้ไม่ถูกต้อง หรือน่าเบื่อ
|
||||
youHaveNbSecondsToMakeYourFirstMove=คุณมีเวลา %s วินาที ในการเริ่มเดิน!
|
||||
nbGamesInPlay=%s เกมที่กำลังดำเนิน
|
||||
automaticallyProceedToNextGameAfterMoving=ดำเนินสู่เกมถัดไปอัตโนมัติหลังจากการเดิน
|
||||
autoSwitch=สลับอัตโนมัติ
|
||||
|
|
|
@ -305,3 +305,5 @@ thisPuzzleIsCorrect=Bu bulmaca doğru ve ilginç
|
|||
thisPuzzleIsWrong=Bu bulmaca yanlış veya sıkıcı
|
||||
youHaveNbSecondsToMakeYourFirstMove=İlk hamleni yapman için %s saniyen kaldı!
|
||||
nbGamesInPlay=Şu anda %s oyun oynanıyor
|
||||
automaticallyProceedToNextGameAfterMoving=Hamleden sonra otomatik olarak diğer oyuna devam edecek
|
||||
autoSwitch=Otomatik Seç
|
||||
|
|
|
@ -81,6 +81,7 @@ players=Гравці
|
|||
minutesPerSide=Хвилин на кожного
|
||||
variant=Варіант
|
||||
timeControl=Контроль часу
|
||||
realTime=Швидкі шахи
|
||||
correspondence=Переписка
|
||||
daysPerTurn=Днів на хід
|
||||
oneDay=Один день
|
||||
|
@ -304,3 +305,5 @@ thisPuzzleIsCorrect=Ця задача є правильною та цікаво
|
|||
thisPuzzleIsWrong=Ця задача є неправильною чи нудною
|
||||
youHaveNbSecondsToMakeYourFirstMove=Ви маєте %s секунд, щоб зробити перший хід!
|
||||
nbGamesInPlay=%s ігор тривають
|
||||
automaticallyProceedToNextGameAfterMoving=Автоматично перейти до наступної гри після ходу
|
||||
autoSwitch=Авт. перехід
|
||||
|
|
11
conf/routes
11
conf/routes
|
@ -60,7 +60,7 @@ GET /blog/:id/:slug controllers.Blog.show(id: String, slug: String, ref: O
|
|||
GET /blog.atom controllers.Blog.atom(ref: Option[String] ?= None)
|
||||
|
||||
# Game
|
||||
GET /games controllers.Game.realtime
|
||||
GET /games controllers.Game.playing
|
||||
GET /games/all controllers.Game.all(page: Int ?= 1)
|
||||
GET /games/checkmate controllers.Game.checkmate(page: Int ?= 1)
|
||||
GET /games/bookmark controllers.Game.bookmark(page: Int ?= 1)
|
||||
|
@ -89,6 +89,10 @@ GET /training/:id/load controllers.Puzzle.load(id: Int)
|
|||
POST /training/:id/attempt controllers.Puzzle.attempt(id: Int)
|
||||
POST /training/:id/vote controllers.Puzzle.vote(id: Int)
|
||||
|
||||
# User Analysis
|
||||
GET /analysis/*urlFen controllers.UserAnalysis.load(urlFen: String)
|
||||
GET /analysis controllers.UserAnalysis.index
|
||||
|
||||
# Round
|
||||
GET /$gameId<\w{8}> controllers.Round.watcher(gameId: String, color: String = "white")
|
||||
GET /$gameId<\w{8}>/$color<white|black> controllers.Round.watcher(gameId: String, color: String)
|
||||
|
@ -97,9 +101,12 @@ GET /$gameId<\w{8}>/$color<white|black>/socket controllers.Round.websocketWatc
|
|||
GET /$fullId<\w{12}>/socket/v:apiVersion controllers.Round.websocketPlayer(fullId: String, apiVersion: Int)
|
||||
GET /$gameId<\w{8}>/$color<white|black>/side controllers.Round.sideWatcher(gameId: String, color: String)
|
||||
GET /$fullId<\w{12}>/side controllers.Round.sidePlayer(fullId: String)
|
||||
GET /$gameId<\w{8}>/others controllers.Round.others(gameId: String)
|
||||
GET /$gameId<\w{8}>/next controllers.Round.next(gameId: String)
|
||||
GET /$gameId<\w{8}>/continue/:mode controllers.Round.continue(gameId: String, mode: String)
|
||||
POST /$gameId<\w{8}>/note controllers.Round.writeNote(gameId: String)
|
||||
GET /$gameId<\w{8}>/edit controllers.Editor.game(gameId: String)
|
||||
GET /$gameId<\w{8}>/$color<white|black>/analysis controllers.UserAnalysis.game(gameId: String, color: String)
|
||||
|
||||
# Round accessibility: text representation
|
||||
GET /$fullId<\w{12}>/text controllers.Round.playerText(fullId: String)
|
||||
|
@ -241,7 +248,7 @@ DELETE /notification/$id<\w{8}> controllers.Notification.remove(id)
|
|||
GET /paste controllers.Importer.importGame
|
||||
POST /import controllers.Importer.sendGame
|
||||
|
||||
# Progressive import API
|
||||
# Progressive Import API
|
||||
POST /api/import/live controllers.Importer.liveCreate
|
||||
POST /api/import/live/$id<\w{8}>/:move controllers.Importer.liveMove(id: String, move: String)
|
||||
|
||||
|
|
|
@ -41,11 +41,11 @@ private[api] final class UserApi(
|
|||
|
||||
def one(username: String, token: Option[String]): Fu[Option[JsObject]] = UserRepo named username flatMap {
|
||||
case None => fuccess(none)
|
||||
case Some(u) => GameRepo nowPlaying u.id zip
|
||||
case Some(u) => GameRepo lastPlayed u zip
|
||||
makeUrl(R User username) zip
|
||||
(check(token) ?? (knownEnginesSharingIp(u.id) map (_.some))) flatMap {
|
||||
case ((gameOption, userUrl), knownEngines) => gameOption ?? { g =>
|
||||
makeUrl(R.Watcher(g.id, g.firstPlayer.color.name)) map (_.some)
|
||||
makeUrl(R.Watcher(g.gameId, g.color.name)) map (_.some)
|
||||
} map { gameUrlOption =>
|
||||
jsonView(u, extended = true) ++ Json.obj(
|
||||
"url" -> userUrl,
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2ff1c84d1dd70531ab2fce7fc49691b67d539f33
|
||||
Subproject commit afa9274da767775f9f228f4d06d2e58ddb0176f6
|
|
@ -20,12 +20,12 @@ final class Paginator[A] private[paginator] (
|
|||
/**
|
||||
* Returns the previous page.
|
||||
*/
|
||||
def previousPage: Option[Int] = (currentPage != 1) option (currentPage - 1)
|
||||
def previousPage: Option[Int] = (currentPage > 1) option (currentPage - 1)
|
||||
|
||||
/**
|
||||
* Returns the next page.
|
||||
*/
|
||||
def nextPage: Option[Int] = (currentPage != nbPages) option (currentPage + 1)
|
||||
def nextPage: Option[Int] = (currentPage < nbPages) option (currentPage + 1)
|
||||
|
||||
/**
|
||||
* Returns the number of pages.
|
||||
|
|
|
@ -61,8 +61,25 @@ object BSON {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicit def MapHandler[V](implicit vr: BSONReader[_ <: BSONValue, V], vw: BSONWriter[V, _ <: BSONValue]): BSONHandler[BSONDocument, Map[String, V]] = new BSONHandler[BSONDocument, Map[String, V]] {
|
||||
private val reader = MapReader[V]
|
||||
private val writer = MapWriter[V]
|
||||
def read(bson: BSONDocument): Map[String, V] = reader read bson
|
||||
def write(map: Map[String, V]): BSONDocument = writer write map
|
||||
}
|
||||
}
|
||||
|
||||
// List Handler
|
||||
final class ListHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]) extends BSONHandler[BSONArray, List[T]] {
|
||||
def read(array: BSONArray) = array.stream.filter(_.isSuccess).map { v =>
|
||||
reader.asInstanceOf[BSONReader[BSONValue, T]].read(v.get)
|
||||
}.toList
|
||||
def write(repr: List[T]) =
|
||||
new BSONArray(repr.map(s => scala.util.Try(writer.write(s))).to[Stream])
|
||||
}
|
||||
implicit def bsonArrayToListHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, List[T]] = new ListHandler
|
||||
|
||||
final class Reader(val doc: BSONDocument) {
|
||||
|
||||
val map = (doc.stream collect { case Success(e) => e }).toMap
|
||||
|
|
|
@ -164,11 +164,14 @@ object BinaryFormat {
|
|||
}
|
||||
|
||||
def read(ba: ByteArray): PieceMap = {
|
||||
def splitInts(int: Int) = Array(int >> 4, int & 0x0F)
|
||||
def splitInts(b: Byte) = {
|
||||
val int = b.toInt
|
||||
Array(int >> 4, int & 0x0F)
|
||||
}
|
||||
def intPiece(int: Int): Option[Piece] =
|
||||
intToRole(int & 7) map { role => Piece(Color((int & 8) == 0), role) }
|
||||
val (aliveInts, deadInts) = ba.value map toInt flatMap splitInts splitAt 64
|
||||
(Pos.all zip aliveInts flatMap {
|
||||
val pieceInts = ba.value flatMap splitInts
|
||||
(Pos.all zip pieceInts flatMap {
|
||||
case (pos, int) => intPiece(int) map (pos -> _)
|
||||
}).toMap
|
||||
}
|
||||
|
|
|
@ -6,29 +6,51 @@ import org.joda.time.DateTime
|
|||
import play.api.libs.json.JsObject
|
||||
|
||||
import lila.db.api.$count
|
||||
import lila.memo.{ AsyncCache, ExpireSetMemo, Builder }
|
||||
import lila.db.BSON._
|
||||
import lila.memo.{ AsyncCache, MongoCache, ExpireSetMemo, Builder }
|
||||
import lila.user.{ User, UidNb }
|
||||
import tube.gameTube
|
||||
import UidNb.UidNbBSONHandler
|
||||
|
||||
final class Cached(ttl: Duration) {
|
||||
final class Cached(
|
||||
mongoCache: MongoCache.Builder,
|
||||
defaultTtl: FiniteDuration) {
|
||||
|
||||
def nbGames: Fu[Int] = count(Query.all)
|
||||
def nbMates: Fu[Int] = count(Query.mate)
|
||||
def nbImported: Fu[Int] = count(Query.imported)
|
||||
def nbImportedBy(userId: String): Fu[Int] = count(Query imported userId)
|
||||
|
||||
def nbPlaying(userId: String): Fu[Int] = count(Query nowPlaying userId)
|
||||
def nbPlaying(userId: String): Fu[Int] = countShortTtl(Query nowPlaying userId)
|
||||
|
||||
private implicit val userHandler = User.userBSONHandler
|
||||
|
||||
private val isPlayingSimulCache = AsyncCache[String, Boolean](
|
||||
f = userId => GameRepo.countPlayingRealTime(userId) map (1 <),
|
||||
timeToLive = 10.seconds)
|
||||
|
||||
val isPlayingSimul: String => Fu[Boolean] = isPlayingSimulCache.apply _
|
||||
|
||||
val rematch960 = new ExpireSetMemo(3.hours)
|
||||
|
||||
val activePlayerUidsDay = AsyncCache(
|
||||
val activePlayerUidsDay = mongoCache[Int, List[UidNb]](
|
||||
prefix = "player:active:day",
|
||||
(nb: Int) => GameRepo.activePlayersSince(DateTime.now minusDays 1, nb),
|
||||
timeToLive = 1 hour)
|
||||
|
||||
val activePlayerUidsWeek = AsyncCache(
|
||||
val activePlayerUidsWeek = mongoCache[Int, List[UidNb]](
|
||||
prefix = "player:active:week",
|
||||
(nb: Int) => GameRepo.activePlayersSince(DateTime.now minusWeeks 1, nb),
|
||||
timeToLive = 6 hours)
|
||||
|
||||
private val count = AsyncCache((o: JsObject) => $count(o), timeToLive = ttl)
|
||||
private val countShortTtl = AsyncCache[JsObject, Int](
|
||||
f = (o: JsObject) => $count(o),
|
||||
timeToLive = 5.seconds)
|
||||
|
||||
private val count = mongoCache(
|
||||
prefix = "game:count",
|
||||
f = (o: JsObject) => $count(o),
|
||||
timeToLive = defaultTtl)
|
||||
|
||||
object Divider {
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import lila.common.PimpedConfig._
|
|||
final class Env(
|
||||
config: Config,
|
||||
db: lila.db.Env,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
system: ActorSystem,
|
||||
hub: lila.hub.Env,
|
||||
getLightUser: String => Option[lila.common.LightUser],
|
||||
|
@ -41,7 +42,9 @@ final class Env(
|
|||
|
||||
lazy val pngExport = PngExport(PngExecPath) _
|
||||
|
||||
lazy val cached = new Cached(ttl = CachedNbTtl)
|
||||
lazy val cached = new Cached(
|
||||
mongoCache = mongoCache,
|
||||
defaultTtl = CachedNbTtl)
|
||||
|
||||
lazy val paginator = new PaginatorBuilder(
|
||||
cached = cached,
|
||||
|
@ -100,6 +103,7 @@ object Env {
|
|||
lazy val current = "[boot] game" describes new Env(
|
||||
config = lila.common.PlayApp loadConfig "game",
|
||||
db = lila.db.Env.current,
|
||||
mongoCache = lila.memo.Env.current.mongoCache,
|
||||
system = lila.common.PlayApp.system,
|
||||
hub = lila.hub.Env.current,
|
||||
getLightUser = lila.user.Env.current.lightUser,
|
||||
|
|
|
@ -86,10 +86,11 @@ case class Game(
|
|||
// in tenths
|
||||
private def lastMoveTime: Option[Long] = castleLastMoveTime.lastMoveTime map {
|
||||
_.toLong + (createdAt.getMillis / 100)
|
||||
}
|
||||
} orElse updatedAt.map(_.getMillis / 100)
|
||||
|
||||
private def lastMoveTimeDate: Option[DateTime] = castleLastMoveTime.lastMoveTime map { lmt =>
|
||||
createdAt plusMillis (lmt * 100)
|
||||
}
|
||||
} orElse updatedAt
|
||||
|
||||
def lastMoveTimeInSeconds: Option[Int] = lastMoveTime.map(x => (x / 10).toInt)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import lila.db.api._
|
|||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.ByteArray
|
||||
import lila.db.Implicits._
|
||||
import lila.user.User
|
||||
import lila.user.{ User, UidNb }
|
||||
|
||||
object GameRepo {
|
||||
|
||||
|
@ -122,6 +122,18 @@ object GameRepo {
|
|||
_ flatMap { Pov(_, user) } sortBy Pov.priority
|
||||
}
|
||||
|
||||
// gets most urgent game to play
|
||||
def onePlaying(user: User): Fu[Option[Pov]] = nowPlaying(user) map (_.headOption)
|
||||
|
||||
// gets last recently played move game
|
||||
def lastPlayed(user: User): Fu[Option[Pov]] =
|
||||
$find.one($query(Query recentlyPlayingWithClock user.id) sort Query.sortUpdatedNoIndex) map {
|
||||
_ flatMap { Pov(_, user) }
|
||||
}
|
||||
|
||||
def countPlayingRealTime(userId: String): Fu[Int] =
|
||||
$count(Query.nowPlaying(userId) ++ Query.clock(true))
|
||||
|
||||
def setTv(id: ID) {
|
||||
$update.fieldUnchecked(id, F.tvAt, $date(DateTime.now))
|
||||
}
|
||||
|
@ -179,15 +191,15 @@ object GameRepo {
|
|||
_ sort Query.sortCreated skip (Random nextInt distribution)
|
||||
)
|
||||
|
||||
def insertDenormalized(game: Game, ratedCheck: Boolean = true): Funit = {
|
||||
val g2 = if (ratedCheck && game.rated && game.userIds.distinct.size != 2)
|
||||
game.copy(mode = chess.Mode.Casual)
|
||||
else game
|
||||
val userIds = game.userIds.distinct
|
||||
def insertDenormalized(g: Game, ratedCheck: Boolean = true): Funit = {
|
||||
val g2 = if (ratedCheck && g.rated && g.userIds.distinct.size != 2)
|
||||
g.copy(mode = chess.Mode.Casual)
|
||||
else g
|
||||
val userIds = g2.userIds.distinct
|
||||
val bson = (gameTube.handler write g2) ++ BSONDocument(
|
||||
F.initialFen -> g2.variant.exotic.option(Forsyth >> g2.toChess),
|
||||
F.checkAt -> (!game.isPgnImport).option(DateTime.now.plusHours(game.hasClock.fold(1, 24))),
|
||||
F.playingUids -> userIds.nonEmpty.option(userIds)
|
||||
F.checkAt -> (!g2.isPgnImport).option(DateTime.now.plusHours(g2.hasClock.fold(1, 24))),
|
||||
F.playingUids -> (g2.started && userIds.nonEmpty).option(userIds)
|
||||
)
|
||||
$insert bson bson
|
||||
}
|
||||
|
@ -239,16 +251,6 @@ object GameRepo {
|
|||
$count(Json.obj(F.createdAt -> ($gte($date(from)) ++ $lt($date(to)))))
|
||||
}).sequenceFu
|
||||
|
||||
def nowPlaying(userId: String): Fu[Option[Game]] =
|
||||
$find.one(Query.status(Status.Started) ++ Query.user(userId) ++ Json.obj(
|
||||
F.createdAt -> $gt($date(DateTime.now minusHours 1))
|
||||
))
|
||||
|
||||
def isNowPlaying(userId: String): Fu[Boolean] = nowPlaying(userId) map (_.isDefined)
|
||||
|
||||
def lastPlayed(userId: String): Fu[Option[Game]] =
|
||||
$find($query(Query user userId) sort ($sort desc F.createdAt), 1) map (_.headOption)
|
||||
|
||||
def bestOpponents(userId: String, limit: Int): Fu[List[(String, Int)]] = {
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.core.commands._
|
||||
|
@ -320,7 +322,7 @@ object GameRepo {
|
|||
))
|
||||
}
|
||||
|
||||
def activePlayersSince(since: DateTime, max: Int): Fu[List[(String, Int)]] = {
|
||||
def activePlayersSince(since: DateTime, max: Int): Fu[List[UidNb]] = {
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.core.commands._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
|
@ -342,7 +344,7 @@ object GameRepo {
|
|||
(stream.toList map { obj =>
|
||||
toJSON(obj).asOpt[JsObject] flatMap { o =>
|
||||
o int "nb" map { nb =>
|
||||
~(o str "_id") -> nb
|
||||
UidNb(~(o str "_id"), nb)
|
||||
}
|
||||
}
|
||||
}).flatten
|
||||
|
|
|
@ -18,9 +18,6 @@ case class Pov(game: Game, color: Color) {
|
|||
|
||||
def unary_! = Pov(game, !color)
|
||||
|
||||
def isPlayerFullId(fullId: Option[String]): Boolean =
|
||||
fullId ?? { game.isPlayerFullId(player, _) }
|
||||
|
||||
def ref = PovRef(game.id, color)
|
||||
|
||||
def withGame(g: Game) = copy(game = g)
|
||||
|
@ -32,6 +29,8 @@ case class Pov(game: Game, color: Color) {
|
|||
game.correspondenceClock.map(_.remainingTime(color).toInt)
|
||||
}
|
||||
|
||||
def hasMoved = game playerHasMoved color
|
||||
|
||||
override def toString = ref.toString
|
||||
}
|
||||
|
||||
|
@ -53,9 +52,11 @@ object Pov {
|
|||
game player user map { apply(game, _) }
|
||||
|
||||
def priority(pov: Pov) =
|
||||
if (pov.isMyTurn) pov.remainingSeconds.getOrElse(Int.MaxValue - 1)
|
||||
if (pov.isMyTurn) {
|
||||
if (pov.hasMoved) pov.remainingSeconds.getOrElse(Int.MaxValue - 1)
|
||||
else 10 // first move has priority over games with more than 10s left
|
||||
}
|
||||
else Int.MaxValue
|
||||
|
||||
}
|
||||
|
||||
case class PovRef(gameId: String, color: Color) {
|
||||
|
|
|
@ -50,6 +50,11 @@ object Query {
|
|||
|
||||
def nowPlaying(u: String) = Json.obj(F.playingUids -> u)
|
||||
|
||||
def recentlyPlayingWithClock(u: String) =
|
||||
nowPlaying(u) ++ clock(true) ++ Json.obj(
|
||||
F.updatedAt -> $gt($date(DateTime.now minusMinutes 5))
|
||||
)
|
||||
|
||||
// use the us index
|
||||
def win(u: String) = user(u) ++ Json.obj(F.winnerId -> u)
|
||||
|
||||
|
@ -65,4 +70,5 @@ object Query {
|
|||
def checkable = Json.obj(F.checkAt -> $lt($date(DateTime.now)))
|
||||
|
||||
val sortCreated = $sort desc F.createdAt
|
||||
val sortUpdatedNoIndex = $sort desc F.updatedAt
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ object Rewind {
|
|||
val rewindedHistory = rewindedGame.board.history
|
||||
val rewindedSituation = rewindedGame.situation
|
||||
def rewindPlayer(player: Player) = player.copy(isProposingTakeback = false)
|
||||
Progress(game, game.copy(
|
||||
val newGame = game.copy(
|
||||
whitePlayer = rewindPlayer(game.whitePlayer),
|
||||
blackPlayer = rewindPlayer(game.blackPlayer),
|
||||
binaryPieces = BinaryFormat.piece write rewindedGame.board.pieces,
|
||||
|
@ -29,7 +29,7 @@ object Rewind {
|
|||
check = if (rewindedSituation.check) rewindedSituation.kingPos else None),
|
||||
binaryMoveTimes = BinaryFormat.moveTime write (game.moveTimes take rewindedGame.turns),
|
||||
status = game.status,
|
||||
clock = game.clock map (_.switch)
|
||||
))
|
||||
clock = game.clock map (_.takeback))
|
||||
Progress(game, newGame, newGame.clock.map(Event.Clock.apply).toList)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import lila.common.PimpedConfig._
|
|||
|
||||
final class Env(
|
||||
config: Config,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
db: lila.db.Env) {
|
||||
|
||||
private val CachedRatingChartTtl = config duration "cached.rating_chart.ttl"
|
||||
|
@ -13,12 +14,16 @@ final class Env(
|
|||
|
||||
lazy val api = new HistoryApi(db(Collectionhistory))
|
||||
|
||||
lazy val ratingChartApi = new RatingChartApi(api, CachedRatingChartTtl)
|
||||
lazy val ratingChartApi = new RatingChartApi(
|
||||
historyApi = api,
|
||||
mongoCache = mongoCache,
|
||||
cacheTtl = CachedRatingChartTtl)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current = "[boot] history" describes new Env(
|
||||
config = lila.common.PlayApp loadConfig "history",
|
||||
mongoCache = lila.memo.Env.current.mongoCache,
|
||||
db = lila.db.Env.current)
|
||||
}
|
||||
|
|
|
@ -9,13 +9,21 @@ import play.api.libs.json._
|
|||
import lila.rating.{ Glicko, PerfType }
|
||||
import lila.user.{ User, Perfs }
|
||||
|
||||
final class RatingChartApi(historyApi: HistoryApi, cacheTtl: Duration) {
|
||||
final class RatingChartApi(
|
||||
historyApi: HistoryApi,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
cacheTtl: FiniteDuration) {
|
||||
|
||||
def apply(user: User): Fu[Option[String]] = cache(user)
|
||||
def apply(user: User): Fu[Option[String]] = cache(user) map { chart =>
|
||||
chart.nonEmpty option chart
|
||||
}
|
||||
|
||||
private val cache = lila.memo.AsyncCache(build,
|
||||
maxCapacity = 50,
|
||||
timeToLive = cacheTtl)
|
||||
private val cache = mongoCache[User, String](
|
||||
prefix = "history:rating",
|
||||
f = (user: User) => build(user) map (~_),
|
||||
maxCapacity = 64,
|
||||
timeToLive = cacheTtl,
|
||||
keyToString = _.id)
|
||||
|
||||
private val columns = Json stringify {
|
||||
Json.arr(
|
||||
|
|
|
@ -152,7 +152,8 @@ case class MoveEvent(
|
|||
gameId: String,
|
||||
fen: String,
|
||||
move: String,
|
||||
ip: String)
|
||||
ip: String,
|
||||
opponentUserId: Option[String])
|
||||
case class NbRounds(nb: Int)
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -21,11 +21,13 @@ private[lobby] final class Lobby(
|
|||
|
||||
def receive = {
|
||||
|
||||
case GetOpen(userOption) =>
|
||||
case HooksFor(userOption) =>
|
||||
val replyTo = sender
|
||||
(userOption.map(_.id) ?? blocking) foreach { blocks =>
|
||||
val lobbyUser = userOption map { LobbyUser.make(_, blocks) }
|
||||
replyTo ! HookRepo.list.filter { Biter.canJoin(_, lobbyUser) }
|
||||
replyTo ! HookRepo.list.filter { hook =>
|
||||
~(hook.userId |@| lobbyUser.map(_.id)).apply(_ == _) || Biter.canJoin(hook, lobbyUser)
|
||||
}
|
||||
}
|
||||
|
||||
case msg@AddHook(hook) => {
|
||||
|
|
|
@ -83,7 +83,7 @@ object Seek {
|
|||
createdAt = DateTime.now)
|
||||
|
||||
import reactivemongo.bson.Macros
|
||||
import lila.db.BSON.MapValue._
|
||||
import lila.db.BSON.MapValue.MapHandler
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
private[lobby] implicit val lobbyUserBSONHandler = Macros.handler[LobbyUser]
|
||||
private[lobby] implicit val seekBSONHandler = Macros.handler[Seek]
|
||||
|
|
|
@ -3,9 +3,11 @@ package lila.lobby
|
|||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
|
||||
import reactivemongo.core.commands._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import actorApi.LobbyUser
|
||||
import lila.db.Types.Coll
|
||||
import lila.memo.AsyncCache
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class SeekApi(
|
||||
|
@ -14,18 +16,33 @@ final class SeekApi(
|
|||
maxPerPage: Int,
|
||||
maxPerUser: Int) {
|
||||
|
||||
def forAnon: Fu[List[Seek]] =
|
||||
private sealed trait CacheKey
|
||||
private object ForAnon extends CacheKey
|
||||
private object ForUser extends CacheKey
|
||||
|
||||
private def allCursor =
|
||||
coll.find(BSONDocument())
|
||||
.sort(BSONDocument("createdAt" -> -1))
|
||||
.cursor[Seek].collect[List](maxPerPage)
|
||||
.cursor[Seek]
|
||||
|
||||
private val cache = AsyncCache[CacheKey, List[Seek]](
|
||||
f = {
|
||||
case ForAnon => allCursor.collect[List](maxPerPage)
|
||||
case ForUser => allCursor.collect[List]()
|
||||
},
|
||||
timeToLive = 5.seconds)
|
||||
|
||||
def forAnon = cache(ForAnon)
|
||||
|
||||
def forUser(user: User): Fu[List[Seek]] =
|
||||
blocking(user.id) flatMap { blocking =>
|
||||
forUser(LobbyUser.make(user, blocking))
|
||||
}
|
||||
|
||||
def forUser(user: LobbyUser): Fu[List[Seek]] = forAnon map {
|
||||
_ filter { Biter.canJoin(_, user) }
|
||||
def forUser(user: LobbyUser): Fu[List[Seek]] = cache(ForUser) map {
|
||||
_ filter { seek =>
|
||||
seek.user.id == user.id || Biter.canJoin(seek, user)
|
||||
} take maxPerPage
|
||||
}
|
||||
|
||||
def find(id: String): Fu[Option[Seek]] =
|
||||
|
@ -33,19 +50,21 @@ final class SeekApi(
|
|||
|
||||
def insert(seek: Seek) = coll.insert(seek) >> findByUser(seek.user.id).flatMap {
|
||||
case seeks if seeks.size <= maxPerUser => funit
|
||||
case seeks => seeks.drop(maxPerUser).map(remove).sequenceFu
|
||||
}
|
||||
case seeks =>
|
||||
seeks.drop(maxPerUser).map(remove).sequenceFu
|
||||
} >> cache.clear
|
||||
|
||||
def findByUser(userId: String): Fu[List[Seek]] =
|
||||
coll.find(BSONDocument("user.id" -> userId))
|
||||
.sort(BSONDocument("createdAt" -> -1))
|
||||
.cursor[Seek].collect[List]()
|
||||
|
||||
def remove(seek: Seek) = coll.remove(BSONDocument("_id" -> seek.id)).void
|
||||
def remove(seek: Seek) =
|
||||
coll.remove(BSONDocument("_id" -> seek.id)).void >> cache.clear
|
||||
|
||||
def removeBy(seekId: String, userId: String) =
|
||||
coll.remove(BSONDocument(
|
||||
"_id" -> seekId,
|
||||
"user.id" -> userId
|
||||
)).void
|
||||
)).void >> cache.clear
|
||||
}
|
||||
|
|
|
@ -64,4 +64,4 @@ private[lobby] case class HookIds(ids: List[String])
|
|||
|
||||
case class AddHook(hook: Hook)
|
||||
case class AddSeek(seek: Seek)
|
||||
case class GetOpen(user: Option[User])
|
||||
case class HooksFor(user: Option[User])
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package lila.memo
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import lila.db.Types._
|
||||
|
||||
final class Env(config: Config, db: lila.db.Env) {
|
||||
|
||||
private val CollectionCache = config getString "collection.cache"
|
||||
|
||||
lazy val mongoCache: MongoCache.Builder = MongoCache(db(CollectionCache))
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current = "[boot] memo" describes new Env(
|
||||
lila.common.PlayApp loadConfig "memo",
|
||||
lila.db.Env.current)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package lila.memo
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.bson.Macros
|
||||
import scala.concurrent.duration._
|
||||
import spray.caching.{ LruCache, Cache }
|
||||
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.Types._
|
||||
|
||||
final class MongoCache[K, V: MongoCache.Handler] private (
|
||||
prefix: String,
|
||||
expiresAt: () => DateTime,
|
||||
cache: Cache[V],
|
||||
coll: Coll,
|
||||
f: K => Fu[V],
|
||||
keyToString: K => String) {
|
||||
|
||||
def apply(k: K): Fu[V] = cache(k) {
|
||||
coll.find(select(k)).one[Entry] flatMap {
|
||||
case None => f(k) flatMap { v =>
|
||||
coll.insert(makeEntry(k, v)) inject v
|
||||
}
|
||||
case Some(entry) => fuccess(entry.v)
|
||||
}
|
||||
}
|
||||
|
||||
def remove(k: K): Funit =
|
||||
coll.remove(select(k)).void >>- (cache remove k)
|
||||
|
||||
private case class Entry(_id: String, v: V, e: DateTime)
|
||||
|
||||
private implicit val entryBSONHandler = Macros.handler[Entry]
|
||||
|
||||
private def makeEntry(k: K, v: V) = Entry(makeKey(k), v, expiresAt())
|
||||
|
||||
private def makeKey(k: K) = s"$prefix:${keyToString(k)}"
|
||||
|
||||
private def select(k: K) = BSONDocument("_id" -> makeKey(k))
|
||||
}
|
||||
|
||||
object MongoCache {
|
||||
|
||||
private type Handler[T] = BSONHandler[_ <: BSONValue, T]
|
||||
|
||||
private def expiresAt(ttl: Duration)(): DateTime =
|
||||
DateTime.now plusSeconds ttl.toSeconds.toInt
|
||||
|
||||
final class Builder(coll: Coll) {
|
||||
|
||||
def apply[K, V: Handler](
|
||||
prefix: String,
|
||||
f: K => Fu[V],
|
||||
maxCapacity: Int = 512,
|
||||
initialCapacity: Int = 64,
|
||||
timeToLive: FiniteDuration,
|
||||
timeToLiveMongo: Option[FiniteDuration] = None,
|
||||
keyToString: K => String = (k: K) => k.toString): MongoCache[K, V] = new MongoCache[K, V](
|
||||
prefix = prefix,
|
||||
expiresAt = expiresAt(timeToLiveMongo | timeToLive),
|
||||
cache = LruCache(maxCapacity, initialCapacity, timeToLive),
|
||||
coll = coll,
|
||||
f = f,
|
||||
keyToString = keyToString)
|
||||
|
||||
def single[V: Handler](
|
||||
prefix: String,
|
||||
f: => Fu[V],
|
||||
timeToLive: FiniteDuration,
|
||||
timeToLiveMongo: Option[FiniteDuration] = None) = new MongoCache[Boolean, V](
|
||||
prefix = prefix,
|
||||
expiresAt = expiresAt(timeToLiveMongo | timeToLive),
|
||||
cache = LruCache(timeToLive = timeToLive),
|
||||
coll = coll,
|
||||
f = _ => f,
|
||||
keyToString = _.toString)
|
||||
}
|
||||
|
||||
def apply(coll: Coll) = new Builder(coll)
|
||||
}
|
|
@ -8,6 +8,7 @@ import lila.hub.actorApi.message.LichessThread
|
|||
final class Env(
|
||||
config: Config,
|
||||
db: lila.db.Env,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
blocks: (String, String) => Fu[Boolean],
|
||||
system: ActorSystem) {
|
||||
|
||||
|
@ -17,7 +18,7 @@ final class Env(
|
|||
|
||||
private[message] lazy val threadColl = db(CollectionThread)
|
||||
|
||||
private lazy val unreadCache = new UnreadCache
|
||||
private lazy val unreadCache = new UnreadCache(mongoCache)
|
||||
|
||||
lazy val forms = new DataForm(blocks = blocks)
|
||||
|
||||
|
@ -46,6 +47,7 @@ object Env {
|
|||
lazy val current = "[boot] message" describes new Env(
|
||||
config = lila.common.PlayApp loadConfig "message",
|
||||
db = lila.db.Env.current,
|
||||
mongoCache = lila.memo.Env.current.mongoCache,
|
||||
blocks = lila.relation.Env.current.api.blocks,
|
||||
system = lila.common.PlayApp.system)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
package lila.message
|
||||
|
||||
import spray.caching.{ LruCache, Cache }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.BSON._
|
||||
import lila.user.User
|
||||
|
||||
private[message] final class UnreadCache {
|
||||
private[message] final class UnreadCache(
|
||||
mongoCache: lila.memo.MongoCache.Builder) {
|
||||
|
||||
// userId => thread IDs
|
||||
private val cache: Cache[List[String]] = LruCache(maxCapacity = 99999)
|
||||
private val cache = mongoCache[String, List[String]](
|
||||
prefix = "message:unread",
|
||||
f = ThreadRepo.userUnreadIds,
|
||||
maxCapacity = 4096,
|
||||
timeToLive = 2.days)
|
||||
|
||||
def apply(userId: String): Fu[List[String]] =
|
||||
cache(userId)(ThreadRepo userUnreadIds userId)
|
||||
def apply(userId: String): Fu[List[String]] = cache(userId)
|
||||
|
||||
def refresh(userId: String): Fu[List[String]] =
|
||||
(cache remove userId).fold(apply(userId))(_ >> apply(userId))
|
||||
(cache remove userId) >> apply(userId)
|
||||
|
||||
def clear(userId: String) = (cache remove userId).fold(funit)(_.void)
|
||||
def clear(userId: String) = cache remove userId
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ final class PrefApi(coll: Coll, cacheTtl: Duration) {
|
|||
|
||||
private implicit val prefBSONHandler = new BSON[Pref] {
|
||||
|
||||
import lila.db.BSON.MapValue._
|
||||
import lila.db.BSON.MapValue.{ MapReader, MapWriter }
|
||||
implicit val tagsReader = MapReader[String]
|
||||
implicit val tagsWriter = MapWriter[String]
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ final class Env(
|
|||
config: Config,
|
||||
hub: lila.hub.Env,
|
||||
detectLanguage: DetectLanguage,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
db: lila.db.Env) {
|
||||
|
||||
private val CollectionQuestion = config getString "collection.question"
|
||||
|
@ -19,6 +20,7 @@ final class Env(
|
|||
lazy val api = new QaApi(
|
||||
questionColl = questionColl,
|
||||
answerColl = db(CollectionAnswer),
|
||||
mongoCache = mongoCache,
|
||||
notifier = notifier)
|
||||
|
||||
private lazy val notifier = new Notifier(
|
||||
|
@ -37,5 +39,6 @@ object Env {
|
|||
config = lila.common.PlayApp loadConfig "qa",
|
||||
hub = lila.hub.Env.current,
|
||||
detectLanguage = DetectLanguage(lila.common.PlayApp loadConfig "detectlanguage"),
|
||||
mongoCache = lila.memo.Env.current.mongoCache,
|
||||
db = lila.db.Env.current)
|
||||
}
|
||||
|
|
|
@ -9,15 +9,15 @@ import org.joda.time.DateTime
|
|||
import spray.caching.{ LruCache, Cache }
|
||||
|
||||
import lila.common.paginator._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.BSON._
|
||||
import lila.db.paginator._
|
||||
import lila.db.Types.Coll
|
||||
import lila.memo.AsyncCache
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class QaApi(
|
||||
questionColl: Coll,
|
||||
answerColl: Coll,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
notifier: Notifier) {
|
||||
|
||||
object question {
|
||||
|
@ -84,11 +84,12 @@ final class QaApi(
|
|||
currentPage = page,
|
||||
maxPerPage = perPage)
|
||||
|
||||
private def popularCache = AsyncCache(
|
||||
(nb: Int) => questionColl.find(BSONDocument())
|
||||
private def popularCache = mongoCache(
|
||||
prefix = "qa:popular",
|
||||
f = (nb: Int) => questionColl.find(BSONDocument())
|
||||
.sort(BSONDocument("vote.score" -> -1))
|
||||
.cursor[Question].collect[List](nb),
|
||||
timeToLive = 1 hour)
|
||||
timeToLive = 3 hour)
|
||||
|
||||
def popular(max: Int): Fu[List[Question]] = popularCache(max)
|
||||
|
||||
|
@ -111,7 +112,7 @@ final class QaApi(
|
|||
questionColl.update(
|
||||
BSONDocument("_id" -> q.id),
|
||||
BSONDocument("$set" -> BSONDocument("vote" -> newVote))
|
||||
) >> profile.clearCache >> popularCache.clear inject newVote.some
|
||||
) >> profile.clearCache inject newVote.some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,4 +82,5 @@ object PerfType {
|
|||
def name(key: Perf.Key): Option[String] = apply(key) map (_.name)
|
||||
|
||||
val nonPuzzle: List[PerfType] = List(Bullet, Blitz, Classical, Correspondence, Chess960, KingOfTheHill, ThreeCheck)
|
||||
val leaderboardable: List[PerfType] = List(Bullet, Blitz, Classical, Chess960, KingOfTheHill, ThreeCheck)
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ private[report] final class ReportApi(evaluator: ActorSelection) {
|
|||
if (by.id == UserRepo.lichessId) reportTube.coll.update(
|
||||
selectRecent(user, reason),
|
||||
reportTube.toMongo(report).get - "_id"
|
||||
) map { res =>
|
||||
if (!res.updatedExisting) {
|
||||
) flatMap { res =>
|
||||
(!res.updatedExisting) ?? {
|
||||
if (report.isCheat) evaluator ! user
|
||||
$insert(report)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import scala.concurrent.duration._
|
|||
import actorApi.{ GetSocketStatus, SocketStatus }
|
||||
import lila.common.PimpedConfig._
|
||||
import lila.hub.actorApi.map.Ask
|
||||
import lila.memo.AsyncCache
|
||||
import lila.socket.actorApi.GetVersion
|
||||
import makeTimeout.large
|
||||
|
||||
|
@ -29,6 +28,7 @@ final class Env(
|
|||
prefApi: lila.pref.PrefApi,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
historyApi: lila.history.HistoryApi,
|
||||
isPlayingSimul: String => Fu[Boolean],
|
||||
scheduler: lila.common.Scheduler) {
|
||||
|
||||
private val settings = new {
|
||||
|
@ -84,7 +84,8 @@ final class Env(
|
|||
uidTimeout = UidTimeout,
|
||||
socketTimeout = SocketTimeout,
|
||||
disconnectTimeout = PlayerDisconnectTimeout,
|
||||
ragequitTimeout = PlayerRagequitTimeout)
|
||||
ragequitTimeout = PlayerRagequitTimeout,
|
||||
isPlayingSimul = isPlayingSimul)
|
||||
def receive: Receive = ({
|
||||
case msg@lila.chat.actorApi.ChatLine(id, line) =>
|
||||
self ! lila.hub.actorApi.map.Tell(id take 8, msg)
|
||||
|
@ -197,5 +198,6 @@ object Env {
|
|||
prefApi = lila.pref.Env.current.api,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
historyApi = lila.history.Env.current.api,
|
||||
isPlayingSimul = lila.game.Env.current.cached.isPlayingSimul,
|
||||
scheduler = lila.common.PlayApp.scheduler)
|
||||
}
|
||||
|
|
|
@ -62,9 +62,7 @@ final class JsonView(
|
|||
"check" -> game.check.map(_.key),
|
||||
"rematch" -> game.next,
|
||||
"source" -> game.source.map(sourceJson),
|
||||
"status" -> Json.obj(
|
||||
"id" -> game.status.id,
|
||||
"name" -> game.status.name)),
|
||||
"status" -> statusJson(game.status)),
|
||||
"clock" -> game.clock.map(clockJson),
|
||||
"correspondence" -> game.correspondenceClock.map(correspondenceJson),
|
||||
"player" -> Json.obj(
|
||||
|
@ -166,9 +164,7 @@ final class JsonView(
|
|||
"size" -> o.size
|
||||
)
|
||||
},
|
||||
"status" -> Json.obj(
|
||||
"id" -> game.status.id,
|
||||
"name" -> game.status.name)),
|
||||
"status" -> statusJson(game.status)),
|
||||
"clock" -> game.clock.map(clockJson),
|
||||
"correspondence" -> game.correspondenceClock.map(correspondenceJson),
|
||||
"player" -> Json.obj(
|
||||
|
@ -219,6 +215,32 @@ final class JsonView(
|
|||
)
|
||||
}
|
||||
|
||||
def userAnalysisJson(pov: Pov, pref: Pref) = {
|
||||
import pov._
|
||||
val fen = Forsyth >> game.toChess
|
||||
Json.obj(
|
||||
"game" -> Json.obj(
|
||||
"id" -> gameId,
|
||||
"variant" -> variantJson(game.variant),
|
||||
"initialFen" -> fen,
|
||||
"fen" -> fen,
|
||||
"player" -> game.turnColor.name,
|
||||
"status" -> statusJson(game.status)),
|
||||
"player" -> Json.obj(
|
||||
"color" -> color.name
|
||||
),
|
||||
"opponent" -> Json.obj(
|
||||
"color" -> opponent.color.name
|
||||
),
|
||||
"pref" -> Json.obj(
|
||||
"animationDuration" -> animationDuration(pov, pref),
|
||||
"highlight" -> pref.highlight,
|
||||
"destination" -> pref.destination,
|
||||
"coords" -> pref.coords
|
||||
),
|
||||
"userAnalysis" -> true)
|
||||
}
|
||||
|
||||
private def blurs(game: Game, player: lila.game.Player) = {
|
||||
val percent = game.playerBlurPercent(player.color)
|
||||
(percent > 30) option Json.obj(
|
||||
|
@ -258,10 +280,13 @@ final class JsonView(
|
|||
"moretime" -> moretimeSeconds)
|
||||
|
||||
private def correspondenceJson(c: CorrespondenceClock) = Json.obj(
|
||||
"increment" -> c.increment,
|
||||
"white" -> c.whiteTime,
|
||||
"black" -> c.blackTime,
|
||||
"emerg" -> c.emerg)
|
||||
"increment" -> c.increment,
|
||||
"white" -> c.whiteTime,
|
||||
"black" -> c.blackTime,
|
||||
"emerg" -> c.emerg)
|
||||
|
||||
private def statusJson(s: chess.Status) =
|
||||
Json.obj("id" -> s.id, "name" -> s.name)
|
||||
|
||||
private def sourceJson(source: Source) = source.name
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ private[round] final class Player(
|
|||
case (progress, move) =>
|
||||
(GameRepo save progress) >>-
|
||||
(pov.game.hasAi ! uciMemo.add(pov.game, move)) >>-
|
||||
notifyProgress(move, progress, ip) >>
|
||||
notifyMove(move, progress.game, ip) >>
|
||||
progress.game.finished.fold(
|
||||
moveFinish(progress.game, color) map { progress.events ::: _ }, {
|
||||
cheatDetector(progress.game) addEffect {
|
||||
|
@ -61,13 +61,13 @@ private[round] final class Player(
|
|||
fufail(s"[ai play] game ${game.id} turn ${game.turns} not AI turn")
|
||||
) logFailureErr s"[ai play] game ${game.id} turn ${game.turns}"
|
||||
|
||||
private def notifyProgress(move: chess.Move, progress: Progress, ip: String) {
|
||||
val game = progress.game
|
||||
private def notifyMove(move: chess.Move, game: Game, ip: String) {
|
||||
bus.publish(MoveEvent(
|
||||
ip = ip,
|
||||
gameId = game.id,
|
||||
fen = Forsyth exportBoard game.toChess.board,
|
||||
move = move.keyString
|
||||
move = move.keyString,
|
||||
opponentUserId = game.player(!move.color).userId
|
||||
), 'moveEvent)
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue