Merge branch 'master' of github.com:ornicar/lila into Divider
* 'master' of github.com:ornicar/lila: (23 commits) upgrade scalachess So long, pgn4web. The day I can ditch you has finally come. mithril/chessground analysis seems to be working analysis tree navigation is working more work on new analysis page fix analysis json and template fix variation issue by copying arrays. Dammit mutability sucks. less AI recover attempts progress on pgn4web replacement try a new analysis client data structure compile client apps as dev, trash pgn4web tree analysis WIP more on mithril analysis rewrite connect replay with advantage chart good progress on new replay UI share more code between round and analyse progress on analysis ignore setup clock constraints when disabled - fixes #125 more progress on new analyse, try to reuse code from round remove round.util ... Conflicts: app/controllers/Analyse.scala app/views/analyse/replay.scala.html
This commit is contained in:
commit
b9a5ba2861
|
@ -52,35 +52,31 @@ object Analyse extends LilaController {
|
|||
}
|
||||
|
||||
def replay(pov: Pov)(implicit ctx: Context) =
|
||||
Env.round.version(pov.gameId) zip
|
||||
Env.game.pgnDump(pov.game) zip
|
||||
Env.game.pgnDump(pov.game) zip
|
||||
(env.analyser get pov.game.id) zip
|
||||
(pov.game.tournamentId ?? lila.tournament.TournamentRepo.byId) zip
|
||||
Env.game.crosstableApi(pov.game) zip
|
||||
(GameRepo initialFen pov.game.id) zip
|
||||
(ctx.isAuth ?? {
|
||||
Env.chat.api.userChat find s"${pov.gameId}/w" map (_.forUser(ctx.me).some)
|
||||
}) map {
|
||||
case ((((((version, pgn), analysis), tour), crosstable), initialFen), chat) => {
|
||||
val opening = gameOpening(pov.game)
|
||||
val replay = chess.Replay(
|
||||
pgn = pov.game.pgnMoves mkString " ",
|
||||
initialFen = initialFen,
|
||||
variant = pov.game.variant
|
||||
).toOption
|
||||
Ok(html.analyse.replay(
|
||||
pov,
|
||||
Env.analyse.annotator(pgn, analysis, opening, pov.game.winnerColor, pov.game.status, pov.game.clock).toString,
|
||||
opening,
|
||||
analysis,
|
||||
analysis filter (_.done) map { a => AdvantageChart(a.infoAdvices, pov.game.pgnMoves) },
|
||||
version,
|
||||
chat,
|
||||
tour,
|
||||
new TimeChart(pov.game, pov.game.pgnMoves),
|
||||
crosstable,
|
||||
replay))
|
||||
}
|
||||
Env.game.crosstableApi(pov.game) flatMap {
|
||||
case ((((pgn, analysis), tour), initialFen), crosstable) =>
|
||||
Env.api.roundApi.watcher(pov, Env.api.version, tv = none, analysis.map(pgn -> _)) map { data =>
|
||||
val opening = gameOpening(pov.game)
|
||||
val replay = chess.Replay(
|
||||
pgn = pov.game.pgnMoves mkString " ",
|
||||
initialFen = initialFen,
|
||||
variant = pov.game.variant
|
||||
).toOption
|
||||
Ok(html.analyse.replay(
|
||||
pov,
|
||||
data,
|
||||
Env.analyse.annotator(pgn, analysis, opening, pov.game.winnerColor, pov.game.status, pov.game.clock).toString,
|
||||
opening,
|
||||
analysis,
|
||||
analysis filter (_.done) map { a => AdvantageChart(a.infoAdvices, pov.game.pgnMoves) },
|
||||
tour,
|
||||
new TimeChart(pov.game, pov.game.pgnMoves),
|
||||
crosstable,
|
||||
replay))
|
||||
}
|
||||
}
|
||||
|
||||
private def gameOpening(game: GameModel) =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(pov: Pov, pgn: String, opening: Option[chess.OpeningExplorer.Opening], analysis: Option[lila.analyse.Analysis], advantageChart: Option[String], version: Int, chat: Option[lila.chat.UserChat], tour: Option[lila.tournament.Tournament], timeChart: lila.analyse.TimeChart, cross: Option[lila.game.Crosstable], replay: Option[chess.Replay])(implicit ctx: Context)
|
||||
@(pov: Pov, data: play.api.libs.json.JsObject, pgn: String, opening: Option[chess.OpeningExplorer.Opening], analysis: Option[lila.analyse.Analysis], advantageChart: Option[String], tour: Option[lila.tournament.Tournament], timeChart: lila.analyse.TimeChart, cross: Option[lila.game.Crosstable], replay: Option[chess.Replay])(implicit ctx: Context)
|
||||
|
||||
@import pov._
|
||||
|
||||
|
@ -9,13 +9,17 @@
|
|||
}
|
||||
|
||||
@moreJs = {
|
||||
@jsAt("vendor/pgn4web/pgn4web-compacted.js")
|
||||
@jsTag("vendor/jquery.mousewheel.js")
|
||||
@jsTagCompiled("pgn4hacks.js")
|
||||
@highchartsTag
|
||||
@jsTagCompiled("chart2.js")
|
||||
@analysis.map { a =>
|
||||
@embedJs(s"var lichess_best_moves=${toJson(a.bestMoves)};")
|
||||
@jsAt(s"compiled/lichess.analyse${isProd??(".min")}.js")
|
||||
@round.jsRoutes()
|
||||
@embedJs {
|
||||
lichess = lichess || {};
|
||||
lichess.analyse = {
|
||||
data: @Html(play.api.libs.json.Json.stringify(data)),
|
||||
routes: roundRoutes.controllers,
|
||||
i18n: @round.jsI18n()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,157 +42,121 @@
|
|||
@analyse.layout(
|
||||
title = title,
|
||||
side = views.html.game.side(pov, tour, withTourStanding = false).some,
|
||||
chat = chat.map(c => base.chat(c, trans.spectatorRoom.str())),
|
||||
chat = base.chatDom(trans.spectatorRoom.str()).some,
|
||||
underchat = underchat.some,
|
||||
moreCss = moreCss,
|
||||
moreJs = moreJs,
|
||||
openGraph = povOpenGraph(pov)) {
|
||||
<div class="analyse clearfix">
|
||||
@if(ctx.blindMode) {
|
||||
<div id="lichess_board_blind" data-href="@routes.Round.watcherText(gameId, color.name)">
|
||||
@views.html.game.textualRepresentation(pov, false)
|
||||
</div>
|
||||
}
|
||||
<div class="board_wrap">
|
||||
<div
|
||||
id="GameBoard"
|
||||
data-version="@version"
|
||||
data-socket-url="@routes.Round.websocketWatcher(gameId, color.name)"
|
||||
class="@color.fold("", "flip") @pov.game.variant.key"></div>
|
||||
<div class="game_control">
|
||||
<a class="button flip hint--bottom" data-hint="@trans.flipBoard()" href="@routes.Round.watcher(gameId, (!color).name)#0">
|
||||
<span data-icon="B"></span>
|
||||
</a>
|
||||
<a class="fen_link button hint--bottom" data-hint="@trans.boardEditor()" href="@routes.Editor.game(game.id)?fen=">
|
||||
<span data-icon="m"></span>
|
||||
</a>
|
||||
<a class="continue button hint--bottom" data-hint="@trans.continueFromHere()" href="#">
|
||||
<span data-icon="U"></span>
|
||||
</a>
|
||||
<div id="GameButtons"></div>
|
||||
</div>
|
||||
<div class="continue none">
|
||||
<a class="fen_link button" href="@routes.Round.continue(game.id, "ai")?fen=">@trans.playWithTheMachine()</a>
|
||||
<a class="fen_link button" href="@routes.Round.continue(game.id, "friend")?fen=">@trans.playWithAFriend()</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="moves_wrap">
|
||||
<div id="GameText" class="scroll-shadow-soft"></div>
|
||||
@game.next.map { n =>
|
||||
<p class="rematch_wrap">
|
||||
<a class="button" href="@routes.Round.watcher(n, pov.opponent.color.name)" data-icon="G"> @trans.viewRematch()</a>
|
||||
</p>
|
||||
<div class="analyse cg-512">@miniBoardContent</div>
|
||||
<div class="advice_summary" style="display:none">
|
||||
@round.blurs(game)
|
||||
@round.holdAlerts(game)
|
||||
@analysis.filter(_.done).map { a =>
|
||||
<table>
|
||||
@for((color, pairs) <- a.summary) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">
|
||||
@playerLink(pov.game.player(color), withOnline = false, cssClass = Some("player color " + color.name))
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@for((nag, nb) <- pairs) {
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>@nagName(nag)</th>
|
||||
<td><strong>@nb</strong></td>
|
||||
<td><strong>@("%.1f".format(100f * nb / pov.game.playerMoves(color)))</strong>%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
<div class="advice_summary">
|
||||
@round.blurs(game)
|
||||
@round.holdAlerts(game)
|
||||
@analysis.filter(_.done).map { a =>
|
||||
<table>
|
||||
@for((color, pairs) <- a.summary) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">
|
||||
@playerLink(pov.game.player(color), withOnline = false, cssClass = Some("player color " + color.name))
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@for((nag, nb) <- pairs) {
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>@nagName(nag)</th>
|
||||
<td><strong>@nb</strong></td>
|
||||
<td><strong>@("%.1f".format(100f * nb / pov.game.playerMoves(color)))</strong>%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
<div class="underboard_content" style="display:none">
|
||||
<div class="analysis_panels">
|
||||
@if(game.analysable) {
|
||||
<div class="panel computer_analysis">
|
||||
@analysis.map { a =>
|
||||
@advantageChart.map { chart =>
|
||||
<div
|
||||
@replay.map { replay =>
|
||||
@chess.Divider(replay) match {
|
||||
case (mid, end) => {
|
||||
data-division-mid="@mid.getOrElse("null")"
|
||||
data-division-end="@end.getOrElse("null")"
|
||||
}
|
||||
}
|
||||
</table>
|
||||
}
|
||||
id="adv_chart"
|
||||
data-title="Advantage (up: white, down: black)"
|
||||
data-max="@lila.analyse.AdvantageChart.max"
|
||||
data-rows="@chart"></div>
|
||||
}.getOrElse {
|
||||
@analyse.computing()
|
||||
}
|
||||
}.getOrElse {
|
||||
@if(analysis.isEmpty) {
|
||||
<form class="future_game_analysis@if(ctx.isAnon) { must_login }" action="@routes.Analyse.requestAnalysis(gameId)" method="post">
|
||||
<button type="submit" class="button"><span class="is3" data-icon="A"> @trans.requestAComputerAnalysis()</span></button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<div class="view_game_analysis future_game_analysis" data-href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<a class="button" href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<span class="is3" data-icon="A"> @trans.viewTheComputerAnalysis()</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="pgnText" readonly="readonly">@Html(pgn)</textarea>
|
||||
<div class="analysis_panels">
|
||||
@if(game.analysable) {
|
||||
<div class="panel computer_analysis">
|
||||
@analysis.map { a =>
|
||||
@advantageChart.map { chart =>
|
||||
<div
|
||||
@replay.map { replay =>
|
||||
@chess.Divider(replay) match {
|
||||
case (mid, end) => {
|
||||
data-division-mid="@mid.getOrElse("null")"
|
||||
data-division-end="@end.getOrElse("null")"
|
||||
}
|
||||
}
|
||||
}
|
||||
id="adv_chart"
|
||||
data-title="Advantage (up: white, down: black)"
|
||||
data-max="@lila.analyse.AdvantageChart.max"
|
||||
data-rows="@chart"></div>
|
||||
}.getOrElse {
|
||||
@analyse.computing()
|
||||
}
|
||||
}.getOrElse {
|
||||
@if(analysis.isEmpty) {
|
||||
<form class="future_game_analysis@if(ctx.isAnon) { must_login }" action="@routes.Analyse.requestAnalysis(gameId)" method="post">
|
||||
<button type="submit" class="button"><span class="is3" data-icon="A"> @trans.requestAComputerAnalysis()</span></button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<div class="view_game_analysis future_game_analysis" data-href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<a class="button" href="@routes.Round.watcher(pov.gameId, pov.color.name)">
|
||||
<span class="is3" data-icon="A"> @trans.viewTheComputerAnalysis()</span>
|
||||
</a>
|
||||
<div class="panel fen_pgn">
|
||||
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
|
||||
<p><strong>PGN</strong>
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)"> Download annotated</a>
|
||||
@if(analysis.isDefined) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=raw"> Download raw</a>
|
||||
}
|
||||
@if(game.isPgnImport) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=imported"> Download imported</a>
|
||||
}
|
||||
/
|
||||
<a data-icon="x" target="_blank" href="@cdnUrl(routes.Export.pdf(game.id).url)"> Print-friendly PDF</a>
|
||||
</p>
|
||||
<div class="pgn">@Html(nl2br(escape(pgn)))</div>
|
||||
</div>
|
||||
<div class="panel move_times">
|
||||
<div
|
||||
id="movetimes_chart"
|
||||
data-series="@timeChart.series"
|
||||
data-max="@timeChart.maxTime"></div>
|
||||
</div>
|
||||
@cross.map { c =>
|
||||
<div class="panel crosstable">
|
||||
@views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov))
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="panel fen_pgn">
|
||||
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
|
||||
<p><strong>PGN</strong>
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)"> Download annotated</a>
|
||||
@if(analysis.isDefined) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=raw"> Download raw</a>
|
||||
}
|
||||
@if(game.isPgnImport) {
|
||||
/
|
||||
<a data-icon="x" href="@routes.Export.pgn(game.id)?as=imported"> Download imported</a>
|
||||
}
|
||||
/
|
||||
<a data-icon="x" target="_blank" href="@cdnUrl(routes.Export.pdf(game.id).url)"> Print-friendly PDF</a>
|
||||
</p>
|
||||
<div class="pgn">@Html(nl2br(escape(pgn)))</div>
|
||||
<div class="analysis_menu">
|
||||
@if(game.analysable) {
|
||||
<a data-panel="computer_analysis">@trans.computerAnalysis()</a>
|
||||
}
|
||||
@if(!game.isPgnImport) {
|
||||
<a data-panel="move_times">@trans.moveTimes()</a>
|
||||
@if(cross.isDefined) {
|
||||
<a data-panel="crosstable">Crosstable</a>
|
||||
}
|
||||
}
|
||||
<a data-panel="fen_pgn">FEN & PGN</a>
|
||||
</div>
|
||||
<div class="panel move_times">
|
||||
<div
|
||||
id="movetimes_chart"
|
||||
data-series="@timeChart.series"
|
||||
data-max="@timeChart.maxTime"></div>
|
||||
</div>
|
||||
@cross.map { c =>
|
||||
<div class="panel crosstable">
|
||||
@views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov))
|
||||
</div>
|
||||
}
|
||||
@analysis.filter(_.old && ctx.isAuth).map { a =>
|
||||
<form class="better_analysis" action="@routes.Analyse.betterAnalysis(gameId, color.name)" method="post">
|
||||
<button type="submit" class="button">
|
||||
<span class="is3" data-icon="A"> Request a better computer analysis</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="analysis_menu">
|
||||
@if(game.analysable) {
|
||||
<a data-panel="computer_analysis">@trans.computerAnalysis()</a>
|
||||
}
|
||||
@if(!game.isPgnImport) {
|
||||
<a data-panel="move_times">@trans.moveTimes()</a>
|
||||
@if(cross.isDefined) {
|
||||
<a data-panel="crosstable">Crosstable</a>
|
||||
}
|
||||
}
|
||||
<a data-panel="fen_pgn">FEN & PGN</a>
|
||||
</div>
|
||||
@analysis.filter(_.old && ctx.isAuth).map { a =>
|
||||
<form class="better_analysis" action="@routes.Analyse.betterAnalysis(gameId, color.name)" method="post">
|
||||
<button type="submit" class="button">
|
||||
<span class="is3" data-icon="A"> Request a better computer analysis</span>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,5 +54,9 @@ trans.blackPlays,
|
|||
trans.youAreViewingThisGameAsASpectator,
|
||||
trans.giveNbSeconds,
|
||||
trans.gameOver,
|
||||
trans.replayAndAnalyse
|
||||
trans.replayAndAnalyse,
|
||||
trans.boardEditor,
|
||||
trans.continueFromHere,
|
||||
trans.playWithTheMachine,
|
||||
trans.playWithAFriend
|
||||
)))
|
||||
|
|
|
@ -8,5 +8,7 @@ routes.javascript.Round.watcher,
|
|||
routes.javascript.Round.playerText,
|
||||
routes.javascript.Round.watcherText,
|
||||
routes.javascript.Round.sideWatcher,
|
||||
routes.javascript.Round.continue,
|
||||
routes.javascript.Tv.index,
|
||||
routes.javascript.Tv.side)(ctx.req)
|
||||
routes.javascript.Tv.side,
|
||||
routes.javascript.Editor.game)(ctx.req)
|
||||
|
|
|
@ -3,25 +3,13 @@
|
|||
|
||||
mkdir -p public/compiled
|
||||
|
||||
cd ui/editor
|
||||
npm install
|
||||
gulp dev
|
||||
gulp prod
|
||||
cd -
|
||||
for app in editor puzzle round analyse; do
|
||||
cd ui/$app
|
||||
npm install && gulp dev && gulp prod
|
||||
cd -
|
||||
done
|
||||
|
||||
cd ui/puzzle
|
||||
npm install
|
||||
gulp dev
|
||||
gulp prod
|
||||
cd -
|
||||
|
||||
cd ui/round
|
||||
npm install
|
||||
gulp dev
|
||||
gulp prod
|
||||
cd -
|
||||
|
||||
for file in strongSocket.js tv.js common.js big.js chart2.js user.js coordinate.js pgn4hacks.js; do
|
||||
for file in strongSocket.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
|
||||
|
@ -29,9 +17,3 @@ for file in strongSocket.js tv.js common.js big.js chart2.js user.js coordinate.
|
|||
closure --js $orig --js_output_file $comp
|
||||
fi
|
||||
done
|
||||
|
||||
pgn4web_output=public/vendor/pgn4web/pgn4web-compacted.js
|
||||
if [ ! -f $pgn4web_output ]; then
|
||||
lilalog "Compiling pgn4web javascript"
|
||||
closure --js public/vendor/pgn4web/pgn4web.js --js_output_file $pgn4web_output
|
||||
fi
|
||||
|
|
26
modules/api/src/main/AnalysisApi.scala
Normal file
26
modules/api/src/main/AnalysisApi.scala
Normal file
|
@ -0,0 +1,26 @@
|
|||
package lila.api
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
import chess.format.pgn.Pgn
|
||||
import lila.analyse.Analysis
|
||||
import lila.common.PimpedJson._
|
||||
|
||||
private[api] final class AnalysisApi {
|
||||
|
||||
def game(analysis: Analysis, pgn: Pgn) = JsArray(analysis.infoAdvices zip pgn.moves map {
|
||||
case ((info, adviceOption), move) => Json.obj(
|
||||
"eval" -> info.score.map(_.centipawns),
|
||||
"mate" -> info.mate,
|
||||
"variation" -> info.variation.isEmpty.fold(JsNull, info.variation mkString " "),
|
||||
"comment" -> adviceOption.map(_.makeComment(false, true))
|
||||
).noNull
|
||||
})
|
||||
|
||||
def player(color: chess.Color)(analysis: Analysis) =
|
||||
analysis.summary.find(_._1 == color).map(_._2).map(s =>
|
||||
JsObject(s map {
|
||||
case (nag, nb) => nag.toString.toLowerCase -> JsNumber(nb)
|
||||
})
|
||||
)
|
||||
}
|
|
@ -2,8 +2,8 @@ package lila.api
|
|||
|
||||
import akka.actor._
|
||||
import com.typesafe.config.Config
|
||||
import scala.collection.JavaConversions._
|
||||
import lila.common.PimpedConfig._
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
|
@ -52,13 +52,17 @@ final class Env(
|
|||
apiToken = apiToken,
|
||||
userIdsSharingIp = userIdsSharingIp)
|
||||
|
||||
val analysisApi = new AnalysisApi
|
||||
|
||||
val gameApi = new GameApi(
|
||||
netBaseUrl = Net.BaseUrl,
|
||||
apiToken = apiToken,
|
||||
pgnDump = pgnDump)
|
||||
pgnDump = pgnDump,
|
||||
analysisApi = analysisApi)
|
||||
|
||||
val roundApi = new RoundApi(
|
||||
jsonView = roundJsonView)
|
||||
jsonView = roundJsonView,
|
||||
analysisApi = analysisApi)
|
||||
|
||||
val puzzleApi = new PuzzleApi(
|
||||
env = puzzleEnv,
|
||||
|
|
|
@ -16,7 +16,8 @@ import makeTimeout.short
|
|||
private[api] final class GameApi(
|
||||
netBaseUrl: String,
|
||||
apiToken: String,
|
||||
pgnDump: PgnDump) {
|
||||
pgnDump: PgnDump,
|
||||
analysisApi: AnalysisApi) {
|
||||
|
||||
def list(
|
||||
username: Option[String],
|
||||
|
@ -131,23 +132,10 @@ private[api] final class GameApi(
|
|||
"sd" -> h.sd
|
||||
)
|
||||
},
|
||||
"analysis" -> analysisOption.map(_.summary).flatMap(_.find(_._1 == p.color).map(_._2)).map(s =>
|
||||
JsObject(s map {
|
||||
case (nag, nb) => nag.toString.toLowerCase -> JsNumber(nb)
|
||||
})
|
||||
)
|
||||
"analysis" -> analysisOption.flatMap(analysisApi.player(p.color))
|
||||
).noNull
|
||||
}),
|
||||
"analysis" -> analysisOption.ifTrue(withAnalysis).|@|(pgnOption).apply {
|
||||
case (analysis, pgn) => JsArray(analysis.infoAdvices zip pgn.moves map {
|
||||
case ((info, adviceOption), move) => Json.obj(
|
||||
"move" -> move.san,
|
||||
"eval" -> info.score.map(_.centipawns),
|
||||
"mate" -> info.mate,
|
||||
"variation" -> info.variation.isEmpty.fold(JsNull, info.variation mkString " ")
|
||||
).noNull
|
||||
})
|
||||
},
|
||||
"analysis" -> analysisOption.ifTrue(withAnalysis).|@|(pgnOption).apply(analysisApi.game),
|
||||
"moves" -> withMoves.option(g.pgnMoves mkString " "),
|
||||
"opening" -> withOpening.?? {
|
||||
chess.OpeningExplorer.openingOf(g.pgnMoves) map { opening =>
|
||||
|
|
|
@ -2,6 +2,8 @@ package lila.api
|
|||
|
||||
import play.api.libs.json._
|
||||
|
||||
import chess.format.pgn.Pgn
|
||||
import lila.analyse.Analysis
|
||||
import lila.game.Pov
|
||||
import lila.pref.Pref
|
||||
import lila.round.JsonView
|
||||
|
@ -9,7 +11,9 @@ import lila.security.Granter
|
|||
import lila.tournament.{ Tournament, TournamentRepo }
|
||||
import lila.user.User
|
||||
|
||||
private[api] final class RoundApi(jsonView: JsonView) {
|
||||
private[api] final class RoundApi(
|
||||
jsonView: JsonView,
|
||||
analysisApi: AnalysisApi) {
|
||||
|
||||
def player(pov: Pov, apiVersion: Int)(implicit ctx: Context): Fu[JsObject] =
|
||||
jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me,
|
||||
|
@ -22,17 +26,27 @@ private[api] final class RoundApi(jsonView: JsonView) {
|
|||
}
|
||||
}
|
||||
|
||||
def watcher(pov: Pov, apiVersion: Int, tv: Option[Boolean])(implicit ctx: Context): Fu[JsObject] =
|
||||
def watcher(pov: Pov, apiVersion: Int, tv: Option[Boolean], analysis: Option[(Pgn, Analysis)] = None)(implicit ctx: Context): Fu[JsObject] =
|
||||
jsonView.watcherJson(pov, ctx.pref, apiVersion, ctx.me, tv,
|
||||
withBlurs = ctx.me ?? Granter(_.ViewBlurs)) zip
|
||||
(pov.game.tournamentId ?? TournamentRepo.byId) map {
|
||||
case (json, tourOption) => blindMode {
|
||||
withTournament(tourOption) {
|
||||
json
|
||||
withAnalysis(analysis) {
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def withAnalysis(a: Option[(Pgn, Analysis)])(json: JsObject) = a.fold(json) {
|
||||
case (pgn, analysis) => json + ("analysis" -> Json.obj(
|
||||
"moves" -> analysisApi.game(analysis, pgn),
|
||||
"white" -> analysisApi.player(chess.Color.White)(analysis),
|
||||
"black" -> analysisApi.player(chess.Color.Black)(analysis)
|
||||
))
|
||||
}
|
||||
|
||||
private def withTournament(tourOption: Option[Tournament])(json: JsObject) =
|
||||
tourOption.fold(json) { tour =>
|
||||
json + ("tournament" -> Json.obj(
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit ed716486de5178d9693b1aad2d708441e38faf4d
|
||||
Subproject commit 8ea03a30d6aca0a896650577b741909d5546af58
|
|
@ -1,7 +1,7 @@
|
|||
package lila.round
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.math.{ min, max, round }
|
||||
import scala.math
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
|
@ -259,9 +259,10 @@ final class JsonView(
|
|||
case _ => 1
|
||||
}
|
||||
|
||||
private def animationDuration(pov: Pov, pref: Pref) = round {
|
||||
animationFactor(pref) * baseAnimationDuration.toMillis * max(0, min(1.2,
|
||||
((pov.game.estimateTotalTime - 60) / 60) * 0.2
|
||||
))
|
||||
private def animationDuration(pov: Pov, pref: Pref) = math.round {
|
||||
animationFactor(pref) * baseAnimationDuration.toMillis * pov.game.finished.fold(
|
||||
1,
|
||||
math.max(0, math.min(1.2, ((pov.game.estimateTotalTime - 60) / 60) * 0.2))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ object Featured {
|
|||
|
||||
private type Heuristic = Game => Float
|
||||
private val heuristicBox = box(0 to 1) _
|
||||
private val ratingBox = box(1000 to 2600) _
|
||||
private val ratingBox = box(1000 to 2700) _
|
||||
private val turnBox = box(1 to 25) _
|
||||
|
||||
private val heuristics: List[(Heuristic, Float)] = List(
|
||||
|
@ -111,7 +111,7 @@ object Featured {
|
|||
progressHeuristic -> 0.7f)
|
||||
|
||||
private[tv] def ratingHeuristic(color: Color): Heuristic = game =>
|
||||
ratingBox(game.player(color).rating | 1100)
|
||||
ratingBox(game.player(color).rating | 1400)
|
||||
|
||||
private[tv] def progressHeuristic: Heuristic = game =>
|
||||
1 - turnBox(game.turns)
|
||||
|
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 234 KiB |
|
@ -330,6 +330,7 @@ var storage = {
|
|||
|
||||
if (lichess.round) startRound(document.getElementById('lichess'), lichess.round);
|
||||
else if (lichess.prelude) startPrelude(document.querySelector('.lichess_game'), lichess.prelude);
|
||||
else if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse);
|
||||
|
||||
setTimeout(function() {
|
||||
if (lichess.socket === null) {
|
||||
|
@ -338,11 +339,6 @@ var storage = {
|
|||
$.idleTimer(lichess.idleTime, lichess.socket.destroy.bind(lichess.socket), lichess.socket.connect.bind(lichess.socket));
|
||||
}, 200);
|
||||
|
||||
var $board = $('div.with_marks');
|
||||
if ($board.length > 0) {
|
||||
$.displayBoardMarks($board.parent(), $('#lichess > div.pov_white').length);
|
||||
}
|
||||
|
||||
// themepicker
|
||||
var $body = $('body');
|
||||
$('#themepicker_toggle').one('click', function() {
|
||||
|
@ -651,22 +647,6 @@ var storage = {
|
|||
return lichess_translations[text] ? lichess_translations[text] : text;
|
||||
};
|
||||
|
||||
$.displayBoardMarks = function($board, isWhite) {
|
||||
var factor = 1,
|
||||
base = 0;
|
||||
if (!isWhite) {
|
||||
factor = -1;
|
||||
base = 575;
|
||||
}
|
||||
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
|
||||
marks = '';
|
||||
for (i = 1; i < 9; i++) {
|
||||
marks += '<span class="board_mark vert" style="bottom:' + (factor * i * 64 - 38 + base) + 'px;">' + i + '</span>';
|
||||
marks += '<span class="board_mark horz" style="left:' + (factor * i * 64 - 35 + base) + 'px;">' + letters[i - 1] + '</span>';
|
||||
}
|
||||
$board.remove('span.board_mark').append(marks);
|
||||
};
|
||||
|
||||
function urlToLink(text) {
|
||||
var exp = /\bhttp:\/\/(?:[a-z]{0,3}\.)?(lichess\.org[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
return text.replace(exp, "<a href='http://$1'>$1</a>");
|
||||
|
@ -1136,6 +1116,9 @@ var storage = {
|
|||
var $incrementInput = $form.find('.increment_choice input');
|
||||
var isHook = $form.hasClass('game_config_hook');
|
||||
var $ratings = $form.find('.ratings > div');
|
||||
var toggleButtons = function() {
|
||||
$form.find('.color_submits button').toggle(!$clockCheckbox.is(':checked') || $timeInput.val() > 0 || $incrementInput.val() > 0);
|
||||
};
|
||||
var showRating = function() {
|
||||
var key;
|
||||
switch ($variantSelect.val()) {
|
||||
|
@ -1193,8 +1176,7 @@ var storage = {
|
|||
$value.text(time);
|
||||
$input.attr('value', time);
|
||||
showRating();
|
||||
$form.find('.color_submits button').toggle(
|
||||
$timeInput.val() > 0 || $incrementInput.val() > 0);
|
||||
toggleButtons();
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
@ -1232,6 +1214,7 @@ var storage = {
|
|||
$clockCheckbox.on('change', function() {
|
||||
var checked = $(this).is(':checked');
|
||||
$form.find('.time_choice, .increment_choice').toggle(checked);
|
||||
toggleButtons();
|
||||
if (isHook && !checked) {
|
||||
$casual.click();
|
||||
}
|
||||
|
@ -1840,9 +1823,140 @@ var storage = {
|
|||
// analyse.js //
|
||||
////////////////
|
||||
|
||||
function startAnalyse(element, cfg) {
|
||||
var data = cfg.data;
|
||||
if (data.chat) $('#chat').chat({
|
||||
messages: data.chat
|
||||
});
|
||||
var $watchers = $('#site_header div.watchers').watchers();
|
||||
var analyse, $panels;
|
||||
lichess.socket = new lichess.StrongSocket(
|
||||
data.url.socket,
|
||||
data.player.version, {
|
||||
options: {
|
||||
name: "analyse"
|
||||
},
|
||||
params: {
|
||||
ran: "--ranph--"
|
||||
},
|
||||
events: {
|
||||
analysisAvailable: function() {
|
||||
$.sound.dong();
|
||||
location.href = location.href.split('#')[0] + '#' + analyse.pathStr();
|
||||
location.reload();
|
||||
},
|
||||
crowd: function(event) {
|
||||
$watchers.watchers("set", event.watchers);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var $advChart = $("#adv_chart");
|
||||
var $timeChart = $("#movetimes_chart");
|
||||
var $inputFen = $('input.fen', element);
|
||||
var unselect = function(chart) {
|
||||
chart.getSelectedPoints().forEach(function(point) {
|
||||
point.select(false);
|
||||
});
|
||||
};
|
||||
var chart, point, adv, title;
|
||||
analyse = LichessAnalyse(element.querySelector('.analyse'), cfg.data, cfg.routes, cfg.i18n, function(fen, path) {
|
||||
$inputFen.val(fen);
|
||||
if ($advChart.length) {
|
||||
chart = $advChart.highcharts();
|
||||
if (chart) {
|
||||
if (path.length > 1) unselect(chart);
|
||||
else {
|
||||
point = chart.series[0].data[path[0].ply - 1];
|
||||
if (typeof point != "undefined") {
|
||||
point.select();
|
||||
adv = "Advantage: <strong>" + point.y + "</strong>";
|
||||
title = point.name + ' ' + adv;
|
||||
chart.setTitle({
|
||||
text: title
|
||||
});
|
||||
} else unselect(chart);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($timeChart.length) {
|
||||
chart = $timeChart.highcharts();
|
||||
if (chart) {
|
||||
if (path.length > 1) unselect(chart);
|
||||
else {
|
||||
var white = path[0].ply % 2 !== 0;
|
||||
var serie = white ? 0 : 1;
|
||||
var turn = Math.floor((path[0].ply - 1) / 2);
|
||||
point = chart.series[serie].data[turn];
|
||||
var time = point.y * (white ? 1 : -1);
|
||||
if (typeof point != "undefined") {
|
||||
point.select();
|
||||
adv = "time used: <strong>" + time + "</strong> s";
|
||||
title = point.name + ' ' + adv;
|
||||
chart.setTitle({
|
||||
text: title
|
||||
});
|
||||
} else unselect(chart);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
lichess.analyse.jump = analyse.jump;
|
||||
|
||||
$('.underboard_content', element).appendTo($('.underboard .center', element)).show();
|
||||
$('.advice_summary', element).appendTo($('.underboard .right', element)).show();
|
||||
|
||||
$panels = $('div.analysis_panels > div');
|
||||
$('div.analysis_menu').on('click', 'a', function() {
|
||||
var panel = $(this).data('panel');
|
||||
$(this).siblings('.active').removeClass('active').end().addClass('active');
|
||||
$panels.removeClass('active').filter('.' + panel).addClass('active');
|
||||
if (panel == 'move_times') try {
|
||||
$.renderMoveTimesChart();
|
||||
} catch (e) {}
|
||||
}).find('a:first').click();
|
||||
|
||||
var pgnLoader = function() {
|
||||
$panels.find('.loader span').each(function() {
|
||||
var t = this;
|
||||
var moves = _.filter(_.values(
|
||||
$('#ShowPgnText a').map(function() {
|
||||
return $(this).text();
|
||||
})
|
||||
), function(x) {
|
||||
return typeof x == 'string';
|
||||
});
|
||||
var len = moves.length,
|
||||
it = 0;
|
||||
setInterval(function() {
|
||||
t.innerHTML = moves[it++ % len] || '';
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
setTimeout(pgnLoader, 500);
|
||||
|
||||
$panels.find('form.future_game_analysis').submit(function() {
|
||||
if ($(this).hasClass('must_login')) {
|
||||
if (confirm($.trans('You need an account to do that') + '.')) {
|
||||
location.href = '/signup';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
method: 'post',
|
||||
url: $(this).attr('action'),
|
||||
success: function(html) {
|
||||
$panels.filter('.panel.computer_analysis').html(html);
|
||||
pgnLoader();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
if (!$("#GameBoard").length) return;
|
||||
if (true || !$("#GameBoard").length) return;
|
||||
|
||||
$('div.game_control a').filter('.continue').click(function() {
|
||||
$('div.board_wrap div.continue').toggle();
|
||||
|
|
|
@ -90,7 +90,7 @@ $(function() {
|
|||
click: function(event) {
|
||||
if (event.point) {
|
||||
event.point.select();
|
||||
GoToMove(event.point.x + 1, 0);
|
||||
lichess.analyse.jump(event.point.x + 1, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -173,7 +173,7 @@ $(function() {
|
|||
click: function(event) {
|
||||
if (event.point) {
|
||||
event.point.select();
|
||||
GoToMove(event.point.x + 1, 0);
|
||||
lichess.analyse.jump(event.point.x + 1, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
// these functions must remain on root namespace
|
||||
|
||||
function customFunctionOnPgnGameLoad() {
|
||||
|
||||
$('div.side a.rotate_board').click(function() {
|
||||
$('#GameBoard').toggleClass('flip');
|
||||
$('#player_links div:first').appendTo($('#player_links'));
|
||||
redrawBoardMarks();
|
||||
return false;
|
||||
});
|
||||
redrawBoardMarks();
|
||||
$("#autoplayButton").click(refreshButtonset);
|
||||
$("#GameBoard td").css('background', 'none');
|
||||
$('#ShowPgnText > span').each(function() {
|
||||
$(this).text($(this).text().replace(/^([\d\.]+).+$/g, '$1'));
|
||||
});
|
||||
}
|
||||
|
||||
function uciToSquareIds(uci) {
|
||||
if (uci.length != 4) return [];
|
||||
var square = function(pos) {
|
||||
var x = "abcdefgh".indexOf(pos[0]),
|
||||
y = 8 - parseInt(pos[1], 10);
|
||||
return "img_tcol" + x + "trow" + y;
|
||||
};
|
||||
return [square(uci.slice(0, 2)), square(uci.slice(2))];
|
||||
}
|
||||
|
||||
function customFunctionOnMove() {
|
||||
refreshButtonset();
|
||||
var $chart = $("#adv_chart");
|
||||
if ($chart.length) {
|
||||
var chart = $chart.highcharts();
|
||||
if (chart) {
|
||||
$("#GameBoard img.bestmove").removeClass("bestmove");
|
||||
if (CurrentVar !== 0) {
|
||||
_.each(chart.getSelectedPoints(), function(point) {
|
||||
point.select(false);
|
||||
});
|
||||
} else {
|
||||
if (!isAutoPlayOn) {
|
||||
var ids = uciToSquareIds(lichess_best_moves[CurrentPly] || '');
|
||||
$.each(ids, function() {
|
||||
$("#" + this).addClass("bestmove");
|
||||
});
|
||||
}
|
||||
var index = CurrentPly - 1;
|
||||
var point = chart.series[0].data[index];
|
||||
if (typeof point != "undefined") {
|
||||
point.select();
|
||||
var adv = "Advantage: <strong>" + point.y + "</strong>";
|
||||
var title = point.name + ' ' + adv;
|
||||
chart.setTitle({
|
||||
text: title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var $gameText = $("#GameText");
|
||||
var $moveOn = $gameText.find(".moveOn:first");
|
||||
var gtHeight = $gameText.height();
|
||||
if ($moveOn.length) {
|
||||
var height = $moveOn.height();
|
||||
var y = $moveOn.position().top;
|
||||
if (y < height * 5) {
|
||||
$gameText.scrollTop($gameText.scrollTop() + y - height * 5);
|
||||
} else if (y > (gtHeight - height * 6)) {
|
||||
$gameText.scrollTop($gameText.scrollTop() + y + height * 6 - gtHeight);
|
||||
}
|
||||
}
|
||||
var fen = CurrentFEN();
|
||||
$('a.fen_link').each(function() {
|
||||
$(this).attr('href', $(this).attr('href').replace(/fen=.*$/, "fen=" + fen));
|
||||
});
|
||||
$('div.fen_pgn input.fen').val(fen);
|
||||
$('a.flip').each(function() {
|
||||
$(this).attr('href', $(this).attr('href').replace(/#\d+$/, "#" + CurrentPly));
|
||||
});
|
||||
if (!$('#GameBoard').hasClass('initialized')) {
|
||||
$('#GameBoard').addClass('initialized');
|
||||
var ply = parseInt(location.hash.replace(/#/, ''));
|
||||
if (ply) GoToMove(ply, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// hack: display captures and checks
|
||||
|
||||
function CleanMove(move) {
|
||||
move = move.replace(/[^a-zA-WYZ0-9#-\+\=]*/g, ''); // patch: remove/add '+' 'x' '=' chars for full chess informant style or pgn style for the game text
|
||||
if (move.match(/^[Oo0]/)) {
|
||||
move = move.replace(/[o0]/g, 'O').replace(/O(?=O)/g, 'O-');
|
||||
}
|
||||
move = move.replace(/ep/i, '');
|
||||
return move;
|
||||
}
|
||||
|
||||
function redrawBoardMarks() {
|
||||
$.displayBoardMarks($('#GameBoard'), !$('#GameBoard').hasClass('flip'));
|
||||
}
|
||||
|
||||
function refreshButtonset() {
|
||||
$("#autoplayButton").addClass('button');
|
||||
$('#GameButtons a[title]').each(function() {
|
||||
$(this)
|
||||
.html('<span data-icon="' + $(this).data('icon') + '"></span>')
|
||||
.removeAttr('data-icon')
|
||||
.attr('data-hint', $(this).attr('title'))
|
||||
.removeAttr('title')
|
||||
.addClass('hint--bottom');
|
||||
});
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
#pgnText {
|
||||
display: none;
|
||||
}
|
||||
div.board_wrap {
|
||||
float: left;
|
||||
width: 514px;
|
||||
|
@ -14,19 +11,6 @@ p.rematch_wrap {
|
|||
margin-top: 31px;
|
||||
text-align: center;
|
||||
}
|
||||
span.board_mark {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
color: #bbb;
|
||||
font-weight: bold;
|
||||
}
|
||||
span.board_mark.vert {
|
||||
right: -10px;
|
||||
}
|
||||
span.board_mark.horz {
|
||||
bottom: -14px;
|
||||
}
|
||||
div.analysis_menu {
|
||||
width: 512px;
|
||||
text-align: center;
|
||||
|
@ -48,6 +32,9 @@ div.analysis_menu > a.active {
|
|||
background-color: #eee;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
div.analysis_panels {
|
||||
margin-top: 10px;
|
||||
}
|
||||
div.analysis_panels > div {
|
||||
width: 512px;
|
||||
height: 210px;
|
||||
|
@ -55,11 +42,12 @@ div.analysis_panels > div {
|
|||
display: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
}
|
||||
div.analysis_panels > div.active {
|
||||
display: block;
|
||||
}
|
||||
div.analysis_panels #crosstable {
|
||||
div.analysis_panels .crosstable table {
|
||||
margin-top: 60px;
|
||||
}
|
||||
div.game_analysis {
|
||||
|
@ -100,117 +88,137 @@ div.fen_pgn .pgn {
|
|||
}
|
||||
div.fen_pgn strong {
|
||||
font-size: 1.1em;
|
||||
display:inline-block;
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
}
|
||||
div.fen_pgn input.fen {
|
||||
width: 450px;
|
||||
}
|
||||
#GameBoard {
|
||||
position: relative;
|
||||
width: 512px;
|
||||
height: 512px;
|
||||
border: 1px solid #ccc;
|
||||
background-size: contain;
|
||||
}
|
||||
#GameBoard.flip table,
|
||||
#GameBoard.flip img.pieceImage {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
#GameBoard td img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: block;
|
||||
}
|
||||
div.game_control,
|
||||
div.continue {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
div.continue {
|
||||
margin: 20px 0;
|
||||
}
|
||||
div.game_control a {
|
||||
font-size: 16px;
|
||||
height: 22px;
|
||||
display: inline-block;
|
||||
}
|
||||
#GameButtons {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#GameButtons a {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
margin-left: -1px;
|
||||
margin-right: -2px;
|
||||
}
|
||||
#GameText {
|
||||
font-size: 15px;
|
||||
width: 242px;
|
||||
height: 502px;
|
||||
margin-bottom: 1em;
|
||||
padding: 5px 7px;
|
||||
border: 1px solid #ccc;
|
||||
div.game_control .jumps {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.lichess_ground {
|
||||
height: 100%;
|
||||
}
|
||||
.lichess_ground .replay {
|
||||
margin-left: 10px;
|
||||
width: 256px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#GameText .move,
|
||||
#GameText .variation {
|
||||
font-family: 'pgn4web ChessSansPiratf', 'pgn4web Liberation Sans', sans-serif;
|
||||
text-decoration: none;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
.lichess_ground .replay .index {
|
||||
display: inline-block;
|
||||
color: #b57600;
|
||||
}
|
||||
/* played */
|
||||
|
||||
#GameText .move {
|
||||
color: #444;
|
||||
}
|
||||
/* any move number */
|
||||
|
||||
#GameText span {
|
||||
padding-right: 0;
|
||||
}
|
||||
/* any move SAN */
|
||||
|
||||
#GameText a {
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
#GameText .comment,
|
||||
#GameText .variation {
|
||||
font-size: 80%;
|
||||
}
|
||||
#GameText .comment {
|
||||
display: block;
|
||||
}
|
||||
#GameText a:hover {
|
||||
width: 26px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
height: 22px;
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
padding: 2px 0;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
#GameText .moveOn,
|
||||
#GameText .variationOn {
|
||||
border: 1px solid #909090;
|
||||
padding: 2px 3px;
|
||||
.lichess_ground .replay .move {
|
||||
display: inline-block;
|
||||
font-family: 'ChessSansPiratf', sans-serif;
|
||||
text-decoration: none;
|
||||
}
|
||||
div.advice_summary {
|
||||
position: absolute;
|
||||
top: 595px;
|
||||
width: 280px;
|
||||
.lichess_ground .replay .turn .move {
|
||||
width: 100px;
|
||||
font-size: 17px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
display: inline-block;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.lichess_ground .replay .turn .move.empty {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.lichess_ground .replay a.move {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.13s;
|
||||
}
|
||||
.lichess_ground .replay a.move:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.lichess_ground .replay a.move.active {
|
||||
color: #d85000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.lichess_ground .replay span {
|
||||
font-size: 0.8em;
|
||||
font-weight: lighter;
|
||||
float: right;
|
||||
margin: 2px 5px 0 0;
|
||||
opacity: 0.7;
|
||||
transition: 0.13s;
|
||||
}
|
||||
.lichess_ground .replay .move:hover span {
|
||||
opacity: 1;
|
||||
}
|
||||
.lichess_ground .comment,
|
||||
.lichess_ground .variation {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
.lichess_ground .comment {
|
||||
padding: 3px 0 0 5px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
.lichess_ground .variation {
|
||||
font-weight: lighter;
|
||||
padding: 3px 0 3px 5px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.lichess_ground .variation .move {
|
||||
padding: 2px 2px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.lichess_ground .variation a.move:hover {
|
||||
background: #eee;
|
||||
}
|
||||
.lichess_ground .result {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
text-align: center;
|
||||
}
|
||||
.lichess_ground .status {
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
div.underboard {
|
||||
margin-top: 25px;
|
||||
}
|
||||
div.underboard .center {
|
||||
padding: 0;
|
||||
}
|
||||
div.advice_summary table {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
div.advice_summary tr {
|
||||
width: 100%;
|
||||
}
|
||||
div.advice_summary th,
|
||||
div.advice_summary td {
|
||||
padding: 3px 0;
|
||||
}
|
||||
div.advice_summary thead th {
|
||||
padding: 3px 0;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
#adv_chart,
|
||||
#movetimes_chart {
|
||||
|
|
|
@ -17,131 +17,93 @@ div.lichess_game div.lichess_ground {
|
|||
padding-left: 15px;
|
||||
width: 242px;
|
||||
}
|
||||
span.board_mark {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
color: #bbb;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
span.board_mark.vert {
|
||||
right: -10px;
|
||||
}
|
||||
span.board_mark.horz {
|
||||
bottom: -14px;
|
||||
}
|
||||
div.lichess_board {
|
||||
position: relative;
|
||||
}
|
||||
body.blue #GameBoard,
|
||||
body.is2d.blue .cg-board,
|
||||
#top div.color_demo.blue {
|
||||
background-image: url(../images/board/blue.png);
|
||||
}
|
||||
body.blue2 #GameBoard,
|
||||
body.is2d.blue2 .cg-board,
|
||||
#top div.color_demo.blue2 {
|
||||
background-image: url(../images/board/blue2.jpg);
|
||||
}
|
||||
body.wood2 #GameBoard,
|
||||
body.is2d.wood2 .cg-board,
|
||||
#top div.color_demo.wood2 {
|
||||
background-image: url(../images/board/wood2.jpg);
|
||||
}
|
||||
body.marble #GameBoard,
|
||||
body.is2d.marble .cg-board,
|
||||
#top div.color_demo.marble {
|
||||
background-image: url(../images/board/marble.jpg);
|
||||
}
|
||||
body.brown #GameBoard,
|
||||
body.is2d.brown .cg-board,
|
||||
#top div.color_demo.brown {
|
||||
background-image: url(../images/board/brown.png);
|
||||
}
|
||||
body.green #GameBoard,
|
||||
body.is2d.green .cg-board,
|
||||
#top div.color_demo.green {
|
||||
background-image: url(../images/board/green.png);
|
||||
}
|
||||
body.olive #GameBoard,
|
||||
body.is2d.olive .cg-board,
|
||||
#top div.color_demo.olive {
|
||||
background-image: url(../images/board/olive.jpg);
|
||||
}
|
||||
body.purple #GameBoard,
|
||||
body.is2d.purple .cg-board,
|
||||
#top div.color_demo.purple {
|
||||
background-image: url(../images/board/purple.png);
|
||||
}
|
||||
body.grey #GameBoard,
|
||||
body.is2d.grey .cg-board,
|
||||
#top div.color_demo.grey {
|
||||
background-image: url(../images/board/grey.jpg);
|
||||
}
|
||||
body.wood #GameBoard,
|
||||
body.is2d.wood .cg-board,
|
||||
#top div.color_demo.wood {
|
||||
background-image: url(../images/board/wood3-1024.jpg);
|
||||
}
|
||||
body.canvas #GameBoard,
|
||||
body.is2d.canvas .cg-board,
|
||||
#top div.color_demo.canvas {
|
||||
background-image: url(../images/board/canvas2-1024.jpg);
|
||||
}
|
||||
body.leather #GameBoard,
|
||||
body.is2d.leather .cg-board,
|
||||
#top div.color_demo.leather {
|
||||
background-image: url(../images/board/leather-1024.jpg);
|
||||
}
|
||||
/* KotH boards */
|
||||
|
||||
body.is2d.blue .kingOfTheHill .cg-board,
|
||||
body.blue #GameBoard.kingOfTheHill {
|
||||
body.is2d.blue .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/blue.jpg);
|
||||
}
|
||||
body.is2d.blue2 .kingOfTheHill .cg-board,
|
||||
body.blue2 #GameBoard.kingOfTheHill {
|
||||
body.is2d.blue2 .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/blue2.jpg);
|
||||
}
|
||||
body.is2d.wood2 .kingOfTheHill .cg-board,
|
||||
body.wood2 #GameBoard.kingOfTheHill {
|
||||
body.is2d.wood2 .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/wood2.jpg);
|
||||
}
|
||||
body.is2d.marble .kingOfTheHill .cg-board,
|
||||
body.marble #GameBoard.kingOfTheHill {
|
||||
body.is2d.marble .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/marble.jpg);
|
||||
}
|
||||
body.is2d.brown .kingOfTheHill .cg-board,
|
||||
body.brown #GameBoard.kingOfTheHill {
|
||||
body.is2d.brown .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/brown.jpg);
|
||||
}
|
||||
body.is2d.green .kingOfTheHill .cg-board,
|
||||
body.green #GameBoard.kingOfTheHill {
|
||||
body.is2d.green .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/green.jpg);
|
||||
}
|
||||
body.is2d.olive .kingOfTheHill .cg-board,
|
||||
body.olive #GameBoard.kingOfTheHill {
|
||||
body.is2d.olive .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/olive.jpg);
|
||||
}
|
||||
body.is2d.purple .kingOfTheHill .cg-board,
|
||||
body.purple #GameBoard.kingOfTheHill {
|
||||
body.is2d.purple .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/purple.jpg);
|
||||
}
|
||||
body.is2d.grey .kingOfTheHill .cg-board,
|
||||
body.grey #GameBoard.kingOfTheHill {
|
||||
body.is2d.grey .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/grey.jpg);
|
||||
}
|
||||
body.is2d.wood .kingOfTheHill .cg-board,
|
||||
body.wood #GameBoard.kingOfTheHill {
|
||||
body.is2d.wood .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/wood.jpg);
|
||||
}
|
||||
body.is2d.canvas .kingOfTheHill .cg-board,
|
||||
body.canvas #GameBoard.kingOfTheHill {
|
||||
body.is2d.canvas .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/canvas.jpg);
|
||||
}
|
||||
body.is2d.leather .kingOfTheHill .cg-board,
|
||||
body.leather #GameBoard.kingOfTheHill {
|
||||
body.is2d.leather .kingOfTheHill .cg-board {
|
||||
background-image: url(../images/board/koth/leather.jpg);
|
||||
}
|
||||
body.is3d.Black-White-Aluminium .cg-board-wrap,
|
||||
|
@ -992,55 +954,55 @@ div.table {
|
|||
div.table .notyet {
|
||||
margin: 20px 0;
|
||||
}
|
||||
div.table .replay {
|
||||
.lichess_ground .table .replay {
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
div.table .replay .moves {
|
||||
.lichess_ground .replay .moves {
|
||||
height: 115px;
|
||||
overflow: auto;
|
||||
}
|
||||
div.table .replay table {
|
||||
.lichess_ground .replay table {
|
||||
width: 100%;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
div.table .replay td.index {
|
||||
.lichess_ground .replay td.index {
|
||||
width: 15%;
|
||||
text-align: center;
|
||||
font-weight: lighter;
|
||||
}
|
||||
div.table .replay td.move {
|
||||
font-family: 'pgn4web ChessSansPiratf', 'pgn4web Liberation Sans', sans-serif;
|
||||
.lichess_ground .replay td.move {
|
||||
font-family: 'ChessSansPiratf', sans-serif;
|
||||
font-size: 1.3em;
|
||||
padding: 2px 5px;
|
||||
cursor: pointer;
|
||||
transition: 0.13s;
|
||||
transition: background-color 0.13s;
|
||||
}
|
||||
div.table .replay td.move:hover {
|
||||
.lichess_ground .replay td.move:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
div.table .replay td.move.active {
|
||||
.lichess_ground .replay td.move.active {
|
||||
color: #d85000;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.table .replay td.result {
|
||||
.lichess_ground .replay td.result {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
padding: 4px 0;
|
||||
text-align: center;
|
||||
}
|
||||
div.table .status td {
|
||||
.lichess_ground .replay .status td {
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
div.table .replay .buttons {
|
||||
.lichess_ground .replay .buttons {
|
||||
text-align: center;
|
||||
margin-top: 7px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
div.table .replay a.button {
|
||||
.lichess_ground .replay a.button {
|
||||
opacity: 0.9;
|
||||
padding: 4px 7px;
|
||||
-webkit-user-select: none;
|
||||
|
@ -1049,13 +1011,13 @@ div.table .replay a.button {
|
|||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
div.table .replay a.flip {
|
||||
.lichess_ground .replay a.flip {
|
||||
margin-right: 10px;
|
||||
}
|
||||
div.table .replay a.disabled {
|
||||
.lichess_ground .replay a.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
div.table .replay a.disabled::before {
|
||||
.lichess_ground .replay a.disabled::before {
|
||||
opacity: 0.4;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
@ -1433,15 +1395,16 @@ span.inline_userlist a:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
div.underboard .center {
|
||||
display: inline-block;
|
||||
display: table-cell;
|
||||
position: relative;
|
||||
width: 512px;
|
||||
text-align: center;
|
||||
padding-top: 15px;
|
||||
}
|
||||
div.underboard .right {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
padding-left: 15px;
|
||||
width: 242px;
|
||||
}
|
||||
div.underboard a {
|
||||
|
@ -1456,15 +1419,3 @@ div.check_count span.player.color {
|
|||
margin-left: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
body.highlight #GameBoard td.highlightWhiteSquare img,
|
||||
body.highlight #GameBoard td.highlightBlackSquare img {
|
||||
background: -moz-radial-gradient(center, ellipse cover, rgba(155, 199, 00, 0.81) 0%, rgba(155, 199, 00, 0) 100%);
|
||||
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, rgba(155, 199, 00, 0.81)), color-stop(100%, rgba(155, 199, 00, 0)));
|
||||
background: -webkit-radial-gradient(center, ellipse cover, rgba(155, 199, 00, 0.81) 0%, rgba(155, 199, 00, 0) 100%);
|
||||
background: -ms-radial-gradient(center, ellipse cover, rgba(155, 199, 00, 0.81) 0%, rgba(155, 199, 00, 0) 100%);
|
||||
background: radial-gradient(ellipse at center, rgba(155, 199, 00, 0.81) 0%, rgba(155, 199, 00, 0) 100%);
|
||||
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#cfd85500', endColorstr='#00d85500', GradientType=1);
|
||||
}
|
||||
#GameBoard td img.bestmove {
|
||||
box-shadow: inset 0 0 10px 2px rgba(216, 85, 0, 0.9);
|
||||
}
|
||||
|
|
|
@ -113,9 +113,9 @@ body.blind_mode [data-icon]::before {
|
|||
visibility: hidden;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'pgn4web ChessSansPiratf';
|
||||
src: url("../vendor/pgn4web/fonts/ChessSansPiratf.eot");
|
||||
src: url("../vendor/pgn4web/fonts/ChessSansPiratf.eot?#iefix") format("embedded-opentype"), url("../vendor/pgn4web/fonts/ChessSansPiratf.woff") format("woff"), url("../vendor/pgn4web/fonts/ChessSansPiratf.ttf") format("truetype"), url("../vendor/pgn4web/fonts/ChessSansPiratf.svg#lichess") format("svg");
|
||||
font-family: 'ChessSansPiratf';
|
||||
src: url("../fonts/ChessSansPiratf.eot");
|
||||
src: url("../fonts/ChessSansPiratf.eot?#iefix") format("embedded-opentype"), url("../fonts/ChessSansPiratf.woff") format("woff"), url("../fonts/ChessSansPiratf.ttf") format("truetype"), url("../fonts/ChessSansPiratf.svg#lichess") format("svg");
|
||||
}
|
||||
.thin {
|
||||
font-weight: lighter;
|
||||
|
|
|
@ -46,10 +46,8 @@ body.dark div.side_box .top,
|
|||
body.dark div.table,
|
||||
body.dark div.separator,
|
||||
body.dark div.table_wrap > div.clock > div.time,
|
||||
body.dark #GameText,
|
||||
body.dark #tournament_side,
|
||||
body.dark #tournament div.standing_wrap,
|
||||
body.dark #GameBoard,
|
||||
body.dark div.shortcuts .title,
|
||||
body.dark div.content_box,
|
||||
body.dark div.content_box_inter,
|
||||
|
@ -157,7 +155,6 @@ body.dark div.undertable a.user_link,
|
|||
body.dark #tv_history a.user_link,
|
||||
body.dark #timeline a.user_link,
|
||||
body.dark #team .forum a.user_link,
|
||||
body.dark span.board_mark,
|
||||
body.dark.coords_2 .cg-square[data-coord-x]::after,
|
||||
body.dark.coords_2 .cg-square[data-coord-y]::before,
|
||||
body.dark div.user_show div.content_box_top > span,
|
||||
|
@ -165,13 +162,11 @@ body.dark span.progress > .zero,
|
|||
body.dark div.undertable_top span.title {
|
||||
color: #777;
|
||||
}
|
||||
body.dark #GameText a:hover,
|
||||
body.dark .crosstable td a,
|
||||
body.dark #hook_filter td > label.hover:hover,
|
||||
body.dark #promotion_choice {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
body.dark #GameText,
|
||||
body.dark #chat .messages,
|
||||
body.dark div.side_box,
|
||||
body.dark div.table,
|
||||
|
@ -242,7 +237,6 @@ body.dark div.game_row:nth-child(odd),
|
|||
body.dark #lichess_forum table.forum_table tr:nth-child(odd),
|
||||
body.dark #lichess_message tr:nth-child(even),
|
||||
body.dark div.content_box_inter,
|
||||
body.dark #GameText tr:nth-child(even),
|
||||
body.dark table.slist tbody tr:nth-child(even),
|
||||
body.dark #team .forum li:nth-child(odd),
|
||||
body.dark table.translations tbody tr:nth-child(odd),
|
||||
|
@ -252,7 +246,7 @@ body.dark div.game_config .optional_config,
|
|||
body.dark div.game_config .ratings,
|
||||
body.dark div.search_status,
|
||||
body.dark #friend_box,
|
||||
body.dark div.table .replay td.move:hover,
|
||||
body.dark .lichess_ground .replay .move:hover,
|
||||
body.dark #powerTip {
|
||||
background: #343434;
|
||||
}
|
||||
|
@ -260,7 +254,6 @@ body.dark #top ul.language_links a.accepted,
|
|||
body.dark #lichess_forum div.post .message,
|
||||
body.dark #lichess_blog,
|
||||
body.dark #lichess_message div.thread_message div.thread_message_body,
|
||||
body.dark #GameText .move,
|
||||
body.dark form.translation_form div.message label,
|
||||
body.dark div.user_show div.content_box_top > span strong {
|
||||
color: #b0b0b0;
|
||||
|
|
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.eot
vendored
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.eot
vendored
Binary file not shown.
1413
public/vendor/pgn4web/fonts/ChessSansMerida.svg
vendored
1413
public/vendor/pgn4web/fonts/ChessSansMerida.svg
vendored
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 263 KiB |
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.ttf
vendored
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.ttf
vendored
Binary file not shown.
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.woff
vendored
BIN
public/vendor/pgn4web/fonts/ChessSansMerida.woff
vendored
Binary file not shown.
4590
public/vendor/pgn4web/pgn4web.js
vendored
4590
public/vendor/pgn4web/pgn4web.js
vendored
File diff suppressed because it is too large
Load diff
61
ui/analyse/gulpfile.js
Normal file
61
ui/analyse/gulpfile.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
var source = require('vinyl-source-stream');
|
||||
var gulp = require('gulp');
|
||||
var gutil = require('gulp-util');
|
||||
var jshint = require('gulp-jshint');
|
||||
var watchify = require('watchify');
|
||||
var browserify = require('browserify');
|
||||
var uglify = require('gulp-uglify');
|
||||
var streamify = require('gulp-streamify');
|
||||
|
||||
var sources = ['./src/main.js'];
|
||||
var destination = '../../public/compiled/';
|
||||
var onError = function(error) {
|
||||
gutil.log(gutil.colors.red(error.message));
|
||||
};
|
||||
var standalone = 'LichessAnalyse';
|
||||
|
||||
gulp.task('lint', function() {
|
||||
return gulp.src(paths.scripts)
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'));
|
||||
});
|
||||
|
||||
gulp.task('prod', function() {
|
||||
return browserify('./src/main.js', {
|
||||
standalone: standalone
|
||||
}).bundle()
|
||||
.on('error', onError)
|
||||
.pipe(source('lichess.analyse.min.js'))
|
||||
.pipe(streamify(uglify()))
|
||||
.pipe(gulp.dest(destination));
|
||||
});
|
||||
|
||||
gulp.task('dev', function() {
|
||||
return browserify('./src/main.js', {
|
||||
standalone: standalone
|
||||
}).bundle()
|
||||
.on('error', onError)
|
||||
.pipe(source('lichess.analyse.js'))
|
||||
.pipe(gulp.dest(destination));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
var opts = watchify.args;
|
||||
opts.debug = true;
|
||||
opts.standalone = standalone;
|
||||
|
||||
var bundleStream = watchify(browserify(sources, opts))
|
||||
.on('update', rebundle)
|
||||
.on('log', gutil.log);
|
||||
|
||||
function rebundle() {
|
||||
return bundleStream.bundle()
|
||||
.on('error', onError)
|
||||
.pipe(source('lichess.analyse.js'))
|
||||
.pipe(gulp.dest(destination));
|
||||
}
|
||||
|
||||
return rebundle();
|
||||
});
|
||||
|
||||
gulp.task('default', ['watch']);
|
40
ui/analyse/package.json
Normal file
40
ui/analyse/package.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "analyse",
|
||||
"version": "1.0.0",
|
||||
"description": "lichess.org analyse",
|
||||
"main": "src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ornicar/lila"
|
||||
},
|
||||
"keywords": [
|
||||
"chess",
|
||||
"lichess",
|
||||
"play",
|
||||
"replay"
|
||||
],
|
||||
"author": "Thibault Duplessis",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ornicar/lila/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ornicar/lila",
|
||||
"devDependencies": {
|
||||
"browserify": "^5.12.0",
|
||||
"gulp": "^3.8.8",
|
||||
"gulp-jshint": "^1.8.4",
|
||||
"gulp-streamify": "0.0.5",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"vinyl-source-stream": "^1.0.0",
|
||||
"watchify": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chessground": "^1.7.9",
|
||||
"chessli.js": "^0.2.0",
|
||||
"game": "file:../game",
|
||||
"lodash-node": "^2.4.1",
|
||||
"mithril": "^0.1.22",
|
||||
"mousetrap": "0.0.1"
|
||||
}
|
||||
}
|
42
ui/analyse/src/analyse.js
Normal file
42
ui/analyse/src/analyse.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
module.exports = function(game, analysis) {
|
||||
|
||||
var makeTree = function(sans, fromPly) {
|
||||
return sans.map(function(san, i) {
|
||||
return {
|
||||
ply: fromPly + i,
|
||||
san: san,
|
||||
comments: [],
|
||||
variations: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var applyAnalysis = function(tree, analysed) {
|
||||
analysed.forEach(function(ana, i) {
|
||||
if (!tree[i]) return;
|
||||
if (ana.mate) tree[i].mate = ana.mate;
|
||||
else if (ana.eval) tree[i].eval = ana.eval;
|
||||
if (ana.comment) tree[i].comments.push(ana.comment);
|
||||
if (ana.variation) tree[i].variations.push(makeTree(ana.variation.split(' '), i + 1));
|
||||
});
|
||||
};
|
||||
|
||||
this.tree = makeTree(game.moves, 1);
|
||||
if (analysis) applyAnalysis(this.tree, analysis.moves);
|
||||
|
||||
this.moveList = function(path) {
|
||||
var tree = this.tree;
|
||||
var moves = [];
|
||||
path.forEach(function(step) {
|
||||
for (i = 0, nb = tree.length; i < nb; i++) {
|
||||
var move = tree[i];
|
||||
if (step.ply === move.ply && step.variation) {
|
||||
tree = move.variations[step.variation - 1];
|
||||
break;
|
||||
} else if (step.ply >= move.ply) moves.push(move.san);
|
||||
else break;
|
||||
}
|
||||
});
|
||||
return moves;
|
||||
}.bind(this);
|
||||
}
|
6
ui/analyse/src/blind.js
Normal file
6
ui/analyse/src/blind.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// #FIXME jQuery crap here
|
||||
|
||||
module.exports = function(el, ctrl) {
|
||||
var route = ctrl.data.player.spectator ? ctrl.router.Round.watcherText(ctrl.data.game.id, ctrl.data.player.color) : ctrl.router.Round.playerText(ctrl.data.game.id + ctrl.data.player.id);
|
||||
$(el).load(route.url);
|
||||
};
|
55
ui/analyse/src/control.js
Normal file
55
ui/analyse/src/control.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
function canGoForward(ctrl) {
|
||||
var tree = ctrl.analyse.tree;
|
||||
var ok = false;
|
||||
ctrl.vm.path.forEach(function(step) {
|
||||
for (i = 0, nb = tree.length; i < nb; i++) {
|
||||
var move = tree[i];
|
||||
if (step.ply === move.ply && step.variation) {
|
||||
tree = move.variations[step.variation - 1];
|
||||
break;
|
||||
} else ok = step.ply < move.ply;
|
||||
}
|
||||
});
|
||||
console.log(ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
next: function(ctrl) {
|
||||
if (!canGoForward(ctrl)) return;
|
||||
var p = ctrl.vm.path;
|
||||
p[p.length - 1].ply++;
|
||||
ctrl.jump(p);
|
||||
},
|
||||
|
||||
prev: function(ctrl) {
|
||||
var p = ctrl.vm.path;
|
||||
var len = p.length;
|
||||
if (len == 1) {
|
||||
if (p[0].ply == 1) return;
|
||||
p[0].ply--;
|
||||
} else {
|
||||
if (p[len - 1].ply > p[len - 2].ply) p[len - 1].ply--;
|
||||
else {
|
||||
p.pop();
|
||||
p[len - 2].variation = null;
|
||||
}
|
||||
}
|
||||
ctrl.jump(p);
|
||||
},
|
||||
|
||||
last: function(ctrl) {
|
||||
ctrl.jump([{
|
||||
ply: ctrl.analyse.tree[ctrl.analyse.tree.length - 1].ply,
|
||||
variation: null
|
||||
}]);
|
||||
},
|
||||
|
||||
first: function(ctrl) {
|
||||
ctrl.jump([{
|
||||
ply: 1,
|
||||
variation: null
|
||||
}]);
|
||||
}
|
||||
};
|
89
ui/analyse/src/ctrl.js
Normal file
89
ui/analyse/src/ctrl.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
var Chess = require('chessli.js').Chess;
|
||||
var chessground = require('chessground');
|
||||
var data = require('./data');
|
||||
var analyse = require('./analyse');
|
||||
var ground = require('./ground');
|
||||
var keyboard = require('./keyboard');
|
||||
var treePath = require('./path');
|
||||
|
||||
module.exports = function(cfg, router, i18n, onChange) {
|
||||
|
||||
this.data = data({}, cfg);
|
||||
this.analyse = new analyse(this.data.game, this.data.analysis);
|
||||
|
||||
this.vm = {
|
||||
flip: false,
|
||||
path: treePath.default(),
|
||||
pathStr: treePath.write(treePath.default()),
|
||||
situation: null,
|
||||
continue: false
|
||||
};
|
||||
|
||||
var situationCache = {};
|
||||
var showGround = function() {
|
||||
var moves = this.analyse.moveList(this.vm.path);
|
||||
var nbMoves = moves.length;
|
||||
var ply, move, cached, fen, hash = '', h = '', lm;
|
||||
for (ply = 1; ply <= nbMoves; ply++) {
|
||||
move = moves[ply - 1];
|
||||
h += move;
|
||||
cached = situationCache[h];
|
||||
if (!cached) break;
|
||||
hash = h;
|
||||
fen = cached.fen;
|
||||
}
|
||||
if (!cached || ply < nbMoves) {
|
||||
var chess = new Chess(
|
||||
fen || this.data.game.initialFen,
|
||||
this.data.game.variant.key == 'chess960' ? 1 : 0
|
||||
);
|
||||
for (ply = ply; ply <= nbMoves; ply++) {
|
||||
move = moves[ply - 1];
|
||||
hash += move;
|
||||
lm = chess.move(move);
|
||||
situationCache[hash] = {
|
||||
fen: chess.fen(),
|
||||
check: chess.in_check(),
|
||||
lastMove: [lm.from, lm.to]
|
||||
};
|
||||
}
|
||||
}
|
||||
this.vm.situation = situationCache[hash];
|
||||
if (this.chessground) this.chessground.set(this.vm.situation);
|
||||
else this.chessground = ground.make(this.data, this.vm.situation);
|
||||
onChange(this.vm.situation.fen, this.vm.path);
|
||||
}.bind(this);
|
||||
|
||||
this.jump = function(path) {
|
||||
this.vm.path = path;
|
||||
this.vm.pathStr = treePath.write(path);
|
||||
showGround();
|
||||
}.bind(this);
|
||||
|
||||
this.jumpToMain = function(ply) {
|
||||
this.jump([{
|
||||
ply: ply,
|
||||
variation: null
|
||||
}]);
|
||||
}.bind(this);
|
||||
|
||||
this.flip = function() {
|
||||
this.vm.flip = !this.vm.flip;
|
||||
this.chessground.set({
|
||||
orientation: this.vm.flip ? this.data.opponent.color : this.data.player.color
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
this.router = router;
|
||||
|
||||
this.trans = function() {
|
||||
var str = i18n[arguments[0]]
|
||||
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
|
||||
str = str.replace('%s', arg);
|
||||
});
|
||||
return str;
|
||||
};
|
||||
|
||||
keyboard.init(this);
|
||||
showGround();
|
||||
};
|
8
ui/analyse/src/data.js
Normal file
8
ui/analyse/src/data.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = function(old, cfg) {
|
||||
|
||||
var data = cfg;
|
||||
|
||||
if (cfg.game.moves) data.game.moves = data.game.moves.split(' ');
|
||||
|
||||
return data;
|
||||
};
|
32
ui/analyse/src/ground.js
Normal file
32
ui/analyse/src/ground.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
var chessground = require('chessground');
|
||||
|
||||
function makeConfig(data, situation, flip) {
|
||||
return {
|
||||
viewOnly: true,
|
||||
fen: situation.fen,
|
||||
check: situation.check,
|
||||
lastMove: situation.lastMove,
|
||||
orientation: flip ? data.opponent.color : data.player.color,
|
||||
coordinates: data.pref.coords !== 0,
|
||||
highlight: {
|
||||
lastMove: data.pref.highlight,
|
||||
check: data.pref.highlight,
|
||||
dragOver: false
|
||||
},
|
||||
animation: {
|
||||
enabled: true,
|
||||
duration: data.pref.animationDuration
|
||||
},
|
||||
events: {
|
||||
capture: $.sound.take
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function make(data, situation) {
|
||||
return new chessground.controller(makeConfig(data, situation));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
make: make
|
||||
};
|
35
ui/analyse/src/keyboard.js
Normal file
35
ui/analyse/src/keyboard.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
var k = require('mousetrap');
|
||||
var control = require('./control');
|
||||
|
||||
function preventing(f) {
|
||||
return function(e) {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
// internet explorer
|
||||
e.returnValue = false;
|
||||
}
|
||||
f();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(ctrl) {
|
||||
k.bind(['left', 'h'], preventing(function() {
|
||||
control.prev(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
k.bind(['right', 'l'], preventing(function() {
|
||||
control.next(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
k.bind(['up', 'j'], preventing(function() {
|
||||
control.first(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
k.bind(['down', 'k'], preventing(function() {
|
||||
control.last(ctrl);
|
||||
m.redraw();
|
||||
}));
|
||||
}
|
||||
};
|
25
ui/analyse/src/main.js
Normal file
25
ui/analyse/src/main.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
var ctrl = require('./ctrl');
|
||||
var view = require('./view');
|
||||
|
||||
module.exports = function(element, config, router, i18n, onChange) {
|
||||
|
||||
var controller = new ctrl(config, router, i18n, onChange);
|
||||
|
||||
m.module(element, {
|
||||
controller: function () { return controller; },
|
||||
view: view
|
||||
});
|
||||
|
||||
return {
|
||||
jump: function(ply) {
|
||||
controller.jumpToMain(ply);
|
||||
m.redraw();
|
||||
},
|
||||
path: function() {
|
||||
return controller.vm.path;
|
||||
},
|
||||
pathStr: function() {
|
||||
return controller.vm.pathStr;
|
||||
}
|
||||
};
|
||||
};
|
49
ui/analyse/src/path.js
Normal file
49
ui/analyse/src/path.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
module.exports = {
|
||||
|
||||
default: function() {
|
||||
return [{
|
||||
ply: 1,
|
||||
variation: null
|
||||
}];
|
||||
},
|
||||
|
||||
read: function(str) {
|
||||
return str.split(',').map(function(step) {
|
||||
var s = step.split(':');
|
||||
return {
|
||||
ply: parseInt(s[0]),
|
||||
variation: s[1] ? parseInt(s[1]) : null
|
||||
};
|
||||
})
|
||||
},
|
||||
|
||||
write: function(path) {
|
||||
return path.map(function(step) {
|
||||
return step.variation ? step.ply + ':' + step.variation : step.ply;
|
||||
}).join(',');
|
||||
},
|
||||
|
||||
currentPly: function(path) {
|
||||
return path[path.length - 1].ply;
|
||||
},
|
||||
|
||||
withPly: function(path, ply) {
|
||||
var p2 = path.slice(0);
|
||||
p2[p2.length - 1].ply = ply;
|
||||
return p2;
|
||||
},
|
||||
|
||||
withVariation: function(path, index) {
|
||||
var p2 = path.slice(0);
|
||||
var last = p2.length - 1;
|
||||
p2[last] = {
|
||||
ply: p2[last].ply,
|
||||
variation: index
|
||||
};
|
||||
p2.push({
|
||||
ply: null,
|
||||
variation: null
|
||||
});
|
||||
return p2;
|
||||
}
|
||||
};
|
245
ui/analyse/src/view.js
Normal file
245
ui/analyse/src/view.js
Normal file
|
@ -0,0 +1,245 @@
|
|||
var m = require('mithril');
|
||||
var chessground = require('chessground');
|
||||
var classSet = require('chessground').util.classSet;
|
||||
var game = require('game').game;
|
||||
var partial = require('chessground').util.partial;
|
||||
var renderStatus = require('game').view.status;
|
||||
var mod = require('game').view.mod;
|
||||
var treePath = require('./path');
|
||||
var control = require('./control');
|
||||
|
||||
function renderEval(e) {
|
||||
e = Math.round(e / 10) / 10;
|
||||
return (e > 0 ? '+' : '') + e;
|
||||
}
|
||||
|
||||
function autoScroll(movelist) {
|
||||
var plyEl = movelist.querySelector('.active');
|
||||
if (plyEl) movelist.scrollTop = plyEl.offsetTop - movelist.offsetHeight / 2 + plyEl.offsetHeight / 2;
|
||||
}
|
||||
|
||||
var emptyMove = m('em.move.empty', '...');
|
||||
|
||||
function renderMove(ctrl, move, path) {
|
||||
if (!move) return emptyMove;
|
||||
var pathStr = treePath.write(treePath.withPly(path, move.ply));
|
||||
return {
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
class: 'move' + (pathStr === ctrl.vm.pathStr ? ' active' : ''),
|
||||
'data-path': pathStr
|
||||
},
|
||||
children: [
|
||||
move.san,
|
||||
move.eval ? m('span', renderEval(move.eval)) : (
|
||||
move.mate ? m('span', '#' + move.mate) : null)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function plyToTurn(ply) {
|
||||
return Math.floor((ply - 1) / 2) + 1;
|
||||
}
|
||||
|
||||
function renderVariation(ctrl, variation, path) {
|
||||
var turns = [];
|
||||
if (variation[0].ply % 2 === 0) {
|
||||
variation = variation.slice(0);
|
||||
var move = variation.shift();
|
||||
turns.push({
|
||||
turn: plyToTurn(move.ply),
|
||||
black: move
|
||||
});
|
||||
}
|
||||
for (i = 0, nb = variation.length; i < nb; i += 2) turns.push({
|
||||
turn: plyToTurn(variation[i].ply),
|
||||
white: variation[i],
|
||||
black: variation[i + 1]
|
||||
});
|
||||
return m('div.variation', turns.map(function(turn) {
|
||||
return renderVariationTurn(ctrl, turn, path);
|
||||
}));
|
||||
}
|
||||
|
||||
function renderVariationTurn(ctrl, turn, path) {
|
||||
var wMove = turn.white ? renderMove(ctrl, turn.white, path) : null;
|
||||
var bMove = turn.black ? renderMove(ctrl, turn.black, path) : null;
|
||||
if (turn.white) return [turn.turn + '.', wMove, (turn.black ? [m.trust(' '), bMove] : ''), ' '];
|
||||
return [turn.turn + '...', bMove, ' '];
|
||||
}
|
||||
|
||||
function renderMeta(ctrl, move, path) {
|
||||
if (!move || !move.comments.length || !move.variations.length) return;
|
||||
return [
|
||||
move.comments.length ? move.comments.map(function(comment) {
|
||||
return m('div.comment', comment);
|
||||
}) : null,
|
||||
move.variations.length ? move.variations.map(function(variation, i) {
|
||||
return renderVariation(ctrl, variation, treePath.withVariation(path, i + 1));
|
||||
}) : null,
|
||||
];
|
||||
}
|
||||
|
||||
function renderTurn(ctrl, turn, path) {
|
||||
var index = m('div.index', turn.turn);
|
||||
var wMove = turn.white ? renderMove(ctrl, turn.white, path) : null;
|
||||
var wMeta = renderMeta(ctrl, turn.white, path);
|
||||
var bMove = turn.black ? renderMove(ctrl, turn.black, path) : null;
|
||||
var bMeta = renderMeta(ctrl, turn.black, path);
|
||||
if (turn.white) {
|
||||
if (wMeta) return [
|
||||
m('div.turn', [index, wMove, emptyMove]),
|
||||
wMeta,
|
||||
turn.black ? [
|
||||
m('div.turn', [index, emptyMove, bMove]),
|
||||
bMeta
|
||||
] : null,
|
||||
];
|
||||
return [
|
||||
m('div.turn', [index, wMove, bMove]),
|
||||
bMeta
|
||||
];
|
||||
}
|
||||
return [
|
||||
m('div.turn', [index, emptyMove, bMove]),
|
||||
bMeta
|
||||
];
|
||||
}
|
||||
|
||||
function renderTree(ctrl, tree) {
|
||||
var turns = [];
|
||||
for (i = 0, nb = tree.length; i < nb; i += 2) turns.push({
|
||||
turn: Math.floor(i / 2) + 1,
|
||||
white: tree[i],
|
||||
black: tree[i + 1]
|
||||
});
|
||||
var path = treePath.default();
|
||||
return turns.map(function(turn) {
|
||||
return renderTurn(ctrl, turn, path);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAnalyse(ctrl) {
|
||||
var result;
|
||||
if (ctrl.data.game.status.id >= 30) switch (ctrl.data.game.winner) {
|
||||
case 'white':
|
||||
result = '1-0';
|
||||
break;
|
||||
case 'black':
|
||||
result = '0-1';
|
||||
break;
|
||||
default:
|
||||
result = '½-½';
|
||||
}
|
||||
var tree = renderTree(ctrl, ctrl.analyse.tree);
|
||||
if (result) {
|
||||
tree.push(m('div.result', result));
|
||||
var winner = game.getPlayer(ctrl.data, ctrl.data.game.winner);
|
||||
tree.push(m('div.status', [
|
||||
renderStatus(ctrl),
|
||||
winner ? ', ' + ctrl.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') : null
|
||||
]));
|
||||
}
|
||||
return m('div.analyse', {
|
||||
onclick: function(e) {
|
||||
var path = e.target.getAttribute('data-path') || e.target.parentNode.getAttribute('data-path');
|
||||
if (path) {
|
||||
ctrl.jump(treePath.read(path));
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
tree);
|
||||
}
|
||||
|
||||
function visualBoard(ctrl) {
|
||||
return m('div.lichess_board_wrap',
|
||||
m('div.lichess_board.' + ctrl.data.game.variant.key,
|
||||
chessground.view(ctrl.chessground)));
|
||||
}
|
||||
|
||||
function blindBoard(ctrl) {
|
||||
return m('div.lichess_board_blind', [
|
||||
m('div.textual', {
|
||||
config: function(el, isUpdate) {
|
||||
if (!isUpdate) blind.init(el, ctrl);
|
||||
}
|
||||
}),
|
||||
chessground.view(ctrl.chessground)
|
||||
]);
|
||||
}
|
||||
|
||||
function buttons(ctrl) {
|
||||
var nbMoves = ctrl.data.game.moves.length;
|
||||
return [
|
||||
m('div.game_control', [
|
||||
m('a.button.flip.hint--bottom', {
|
||||
'data-hint': ctrl.trans('flipBoard'),
|
||||
onclick: ctrl.flip
|
||||
}, m('span[data-icon=B]')),
|
||||
m('a.button.hint--bottom', {
|
||||
'data-hint': ctrl.trans('boardEditor'),
|
||||
href: ctrl.router.Editor.game(ctrl.data.game.id).url + '?fen=' + ctrl.vm.situation.fen
|
||||
}, m('span[data-icon=m]')),
|
||||
m('a.button.hint--bottom', {
|
||||
'data-hint': ctrl.trans('continueFromHere'),
|
||||
onclick: function() {
|
||||
ctrl.vm.continue = !ctrl.vm.continue
|
||||
}
|
||||
}, m('span[data-icon=U]')),
|
||||
m('div.jumps.hint--bottom', {
|
||||
'data-hint': 'Tip: use your keyboard arrow keys!'
|
||||
}, [
|
||||
['first', 'W', control.first],
|
||||
['prev', 'Y', control.prev],
|
||||
['next', 'X', control.next],
|
||||
['last', 'V', control.last]
|
||||
].map(function(b) {
|
||||
var enabled = true;
|
||||
return m('a', {
|
||||
class: 'button ' + b[0] + ' ' + classSet({
|
||||
disabled: (ctrl.broken || !enabled),
|
||||
glowing: ctrl.vm.late && b[0] === 'last'
|
||||
}),
|
||||
'data-icon': b[1],
|
||||
onclick: enabled ? partial(b[2], ctrl) : null
|
||||
});
|
||||
}))
|
||||
]),
|
||||
ctrl.vm.continue ? m('div.continue', [
|
||||
m('a.button', {
|
||||
href: ctrl.router.Round.continue(ctrl.data.game.id, 'ai').url + '?fen=' + ctrl.vm.situation.fen
|
||||
}, ctrl.trans('playWithTheMachine')),
|
||||
m('a.button', {
|
||||
href: ctrl.router.Round.continue(ctrl.data.game.id, 'friend').url + '?fen=' + ctrl.vm.situation.fen
|
||||
}, ctrl.trans('playWithAFriend'))
|
||||
]) : null
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = function(ctrl) {
|
||||
return [
|
||||
m('div.top', [
|
||||
m('div.lichess_game', {
|
||||
config: function(el, isUpdate, context) {
|
||||
if (isUpdate) return;
|
||||
$('body').trigger('lichess.content_loaded');
|
||||
}
|
||||
}, [
|
||||
ctrl.data.blind ? blindBoard(ctrl) : visualBoard(ctrl),
|
||||
m('div.lichess_ground',
|
||||
m('div.replay', {
|
||||
config: function(el, isUpdate) {
|
||||
autoScroll(el);
|
||||
if (!isUpdate) setTimeout(partial(autoScroll, el), 100);
|
||||
}
|
||||
},
|
||||
renderAnalyse(ctrl)))
|
||||
])
|
||||
]),
|
||||
m('div.underboard', [
|
||||
m('div.center', buttons(ctrl)),
|
||||
m('div.right')
|
||||
])
|
||||
];
|
||||
};
|
36
ui/game/package.json
Normal file
36
ui/game/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "game",
|
||||
"version": "1.0.0",
|
||||
"description": "lichess.org game",
|
||||
"main": "src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ornicar/lila"
|
||||
},
|
||||
"keywords": [
|
||||
"chess",
|
||||
"lichess",
|
||||
"play",
|
||||
"replay"
|
||||
],
|
||||
"author": "Thibault Duplessis",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ornicar/lila/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ornicar/lila",
|
||||
"devDependencies": {
|
||||
"browserify": "^5.12.0",
|
||||
"gulp": "^3.8.8",
|
||||
"gulp-jshint": "^1.8.4",
|
||||
"gulp-streamify": "0.0.5",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"vinyl-source-stream": "^1.0.0",
|
||||
"watchify": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash-node": "^2.4.1",
|
||||
"mithril": "^0.1.22"
|
||||
}
|
||||
}
|
9
ui/game/src/main.js
Normal file
9
ui/game/src/main.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
game: require('./game'),
|
||||
status: require('./status'),
|
||||
view: {
|
||||
status: require('./view/status'),
|
||||
user: require('./view/user'),
|
||||
mod: require('./view/mod')
|
||||
}
|
||||
};
|
26
ui/game/src/view/mod.js
Normal file
26
ui/game/src/view/mod.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
var m = require('mithril');
|
||||
var game = require('../game');
|
||||
var renderUser = require('./user');
|
||||
|
||||
function blursOf(ctrl, player) {
|
||||
if (player.blurs) return m('p', [
|
||||
renderUser(ctrl, player, player.color),
|
||||
' ' + player.blurs.nb + '/' + game.nbMoves(ctrl.data, player.color) + ' blurs = ',
|
||||
m('strong', player.blurs.percent + '%')
|
||||
]);
|
||||
}
|
||||
|
||||
function holdOf(ctrl, player) {
|
||||
var h = player.hold;
|
||||
if (h) return m('p', [
|
||||
renderUser(ctrl, player, player.color),
|
||||
' hold alert',
|
||||
m('br'),
|
||||
'ply=' + h.ply + ', mean=' + h.mean + ' ms, SD=' + h.sd
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
blursOf: blursOf,
|
||||
holdOf: holdOf
|
||||
};
|
46
ui/game/src/view/status.js
Normal file
46
ui/game/src/view/status.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
var m = require('mithril');
|
||||
|
||||
module.exports = function(ctrl) {
|
||||
switch (ctrl.data.game.status.name) {
|
||||
case 'started':
|
||||
return ctrl.trans('playingRightNow');
|
||||
case 'aborted':
|
||||
return ctrl.trans('gameAborted');
|
||||
case 'mate':
|
||||
return ctrl.trans('checkmate');
|
||||
case 'resign':
|
||||
return ctrl.trans(ctrl.data.game.winner == 'white' ? 'blackResigned' : 'whiteResigned');
|
||||
case 'stalemate':
|
||||
return ctrl.trans('stalemate');
|
||||
case 'timeout':
|
||||
switch (ctrl.data.game.winner) {
|
||||
case 'white':
|
||||
return ctrl.trans('blackLeftTheGame');
|
||||
case 'black':
|
||||
return ctrl.trans('whiteLeftTheGame');
|
||||
default:
|
||||
return ctrl.trans('draw');
|
||||
}
|
||||
break;
|
||||
case 'draw':
|
||||
return ctrl.trans('draw');
|
||||
case 'outoftime':
|
||||
return ctrl.trans('timeOut');
|
||||
case 'noStart':
|
||||
return (ctrl.data.game.winner == 'white' ? 'Black' : 'White') + ' didn\'t move';
|
||||
case 'cheat':
|
||||
return 'Cheat detected';
|
||||
case 'variantEnd':
|
||||
switch (ctrl.data.game.variant.key) {
|
||||
case 'kingOfTheHill':
|
||||
return 'King in the center';
|
||||
case 'threeCheck':
|
||||
return 'Three checks';
|
||||
default:
|
||||
return 'Variant ending';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ctrl.data.game.status.name;
|
||||
}
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
var m = require('mithril');
|
||||
var round = require('../round');
|
||||
var game = require('../game');
|
||||
|
||||
function getPlayerRating(ctrl, player) {
|
||||
if (player.user) {
|
||||
|
@ -26,7 +26,7 @@ module.exports = function(ctrl, player, klass) {
|
|||
},
|
||||
class: 'user_link ' + (player.user.online ? 'online is-green' : 'offline') + (klass ? ' ' + klass : ''),
|
||||
href: ctrl.router.User.show(player.user.username).url,
|
||||
target: round.isPlayerPlaying(ctrl.data) ? '_blank' : null,
|
||||
target: game.isPlayerPlaying(ctrl.data) ? '_blank' : null,
|
||||
'data-icon': 'r',
|
||||
}, [
|
||||
(player.user.title ? player.user.title + ' ' : '') + player.user.username,
|
|
@ -217,7 +217,7 @@ function renderViewControls(ctrl, fen) {
|
|||
'data-hint': ctrl.trans('fromGameLink', ctrl.data.puzzle.gameId),
|
||||
href: ctrl.router.Round.watcher(ctrl.data.puzzle.gameId, ctrl.data.puzzle.color).url + '#' + ctrl.data.puzzle.initialPly
|
||||
}, m('span[data-icon=v]')) : null,
|
||||
m('a.fen_link.button.hint--bottom', {
|
||||
m('a.button.hint--bottom', {
|
||||
'data-hint': ctrl.trans('boardEditor'),
|
||||
href: ctrl.router.Editor.load(fen).url
|
||||
}, m('span[data-icon=m]')),
|
||||
|
|
|
@ -30,8 +30,9 @@
|
|||
"watchify": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chessli.js": "^0.2.0",
|
||||
"chessground": "^1.7.9",
|
||||
"chessli.js": "^0.2.0",
|
||||
"game": "file:../game",
|
||||
"lodash-node": "^2.4.1",
|
||||
"mithril": "^0.1.22",
|
||||
"mousetrap": "0.0.1"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
var round = require('./round');
|
||||
var game = require('game').game;
|
||||
|
||||
// Register blur events to be sent as move metadata
|
||||
|
||||
var blur = false;
|
||||
|
||||
var init = function(ctrl) {
|
||||
if (round.isPlayerPlaying(ctrl.data)) {
|
||||
if (game.isPlayerPlaying(ctrl.data)) {
|
||||
window.addEventListener('blur', function() {
|
||||
blur = true;
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ var throttle = require('lodash-node/modern/functions/throttle');
|
|||
var chessground = require('chessground');
|
||||
var partial = chessground.util.partial;
|
||||
var data = require('./data');
|
||||
var round = require('./round');
|
||||
var status = require('./status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var ground = require('./ground');
|
||||
var socket = require('./socket');
|
||||
var xhr = require('./xhr');
|
||||
|
@ -62,7 +62,7 @@ module.exports = function(cfg, router, i18n, socketSend) {
|
|||
else this.chessground.apiMove(o.from, o.to);
|
||||
if (this.data.game.threefold) this.data.game.threefold = false;
|
||||
this.data.game.moves.push(o.san);
|
||||
round.setOnGame(this.data, o.color, true);
|
||||
game.setOnGame(this.data, o.color, true);
|
||||
m.redraw();
|
||||
if (this.data.player.spectator || o.color != this.data.player.color) $.sound.move(o.color == 'white');
|
||||
if (this.data.blind) blind.reload(this);
|
||||
|
@ -84,7 +84,7 @@ module.exports = function(cfg, router, i18n, socketSend) {
|
|||
) : false;
|
||||
|
||||
this.isClockRunning = function() {
|
||||
return this.data.clock && round.playable(this.data) &&
|
||||
return this.data.clock && game.playable(this.data) &&
|
||||
((this.data.game.turns - this.data.game.startedAtTurn) > 1 || this.data.clock.running);
|
||||
}.bind(this);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var chessground = require('chessground');
|
||||
var round = require('./round');
|
||||
var game = require('game').game;
|
||||
|
||||
function str2move(m) {
|
||||
return m ? [m.slice(0, 2), m.slice(2, 4)] : null;
|
||||
|
@ -20,8 +20,8 @@ function makeConfig(data, fen, flip) {
|
|||
},
|
||||
movable: {
|
||||
free: false,
|
||||
color: round.isPlayerPlaying(data) ? data.player.color : null,
|
||||
dests: round.parsePossibleMoves(data.possibleMoves),
|
||||
color: game.isPlayerPlaying(data) ? data.player.color : null,
|
||||
dests: game.parsePossibleMoves(data.possibleMoves),
|
||||
showDests: data.pref.destination
|
||||
},
|
||||
animation: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var title = require('./title');
|
||||
var blur = require('./blur');
|
||||
var round = require('./round');
|
||||
var status = require('./status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var keyboard = require('./replay/keyboard');
|
||||
|
||||
module.exports = function(ctrl) {
|
||||
|
@ -13,10 +13,10 @@ module.exports = function(ctrl) {
|
|||
|
||||
blur.init(ctrl);
|
||||
|
||||
if (round.isPlayerPlaying(d) && round.nbMoves(d, d.player.color) === 0) $.sound.dong();
|
||||
if (game.isPlayerPlaying(d) && game.nbMoves(d, d.player.color) === 0) $.sound.dong();
|
||||
|
||||
if (round.isPlayerPlaying(d)) window.addEventListener('beforeunload', function(e) {
|
||||
if (!lichess.hasToReload && !ctrl.data.blind && round.playable(ctrl.data) && ctrl.data.clock) {
|
||||
if (game.isPlayerPlaying(d)) window.addEventListener('beforeunload', function(e) {
|
||||
if (!lichess.hasToReload && !ctrl.data.blind && game.playable(ctrl.data) && ctrl.data.clock) {
|
||||
ctrl.socket.send('bye');
|
||||
var msg = 'There is a game in progress!';
|
||||
(e || window.event).returnValue = msg;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var Chess = require('chessli.js').Chess;
|
||||
var round = require('../round');
|
||||
var game = require('game').game;
|
||||
var xhr = require('../xhr');
|
||||
|
||||
module.exports = function(root) {
|
||||
|
@ -59,8 +59,8 @@ module.exports = function(root) {
|
|||
this.vm.late = false;
|
||||
root.chessground.set({
|
||||
movable: {
|
||||
color: round.isPlayerPlaying(root.data) ? root.data.player.color : null,
|
||||
dests: round.parsePossibleMoves(root.data.possibleMoves)
|
||||
color: game.isPlayerPlaying(root.data) ? root.data.player.color : null,
|
||||
dests: game.parsePossibleMoves(root.data.possibleMoves)
|
||||
}
|
||||
});
|
||||
}.bind(this);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var partial = require('chessground').util.partial;
|
||||
var classSet = require('chessground').util.classSet;
|
||||
var round = require('../round');
|
||||
var status = require('../status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var renderStatus = require('../view/status');
|
||||
|
||||
function renderTd(move, ply, curPly) {
|
||||
|
@ -39,7 +39,7 @@ function renderTable(ctrl, curPly) {
|
|||
});
|
||||
if (result) {
|
||||
trs.push(m('tr', m('td.result[colspan=3]', result)));
|
||||
var winner = round.getPlayer(ctrl.root.data, ctrl.root.data.game.winner);
|
||||
var winner = game.getPlayer(ctrl.root.data, ctrl.root.data.game.winner);
|
||||
trs.push(m('tr.status', m('td[colspan=3]', [
|
||||
renderStatus(ctrl.root),
|
||||
winner ? ', ' + ctrl.root.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') : null
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var m = require('mithril');
|
||||
var round = require('./round');
|
||||
var game = require('game').game;
|
||||
var ground = require('./ground');
|
||||
var xhr = require('./xhr');
|
||||
|
||||
|
@ -12,7 +12,7 @@ module.exports = function(send, ctrl) {
|
|||
ctrl.data.possibleMoves = o;
|
||||
if (!ctrl.replay.active) ctrl.chessground.set({
|
||||
movable: {
|
||||
dests: round.parsePossibleMoves(o)
|
||||
dests: game.parsePossibleMoves(o)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ module.exports = function(send, ctrl) {
|
|||
},
|
||||
crowd: function(o) {
|
||||
['white', 'black'].forEach(function(c) {
|
||||
round.setOnGame(ctrl.data, c, o[c]);
|
||||
game.setOnGame(ctrl.data, c, o[c]);
|
||||
});
|
||||
m.redraw();
|
||||
},
|
||||
|
@ -87,7 +87,7 @@ module.exports = function(send, ctrl) {
|
|||
},
|
||||
gone: function(isGone) {
|
||||
if (!ctrl.data.opponent.ai) {
|
||||
round.setIsGone(ctrl.data, ctrl.data.opponent.color, isGone);
|
||||
game.setIsGone(ctrl.data, ctrl.data.opponent.color, isGone);
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
var round = require('./round');
|
||||
var status = require('./status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var partial = require('chessground').util.partial;
|
||||
|
||||
var initialTitle = document.title;
|
||||
var tickDelay = 400;
|
||||
|
||||
var tick = function(ctrl) {
|
||||
if (status.started(ctrl.data) && round.isPlayerTurn(ctrl.data)) {
|
||||
if (status.started(ctrl.data) && game.isPlayerTurn(ctrl.data)) {
|
||||
document.title = document.title.indexOf('/\\/') === 0 ? '\\/\\ ' + document.title.replace(/\/\\\/ /, '') : '/\\/ ' + document.title.replace(/\\\/\\ /, '');
|
||||
}
|
||||
setTimeout(partial(tick, ctrl), tickDelay);
|
||||
|
@ -21,7 +21,7 @@ var set = function(ctrl, text) {
|
|||
if (!text) {
|
||||
if (status.finished(ctrl.data)) {
|
||||
text = ctrl.trans('gameOver');
|
||||
} else if (round.isPlayerTurn(ctrl.data)) {
|
||||
} else if (game.isPlayerTurn(ctrl.data)) {
|
||||
text = ctrl.trans('yourTurn');
|
||||
} else {
|
||||
text = ctrl.trans('waitingForOpponent');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var chessground = require('chessground');
|
||||
var round = require('../round');
|
||||
var status = require('../status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var partial = chessground.util.partial;
|
||||
var throttle = require('lodash-node/modern/functions/throttle');
|
||||
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||
}, m('span[data-icon=' + icon + ']')) : null;
|
||||
},
|
||||
forceResign: function(ctrl) {
|
||||
if (!ctrl.data.opponent.ai && ctrl.data.clock && ctrl.data.opponent.isGone && round.resignable(ctrl.data))
|
||||
if (!ctrl.data.opponent.ai && ctrl.data.clock && ctrl.data.opponent.isGone && game.resignable(ctrl.data))
|
||||
return m('div.force_resign_zone', [
|
||||
ctrl.trans('theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim'),
|
||||
m('br'),
|
||||
|
@ -138,13 +138,13 @@ module.exports = {
|
|||
}, ctrl.trans('viewTournament'));
|
||||
},
|
||||
moretime: function(ctrl) {
|
||||
if (round.moretimeable(ctrl.data)) return m('a.moretime.hint--bottom-left', {
|
||||
if (game.moretimeable(ctrl.data)) return m('a.moretime.hint--bottom-left', {
|
||||
'data-hint': ctrl.trans('giveNbSeconds', ctrl.data.clock.moretime),
|
||||
onclick: throttle(partial(ctrl.socket.send, 'moretime', null), 600)
|
||||
}, m('span[data-icon=O]'));
|
||||
},
|
||||
replayAndAnalyse: function(ctrl) {
|
||||
if (round.replayable(ctrl.data)) return m('a.button.replay_and_analyse[data-icon=G]', {
|
||||
if (game.replayable(ctrl.data)) return m('a.button.replay_and_analyse[data-icon=G]', {
|
||||
href: ctrl.router.Round.watcher(ctrl.data.game.id, ctrl.data.player.color).url
|
||||
}, ctrl.trans('replayAndAnalyse'));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var m = require('mithril');
|
||||
var round = require('../round');
|
||||
var game = require('game').game;
|
||||
var chessground = require('chessground');
|
||||
var renderTable = require('./table');
|
||||
var renderPromotion = require('../promotion').view;
|
||||
var renderUser = require('./user');
|
||||
var mod = require('game').view.mod;
|
||||
var partial = require('chessground').util.partial;
|
||||
var button = require('./button');
|
||||
var blind = require('../blind');
|
||||
|
@ -24,24 +24,6 @@ function renderMaterial(ctrl, material) {
|
|||
return m('div.cemetery', children);
|
||||
}
|
||||
|
||||
function blursOf(ctrl, player) {
|
||||
if (player.blurs) return m('p', [
|
||||
renderUser(ctrl, player, player.color),
|
||||
' ' + player.blurs.nb + '/' + round.nbMoves(ctrl.data, player.color) + ' blurs = ',
|
||||
m('strong', player.blurs.percent + '%')
|
||||
]);
|
||||
}
|
||||
|
||||
function holdOf(ctrl, player) {
|
||||
var h = player.hold;
|
||||
if (h) return m('p', [
|
||||
renderUser(ctrl, player, player.color),
|
||||
' hold alert',
|
||||
m('br'),
|
||||
'ply=' + h.ply + ', mean=' + h.mean + ' ms, SD=' + h.sd
|
||||
]);
|
||||
}
|
||||
|
||||
function visualBoard(ctrl) {
|
||||
return m('div.lichess_board_wrap', [
|
||||
m('div.lichess_board.' + ctrl.data.game.variant.key, {
|
||||
|
@ -94,7 +76,7 @@ module.exports = function(ctrl) {
|
|||
button.replayAndAnalyse(ctrl)
|
||||
]),
|
||||
m('div.right', [
|
||||
[ctrl.data.opponent, ctrl.data.player].map(partial(blursOf, ctrl)), [ctrl.data.opponent, ctrl.data.player].map(partial(holdOf, ctrl))
|
||||
[ctrl.data.opponent, ctrl.data.player].map(partial(mod.blursOf, ctrl)), [ctrl.data.opponent, ctrl.data.player].map(partial(mod.holdOf, ctrl))
|
||||
])
|
||||
])
|
||||
];
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
var m = require('mithril');
|
||||
var chessground = require('chessground');
|
||||
var round = require('../round');
|
||||
var status = require('../status');
|
||||
var game = require('game').game;
|
||||
var status = require('game').status;
|
||||
var opposite = chessground.util.opposite;
|
||||
var renderClock = require('../clock/view');
|
||||
var renderReplay = require('../replay/view');
|
||||
var renderStatus = require('./status');
|
||||
var renderUser = require('./user');
|
||||
var renderUser = require('game').view.user;
|
||||
var button = require('./button');
|
||||
|
||||
function compact(x) {
|
||||
|
@ -37,7 +37,7 @@ function renderKing(ctrl, color) {
|
|||
}
|
||||
|
||||
function renderResult(ctrl) {
|
||||
var winner = round.getPlayer(ctrl.data, ctrl.data.game.winner);
|
||||
var winner = game.getPlayer(ctrl.data, ctrl.data.game.winner);
|
||||
return winner ? m('div.player.' + winner.color, [
|
||||
renderKing(ctrl, winner.color),
|
||||
m('p', [
|
||||
|
@ -86,16 +86,16 @@ function renderTablePlay(ctrl) {
|
|||
button.cancelDrawOffer(ctrl),
|
||||
button.answerOpponentDrawOffer(ctrl),
|
||||
button.cancelTakebackProposition(ctrl),
|
||||
button.answerOpponentTakebackProposition(ctrl), (round.mandatory(d) && round.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
|
||||
button.answerOpponentTakebackProposition(ctrl), (game.mandatory(d) && game.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
|
||||
ctrl.trans('youHaveNbSecondsToMakeYourFirstMove', 30)
|
||||
) : null
|
||||
]);
|
||||
return [
|
||||
m('div.control.icons', [
|
||||
button.standard(ctrl, round.abortable, 'L', 'abortGame', 'abort'),
|
||||
button.standard(ctrl, round.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
|
||||
button.standard(ctrl, round.drawable, '2', 'offerDraw', 'draw-yes'),
|
||||
button.standard(ctrl, round.resignable, 'b', 'resign', 'resign')
|
||||
button.standard(ctrl, game.abortable, 'L', 'abortGame', 'abort'),
|
||||
button.standard(ctrl, game.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
|
||||
button.standard(ctrl, game.drawable, '2', 'offerDraw', 'draw-yes'),
|
||||
button.standard(ctrl, game.resignable, 'b', 'resign', 'resign')
|
||||
]),
|
||||
buttons ? m('div.control.buttons', buttons) : null,
|
||||
renderReplay(ctrl.replay),
|
||||
|
@ -114,7 +114,7 @@ module.exports = function(ctrl) {
|
|||
renderPlayer(ctrl, ctrl.data.opponent),
|
||||
m('div.table_inner',
|
||||
ctrl.data.player.spectator ? renderTableWatch(ctrl) : (
|
||||
round.playable(ctrl.data) ? renderTablePlay(ctrl) : renderTableEnd(ctrl)
|
||||
game.playable(ctrl.data) ? renderTablePlay(ctrl) : renderTableEnd(ctrl)
|
||||
)
|
||||
)
|
||||
]), (ctrl.clock && !ctrl.data.blind) ? [
|
||||
|
|
Loading…
Reference in a new issue