Merge branch 'master' into tourneyScheduleUI
* master: (24 commits) hi "हिन्दी, हिंदी" translation #14525. Author: AdityaPrakash5000. lt "lietuvių kalba" translation #14524. Author: mast3r. "Refactoring" and review of translation, 50/444 pl "polski" translation #14522. Author: rzenaikrzys. Perfect translation for Polish by Captain Kristo smarter mobile app puzzle export remove body background in embedded iframes export puzzles in mobile app format puzzle UI: get rid of lodash/merge and save 4K minified export puzzles JSON for mobile app move tournament WaitingUsers to a new file drop arena pairing left over logging raise puzzle selector max rating more arena pairing refinements drop user who waiting for less time if arena players count is odd try arena pairings every 3 seconds reorganize tournament waiting users code - no functional change tweak arena pairing system add player provisional rating to game JSON API link APK download on /mobile (content from prismic.io) add message when password recovery fails fix experimental marathon title display ...pull/599/head
commit
aa77c41243
|
@ -71,8 +71,8 @@ object Main extends LilaController {
|
|||
}
|
||||
|
||||
def mobile = Open { implicit ctx =>
|
||||
fuccess {
|
||||
html.site.mobile()
|
||||
OptionOk(Prismic oneShotBookmark "mobile-apk") {
|
||||
case (doc, resolver) => html.site.mobile(doc, resolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ object Simul extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def join(id: String, variant: String) = AuthBody { implicit ctx =>
|
||||
def join(id: String, variant: String) = Auth { implicit ctx =>
|
||||
implicit me =>
|
||||
NoEngine {
|
||||
fuccess {
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.Locale
|
|||
import scala.collection.mutable
|
||||
|
||||
import org.joda.time.format._
|
||||
import org.joda.time.format.ISODateTimeFormat
|
||||
import org.joda.time.{ Period, PeriodType, DurationFieldType, DateTime }
|
||||
import play.twirl.api.Html
|
||||
|
||||
|
@ -68,4 +69,9 @@ trait DateHelper { self: I18nHelper =>
|
|||
}
|
||||
|
||||
def secondsFromNow(seconds: Int) = momentFromNow(DateTime.now plusSeconds seconds)
|
||||
|
||||
private val atomDateFormatter = ISODateTimeFormat.dateTime
|
||||
def atomDate(date: DateTime): String = atomDateFormatter print date
|
||||
def atomDate(field: String)(doc: io.prismic.Document): Option[String] =
|
||||
doc getDate field map (_.value.toDateTimeAtStartOfDay) map atomDate
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ zen = true) {
|
|||
}
|
||||
@trans.passwordReset()
|
||||
</h1>
|
||||
@if(ok.contains(false)) {
|
||||
<p>
|
||||
Are you sure you even registered your email on lichess?<br />
|
||||
It was not required for your registration.
|
||||
</p>
|
||||
}
|
||||
<form action="@routes.Auth.passwordResetApply" method="POST">
|
||||
<ul>
|
||||
<li class="email">
|
||||
|
@ -29,7 +35,7 @@ zen = true) {
|
|||
</li>
|
||||
@errMsg(form)
|
||||
<li>
|
||||
<button type="submit" class="submit button" data-icon="F"> @trans.emailMeALink()</button>
|
||||
<button type="submit" class="submit button text" data-icon="F">@trans.emailMeALink()</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
<link rel="alternate" type="text/html" href="@routes.Blog.index().absoluteURL()"/>
|
||||
<link rel="self" type="application/atom+xml" href="@routes.Blog.atom().absoluteURL()"/>
|
||||
<title>lichess.org blog</title>
|
||||
<updated>@(docs.headOption.flatMap(_.getDate("blog.date", "yyyy-MM-dd'T'HH:mm:ss'Z'")).??(_.toString))</updated>
|
||||
<updated>@docs.headOption.flatMap(atomDate("blog.date"))</updated>
|
||||
@docs.map { doc =>
|
||||
<entry>
|
||||
<id>@routes.Blog.show(doc.id, doc.slug).absoluteURL()</id>
|
||||
<published>@(doc.getDate("blog.date", "yyyy-MM-dd'T'HH:mm:ss'Z'").??(_.toString))</published>
|
||||
<updated>@(doc.getDate("blog.date", "yyyy-MM-dd'T'HH:mm:ss'Z'").??(_.toString))</updated>
|
||||
<published>@atomDate("blog.date")(doc)</published>
|
||||
<updated>@atomDate("blog.date")(doc)</updated>
|
||||
<link rel="alternate" type="text/html" href="@routes.Blog.show(doc.id, doc.slug).absoluteURL()"/>
|
||||
<title>@doc.getText("blog.title")</title>
|
||||
<category>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body
|
||||
class="base highlight @bg"
|
||||
class="base highlight @bg no-bg"
|
||||
style="width: 224px; height: 264px; overflow: hidden;">
|
||||
<div id="daily_puzzle" class="is2d highlight @bg @theme merida" title="@trans.clickToSolve()">
|
||||
@daily.html
|
||||
|
|
|
@ -4,16 +4,10 @@
|
|||
@helper.javascriptRouter("puzzleRoutes")(
|
||||
routes.javascript.Puzzle.attempt,
|
||||
routes.javascript.Puzzle.show,
|
||||
/* routes.javascript.Puzzle.history, */
|
||||
/* routes.javascript.Puzzle.difficulty, */
|
||||
/* routes.javascript.Puzzle.home, */
|
||||
/* routes.javascript.Puzzle.newPuzzle, */
|
||||
routes.javascript.Puzzle.load,
|
||||
routes.javascript.Puzzle.vote,
|
||||
/* routes.javascript.Coordinate.home, */
|
||||
routes.javascript.Editor.load,
|
||||
routes.javascript.Round.watcher
|
||||
/* routes.javascript.Auth.signup */
|
||||
)(ctx.req)
|
||||
@embedJs {
|
||||
LichessPuzzle(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@()(implicit ctx: Context)
|
||||
@(apkDoc: io.prismic.Document, resolver: io.prismic.DocumentLinkResolver)(implicit ctx: Context)
|
||||
|
||||
@base.layout(
|
||||
title = "Mobile",
|
||||
|
@ -22,6 +22,9 @@ moreCss = cssTag("mobile.css")) {
|
|||
width="172"
|
||||
src="https://devimages.apple.com.edgekey.net/app-store/marketing/guidelines/images/badge-download-on-the-app-store.svg" />
|
||||
</a>
|
||||
<div class="apk" style="margin-top: 20px">
|
||||
@Html(~apkDoc.getHtml("doc.content", resolver))
|
||||
</div>
|
||||
<h2>lichess on the go</h2>
|
||||
<ul class="block">
|
||||
<li>Challenge online players</li>
|
||||
|
@ -36,11 +39,13 @@ moreCss = cssTag("mobile.css")) {
|
|||
<li>Zero advertisement</li>
|
||||
<li>Entirely <a href="https://github.com/veloce/lichobile">Open Source</a> as usual</li>
|
||||
</ul>
|
||||
<h2>Three modes included</h2>
|
||||
<h2>Full featured</h2>
|
||||
<ul class="block">
|
||||
<li>Real time with increment</li>
|
||||
<li>Correspondence games</li>
|
||||
<li>Over The Board, offline</li>
|
||||
<li>VS computer, offline</li>
|
||||
<li>Watch Lichess TV</li>
|
||||
</ul>
|
||||
<p class="block">
|
||||
More of the features you love from the website will be added in the near future.<br />
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body
|
||||
class="base highlight @bg"
|
||||
class="base highlight @bg no-bg"
|
||||
style="width: 224px; height: 264px; overflow: hidden;"
|
||||
data-stream-url="@routes.Tv.streamOut">
|
||||
<div id="featured_game" class="is2d highlight @bg @theme merida" title="lichess.org TV">
|
||||
|
|
|
@ -43,7 +43,7 @@ whiteResigned=सफेद ने हार मानली
|
|||
blackResigned=काले ने हार मानली
|
||||
whiteLeftTheGame=सफेद दूम दबाकर भाग गया
|
||||
blackLeftTheGame=काला दूम दबाकर भाग गया
|
||||
shareThisUrlToLetSpectatorsSeeTheGame=यह URL को SHARE करिएँ अपने दोस्तों के साथ
|
||||
shareThisUrlToLetSpectatorsSeeTheGame=इस URL को बाँटें ताकी सभा इसे देख सके।
|
||||
replayAndAnalyse=विश्लेषण
|
||||
computerAnalysisInProgress=कम्प्यूटर विश्लेषण प्रगति में
|
||||
theComputerAnalysisHasFailed=कम्प्यूटर विश्लेषण असफल हुआ
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
playWithAFriend=Žaisti prieš draugą
|
||||
playWithTheMachine=Žaisti prieš kompiuterį
|
||||
toInviteSomeoneToPlayGiveThisUrl=Norint pakviesti ką nors žaidimui, nusiųsk šią nuorodą
|
||||
playWithAFriend=Žaisti su draugu
|
||||
playWithTheMachine=Žaisti su kompiuteriu
|
||||
toInviteSomeoneToPlayGiveThisUrl=Norėdami pakviesti varžovą, pasidalinkite šiuo adresu
|
||||
gameOver=Žaidimas baigtas
|
||||
waitingForOpponent=Laukiama priešininko
|
||||
waitingForOpponent=Laukiama varžovo
|
||||
waiting=Laukiama
|
||||
yourTurn=Tavo ėjimas
|
||||
aiNameLevelAiLevel=%s lygis %s
|
||||
yourTurn=Jūsų ėjimas
|
||||
aiNameLevelAiLevel=%s lygis Nr. %s
|
||||
level=Lygis
|
||||
toggleTheChat=Įgalinti pokalbį
|
||||
toggleTheChat=Įjungti/išjungti pokalbius
|
||||
toggleSound=Įjungti/išjungti garsus
|
||||
chat=Pokalbis
|
||||
resign=Pasiduoti
|
||||
|
@ -19,20 +19,23 @@ randomColor=Atsitiktinė spalva
|
|||
createAGame=Sukurti žaidimą
|
||||
whiteIsVictorious=Baltieji laimėjo
|
||||
blackIsVictorious=Juodieji laimėjo
|
||||
playWithTheSameOpponentAgain=Žaisti su tuo pačiu priešininku dar kartą
|
||||
newOpponent=Naujas priešininkas
|
||||
playWithAnotherOpponent=Žaisti su kitu priešininku
|
||||
yourOpponentWantsToPlayANewGameWithYou=Tavo priešininkas nori žaisti su tavimi iš naujo
|
||||
kingInTheCenter=Karalius centre
|
||||
threeChecks=Trys šachai
|
||||
variantEnding=Variantinė pabaiga
|
||||
playWithTheSameOpponentAgain=Žaisti su tuo pačiu varžovu dar kartą
|
||||
newOpponent=Naujas varžovas
|
||||
playWithAnotherOpponent=Žaisti su kitu varžovu
|
||||
yourOpponentWantsToPlayANewGameWithYou=Jūsų varžovas norėtų sužaisti dar kartą
|
||||
joinTheGame=Prisijungti prie žaidimo
|
||||
whitePlays=Baltųjų eilė
|
||||
blackPlays=Juodųjų eilė
|
||||
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Priešininkas paliko žaidimą. Galite pareikšti pergalę, lygiąsias arba palaukti.
|
||||
makeYourOpponentResign=Pareikšti savo pergalę
|
||||
forceResignation=Priverstinis pralaimėjimas
|
||||
forceDraw=Priverstinės lygiosios
|
||||
whitePlays=Baltųjų ėjimas
|
||||
blackPlays=Juodųjų ėjimas
|
||||
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Panašu, kad varžovas paliko žaidimą. Galite pasiimti pergalę, lygiąsias, arba palaukti.
|
||||
makeYourOpponentResign=Priversti priešininką pasiduoti
|
||||
forceResignation=Pasiimti pergalę
|
||||
forceDraw=Užskaityti lygiąsias
|
||||
talkInChat=Kalbėti
|
||||
theFirstPersonToComeOnThisUrlWillPlayWithYou=Pirmasis žmogus prisijungęs šiuo adresu žais su tavimi.
|
||||
whiteCreatesTheGame=Baltieji sukūrė žaidimą
|
||||
theFirstPersonToComeOnThisUrlWillPlayWithYou=Pirmasis šiuo adresu atėjęs žmogus taps jūsų varžovu.
|
||||
whiteCreatesTheGame=Baltieji sukūrė partiją
|
||||
blackCreatesTheGame=Juodieji sukūrė partiją
|
||||
whiteJoinsTheGame=Baltieji prisijungė
|
||||
blackJoinsTheGame=Juodieji prisijungė
|
||||
|
@ -40,11 +43,11 @@ whiteResigned=Baltieji pasidavė
|
|||
blackResigned=Juodieji pasidavė
|
||||
whiteLeftTheGame=Baltieji paliko žaidimą
|
||||
blackLeftTheGame=Juodieji paliko žaidimą
|
||||
shareThisUrlToLetSpectatorsSeeTheGame=Pasidalinkite šiuo adresu norintiems stebėti šį žaidimą
|
||||
replayAndAnalyse=Pakartoti ir analizuoti
|
||||
shareThisUrlToLetSpectatorsSeeTheGame=Pasidalinę šiuo adresu leisite žiūrovams stebėti partiją
|
||||
replayAndAnalyse=Peržiūrėti ir analizuoti
|
||||
computerAnalysisInProgress=Kompiuteris analizuoja
|
||||
theComputerAnalysisHasFailed=Kompiuterio analizė nepavyko
|
||||
viewTheComputerAnalysis=Žiūrėti kompiuterio analizę
|
||||
viewTheComputerAnalysis=Peržiūrėti kompiuterio analizę
|
||||
requestAComputerAnalysis=Užsakyti kompiuterio analizę
|
||||
computerAnalysis=Kompiuterio analizė
|
||||
analysis=Žaidimo analizė
|
||||
|
|
|
@ -21,6 +21,7 @@ whiteIsVictorious=Białe zwyciężyły
|
|||
blackIsVictorious=Czarne zwyciężyły
|
||||
kingInTheCenter=Król w centrum
|
||||
threeChecks=Trzykrotne zaszachowanie
|
||||
variantEnding=Wariant zakończony
|
||||
playWithTheSameOpponentAgain=Zagraj jeszcze raz z tym zawodnikiem
|
||||
newOpponent=Nowy przeciwnik
|
||||
playWithAnotherOpponent=Zagraj z innym przeciwnikiem
|
||||
|
@ -56,7 +57,7 @@ inaccuracies=Niedokładności
|
|||
moveTimes=Czas przeznaczony na ruch
|
||||
flipBoard=Obróć szachownicę
|
||||
threefoldRepetition=Trzykrotne powtórzenie pozycji
|
||||
claimADraw=Reklamuj remis
|
||||
claimADraw=Wymuś remis
|
||||
offerDraw=Zaproponuj remis
|
||||
draw=Remis
|
||||
nbConnectedPlayers=%s graczy online
|
||||
|
@ -125,7 +126,7 @@ gameAborted=Gra została przerwana
|
|||
standard=Standard
|
||||
unlimited=Bez ograniczeń
|
||||
mode=Tryb
|
||||
casual=Nierankingowa
|
||||
casual=Towarzyska
|
||||
rated=Rankingowa
|
||||
thisGameIsRated=Ta gra jest rankingowa
|
||||
rematch=Rewanż
|
||||
|
@ -173,7 +174,7 @@ tournamentPoints=Punkty Turniejowe
|
|||
viewTournament=Zobacz turniej
|
||||
backToTournament=Wróć do turnieju
|
||||
backToGame=Wróć do gry
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Darmowe szachy online. Zagraj teraz w szachy o przyjaznym interfejsie. Bez rejestracji, bez reklam, bez instalowania wtyczek. Graj w szachy z komputerem, przyjaciółmi lub losowymi przeciwnikami.
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Darmowe szachy online. Zagraj teraz w szachy o przyjaznym interfejsie. Bez rejestracji, bez reklam, bez instalowania wtyczek. Graj w szachy z komputerem, przyjaciółmi lub losowo wybranymi przeciwnikami.
|
||||
teams=Zespoły
|
||||
nbMembers=%s członkowie
|
||||
allTeams=Wszystkie zespoły
|
||||
|
@ -305,7 +306,7 @@ startTraining=Rozpocznij trening
|
|||
continueTraining=Kontynuuj trening
|
||||
retryThisPuzzle=Powtórz to zadanie
|
||||
thisPuzzleIsCorrect=To zadanie jest poprawne i ciekawe
|
||||
thisPuzzleIsWrong=To zadanie jest niepoprawne lub nudne
|
||||
thisPuzzleIsWrong=To zadanie jest niepoprawne i nudne
|
||||
youHaveNbSecondsToMakeYourFirstMove=Masz %s sekund na pierwszy ruch!
|
||||
nbGamesInPlay=%s gier w trakcie
|
||||
automaticallyProceedToNextGameAfterMoving=Automatycznie przejdź do następnej gry po wykonaniu ruchu
|
||||
|
@ -342,8 +343,8 @@ user=Użytkownik
|
|||
reason=Powód
|
||||
whatIsIheMatter=Co się stało?
|
||||
cheat=Oszustwo
|
||||
insult=Wyzwiska
|
||||
troll=Trollowanie
|
||||
insult=Obraza
|
||||
troll=Upierdliwiec(natręt)
|
||||
other=Inny
|
||||
reportDescriptionHelp=Wklej link do tej gry i wytłumacz dlaczego zachowanie użytkownika jest nieprawidłowe
|
||||
by=przez %s
|
||||
|
@ -392,7 +393,7 @@ soundControlInTheTopBarOfEveryPage=Kontrola dźwięku jest umieszczona na po pra
|
|||
yourPreferencesHaveBeenSaved=Twoje preferencje zostały zapisane
|
||||
none=Żadna
|
||||
fast=Szybka
|
||||
normal=Normalne
|
||||
normal=Normalna
|
||||
slow=Wolna
|
||||
insideTheBoard=Wewnątrz szachownicy
|
||||
outsideTheBoard=Na zewnątrz szachownicy
|
||||
|
@ -420,8 +421,8 @@ blocks=%s zablokowanych
|
|||
listBlockedPlayers=Lista zawodników których zablokowałeś
|
||||
human=Człowiek
|
||||
computer=Komputer
|
||||
side=Kolor
|
||||
clock=Czas
|
||||
side=Strona
|
||||
clock=Zegar
|
||||
unauthorizedError=Nieupoważniony dostęp
|
||||
noInternetConnection=Brak połączenia. Możesz jednak wybrać z menu opcję gry offline.
|
||||
connectedToLichess=Zostałeś połączony z lichess.org
|
||||
|
|
|
@ -52,5 +52,6 @@ private[api] final class Cli(bus: lila.common.Bus, renderer: ActorSelection) ext
|
|||
lila.analyse.Env.current.cli.process orElse
|
||||
lila.team.Env.current.cli.process orElse
|
||||
lila.round.Env.current.cli.process orElse
|
||||
lila.puzzle.Env.current.cli.process orElse
|
||||
process
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ private[api] final class GameApi(
|
|||
"name" -> p.name,
|
||||
"rating" -> p.rating,
|
||||
"ratingDiff" -> p.ratingDiff,
|
||||
"provisional" -> p.provisional.option(true),
|
||||
"moveTimes" -> withMoveTimes.fold(
|
||||
g.moveTimes.zipWithIndex.filter(_._2 % 2 == i).map(_._1),
|
||||
JsNull),
|
||||
|
|
|
@ -36,7 +36,7 @@ final class BlogApi(prismicUrl: String, collection: String) {
|
|||
|
||||
object BlogApi {
|
||||
|
||||
def extract(body: Fragment.StructuredText): String =
|
||||
def extract(body: fragments.StructuredText): String =
|
||||
body.blocks
|
||||
.takeWhile(_.isInstanceOf[Fragment.StructuredText.Block.Paragraph])
|
||||
.take(2).map {
|
||||
|
|
|
@ -48,6 +48,14 @@ final class Env(
|
|||
|
||||
lazy val pngExport = PngExport(PngExecPath) _
|
||||
|
||||
def cli = new lila.common.Cli {
|
||||
def process = {
|
||||
case "puzzle" :: "export" :: nbStr :: Nil => parseIntOption(nbStr) ?? { nb =>
|
||||
Export(api, nb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[puzzle] lazy val puzzleColl = db(CollectionPuzzle)
|
||||
private[puzzle] lazy val attemptColl = db(CollectionAttempt)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package lila.puzzle
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Base64
|
||||
import play.api.libs.json._
|
||||
|
||||
object Export {
|
||||
|
||||
private def base64(str: String) = Base64.getEncoder.encodeToString(str getBytes StandardCharsets.UTF_8)
|
||||
|
||||
def apply(api: PuzzleApi, nb: Int) =
|
||||
api.puzzle.export(nb * 2).map { puzzles =>
|
||||
puzzles.map { puzzle =>
|
||||
val encoded = base64(Json stringify {
|
||||
Json.obj(
|
||||
"id" -> puzzle.id,
|
||||
"fen" -> puzzle.fen,
|
||||
"color" -> puzzle.color.name,
|
||||
"move" -> puzzle.initialMove,
|
||||
"lines" -> lila.puzzle.Line.toJson(puzzle.lines))
|
||||
})
|
||||
s""""$encoded"""" -> puzzle.vote.sum
|
||||
}.sortBy(_._1.size)
|
||||
.take(nb)
|
||||
.sortBy(_._2 -> scala.util.Random.nextInt)
|
||||
.map(_._1)
|
||||
.mkString(",\n")
|
||||
}
|
||||
}
|
|
@ -53,6 +53,12 @@ private[puzzle] final class PuzzleApi(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
def export(nb: Int): Fu[List[Puzzle]] = List(true, false).map { mate =>
|
||||
puzzleColl.find(BSONDocument("mate" -> mate))
|
||||
.sort(BSONDocument(Puzzle.BSONFields.voteSum -> -1))
|
||||
.cursor[Puzzle].collect[List](nb / 2)
|
||||
}.sequenceFu.map(_.flatten)
|
||||
}
|
||||
|
||||
object attempt {
|
||||
|
|
|
@ -40,7 +40,7 @@ private[puzzle] final class Selector(
|
|||
.one[Puzzle]
|
||||
case Some(user) if user.perfs.puzzle.nb > maxAttempts => fuccess(none)
|
||||
case Some(user) =>
|
||||
val rating = user.perfs.puzzle.intRating min 2200 max 900
|
||||
val rating = user.perfs.puzzle.intRating min 2300 max 900
|
||||
val step = toleranceStepFor(rating)
|
||||
api.attempt.playedIds(user, maxAttempts) flatMap { ids =>
|
||||
tryRange(rating, step, step, difficultyDecay(difficulty), ids, isMate)
|
||||
|
|
|
@ -121,7 +121,7 @@ final class Env(
|
|||
organizer -> actorApi.AllCreatedTournaments
|
||||
}
|
||||
|
||||
scheduler.message(4 seconds) {
|
||||
scheduler.message(3 seconds) {
|
||||
organizer -> actorApi.StartedTournaments
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@ private[tournament] final class Organizer(
|
|||
}
|
||||
}
|
||||
|
||||
case FinishGame(game, _, _) => api finishGame game
|
||||
case FinishGame(game, _, _) => api finishGame game
|
||||
|
||||
case lila.hub.actorApi.mod.MarkCheater(userId) => api ejectCheater userId
|
||||
case lila.hub.actorApi.mod.MarkCheater(userId) => api ejectCheater userId
|
||||
|
||||
case lila.hub.actorApi.round.Berserk(gameId, userId) => api.berserk(gameId, userId)
|
||||
}
|
||||
|
@ -58,18 +58,15 @@ private[tournament] final class Organizer(
|
|||
_ filterNot isOnline foreach { api.withdraw(tour.id, _) }
|
||||
}
|
||||
|
||||
private def startPairing(tour: Tournament, activeUserIds: List[String]) = for {
|
||||
socketUserIds <- getSocketUserIds(tour)
|
||||
allUsers = socketUserIds.copy(
|
||||
all = activeUserIds intersect socketUserIds.all,
|
||||
waiting = activeUserIds intersect socketUserIds.waiting)
|
||||
} {
|
||||
tour.system.pairingSystem.createPairings(tour, allUsers) onSuccess {
|
||||
case (pairings, events) =>
|
||||
pairings.toNel foreach { api.makePairings(tour, _, events) }
|
||||
private def startPairing(tour: Tournament, activeUserIds: List[String]) =
|
||||
getWaitingUsers(tour) zip PairingRepo.playingUserIds(tour) foreach {
|
||||
case (waitingUsers, playingUserIds) =>
|
||||
val users = waitingUsers intersect activeUserIds diff playingUserIds
|
||||
tour.system.pairingSystem.createPairings(tour, users) onSuccess {
|
||||
case (pairings, events) => pairings.toNel foreach { api.makePairings(tour, _, events) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getSocketUserIds(tour: Tournament): Fu[AllUserIds] =
|
||||
socketHub ? Ask(tour.id, GetAllUserIds) mapTo manifest[AllUserIds]
|
||||
private def getWaitingUsers(tour: Tournament): Fu[WaitingUsers] =
|
||||
socketHub ? Ask(tour.id, GetWaitingUsers) mapTo manifest[WaitingUsers]
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package lila.tournament
|
|||
|
||||
import akka.actor._
|
||||
import akka.pattern.pipe
|
||||
import org.joda.time.DateTime
|
||||
import play.api.libs.iteratee._
|
||||
import play.api.libs.json._
|
||||
import scala.concurrent.duration._
|
||||
|
@ -32,6 +31,8 @@ private[tournament] final class Socket(
|
|||
|
||||
private var clock = none[chess.Clock]
|
||||
|
||||
private var waitingUsers = WaitingUsers.empty
|
||||
|
||||
override def preStart() {
|
||||
super.preStart()
|
||||
TournamentRepo byId tournamentId map SetTournament.apply pipeTo self
|
||||
|
@ -52,9 +53,11 @@ private[tournament] final class Socket(
|
|||
}
|
||||
notifyReload
|
||||
|
||||
case Reload => notifyReload
|
||||
case Reload => notifyReload
|
||||
|
||||
case GetAllUserIds => sender ! AllUserIds(all = userIds, waiting = waitingUserIds)
|
||||
case GetWaitingUsers =>
|
||||
waitingUsers = waitingUsers.update(userIds, clock)
|
||||
sender ! waitingUsers
|
||||
|
||||
case PingVersion(uid, v) => {
|
||||
ping(uid)
|
||||
|
@ -103,28 +106,6 @@ private[tournament] final class Socket(
|
|||
notifyAll("reload")
|
||||
}
|
||||
|
||||
private var waitingUsers = Map[String, DateTime]()
|
||||
|
||||
// users that have been here for some time
|
||||
def waitingUserIds: List[String] = {
|
||||
val us = userIds
|
||||
val date = DateTime.now
|
||||
waitingUsers = waitingUsers.filterKeys { u =>
|
||||
us contains u
|
||||
}.++ {
|
||||
us.filterNot(waitingUsers.contains).map { _ -> date }
|
||||
}
|
||||
// 1+0 -> 5 -> 8
|
||||
// 3+0 -> 9 -> 11
|
||||
// 5+0 -> 17 -> 17
|
||||
// 10+0 -> 32 -> 30
|
||||
val waitSeconds = ((clock.fold(60)(_.estimateTotalTime) / 20) + 2) min 30 max 8
|
||||
val since = date minusSeconds waitSeconds
|
||||
waitingUsers.collect {
|
||||
case (u, d) if d.isBefore(since) => u
|
||||
}.toList
|
||||
}
|
||||
|
||||
def notifyCrowd {
|
||||
if (!delayedCrowdNotification) {
|
||||
delayedCrowdNotification = true
|
||||
|
|
|
@ -28,7 +28,7 @@ object System {
|
|||
trait PairingSystem {
|
||||
def createPairings(
|
||||
tournament: Tournament,
|
||||
users: AllUserIds): Fu[(Pairings, Events)]
|
||||
users: WaitingUsers): Fu[(Pairings, Events)]
|
||||
}
|
||||
|
||||
trait Score {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package lila.tournament
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
private[tournament] case class WaitingUsers(
|
||||
hash: Map[String, DateTime],
|
||||
clock: Option[chess.Clock],
|
||||
date: DateTime) {
|
||||
|
||||
// 1+0 -> 5 -> 7
|
||||
// 3+0 -> 9 -> 11
|
||||
// 5+0 -> 17 -> 17
|
||||
// 10+0 -> 32 -> 30
|
||||
private val waitSeconds = {
|
||||
(clock.fold(60)(_.estimateTotalTime) / 20) + 2
|
||||
} min 30 max 7
|
||||
|
||||
lazy val all = hash.keys.toList
|
||||
lazy val size = hash.size
|
||||
|
||||
def isOdd = size % 2 == 1
|
||||
|
||||
// skips the most recent user if odd
|
||||
def evenNumber: List[String] = {
|
||||
if (isOdd) hash.toList.sortBy(-_._2.getMillis).drop(1).map(_._1)
|
||||
else all
|
||||
}
|
||||
|
||||
def waitSecondsOf(userId: String) = hash get userId map { d =>
|
||||
nowSeconds - d.getSeconds
|
||||
}
|
||||
|
||||
def waiting = {
|
||||
val since = date minusSeconds waitSeconds
|
||||
hash.collect {
|
||||
case (u, d) if d.isBefore(since) => u
|
||||
}.toList
|
||||
}
|
||||
|
||||
def update(us: Seq[String], clock: Option[chess.Clock]) = {
|
||||
val newDate = DateTime.now
|
||||
copy(
|
||||
date = newDate,
|
||||
clock = clock,
|
||||
hash = hash.filterKeys(us.contains) ++
|
||||
us.filterNot(hash.contains).map { _ -> newDate }
|
||||
)
|
||||
}
|
||||
|
||||
def intersect(us: Seq[String]) = copy(hash = hash filterKeys us.contains)
|
||||
|
||||
def diff(us: Set[String]) = copy(hash = hash filterKeys { k => !us.contains(k) })
|
||||
}
|
||||
|
||||
private[tournament] object WaitingUsers {
|
||||
def empty = WaitingUsers(Map.empty, none, DateTime.now)
|
||||
}
|
|
@ -39,6 +39,6 @@ private[tournament] case object ScheduleNow
|
|||
private[tournament] case object NotifyCrowd
|
||||
private[tournament] case object NotifyReload
|
||||
|
||||
private[tournament] case object GetAllUserIds
|
||||
private[tournament] case object GetWaitingUsers
|
||||
|
||||
private[tournament] case class SetTournament(tour: Option[Tournament])
|
||||
|
|
|
@ -11,7 +11,6 @@ object PairingSystem extends AbstractPairingSystem {
|
|||
case class Data(
|
||||
tour: Tournament,
|
||||
recentPairings: List[Pairing],
|
||||
playingUserIds: Set[String],
|
||||
ranking: Map[String, Int],
|
||||
nbActiveUsers: Int)
|
||||
|
||||
|
@ -19,39 +18,37 @@ object PairingSystem extends AbstractPairingSystem {
|
|||
// then pair all users
|
||||
def createPairings(
|
||||
tour: Tournament,
|
||||
users: AllUserIds): Fu[(Pairings, Events)] = for {
|
||||
recentPairings <- PairingRepo.recentByTourAndUserIds(tour.id, users.all, Math.min(100, users.all.size * 5))
|
||||
playingUserIds <- PairingRepo.playingUserIds(tour)
|
||||
users: WaitingUsers): Fu[(Pairings, Events)] = for {
|
||||
recentPairings <- PairingRepo.recentByTourAndUserIds(tour.id, users.all, Math.min(120, users.size * 5))
|
||||
nbActiveUsers <- PlayerRepo.countActive(tour.id)
|
||||
ranking <- PlayerRepo.ranking(tour.id)
|
||||
data = Data(tour, recentPairings, playingUserIds, ranking, nbActiveUsers)
|
||||
pairings <- tryPairings(data, users.waiting) flatMap {
|
||||
case Nil if recentPairings.isEmpty => tryPairings(data, users.all)
|
||||
case Nil => fuccess(Nil)
|
||||
case _ => tryPairings(data, users.all)
|
||||
data = Data(tour, recentPairings, ranking, nbActiveUsers)
|
||||
pairings <- {
|
||||
if (recentPairings.isEmpty) evenOrAll(data, users)
|
||||
else tryPairings(data, users.waiting) flatMap {
|
||||
case Nil => fuccess(Nil)
|
||||
case _ => evenOrAll(data, users)
|
||||
}
|
||||
}
|
||||
} yield {
|
||||
if (pairings.nonEmpty && pairings.size * 2 < users.all.size) {
|
||||
val left = users.all diff pairings.flatMap(_.users)
|
||||
if (left.nonEmpty) println(s"[arena ${tour.id}] left over: ${left mkString ","} out of ${users.all.size}")
|
||||
} yield pairings -> Nil
|
||||
|
||||
private def evenOrAll(data: Data, users: WaitingUsers) =
|
||||
tryPairings(data, users.evenNumber) flatMap {
|
||||
case Nil if users.isOdd => tryPairings(data, users.all)
|
||||
case x => fuccess(x)
|
||||
}
|
||||
pairings -> Nil
|
||||
}
|
||||
|
||||
val smartHardLimit = 24
|
||||
|
||||
private def tryPairings(data: Data, users: List[String]): Fu[Pairings] = {
|
||||
import data._
|
||||
if (users.size < 2) fuccess(Nil)
|
||||
else PlayerRepo.rankedByTourAndUserIds(
|
||||
tour.id,
|
||||
users.toSet diff playingUserIds,
|
||||
ranking) map { idles =>
|
||||
if (recentPairings.isEmpty) naivePairings(tour, idles)
|
||||
else
|
||||
smartPairings(data, idles take smartHardLimit) :::
|
||||
naivePairings(tour, idles drop smartHardLimit)
|
||||
}
|
||||
else PlayerRepo.rankedByTourAndUserIds(tour.id, users, ranking) map { idles =>
|
||||
if (recentPairings.isEmpty) naivePairings(tour, idles)
|
||||
else
|
||||
smartPairings(data, idles take smartHardLimit) :::
|
||||
naivePairings(tour, idles drop smartHardLimit)
|
||||
}
|
||||
}
|
||||
|
||||
private def naivePairings(tour: Tournament, players: RankedPlayers) =
|
||||
|
@ -81,11 +78,12 @@ object PairingSystem extends AbstractPairingSystem {
|
|||
|
||||
// lower is better
|
||||
def pairingScore(pair: RankedPairing): Score = pair match {
|
||||
case (a, b) if justPlayedTogether(a.player.userId, b.player.userId) =>
|
||||
if (veryMuchJustPlayedTogether(a.player.userId, b.player.userId)) 9000 * 1000
|
||||
else 8000 * 1000
|
||||
case (a, b) => Math.abs(a.rank - b.rank) * 1000 +
|
||||
Math.abs(a.player.rating - b.player.rating)
|
||||
Math.abs(a.player.rating - b.player.rating) +
|
||||
justPlayedTogether(a.player.userId, b.player.userId).?? {
|
||||
if (veryMuchJustPlayedTogether(a.player.userId, b.player.userId)) 9000 * 1000
|
||||
else 8000 * 1000
|
||||
}
|
||||
}
|
||||
def score(pairs: Combination): Score = pairs.foldLeft(0) {
|
||||
case (s, p) => s + pairingScore(p)
|
||||
|
|
|
@ -4,15 +4,17 @@ import lila.socket.WithSocket
|
|||
|
||||
package object tournament extends PackageObject with WithPlay with WithSocket {
|
||||
|
||||
private[tournament] type Players = List[tournament.Player]
|
||||
private[tournament]type Players = List[tournament.Player]
|
||||
|
||||
private[tournament] type RankedPlayers = List[RankedPlayer]
|
||||
private[tournament]type RankedPlayers = List[RankedPlayer]
|
||||
|
||||
private[tournament] type Pairings = List[tournament.Pairing]
|
||||
private[tournament]type Pairings = List[tournament.Pairing]
|
||||
|
||||
private[tournament] type Events = List[tournament.Event]
|
||||
private[tournament]type Events = List[tournament.Event]
|
||||
|
||||
private[tournament] type Ranking = Map[String, Int]
|
||||
private[tournament]type Ranking = Map[String, Int]
|
||||
|
||||
private[tournament]type Waiting = Map[String, Int]
|
||||
|
||||
private[tournament] object RandomName {
|
||||
|
||||
|
@ -31,6 +33,4 @@ case class RankedPlayer(rank: Int, player: Player) {
|
|||
}
|
||||
|
||||
case class Winner(tourId: String, tourName: String, userId: String)
|
||||
|
||||
private[tournament] case class AllUserIds(all: List[String], waiting: List[String])
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ object Dependencies {
|
|||
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.3-THIB"
|
||||
val prismic = "io.prismic" %% "scala-kit" % "1.3.1"
|
||||
val prismic = "io.prismic" %% "scala-kit" % "1.3.3"
|
||||
|
||||
object play {
|
||||
val version = "2.3.9"
|
||||
|
|
|
@ -1514,8 +1514,9 @@ div.game_tournament .clock {
|
|||
div.game_tournament table.standing {
|
||||
border-bottom: none;
|
||||
}
|
||||
div.game_tournament table.standing tr.me td {
|
||||
background: #e4e4e4;
|
||||
div.game_tournament table.standing tr.me td:first-child {
|
||||
border-left: 10px solid #d59120;
|
||||
padding-left: 5px;
|
||||
}
|
||||
div.game_tournament table.standing td {
|
||||
padding: 0 10px;
|
||||
|
|
|
@ -439,6 +439,9 @@ body {
|
|||
overflow-x: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
body.no-bg {
|
||||
background: none!important;
|
||||
}
|
||||
body.no-select {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
|
@ -390,9 +390,6 @@ body.dark #puzzle > .right .please_vote {
|
|||
background: #103410;
|
||||
color: #74a962;
|
||||
}
|
||||
body.dark div.game_tournament table.standing tr.me td {
|
||||
background: #333;
|
||||
}
|
||||
body.dark #tournament_list table.slist .icon span {
|
||||
color: rgba(200,200,200,0.5);
|
||||
text-shadow: 2px 2px 2px #000;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var merge = require('lodash/object/merge');
|
||||
var merge = require('merge');
|
||||
var m = require('mithril');
|
||||
var chess = require('./chess');
|
||||
var puzzle = require('./puzzle');
|
||||
|
@ -16,7 +16,7 @@ module.exports = function(cfg) {
|
|||
|
||||
if (cfg.user) cfg.user.history = cfg.user.history || [];
|
||||
|
||||
merge(data, cfg);
|
||||
merge.recursive(data, cfg);
|
||||
|
||||
data.puzzle.initialMove = puzzle.str2move(data.puzzle.initialMove);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ function miniGame(game) {
|
|||
|
||||
module.exports = {
|
||||
title: function(ctrl) {
|
||||
if (ctrl.data.schedule && ctrl.data.schedule.freq.indexOf('marathon') !== -1)
|
||||
if (ctrl.data.schedule && ctrl.data.schedule.freq.indexOf('Marathon') !== -1)
|
||||
return m('h1.marathon_title', [
|
||||
m('span.fire_trophy.marathonWinner', m('span[data-icon=\\]')),
|
||||
ctrl.data.fullName
|
||||
|
|
Loading…
Reference in New Issue