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:
Thibault Duplessis 2014-11-08 16:31:54 +01:00
commit b9a5ba2861
60 changed files with 1346 additions and 6623 deletions

View file

@ -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) =

View file

@ -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">&nbsp;@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 &amp; 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 &amp; 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>
}
}

View file

@ -54,5 +54,9 @@ trans.blackPlays,
trans.youAreViewingThisGameAsASpectator,
trans.giveNbSeconds,
trans.gameOver,
trans.replayAndAnalyse
trans.replayAndAnalyse,
trans.boardEditor,
trans.continueFromHere,
trans.playWithTheMachine,
trans.playWithAFriend
)))

View file

@ -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)

View file

@ -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

View 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)
})
)
}

View file

@ -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,

View file

@ -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 =>

View file

@ -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

View file

@ -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))
)
}
}

View file

@ -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)

View file

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View file

@ -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();

View file

@ -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);
}
}
},

View file

@ -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');
});
}

View file

@ -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 {

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

61
ui/analyse/gulpfile.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
};

View 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
View 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
View 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
View 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('&nbsp;'), 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
View 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
View 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
View 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
};

View 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;
}
};

View file

@ -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,

View file

@ -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]')),

View file

@ -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"

View file

@ -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;
});

View file

@ -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);

View file

@ -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: {

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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();
}
},

View file

@ -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');

View file

@ -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'));
}

View file

@ -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))
])
])
];

View file

@ -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) ? [