Merge branch 'master' into atomic_chess

* master: (65 commits)
  upgrade chessground, enable autoCastle in round
  tournament style tweaks
  lazy load sounds
  fix detection of simul
  pass formatted dates to mithril build
  fix lobby now playing mithril key
  tweak tournament CSS
  start tournament clock immediately, slightly trim down WS messages
  fix tournament UI bugs
  tournament UI: help mithril with element keys
  tournament UI: watch last games
  tournament missing translation
  actor efficiency: listen to StartGame in round socket hub
  fix funny bug when round socket receives alien games
  reset round socket user id on game start - fixes #178
  tweak Pov priority and simul detection
  lt "lietuvių kalba" translation #11659. Author: patrimpas.
  sv "svenska" translation #11658. Author: Titanoboa. There was a typo, and "Rating" has been translated as the same throughout the other translations, so I kept consistent for #91..
  add note about sound control in the preferences page
  fix chessground on puzzle page
  ...

Conflicts:
	modules/chess
	public/javascripts/vendor/chessground.min.js
	ui/analyse/package.json
	ui/editor/package.json
	ui/lobby/package.json
	ui/puzzle/package.json
	ui/round/package.json
This commit is contained in:
Thibault Duplessis 2015-01-07 00:01:17 +01:00
commit 3489b9de84
137 changed files with 1356 additions and 1291 deletions

View file

@ -54,7 +54,7 @@ object Round extends LilaController with TheftPrevention {
PreventTheft(pov) {
(pov.game.tournamentId ?? TournamentRepo.byId) zip
Env.game.crosstableApi(pov.game) zip
otherPovs(pov.gameId) flatMap {
(!pov.game.isTournament ?? otherPovs(pov.gameId)) flatMap {
case ((tour, crosstable), playing) =>
Env.api.roundApi.player(pov, Env.api.version, playing) map { data =>
Ok(html.round.player(pov, data, tour = tour, cross = crosstable, playing = playing))
@ -64,7 +64,10 @@ object Round extends LilaController with TheftPrevention {
Redirect(routes.Setup.await(fullId)).fuccess
)
},
api = apiVersion => Env.api.roundApi.player(pov, apiVersion, Nil) map { Ok(_) }
api = apiVersion => {
if (pov.game.playableByAi) env.roundMap ! Tell(pov.game.id, AiPlay)
Env.api.roundApi.player(pov, apiVersion, Nil) map { Ok(_) }
}
)
}
}

View file

@ -1,7 +1,7 @@
package controllers
import play.api.data.Form
import play.api.libs.json.JsValue
import play.api.libs.json._
import play.api.mvc._
import lila.api.Context
@ -18,11 +18,6 @@ object Tournament extends LilaController {
private def tournamentNotFound(implicit ctx: Context) = NotFound(html.tournament.notFound())
protected def TourOptionFuRedirect[A](fua: Fu[Option[A]])(op: A => Fu[Call])(implicit ctx: Context) =
fua flatMap {
_.fold(tournamentNotFound(ctx).fuccess)(a => op(a) map { b => Redirect(b) })
}
val home = Open { implicit ctx =>
fetchTournaments zip repo.scheduled zip UserRepo.allSortToints(10) map {
case ((((created, started), finished), scheduled), leaderboard) =>
@ -34,7 +29,7 @@ object Tournament extends LilaController {
val system = sysStr flatMap {
case "arena" => System.Arena.some
case "swiss" => System.Swiss.some
case _ => none
case _ => none
}
Ok(html.tournament.faqPage(system)).fuccess
}
@ -47,131 +42,54 @@ object Tournament extends LilaController {
}
private def fetchTournaments =
env allCreatedSorted true zip repo.noPasswordStarted zip repo.finished(20)
env allCreatedSorted true zip repo.publicStarted zip repo.finished(20)
def show(id: String) = Open { implicit ctx =>
repo byId id flatMap {
_ match {
case Some(tour: Created) => showCreated(tour) map { Ok(_) }
case Some(tour: Started) => showStarted(tour) map { Ok(_) }
case Some(tour: Finished) => showFinished(tour) map { Ok(_) }
case _ => tournamentNotFound.fuccess
_.fold(tournamentNotFound.fuccess) { tour =>
env.version(tour.id) zip
env.jsonView(tour) zip
chatOf(tour) map {
case ((version, data), chat) => html.tournament.show(tour, version, data, chat)
}
}
}
}
private def showCreated(tour: Created)(implicit ctx: Context) =
env.version(tour.id) zip chatOf(tour) map {
case (version, chat) => html.tournament.show.created(tour, version, chat)
}
private def showStarted(tour: Started)(implicit ctx: Context) =
env.version(tour.id) zip
chatOf(tour) zip
GameRepo.games(tour recentGameIds 4) zip
tour.userCurrentPov(ctx.me).??(GameRepo.pov) map {
case (((version, chat), games), pov) =>
html.tournament.show.started(tour, version, chat, games, pov)
}
private def showFinished(tour: Finished)(implicit ctx: Context) =
env.version(tour.id) zip
chatOf(tour) zip
GameRepo.games(tour recentGameIds 4) map {
case ((version, chat), games) =>
html.tournament.show.finished(tour, version, chat, games)
}
def join(id: String) = AuthBody { implicit ctx =>
implicit me =>
NoEngine {
TourOptionFuRedirect(repo enterableById id) { tour =>
fuccess {
if (tour.hasPassword) routes.Tournament.joinPassword(id)
else {
env.api.join(tour, me, none)
routes.Tournament.show(tour.id)
}
negotiate(
html = repo enterableById id map {
case None => tournamentNotFound
case Some(tour) =>
env.api.join(tour, me)
Redirect(routes.Tournament.show(tour.id))
},
api = _ => OptionFuOk(repo enterableById id) { tour =>
env.api.join(tour, me)
fuccess(Json.obj("ok" -> true))
}
}
)
}
}
def joinPasswordForm(id: String) = Auth { implicit ctx =>
implicit me => NoEngine {
repo createdById id flatMap {
_.fold(tournamentNotFound(ctx).fuccess) { tour =>
renderJoinPassword(tour, env.forms.joinPassword) map { Ok(_) }
}
}
}
}
def joinPassword(id: String) = AuthBody { implicit ctx =>
implicit me =>
NoEngine {
implicit val req = ctx.body
repo createdById id flatMap {
_.fold(tournamentNotFound(ctx).fuccess) { tour =>
env.forms.joinPassword.bindFromRequest.fold(
err => renderJoinPassword(tour, err) map { BadRequest(_) },
password => {
env.api.join(tour, me, password.some)
fuccess(Redirect(routes.Tournament.show(tour.id)))
})
}
}
}
}
private def renderJoinPassword(tour: Created, form: Form[_])(implicit ctx: Context) =
env version tour.id map { html.tournament.joinPassword(tour, form, _) }
def withdraw(id: String) = Auth { implicit ctx =>
me =>
TourOptionFuRedirect(repo byId id) { tour =>
OptionResult(repo byId id) { tour =>
env.api.withdraw(tour, me.id)
fuccess(routes.Tournament.show(tour.id))
Ok(Json.obj("ok" -> true)) as JSON
}
}
def earlyStart(id: String) = Auth { implicit ctx =>
implicit me =>
TourOptionFuRedirect(repo.createdByIdAndCreator(id, me.id)) { tour =>
OptionResult(repo.createdByIdAndCreator(id, me.id)) { tour =>
env.api startIfReady tour
fuccess(routes.Tournament show tour.id)
Ok(Json.obj("ok" -> true)) as JSON
}
}
def reload(id: String) = Open { implicit ctx =>
OptionFuOk(repo byId id) {
case tour: Created => reloadCreated(tour)
case tour: Started => reloadStarted(tour)
case tour: Finished => reloadFinished(tour)
}
}
private def reloadCreated(tour: Created)(implicit ctx: Context) = fuccess {
html.tournament.show.inner(none)(html.tournament.show.createdInner(tour))
}
private def reloadStarted(tour: Started)(implicit ctx: Context) =
GameRepo.games(tour recentGameIds 4) zip
tour.userCurrentPov(ctx.me).??(GameRepo.pov) map {
case (games, pov) => {
val pairings = html.tournament.pairings(tour)
val inner = html.tournament.show.startedInner(tour, games, pov)
html.tournament.show.inner(pairings.some)(inner)
}
}
private def reloadFinished(tour: Finished)(implicit ctx: Context) =
GameRepo games (tour recentGameIds 4) map { games =>
val pairings = html.tournament.pairings(tour)
val inner = html.tournament.show.finishedInner(tour, games)
html.tournament.show.inner(pairings.some)(inner)
}
def form = Auth { implicit ctx =>
me =>
NoEngine {
@ -191,7 +109,7 @@ object Tournament extends LilaController {
}
}
def websocket(id: String) = Socket[JsValue] { implicit ctx =>
def websocket(id: String, apiVersion: Int) = Socket[JsValue] { implicit ctx =>
~(getInt("version") |@| get("sri") apply {
case (version, uid) => env.socketHandler.join(id, version, uid, ctx.me)
})

View file

@ -44,8 +44,9 @@ trait SetupHelper { self: I18nHelper =>
variantTuple(Variant.FromPosition)
def translatedVariantChoicesWithFenAndKingOfTheHill(implicit ctx: Context) =
translatedVariantChoicesWithFen(ctx) :+
variantTuple(Variant.KingOfTheHill)
translatedVariantChoices(ctx) :+
variantTuple(Variant.KingOfTheHill) :+
variantTuple(Variant.FromPosition)
def translatedVariantChoicesWithVariantsAndFen(implicit ctx: Context) =
translatedVariantChoicesWithVariants :+

View file

@ -7,8 +7,8 @@
<div class="signup_box">
<h1 class="lichess_title">Close your account</h1>
<p class="explanation">
Are you sure you want to close your account? Closing your account is a permanent decision.
You will no longer be able to login, and your profile page will no longer be accessible.
Are you sure you want to close your account? Closing your account is a permanent decision.
You will no longer be able to login, and your profile page will no longer be accessible.
</p>
<form action="@routes.Account.closeConfirm" method="POST">
<br /><br />

View file

@ -89,6 +89,14 @@
</li>
</ul>
</fieldset>
<fieldset>
<legend>Sound</legend>
<ul>
<li>
The sound control is in the top bar of every page, on the right side.
</li>
</ul>
</fieldset>
<p data-icon="E"> Your preferences have been saved.</p>
</form>
</div>

View file

@ -29,6 +29,7 @@ LichessEditor(document.getElementById('board_editor'), {
trans.loadPosition,
trans.whitePlays,
trans.blackPlays,
trans.continueFromHere,
trans.playWithTheMachine,
trans.playWithAFriend,
trans.analysis

View file

@ -1,54 +0,0 @@
@(tour: lila.tournament.StartedOrFinished)(implicit ctx: Context)
@import lila.tournament.arena.ScoringSystem
<div class="standing_wrap scroll-shadow-soft">
<table class="slist standing @if(tour.scheduled) { scheduled }">
<thead>
<tr>
<th class="large">@trans.standing() (@tour.nbPlayers)</th>
<th class="legend">
<span class="streakstarter">Streak starter</span>
<span class="double">Double points</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
@tour.rankedPlayers.map {
case (rank, player) => {
@defining((
if(tour.isFinished && rank == 1) "winner" else if (player.withdraw) "withdraw" else "",
ScoringSystem.scoreSheet(tour, player.id)
)) {
case (flag, scoreSheet) => {
<tr @if(ctx.userId.exists(player.id==)) { class="me" }>
<td class="name">
@if(player.withdraw) {
<span data-icon="b" title="@trans.withdraw()"></span>
} else {
@if(tour.isFinished && rank == 1) {
<span data-icon="g" title="@trans.winner()"></span>
} else {
<span class="rank">@rank</span>
}
}
@userInfosLink(player.id, none, withOnline = false)
</td>
<td class="sheet">
@scoreSheet.scores.take(20).reverse.map { score =>
<span class="@score.flag.toString.toLowerCase">@score.value</span>
}
</td>
<td class="total">
<strong@if(tour.isRunning && scoreSheet.onFire) { class="is-gold" data-icon="Q" }>@scoreSheet.total</strong>
</td>
</tr>
<tr><td class="around-bar" colspan="3"><div class="bar" data-value="@scoreSheet.total"></div></td></tr>
}
}
}
}
</tbody>
</table>
</div>

View file

@ -1,8 +1,12 @@
@(nbPlayers: Option[String] = Some("all"), rated: Option[Boolean] = None, system: Option[lila.tournament.System] = None)
@(nbPlayers: Option[String] = Some("all"), rated: Option[Boolean] = None, system: Option[lila.tournament.System] = None, privateId: Option[String] = None)
@import lila.tournament.System
<article class="faq">
@privateId.map { id =>
<h2>This is a private tournament</h2>
Share this URL to let people join it: @netBaseUrl@routes.Tournament.show(id)
}
@nbPlayers.map { players =>
<h2>When will the tournament start?</h2>
As soon as @players players join it.
@ -82,7 +86,7 @@
}
<h2>How does it end?</h2>
The tournament has a countdown clock.
The tournament has a countdown clock.
When it reaches zero, the tournament rankings are frozen, and the winner is announced.
Games in progress must be finished, however they don't count for the tournament.

View file

@ -5,23 +5,20 @@
@moreJs = {
<script>
$(function() {
var $pass = $('#tournament tr.password');
var $check = $('#tournament #isprivate');
var $time = $('#tournament tr.time td');
var $minutes = $('#tournament tr.minutes td');
function showPassword() {
function showPrivate() {
if ($check.prop('checked')) {
$pass.show(500);
$time.html($('#tournament .private_time').html());
$minutes.html($('#tournament .private_minutes').html());
} else {
$pass.hide().find('input').val('');
$time.html($('#tournament .public_time').html());
$minutes.html($('#tournament .public_minutes').html());
}
};
$check.on('change', showPassword);
showPassword();
$check.on('change', showPrivate);
showPrivate();
})
</script>
}
@ -40,11 +37,7 @@ moreJs = moreJs) {
<table>
<tr>
<th><label for="isprivate">@trans.isPrivate()</label></th>
<td><input type="checkbox" id="isprivate" @if(form("password").value.filter(_.nonEmpty).isDefined) { checked } /></td>
</tr>
<tr class="password">
<th><label for="@form("password").id">@trans.password()</label></th>
<td>@base.input(form("password"))</td>
<td><input type="checkbox" name="private" id="isprivate" @if(form("private").value.isDefined) { checked } /></td>
</tr>
<tr>
<th><label for="@form("system").id">System</label></th>

View file

@ -1,10 +0,0 @@
@(games: List[Game])(implicit ctx: Context)
<div class="game_list playing">
@games.map { g =>
<div>
@gameFen(g, g.firstPlayer.color)
@game.vstext(g)(ctx.some)
</div>
}
</div>

View file

@ -27,7 +27,7 @@
@tournament.layout(
title = trans.tournaments.str(),
side = side.some) {
<div id="tournament" data-href="@routes.Tournament.homeReload()">
<div id="tournament_list" data-href="@routes.Tournament.homeReload()">
@tournament.homeInner(createds, starteds, finisheds)
</div>
}

View file

@ -6,7 +6,7 @@
<span class="joined label" data-icon="E">&nbsp;JOINED</span>
} else {
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button">&nbsp;@trans.join()</button>
<button data-icon="G" type="submit" class="submit button text">@trans.join()</button>
</form>
}
}
@ -54,10 +54,7 @@
@if(ctx.isAuth) {
<tr class="create">
<td colspan="5">
<br />
<a href="@routes.Tournament.form()" class="action button" data-icon="g"> @trans.createANewTournament()</a>
<br />
<br />
<a href="@routes.Tournament.form()" class="action button text">@trans.createANewTournament()</a>
</td>
</tr>
}

View file

@ -1,21 +0,0 @@
@(tour: lila.tournament.Created, form: Form[_], version: Int)(implicit ctx: Context)
@formHtml = {
<div class="content_box_content join_password">
<h2>A password is required to join this tournament</h2>
<form action="@routes.Tournament.joinPassword(tour.id)" method="POST">
@base.input(form("password"), "Password".some)
<input type="submit" class="submit" value="@trans.join()" />
<a href="@routes.Tournament.show(tour.id)">@trans.cancel()</a>
</form>
</div>
}
@tournament.show.layout(
tour = tour,
side = tournament.side(tour),
chat = None,
version = version,
title = s"${trans.join.str()}${tour.fullName}") {
@tournament.show.createdInner(tour, formHtml.some)
}

View file

@ -0,0 +1,14 @@
@()(implicit ctx: Context)
@Html(J.stringify(i18nJsObject(
trans.standing,
trans.isPrivate,
trans.tournamentIsStarting,
trans.tournamentPoints,
trans.viewTournament,
trans.backToTournament,
trans.waitingForNbPlayers,
trans.join,
trans.withdraw,
trans.joinTheGame,
trans.finished
)))

View file

@ -1,4 +1,4 @@
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None)(body: Html)(implicit ctx: Context)
@(title: String, moreJs: Html = Html(""), side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, chessground: Boolean = true)(body: Html)(implicit ctx: Context)
@moreCss = {
@cssTag("tournament.css")
@ -11,6 +11,7 @@ moreJs = moreJs,
side = side,
chat = chat,
active = siteMenu.tournament.some,
underchat = underchat) {
underchat = underchat,
chessground = chessground) {
@body
}

View file

@ -1,13 +0,0 @@
@(tour: lila.tournament.Tournament)(implicit ctx: Context)
@showUser(p: lila.tournament.Pairing, u: String) = {
<span class="@p.finished.fold(p.draw.fold("draw", p.wonBy(u).fold("win", "loss")), "playing")">@usernameOrId(u)</span>
}
<div class="pairings">
@tour.pairings.map { pairing =>
<a class="revert-underline" href="@routes.Round.watcher(pairing.gameId, "white")">
@showUser(pairing, pairing.user1) <em>vs</em> @showUser(pairing, pairing.user2)
</a>
}
</div>

View file

@ -0,0 +1,35 @@
@(tour: lila.tournament.Tournament, socketVersion: Int, data: play.api.libs.json.JsObject, chat: Option[lila.chat.UserChat])(implicit ctx: Context)
@underchat = {
<div class="watchers" data-icon="v">
<span class="list inline_userlist"></span>
</div>
}
@moreJs = {
@jsAt(s"compiled/lichess.tournament${isProd??(".min")}.js")
@embedJs {
lichess = lichess || {};
lichess.tournament = {
data: @Html(play.api.libs.json.Json.stringify(data)),
i18n: @jsI18n(),
socketVersion: @socketVersion,
userId: @Html(ctx.userId.fold("null")(id => s""""$id""""))
};
}
}
@tournament.layout(
title = tour.fullName,
side = tournament.side(tour).some,
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
underchat = underchat.some,
moreJs = moreJs,
chessground = false) {
<div id="tournament" @if(tour.scheduled) { class="scheduled" }></div>
@if(!tour.isRunning && !tour.isFinished) {
<div id="tournament_faq" class="none">
@faq(!tour.scheduled option tour.minPlayers.toString, tour.rated.some, tour.system.some, tour.`private`.option(tour.id))
</div>
}
}

View file

@ -1,11 +0,0 @@
@(tour: lila.tournament.Created, version: Int, chat: Option[lila.chat.UserChat])(implicit ctx: Context)
@tournament.show.layout(
tour = tour,
side = tournament.side(tour),
chat = chat,
version = version,
title = tour.fullName) {
@tournament.show.createdInner(tour, none)
}

View file

@ -1,56 +0,0 @@
@(tour: lila.tournament.Created, form: Option[Html] = None)(implicit ctx: Context)
<h1 data-icon="g">
@tour.fullName
@if(tour.isSwiss) { [beta] }
@if(tour.hasPassword) {
<span data-icon="a"> @trans.isPrivate()</span>
}
</h1>
@form
<div class="user_list">
<table class="slist user_list">
<thead>
<tr>
<th class="large">
@tour.schedule.map { s =>
Starting @momentFromNow(s.at)
}.getOrElse {
@if(tour.enoughPlayersToStart) {
@trans.tournamentIsStarting()
} else {
@trans.waitingForNbPlayers(tour.missingPlayers)
}
}
</th>
@ctx.me.map { me =>
<th>
@if(tour contains me) {
@if(tour.isCreator(me.id) && tour.enoughPlayersToEarlyStart) {
<form class="inline" action="@routes.Tournament.earlyStart(tour.id)" method="POST">
<input type="submit" class="submit button" value="Early Start" />
</form>
}
<form class="inline" action="@routes.Tournament.withdraw(tour.id)" method="POST">
<button type="submit" class="submit button" data-icon="b">&nbsp;@trans.withdraw()</button>
</form>
} else {
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
<button type="submit" class="submit button" data-icon="@tour.hasPassword.fold("a", "G")">&nbsp;@trans.join()</button>
</form>
}
</th>
}
</tr>
</thead>
<tbody>
@tour.players.map { player =>
<tr>
<td colspan="2">@userInfosLink(player.id, player.rating.some)</td>
</tr>
}
</tbody>
</table>
</div>
<br />
<div class="content_box_content">@tournament.faq(!tour.scheduled option tour.minPlayers.toString, tour.rated.some, Some(tour.system))</div>

View file

@ -1,11 +0,0 @@
@(tour: lila.tournament.Finished, version: Int, chat: Option[lila.chat.UserChat], games: List[Game])(implicit ctx: Context)
@tournament.show.layout(
tour = tour,
side = tournament.side(tour),
chat = chat,
version = version,
pairings = tournament.pairings(tour).some,
title = tour.fullName) {
@tournament.show.finishedInner(tour, games)
}

View file

@ -1,17 +0,0 @@
@(tour: lila.tournament.Finished, games: List[Game])(implicit ctx: Context)
<span class="title_tag">@trans.finished()</span>
<h1 data-icon="g">&nbsp;@tour.fullName</h1>
@tour.system match {
case lila.tournament.System.Arena => {
@tournament.arenaStanding(tour)
}
case lila.tournament.System.Swiss => {
@tournament.swissStanding(tour)
}
}
@tournament.games(games)

View file

@ -1,10 +0,0 @@
@(pairings: Option[Html])(body: Html)(implicit ctx: Context)
@pairings.map { s =>
<div id="tournament_side" class="scroll-shadow-soft">@s</div>
}
<div id="tournament_main">
<div class="content_box no_padding tournament_box tournament_show">
@body
</div>
</div>

View file

@ -1,22 +0,0 @@
@(tour: lila.tournament.Tournament, chat: Option[lila.chat.UserChat], title: String, version: Int, side: Html, pairings: Option[Html] = None)(body: Html)(implicit ctx: Context)
@underchat = {
<div class="watchers" data-icon="v">
<span class="list inline_userlist"></span>
</div>
}
@tournament.layout(
title = title,
side = side.some,
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
underchat = underchat.some) {
<div
id="tournament"
@if(tour.scheduled) { class="scheduled" }
data-href="@routes.Tournament.reload(tour.id)"
data-socket-url="@routes.Tournament.websocket(tour.id)">
@tournament.show.inner(pairings)(body)
</div>
@embedJs("var _ld_ = " + tournamentJsData(tour, version, ctx.me))
}

View file

@ -1,11 +0,0 @@
@(tour: lila.tournament.Started, version: Int, chat: Option[lila.chat.UserChat], games: List[Game], pov: Option[Pov])(implicit ctx: Context)
@tournament.show.layout(
tour = tour,
side = tournament.side(tour),
chat = chat,
version = version,
pairings = tournament.pairings(tour).some,
title = tour.fullName) {
@tournament.show.startedInner(tour, games, pov)
}

View file

@ -1,28 +0,0 @@
@(tour: lila.tournament.Started, games: List[Game], pov: Option[Pov])(implicit ctx: Context)
<div class="tournament_clock title_tag" data-time="@tour.remainingSeconds">
<div class="time" data-icon="p">@tour.clockStatus</div>
</div>
<h1 data-icon="g">
@tour.fullName
@if(tour.isSwiss) { [beta] }
</h1>
@pov.map { p =>
<a class="is pov button glowing" href="@routes.Round.player(p.fullId)">
You are playing @usernameOrAnon(p.opponent.userId)
<span class="pov_join" data-icon="G">&nbsp;@trans.joinTheGame()</span>
</a>
}
@tour.system match {
case lila.tournament.System.Arena => {
@tournament.arenaStanding(tour)
}
case lila.tournament.System.Swiss => {
@tournament.swissStanding(tour)
}
}
@tournament.games(games)

View file

@ -8,16 +8,6 @@
}.getOrElse {
@momentFormat(tour.createdAt)
}
@tour.password.map { password =>
<br />
<span data-icon="a">
@if(ctx.userId == tour.createdBy.some) {
@trans.password(): @tour.password
} else {
@trans.isPrivate()
}
</span>
}
<br /><br />
<span data-icon="p"> @tour.clock.show</span>,
@game.variantLink(tour.variant, variantName(tour.variant)),
@ -27,18 +17,6 @@
(<a href="@routes.Tournament.help(tour.system.toString.toLowerCase.some)">help</a>)
<br /><br />
@trans.duration(): @tour.minutes minutes
@if(tour.isRunning) {
<br /><br />
@if(tour isActive ctx.me) {
<form action="@routes.Tournament.withdraw(tour.id)" method="POST">
<button type="submit" class="submit button strong" data-icon="b"> @trans.withdraw()</button>
</form>
} else {
<form class="inline" action="@routes.Tournament.join(tour.id)" method="POST">
<button data-icon="@tour.hasPassword.fold("a", "G")" type="submit" class="submit button">&nbsp;@trans.join()</button>
</form>
}
}
@tour.winner.filter(_ => tour.isFinished).map { winner =>
<br /><br />
@trans.winner(): @userInfosLink(winner.id, none)

View file

@ -1,49 +0,0 @@
@(tour: lila.tournament.StartedOrFinished)(implicit ctx: Context)
@import lila.tournament.swiss.SwissSystem
<div class="standing_wrap scroll-shadow-soft">
<table class="slist standing @if(tour.scheduled) { scheduled }">
<thead>
<tr>
<th class="large">@trans.standing() (@tour.nbPlayers)</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@defining(SwissSystem.scoreSheets(tour)) { scoreSheets =>
@tour.rankedPlayers.map {
case (rank, player) => {
@defining(scoreSheets(player.id)) { scoreSheet =>
<tr @if(ctx.userId.exists(player.id==)) { class="me" }>
<td class="name">
@if(player.withdraw) {
<span data-icon="b" title="@trans.withdraw()"></span>
} else {
@if(tour.isFinished && rank == 1) {
<span data-icon="g" title="@trans.winner()"></span>
} else {
<span class="rank">@rank</span>
}
}
@userInfosLink(player.id, none, withOnline = false)
</td>
<td class="sheet">
@scoreSheet.scores.reverse.map { score =>
<span class="normal">@score.repr</span>
}
</td>
<td class="total">
<strong>@scoreSheet.totalRepr</strong>
<span data-hint='Tie-breaker "Neustadtl" score' class="hint--bottom-left">(@scoreSheet.neustadtlRepr)</span>
</td>
</tr>
<tr><td class="around-bar" colspan="3"><div class="bar" data-value="@scoreSheet.total"></div></td></tr>
}
}
}
}
</tbody>
</table>
</div>

View file

@ -87,6 +87,7 @@ oneDay=Aдзін дзень
nbDays=%s Дзён
nbHours=%s Гадзін
time=Час
rating=Рэйтынг
username=Карыстальнік
password=Пароль
haveAnAccount=Ужо зарэгістраваны?
@ -168,6 +169,7 @@ tournaments=Турніры
tournamentPoints=Турнірныя балы
viewTournament=Назіраць за турнірам
backToTournament=Вараціцца да турніру
backToGame=Вярнуцца да гульні
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Бясплатныя он-лайн шахматы. Гуляй у шахматы з зручным інтэрфэйсам. Без рэгістрацыі, без рэклямы, без дадатковых праграм. Гуляй у шахматы з камп'ютарам, сябрамі ці выпадковым супернікам.
teams=Каманды
nbMembers=%s чальцоў
@ -217,7 +219,7 @@ unblock=Разблякаваць
followsYou=Прыхільнікі
xStartedFollowingY=%s пачалі сачыць %s
nbFollowers=%s прыхільнікаў
nbFollowing=%s сочаць
nbFollowing=%s сочыць
more=Больш
memberSince=Чалец ад
lastLogin=Апошні логін

View file

@ -88,6 +88,7 @@ oneDay=Jedan dan
nbDays=%s dana
nbHours=%s sati
time=Vrijeme
rating=Rejting
username=Korisničko ime
password=Lozinka
haveAnAccount=Već imate račun?

View file

@ -88,6 +88,7 @@ oneDay=En dag
nbDays=%s dage
nbHours=%s timer
time=Tid
rating=Rating
username=Brugernavn
password=Password
haveAnAccount=Har du en konto?

View file

@ -88,6 +88,7 @@ oneDay=Un día
nbDays=%s días
nbHours=%s horas
time=Tempo
rating=Puntuación
username=Nome de usuario
password=Contrasinal
haveAnAccount=Tén unha conta?

View file

@ -48,6 +48,7 @@ theComputerAnalysisHasFailed=Analysis computore defuit
viewTheComputerAnalysis=Videre analysim computore
requestAComputerAnalysis=Quaerere analysim computore
computerAnalysis=Analysis computore
analysis=Explicare
blunders=Errores magni
mistakes=Errores
inaccuracies=Inaccuratitates
@ -79,6 +80,7 @@ minutesPerSide=Minutae pro utriusque lusore
variant=Varietas
timeControl=Temporis curatio
nbDays=%s dies
nbHours=%s horae
time=Tempum
username=Usoris nomen
password=Tessera
@ -147,10 +149,13 @@ takebackPropositionAccepted=Accepta est motus reversionis conditio
takebackPropositionCanceled=Sublata est motus reversionis conditio
yourOpponentProposesATakeback=Motus reversionem adversarius offert
bookmarkThisGame=Lusionem bookmarkare
search=Quaerere
advancedSearch=Investigatio multiplex
tournament=Certamen
tournamentPoints=Certaminis puncta
viewTournament=Certamen videre
backToTournament=Referre ad torneamento
backToGame=Referre ad ludo
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Liberi scacci in rete. Lude jam scaccos super clarissimas pagines. Nec conventum, nec praeconia mercatoria, nec plugin necesse sunt. Lude scaccos sive cum computatro, sive cum amicis, sive cum aleatoriis adversariis.
teams=Legiones
nbMembers=%s sodales
@ -180,6 +185,14 @@ continueFromHere=Hinc persequi
importGame=Lusionem imponere
nbImportedGames=%s impositae lusiones
thisIsAChessCaptcha=Hic ludus CAPTCHA est
retry=Adparare iterum
findFriends=Reperire amici
follow=Spectare
following=Spectans
unfollow=Non Spectare
block=Operire
unblock=Non operire
nbFollowers=%s spectatores
nbFollowing=%s spectans
more=Multus
isPrivate=Privatis

View file

@ -81,12 +81,14 @@ players=pl@Y3Rs
minutesPerSide=mINU7e$ p3R siDe
variant=V@ri@n7
timeControl=7IM3 Con7R0l
realTime=R341 71m3
correspondence=(0|?|?3$|20|\||)3|\|(3
daysPerTurn=|)4j$ |?3|? TU|?|\|
oneDay=0|\|3 |)4Y
nbDays=%s D4YS
nbHours=%s |-|0u|?$
time=7im3
rating=R4t1ng
username=us3r N@M3
password=P@5$w0Rd
haveAnAccount=h@V3 @n @Cc0unt?
@ -168,6 +170,7 @@ tournaments=70uRn@M3N75
tournamentPoints=70uRN@m3n7 P0In75
viewTournament=vi3W 70uRN@m3n7
backToTournament=b@ck 70 70URN@m3N7
backToGame=R3turn 2 g4m3
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=FR33 0NLiN3 Ch35$ g@M3. Pl@Y Ch3$$ n0w iN @ Cl3@N in73RF@c3. N0 R3GI$7R@7I0N, n0 @D$, N0 pLUgin R3quIr3D. PL@y Ch35s with 7H3 C0Mpu73R, fRi3nD$ 0r R@NdoM 0pp0N3N7$.
teams=73@M$
nbMembers=%s mem83r$
@ -304,3 +307,5 @@ thisPuzzleIsCorrect=7HI5 PuZZl3 i$ C0RR3c7 @nd in73r3$7INg
thisPuzzleIsWrong=7hi$ PuzZl3 I5 wR0ng 0R 80RInG
youHaveNbSecondsToMakeYourFirstMove=Y0u h@V3 %s 53c0nd5 2 m@k3 y0ur |=1r5t m0v3!
nbGamesInPlay=%s 94|\/|3$ 1|\| |2|4j
automaticallyProceedToNextGameAfterMoving=4ut0m4t1c411y pr0c33d 2 n3x7 g4m3 4ft3r m0v1ng
autoSwitch=4ut0 sw1tch

View file

@ -82,12 +82,15 @@ minutesPerSide=Žaidimo trukmė
variant=Variantas
timeControl=Laiko kontrolė
daysPerTurn=Dienos vienam ėjimui
oneDay=Viena diena
time=Laikas
rating=Reitingas
username=Prisijungimo Vardas
password=Slaptažodis
haveAnAccount=Turite paskyrą?
allYouNeedIsAUsernameAndAPassword=Viskas ko reikia yra jūsų pasirinktas vardas ir slaptažodis.
changePassword=Pasikeisti slaptažodį
changeEmail=Pakeisti el. pašto adresą
learnMoreAboutLichess=Sužinokite daugiau apie Lichess
rank=Rangas
gamesPlayed=Sužaisti žaidimai
@ -159,6 +162,7 @@ tournaments=Turnyrai
tournamentPoints=Turnyro taškai
viewTournament=Stebėti turnyrą
backToTournament=Grįžti į turnyrą
backToGame=Grįžti į žaidimą
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Nemokamas šachmatų žaidimas tinkle. Žaiskite šachmatais patrauklioje vartotojo sąsajoje. Nebūtina registracija, nėra reklamos, nereikia jokių priedų. Žaiskite šachmatais prieš kompiuterį, su draugu arba su atsitiktiniais varžovais.
teams=Komandos
nbMembers=%s narių

View file

@ -88,6 +88,7 @@ oneDay=Én dag
nbDays=%s dager
nbHours=%s timer
time=Tid
rating=Rating
username=Brukernavn
password=Passord
haveAnAccount=Har du en konto?

View file

@ -88,6 +88,7 @@ oneDay=En dag
nbDays=%s dagar
nbHours=%s timmar
time=Tid
rating=Rating
username=Användarnamn
password=Lösenord
haveAnAccount=Har du ett konto?
@ -187,7 +188,7 @@ teamBestPlayers=Topplista
teamRecentMembers=Nya medlemmar
xJoinedTeamY=%s gick med i lag %s
xCreatedTeamY=%s skapade lag %s
averageElo=Medelrakning
averageElo=Medelrating
location=Plats
settings=Inställningar
filterGames=Filtrera partier

View file

@ -118,12 +118,9 @@ GET /tournament/reload controllers.Tournament.homeReload
GET /tournament/new controllers.Tournament.form
POST /tournament/new controllers.Tournament.create
GET /tournament/$id<\w{8}> controllers.Tournament.show(id: String)
GET /tournament/$id<\w{8}>/socket controllers.Tournament.websocket(id: String)
GET /tournament/$id<\w{8}>/socket/v:apiVersion controllers.Tournament.websocket(id: String, apiVersion: Int)
POST /tournament/$id<\w{8}>/join controllers.Tournament.join(id: String)
GET /tournament/$id<\w{8}>/join/password controllers.Tournament.joinPasswordForm(id: String)
POST /tournament/$id<\w{8}>/join/password controllers.Tournament.joinPassword(id: String)
POST /tournament/$id<\w{8}>/withdraw controllers.Tournament.withdraw(id: String)
GET /tournament/$id<\w{8}>/reload controllers.Tournament.reload(id: String)
POST /tournament/$id<\w{8}>/early-start controllers.Tournament.earlyStart(id: String)
GET /tournament/help controllers.Tournament.help(system: Option[String] ?= None)

View file

@ -41,7 +41,8 @@ private[api] final class RoundApi(
}
private def withOtherPovs(otherPovs: List[Pov])(json: JsObject) =
if (otherPovs.isEmpty) json else json + ("simul" -> JsBoolean(true))
if (otherPovs.exists(_.game.nonAi)) json + ("simul" -> JsBoolean(true))
else json
private def withNote(note: String)(json: JsObject) =
if (note.isEmpty) json else json + ("note" -> JsString(note))

@ -1 +1 @@
Subproject commit ec8c85122d752da9c3b5bbdddc8f8061bc9784c3
Subproject commit e0989842735e58803c2ac20b2548c4d8f8c03ac2

View file

@ -26,8 +26,8 @@ final class Cached(
private implicit val userHandler = User.userBSONHandler
private val isPlayingSimulCache = AsyncCache[String, Boolean](
f = userId => GameRepo.countPlayingRealTime(userId) map (1 <),
timeToLive = 10.seconds)
f = userId => GameRepo.countPlayingRealTimeHuman(userId) map (1 <),
timeToLive = 15.seconds)
val isPlayingSimul: String => Fu[Boolean] = isPlayingSimulCache.apply _

View file

@ -84,8 +84,7 @@ final class Env(
def onStart(gameId: String) = GameRepo game gameId foreach {
_ foreach { game =>
// nobody needs it for now
// system.lilaBus.publish(actorApi.StartGame(game), 'startGame)
system.lilaBus.publish(actorApi.StartGame(game), 'startGame)
game.userIds foreach { userId =>
system.lilaBus.publish(
actorApi.UserStartGame(userId, game),

View file

@ -136,8 +136,8 @@ object GameRepo {
_.sortBy(_.updatedAt).lastOption flatMap { Pov(_, user) }
}
def countPlayingRealTime(userId: String): Fu[Int] =
$count(Query.nowPlaying(userId) ++ Query.clock(true))
def countPlayingRealTimeHuman(userId: String): Fu[Int] =
$count(Query.nowPlaying(userId) ++ Query.clock(true) ++ Query.noAi)
def setTv(id: ID) {
$update.fieldUnchecked(id, F.tvAt, $date(DateTime.now))

View file

@ -51,12 +51,14 @@ object Pov {
def apply(game: Game, user: lila.user.User): Option[Pov] =
game player user map { apply(game, _) }
def priority(pov: Pov) =
if (pov.isMyTurn) {
def priority(pov: Pov) = {
val base = if (pov.isMyTurn) {
if (pov.hasMoved) pov.remainingSeconds.getOrElse(Int.MaxValue - 1)
else 10 // first move has priority over games with more than 10s left
}
else Int.MaxValue
if (pov.game.hasClock) base - 1000 else base
}
}
case class PovRef(gameId: String, color: Color) {

View file

@ -48,6 +48,10 @@ object Query {
def user(u: String) = Json.obj(F.playerUids -> u)
def users(u: Seq[String]) = Json.obj(F.playerUids -> $in(u))
val noAi = Json.obj(
"p0.ai" -> $exists(false),
"p1.ai" -> $exists(false))
def nowPlaying(u: String) = Json.obj(F.playingUids -> u)
def recentlyPlayingWithClock(u: String) =

View file

@ -29,11 +29,14 @@ object Rewind {
castleLastMoveTime = CastleLastMoveTime(
castles = rewindedHistory.castles,
lastMove = rewindedHistory.lastMove,
lastMoveTime = Some(nowSeconds - game.createdAt.getSeconds.toInt),
lastMoveTime = Some(((nowMillis - game.createdAt.getMillis) / 100).toInt),
check = if (rewindedSituation.check) rewindedSituation.kingPos else None),
binaryMoveTimes = BinaryFormat.moveTime write (game.moveTimes take rewindedGame.turns),
status = game.status,
clock = game.clock map (_.takeback))
Progress(game, newGame, newGame.clock.map(Event.Clock.apply).toList)
Progress(game, newGame, List(
newGame.clock.map(Event.Clock.apply),
newGame.correspondenceClock.map(Event.CorrespondenceClock.apply)
).flatten)
}
}

View file

@ -53,6 +53,7 @@ private[i18n] final class JsDump(
keys.challengeToPlay,
keys.youNeedAnAccountToDoThat,
keys.createANewTournament,
keys.withdraw,
keys.join,
keys.joinTheGame,
keys.tournamentIsStarting)

View file

@ -41,7 +41,7 @@ private[lobby] final class Socket(
addMember(uid, member)
sender ! Connected(enumerator, member)
case ReloadTournaments(html) => notifyTournaments(html)
case ReloadTournaments(html) => notifyAll(makeMessage("tournaments", html))
case NewForumPost => notifyAll("reload_forum")
@ -88,10 +88,6 @@ private[lobby] final class Socket(
private def playerUrl(fullId: String) = s"/$fullId"
private def notifyTournaments(html: String) {
notifyAll(makeMessage("tournaments", html))
}
private def notifySeeks() {
notifyAll(makeMessage("reload_seeks"))
}

View file

@ -7,7 +7,7 @@ import scala.concurrent.duration._
import actorApi.{ GetSocketStatus, SocketStatus }
import lila.common.PimpedConfig._
import lila.hub.actorApi.map.Ask
import lila.hub.actorApi.map.{Ask,Tell,TellAll}
import lila.socket.actorApi.GetVersion
import makeTimeout.large
@ -88,13 +88,15 @@ final class Env(
isPlayingSimul = isPlayingSimul)
def receive: Receive = ({
case msg@lila.chat.actorApi.ChatLine(id, line) =>
self ! lila.hub.actorApi.map.Tell(id take 8, msg)
case m: lila.hub.actorApi.game.ChangeFeatured =>
self ! lila.hub.actorApi.map.TellAll(m)
self ! Tell(id take 8, msg)
case msg: lila.hub.actorApi.game.ChangeFeatured =>
self ! TellAll(msg)
case msg: lila.game.actorApi.StartGame =>
self ! Tell(msg.game.id, msg)
}: Receive) orElse socketHubReceive
}),
name = SocketName)
system.lilaBus.subscribe(actor, 'changeFeaturedGame)
system.lilaBus.subscribe(actor, 'changeFeaturedGame, 'startGame)
actor
}

View file

@ -62,7 +62,8 @@ final class JsonView(
"check" -> game.check.map(_.key),
"rematch" -> game.next,
"source" -> game.source.map(sourceJson),
"status" -> statusJson(game.status)),
"status" -> statusJson(game.status),
"tournamentId" -> game.tournamentId),
"clock" -> game.clock.map(clockJson),
"correspondence" -> game.correspondenceClock.map(correspondenceJson),
"player" -> Json.obj(

View file

@ -10,7 +10,7 @@ import play.api.libs.json._
import actorApi._
import lila.common.LightUser
import lila.game.actorApi.UserStartGame
import lila.game.actorApi.{ StartGame, UserStartGame }
import lila.game.Event
import lila.hub.actorApi.game.ChangeFeatured
import lila.hub.TimeBomb
@ -87,6 +87,11 @@ private[round] final class Socket(
whitePlayer.userId = game.player(White).userId
blackPlayer.userId = game.player(Black).userId
// from lilaBus 'startGame
// sets definitive user ids
// in case one joined after the socket creation
case StartGame(game) => self ! SetGame(game.some)
case PingVersion(uid, v) =>
timeBomb.delay
ping(uid)

View file

@ -76,7 +76,8 @@ final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.C
private val lameSuffixes = List("-", "_")
private val lameUsernames = List(
private val lameUsernames = for {
base <- List(
"hitler",
"fuck",
"penis",
@ -99,7 +100,10 @@ final class DataForm(val captcher: akka.actor.ActorSelection) extends lila.hub.C
"pussy",
"slut",
"whore",
"nazi")
"nazi",
"morteza")
replacement <- List("" -> "", "o" -> "0", "i" -> "1")
} yield base.replace(replacement._1, replacement._2)
}
object DataForm {

View file

@ -80,6 +80,7 @@ trait Positional { self: Config =>
case sit@SituationPlus(Situation(board, _), _) => game.copy(
variant = Variant.FromPosition,
castleLastMoveTime = game.castleLastMoveTime.copy(
lastMove = board.history.lastMove,
castles = board.history.castles
),
turns = sit.turns)

View file

@ -23,16 +23,16 @@ private[setup] final class FriendJoiner(
game.updatePlayer(color, _.withUser(u.id, PerfPicker.mainOrDefault(game)(u.perfs)))
}
for {
p1 GameRepo.setUsers(g1.id, g1.player(_.white).userInfos, g1.player(_.black).userInfos) inject Progress(game, g1)
p2 = p1 map (_.start)
p3 = p2 + Event.RedirectOwner(
_ GameRepo.setUsers(g1.id, g1.player(_.white).userInfos, g1.player(_.black).userInfos)
p1 = Progress(game, g1.start)
p2 = p1 + Event.RedirectOwner(
!color,
p2.game fullIdOf !color,
AnonCookie.json(p2.game, !color))
_ GameRepo save p3
p1.game fullIdOf !color,
AnonCookie.json(p1.game, !color))
_ GameRepo save p2
} yield {
onStart(p3.game.id)
Pov(p3.game, color) -> p3.events
onStart(p2.game.id)
Pov(p2.game, color) -> p2.events
}
} toValid "Can't join started game " + game.id
}

View file

@ -28,7 +28,7 @@ object BSONHandlers {
minPlayers = r int "minPlayers",
variant = r.intO("variant").fold[Variant](Variant.default)(Variant.orDefault),
mode = r.intO("mode").fold[Mode](Mode.default)(Mode.orDefault),
password = r strO "password",
`private` = r boolD "private",
schedule = r.getO[Schedule]("schedule"),
createdAt = r date "createdAt",
createdBy = r str "createdBy")
@ -40,24 +40,11 @@ object BSONHandlers {
"minPlayers" -> o.minPlayers,
"variant" -> o.variant.id,
"mode" -> o.mode.id,
"password" -> o.password,
"private" -> w.boolO(o.`private`),
"schedule" -> o.schedule,
"createdAt" -> w.date(o.createdAt),
"createdBy" -> w.str(o.createdBy))
}
// private implicit val playerHandler = new BSON[Player] {
// def reads(r: BSON.Reader) = Player(
// id = r str "id",
// rating = r int "rating",
// withdraw = r boolD "withdraw",
// score = r intD "score")
// def writes(w: BSON.Writer, o: Player) = BSONDocument(
// "id" -> o.id,
// "rating" -> o.rating,
// "withdraw" -> w.boolO(o.withdraw),
// "score" -> w.intO(o.score))
// }
private implicit val playerBSONHandler = Macros.handler[Player]
private implicit val pairingHandler = new BSON[Pairing] {

View file

@ -45,7 +45,7 @@ final class DataForm(isDev: Boolean) {
"variant" -> number.verifying(Set(Variant.Standard.id, Variant.Chess960.id, Variant.KingOfTheHill.id,
Variant.ThreeCheck.id, Variant.Antichess.id, Variant.Atomic.id) contains _),
"mode" -> optional(number.verifying(Mode.all map (_.id) contains _)),
"password" -> optional(nonEmptyText)
"private" -> optional(text.verifying("on" == _))
)(TournamentSetup.apply)(TournamentSetup.unapply)
.verifying("Invalid clock", _.validClock)
.verifying("Increase tournament duration, or decrease game clock", _.validTiming)
@ -56,12 +56,8 @@ final class DataForm(isDev: Boolean) {
minPlayers = minPlayerDefault,
system = System.default.id,
variant = Variant.Standard.id,
password = none,
`private` = None,
mode = Mode.Casual.id.some)
lazy val joinPassword = Form(single(
"password" -> nonEmptyText
))
}
private[tournament] case class TournamentSetup(
@ -72,7 +68,7 @@ private[tournament] case class TournamentSetup(
system: Int,
variant: Int,
mode: Option[Int],
password: Option[String]) {
`private`: Option[String]) {
def validClock = (clockTime + clockIncrement) > 0

View file

@ -45,6 +45,7 @@ final class Env(
lazy val forms = new DataForm(isDev)
lazy val api = new TournamentApi(
system = system,
sequencers = sequencerMap,
autoPairing = autoPairing,
router = hub.actor.router,
@ -67,11 +68,14 @@ final class Env(
lazy val cached = new Cached
lazy val jsonView = new JsonView(lightUser)
private val socketHub = system.actorOf(
Props(new lila.socket.SocketHubActor.Default[Socket] {
def mkActor(tournamentId: String) = new Socket(
tournamentId = tournamentId,
history = new History(ttl = HistoryMessageTtl),
jsonView = jsonView,
uidTimeout = UidTimeout,
socketTimeout = SocketTimeout,
lightUser = lightUser)
@ -97,7 +101,7 @@ final class Env(
socketHub ? Ask(tourId, GetVersion) mapTo manifest[Int]
val allCreatedSorted =
lila.memo.AsyncCache.single(TournamentRepo.noPasswordCreatedSorted, timeToLive = CreatedCacheTtl)
lila.memo.AsyncCache.single(TournamentRepo.publicCreatedSorted, timeToLive = CreatedCacheTtl)
val promotable =
lila.memo.AsyncCache.single(TournamentRepo.promotable, timeToLive = CreatedCacheTtl)

View file

@ -5,28 +5,8 @@ import org.joda.time.DateTime
// Metadata about running tournaments: who got byed, when a round completed, this sort of things.
sealed abstract class Event(val id: Int) {
def timestamp: DateTime
def encode: RawEvent
}
case class RoundEnd(timestamp: DateTime) extends Event(1) {
def encode = RawEvent(id, timestamp, None)
}
case class RoundEnd(timestamp: DateTime) extends Event(1)
case class Bye(user: String, timestamp: DateTime) extends Event(10) {
def encode = RawEvent(id, timestamp, Some(user))
}
private[tournament] case class RawEvent(
i: Int,
t: DateTime,
u: Option[String]) {
def decode: Option[Event] = roundEnd orElse bye
def roundEnd: Option[RoundEnd] = (i == 1) option RoundEnd(t)
def bye: Option[Bye] = for {
usr <- u
if i == 10
} yield Bye(usr, t)
}
case class Bye(user: String, timestamp: DateTime) extends Event(10)

View file

@ -0,0 +1,113 @@
package lila.tournament
import play.api.libs.json._
import lila.common.LightUser
import lila.common.PimpedJson._
import lila.game.{ Game, GameRepo }
import lila.user.User
final class JsonView(
getLightUser: String => Option[LightUser]) {
def apply(id: String): Fu[JsObject] =
TournamentRepo byId id flatten s"No such tournament: $id" flatMap apply
def apply(tour: Tournament): Fu[JsObject] =
lastGames(tour) map { games =>
val sheets = tour.system.scoringSystem scoreSheets tour
Json.obj(
"id" -> tour.id,
"createdBy" -> tour.createdBy,
"system" -> tour.system.toString.toLowerCase,
"fullName" -> tour.fullName,
"private" -> tour.`private`,
"schedule" -> tour.schedule.map(scheduleJson),
"variant" -> tour.variant.key,
"players" -> tour.rankedPlayers.map((playerJson(sheets) _).tupled),
"winner" -> tour.winner.map(_.id),
"pairings" -> tour.pairings.take(50).map(pairingJson),
"isOpen" -> tour.isOpen,
"isRunning" -> tour.isRunning,
"isFinished" -> tour.isFinished,
"lastGames" -> games.map(gameJson)) ++ specifics(tour)
}
private def specifics(tour: Tournament) = tour match {
case t: Created => Json.obj(
"enoughPlayersToStart" -> t.enoughPlayersToStart,
"enoughPlayersToEarlyStart" -> t.enoughPlayersToEarlyStart,
"missingPlayers" -> (t.missingPlayers != -1).option(t.missingPlayers)
).noNull
case t: Started => Json.obj(
"seconds" -> t.remainingSeconds)
case _ => Json.obj()
}
private def lastGames(tour: Tournament) = tour match {
case t: StartedOrFinished => GameRepo.games(t recentGameIds 4)
case _ => fuccess(Nil)
}
private def scheduleJson(s: Schedule) = Json.obj(
"seconds" -> s.inSeconds)
private def gameUserJson(player: lila.game.Player) = {
val light = player.userId flatMap getLightUser
Json.obj(
"id" -> player.userId,
"name" -> light.map(_.name),
"title" -> light.map(_.title),
"rating" -> player.rating
).noNull
}
private def gameJson(g: Game) = Json.obj(
"id" -> g.id,
"fen" -> (chess.format.Forsyth exportBoard g.toChess.board),
"color" -> g.firstColor.name,
"lastMove" -> ~g.castleLastMoveTime.lastMoveString,
"user1" -> gameUserJson(g.firstPlayer),
"user2" -> gameUserJson(g.secondPlayer))
private def sheetJson(sheet: ScoreSheet) = sheet match {
case s: arena.ScoringSystem.Sheet => Json.obj(
"scores" -> s.scores.take(20).reverse.map { score =>
if (score.flag == arena.ScoringSystem.Normal) Json.arr(score.value)
else Json.arr(score.value, score.flag.toString.toLowerCase)
},
"total" -> s.total,
"fire" -> s.onFire.option(true)
).noNull
case s: swiss.SwissSystem.Sheet => Json.obj(
"scores" -> s.scores.take(20).reverse.map(_.repr),
"total" -> s.totalRepr,
"neustadtl" -> s.neustadtlRepr)
}
private def playerJson(sheets: Map[String, ScoreSheet])(rank: Int, p: Player) = {
val light = getLightUser(p.id)
Json.obj(
"rank" -> rank,
"id" -> p.id,
"username" -> light.map(_.name),
"title" -> light.map(_.title),
"rating" -> p.rating,
"withdraw" -> p.withdraw.option(true),
"score" -> p.score,
"sheet" -> sheets.get(p.id).map(sheetJson)).noNull
}
private def pairingUserJson(userId: String) = {
val name = getLightUser(userId).fold(userId)(_.name)
if (name == userId) Json.arr(userId)
else Json.arr(userId, name)
}
private def pairingJson(p: Pairing) = Json.obj(
"gameId" -> p.gameId,
"status" -> p.status.id,
"user1" -> pairingUserJson(p.user1),
"user2" -> pairingUserJson(p.user2),
"winner" -> p.winner)
}

View file

@ -60,7 +60,6 @@ private[tournament] final class Organizer(
if (!tour.isAlmostFinished) {
withUserIds(tour.id) { ids =>
(tour.activeUserIds intersect ids) |> { users =>
tour.system.pairingSystem.createPairings(tour, users) onSuccess {
case (pairings, events) =>
pairings.toNel foreach { pairings =>

View file

@ -7,6 +7,8 @@ case class Schedule(
speed: Schedule.Speed,
at: DateTime) {
def inSeconds: Int = (at.getSeconds - nowSeconds).toInt
def name = s"${freq.toString} ${speed.toString}"
def sameSpeed(other: Schedule) = speed == other.speed

View file

@ -18,6 +18,7 @@ import lila.socket.{ SocketActor, History, Historical }
private[tournament] final class Socket(
tournamentId: String,
val history: History[Messadata],
jsonView: JsonView,
lightUser: String => Option[LightUser],
uidTimeout: Duration,
socketTimeout: Duration) extends SocketActor[Member](uidTimeout) with Historical[Member, Messadata] {
@ -41,10 +42,6 @@ private[tournament] final class Socket(
case Reload => notifyReload
case Start => notifyVersion("start", JsNull, Messadata())
case ReloadPage => notifyVersion("reloadPage", JsNull, Messadata())
case WithUserIds(f) => f(userIds)
case PingVersion(uid, v) => {
@ -91,7 +88,9 @@ private[tournament] final class Socket(
case NotifyReload =>
delayedReloadNotification = false
notifyAll(makeMessage("reload"))
jsonView(tournamentId) foreach { obj =>
notifyVersion("reload", obj, Messadata())
}
}
def notifyCrowd {
@ -104,7 +103,9 @@ private[tournament] final class Socket(
def notifyReload {
if (!delayedReloadNotification) {
delayedReloadNotification = true
context.system.scheduler.scheduleOnce(1 second, self, NotifyReload)
// keep the delay low for immediate response to join/withdraw,
// but still debounce to avoid tourney start message rush
context.system.scheduler.scheduleOnce(50 millis, self, NotifyReload)
}
}

View file

@ -15,7 +15,7 @@ private[tournament] case class Data(
minPlayers: Int,
variant: Variant,
mode: Mode,
password: Option[String],
`private`: Boolean,
schedule: Option[Schedule],
createdAt: DateTime,
createdBy: String)
@ -31,6 +31,7 @@ sealed trait Tournament {
def isOpen: Boolean = false
def isRunning: Boolean = false
def isFinished: Boolean = false
def `private`: Boolean = data.`private`
def name = data.name
def fullName = s"$name $system"
@ -44,8 +45,6 @@ sealed trait Tournament {
def mode = data.mode
def speed = Speed(clock.chessClock.some)
def rated = mode.rated
def password = data.password
def hasPassword = password.isDefined
def schedule = data.schedule
def scheduled = data.schedule.isDefined
@ -69,6 +68,7 @@ sealed trait Tournament {
def isActive(user: User): Boolean = isActive(user.id)
def isActive(user: Option[User]): Boolean = ~user.map(isActive)
def missingPlayers = minPlayers - players.size
def rankedPlayers: RankedPlayers = system.scoringSystem.rank(this, players)
def createdBy = data.createdBy
def createdAt = data.createdAt
@ -90,16 +90,14 @@ sealed trait Enterable extends Tournament {
def withPlayers(s: Players): Enterable
def join(user: User, pass: Option[String]): Valid[Enterable]
def join(user: User): Valid[Enterable]
def withdraw(userId: String): Valid[Enterable]
def joinNew(user: User, pass: Option[String]): Valid[Enterable] = contains(user).fold(
def joinNew(user: User): Valid[Enterable] = contains(user).fold(
!!("User %s is already part of the tournament" format user.id),
(pass != password).fold(
!!("Invalid tournament password"),
withPlayers(players :+ Player.make(user, perfLens)).success
))
withPlayers(players :+ Player.make(user, perfLens)).success
)
def ejectCheater(userId: String): Option[Enterable] =
activePlayers.find(_.id == userId) map { player =>
@ -111,14 +109,11 @@ sealed trait Enterable extends Tournament {
}
sealed trait StartedOrFinished extends Tournament {
type RankedPlayers = List[(Int, Player)]
def startedAt: DateTime
def withPlayers(s: Players): StartedOrFinished
def refreshPlayers: StartedOrFinished
def rankedPlayers: RankedPlayers = system.scoringSystem.rank(this, players)
def winner = players.headOption
def winnerUserId = winner map (_.id)
@ -161,7 +156,7 @@ case class Created(
def asScheduled = schedule map { Scheduled(this, _) }
def join(user: User, pass: Option[String]) = joinNew(user, pass)
def join(user: User) = joinNew(user)
}
case class Scheduled(tour: Created, schedule: Schedule) {
@ -243,16 +238,14 @@ case class Started(
def withPlayers(s: Players) = copy(players = s)
def refreshPlayers = withPlayers(Player refresh this)
def join(user: User, pass: Option[String]) = joinNew(user, pass) orElse joinBack(user, pass)
def join(user: User) = joinNew(user) orElse joinBack(user)
private def joinBack(user: User, pass: Option[String]) = withdrawnPlayers.find(_ is user) match {
private def joinBack(user: User) = withdrawnPlayers.find(_ is user) match {
case None => !!("User %s is already part of the tournament" format user.id)
case Some(player) => (pass != password).fold(
!!("Invalid tournament password"),
withPlayers(players map {
case p if p is player => p.unWithdraw
case p => p
}).success)
case Some(player) => withPlayers(players map {
case p if p is player => p.unWithdraw
case p => p
}).success
}
}
@ -282,7 +275,7 @@ object Tournament {
system: System,
variant: Variant,
mode: Mode,
password: Option[String]): Created = {
`private`: Boolean): Created = {
val tour = Created(
id = Random nextStringUppercase 8,
data = Data(
@ -293,7 +286,7 @@ object Tournament {
createdAt = DateTime.now,
variant = variant,
mode = mode,
password = password,
`private` = `private`,
minutes = minutes,
schedule = None,
minPlayers = minPlayers),
@ -311,7 +304,7 @@ object Tournament {
createdAt = DateTime.now,
variant = Variant.Standard,
mode = Mode.Rated,
password = None,
`private` = false,
minutes = minutes,
schedule = Some(sched),
minPlayers = 0),

View file

@ -1,13 +1,15 @@
package lila.tournament
import akka.actor.{ ActorRef, ActorSelection }
import akka.actor.{ Props, ActorRef, ActorSelection, ActorSystem }
import akka.pattern.{ ask, pipe }
import chess.{ Mode, Variant }
import org.joda.time.DateTime
import play.api.libs.json._
import scala.concurrent.duration._
import scalaz.NonEmptyList
import actorApi._
import lila.common.Debouncer
import lila.db.api._
import lila.game.{ Game, GameRepo }
import lila.hub.actorApi.lobby.ReloadTournaments
@ -20,6 +22,7 @@ import lila.user.{ User, UserRepo }
import makeTimeout.short
private[tournament] final class TournamentApi(
system: ActorSystem,
sequencers: ActorRef,
autoPairing: AutoPairing,
router: ActorSelection,
@ -55,19 +58,18 @@ private[tournament] final class TournamentApi(
minutes = setup.minutes,
minPlayers = setup.minPlayers,
mode = setup.mode.fold(Mode.default)(Mode.orDefault),
password = setup.password,
`private` = setup.`private`.isDefined,
system = System orDefault setup.system,
variant = Variant orDefault setup.variant)
TournamentRepo.insert(created).void >>-
(withdrawIds foreach socketReload) >>-
reloadSiteSocket >>-
lobbyReload inject created
publish() inject created
}
def createScheduled(schedule: Schedule): Funit =
(Schedule durationFor schedule) ?? { minutes =>
val created = Tournament.schedule(schedule, minutes)
TournamentRepo.insert(created).void >>- reloadSiteSocket >>- lobbyReload
TournamentRepo.insert(created).void >>- publish()
}
def startIfReady(created: Created) {
@ -84,9 +86,8 @@ private[tournament] final class TournamentApi(
case Some(created) =>
val started = created.start
TournamentRepo.update(started).void >>-
sendTo(started.id, Start) >>-
reloadSiteSocket >>-
lobbyReload
sendTo(started.id, Reload) >>-
publish()
case None => fufail("Can't start missing tournament " + oldTour.id)
}
}
@ -95,18 +96,18 @@ private[tournament] final class TournamentApi(
def wipeEmpty(created: Created): Funit = created.isEmpty ?? doWipe(created)
private def doWipe(created: Created): Funit =
TournamentRepo.remove(created).void >>- reloadSiteSocket >>- lobbyReload
TournamentRepo.remove(created).void >>- publish()
def finish(oldTour: Started) {
sequence(oldTour.id) {
TournamentRepo startedById oldTour.id flatMap {
case Some(started) =>
if (started.pairings.isEmpty) TournamentRepo.remove(started).void >>- reloadSiteSocket >>- lobbyReload
if (started.pairings.isEmpty) TournamentRepo.remove(started).void >>- publish()
else started.readyToFinish ?? {
val finished = started.finish
TournamentRepo.update(finished).void >>-
sendTo(finished.id, ReloadPage) >>-
reloadSiteSocket >>-
sendTo(finished.id, Reload) >>-
publish() >>-
finished.players.filter(_.score > 0).map { p =>
UserRepo.incToints(p.id, p.score)
}.sequenceFu
@ -116,16 +117,15 @@ private[tournament] final class TournamentApi(
}
}
def join(oldTour: Enterable, me: User, password: Option[String]) {
def join(oldTour: Enterable, me: User) {
sequence(oldTour.id) {
TournamentRepo enterableById oldTour.id flatMap {
case Some(tour) => (tour.join(me, password)).future flatMap { tour2 =>
case Some(tour) => tour.join(me).future flatMap { tour2 =>
TournamentRepo withdraw me.id flatMap { withdrawIds =>
TournamentRepo.update(tour2).void >>- {
sendTo(tour.id, Joining(me.id))
(tour.id :: withdrawIds) foreach socketReload
reloadSiteSocket
lobbyReload
publish()
import lila.hub.actorApi.timeline.{ Propagate, TourJoin }
timeline ! (Propagate(TourJoin(me.id, tour2.id, tour2.fullName)) toFollowersOf me.id)
}
@ -141,7 +141,7 @@ private[tournament] final class TournamentApi(
TournamentRepo byId oldTour.id flatMap {
case Some(created: Created) => (created withdraw userId).fold(
err => fulogwarn(err.shows),
tour2 => TournamentRepo.update(tour2).void >>- socketReload(tour2.id) >>- reloadSiteSocket >>- lobbyReload
tour2 => TournamentRepo.update(tour2).void >>- socketReload(tour2.id) >>- publish()
)
case Some(started: Started) => (started withdraw userId).fold(
err => fufail(err.shows),
@ -150,7 +150,7 @@ private[tournament] final class TournamentApi(
roundMap ! Tell(povRef.gameId, ResignColor(povRef.color))
}) >>-
socketReload(tour2.id) >>-
reloadSiteSocket
publish()
)
case _ => fufail("Cannot withdraw from finished or missing tournament " + oldTour.id)
}
@ -175,14 +175,6 @@ private[tournament] final class TournamentApi(
}
}
private def lobbyReload {
TournamentRepo.promotable foreach { tours =>
renderer ? TournamentTable(tours) map {
case view: play.twirl.api.Html => ReloadTournaments(view.body)
} pipeToSelection lobby
}
}
def ejectCheater(userId: String) {
TournamentRepo.allEnterable foreach {
_ foreach { oldTour =>
@ -207,9 +199,18 @@ private[tournament] final class TournamentApi(
sendTo(tourId, Reload)
}
private val reloadMessage = SendToFlag("tournament", Json.obj("t" -> "reload"))
private def reloadSiteSocket {
site ! reloadMessage
private object publish {
private val siteMessage = SendToFlag("tournament", Json.obj("t" -> "reload"))
private val debouncer = system.actorOf(Props(new Debouncer(2 seconds, {
(_: Debouncer.Nothing) =>
site ! siteMessage
TournamentRepo.promotable foreach { tours =>
renderer ? TournamentTable(tours) map {
case view: play.twirl.api.Html => ReloadTournaments(view.body)
} pipeToSelection lobby
}
})))
def apply() { debouncer ! Debouncer.Nothing }
}
private def sendTo(tourId: String, msg: Any) {

View file

@ -41,11 +41,13 @@ object TournamentRepo {
"schedule" -> BSONDocument("$exists" -> false)
)).toList[Created](None)
def createdIncludingScheduled: Fu[List[Created]] = coll.find(createdSelect).toList[Created](None)
def started: Fu[List[Started]] =
coll.find(startedSelect).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
def noPasswordStarted: Fu[List[Started]] =
coll.find(startedSelect ++ BSONDocument("password" -> BSONDocument("$exists" -> false))).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
def publicStarted: Fu[List[Started]] =
coll.find(startedSelect ++ BSONDocument("private" -> BSONDocument("$exists" -> false))).sort(BSONDocument("createdAt" -> -1)).toList[Started](None)
def finished(limit: Int): Fu[List[Finished]] =
coll.find(finishedSelect).sort(BSONDocument("startedAt" -> -1)).toList[Finished](limit.some)
@ -57,18 +59,18 @@ object TournamentRepo {
)
)
def noPasswordCreatedSorted: Fu[List[Created]] = coll.find(
allCreatedSelect ++ BSONDocument("password" -> BSONDocument("$exists" -> false))
def publicCreatedSorted: Fu[List[Created]] = coll.find(
allCreatedSelect ++ BSONDocument("private" -> BSONDocument("$exists" -> false))
).sort(BSONDocument("schedule.at" -> 1, "createdAt" -> 1)).toList[Created](None)
def allCreated: Fu[List[Created]] = coll.find(allCreatedSelect).toList[Created](None)
def recentlyStartedSorted: Fu[List[Started]] = coll.find(startedSelect ++ BSONDocument(
"password" -> BSONDocument("$exists" -> false),
"private" -> BSONDocument("$exists" -> false),
"startedAt" -> BSONDocument("$gt" -> (DateTime.now minusMinutes 20))
)).sort(BSONDocument("schedule.at" -> 1, "createdAt" -> 1)).toList[Started](none)
def promotable: Fu[List[Enterable]] = noPasswordCreatedSorted zip recentlyStartedSorted map {
def promotable: Fu[List[Enterable]] = publicCreatedSorted zip recentlyStartedSorted map {
case (created, started) => created ::: started
}
@ -89,7 +91,7 @@ object TournamentRepo {
def exists(id: String) = coll.db command Count(coll.name, BSONDocument("_id" -> id).some) map (0 !=)
def withdraw(userId: String): Fu[List[String]] = for {
createds created
createds createdIncludingScheduled
createdIds (createds map (_ withdraw userId) collect {
case scalaz.Success(tour) => update(tour) inject tour.id
}).sequenceFu

View file

@ -24,9 +24,7 @@ private[tournament] case class Join(
user: Option[User],
version: Int)
private[tournament] case class Talk(tourId: String, u: String, t: String, troll: Boolean)
private[tournament] case object Start
private[tournament] case object Reload
private[tournament] case object ReloadPage
private[tournament] case class StartGame(game: Game)
private[tournament] case class Joining(userId: String)
private[tournament] case class Connected(enumerator: JsEnumerator, member: Member)

View file

@ -6,6 +6,8 @@ package object tournament extends PackageObject with WithPlay with WithSocket {
private[tournament]type Players = List[tournament.Player]
private[tournament]type RankedPlayers = List[(Int, Player)]
private[tournament]type Pairings = List[tournament.Pairing]
private[tournament]type Events = List[tournament.Event]

View file

@ -54,8 +54,7 @@ trait UserRepo {
def byOrderedIds(ids: Iterable[ID]): Fu[List[User]] = $find byOrderedIds ids
def enabledByIds(ids: Seq[ID]): Fu[List[User]] =
$find(enabledSelect ++ $select.byIds(ids))
def enabledByIds(ids: Seq[ID]): Fu[List[User]] = $find(enabledSelect ++ $select.byIds(ids))
def enabledById(id: ID): Fu[Option[User]] =
$find.one(enabledSelect ++ $select.byId(id))

View file

@ -45,7 +45,7 @@ object Dependencies {
val elastic4s = "com.sksamuel.elastic4s" %% "elastic4s" % "1.3.2"
val RM = "org.reactivemongo" %% "reactivemongo" % "0.10.5.0.akka23"
val PRM = "org.reactivemongo" %% "play2-reactivemongo" % "0.10.5.0.akka23"
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.2.2-THIB"
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.2.3-THIB"
val prismic = "io.prismic" %% "scala-kit" % "1.2.4"
object play {

View file

@ -367,6 +367,7 @@ lichess.storage = {
};
};
var nbEl = document.querySelector('#nb_connected_players > strong');
lichess.socket = null;
lichess.idleTime = 20 * 60 * 1000;
$.extend(true, lichess.StrongSocket.defaults, {
@ -381,16 +382,15 @@ lichess.storage = {
$('#friend_box').friends('leaves', name);
},
n: function(e) {
var $tag = $('#nb_connected_players > strong');
if ($tag.length && e) {
var prev = parseInt($tag.text(), 10) || Math.max(0, (e - 10));
var k = 6;
if (nbEl && e) {
var prev = parseInt(nbEl.textContent, 10) || Math.max(0, (e - 10));
var k = 5;
var interv = lichess.socket.pingInterval() / k;
$.fp.range(k).forEach(function(it) {
setTimeout(function() {
var val = Math.round(((prev * (k - 1 - it)) + (e * (it + 1))) / k);
if (val != prev) {
$tag.text(val);
nbEl.textContent = val;
prev = val;
}
}, Math.round(it * interv));
@ -416,6 +416,7 @@ lichess.storage = {
$('#tournament_reminder').remove();
return false;
});
$('body').trigger('lichess.content_loaded');
}
},
challengeReminder: function(data) {
@ -567,6 +568,7 @@ lichess.storage = {
else if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse);
else if (lichess.user_analysis) startUserAnalysis(document.getElementById('lichess'), lichess.user_analysis);
else if (lichess.lobby) startLobby(document.getElementById('hooks_wrap'), lichess.lobby);
else if (lichess.tournament) startTournament(document.getElementById('tournament'), lichess.tournament);
$('#lichess').on('click', '.socket-link:not(.disabled)', function() {
lichess.socket.send($(this).data('msg'), $(this).data('data'));
@ -622,6 +624,7 @@ lichess.storage = {
$('#message_notifications_tag').on('click', function() {
$.ajax({
url: $(this).data('href'),
cache: false,
success: function(html) {
$('#message_notifications_display').html(html)
.find('a.mark_as_read').click(function() {
@ -680,6 +683,7 @@ lichess.storage = {
var $themepicker = $('#themepicker');
$.ajax({
url: $(this).data('url'),
cache: false,
success: function(html) {
$themepicker.append(html);
var $body = $('body');
@ -902,6 +906,7 @@ lichess.storage = {
langs = acceptLanguages.split(',');
$.ajax({
url: $links.data('url'),
cache: false,
success: function(list) {
$links.prepend(list.map(function(lang) {
var klass = $.fp.contains(langs, lang[0]) ? 'class="accepted"' : '';
@ -955,22 +960,38 @@ lichess.storage = {
}, 1500);
});
$.lazy = function(factory) {
var loaded = {};
return function(key) {
if (!loaded[key]) loaded[key] = factory(key);
return loaded[key];
};
};
$.sound = (function() {
var baseUrl = $('body').data('sound-dir') + '/';
var a = new Audio();
var hasOgg = !!a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"');
var hasMp3 = !!a.canPlayType && a.canPlayType('audio/mpeg;');
var ext = hasOgg ? 'ogg' : 'mp3';
var audio = {
dong: new Audio(baseUrl + 'dong2.' + ext),
moveW: new Audio(baseUrl + 'move3.' + ext),
moveB: new Audio(baseUrl + 'move3.' + ext),
take: new Audio(baseUrl + 'take2.' + ext),
lowtime: new Audio(baseUrl + 'lowtime.' + ext)
var names = {
dong: 'dong2',
moveW: 'move3',
moveB: 'move3',
take: 'take2',
lowtime: 'lowtime'
};
var volumes = {
lowtime: 0.6
lowtime: 0.5
};
var computeVolume = function(k, v) {
return v * (volumes[k] || 1);
};
var get = new $.lazy(function(k) {
var audio = new Audio(baseUrl + names[k] + '.' + ext);
audio.volume = computeVolume(k, getVolume());
return audio;
});
var canPlay = hasOgg || hasMp3;
var $control = $('#sound_control');
var $toggle = $('#sound_state');
@ -984,18 +1005,18 @@ lichess.storage = {
var play = {
move: function(white) {
if (shouldPlay()) {
if (white) audio.moveW.play();
else audio.moveB.play();
if (white) get('moveW').play();
else get('moveB').play();
}
},
take: function() {
if (shouldPlay()) audio.take.play();
if (shouldPlay()) get('take').play();
},
dong: function() {
if (shouldPlay()) audio.dong.play();
if (shouldPlay()) get('dong').play();
},
lowtime: function() {
if (shouldPlay()) audio.lowtime.play();
if (shouldPlay()) get('lowtime').play();
}
};
var getVolume = function() {
@ -1003,15 +1024,14 @@ lichess.storage = {
};
var setVolume = function(v) {
lichess.storage.set('sound-volume', v);
Object.keys(audio).forEach(function(k) {
audio[k].volume = v * (volumes[k] ? volumes[k] : 1);
Object.keys(names).forEach(function(k) {
get(k).volume = computeVolume(k, v);
});
};
var manuallySetVolume = $.fp.debounce(function(v) {
setVolume(v);
play.move(true);
}, 100);
setVolume(getVolume());
if (canPlay) {
$toggle.click(function() {
$control.add($toggle).toggleClass('sound_state_on', !enabled());
@ -1059,9 +1079,8 @@ lichess.storage = {
var startTournamentClock = function() {
$("div.game_tournament div.clock").each(function() {
$(this).clock({
time: $(this).data("time"),
showTenths: false
}).clock("start");
time: parseFloat($(this).data("time"))
});
});
};
@ -1102,10 +1121,14 @@ lichess.storage = {
},
end: function() {
var url = (data.tv ? cfg.routes.Tv.side : cfg.routes.Round.sideWatcher)(data.game.id, data.player.color).url;
$.get(url, function(html) {
$('#site_header div.side').replaceWith(html);
$('body').trigger('lichess.content_loaded');
startTournamentClock();
$.ajax({
url: url,
cache: false,
success: function(html) {
$('#site_header div.side').replaceWith(html);
$('body').trigger('lichess.content_loaded');
startTournamentClock();
}
});
},
checkCount: function(e) {
@ -1345,91 +1368,42 @@ lichess.storage = {
$.widget("lichess.clock", {
_create: function() {
var o = this.options;
this.options.time = parseFloat(this.options.time) * 1000;
this.options.barTime = parseFloat(this.options.barTime) * 1000;
this.options.emerg = parseFloat(this.options.emerg) * 1000;
$.extend(this.options, {
state: 'ready'
});
var self = this;
this.options.time = this.options.time * 1000;
this.$time = this.element.find('>div.time');
this.$bar = this.element.find('>div.bar>span');
this._show();
var end_time = new Date().getTime() + self.options.time;
var tick = function() {
var current_time = Math.round(end_time - new Date().getTime());
if (current_time <= 0) {
clearInterval(self.options.interval);
current_time = 0;
}
self.options.time = current_time;
self._show();
};
self.options.interval = setInterval(tick, 1000);
tick();
},
destroy: function() {
this.stop();
$.Widget.prototype.destroy.apply(this);
},
start: function() {
var self = this;
self.options.state = 'running';
self.element.addClass('running');
var end_time = new Date().getTime() + self.options.time;
self.options.interval = setInterval(function() {
if (self.options.state == 'running') {
var current_time = Math.round(end_time - new Date().getTime());
if (current_time <= 0) {
clearInterval(self.options.interval);
current_time = 0;
}
self.options.time = current_time;
self._show();
//If the timer completed, fire the buzzer callback
if (current_time === 0 && $.isFunction(self.options.buzzer)) self.options.buzzer(self.element);
} else {
clearInterval(self.options.interval);
}
},
100);
},
setTime: function(time) {
this.options.time = parseFloat(time) * 1000;
this._show();
},
getSeconds: function() {
return Math.round(this.options.time / 1000);
},
stop: function() {
clearInterval(this.options.interval);
this.options.state = 'stop';
this.element.removeClass('running');
this.element.toggleClass('outoftime', this.options.time <= 0);
},
_show: function() {
var html = this._formatDate(new Date(this.options.time));
if (html != this.$time.html()) {
this.$time.html(html);
this.element.toggleClass('emerg', this.options.time < this.options.emerg);
}
if (this.options.showBar) {
var barWidth = Math.max(0, Math.min(100, (this.options.time / this.options.barTime) * 100));
this.$bar.css('width', barWidth + '%');
}
this.$time.html(this._formatDate(new Date(this.options.time)));
},
_formatDate: function(date) {
var minutes = this._prefixInteger(date.getUTCMinutes(), 2);
var seconds = this._prefixInteger(date.getSeconds(), 2);
var b = function(x) {
return '<b>' + x + '</b>';
};
if (this.options.showTenths && this.options.time < 10000) {
tenths = Math.floor(date.getMilliseconds() / 100);
return b(minutes) + ':' + b(seconds) + '<span>.' + b(tenths) + '</span>';
} else if (this.options.time >= 3600000) {
if (this.options.time >= 3600000) {
var hours = this._prefixInteger(date.getUTCHours(), 2);
return b(hours) + ':' + b(minutes) + ':' + b(seconds);
} else {
return b(minutes) + ':' + b(seconds);
}
},
_prefixInteger: function(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
@ -1521,13 +1495,13 @@ lichess.storage = {
'/lobby/socket/v1',
cfg.data.version, {
receive: function(t, d) {
if (lobby) lobby.socketReceive(t, d);
else console.log('missed', t, d);
lobby.socketReceive(t, d);
},
events: {
reload_timeline: function() {
$.ajax({
url: $("#timeline").data('href'),
cache: false,
success: function(html) {
$('#timeline').html(html);
$('body').trigger('lichess.content_loaded');
@ -1551,7 +1525,9 @@ lichess.storage = {
},
reload_forum: function() {
setTimeout(function() {
$.ajax($newposts.data('url'), {
$.ajax({
url: $newposts.data('url'),
cache: false,
success: function(data) {
$newposts.find('ol').html(data).end().scrollTop(0);
$('body').trigger('lichess.content_loaded');
@ -1843,6 +1819,7 @@ lichess.storage = {
$('.lichess_overboard').remove();
$.ajax({
url: $(this).attr('href'),
cache: false,
success: function(html) {
$('.lichess_overboard').remove();
$('#hooks_wrap').prepend(html);
@ -1868,91 +1845,43 @@ lichess.storage = {
$(function() {
var $wrap = $('#tournament');
if (!$wrap.length) return;
if (!lichess.StrongSocket.available) return;
if (typeof _ld_ == "undefined") {
var $tournamentList = $('#tournament_list');
if ($tournamentList.length) {
// handle tournament list
lichess.StrongSocket.defaults.params.flag = "tournament";
lichess.StrongSocket.defaults.events.reload = function() {
$wrap.load($wrap.data("href"), function() {
$tournamentList.load($tournamentList.data("href"), function() {
$('body').trigger('lichess.content_loaded');
});
};
return;
}
});
$('body').data('tournament-id', _ld_.tournament.id);
function startTournament(element, cfg) {
$('body').data('tournament-id', cfg.data.id);
var $watchers = $("div.watchers").watchers();
var $chat = $('#chat');
if ($chat.length) $chat.chat({
if (lichess_chat) $('#chat').chat({
messages: lichess_chat
});
function startClock() {
$("div.tournament_clock").each(function() {
$(this).clock({
time: $(this).data("time")
}).clock("start");
});
}
startClock();
function drawBars() {
$wrap.find('table.standing').each(function() {
var $bars = $(this).find('.bar');
var max = Math.max.apply(Math, $bars.map(function() {
return parseInt(this.getAttribute('data-value'));
}));
$bars.each(function() {
var width = Math.ceil((parseInt($(this).data('value')) * 100) / max);
$(this).css('width', width + '%');
});
});
}
drawBars();
function reload() {
$.ajax({
url: $wrap.data('href'),
success: function(html) {
var $tour = $(html);
if ($wrap.find('table.standing').length) {
// started
$wrap.find('table.standing thead').replaceWith($tour.find('table.standing thead'));
$wrap.find('table.standing tbody').replaceWith($tour.find('table.standing tbody'));
drawBars();
$wrap.find('div.pairings').replaceWith($tour.find('div.pairings'));
$wrap.find('div.game_list').replaceWith($tour.find('div.game_list'));
} else {
// created
$wrap.find('table.user_list').replaceWith($tour.find('table.user_list'));
}
$('body').trigger('lichess.content_loaded');
}
});
}
lichess.socket = new lichess.StrongSocket($wrap.data("socket-url"), _ld_.version, {
events: {
start: reload,
reload: reload,
reloadPage: function() {
location.reload();
var tournament;
lichess.socket = new lichess.StrongSocket(
'/tournament/' + cfg.data.id + '/socket/v1', cfg.socketVersion, {
receive: function(t, d) {
tournament.socketReceive(t, d)
},
crowd: function(data) {
$watchers.watchers("set", data);
events: {
crowd: function(data) {
$watchers.watchers("set", data);
}
},
options: {
name: "tournament"
}
},
options: {
name: "tournament"
}
});
});
});
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
tournament = LichessTournament(element, cfg);
};
////////////////
// analyse.js //
@ -2091,6 +2020,20 @@ lichess.storage = {
});
return false;
});
$panels.find('div.pgn').click(function() {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText($(this)[0]);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents($(this)[0]);
selection.removeAllRanges();
selection.addRange(range);
}
});
}
////////////////
@ -2140,4 +2083,16 @@ lichess.storage = {
} else startListening();
}, 5000);
};
$.modal = function(html) {
var $wrap = $('<div id="modal-wrap">').html(html.clone().show());
var $overlay = $('<div id="modal-overlay">').html($wrap);
$overlay.one('click', function() {
$('#modal-overlay').remove();
});
$wrap.click(function(e) {
e.stopPropagation();
});
$('body').prepend($overlay);
};
})();

File diff suppressed because one or more lines are too long

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
</svg>

Before

Width:  |  Height:  |  Size: 179 B

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
</svg>

Before

Width:  |  Height:  |  Size: 179 B

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
</svg>

Before

Width:  |  Height:  |  Size: 179 B

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
</svg>

Before

Width:  |  Height:  |  Size: 179 B

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M2082 838c0,0 621,269 458,883 -163,614 -570,260 -570,260l113 -1142 0 0z"/>
<rect class="fil0" x="1010" y="3594" width="2217" height="204"/>
<rect class="fil0" x="1179" y="3741" width="1885" height="153"/>
<rect class="fil0" x="1006" y="3860" width="2217" height="204"/>
<polygon class="fil0" points="1792,1909 2390,1909 2891,3613 1336,3613 "/>
<rect class="fil0" x="1497" y="1866" width="1200" height="152"/>
<path class="fil0" d="M2097 839c0,0 -623,269 -459,883 164,614 572,260 572,260l-113 -1142 0 0z"/>
<ellipse class="fil0" cx="2092" cy="804" rx="174" ry="143"/>
<path class="fil1" d="M2177 679c0,0 43,54 43,115 0,61 -14,118 -14,118 0,0 153,-118 -29,-233z"/>
<polygon class="fil1" points="1179,3798 3064,3798 3064,3860 1179,3860 "/>
<path class="fil1" d="M2206 912c0,0 221,144 300,333 79,189 66,259 50,397 -16,138 -68,224 -68,224l209 0 0 152 -321 0 -17 -152c0,0 -2,-42 -12,-77 -10,-36 93,-85 104,-240 10,-155 1,-253 -53,-368 -53,-116 -194,-269 -194,-269z"/>
<polygon class="fil1" points="1752,2018 2422,2018 2887,3594 3227,3594 3227,3798 3064,3798 3064,3860 3223,3860 3223,4064 2743,4064 2743,3860 2743,3798 2658,3594 2347,2164 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1554,2064 2652,2073 2968,3725 1280,3702 "/>
<rect class="fil0" x="1010" y="3672" width="2217" height="204"/>
<rect class="fil0" x="1179" y="3819" width="1885" height="153"/>
<rect class="fil0" x="1006" y="3939" width="2217" height="204"/>
<path class="fil1" d="M1612 2183l1061 0 288 1489 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -150,-1101 -150,-1101l-939 -145z"/>
<polygon class="fil1" points="1181,3874 3066,3874 3066,3936 1181,3936 "/>
<rect class="fil0" x="1300" y="1963" width="1638" height="220"/>
<path class="fil0" d="M1490 2042c0,0 -241,-328 -199,-572 50,-285 382,-408 875,-435 148,-8 98,1007 98,1007l-774 0z"/>
<path class="fil0" d="M2726 2042c0,0 241,-328 199,-572 -50,-285 -382,-408 -875,-435 -148,-8 -98,1007 -98,1007l774 0z"/>
<path class="fil0" d="M1772 1087c0,0 253,-63 226,-169 -27,-106 114,-19 114,-19l10 146 -350 43z"/>
<path class="fil0" d="M2384 1079c0,0 -154,-75 -159,-161 -5,-86 -114,-19 -114,-19l-10 147 282 34z"/>
<ellipse class="fil0" cx="1967" cy="711" rx="91" ry="89"/>
<ellipse class="fil0" cx="1995" cy="848" rx="64" ry="62"/>
<ellipse class="fil0" cx="2250" cy="711" rx="91" ry="89"/>
<ellipse class="fil0" cx="2222" cy="849" rx="64" ry="62"/>
<ellipse class="fil0" cx="2112" cy="632" rx="97" ry="101"/>
<polygon class="fil0" points="2047,957 1967,711 2112,632 2250,711 2176,962 "/>
<path class="fil1" d="M2226 918c0,0 -49,94 18,144 66,50 231,81 319,139 89,58 240,120 241,284 2,164 -33,236 -139,351 -107,116 -175,126 -175,126l288 0c0,0 87,-152 110,-226 23,-74 52,-167 36,-267 -16,-100 -90,-225 -206,-280 -116,-56 -86,-51 -170,-75 -83,-24 -94,-26 -103,-28 -9,-2 -95,-27 -95,-27 0,0 -45,-25 -70,-51 -25,-26 -55,-91 -55,-91z"/>
<polygon class="fil1" points="2490,1963 2524,2183 2938,2183 2938,1963 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M1040 1733c-165,151 -145,140 -72,271 72,131 241,188 400,142 158,-46 506,-183 633,-154 127,30 159,91 167,157 8,66 -186,227 -400,433 -214,205 -402,243 -373,671 30,428 19,525 -42,592 -61,67 1074,263 1221,196 147,-68 543,-55 465,-460 -78,-406 -208,-900 -10,-1238 198,-338 398,-1042 -527,-1423 -925,-381 -558,35 -648,83 -90,47 -121,57 -175,83 -98,47 -462,486 -639,647z"/>
<rect class="fil0" x="1102" y="3595" width="2217" height="204"/>
<rect class="fil0" x="1271" y="3742" width="1885" height="153"/>
<rect class="fil0" x="1098" y="3861" width="2217" height="204"/>
<path class="fil1" d="M1827 2000c0,0 85,-124 262,-131 176,-7 75,-331 75,-331 0,0 227,293 227,444 0,151 -239,217 -239,217 0,0 -30,-182 -324,-200z"/>
<path class="fil1" d="M2831 1100c0,0 425,238 221,842 -204,604 -560,915 -498,1226 62,311 9,364 31,391 22,27 93,27 195,-4 102,-31 84,44 93,89 9,44 72,421 72,421l369 0 0 -204 -159 0 0 -63 163 0 0 -204 -277 0c0,0 -95,-448 -116,-728 -21,-280 58,-454 170,-653 111,-200 231,-777 -265,-1113z"/>
<polygon class="fil1" points="1272,3798 3157,3798 3157,3861 1272,3861 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<rect class="fil0" x="1165" y="3595" width="1907" height="204"/>
<rect class="fil0" x="1310" y="3742" width="1621" height="153"/>
<rect class="fil0" x="1162" y="3862" width="1907" height="204"/>
<polygon class="fil1" points="1311,3799 2932,3799 2932,3861 1311,3861 "/>
<polygon class="fil0" points="1871,2415 2167,2411 2167,3693 1550,3684 "/>
<polygon class="fil0" points="2352,2415 2088,2411 2087,3693 2705,3684 "/>
<rect class="fil0" x="1566" y="2263" width="1115" height="158"/>
<ellipse class="fil0" cx="2101" cy="1905" rx="414" ry="433"/>
<path class="fil1" d="M2191 1482c0,0 195,147 218,349 36,315 -162,418 -446,351 -143,-34 -244,-110 -231,-82 0,0 21,51 59,90 37,39 82,75 82,75l457 0c0,0 74,-41 114,-117 40,-77 72,-137 72,-244 0,-107 -41,-199 -68,-237 -27,-38 -56,-71 -105,-114 -48,-44 -152,-71 -152,-71z"/>
<polygon class="fil1" points="2286,2263 2355,2421 2681,2421 2681,2263 "/>
<polygon class="fil1" points="1770,2421 2355,2421 2701,3663 2446,3663 2277,2568 "/>
<polygon class="fil1" points="2678,3595 2748,4065 3069,4065 3069,3862 2932,3861 2932,3799 3072,3799 3072,3595 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1656,1982 2548,1985 2968,3643 1280,3620 "/>
<rect class="fil0" x="1010" y="3590" width="2217" height="204"/>
<rect class="fil0" x="1179" y="3737" width="1885" height="153"/>
<rect class="fil0" x="1006" y="3857" width="2217" height="204"/>
<path class="fil1" d="M1612 2101l965 0 383 1489 266 0 0 204 -161 0 0 60 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -253,-1117 -253,-1117l-836 -129z"/>
<polygon class="fil1" points="1181,3792 3066,3792 3066,3854 1181,3854 "/>
<rect class="fil0" x="1300" y="1881" width="1638" height="220"/>
<polygon class="fil0" points="1564,1918 1161,1154 1658,1413 1618,730 2096,1286 2128,1918 "/>
<polygon class="fil0" points="2628,1919 3031,1154 2533,1414 2573,730 2095,1287 2076,1919 "/>
<circle class="fil0" cx="1161" cy="1154" r="100"/>
<circle class="fil0" cx="1618" cy="730" r="100"/>
<circle class="fil0" cx="2573" cy="730" r="100"/>
<circle class="fil1" cx="3031" cy="1154" r="100"/>
<path class="fil1" d="M2633 650l-97 173 -240 1058 351 0 337 -639 -42 -42 -409 213 40 -584c0,0 173,-34 60,-180z"/>
<polygon class="fil1" points="2472,1881 2472,2101 2938,2101 2938,1881 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#111111}
.fil0 {fill:#2E2E2E}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1471,2050 2754,2054 2963,3644 1274,3621 "/>
<polygon class="fil0" points="1213,2103 3015,2103 3233,951 2709,982 2709,1264 2402,1264 2402,991 1852,991 1852,1264 1533,1264 1530,992 1009,951 "/>
<rect class="fil0" x="1005" y="3591" width="2217" height="204"/>
<rect class="fil0" x="1174" y="3738" width="1885" height="153"/>
<rect class="fil0" x="1001" y="3857" width="2217" height="204"/>
<path class="fil1" d="M3064 961c0,0 -146,919 -168,986 -22,67 -94,155 -94,155l213 0 217 -1151 -169 10z"/>
<path class="fil1" d="M1598 2103l1162 0 195 1488 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -24,-171 -67,-269 -42,-99 -90,-78 -139,-445 -49,-368 -153,-1020 -153,-1020l-945 -223z"/>
<polygon class="fil1" points="1176,3792 3061,3792 3061,3855 1176,3855 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#E3E3E3}
.fil0 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M2082 821c0,0 621,269 458,883 -163,614 -570,260 -570,260l113 -1142 0 0z"/>
<rect class="fil0" x="1010" y="3577" width="2217" height="204"/>
<rect class="fil0" x="1179" y="3724" width="1885" height="153"/>
<rect class="fil0" x="1006" y="3843" width="2217" height="204"/>
<polygon class="fil0" points="1792,1892 2390,1892 2891,3596 1336,3596 "/>
<rect class="fil0" x="1497" y="1849" width="1200" height="152"/>
<path class="fil0" d="M2097 822c0,0 -623,269 -459,883 164,614 572,260 572,260l-113 -1142 0 0z"/>
<ellipse class="fil0" cx="2092" cy="787" rx="174" ry="143"/>
<path class="fil1" d="M2182 664c0,0 38,51 38,113 0,61 -14,118 -14,118 0,0 162,-115 -24,-231z"/>
<polygon class="fil1" points="1179,3781 3064,3781 3064,3843 1179,3843 "/>
<path class="fil1" d="M2206 895c0,0 221,144 300,333 79,189 66,259 50,397 -16,138 -68,224 -68,224l209 0 0 152 -321 0 -17 -152c0,0 -2,-42 -12,-77 -10,-36 93,-85 104,-240 10,-155 1,-253 -53,-368 -53,-116 -194,-269 -194,-269z"/>
<polygon class="fil1" points="1752,2001 2422,2001 2887,3577 3227,3577 3227,3781 3064,3781 3064,3843 3223,3843 3223,4047 2743,4047 2743,3843 2743,3781 2658,3577 2347,2147 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil2 {fill:#E3E3E3}
.fil0 {fill:#FEFEFE}
.fil1 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1554,1987 2652,1996 2968,3649 1280,3626 "/>
<rect class="fil1" x="1010" y="3596" width="2217" height="204"/>
<rect class="fil1" x="1179" y="3743" width="1885" height="153"/>
<rect class="fil1" x="1006" y="3863" width="2217" height="204"/>
<path class="fil2" d="M1612 2107l1061 0 288 1489 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -150,-1101 -150,-1101l-939 -145z"/>
<polygon class="fil2" points="1181,3797 3066,3797 3066,3860 1181,3860 "/>
<rect class="fil1" x="1300" y="1887" width="1638" height="220"/>
<path class="fil1" d="M1490 1966c0,0 -241,-328 -199,-572 50,-285 382,-408 875,-435 148,-8 98,1007 98,1007l-774 0z"/>
<path class="fil1" d="M2726 1966c0,0 241,-328 199,-572 -50,-285 -382,-408 -875,-435 -148,-8 -98,1007 -98,1007l774 0z"/>
<path class="fil1" d="M1772 1010c0,0 253,-63 226,-169 -27,-106 114,-19 114,-19l10 146 -350 43z"/>
<path class="fil1" d="M2384 1003c0,0 -154,-75 -159,-161 -5,-86 -114,-19 -114,-19l-10 147 282 34z"/>
<ellipse class="fil1" cx="1967" cy="634" rx="91" ry="89"/>
<ellipse class="fil1" cx="1995" cy="772" rx="64" ry="62"/>
<ellipse class="fil1" cx="2250" cy="635" rx="91" ry="89"/>
<ellipse class="fil1" cx="2222" cy="773" rx="64" ry="62"/>
<ellipse class="fil1" cx="2112" cy="556" rx="97" ry="101"/>
<polygon class="fil1" points="2047,881 1967,634 2112,556 2250,635 2176,886 "/>
<path class="fil2" d="M2226 842c0,0 -49,94 18,144 66,50 231,81 319,139 89,58 240,120 241,284 2,164 -33,236 -139,351 -107,116 -175,126 -175,126l288 0c0,0 87,-152 110,-226 23,-74 52,-167 36,-267 -16,-100 -90,-225 -206,-280 -116,-56 -86,-51 -170,-75 -83,-24 -94,-26 -103,-28 -9,-2 -95,-27 -95,-27 0,0 -45,-25 -70,-51 -25,-26 -55,-91 -55,-91z"/>
<polygon class="fil2" points="2490,1887 2524,2107 2938,2107 2938,1887 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#E3E3E3}
.fil0 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M1040 1733c-183,169 -132,155 -60,286 72,131 195,227 503,77 308,-150 390,-133 517,-103 127,30 159,91 167,157 8,66 -186,227 -400,433 -214,205 -402,243 -373,671 30,428 19,525 -42,592 -61,67 1074,263 1221,196 147,-68 543,-55 465,-460 -78,-406 -208,-900 -10,-1238 198,-338 398,-1042 -527,-1423 -925,-381 -558,35 -648,83 -90,47 -121,57 -175,83 -98,47 -463,485 -639,647z"/>
<rect class="fil0" x="1101" y="3595" width="2217" height="204"/>
<rect class="fil0" x="1271" y="3742" width="1885" height="153"/>
<rect class="fil0" x="1098" y="3861" width="2217" height="204"/>
<path class="fil1" d="M1826 2000c0,0 85,-124 262,-131 176,-7 75,-331 75,-331 0,0 227,293 227,444 0,151 -239,217 -239,217 0,0 -30,-182 -324,-200z"/>
<path class="fil1" d="M2831 1100c0,0 425,238 221,842 -204,604 -560,915 -498,1226 62,311 9,364 31,391 22,27 93,27 195,-4 102,-31 84,44 93,89 9,44 72,421 72,421l369 0 0 -204 -159 0 0 -63 163 0 0 -204 -277 0c0,0 -95,-448 -116,-728 -21,-280 58,-454 170,-653 111,-200 231,-777 -265,-1113z"/>
<polygon class="fil1" points="1272,3798 3156,3798 3156,3861 1272,3861 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#E3E3E3}
.fil0 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<rect class="fil0" x="1165" y="3595" width="1907" height="204"/>
<rect class="fil0" x="1310" y="3742" width="1621" height="153"/>
<rect class="fil0" x="1162" y="3862" width="1907" height="204"/>
<polygon class="fil1" points="1311,3799 2932,3799 2932,3861 1311,3861 "/>
<polygon class="fil0" points="1871,2415 2167,2411 2167,3693 1550,3684 "/>
<polygon class="fil0" points="2352,2415 2088,2411 2087,3693 2705,3684 "/>
<rect class="fil0" x="1566" y="2263" width="1115" height="158"/>
<ellipse class="fil0" cx="2101" cy="1905" rx="414" ry="433"/>
<path class="fil1" d="M2191 1482c0,0 195,147 218,349 36,315 -162,418 -446,351 -143,-34 -244,-110 -231,-82 0,0 21,51 59,90 37,39 82,75 82,75l457 0c0,0 74,-41 114,-117 40,-77 72,-137 72,-244 0,-107 -41,-199 -68,-237 -27,-38 -56,-71 -105,-114 -48,-44 -152,-71 -152,-71z"/>
<polygon class="fil1" points="2286,2263 2355,2421 2681,2421 2681,2263 "/>
<polygon class="fil1" points="1770,2421 2355,2421 2701,3663 2446,3663 2277,2568 "/>
<polygon class="fil1" points="2678,3595 2748,4065 3069,4065 3069,3862 2932,3861 2932,3799 3072,3799 3072,3595 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#E3E3E3}
.fil0 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1656,1982 2548,1985 2968,3643 1280,3620 "/>
<rect class="fil0" x="1010" y="3590" width="2217" height="204"/>
<rect class="fil0" x="1179" y="3737" width="1885" height="153"/>
<rect class="fil0" x="1006" y="3857" width="2217" height="204"/>
<path class="fil1" d="M1612 2101l965 0 383 1489 266 0 0 204 -161 0 0 60 157 3 0 204 -316 0c0,0 -26,-149 -71,-267 -45,-117 -85,-80 -135,-448 -49,-368 -253,-1117 -253,-1117l-836 -129z"/>
<polygon class="fil1" points="1181,3792 3066,3792 3066,3854 1181,3854 "/>
<rect class="fil0" x="1300" y="1881" width="1638" height="220"/>
<polygon class="fil0" points="1564,1918 1161,1154 1658,1413 1618,730 2096,1286 2128,1918 "/>
<polygon class="fil0" points="2628,1919 3031,1154 2533,1414 2573,730 2095,1287 2076,1919 "/>
<circle class="fil0" cx="1161" cy="1154" r="100"/>
<circle class="fil0" cx="1618" cy="730" r="100"/>
<circle class="fil0" cx="2573" cy="730" r="100"/>
<circle class="fil1" cx="3031" cy="1154" r="100"/>
<path class="fil1" d="M2633 650l-97 173 -240 1058 351 0 337 -639 -42 -42 -409 213 43 -584c0,0 182,-32 57,-180z"/>
<polygon class="fil1" points="2472,1881 2472,2101 2938,2101 2938,1881 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X5 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="42.3332mm" height="42.3332mm" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 4233 4233"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil1 {fill:#E3E3E3}
.fil0 {fill:white}
]]>
</style>
</defs>
<g id="Warstwa_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="1471,2050 2754,2054 2963,3644 1274,3621 "/>
<polygon class="fil0" points="1213,2103 3015,2103 3233,951 2709,982 2709,1264 2402,1264 2402,991 1852,991 1852,1264 1533,1264 1530,992 1009,951 "/>
<rect class="fil0" x="1005" y="3591" width="2217" height="204"/>
<rect class="fil0" x="1174" y="3738" width="1885" height="153"/>
<rect class="fil0" x="1001" y="3857" width="2217" height="204"/>
<path class="fil1" d="M3064 961c0,0 -146,919 -168,986 -22,67 -94,155 -94,155l213 0 217 -1151 -169 10z"/>
<path class="fil1" d="M1598 2103l1162 0 195 1488 266 0 0 204 -161 -3 0 63 157 3 0 204 -316 0c0,0 -24,-171 -67,-269 -42,-99 -90,-78 -139,-445 -49,-368 -153,-1020 -153,-1020l-945 -223z"/>
<polygon class="fil1" points="1176,3792 3061,3792 3061,3855 1176,3855 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 500 500" width="500" height="500" id="clear_svg">
</svg>

Before

Width:  |  Height:  |  Size: 179 B

View file

@ -38,7 +38,7 @@
#editor-side {
position: absolute;
left: 552px;
top: 90px;
top: 114px;
width: 228px;
}
#editor-side > div {

View file

@ -76,7 +76,8 @@ time {
font-style: normal;
}
.is::before,
[data-icon]::before {
[data-icon]::before,
.is-after::after {
font-size: 1.2em;
vertical-align: middle;
font-family: "lichess" !important;
@ -127,7 +128,6 @@ body.blind_mode [data-icon]::before {
.is.color-icon.random::before {
content: "l";
}
@font-face {
font-family: 'ChessSansPiratf';
src: url("../fonts/ChessSansPiratf.eot");
@ -886,7 +886,8 @@ div.side_box .game_infos .bookmark {
opacity: 0;
transform: translate(0, -20px);
transition: 0.3s;
z-index: 10; /* to go over the board */
z-index: 10;
/* to go over the board */
}
div.side_box .game_infos:hover .bookmark {
transform: translate(0, 0);
@ -895,8 +896,6 @@ div.side_box .game_infos:hover .bookmark {
div.side_box .game_infos .setup {
display: block;
}
div.side_box div.players {
}
div.side_box div.status {
text-align: center;
margin-top: 5px;
@ -2194,3 +2193,35 @@ input.copyable {
-moz-transform: translateX(-8px);
transform: translateX(-8px);
}
.continue_with {
display: none;
}
.continue_with .button {
display: inline-block;
margin: 1em;
}
#modal-overlay {
display: block;
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
overflow: auto;
background: rgba(0, 0, 0, 0.5);
z-index: 5;
cursor: pointer;
}
#modal-wrap {
display: inline-block;
position: relative;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
border-radius: 3px;
box-shadow: 0 0 95px 25px rgba(0, 0, 0, 0.8);
background: #fff;
text-align: center;
cursor: default;
}

View file

@ -140,7 +140,8 @@ body.dark .crosstable td:nth-last-child(3) {
body.dark .crosstable td.current {
box-shadow: none;
}
body.dark .crosstable td.current a {
body.dark .crosstable td.current a,
body.dark #modal-wrap {
background-color: #303030;
}
body.dark .ui-slider,

View file

@ -107,10 +107,6 @@
height: 22px;
display:inline-block;
}
#puzzle div.continue.links {
margin: 20px 0 20px 0;
text-align: center;
}
#puzzle #GameButtons {
display: inline-block;
margin-left: 10px;

View file

@ -1,11 +1,14 @@
#tournament table.slist td:first-child a {
#tournament table.slist td:first-child a,
#tournament_list table.slist td:first-child a,
ol.scheduled_tournaments a {
font-weight: bold;
text-decoration: none;
}
#tournament table.slist td:first-child a:hover {
#tournament table.slist td:first-child a:hover,
#tournament_list table.slist td:first-child a:hover,
ol.scheduled_tournaments a:hover {
text-decoration: underline;
}
#tournament table.slist tbody.scheduled a,
#tournament.scheduled h1,
#tournament table tr.standing.scheduled:first-child td:first-child {
color: #d59120;
@ -26,6 +29,12 @@
padding-top: 20px;
overflow: hidden;
}
#tournament a.user_link em {
font-weight: lighter;
font-style: italic;
font-size: 0.8em;
padding-left: 5px;
}
#tournament a.pov {
display: block;
margin: 0 25px 1em 25px;
@ -57,8 +66,8 @@
#tournament a.pov::after {
right: 15px;
}
#tournament table.slist .create td {
padding: 0.6em 1.2em 0.6em 0.6em;
#tournament_list table.slist .create td {
padding: 2em;
text-align: center;
}
#tournament table.slist span.rank {
@ -81,11 +90,12 @@
}
#tournament table.standing td.name,
#tournament table.standing th:first-child {
padding-left: 15px;
padding-left: 10px;
white-space: nowrap;
}
#tournament table.standing td.name span {
display: inline-block;
width: 20px;
width: 24px;
}
#tournament table.standing tr.me td.name {
border-left: 10px solid #d59120;
@ -97,16 +107,19 @@
#tournament table.standing .legend span {
margin-left: 10px;
}
#tournament table.standing .sheet {
letter-spacing: -1.5px;
}
#tournament table.standing .sheet,
#tournament table.standing .total {
text-align: right;
font-family: monospace;
letter-spacing: 0;
}
#tournament div.standing_wrap:hover td.total {
padding-right: 22px;
#tournament table.standing .sheet span,
#tournament table.standing .total span {
display: inline-block;
width: 12px;
}
#tournament div.standing_wrap td.total {
padding-right: 20px;
}
#tournament .double {
color: #d59120;
@ -124,11 +137,11 @@
width: 0;
background: #759900;
opacity: 0.5;
transition: width 0.5s;
}
#tournament form.inline {
display: inline;
#tournament button.right {
float: right;
padding-right: 0.6em;
margin-left: 5px;
}
#tournament span.joined {
font-weight: bold;
@ -182,29 +195,31 @@
border: 1px solid #ccc;
padding: 3px;
overflow: auto;
}
#tournament_side div.pairings {
text-align: center;
}
#tournament_side div.pairings a {
#tournament_side a {
padding: 0.3em 0.2em;
display: block;
white-space: nowrap;
overflow: hidden;
text-decoration: none;
}
#tournament_side div.pairings span {
#tournament_side a:hover {
text-decoration: underline;
}
#tournament_side span {
font-weight: bold;
}
#tournament_side div.pairings span.win {
#tournament_side span.win {
color: #00aa00;
}
#tournament_side div.pairings span.loss {
#tournament_side span.loss {
color: #aa0000;
}
#tournament_side div.pairings span.draw {
#tournament_side span.draw {
color: #aaaa00;
}
#tournament_side div.pairings em {
#tournament_side em {
font-style: italic;
}
#tournament article.faq h2 {
@ -251,10 +266,3 @@ ol.scheduled_tournaments li::before {
ol.scheduled_tournaments li:hover::before {
opacity: 1;
}
ol.scheduled_tournaments a {
font-weight: bold;
text-decoration: none;
}
ol.scheduled_tournaments a:hover {
text-decoration: underline;
}

View file

@ -30,11 +30,11 @@
"watchify": "^1.0.2"
},
"dependencies": {
"chessground": "1.8.6",
"chessground": "1.8.10",
"chessli.js": "file:../chessli",
"game": "file:../game",
"lodash-node": "^2.4.1",
"mithril": "0.1.27",
"mithril": "0.1.28",
"mousetrap": "0.0.1"
}
}

View file

@ -25,14 +25,15 @@ module.exports = {
prev: function(ctrl) {
var p = ctrl.vm.path;
var len = p.length;
if (len == 1) {
if (p[0].ply == 1) return;
if (len === 1) {
if (p[0].ply === 0) 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;
if (p[len - 2].ply > 1) p[len - 2].ply--;
}
}
ctrl.jump(p);

View file

@ -18,7 +18,6 @@ module.exports = function(cfg, router, i18n, onChange) {
path: initialPath,
pathStr: treePath.write(initialPath),
situation: null,
continue: false,
comments: true,
flip: false
};

View file

@ -23,11 +23,11 @@ module.exports = function(ctrl) {
control.next(ctrl);
m.redraw();
}));
k.bind(['up', 'j'], preventing(function() {
k.bind(['up', 'k'], preventing(function() {
control.first(ctrl);
m.redraw();
}));
k.bind(['down', 'k'], preventing(function() {
k.bind(['down', 'j'], preventing(function() {
control.last(ctrl);
m.redraw();
}));

View file

@ -24,3 +24,7 @@ module.exports = function(element, config, router, i18n, onChange) {
}
};
};
// lol, that's for the rest of lichess to access mithril
// without having to include it a second time
window.Chessground = require('chessground');

View file

@ -270,20 +270,22 @@ function buttons(ctrl) {
m('div.jumps.hint--bottom', {
'data-hint': 'Tip: use your keyboard arrow keys!'
}, [
['first', 'W', control.first],
['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
});
return {
tag: 'a',
attrs: {
class: 'button ' + b[0] + ' ' + classSet({
disabled: ctrl.broken,
glowing: ctrl.vm.late && b[0] === 'last'
}),
'data-icon': b[1],
onclick: partial(b[2], ctrl)
}
};
})),
m('a.button.hint--bottom', flipAttrs, m('span[data-icon=B]')),
ctrl.data.inGame ? null : m('a.button.hint--bottom', {
@ -294,20 +296,21 @@ function buttons(ctrl) {
ctrl.data.inGame ? null : m('a.button.hint--bottom', {
'data-hint': ctrl.trans('continueFromHere'),
onclick: function() {
ctrl.vm.continue = !ctrl.vm.continue
$.modal($('.continue_with.' + ctrl.data.game.id));
}
}, m('span[data-icon=U]'))
]),
ctrl.vm.continue ? m('div.continue', [
m('div.continue_with.' + ctrl.data.game.id, [
m('a.button', {
href: ctrl.data.userAnalysis ? '/?fen=' + ctrl.vm.situation.fen + '#ai' : ctrl.router.Round.continue(ctrl.data.game.id, 'ai').url + '?fen=' + ctrl.vm.situation.fen,
rel: 'nofollow'
}, ctrl.trans('playWithTheMachine')),
m('br'),
m('a.button', {
href: ctrl.data.userAnalysis ? '/?fen=' + ctrl.vm.situation.fen + '#friend' : ctrl.router.Round.continue(ctrl.data.game.id, 'friend').url + '?fen=' + ctrl.vm.situation.fen,
rel: 'nofollow'
}, ctrl.trans('playWithAFriend'))
]) : null
])
];
}

View file

@ -5,7 +5,7 @@ target=${1-dev}
mkdir -p public/compiled
for app in editor puzzle round analyse lobby; do
for app in editor puzzle round analyse lobby tournament; do
cd ui/$app
npm install && gulp $target
cd -

View file

@ -30,8 +30,8 @@
"watchify": "^1.0.2"
},
"dependencies": {
"chessground": "1.8.6",
"chessground": "1.8.10",
"lodash-node": "^2.4.1",
"mithril": "0.1.27"
"mithril": "0.1.28"
}
}

Some files were not shown because too many files have changed in this diff Show more