Merge branch 'master' into puzzle-ui
* master: (159 commits) don't auto-pair pool-compatible hooks monitor color of standard hooks inc assets version nb "Norsk bokmål" translation #17028. Author: hmalmedal. ia "Interlingua" translation #17027. Author: GuimaraesMello. make popular pools faster remove unused lobby NbHooks socket message make pools steal hooks from lobby remove support for hiding lobby hooks steal hooks for the pool - WIP tweak hook compatibility function cache hook computations clock configs everywhere use clock configs for simuls use clock configs in pools use clock configs in tournaments Clock.Config Hook.poolCompatible honor more hooks rating range when converting to pool fix forum mention autocomplete with titled players ...puzzle-ui
commit
1b78f74d4c
|
@ -87,7 +87,6 @@ final class Env(
|
|||
Env.playban, // required to load the actor
|
||||
Env.shutup, // required to load the actor
|
||||
Env.insight, // required to load the actor
|
||||
Env.worldMap, // required to load the actor
|
||||
Env.push, // required to load the actor
|
||||
Env.perfStat, // required to load the actor
|
||||
Env.slack, // required to load the actor
|
||||
|
@ -152,7 +151,6 @@ object Env {
|
|||
def blog = lila.blog.Env.current
|
||||
def qa = lila.qa.Env.current
|
||||
def history = lila.history.Env.current
|
||||
def worldMap = lila.worldMap.Env.current
|
||||
def video = lila.video.Env.current
|
||||
def playban = lila.playban.Env.current
|
||||
def shutup = lila.shutup.Env.current
|
||||
|
@ -169,4 +167,5 @@ object Env {
|
|||
def plan = lila.plan.Env.current
|
||||
def event = lila.event.Env.current
|
||||
def coach = lila.coach.Env.current
|
||||
def pool = lila.pool.Env.current
|
||||
}
|
||||
|
|
|
@ -150,8 +150,8 @@ object Api extends LilaController {
|
|||
} map toApiResult
|
||||
}
|
||||
|
||||
def gameStream = Action { req =>
|
||||
val userIds = get("users", req).??(_.split(',').take(300).toSet map lila.user.User.normalize)
|
||||
def gameStream = Action(parse.tolerantText) { req =>
|
||||
val userIds = req.body.split(',').take(300).toSet map lila.user.User.normalize
|
||||
Ok.chunked(Env.game.stream.startedByUserIds(userIds))
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ object ForumTopic extends LilaController with ForumController {
|
|||
*/
|
||||
def participants(topicId: String) = Auth { implicit ctx => me =>
|
||||
postApi.userIds(topicId) map { ids =>
|
||||
val usernames = Env.user.lightUserApi.getList(ids.sorted).map(_.titleName)
|
||||
val usernames = Env.user.lightUserApi.getList(ids.sorted).map(_.name)
|
||||
Ok(Json.toJson(usernames))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import play.api.http._
|
|||
import play.api.libs.iteratee.{ Iteratee, Enumerator }
|
||||
import play.api.libs.json.{ Json, JsValue, JsObject, JsArray, Writes }
|
||||
import play.api.mvc._, Results._
|
||||
import play.api.mvc.WebSocket.FrameFormatter
|
||||
import play.twirl.api.Html
|
||||
import scalaz.Monoid
|
||||
|
||||
|
@ -52,38 +51,6 @@ private[controllers] trait LilaController
|
|||
"X-Frame-Options" -> "SAMEORIGIN"
|
||||
)
|
||||
|
||||
protected def Socket[A: FrameFormatter](f: Context => Fu[(Iteratee[A, _], Enumerator[A])]) =
|
||||
WebSocket.tryAccept[A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap f map scala.util.Right.apply
|
||||
}
|
||||
}
|
||||
|
||||
protected def SocketEither[A: FrameFormatter](f: Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]]) =
|
||||
WebSocket.tryAccept[A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap f
|
||||
}
|
||||
}
|
||||
|
||||
protected def SocketOption[A: FrameFormatter](f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) =
|
||||
WebSocket.tryAccept[A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap f map {
|
||||
case None => Left(NotFound(jsonError("socket resource not found")))
|
||||
case Some(pair) => Right(pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected def SocketOptionLimited[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) =
|
||||
rateLimitedSocket[A](consumer, name) { ctx =>
|
||||
f(ctx) map {
|
||||
case None => Left(NotFound(jsonError("socket resource not found")))
|
||||
case Some(pair) => Right(pair)
|
||||
}
|
||||
}
|
||||
|
||||
protected def Open(f: Context => Fu[Result]): Action[Unit] =
|
||||
Open(BodyParsers.parse.empty)(f)
|
||||
|
||||
|
@ -357,15 +324,12 @@ private[controllers] trait LilaController
|
|||
}
|
||||
}
|
||||
|
||||
private val csrfCheck = Env.security.csrfRequestHandler.check _
|
||||
private val csrfForbiddenResult = Forbidden("Cross origin request forbidden").fuccess
|
||||
protected val csrfCheck = Env.security.csrfRequestHandler.check _
|
||||
protected val csrfForbiddenResult = Forbidden("Cross origin request forbidden").fuccess
|
||||
|
||||
private def CSRF(req: RequestHeader)(f: => Fu[Result]): Fu[Result] =
|
||||
if (csrfCheck(req)) f else csrfForbiddenResult
|
||||
|
||||
protected def SocketCSRF[A](req: RequestHeader)(f: => Fu[Either[Result, A]]): Fu[Either[Result, A]] =
|
||||
if (csrfCheck(req)) f else csrfForbiddenResult map Left.apply
|
||||
|
||||
protected def XhrOnly(res: => Fu[Result])(implicit ctx: Context) =
|
||||
if (HTTPRequest isXhr ctx.req) res else notFound
|
||||
|
||||
|
|
|
@ -12,11 +12,37 @@ import lila.common.HTTPRequest
|
|||
|
||||
trait LilaSocket { self: LilaController =>
|
||||
|
||||
private type AcceptType[A] = Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]]
|
||||
private type Pipe[A] = (Iteratee[A, _], Enumerator[A])
|
||||
|
||||
private val logger = lila.log("ratelimit")
|
||||
private val notFoundResponse = NotFound(jsonError("socket resource not found"))
|
||||
|
||||
def rateLimitedSocket[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: AcceptType[A]): WebSocket[A, A] =
|
||||
protected def SocketEither[A: FrameFormatter](f: Context => Fu[Either[Result, Pipe[A]]]) =
|
||||
WebSocket.tryAccept[A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap f
|
||||
}
|
||||
}
|
||||
|
||||
protected def Socket[A: FrameFormatter](f: Context => Fu[Pipe[A]]) =
|
||||
SocketEither[A] { ctx =>
|
||||
f(ctx) map scala.util.Right.apply
|
||||
}
|
||||
|
||||
protected def SocketOption[A: FrameFormatter](f: Context => Fu[Option[Pipe[A]]]) =
|
||||
SocketEither[A] { ctx =>
|
||||
f(ctx).map(_ toRight notFoundResponse)
|
||||
}
|
||||
|
||||
protected def SocketOptionLimited[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: Context => Fu[Option[Pipe[A]]]) =
|
||||
rateLimitedSocket[A](consumer, name) { ctx =>
|
||||
f(ctx).map(_ toRight notFoundResponse)
|
||||
}
|
||||
|
||||
private type AcceptType[A] = Context => Fu[Either[Result, Pipe[A]]]
|
||||
|
||||
private val rateLimitLogger = lila.log("ratelimit")
|
||||
|
||||
private def rateLimitedSocket[A: FrameFormatter](consumer: TokenBucket.Consumer, name: String)(f: AcceptType[A]): WebSocket[A, A] =
|
||||
WebSocket[A, A] { req =>
|
||||
SocketCSRF(req) {
|
||||
reqToCtx(req) flatMap { ctx =>
|
||||
|
@ -26,7 +52,6 @@ trait LilaSocket { self: LilaController =>
|
|||
val username = ctx.usernameOrAnon
|
||||
s"user:$username sri:$sri"
|
||||
}
|
||||
// logger.debug(s"socket:$name socket connect $ip $userInfo")
|
||||
f(ctx).map { resultOrSocket =>
|
||||
resultOrSocket.right.map {
|
||||
case (readIn, writeOut) => (e, i) => {
|
||||
|
@ -35,7 +60,7 @@ trait LilaSocket { self: LilaController =>
|
|||
consumer(ip).map { credit =>
|
||||
if (credit >= 0) in
|
||||
else {
|
||||
logger.info(s"socket:$name socket close $ip $userInfo $in")
|
||||
rateLimitLogger.info(s"socket:$name socket close $ip $userInfo $in")
|
||||
Input.EOF
|
||||
}
|
||||
}
|
||||
|
@ -46,4 +71,7 @@ trait LilaSocket { self: LilaController =>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def SocketCSRF[A](req: RequestHeader)(f: => Fu[Either[Result, A]]): Fu[Either[Result, A]] =
|
||||
if (csrfCheck(req)) f else csrfForbiddenResult map Left.apply
|
||||
}
|
||||
|
|
|
@ -12,15 +12,14 @@ import views._
|
|||
|
||||
object Lobby extends LilaController {
|
||||
|
||||
private val lobbyJson = Json.obj(
|
||||
"lobby" -> Json.obj("version" -> 0)
|
||||
)
|
||||
|
||||
def home = Open { implicit ctx =>
|
||||
negotiate(
|
||||
html = renderHome(Results.Ok).map(NoCache),
|
||||
api = _ => fuccess {
|
||||
Ok(Json.obj(
|
||||
"lobby" -> Json.obj(
|
||||
"version" -> Env.lobby.history.version)
|
||||
))
|
||||
}
|
||||
api = _ => fuccess(Ok(lobbyJson))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -48,10 +47,7 @@ object Lobby extends LilaController {
|
|||
|
||||
def socket(apiVersion: Int) = SocketOptionLimited[JsValue](socketConsumer, "lobby") { implicit ctx =>
|
||||
get("sri") ?? { uid =>
|
||||
Env.lobby.socketHandler(
|
||||
uid = uid,
|
||||
user = ctx.me,
|
||||
mobile = getBool("mobile")) map some
|
||||
Env.lobby.socketHandler(uid = uid, user = ctx.me, mobile = getBool("mobile")) map some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,12 @@ object Puzzle extends LilaController {
|
|||
}
|
||||
case None =>
|
||||
lila.mon.puzzle.round.anon()
|
||||
renderJson(puzzle, none, "view", win = data.isWin.some, voted = none) map { Ok(_) }
|
||||
env.finisher.incPuzzleAttempts(puzzle)
|
||||
Ok(JsData(puzzle, none, "view",
|
||||
win = data.isWin.some,
|
||||
voted = none,
|
||||
animationDuration = env.AnimationDuration))
|
||||
}
|
||||
}
|
||||
) map (_ as JSON)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ object Report extends LilaController {
|
|||
|
||||
def clarkeyBotNext = Open { implicit ctx =>
|
||||
Mod.ModExternalBot {
|
||||
api unprocessedAndRecent 50 map { all =>
|
||||
api unprocessedAndRecent 100 map { all =>
|
||||
all.find { r =>
|
||||
r.report.isCheat && r.report.unprocessed && !r.hasLichessNote &&
|
||||
!clarkeyProcessedUserIds.get(r.user.id)
|
||||
|
|
|
@ -38,9 +38,8 @@ object Setup extends LilaController with TheftPrevention {
|
|||
}
|
||||
}
|
||||
|
||||
def ai = process(env.forms.ai) { config =>
|
||||
implicit ctx =>
|
||||
env.processor ai config
|
||||
def ai = process(env.forms.ai) { config => implicit ctx =>
|
||||
env.processor ai config
|
||||
}
|
||||
|
||||
def friendForm(userId: Option[String]) = Open { implicit ctx =>
|
||||
|
@ -78,10 +77,10 @@ object Setup extends LilaController with TheftPrevention {
|
|||
variant = config.variant,
|
||||
initialFen = config.fen,
|
||||
timeControl = config.makeClock map { c =>
|
||||
TimeControl.Clock(c.limit, c.increment)
|
||||
} orElse config.makeDaysPerTurn.map {
|
||||
TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited,
|
||||
TimeControl.Clock(chess.Clock.Config(c.limit, c.increment))
|
||||
} orElse config.makeDaysPerTurn.map {
|
||||
TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited,
|
||||
mode = config.mode,
|
||||
color = config.color.name,
|
||||
challenger = (ctx.me, HTTPRequest sid req) match {
|
||||
|
@ -123,7 +122,7 @@ object Setup extends LilaController with TheftPrevention {
|
|||
case HookResult.Refused => BadRequest(jsonError("Game was not created"))
|
||||
}
|
||||
|
||||
private val hookRefused = BadRequest(jsonError("Game was not created"))
|
||||
private val hookSaveOnlyResponse = Ok(Json.obj("ok" -> true))
|
||||
|
||||
def hook(uid: String) = OpenBody { implicit ctx =>
|
||||
implicit val req = ctx.body
|
||||
|
@ -133,10 +132,12 @@ object Setup extends LilaController with TheftPrevention {
|
|||
err => negotiate(
|
||||
html = BadRequest(errorsAsJson(err).toString).fuccess,
|
||||
api = _ => BadRequest(errorsAsJson(err)).fuccess),
|
||||
config => (ctx.userId ?? Env.relation.api.fetchBlocking) flatMap {
|
||||
blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse
|
||||
}
|
||||
config =>
|
||||
if (getBool("pool")) env.processor.saveHookConfig(config) inject hookSaveOnlyResponse
|
||||
else (ctx.userId ?? Env.relation.api.fetchBlocking) flatMap {
|
||||
blocking =>
|
||||
env.processor.hook(config, uid, HTTPRequest sid req, blocking) map hookResponse
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import play.api.libs.EventSource
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._, Results._
|
||||
|
||||
import lila.app._
|
||||
import views._
|
||||
|
||||
object WorldMap extends LilaController {
|
||||
|
||||
def index = Action {
|
||||
Ok(views.html.site.worldMap())
|
||||
}
|
||||
|
||||
def stream = Action.async {
|
||||
Env.worldMap.getStream map { stream =>
|
||||
Ok.chunked(
|
||||
stream &> EventSource()
|
||||
) as "text/event-stream"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
description = describePov(pov))
|
||||
|
||||
def titleGame(g: Game) = {
|
||||
val speed = chess.Speed(g.clock).name
|
||||
val speed = chess.Speed(g.clock.map(_.config)).name
|
||||
val variant = g.variant.exotic ?? s" ${g.variant.name}"
|
||||
s"$speed$variant Chess • ${playerText(g.whitePlayer)} vs ${playerText(g.blackPlayer)}"
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
val speedAndClock =
|
||||
if (game.imported) "imported"
|
||||
else game.clock.fold(chess.Speed.Correspondence.name) { c =>
|
||||
s"${chess.Speed(c.some).name} (${c.show})"
|
||||
s"${chess.Speed(c.config).name} (${c.show})"
|
||||
}
|
||||
val mode = game.mode.name
|
||||
val variant = if (game.variant == chess.variant.FromPosition) "position setup chess"
|
||||
|
@ -75,10 +75,10 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
case v => v.name
|
||||
}
|
||||
|
||||
def shortClockName(clock: Option[Clock])(implicit ctx: UserContext): Html =
|
||||
def shortClockName(clock: Option[Clock.Config])(implicit ctx: UserContext): Html =
|
||||
clock.fold(trans.unlimited())(shortClockName)
|
||||
|
||||
def shortClockName(clock: Clock): Html = Html(clock.show)
|
||||
def shortClockName(clock: Clock.Config): Html = Html(clock.show)
|
||||
|
||||
def modeName(mode: Mode)(implicit ctx: UserContext): String = mode match {
|
||||
case Mode.Casual => trans.casual.str()
|
||||
|
@ -248,7 +248,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
|
|||
}
|
||||
|
||||
def challengeTitle(c: lila.challenge.Challenge)(implicit ctx: UserContext) = {
|
||||
val speed = c.clock.map(_.chessClock).fold(trans.unlimited.str()) { clock =>
|
||||
val speed = c.clock.map(_.config).fold(trans.unlimited.str()) { clock =>
|
||||
s"${chess.Speed(clock).name} (${clock.show})"
|
||||
}
|
||||
val variant = c.variant.exotic ?? s" ${c.variant.name}"
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
<a href="/mobile">@trans.mobileApp()</a> ı
|
||||
}
|
||||
<a href="/blog">@trans.blog()</a> ı
|
||||
<a href="/network">World map</a> ı
|
||||
@NotForKids {
|
||||
<a href="/developers">@trans.webmasters()</a> ı
|
||||
<a href="/help/contribute">@trans.contribute()</a> ı
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
@c.daysPerTurn.map { days =>
|
||||
<span class="text" data-icon=";">@{(days == 1).fold(trans.oneDay(), trans.nbDays(days))}</span>
|
||||
}.getOrElse {
|
||||
<span class="text" data-icon="p">@shortClockName(c.clock.map(_.chessClock))</span>
|
||||
<span class="text" data-icon="p">@shortClockName(c.clock.map(_.config))</span>
|
||||
}
|
||||
</p>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
@lightUser(pov.opponent.userId).flatMap(_.title).map(" " + _)
|
||||
</div>
|
||||
@pov.game.clock.map { c =>
|
||||
<div class="center"><span data-icon="p"> @shortClockName(c)</span></div>
|
||||
<div class="center"><span data-icon="p"> @shortClockName(c.config)</span></div>
|
||||
}.getOrElse {
|
||||
@ctxOption.map { ctx =>
|
||||
@pov.game.daysPerTurn.map { days =>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@userIdLink(simul.hostId.some)
|
||||
</td>
|
||||
<td class="small">
|
||||
<span class="text" data-icon="p">@simul.clock.show</span>
|
||||
<span class="text" data-icon="p">@simul.clock.config.show</span>
|
||||
</td>
|
||||
<td class="text" data-icon="r">@simul.applicants.size</td>
|
||||
<td>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@(sim: lila.simul.Simul)(implicit ctx: Context)
|
||||
<span class="setup @if(sim.variantRich){rich}">
|
||||
@sim.clock.show •
|
||||
@sim.clock.config.show •
|
||||
@sim.variants.map(_.name).mkString(", ")
|
||||
</span>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<span data-icon="@pt.iconChar"></span>
|
||||
}
|
||||
</div>
|
||||
<span class="clock">@sim.clock.show</span><br />
|
||||
<span class="clock">@sim.clock.config.show</span><br />
|
||||
<div class="setup">
|
||||
@sim.variants.map(_.name).mkString(", ") • @trans.casual()
|
||||
</div>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
@()
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Lichess World Map</title>
|
||||
<link rel="shortcut icon" href="@routes.Assets.at("images/favicon-32-white.png")" type="image/x-icon" />
|
||||
@cssAt("worldMap/main.css")
|
||||
<meta content="noindex, nofollow" name="robots">
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="//lichess.org">lichess<span class="extension">.org</span></a> network</h1>
|
||||
<div id="worldmap"></div>
|
||||
<div id="legend">
|
||||
<h1>Map of live games</h1>
|
||||
Orange: games in progress<br />
|
||||
White: game creations
|
||||
</div>
|
||||
@jQueryTag
|
||||
@jsAt("worldMap/raphael-min.js")
|
||||
@jsAt("worldMap/world.js")
|
||||
@jsAt("worldMap/app.js")
|
||||
</body>
|
||||
</html>
|
|
@ -10,7 +10,7 @@
|
|||
@playerLink(pov.game.blackPlayer, withRating = false, withOnline = false, withDiff = false, variant = pov.game.variant)
|
||||
</div>
|
||||
<br />
|
||||
<span data-icon="p"> @shortClockName(pov.game.clock)</span>, @game.variantLink(pov.game.variant, variantName(pov.game.variant))
|
||||
<span data-icon="p"> @shortClockName(pov.game.clock.map(_.config))</span>, @game.variantLink(pov.game.variant, variantName(pov.game.variant))
|
||||
@if(pov.game.rated) {
|
||||
, @trans.rated()
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@
|
|||
@p.game.perfType.map { pt =>
|
||||
<span data-icon="@pt.iconChar"></span>
|
||||
}
|
||||
@shortClockName(p.game.clock)
|
||||
@shortClockName(p.game.clock.map(_.config))
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
|
|
|
@ -5,6 +5,11 @@ dir=$(mktemp -d)
|
|||
echo "Building in $dir"
|
||||
cd "$dir"
|
||||
|
||||
git clone https://github.com/ornicar/Kamon --branch lila
|
||||
cd Kamon
|
||||
sbt publish-local
|
||||
cd ..
|
||||
|
||||
git clone https://github.com/gilt/gfc-semver
|
||||
cd gfc-semver
|
||||
sbt publish-local
|
||||
|
|
|
@ -10,7 +10,7 @@ net {
|
|||
ip = "5.196.91.160"
|
||||
asset {
|
||||
domain = ${net.domain}
|
||||
version = 1246
|
||||
version = 1266
|
||||
}
|
||||
email = "contact@lichess.org"
|
||||
crawlable = false
|
||||
|
@ -30,7 +30,7 @@ play {
|
|||
}
|
||||
}
|
||||
i18n {
|
||||
langs=[en,fr,ru,de,tr,sr,lv,bs,da,es,ro,it,fi,uk,pt,pl,nl,vi,sv,cs,sk,hu,ca,sl,az,nn,eo,tp,el,fp,lt,nb,et,hy,af,hi,ar,zh,gl,hr,mk,id,ja,bg,th,fa,he,mr,mn,cy,gd,ga,sq,be,ka,sw,ps,is,kk,io,gu,fo,eu,bn,id,la,jv,ky,pi,as,le,ta,sa,ml,kn,ko,mg,kb,zu,ur,yo,tl,fy,jb,tg,cv]
|
||||
langs=[en,fr,ru,de,tr,sr,lv,bs,da,es,ro,it,fi,uk,pt,pl,nl,vi,sv,cs,sk,hu,ca,sl,az,nn,eo,tp,el,fp,lt,nb,et,hy,af,hi,ar,zh,gl,hr,mk,id,ja,bg,th,fa,he,mr,mn,cy,gd,ga,sq,be,ka,sw,ps,is,kk,io,gu,fo,eu,bn,id,la,jv,ky,pi,as,le,ta,sa,ml,kn,ko,mg,kb,zu,ur,yo,tl,fy,jb,tg,cv,ia]
|
||||
}
|
||||
http {
|
||||
session {
|
||||
|
@ -63,6 +63,10 @@ app {
|
|||
}
|
||||
api {
|
||||
token = secret
|
||||
influx_event = {
|
||||
endpoint = "http://bofur.lichess.org:8086/write?db=events"
|
||||
env = "dev"
|
||||
}
|
||||
}
|
||||
cli {
|
||||
username = "thibault"
|
||||
|
@ -279,9 +283,6 @@ shutup {
|
|||
playban {
|
||||
collection.playban = playban
|
||||
}
|
||||
worldMap {
|
||||
geoip = ${geoip}
|
||||
}
|
||||
perfStat {
|
||||
collection.perf_stat = "perf_stat"
|
||||
}
|
||||
|
@ -343,8 +344,6 @@ mailgun {
|
|||
base_url = ${net.base_url}
|
||||
}
|
||||
lobby {
|
||||
message.ttl = 30 seconds
|
||||
orphan_hook.ttl = 5 seconds
|
||||
socket {
|
||||
name = lobby-socket
|
||||
uid.ttl = ${site.socket.uid.ttl}
|
||||
|
@ -643,7 +642,7 @@ plan {
|
|||
patron = plan_patron
|
||||
charge = plan_charge
|
||||
}
|
||||
monthly_goal = 3982
|
||||
monthly_goal = 3923
|
||||
}
|
||||
hub {
|
||||
actor {
|
||||
|
@ -777,7 +776,7 @@ kamon {
|
|||
hostname-override = none
|
||||
|
||||
# For histograms, which percentiles to count
|
||||
percentiles = [50.0, 70.0, 90.0, 95.0, 99.0, 99.9]
|
||||
percentiles = []
|
||||
|
||||
# Subscription patterns used to select which metrics will be pushed to InfluxDB. Note that first, metrics
|
||||
# collection for your desired entities must be activated under the kamon.metrics.filters settings.
|
||||
|
|
|
@ -75,6 +75,16 @@
|
|||
</rollingPolicy>
|
||||
</appender>
|
||||
</logger>
|
||||
<logger name="pool" level="DEBUG">
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/var/log/lichess/pool.log</file>
|
||||
<encoder><pattern>%date %-5level %logger{30} %message%n%xException</pattern></encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/var/log/lichess/pool-log-%d{yyyy-MM-dd}.gz</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
</logger>
|
||||
<logger name="tournament" level="DEBUG">
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/var/log/lichess/tournament.log</file>
|
||||
|
|
|
@ -443,7 +443,7 @@ GET /api/tournament controllers.Api.currentTournaments
|
|||
GET /api/tournament/:id controllers.Api.tournament(id: String)
|
||||
GET /api/status controllers.Api.status
|
||||
GET /api/socket controllers.Main.apiWebsocket
|
||||
GET /api/game-stream controllers.Api.gameStream
|
||||
POST /api/game-stream controllers.Api.gameStream
|
||||
|
||||
# Events
|
||||
GET /event controllers.Event.index
|
||||
|
@ -463,10 +463,6 @@ GET /themepicker controllers.Main.themepicker
|
|||
GET /mobile controllers.Main.mobile
|
||||
GET /lag controllers.Main.lag
|
||||
|
||||
# Map
|
||||
GET /network controllers.WorldMap.index
|
||||
GET /network/stream controllers.WorldMap.stream
|
||||
|
||||
# Mobile Push
|
||||
POST /mobile/register/:platform/:deviceId controllers.Main.mobileRegister(platform: String, deviceId: String)
|
||||
POST /mobile/unregister controllers.Main.mobileUnregister
|
||||
|
|
|
@ -30,6 +30,7 @@ final class Env(
|
|||
getSimul: Simul.ID => Fu[Option[Simul]],
|
||||
getSimulName: Simul.ID => Option[String],
|
||||
getTournamentName: String => Option[String],
|
||||
pools: List[lila.pool.PoolConfig],
|
||||
val isProd: Boolean) {
|
||||
|
||||
val CliUsername = config getString "cli.username"
|
||||
|
@ -51,6 +52,9 @@ final class Env(
|
|||
val ExplorerEndpoint = config getString "explorer.endpoint"
|
||||
val TablebaseEndpoint = config getString "explorer.tablebase.endpoint"
|
||||
|
||||
private val InfluxEventEndpoint = config getString "api.influx_event.endpoint"
|
||||
private val InfluxEventEnv = config getString "api.influx_event.env"
|
||||
|
||||
object assetVersion {
|
||||
import reactivemongo.bson._
|
||||
import lila.db.dsl._
|
||||
|
@ -113,11 +117,10 @@ final class Env(
|
|||
nbActors = math.max(1, math.min(16, Runtime.getRuntime.availableProcessors - 1)))
|
||||
|
||||
val lobbyApi = new LobbyApi(
|
||||
lobby = lobbyEnv.lobby,
|
||||
lobbyVersion = () => lobbyEnv.history.version,
|
||||
getFilter = setupEnv.filter,
|
||||
lightUser = userEnv.lightUser,
|
||||
seekApi = lobbyEnv.seekApi)
|
||||
seekApi = lobbyEnv.seekApi,
|
||||
pools = pools)
|
||||
|
||||
private def makeUrl(path: String): String = s"${Net.BaseUrl}/$path"
|
||||
|
||||
|
@ -126,6 +129,11 @@ final class Env(
|
|||
KamonPusher.start(system) {
|
||||
new KamonPusher(countUsers = () => userEnv.onlineUserIdMemo.count)
|
||||
}
|
||||
|
||||
if (InfluxEventEnv != "dev") system.actorOf(Props(new InfluxEvent(
|
||||
endpoint = InfluxEventEndpoint,
|
||||
env = InfluxEventEnv
|
||||
)), name = "influx-event")
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
@ -153,5 +161,6 @@ object Env {
|
|||
gameCache = lila.game.Env.current.cached,
|
||||
system = lila.common.PlayApp.system,
|
||||
scheduler = lila.common.PlayApp.scheduler,
|
||||
pools = lila.pool.Env.current.api.configs,
|
||||
isProd = lila.common.PlayApp.isProd)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package lila.api
|
||||
|
||||
import akka.actor._
|
||||
import play.api.libs.ws.WS
|
||||
import play.api.Play.current
|
||||
|
||||
import lila.hub.actorApi.{ DeployPre, DeployPost }
|
||||
|
||||
private final class InfluxEvent(endpoint: String, env: String) extends Actor {
|
||||
|
||||
override def preStart() {
|
||||
context.system.lilaBus.subscribe(self, 'deploy)
|
||||
event("lila_start", "Lila starts")
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case DeployPre => event("lila_deploy_pre", "Lila will soon restart")
|
||||
case DeployPost => event("lila_deploy_post", "Lila restarts for deploy now")
|
||||
}
|
||||
|
||||
def event(key: String, text: String) = {
|
||||
val data = s"""event,program=lila,env=$env,title=$key text="$text""""
|
||||
WS.url(endpoint).post(data).effectFold(
|
||||
err => onError(s"$endpoint $data $err"),
|
||||
res => if (res.status != 204) onError(s"$endpoint $data ${res.status}")
|
||||
)
|
||||
}
|
||||
|
||||
def onError(msg: String) = lila.log("influx_event").warn(msg)
|
||||
}
|
|
@ -1,41 +1,45 @@
|
|||
package lila.api
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.pattern.ask
|
||||
import play.api.libs.json.{ Json, JsObject, JsArray }
|
||||
|
||||
import lila.common.LightUser
|
||||
import lila.common.PimpedJson._
|
||||
import lila.game.{ GameRepo, Pov }
|
||||
import lila.lobby.actorApi.HooksFor
|
||||
import lila.lobby.{ Hook, HookRepo, Seek, SeekApi }
|
||||
import lila.lobby.SeekApi
|
||||
import lila.setup.FilterConfig
|
||||
import lila.user.{ User, UserContext }
|
||||
|
||||
final class LobbyApi(
|
||||
lobby: ActorRef,
|
||||
lobbyVersion: () => Int,
|
||||
getFilter: UserContext => Fu[FilterConfig],
|
||||
lightUser: String => Option[LightUser],
|
||||
seekApi: SeekApi) {
|
||||
seekApi: SeekApi,
|
||||
pools: List[lila.pool.PoolConfig]) {
|
||||
|
||||
import makeTimeout.large
|
||||
|
||||
private val poolsJson = JsArray {
|
||||
pools.map { p =>
|
||||
Json.obj(
|
||||
"id" -> p.id.value,
|
||||
"lim" -> p.clock.limitInMinutes,
|
||||
"inc" -> p.clock.increment,
|
||||
"perf" -> p.perfType.name)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(implicit ctx: Context): Fu[JsObject] =
|
||||
(lobby ? HooksFor(ctx.me)).mapTo[Vector[Hook]] zip
|
||||
ctx.me.fold(seekApi.forAnon)(seekApi.forUser) zip
|
||||
(ctx.me ?? GameRepo.urgentGames) zip
|
||||
getFilter(ctx) map {
|
||||
case (((hooks, seeks), povs), filter) => Json.obj(
|
||||
case ((seeks, povs), filter) => Json.obj(
|
||||
"me" -> ctx.me.map { u =>
|
||||
Json.obj("username" -> u.username)
|
||||
},
|
||||
"version" -> lobbyVersion(),
|
||||
"hooks" -> JsArray(hooks map (_.render)),
|
||||
"seeks" -> JsArray(seeks map (_.render)),
|
||||
"nowPlaying" -> JsArray(povs take 9 map nowPlaying),
|
||||
"nbNowPlaying" -> povs.size,
|
||||
"filter" -> filter.render)
|
||||
"filter" -> filter.render,
|
||||
"pools" -> poolsJson)
|
||||
}
|
||||
|
||||
def nowPlaying(pov: Pov) = Json.obj(
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package lila.api
|
||||
|
||||
import org.specs2.mutable.Specification
|
||||
import play.api.libs.json._
|
||||
|
||||
class JsonStringifyPerfTest extends Specification {
|
||||
|
||||
val jsonStr = """{"t":"had","d":{"id":"Xng0Mqgw","uid":"kxukwt3ngu","u":"Mihail_Shatohin","rating":1778,"ra":1,"clock":"5+0","t":300,"s":2,"c":"white","perf":"Blitz"}}"""
|
||||
val jsonObj = Json.parse(jsonStr)
|
||||
|
||||
val nb = 100000
|
||||
val iterations = 10
|
||||
// val nb = 1
|
||||
// val iterations = 1
|
||||
|
||||
def stringify = Json stringify jsonObj
|
||||
|
||||
def runOne = stringify
|
||||
def run { for (i ← 1 to nb) runOne }
|
||||
|
||||
"stringify a hook" should {
|
||||
"many times" in {
|
||||
runOne must_== jsonStr
|
||||
if (nb * iterations > 1) {
|
||||
println("warming up")
|
||||
run
|
||||
}
|
||||
println("running tests")
|
||||
val durations = for (i ← 1 to iterations) yield {
|
||||
val start = System.currentTimeMillis
|
||||
run
|
||||
val duration = System.currentTimeMillis - start
|
||||
println(s"$nb runs in $duration ms")
|
||||
duration
|
||||
}
|
||||
val totalNb = iterations * nb
|
||||
val avg = (1000000 * durations.sum) / totalNb
|
||||
println(s"Average = $avg nanoseconds per run")
|
||||
println(s" ${1000000000 / avg} runs per second")
|
||||
true === true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,14 +31,14 @@ private object BSONHandlers {
|
|||
}
|
||||
implicit val TimeControlBSONHandler = new BSON[TimeControl] {
|
||||
def reads(r: Reader) = (r.intO("l") |@| r.intO("i")) {
|
||||
case (limit, inc) => TimeControl.Clock(limit, inc)
|
||||
case (limit, inc) => TimeControl.Clock(chess.Clock.Config(limit, inc))
|
||||
} orElse {
|
||||
r intO "d" map TimeControl.Correspondence.apply
|
||||
} getOrElse TimeControl.Unlimited
|
||||
def writes(w: Writer, t: TimeControl) = t match {
|
||||
case TimeControl.Clock(l, i) => $doc("l" -> l, "i" -> i)
|
||||
case TimeControl.Correspondence(d) => $doc("d" -> d)
|
||||
case TimeControl.Unlimited => $empty
|
||||
case TimeControl.Clock(chess.Clock.Config(l, i)) => $doc("l" -> l, "i" -> i)
|
||||
case TimeControl.Correspondence(d) => $doc("d" -> d)
|
||||
case TimeControl.Unlimited => $empty
|
||||
}
|
||||
}
|
||||
implicit val VariantBSONHandler = new BSONHandler[BSONInteger, Variant] {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lila.challenge
|
||||
|
||||
import chess.variant.{Variant, FromPosition}
|
||||
import chess.variant.{ Variant, FromPosition }
|
||||
import chess.{ Mode, Clock, Speed }
|
||||
import org.joda.time.DateTime
|
||||
|
||||
|
@ -87,10 +87,11 @@ object Challenge {
|
|||
object TimeControl {
|
||||
case object Unlimited extends TimeControl
|
||||
case class Correspondence(days: Int) extends TimeControl
|
||||
case class Clock(limit: Int, increment: Int) extends TimeControl {
|
||||
case class Clock(config: chess.Clock.Config) extends TimeControl {
|
||||
// All durations are expressed in seconds
|
||||
def show = chessClock.show
|
||||
lazy val chessClock = chess.Clock(limit, increment)
|
||||
def limit = config.limit
|
||||
def increment = config.increment
|
||||
def show = config.show
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,8 +103,8 @@ object Challenge {
|
|||
}
|
||||
|
||||
private def speedOf(timeControl: TimeControl) = timeControl match {
|
||||
case c: TimeControl.Clock => Speed(c.chessClock)
|
||||
case _ => Speed.Correspondence
|
||||
case TimeControl.Clock(config) => Speed(config)
|
||||
case _ => Speed.Correspondence
|
||||
}
|
||||
|
||||
private def perfTypeOf(variant: Variant, timeControl: TimeControl): PerfType =
|
||||
|
|
|
@ -77,7 +77,7 @@ final class ChallengeApi(
|
|||
variant = pov.game.variant,
|
||||
initialFen = initialFen,
|
||||
timeControl = (pov.game.clock, pov.game.daysPerTurn) match {
|
||||
case (Some(clock), _) => TimeControl.Clock(clock.limit, clock.increment)
|
||||
case (Some(clock), _) => TimeControl.Clock(clock.config)
|
||||
case (_, Some(days)) => TimeControl.Correspondence(days)
|
||||
case _ => TimeControl.Unlimited
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ private[challenge] final class Joiner(onStart: String => Unit) {
|
|||
c.challengerUserId.??(UserRepo.byId) flatMap { challengerUser =>
|
||||
|
||||
def makeChess(variant: chess.variant.Variant): chess.Game =
|
||||
chess.Game(board = chess.Board init variant, clock = c.clock.map(_.chessClock))
|
||||
chess.Game(board = chess.Board init variant, clock = c.clock.map(_.config.toClock))
|
||||
|
||||
val baseState = c.initialFen.ifTrue(c.variant == chess.variant.FromPosition) flatMap Forsyth.<<<
|
||||
val (chessGame, state) = baseState.fold(makeChess(c.variant) -> none[SituationPlus]) {
|
||||
|
@ -28,7 +28,7 @@ private[challenge] final class Joiner(onStart: String => Unit) {
|
|||
player = color,
|
||||
turns = sit.turns,
|
||||
startedAtTurn = sit.turns,
|
||||
clock = c.clock.map(_.chessClock))
|
||||
clock = c.clock.map(_.config.toClock))
|
||||
if (Forsyth.>>(game) == Forsyth.initial) makeChess(chess.variant.Standard) -> none
|
||||
else game -> baseState
|
||||
}
|
||||
|
|
|
@ -31,11 +31,11 @@ final class JsonView(
|
|||
"initialFen" -> c.initialFen,
|
||||
"rated" -> c.mode.rated,
|
||||
"timeControl" -> (c.timeControl match {
|
||||
case c@TimeControl.Clock(l, i) => Json.obj(
|
||||
case c@TimeControl.Clock(clock) => Json.obj(
|
||||
"type" -> "clock",
|
||||
"limit" -> l,
|
||||
"increment" -> i,
|
||||
"show" -> c.show)
|
||||
"limit" -> clock.limit,
|
||||
"increment" -> clock.increment,
|
||||
"show" -> clock.show)
|
||||
case TimeControl.Correspondence(d) => Json.obj(
|
||||
"type" -> "correspondence",
|
||||
"daysPerTurn" -> d)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 731ac9787254ddddb0c91389a6b4ad535da278a3
|
||||
Subproject commit fd402c6243e2491c325700961047afed70444edd
|
|
@ -78,4 +78,3 @@ object Bus extends ExtensionId[Bus] with ExtensionIdProvider {
|
|||
|
||||
override def createExtension(system: ExtendedActorSystem) = new Bus(system)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,27 @@ A C program for maximum weight matching by Ed Rothberg was used extensively
|
|||
to validate this new code.
|
||||
*/
|
||||
|
||||
package lila.tournament
|
||||
package arena
|
||||
package lila.common
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{ Try, Success }
|
||||
|
||||
object WMMatching {
|
||||
def maxWeightMatching (edges: Array[(Int, Int, Int)], maxcardinality: Boolean) : Array[Int] = {
|
||||
|
||||
def apply[A](vertices: Array[A], pairScore: (A, A) => Int): Try[List[(A, A)]] = Try {
|
||||
if (vertices.length < 2) Nil
|
||||
else lowLevel(
|
||||
vertices.length,
|
||||
(i, j) => pairScore(vertices(i), vertices(j))
|
||||
) map {
|
||||
case (i, j) => vertices(i) -> vertices(j)
|
||||
}
|
||||
}
|
||||
|
||||
private def lowLevel(nvertex: Int, pairScore: (Int, Int) => Int): List[(Int, Int)] =
|
||||
mateToEdges(minWeightMatching(fullGraph(nvertex, pairScore)))
|
||||
|
||||
private def maxWeightMatching(edges: Array[(Int, Int, Int)], maxcardinality: Boolean): Array[Int] = {
|
||||
/*
|
||||
Compute a maximum-weighted matching in the general undirected
|
||||
weighted graph given by "edges". If "maxcardinality" is true,
|
||||
|
@ -49,15 +63,15 @@ object WMMatching {
|
|||
|
||||
*/
|
||||
val nedge = edges.length
|
||||
val nvertex = 1 + edges.view.map ( x => x._1.max (x._2) ).max
|
||||
val nvertex = 1 + edges.view.map(x => x._1.max(x._2)).max
|
||||
// Find the maximum edge weight.
|
||||
val maxweight = edges.view.map(_._3).max.max(0)
|
||||
// If p is an edge endpoint,
|
||||
// endpoint(p) is the vertex to which endpoint p is attached.
|
||||
val endpoint:Array[Int] = edges.flatMap ( x => List(x._1, x._2))
|
||||
val endpoint: Array[Int] = edges.flatMap(x => List(x._1, x._2))
|
||||
//If v is a vertex,
|
||||
//neighbend(v) is the list of remote endpoints of the edges attached to v.
|
||||
val neighbend:Array[List[Int]] = Array.fill(nvertex)(Nil)
|
||||
val neighbend: Array[List[Int]] = Array.fill(nvertex)(Nil)
|
||||
edges.zipWithIndex.reverseIterator.foreach { case ((i, j, w), k) => neighbend(i) ::= (2 * k + 1); neighbend(j) ::= (2 * k) }
|
||||
|
||||
// If v is a vertex,
|
||||
|
@ -111,7 +125,7 @@ object WMMatching {
|
|||
|
||||
// If b is a (sub-)blossom,
|
||||
// blossombase(b) is its base VERTEX (i.e. recursive sub-blossom).
|
||||
val blossombase: Array[Int] = Array.tabulate(2*nvertex) { i:Int => if (i < nvertex) i else -1 }
|
||||
val blossombase: Array[Int] = Array.tabulate(2 * nvertex) { i: Int => if (i < nvertex) i else -1 }
|
||||
|
||||
/*
|
||||
If b is a non-trivial (sub-)blossom,
|
||||
|
@ -149,7 +163,7 @@ object WMMatching {
|
|||
// If b is a non-trivial blossom,
|
||||
// dualvar(b) = z(b) where z(b) is b's variable in the dual optimization
|
||||
// problem.
|
||||
val dualvar: Array[Int] = Array.tabulate(2*nvertex) { i:Int => if (i < nvertex) maxweight else 0 }
|
||||
val dualvar: Array[Int] = Array.tabulate(2 * nvertex) { i: Int => if (i < nvertex) maxweight else 0 }
|
||||
|
||||
// If allowedge(k) is true, edge k has zero slack in the optimization
|
||||
// problem; if allowedge(k) is false, the edge's slack may or may not
|
||||
|
@ -164,7 +178,7 @@ object WMMatching {
|
|||
|
||||
// Generate the leaf vertices of a blossom.
|
||||
def blossomLeaves(b: Int): Traversable[Int] = {
|
||||
class BlossomLeavesTraversable(b:Int) extends Traversable[Int] {
|
||||
class BlossomLeavesTraversable(b: Int) extends Traversable[Int] {
|
||||
def foreach[U](f: Int => U): Unit = {
|
||||
def g(v: Int): Unit = {
|
||||
blossomchilds(v).foreach(w => if (w < nvertex) f(w) else g(w))
|
||||
|
@ -179,7 +193,7 @@ object WMMatching {
|
|||
// and record the fact that w was reached through the edge with
|
||||
// remote endpoint p.
|
||||
|
||||
@tailrec def assignLabel(w:Int, t:Int, p:Int) : Unit = {
|
||||
@tailrec def assignLabel(w: Int, t: Int, p: Int): Unit = {
|
||||
val b = inblossom(w)
|
||||
label(w) = t
|
||||
label(b) = t
|
||||
|
@ -189,8 +203,9 @@ object WMMatching {
|
|||
bestedge(b) = -1
|
||||
if (t == 1) {
|
||||
//b became an S-vertex/blossom; add it(s vertices) to the queue.
|
||||
blossomLeaves(b).foreach ( queue ::= _ )
|
||||
} else if (t == 2) {
|
||||
blossomLeaves(b).foreach(queue ::= _)
|
||||
}
|
||||
else if (t == 2) {
|
||||
// b became a T-vertex/blossom; assign label S to its mate.
|
||||
// (If b is a non-trivial blossom, its base is the only vertex
|
||||
// with an external mate.)
|
||||
|
@ -200,9 +215,9 @@ object WMMatching {
|
|||
}
|
||||
// Trace back from vertices v and w to discover either a new blossom
|
||||
// or an augmenting path. Return the base vertex of the new blossom or -1.
|
||||
def scanBlossom(v:Int, w:Int) = {
|
||||
def scanBlossom(v: Int, w: Int) = {
|
||||
// Trace back from v and w, placing breadcrumbs as we go.
|
||||
@tailrec def scan(v:Int, w:Int, path: List[Int]) : (Int, List[Int]) = {
|
||||
@tailrec def scan(v: Int, w: Int, path: List[Int]): (Int, List[Int]) = {
|
||||
if (v == -1 && w == -1) (-1, path) //not found
|
||||
else {
|
||||
// Look for a breadcrumb in v's blossom or put a new breadcrumb.
|
||||
|
@ -251,7 +266,7 @@ object WMMatching {
|
|||
blossomparent(bb) = b
|
||||
// Make list of sub-blossoms and their interconnecting edge endpoints.
|
||||
|
||||
@tailrec def traceBack(v:Int, d:Int, path:List[Int], endps:List[Int]): (List[Int], List[Int]) = {
|
||||
@tailrec def traceBack(v: Int, d: Int, path: List[Int], endps: List[Int]): (List[Int], List[Int]) = {
|
||||
val bv = inblossom(v)
|
||||
if (bv == bb) (path, endps)
|
||||
else {
|
||||
|
@ -264,7 +279,7 @@ object WMMatching {
|
|||
// Trace back from w to base.
|
||||
val (path2, endps2) = traceBack(w, 1, Nil, Nil)
|
||||
// Trace back from v to base.
|
||||
val (path1, endps1) = traceBack(v, 0, path2.reverse, (2*k) :: (endps2.reverse))
|
||||
val (path1, endps1) = traceBack(v, 0, path2.reverse, (2 * k) :: (endps2.reverse))
|
||||
blossomchilds(b) = (bb :: path1).toArray
|
||||
blossomendps(b) = endps1.toArray
|
||||
|
||||
|
@ -286,8 +301,8 @@ object WMMatching {
|
|||
|
||||
val bestedgeto = Array.fill(allocatedvertex)(-1)
|
||||
for (bv <- blossomchilds(b)) {
|
||||
val nblists:Traversable[Int] =
|
||||
if (blossombestedges(bv) == null) blossomLeaves(bv).flatMap(v => neighbend(v).view.map { p => p >> 1 } )
|
||||
val nblists: Traversable[Int] =
|
||||
if (blossombestedges(bv) == null) blossomLeaves(bv).flatMap(v => neighbend(v).view.map { p => p >> 1 })
|
||||
else blossombestedges(bv)
|
||||
for (k <- nblists) {
|
||||
val e = edges(k)
|
||||
|
@ -299,7 +314,7 @@ object WMMatching {
|
|||
blossombestedges(bv) = null
|
||||
bestedge(bv) = -1
|
||||
}
|
||||
blossombestedges(b) = bestedgeto.view.filter { k => k != -1 } . toList
|
||||
blossombestedges(b) = bestedgeto.view.filter { k => k != -1 }.toList
|
||||
// Select bestedge(b).
|
||||
bestedge(b) = -1
|
||||
for (k <- blossombestedges(b)) {
|
||||
|
@ -309,7 +324,7 @@ object WMMatching {
|
|||
}
|
||||
|
||||
// Expand the given top-level blossom.
|
||||
def expandBlossom(b:Int, endstage:Boolean) : Unit = {
|
||||
def expandBlossom(b: Int, endstage: Boolean): Unit = {
|
||||
// Convert sub-blossoms into top-level blossoms.
|
||||
for (s <- blossomchilds(b)) {
|
||||
blossomparent(s) = -1
|
||||
|
@ -319,7 +334,7 @@ object WMMatching {
|
|||
//Recursively expand this sub-blossom.
|
||||
expandBlossom(s, endstage)
|
||||
else
|
||||
blossomLeaves(s).foreach(v => inblossom(v) = s)
|
||||
blossomLeaves(s).foreach(v => inblossom(v) = s)
|
||||
}
|
||||
// If we expand a T-blossom during a stage, its sub-blossoms must be
|
||||
// relabeled.
|
||||
|
@ -343,15 +358,15 @@ object WMMatching {
|
|||
while (j != 0) {
|
||||
//Relabel the T-sub-blossom.
|
||||
label(endpoint(p ^ 1)) = 0
|
||||
label(endpoint(blossomendps(b)(j-endptrick)^endptrick^1)) = 0
|
||||
label(endpoint(blossomendps(b)(j - endptrick) ^ endptrick ^ 1)) = 0
|
||||
assignLabel(endpoint(p ^ 1), 2, p)
|
||||
//Step to the next S-sub-blossom and note its forward endpoint.
|
||||
allowedge(blossomendps(b)(j-endptrick) >> 1) = true
|
||||
j = jstep (j)
|
||||
p = blossomendps(b)(j-endptrick) ^ endptrick
|
||||
allowedge(blossomendps(b)(j - endptrick) >> 1) = true
|
||||
j = jstep(j)
|
||||
p = blossomendps(b)(j - endptrick) ^ endptrick
|
||||
// Step to the next T-sub-blossom.
|
||||
allowedge(p >> 1) = false
|
||||
j = jstep (j)
|
||||
j = jstep(j)
|
||||
}
|
||||
// Relabel the base T-sub-blossom WITHOUT stepping through to
|
||||
// its mate (so don't call assignLabel).
|
||||
|
@ -362,7 +377,7 @@ object WMMatching {
|
|||
labelend(bv) = p
|
||||
bestedge(bv) = -1
|
||||
// Continue along the blossom until we get back to entrychild.
|
||||
j = jstep (j)
|
||||
j = jstep(j)
|
||||
while (blossomchilds(b)(j) != entrychild) {
|
||||
// Examine the vertices of the sub-blossom to see whether
|
||||
// it is reachable from a neighbouring S-vertex outside the
|
||||
|
@ -371,15 +386,16 @@ object WMMatching {
|
|||
if (label(bv) == 1) {
|
||||
// This sub-blossom just got label S through one of its
|
||||
// neighbours; leave it.
|
||||
} else {
|
||||
(blossomLeaves(bv).find(x => label(x) != 0)).foreach ( v => {
|
||||
label(v) = 0
|
||||
label(endpoint(mate(blossombase(bv)))) = 0
|
||||
assignLabel(v, 2, labelend(v))
|
||||
}
|
||||
}
|
||||
else {
|
||||
(blossomLeaves(bv).find(x => label(x) != 0)).foreach(v => {
|
||||
label(v) = 0
|
||||
label(endpoint(mate(blossombase(bv)))) = 0
|
||||
assignLabel(v, 2, labelend(v))
|
||||
}
|
||||
)
|
||||
}
|
||||
j = jstep (j)
|
||||
j = jstep(j)
|
||||
}
|
||||
}
|
||||
// Recycle the blossom number.
|
||||
|
@ -398,10 +414,10 @@ object WMMatching {
|
|||
|
||||
// Swap matched/unmatched edges over an alternating path through blossom b
|
||||
// between vertex v and the base vertex. Keep blossom bookkeeping consistent.
|
||||
def augmentBlossom(b:Int, v:Int) : Unit = {
|
||||
def augmentBlossom(b: Int, v: Int): Unit = {
|
||||
// Bubble up through the blossom tree from vertex v to an immediate
|
||||
// isub-blossom of b.
|
||||
def rotate(a: Array[Int], shift:Int): Array[Int] = {
|
||||
def rotate(a: Array[Int], shift: Int): Array[Int] = {
|
||||
if (shift == 0) a
|
||||
else {
|
||||
val n = a.length
|
||||
|
@ -423,14 +439,14 @@ object WMMatching {
|
|||
val i = blossomchilds(b).indexOf(t)
|
||||
var j = i
|
||||
val (jstep, endptrick) =
|
||||
if ((j & 1) != 0) ( (j:Int) => { if (j == l1) 0 else (j + 1) } , 0)
|
||||
else ((j:Int) => { if (j == 0) l1 else (j - 1) } , 1)
|
||||
if ((j & 1) != 0) ((j: Int) => { if (j == l1) 0 else (j + 1) }, 0)
|
||||
else ((j: Int) => { if (j == 0) l1 else (j - 1) }, 1)
|
||||
// Move along the blossom until we get to the base.
|
||||
while (j != 0) {
|
||||
// Step to the next sub-blossom and augment it recursively.
|
||||
j = jstep(j)
|
||||
t = blossomchilds(b)(j)
|
||||
val p = blossomendps(b)(j-endptrick) ^ endptrick
|
||||
val p = blossomendps(b)(j - endptrick) ^ endptrick
|
||||
if (t >= nvertex)
|
||||
augmentBlossom(t, endpoint(p))
|
||||
// Step to the next sub-blossom and augment it recursively.
|
||||
|
@ -451,12 +467,12 @@ object WMMatching {
|
|||
// Swap matched/unmatched edges over an alternating path between two
|
||||
// single vertices. The augmenting path runs through edge k, which
|
||||
// connects a pair of S vertices.
|
||||
def augmentMatching(k: Int) : Unit = {
|
||||
def augmentMatching(k: Int): Unit = {
|
||||
val (v, w, wt) = edges(k)
|
||||
// Match vertex s to remote endpoint p. Then trace back from s
|
||||
// until we find a single vertex, swapping matched and unmatched
|
||||
// edges as we go.
|
||||
@tailrec def f(s:Int, p:Int) : Unit = {
|
||||
@tailrec def f(s: Int, p: Int): Unit = {
|
||||
val bs = inblossom(s)
|
||||
// Augment through the S-blossom from s to base.
|
||||
if (bs >= nvertex)
|
||||
|
@ -465,7 +481,8 @@ object WMMatching {
|
|||
// Trace one step back.
|
||||
if (labelend(bs) == -1) {
|
||||
//Reached single vertex; stop.
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
val t = endpoint(labelend(bs))
|
||||
val bt = inblossom(t)
|
||||
// Trace one step back.
|
||||
|
@ -482,23 +499,24 @@ object WMMatching {
|
|||
f(ns, np)
|
||||
}
|
||||
}
|
||||
f(v, 2*k+1)
|
||||
f(w, 2*k)
|
||||
f(v, 2 * k + 1)
|
||||
f(w, 2 * k)
|
||||
}
|
||||
@tailrec def substage () : Boolean = {
|
||||
@tailrec def substage(): Boolean = {
|
||||
if (queue.isEmpty) false
|
||||
else {
|
||||
// Take an S vertex from the queue.
|
||||
val v = queue.head
|
||||
queue = queue.tail
|
||||
def go (p: Int) : Boolean = {
|
||||
def go(p: Int): Boolean = {
|
||||
val k = p >> 1
|
||||
val w = endpoint(p)
|
||||
// w is a neighbour to v
|
||||
if (inblossom(v) == inblossom(w)) {
|
||||
//this edge is internal to a blossom; ignore it
|
||||
//this edge is internal to a blossom; ignore it
|
||||
false
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
var kslack = 0
|
||||
if (!allowedge(k)) {
|
||||
kslack = slack(k)
|
||||
|
@ -512,7 +530,8 @@ object WMMatching {
|
|||
// label w with T and label its mate with S (R12).
|
||||
assignLabel(w, 2, p ^ 1)
|
||||
false
|
||||
} else if (label(inblossom(w)) == 1) {
|
||||
}
|
||||
else if (label(inblossom(w)) == 1) {
|
||||
// (C2) w is an S-vertex (not in the same blossom);
|
||||
// follow back-links to discover either an
|
||||
// augmenting path or a new blossom.
|
||||
|
@ -522,13 +541,15 @@ object WMMatching {
|
|||
// bookkeeping and turn it into an S-blossom.
|
||||
addBlossom(base, k)
|
||||
false
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Found an augmenting path; augment the
|
||||
// matching and end this stage.
|
||||
augmentMatching(k)
|
||||
true
|
||||
}
|
||||
} else if (label(w) == 0) {
|
||||
}
|
||||
else if (label(w) == 0) {
|
||||
// w is inside a T-blossom, but w itself has not
|
||||
// yet been reached from outside the blossom;
|
||||
// mark it as reached (we need this to relabel
|
||||
|
@ -536,10 +557,12 @@ object WMMatching {
|
|||
label(w) = 2
|
||||
labelend(w) = p ^ 1
|
||||
false
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
} else if (label(inblossom(w)) == 1) {
|
||||
}
|
||||
else if (label(inblossom(w)) == 1) {
|
||||
// keep track of the least-slack non-allowable edge to
|
||||
// a different S-blossom.
|
||||
val b = inblossom(v)
|
||||
|
@ -547,7 +570,8 @@ object WMMatching {
|
|||
bestedge(b) = k
|
||||
}
|
||||
false
|
||||
} else if (label(w) == 0) {
|
||||
}
|
||||
else if (label(w) == 0) {
|
||||
// w is a free vertex (or an unreached vertex inside
|
||||
// a T-blossom) but we can not reach it yet;
|
||||
// keep track of the least-slack edge that reaches w.
|
||||
|
@ -555,15 +579,17 @@ object WMMatching {
|
|||
bestedge(w) = k
|
||||
}
|
||||
false
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (neighbend(v).exists(go)) {
|
||||
true
|
||||
} else {
|
||||
substage ()
|
||||
}
|
||||
else {
|
||||
substage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -571,7 +597,7 @@ object WMMatching {
|
|||
var tp = -1
|
||||
var delta = -1
|
||||
var extra = -1
|
||||
def update (t: Int, d: Int, e: Int) = {
|
||||
def update(t: Int, d: Int, e: Int) = {
|
||||
if (tp == -1 || d < delta) {
|
||||
tp = t
|
||||
delta = d
|
||||
|
@ -580,7 +606,7 @@ object WMMatching {
|
|||
}
|
||||
}
|
||||
|
||||
def updateDual () : Boolean = {
|
||||
def updateDual(): Boolean = {
|
||||
// There is no augmenting path under these constraints;
|
||||
// compute delta and reduce slack in the optimization problem.
|
||||
// (Note that our vertex dual variables, edge slacks and delta's
|
||||
|
@ -590,7 +616,7 @@ object WMMatching {
|
|||
|
||||
// Compute delta1: the minumum value of any vertex dual.
|
||||
if (!maxcardinality) {
|
||||
dt.update (1, dualvar.slice(0, nvertex).min, -1)
|
||||
dt.update(1, dualvar.slice(0, nvertex).min, -1)
|
||||
}
|
||||
|
||||
// Compute delta2: the minimum slack on any edge between
|
||||
|
@ -598,7 +624,7 @@ object WMMatching {
|
|||
|
||||
for (v <- 0 until nvertex) {
|
||||
if (label(inblossom(v)) == 0 && bestedge(v) != -1) {
|
||||
dt.update (2, slack(bestedge(v)), bestedge(v))
|
||||
dt.update(2, slack(bestedge(v)), bestedge(v))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -614,7 +640,7 @@ object WMMatching {
|
|||
// Compute delta4: minimum z variable of any T-blossom.
|
||||
for (b <- nvertex until allocatedvertex) {
|
||||
if (blossombase(b) >= 0 && blossomparent(b) == -1 && label(b) == 2) {
|
||||
dt.update (4, dualvar(b), b)
|
||||
dt.update(4, dualvar(b), b)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,7 +656,8 @@ object WMMatching {
|
|||
if (label(inblossom(v)) == 1) {
|
||||
// S-vertex: 2*u = 2*u - 2*delta
|
||||
dualvar(v) -= dt.delta
|
||||
} else if (label(inblossom(v)) == 2) {
|
||||
}
|
||||
else if (label(inblossom(v)) == 2) {
|
||||
//T-vertex: 2*u = 2*u + 2*delta
|
||||
dualvar(v) += dt.delta
|
||||
}
|
||||
|
@ -648,26 +675,29 @@ object WMMatching {
|
|||
// Take action at the point where minimum delta occurred.
|
||||
if (dt.tp == 1) {
|
||||
// No further improvement possible; optimum reached.
|
||||
} else if (dt.tp == 2) {
|
||||
}
|
||||
else if (dt.tp == 2) {
|
||||
// Use the least-slack edge to continue the search.
|
||||
allowedge(dt.extra) = true
|
||||
val (ei, ej, wt) = edges(dt.extra)
|
||||
val (i, j) = if (label(inblossom(ei)) == 0) (ej, ei) else (ei, ej)
|
||||
queue ::= i
|
||||
} else if (dt.tp == 3) {
|
||||
// Use the least-slack edge to continue the search.
|
||||
allowedge(dt.extra) = true
|
||||
val (i, j, wt) = edges(dt.extra)
|
||||
queue ::= i
|
||||
} else if (dt.tp == 4) {
|
||||
}
|
||||
else if (dt.tp == 3) {
|
||||
// Use the least-slack edge to continue the search.
|
||||
allowedge(dt.extra) = true
|
||||
val (i, j, wt) = edges(dt.extra)
|
||||
queue ::= i
|
||||
}
|
||||
else if (dt.tp == 4) {
|
||||
expandBlossom(dt.extra, false)
|
||||
}
|
||||
dt.tp > 1
|
||||
}
|
||||
|
||||
// Main loop: continue until no further improvement is possible.
|
||||
@tailrec def mainLoop(iterations: Int) : Unit = {
|
||||
@tailrec def stage() : Boolean = {
|
||||
@tailrec def mainLoop(iterations: Int): Unit = {
|
||||
@tailrec def stage(): Boolean = {
|
||||
if (substage()) true
|
||||
else if (!updateDual()) false
|
||||
else stage()
|
||||
|
@ -707,12 +737,12 @@ object WMMatching {
|
|||
}
|
||||
mate
|
||||
}
|
||||
def minWeightMatching (edges: Array[(Int, Int, Int)]) : Array[Int] = {
|
||||
private def minWeightMatching(edges: Array[(Int, Int, Int)]): Array[Int] = {
|
||||
val maxweight = edges.view.map(_._3).max
|
||||
maxWeightMatching (edges.map { x => (x._1, x._2, maxweight - x._3) }, true)
|
||||
maxWeightMatching(edges.map { x => (x._1, x._2, maxweight - x._3) }, true)
|
||||
}
|
||||
def fullGraph(nvertex: Int, pairScore : (Int, Int) => Int) : Array[(Int, Int, Int)] =
|
||||
private def fullGraph(nvertex: Int, pairScore: (Int, Int) => Int): Array[(Int, Int, Int)] =
|
||||
(for (j <- 1 until nvertex; i <- 0 until j) yield (i, j, pairScore(i, j))).toArray
|
||||
def mateToEdges(mate: Array[Int]) : List[(Int, Int)] =
|
||||
private def mateToEdges(mate: Array[Int]): List[(Int, Int)] =
|
||||
(for (i <- 0 until mate.length; if (i < mate(i))) yield (i, mate(i))).toList
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package lila.common
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.stm.Ref
|
||||
|
||||
final class WindowCount(timeout: FiniteDuration) {
|
||||
|
||||
private val counter = Ref((0, (0, nowMillis)))
|
||||
|
||||
private val tms = timeout.toMillis
|
||||
|
||||
def add {
|
||||
val current = nowMillis
|
||||
counter.single.transform {
|
||||
case (precedent, (count, millis)) if current > millis + tms => (0, (1, current))
|
||||
case (precedent, (count, millis)) if current > millis + (tms / 2) => (count, (1, current))
|
||||
case (precedent, (count, millis)) => (precedent, (count + 1, millis))
|
||||
}
|
||||
}
|
||||
|
||||
def get = {
|
||||
val current = nowMillis
|
||||
val (precedent, (count, millis)) = counter.single()
|
||||
val since = current - millis
|
||||
if (since <= tms) ((count + precedent) * 1000) / (since + tms / 2)
|
||||
else 0
|
||||
} toInt
|
||||
|
||||
}
|
|
@ -61,21 +61,57 @@ object mon {
|
|||
val size = rec("lobby.hook.size")
|
||||
def acceptedRatedClock(clock: String) =
|
||||
inc(s"lobby.hook.a_r_clock.${clock.replace("+", "_")}")
|
||||
def joinMobile(isMobile: Boolean) = inc(s"lobby.hook.join_mobile.$isMobile")
|
||||
def createdLikePoolFiveO(isMobile: Boolean) = inc(s"lobby.hook.like_pool_5_0.$isMobile")
|
||||
def acceptedLikePoolFiveO(isMobile: Boolean) = inc(s"lobby.hook.like_pool_5_0_accepted.$isMobile")
|
||||
def standardColor(mode: String, color: String) = inc(s"lobby.hook.standard_color.$mode.$color")
|
||||
}
|
||||
object seek {
|
||||
val create = inc("lobby.seek.create")
|
||||
val join = inc("lobby.seek.join")
|
||||
def joinMobile(isMobile: Boolean) = inc(s"lobby.seek.join_mobile.$isMobile")
|
||||
}
|
||||
object socket {
|
||||
val getUids = rec("lobby.socket.get_uids")
|
||||
val member = rec("lobby.socket.member")
|
||||
val resync = inc("lobby.socket.resync")
|
||||
val idle = rec("lobby.socket.idle")
|
||||
val hookSubscribers = rec("lobby.socket.hook_subscribers")
|
||||
val mobile = rec(s"lobby.socket.mobile")
|
||||
}
|
||||
object cache {
|
||||
val user = inc("lobby.cache.count.user")
|
||||
val anon = inc("lobby.cache.count.anon")
|
||||
val miss = inc("lobby.cache.count.miss")
|
||||
}
|
||||
object pool {
|
||||
object wave {
|
||||
def scheduled(id: String) = inc(s"lobby.pool.$id.wave.scheduled")
|
||||
def full(id: String) = inc(s"lobby.pool.$id.wave.full")
|
||||
def paired(id: String) = rec(s"lobby.pool.$id.wave.paired")
|
||||
def missed(id: String) = rec(s"lobby.pool.$id.wave.missed")
|
||||
def wait(id: String) = rec(s"lobby.pool.$id.wave.wait")
|
||||
def ratingDiff(id: String) = rec(s"lobby.pool.$id.wave.rating_diff")
|
||||
def withRange(id: String) = rec(s"lobby.pool.$id.wave.with_range")
|
||||
}
|
||||
object thieve {
|
||||
def timeout(id: String) = inc(s"lobby.pool.$id.thieve.timeout")
|
||||
def candidates(id: String) = rec(s"lobby.pool.$id.thieve.candidates")
|
||||
def stolen(id: String) = rec(s"lobby.pool.$id.thieve.stolen")
|
||||
}
|
||||
object join {
|
||||
def count(id: String) = inc(s"lobby.pool.$id.join.count")
|
||||
}
|
||||
object leave {
|
||||
def count(id: String) = inc(s"lobby.pool.$id.leave.count")
|
||||
def wait(id: String) = rec(s"lobby.pool.$id.leave.wait")
|
||||
}
|
||||
object matchMaking {
|
||||
def duration(id: String) = rec(s"lobby.pool.$id.match_making.duration")
|
||||
}
|
||||
object gameStart {
|
||||
def duration(id: String) = rec(s"lobby.pool.$id.game_start.duration")
|
||||
}
|
||||
}
|
||||
}
|
||||
object round {
|
||||
object api {
|
||||
|
|
|
@ -71,17 +71,15 @@ object BinaryFormat {
|
|||
case Array(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12) =>
|
||||
readTimer(b9, b10, b11, b12) match {
|
||||
case 0 => PausedClock(
|
||||
config = Clock.Config(readClockLimit(b1), b2),
|
||||
color = color,
|
||||
limit = readClockLimit(b1),
|
||||
increment = b2,
|
||||
whiteTime = readSignedInt24(b3, b4, b5).toFloat / 100,
|
||||
blackTime = readSignedInt24(b6, b7, b8).toFloat / 100,
|
||||
whiteBerserk = whiteBerserk,
|
||||
blackBerserk = blackBerserk)
|
||||
case timer => RunningClock(
|
||||
config = Clock.Config(readClockLimit(b1), b2),
|
||||
color = color,
|
||||
limit = readClockLimit(b1),
|
||||
increment = b2,
|
||||
whiteTime = readSignedInt24(b3, b4, b5).toFloat / 100,
|
||||
blackTime = readSignedInt24(b6, b7, b8).toFloat / 100,
|
||||
whiteBerserk = whiteBerserk,
|
||||
|
@ -92,9 +90,8 @@ object BinaryFormat {
|
|||
// #TODO remove me! But fix the DB first!
|
||||
case Array(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, _) =>
|
||||
PausedClock(
|
||||
config = Clock.Config(readClockLimit(b1), b2),
|
||||
color = color,
|
||||
limit = readClockLimit(b1),
|
||||
increment = b2,
|
||||
whiteTime = readSignedInt24(b3, b4, b5).toFloat / 100,
|
||||
blackTime = readSignedInt24(b6, b7, b8).toFloat / 100,
|
||||
whiteBerserk = whiteBerserk,
|
||||
|
|
|
@ -247,7 +247,7 @@ case class Game(
|
|||
def playableCorrespondenceClock: Option[CorrespondenceClock] =
|
||||
playable ?? correspondenceClock
|
||||
|
||||
def speed = chess.Speed(clock)
|
||||
def speed = chess.Speed(clock.map(_.config))
|
||||
|
||||
def perfKey = PerfPicker.key(this)
|
||||
def perfType = PerfType(perfKey)
|
||||
|
@ -317,7 +317,7 @@ case class Game(
|
|||
|
||||
def abortable = status == Status.Started && playedTurns < 2 && nonMandatory
|
||||
|
||||
def berserkable = clock.??(_.berserkable) && status == Status.Started && playedTurns < 2
|
||||
def berserkable = clock.??(_.config.berserkable) && status == Status.Started && playedTurns < 2
|
||||
|
||||
def goBerserk(color: Color) =
|
||||
clock.ifTrue(berserkable && !player(color).berserk).map { c =>
|
||||
|
@ -369,6 +369,8 @@ case class Game(
|
|||
|
||||
def imported = source contains Source.Import
|
||||
|
||||
def fromPool = source contains Source.Pool
|
||||
|
||||
def winner = players find (_.wins)
|
||||
|
||||
def loser = winner map opponent
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lila.game
|
||||
|
||||
import chess.Speed
|
||||
import chess.{Speed,Clock}
|
||||
import lila.rating.{ Perf, PerfType }
|
||||
import lila.user.Perfs
|
||||
|
||||
|
|
|
@ -17,11 +17,12 @@ object Source {
|
|||
case object ImportLive extends Source(id = 9)
|
||||
case object Simul extends Source(id = 10)
|
||||
case object Relay extends Source(id = 11)
|
||||
case object Pool extends Source(id = 12)
|
||||
|
||||
val all = List(Lobby, Friend, Ai, Api, Tournament, Position, Import, Simul, Relay)
|
||||
val all = List(Lobby, Friend, Ai, Api, Tournament, Position, Import, Simul, Relay, Pool)
|
||||
val byId = all map { v => (v.id, v) } toMap
|
||||
|
||||
val searchable = List(Lobby, Friend, Ai, Position, Import, Tournament, Simul)
|
||||
val searchable = List(Lobby, Friend, Ai, Position, Import, Tournament, Simul, Pool)
|
||||
|
||||
def apply(id: Int): Option[Source] = byId get id
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ trait SequentialProvider extends Actor {
|
|||
def debug = false
|
||||
lazy val name = ornicar.scalalib.Random nextString 4
|
||||
|
||||
val windowCount = new lila.common.WindowCount(1 second)
|
||||
|
||||
private def idle: Receive = {
|
||||
|
||||
case msg =>
|
||||
|
@ -54,7 +52,7 @@ trait SequentialProvider extends Actor {
|
|||
private def debugQueue {
|
||||
if (debug) queue.size match {
|
||||
case size if (size == 50 || (size >= 100 && size % 100 == 0)) =>
|
||||
logger.branch("SequentialProvider").warn(s"Seq[$name] queue = $size, mps = ${windowCount.get}")
|
||||
logger.branch("SequentialProvider").warn(s"Seq[$name] queue = $size")
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +64,6 @@ trait SequentialProvider extends Actor {
|
|||
}
|
||||
|
||||
private def processThenDone(signal: Any) {
|
||||
windowCount.add
|
||||
signal match {
|
||||
// we don't want to send Done after actor death
|
||||
case SequentialProvider.Terminate => self ! PoisonPill
|
||||
|
|
|
@ -173,10 +173,6 @@ case class Abort(gameId: String, byColor: String)
|
|||
case class Berserk(gameId: String, userId: String)
|
||||
case class IsOnGame(color: chess.Color)
|
||||
sealed trait SocketEvent
|
||||
object SocketEvent {
|
||||
case class OwnerJoin(gameId: String, color: chess.Color, ip: String) extends SocketEvent
|
||||
case class Stop(gameId: String) extends SocketEvent
|
||||
}
|
||||
case class FishnetPlay(uci: chess.format.Uci, currentFen: chess.format.FEN)
|
||||
}
|
||||
|
||||
|
|
|
@ -380,6 +380,11 @@ whenTimeRemainingLessThanThirtySeconds=< 30 sekondes voor tyd uitloop
|
|||
difficultyEasy=Maklik
|
||||
difficultyNormal=Normaal
|
||||
difficultyHard=Moeilik
|
||||
xLeftANoteOnY=%s het 'n nota op %s
|
||||
xCompetesInY=%s kompeteer in %s
|
||||
xAskedY=%s gevra %s
|
||||
xAnsweredY=%s antwoord %s
|
||||
xCommentedY=%s kommentaar %s
|
||||
timeline=Tydlyn
|
||||
seeAllTournaments=Bekyk alle toernooie
|
||||
starting=Aanvang:
|
||||
|
|
|
@ -32,7 +32,7 @@ theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Tu oponente puede
|
|||
makeYourOpponentResign=Forzar victoria
|
||||
forceResignation=Reclamar la victoria
|
||||
forceDraw=Declarar tablas
|
||||
talkInChat=Por favor, sé amable en el chat
|
||||
talkInChat=Por favor, sea amable en el chat
|
||||
theFirstPersonToComeOnThisUrlWillPlayWithYou=La primera persona que entre en este enlace jugará contigo.
|
||||
whiteResigned=Las blancas abandonaron
|
||||
blackResigned=Las negras abandonaron
|
||||
|
@ -66,7 +66,7 @@ signIn=Iniciar sesión
|
|||
newToLichess=¿Nuevo en Lichess?
|
||||
youNeedAnAccountToDoThat=Necesitas crear una cuenta para hacer eso
|
||||
signUp=Registrarse
|
||||
computersAreNotAllowedToPlay=No les está permitido jugar a las computadoras ni a jugadores asistidos por ellas. Por favor, no te ayudes de motores de ajedrez, bases de datos o de otros jugadores durante la partida. Además, ten en cuenta que se desaconseja la utilización de varias cuentas de usuario y que el uso de un excesivo número de cuentas resultará en la cancelación de las mismas.
|
||||
computersAreNotAllowedToPlay=No les está permitido jugar a las computadoras ni a jugadores computarizados. Por favor, no te ayudes de motores de ajedrez, bases de datos o de otros jugadores durante la partida. Además, ten en cuenta que no se aconseja la utilización de varias cuentas de usuario y que el uso de un excesivo número de cuentas resultará en la cancelación de las mismas.
|
||||
games=Partidas
|
||||
forum=Foro
|
||||
xPostedInForumY=%s ha escrito en el tema %s
|
||||
|
@ -112,7 +112,7 @@ blackDeclinesDraw=Las negras rechazan tablas
|
|||
yourOpponentOffersADraw=Tu oponente ofrece tablas
|
||||
accept=Aceptar
|
||||
decline=Rechazar
|
||||
playingRightNow=Jugándose ahora
|
||||
playingRightNow=Jugándo ahora
|
||||
finished=Terminado
|
||||
abortGame=Cancelar partida
|
||||
gameAborted=Partida cancelada
|
||||
|
@ -175,7 +175,7 @@ newTeam=Equipo nuevo
|
|||
myTeams=Mis equipos
|
||||
noTeamFound=Ningún equipo encontrado
|
||||
joinTeam=Únete al equipo
|
||||
quitTeam=Abandona el equipo
|
||||
quitTeam=Abandonar el equipo
|
||||
anyoneCanJoin=Cualquiera puede unirse
|
||||
aConfirmationIsRequiredToJoin=Se requiere una confirmación para unirse
|
||||
joiningPolicy=Política para unirse
|
||||
|
@ -509,7 +509,7 @@ youHaveAlreadyRegisteredTheEmail=Ya has registrado el correo electrónico: %s
|
|||
kidMode=Modo Infantil
|
||||
playChessEverywhere=Juega ajedrez donde sea
|
||||
asFreeAsLichess=Tan gratuito como lichess
|
||||
builtForTheLoveOfChessNotMoney=Trabajar por amor al ajedrez, no por dinero
|
||||
builtForTheLoveOfChessNotMoney=Construido por amor al ajedrez, no por dinero
|
||||
everybodyGetsAllFeaturesForFree=Todos tienen acceso gratuito a todas las opciones
|
||||
zeroAdvertisement=Sin anuncios
|
||||
fullFeatured=Todas las funciones
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
playWithAFriend=Jocar con un amico
|
||||
playWithTheMachine=Jocar con le machina
|
||||
toInviteSomeoneToPlayGiveThisUrl=Pro invitar alcuno a jocar, dona iste adresse
|
||||
gameOver=Partita finite
|
||||
waitingForOpponent=Attendente pro adversario
|
||||
waiting=Attendente
|
||||
yourTurn=Tu vice
|
||||
aiNameLevelAiLevel=%s nivello %s
|
||||
level=Nivello
|
||||
toggleTheChat=Alternar le chat
|
||||
toggleSound=Alternar sono
|
||||
chat=Chat
|
||||
resign=Resignar se
|
||||
checkmate=Chaco mat
|
||||
stalemate=Impasse
|
||||
white=Blanc
|
||||
black=Nigre
|
||||
randomColor=Latere aleatori
|
||||
createAGame=Crear un partita
|
||||
whiteIsVictorious=Le blanc es victoriose
|
||||
blackIsVictorious=Le nigre es victoriose
|
||||
kingInTheCenter=Rege in le centro
|
||||
threeChecks=Tres chacos
|
||||
raceFinished=Curso finite
|
||||
variantEnding=Finite per regula variante
|
||||
newOpponent=Nove adversario
|
||||
yourOpponentWantsToPlayANewGameWithYou=Tu adversario vole jocar un nove partita con tu
|
||||
joinTheGame=Entrar in le joco
|
||||
whitePlays=Le blanc deve jocar
|
||||
blackPlays=Le nigre deve jocar
|
||||
theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim=Le altere jocator forsan abandonava le partite. Tu pote reclamar victoria, demandar le partita nulle o attender.
|
||||
makeYourOpponentResign=Obligar tu adversario a resginar
|
||||
forceResignation=Declarar victoria
|
||||
forceDraw=Considerar le partita nulle
|
||||
talkInChat=Per favor, sia gentil in le chat!
|
||||
theFirstPersonToComeOnThisUrlWillPlayWithYou=Le prime persona qui veni a iste adresse jocara con tu.
|
||||
whiteResigned=Le blanc resignava
|
||||
blackResigned=Le nigre resignava
|
||||
whiteLeftTheGame=Le blanc abandonava le partita
|
||||
blackLeftTheGame=Le nigre abandonava le partita
|
||||
shareThisUrlToLetSpectatorsSeeTheGame=Publica iste adresse pro lassar spectatores vider le joco
|
||||
theComputerAnalysisHasFailed=Le analyse de computator falleva
|
||||
viewTheComputerAnalysis=Vide le analyse de computator
|
||||
requestAComputerAnalysis=Requirer un analyse de computator
|
||||
computerAnalysis=Analyse de computator
|
||||
analysis=Analyse
|
||||
blunders=Errores grave
|
||||
mistakes=Errores
|
||||
inaccuracies=imprecisiones
|
||||
moveTimes=Tempo per movimento
|
||||
flipBoard=Girar le tabuliero
|
||||
threefoldRepetition=Repetition triple
|
||||
claimADraw=Reclamar victoria
|
||||
offerDraw=Offerer que la partita sia nulle
|
||||
draw=Partita nulle
|
||||
nbConnectedPlayers=%s jocatores
|
||||
gamesBeingPlayedRightNow=Partitas currente
|
||||
viewAllNbGames=%s Jocos
|
||||
viewNbCheckmates=%s Chacos mat
|
||||
nbBookmarks=%s Favoritos
|
||||
nbPopularGames=%s Jocos popular
|
||||
nbAnalysedGames=%s Jocos analysate
|
||||
viewInFullSize=Vider in plen dimension
|
||||
logOut=Clauder session
|
||||
signIn=Identification
|
||||
newToLichess=Tu es nove a Lichess?
|
||||
youNeedAnAccountToDoThat=Tu manca un conto pro isto
|
||||
signUp=Registration
|
||||
computersAreNotAllowedToPlay=Computatores e programmas non esse permittite jocar hic. Per favor, non prende assistentia de motores de chacos, bases de datos o de altere jocatores durante que joca. De plus, nota que crear multiple contos es fortemente discoragiate e iste practica excessive conduce a interdiction de uso.
|
||||
games=Jocos
|
||||
forum=Foro
|
||||
xPostedInForumY=%s postava in topico %s
|
||||
latestForumPosts=Ultime messages de foro
|
||||
players=Jocatores
|
||||
minutesPerSide=Minutas per latere
|
||||
variant=Variante
|
||||
variants=Variantes
|
||||
timeControl=Controlo de tempore
|
||||
realTime=Tempore real
|
||||
correspondence=Correspondentia
|
||||
daysPerTurn=Dies per vice
|
||||
oneDay=Un die
|
||||
nbDays=%s dies
|
||||
nbHours=%s horas
|
||||
time=Tempore
|
||||
rating=Classification
|
||||
ratingStats=Statisticas de classification
|
||||
username=Nomine de usator
|
||||
usernameOrEmail=Nomine de usator o adresse de e-posta
|
||||
password=Contrasigno
|
||||
haveAnAccount=Tu ha un conto?
|
||||
changePassword=Cambiar contrasigno
|
||||
changeEmail=Cambiar adresse de e-posta
|
||||
email=E-posta
|
||||
emailIsOptional=Le e-posta es optional. Lichess va usar tu adresse de e-posta pro restabilir tu contrasigno si tu oblida lo.
|
||||
passwordReset=Restabilir contrasigno
|
||||
forgotPassword=Io ha oblidate mi contrasigno.
|
||||
rank=Classification
|
||||
gamesPlayed=Partitas jocate
|
||||
nbGamesWithYou=%s partitas con vos
|
||||
declineInvitation=Declinar invitation
|
||||
cancel=Cancellar
|
||||
timeOut=Tempore finite
|
||||
drawOfferSent=Offerta de partita nulle inviate
|
||||
drawOfferDeclined=Offerta de partita nulle declinate
|
||||
drawOfferAccepted=Offerta de partita nulle acceptate
|
||||
drawOfferCanceled=Offerta de partita nulle cancellate
|
||||
whiteOffersDraw=Le blanc offere le partita nulle
|
||||
blackOffersDraw=Le nigre offere le partita nulle
|
||||
whiteDeclinesDraw=Le blanca declina le partita nulle
|
||||
blackDeclinesDraw=Le nigre declina le partita nulle
|
||||
yourOpponentOffersADraw=Tu adversario offere le partita nulle
|
||||
accept=Acceptar
|
||||
decline=Declinar
|
||||
playingRightNow=Jocando ora mesmo
|
||||
finished=Finite
|
||||
abortGame=Cancellar partita
|
||||
gameAborted=Partita cancellate
|
||||
standard=Standard
|
||||
unlimited=Illimitate
|
||||
mode=Modo
|
||||
casual=Amical
|
||||
rated=Classificatori
|
||||
thisGameIsRated=Iste partita vale punctos classificatori
|
||||
rematch=Vindicar
|
||||
rematchOfferSent=Offerta de vindicantia inviate
|
||||
rematchOfferAccepted=Offerta de vindicantia acceptate
|
||||
rematchOfferCanceled=Offerta de vindicantia cancellate
|
||||
rematchOfferDeclined=Offerta de vindicantia declinate
|
||||
cancelRematchOffer=Cancellar offerta de vindicantia
|
||||
viewRematch=Vider le vindicantia a iste partita
|
||||
play=Jocar
|
||||
inbox=Cassa de entrata
|
||||
chatRoom=Sala de conversation
|
||||
spectatorRoom=Sala de spectatores
|
||||
composeMessage=Componer message
|
||||
noNewMessages=Nulle messages nove
|
||||
subject=Subjecto
|
||||
recipient=A
|
||||
send=Inviar
|
||||
incrementInSeconds=Incremento in secundas
|
||||
freeOnlineChess=Chacos Gratis in Linea
|
||||
spectators=Spectatores:
|
||||
nbWins=%s victorias
|
||||
nbLosses=%s perditas
|
||||
nbDraws=%s partitas nulle
|
||||
exportGames=Exportar partitas
|
||||
ratingRange=Intervallo de classificationes
|
||||
giveNbSeconds=Dona %s secundas
|
||||
premoveEnabledClickAnywhereToCancel=Preselection active - Clicca ubique pro cancellar
|
||||
thisPlayerUsesChessComputerAssistance=Iste jocator usa assistentia de un computator de chacos
|
||||
thisPlayerArtificiallyIncreasesTheirRating=Iste jocator alterava su classification de un maniera artificial
|
||||
openingExplorer=Explorator de initio de joco
|
||||
takeback=Retornar
|
||||
proposeATakeback=Proponer un retorno del ultime movimento
|
||||
takebackPropositionSent=Proposition de retorno inviate
|
||||
takebackPropositionDeclined=Proposition de retorno declinate
|
||||
takebackPropositionAccepted=Proposition de retorno acceptate
|
||||
takebackPropositionCanceled=Proposition de retorno cancellate
|
||||
yourOpponentProposesATakeback=Tu adversario propone un retorno del ultime movimento
|
||||
bookmarkThisGame=Adder iste joco al favorites
|
||||
search=Recercar
|
||||
advancedSearch=Recerca avantiate
|
||||
tournament=Torneo
|
||||
tournaments=Torneos
|
||||
tournamentPoints=Punctos ab torneos
|
||||
viewTournament=Vider torneo
|
||||
backToTournament=Retro al torneo
|
||||
backToGame=Retro al partite
|
||||
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=Servitor de Chacos gratis in linea. Joca Chacos ora in un interfacie simple. Nulle registration, nulle annuncios, nulle plugins requirite. Joca Chacos con le computator, con amicos o con adversarios aleatori.
|
||||
teams=Equipas
|
||||
nbMembers=%s membros
|
||||
allTeams=Tote equipas
|
||||
newTeam=Nove equipa
|
||||
myTeams=Mi equipas
|
||||
noTeamFound=Nulle equipa trovate
|
||||
joinTeam=Facer se membro de equipa
|
||||
quitTeam=Abandonar equipa
|
||||
anyoneCanJoin=Libere a totos
|
||||
aConfirmationIsRequiredToJoin=Un confirmation es requirite pro facer se membro.
|
||||
joiningPolicy=Linea de conducta pro facer se membro.
|
||||
teamLeader=Conductor de equipa
|
||||
teamBestPlayers=Le melior jocatores
|
||||
teamRecentMembers=Membros plus recente
|
||||
xJoinedTeamY=%s se faceva membro de %s
|
||||
xCreatedTeamY=%s creava le equipa %s
|
||||
averageElo=Classification medie
|
||||
location=Location
|
||||
settings=Configuration
|
||||
filterGames=Filtrar partitas
|
||||
reset=Remontar al patronos
|
||||
apply=Submitter
|
||||
leaderboard=Tabula de melior jocatores
|
||||
pasteTheFenStringHere=Colla le notation FEN hic
|
||||
pasteThePgnStringHere=Colla le notation PGN hic
|
||||
fromPosition=Ab position
|
||||
continueFromHere=Continuar ab hic
|
||||
importGame=Importar partita
|
||||
nbImportedGames=%s partitas importate
|
||||
thisIsAChessCaptcha=Isto es un CAPTCHA de chacos.
|
||||
clickOnTheBoardToMakeYourMove=Clicca in le tabuliero pro facer su movimento e provar que es human.
|
||||
notACheckmate=Non es chaco mat
|
||||
colorPlaysCheckmateInOne=Le %s debe jocar; chaco mat in un movimento
|
||||
retry=Tentar de nove
|
||||
reconnecting=Reconnectente
|
||||
onlineFriends=Amicos in linea
|
||||
noFriendsOnline=Nulle amicos in linea
|
||||
findFriends=Cerca amicos
|
||||
favoriteOpponents=Adversarios favorite
|
||||
follow=Sequer
|
||||
following=Sequente
|
||||
unfollow=Cessar de sequer
|
||||
block=Blocar
|
||||
blocked=Blocate
|
||||
unblock=Disblocar
|
||||
followsYou=Sequente tu
|
||||
xStartedFollowingY=%s initiava sequer %s
|
||||
nbFollowers=%s sequitores
|
||||
nbFollowing=sequente %s jocatores
|
||||
more=Plus
|
||||
memberSince=Membro desde
|
||||
lastSeenActive=Ultime connexion %s
|
||||
challengeToPlay=Defiar a un joco
|
||||
player=Jocator
|
||||
list=Lista
|
||||
graph=Graphico
|
||||
lessThanNbMinutes=Minus que %s minutas
|
||||
xToYMinutes=Inter %s e %s minutas
|
||||
textIsTooShort=Texto es troppo breve.
|
||||
textIsTooLong=Texto es troppo longe.
|
||||
required=Requirite.
|
||||
openTournaments=Torneos aperte
|
||||
duration=Duration
|
||||
winner=Vincitor
|
||||
standing=Punctuation
|
||||
createANewTournament=Crear un nove torneo
|
||||
join=Participar
|
||||
withdraw=Retirar se
|
||||
points=Punctos
|
||||
wins=Victorias
|
||||
losses=Peditas
|
||||
winStreak=Victorias consecutive
|
||||
createdBy=Create per
|
||||
tournamentIsStarting=Le torneo es a initiar
|
||||
membersOnly=Solmente membros
|
||||
boardEditor=Editor de tabuliero
|
||||
startPosition=Position de initio
|
||||
clearBoard=Rader tabuliero
|
||||
savePosition=Salvar position
|
||||
loadPosition=Cargar position
|
||||
isPrivate=Private
|
||||
reportXToModerators=Reportar %s al moderatores
|
||||
profile=Profilo
|
||||
editProfile=Editar profilo
|
||||
firstName=Prenomine
|
||||
lastName=Nomine de famiglia
|
||||
biography=Biographia
|
||||
country=Pais
|
||||
preferences=Preferentias
|
||||
watchLichessTV=Spectar Lichess TV
|
||||
previouslyOnLichessTV=Previemente in Lichess TV
|
||||
onlinePlayers=Jocatores in linea
|
||||
activeToday=Active hodie
|
||||
activePlayers=Jocatores active
|
||||
bewareTheGameIsRatedButHasNoClock=Attention, le partita es classificatori sed illo non ha controlo de tempore!
|
||||
training=Exercitation
|
||||
yourPuzzleRatingX=Tu classification de enigmas: %s
|
||||
findTheBestMoveForWhite=Cerca le melior movimento pro le blanc.
|
||||
findTheBestMoveForBlack=Cerca le melior movimento pro le nigre.
|
||||
toTrackYourProgress=Pro salvar tu progresso:
|
||||
trainingSignupExplanation=Lichess providera enigmas que equala a tu habilitates, pro facer melior sessiones de exercitio.
|
||||
puzzleId=Enigma %s
|
||||
puzzleOfTheDay=Enigma del die
|
||||
clickToSolve=Clicca pro resolver
|
||||
goodMove=Bon movimento
|
||||
butYouCanDoBetter=Ma tu pote facer melior.
|
||||
bestMove=Melior movimento!
|
||||
keepGoing=Continua...
|
||||
puzzleFailed=Enigma falleva
|
||||
butYouCanKeepTrying=Ma tu pote continuar a tentar.
|
||||
victory=Victoria!
|
||||
giveUp=Ceder
|
||||
puzzleSolvedInXSeconds=Enigma resolvite in %s secundas.
|
||||
wasThisPuzzleAnyGood=Esque iste enigma esseva bon?
|
||||
pleaseVotePuzzle=Adjuta Lichess a meliorar per tu voto, usante le sagitta a alto o a basso:
|
||||
thankYou=Gratias!
|
||||
ratingX=Classification: %s
|
||||
playedXTimes=Jocate %s vices
|
||||
fromGameLink=Ab joco %s
|
||||
startTraining=Comenciar a exercitar
|
||||
continueTraining=Continuar exercitante
|
||||
retryThisPuzzle=Tentar de nove iste enigma
|
||||
thisPuzzleIsCorrect=Iste enigma es correcte e interessante
|
||||
thisPuzzleIsWrong=Iste enigma es incorrecte o enoiose
|
||||
youHaveNbSecondsToMakeYourFirstMove=Tu ha %s secundas pro facer tu prime movimento!
|
||||
puzzles=Enigmas
|
||||
coordinates=Coordinates
|
||||
tournamentWinners=Vincitores de torneos
|
||||
name=Nomine
|
||||
description=Description
|
||||
no=No
|
||||
yes=Si
|
||||
help=Adjuta:
|
||||
createANewTopic=Crear un nove thema
|
||||
topics=Themas
|
||||
replies=Responsas
|
||||
reply=Responder
|
||||
message=Message
|
||||
createTheTopic=Crear le thema
|
||||
reportAUser=Reportar un usator
|
||||
user=Usator
|
||||
reason=Ration
|
||||
whatIsIheMatter=Qual es le motivo?
|
||||
cheat=Fraude
|
||||
insult=Insulto
|
||||
troll=Troll
|
||||
other=Altere
|
||||
by=per %s
|
||||
thisTopicIsNowClosed=Iste thema es ora claudite
|
||||
donate=Donar
|
||||
blog=Blog
|
||||
questionsAndAnswers=Demandas e responsas
|
||||
currentPassword=Contrasigno actual
|
||||
newPassword=Nove contrasigno
|
||||
newPasswordAgain=Confirmar nove contrasigno
|
||||
chessClock=Horologio de chacos
|
||||
never=Nunquam
|
|
@ -1,7 +1,7 @@
|
|||
playWithAFriend=Spill mot en venn
|
||||
playWithTheMachine=Spill mot maskinen
|
||||
toInviteSomeoneToPlayGiveThisUrl=For å invitere noen til et spill, gi dem denne lenken
|
||||
gameOver=Spillet er slutt
|
||||
gameOver=Partiet er slutt
|
||||
waitingForOpponent=Venter på motstander
|
||||
waiting=Venter
|
||||
yourTurn=Ditt trekk
|
||||
|
@ -16,7 +16,7 @@ stalemate=Patt
|
|||
white=Hvit
|
||||
black=Svart
|
||||
randomColor=Tilfeldig farge
|
||||
createAGame=Start et spill
|
||||
createAGame=Start et parti
|
||||
whiteIsVictorious=Hvit har vunnet
|
||||
blackIsVictorious=Svart har vunnet
|
||||
kingInTheCenter=Kongen i midten
|
||||
|
@ -96,7 +96,7 @@ emailIsOptional=E-postadresse er valgfritt. Lichess vil bruke den til å tilbake
|
|||
passwordReset=Tilbakestill passord
|
||||
forgotPassword=Glemt passord?
|
||||
rank=Rangering
|
||||
gamesPlayed=Partier spilt
|
||||
gamesPlayed=partier spilt
|
||||
nbGamesWithYou=%s partier med deg
|
||||
declineInvitation=Avslå invitasjon
|
||||
cancel=Avbryt
|
||||
|
@ -495,7 +495,7 @@ weHaveSentYouAnEmailTo=Vi har sendt en e-post til %s. Klikk på linken i e-poste
|
|||
byRegisteringYouAgreeToBeBoundByOur=Ved å registrere deg, godtar du å våre %s.
|
||||
networkLagBetweenYouAndLichess=Nettverksforsinkelse mellom deg og lichess
|
||||
timeToProcessAMoveOnLichessServer=Tiden det tar å behandle et trekk på lichess serveren.
|
||||
downloadAnnotated=Nedlasting notert
|
||||
downloadAnnotated=Last ned kommentert
|
||||
downloadRaw=Last ned rå
|
||||
downloadImported=Last ned importen
|
||||
printFriendlyPDF=Printervennelig PDF
|
||||
|
|
|
@ -493,7 +493,7 @@ areYouSureYouEvenRegisteredYourEmailOnLichess=Czy jesteś pewien, że chcesz zar
|
|||
itWasNotRequiredForYourRegistration=To nie jest wymagane aby się zarejestrować
|
||||
weHaveSentYouAnEmailTo=Wysłaliśmy wiadomość e-mail do %s.Kliknij w link znajdujący się w wiadomości aby zresetować swoje hasło.
|
||||
byRegisteringYouAgreeToBeBoundByOur=Rejestrując się,wyrażasz zgodę na przestrzeganie przez nas %s.
|
||||
networkLagBetweenYouAndLichess=Opóźnienie w połączenia internetowym pomiędzy tobą i lichess
|
||||
networkLagBetweenYouAndLichess=Opóźnienie w połączeniu internetowym pomiędzy tobą i lichess
|
||||
timeToProcessAMoveOnLichessServer=Czas na przetworzenie zagrania na serwerze lichess
|
||||
downloadAnnotated=Pobranie odnotowane
|
||||
downloadRaw=Pobranie puste
|
||||
|
|
|
@ -5,3 +5,4 @@ gameOver=Game Over
|
|||
waitingForOpponent=Elinde isitha
|
||||
waiting=Ukulinda
|
||||
yourTurn=Ithuba lakho
|
||||
aiNameLevelAiLevel=%s Izinga %s
|
||||
|
|
|
@ -87,7 +87,8 @@ private[i18n] object Contributors {
|
|||
"fy" -> List("FishingCat"),
|
||||
"jb" -> List("username05"),
|
||||
"tg" -> List("mondayguy"),
|
||||
"cv" -> List("pentille"))
|
||||
"cv" -> List("pentille"),
|
||||
"ia" -> List("GuimaraesMello"))
|
||||
|
||||
def apply(code: String): List[String] = ~(all get code)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Generated with bin/trans-dump at 2016-11-29 15:28:02 UTC
|
||||
// Generated with bin/trans-dump at 2016-12-01 08:22:12 UTC
|
||||
package lila.i18n
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
|
|
@ -4,7 +4,7 @@ import akka.actor.ActorRef
|
|||
import chess.{ Game => ChessGame, Board, Mode, Clock, Color => ChessColor }
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import actorApi.{ RemoveHook, BiteHook, BiteSeek, JoinHook, JoinSeek, LobbyUser }
|
||||
import actorApi.{ JoinHook, JoinSeek, LobbyUser }
|
||||
import lila.game.{ GameRepo, Game, Player, Pov, Progress, PerfPicker }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
|
@ -62,7 +62,7 @@ private[lobby] object Biter {
|
|||
private def makeGame(hook: Hook) = Game.make(
|
||||
game = ChessGame(
|
||||
board = Board init hook.realVariant,
|
||||
clock = hook.clock.some),
|
||||
clock = hook.clock.toClock.some),
|
||||
whitePlayer = Player.white,
|
||||
blackPlayer = Player.black,
|
||||
mode = hook.realMode,
|
||||
|
@ -85,7 +85,7 @@ private[lobby] object Biter {
|
|||
def canJoin(hook: Hook, user: Option[LobbyUser]): Boolean =
|
||||
hook.realMode.casual.fold(
|
||||
user.isDefined || hook.allowAnon,
|
||||
user ?? { _.lame == hook.lame }
|
||||
user ?? { _.engine == hook.engine }
|
||||
) &&
|
||||
!(user ?? (u => hook.userId contains u.id)) &&
|
||||
!(hook.userId ?? (user ?? (_.blocking)).contains) &&
|
||||
|
@ -98,7 +98,7 @@ private[lobby] object Biter {
|
|||
|
||||
def canJoin(seek: Seek, user: LobbyUser): Boolean =
|
||||
seek.user.id != user.id &&
|
||||
(seek.realMode.casual || user.lame == seek.user.lame) &&
|
||||
(seek.realMode.casual || user.engine == seek.user.engine) &&
|
||||
!(user.blocking contains seek.user.id) &&
|
||||
!(seek.user.blocking contains user.id) &&
|
||||
seek.realRatingRange.fold(true) { range =>
|
||||
|
|
|
@ -4,7 +4,6 @@ import akka.actor._
|
|||
import com.typesafe.config.Config
|
||||
|
||||
import lila.common.PimpedConfig._
|
||||
import lila.socket.History
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
|
@ -14,15 +13,14 @@ final class Env(
|
|||
blocking: String => Fu[Set[String]],
|
||||
playban: String => Fu[Option[lila.playban.TempBan]],
|
||||
gameCache: lila.game.Cached,
|
||||
poolApi: lila.pool.PoolApi,
|
||||
system: ActorSystem,
|
||||
scheduler: lila.common.Scheduler) {
|
||||
|
||||
private val settings = new {
|
||||
val MessageTtl = config duration "message.ttl"
|
||||
val NetDomain = config getString "net.domain"
|
||||
val SocketName = config getString "socket.name"
|
||||
val SocketUidTtl = config duration "socket.uid.ttl"
|
||||
val OrphanHookTtl = config duration "orphan_hook.ttl"
|
||||
val ActorName = config getString "actor.name"
|
||||
val BroomPeriod = config duration "broom_period"
|
||||
val ResyncIdsPeriod = config duration "resync_ids_period"
|
||||
|
@ -35,7 +33,6 @@ final class Env(
|
|||
import settings._
|
||||
|
||||
private val socket = system.actorOf(Props(new Socket(
|
||||
history = history,
|
||||
uidTtl = SocketUidTtl)), name = SocketName)
|
||||
|
||||
lazy val seekApi = new SeekApi(
|
||||
|
@ -55,6 +52,7 @@ final class Env(
|
|||
maxPlaying = MaxPlaying,
|
||||
blocking = blocking,
|
||||
playban = playban,
|
||||
poolApi = poolApi,
|
||||
onStart = onStart)
|
||||
}
|
||||
|
||||
|
@ -62,10 +60,9 @@ final class Env(
|
|||
hub = hub,
|
||||
lobby = lobby,
|
||||
socket = socket,
|
||||
poolApi = poolApi,
|
||||
blocking = blocking)
|
||||
|
||||
lazy val history = new History[actorApi.Messadata](ttl = MessageTtl)
|
||||
|
||||
private val abortListener = new AbortListener(seekApi = seekApi)
|
||||
|
||||
system.lilaBus.subscribe(system.actorOf(Props(new Actor {
|
||||
|
@ -85,6 +82,7 @@ object Env {
|
|||
blocking = lila.relation.Env.current.api.fetchBlocking,
|
||||
playban = lila.playban.Env.current.api.currentBan _,
|
||||
gameCache = lila.game.Env.current.cached,
|
||||
poolApi = lila.pool.Env.current.api,
|
||||
system = lila.common.PlayApp.system,
|
||||
scheduler = lila.common.PlayApp.scheduler)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ import ornicar.scalalib.Random
|
|||
import play.api.libs.json._
|
||||
|
||||
import actorApi.LobbyUser
|
||||
import lila.common.PimpedJson._
|
||||
import lila.game.PerfPicker
|
||||
import lila.rating.RatingRange
|
||||
import lila.user.{ User, Perfs }
|
||||
import lila.common.PimpedJson._
|
||||
|
||||
// realtime chess, volatile
|
||||
case class Hook(
|
||||
|
@ -17,7 +17,7 @@ case class Hook(
|
|||
uid: String, // owner socket uid
|
||||
sid: Option[String], // owner cookie (used to prevent multiple hooks)
|
||||
variant: Int,
|
||||
clock: Clock,
|
||||
clock: Clock.Config,
|
||||
mode: Int,
|
||||
allowAnon: Boolean,
|
||||
color: String,
|
||||
|
@ -34,7 +34,9 @@ case class Hook(
|
|||
def memberOnly = !allowAnon
|
||||
|
||||
def compatibleWith(h: Hook) =
|
||||
compatibilityProperties == h.compatibilityProperties &&
|
||||
mode == h.mode &&
|
||||
variant == h.variant &&
|
||||
clock == h.clock &&
|
||||
(realColor compatibleWith h.realColor) &&
|
||||
(memberOnly || h.memberOnly).fold(isAuth && h.isAuth, true) &&
|
||||
ratingRangeCompatibleWith(h) && h.ratingRangeCompatibleWith(this) &&
|
||||
|
@ -44,8 +46,6 @@ case class Hook(
|
|||
range => h.rating ?? range.contains
|
||||
}
|
||||
|
||||
private def compatibilityProperties = (variant, clock.limit, clock.increment, mode)
|
||||
|
||||
lazy val realRatingRange: Option[RatingRange] = RatingRange noneIfDefault ratingRange
|
||||
|
||||
def userId = user.map(_.id)
|
||||
|
@ -53,10 +53,8 @@ case class Hook(
|
|||
def username = user.fold(User.anonymous)(_.username)
|
||||
def rating = user flatMap { u => perfType map (_.key) flatMap u.ratingMap.get }
|
||||
def engine = user ?? (_.engine)
|
||||
def booster = user ?? (_.booster)
|
||||
def lame = user ?? (_.lame)
|
||||
|
||||
def render: JsObject = Json.obj(
|
||||
lazy val render: JsObject = Json.obj(
|
||||
"id" -> id,
|
||||
"uid" -> uid,
|
||||
"u" -> user.map(_.username),
|
||||
|
@ -72,7 +70,29 @@ case class Hook(
|
|||
|
||||
lazy val perfType = PerfPicker.perfType(speed, realVariant, none)
|
||||
|
||||
private lazy val speed = Speed(clock.some)
|
||||
def randomColor = color == "random"
|
||||
|
||||
lazy val compatibleWithPools =
|
||||
realMode.rated && realVariant.standard && randomColor &&
|
||||
lila.pool.PoolList.clockStringSet.contains(clock.show)
|
||||
|
||||
def compatibleWithPool(poolClock: chess.Clock.Config) =
|
||||
compatibleWithPools && clock == poolClock
|
||||
|
||||
def likePoolFiveO = compatibleWithPools && clock.show == "5+0"
|
||||
|
||||
def toPool = lila.pool.HookThieve.PoolHook(
|
||||
hookId = id,
|
||||
member = lila.pool.PoolMember(
|
||||
userId = user.??(_.id),
|
||||
socketId = lila.socket.Socket.Uid(uid),
|
||||
rating = rating | lila.rating.Glicko.defaultIntRating,
|
||||
ratingRange = realRatingRange,
|
||||
engine = user.??(_.engine),
|
||||
blocking = lila.pool.PoolMember.BlockedUsers(user.??(_.blocking)),
|
||||
since = createdAt))
|
||||
|
||||
private lazy val speed = Speed(clock)
|
||||
}
|
||||
|
||||
object Hook {
|
||||
|
@ -82,7 +102,7 @@ object Hook {
|
|||
def make(
|
||||
uid: String,
|
||||
variant: chess.variant.Variant,
|
||||
clock: Clock,
|
||||
clock: Clock.Config,
|
||||
mode: Mode,
|
||||
allowAnon: Boolean,
|
||||
color: String,
|
||||
|
|
|
@ -22,6 +22,8 @@ object HookRepo {
|
|||
|
||||
def byId(id: String) = hooks find (_.id == id)
|
||||
|
||||
def byIds(ids: Set[String]) = hooks filter { h => ids contains h.id }
|
||||
|
||||
def byUid(uid: String) = hooks find (_.uid == uid)
|
||||
|
||||
def bySid(sid: String) = hooks find (_.sid == sid.some)
|
||||
|
@ -42,6 +44,9 @@ object HookRepo {
|
|||
partition(_.createdAt isAfter limit)
|
||||
}
|
||||
|
||||
def poolCandidates(clock: chess.Clock.Config): Vector[lila.pool.HookThieve.PoolHook] =
|
||||
hooks.filter(_ compatibleWithPool clock).map(_.toPool)
|
||||
|
||||
// keeps hooks that hold true
|
||||
// returns removed hooks
|
||||
private def partition(f: Hook => Boolean): Vector[Hook] = {
|
||||
|
|
|
@ -18,24 +18,17 @@ private[lobby] final class Lobby(
|
|||
maxPlaying: Int,
|
||||
blocking: String => Fu[Set[String]],
|
||||
playban: String => Fu[Option[lila.playban.TempBan]],
|
||||
poolApi: lila.pool.PoolApi,
|
||||
onStart: String => Unit) extends Actor {
|
||||
|
||||
def receive = {
|
||||
|
||||
case HooksFor(userOption) =>
|
||||
val replyTo = sender
|
||||
(userOption.map(_.id) ?? blocking) foreach { blocks =>
|
||||
val lobbyUser = userOption map { LobbyUser.make(_, blocks) }
|
||||
replyTo ! HookRepo.vector.filter { hook =>
|
||||
~(hook.userId |@| lobbyUser.map(_.id)).apply(_ == _) || Biter.canJoin(hook, lobbyUser)
|
||||
}
|
||||
}
|
||||
|
||||
case msg@AddHook(hook) => {
|
||||
lila.mon.lobby.hook.create()
|
||||
if (hook.realVariant.standard) lila.mon.lobby.hook.standardColor(hook.realMode.name, hook.color)()
|
||||
HookRepo byUid hook.uid foreach remove
|
||||
hook.sid ?? { sid => HookRepo bySid sid foreach remove }
|
||||
findCompatible(hook) foreach {
|
||||
if (!hook.compatibleWithPools) findCompatible(hook) foreach {
|
||||
case Some(h) => self ! BiteHook(h.id, hook.uid, hook.user)
|
||||
case None => self ! SaveHook(msg)
|
||||
}
|
||||
|
@ -56,9 +49,8 @@ private[lobby] final class Lobby(
|
|||
socket ! msg
|
||||
}
|
||||
|
||||
case CancelHook(uid) => {
|
||||
case CancelHook(uid) =>
|
||||
HookRepo byUid uid foreach remove
|
||||
}
|
||||
|
||||
case CancelSeek(seekId, user) => seekApi.removeBy(seekId, user.id) >>- {
|
||||
socket ! RemoveSeek(seekId)
|
||||
|
@ -108,6 +100,7 @@ private[lobby] final class Lobby(
|
|||
.pipeTo(self)
|
||||
|
||||
case Lobby.WithPromise(SocketUids(uids), promise) =>
|
||||
poolApi socketIds uids
|
||||
val createdBefore = DateTime.now minusSeconds 5
|
||||
val hooks = {
|
||||
(HookRepo notInUids uids).filter {
|
||||
|
@ -128,6 +121,19 @@ private[lobby] final class Lobby(
|
|||
|
||||
case Resync =>
|
||||
socket ! HookIds(HookRepo.vector.map(_.id))
|
||||
|
||||
case msg@HookSub(member, true) =>
|
||||
socket ! AllHooksFor(
|
||||
member,
|
||||
HookRepo.vector.filter { hook =>
|
||||
hook.uid == member.uid || Biter.canJoin(hook, member.user)
|
||||
})
|
||||
|
||||
case lila.pool.HookThieve.GetCandidates(clock) =>
|
||||
sender ! lila.pool.HookThieve.PoolHooks(HookRepo poolCandidates clock)
|
||||
|
||||
case lila.pool.HookThieve.StolenHookIds(ids) =>
|
||||
HookRepo byIds ids.toSet foreach remove
|
||||
}
|
||||
|
||||
private def NoPlayban(user: Option[LobbyUser])(f: => Unit) {
|
||||
|
|
|
@ -12,23 +12,22 @@ import play.twirl.api.Html
|
|||
import actorApi._
|
||||
import lila.common.PimpedJson._
|
||||
import lila.game.actorApi._
|
||||
import lila.game.AnonCookie
|
||||
import lila.game.{ Game, AnonCookie }
|
||||
import lila.hub.actorApi.game.ChangeFeatured
|
||||
import lila.hub.actorApi.lobby._
|
||||
import lila.hub.actorApi.timeline._
|
||||
import lila.socket.actorApi.{ SocketLeave, Connected => _, _ }
|
||||
import lila.socket.{ SocketActor, History, Historical }
|
||||
import lila.socket.SocketActor
|
||||
import makeTimeout.short
|
||||
|
||||
private[lobby] final class Socket(
|
||||
val history: History[Messadata],
|
||||
uidTtl: FiniteDuration) extends SocketActor[Member](uidTtl) with Historical[Member, Messadata] {
|
||||
uidTtl: FiniteDuration) extends SocketActor[Member](uidTtl) {
|
||||
|
||||
override val startsOnApplicationBoot = true
|
||||
|
||||
override def preStart() {
|
||||
super.preStart()
|
||||
context.system.lilaBus.subscribe(self, 'changeFeaturedGame, 'streams, 'nbMembers, 'nbRounds, 'socketDoor)
|
||||
context.system.lilaBus.subscribe(self, 'changeFeaturedGame, 'streams, 'nbMembers, 'nbRounds, 'poolGame)
|
||||
}
|
||||
|
||||
override def postStop() {
|
||||
|
@ -41,19 +40,15 @@ private[lobby] final class Socket(
|
|||
|
||||
var idleUids = scala.collection.mutable.Set[String]()
|
||||
|
||||
var hookSubscriberUids = scala.collection.mutable.Set[String]()
|
||||
|
||||
def receiveSpecific = {
|
||||
|
||||
case PingVersion(uid, v) => Future {
|
||||
ping(uid)
|
||||
withActiveMember(uid) { m =>
|
||||
history.since(v).fold {
|
||||
lila.mon.lobby.socket.resync()
|
||||
resync(m)
|
||||
}(_ foreach sendMessage(m))
|
||||
}
|
||||
}
|
||||
|
||||
case GetUids => sender ! SocketUids(members.keySet.toSet)
|
||||
case GetUids =>
|
||||
sender ! SocketUids(members.keySet.toSet)
|
||||
lila.mon.lobby.socket.idle(idleUids.size)
|
||||
lila.mon.lobby.socket.hookSubscribers(hookSubscriberUids.size)
|
||||
lila.mon.lobby.socket.mobile(members.count(_._2.mobile))
|
||||
|
||||
case Join(uid, user, blocks, mobile) =>
|
||||
val (enumerator, channel) = Concurrent.broadcast[JsValue]
|
||||
|
@ -70,24 +65,61 @@ private[lobby] final class Socket(
|
|||
case ReloadTimeline(userId) =>
|
||||
membersByUserId(userId) foreach (_ push makeMessage("reload_timeline"))
|
||||
|
||||
case AddHook(hook) =>
|
||||
notifyVersion("had", hook.render, Messadata(hook = hook.some))
|
||||
case AddHook(hook) => Future {
|
||||
val msg = makeMessage("had", hook.render)
|
||||
hookSubscriberUids.foreach { uid =>
|
||||
withActiveMember(uid) { member =>
|
||||
if (hook.uid == member.uid || Biter.canJoin(hook, member.user)) member push msg
|
||||
}
|
||||
}
|
||||
if (hook.likePoolFiveO) withMember(hook.uid) { member =>
|
||||
lila.mon.lobby.hook.createdLikePoolFiveO(member.mobile)()
|
||||
}
|
||||
}
|
||||
|
||||
case AddSeek(_) => notifySeeks
|
||||
case AddSeek(_) => notifySeeks
|
||||
|
||||
case RemoveHook(hookId) => notifyVersion("hrm", hookId, Messadata())
|
||||
case RemoveHook(hookId) => Future {
|
||||
val msg = makeMessage("hrm", hookId)
|
||||
hookSubscriberUids.foreach { uid =>
|
||||
withActiveMember(uid)(_ push msg)
|
||||
}
|
||||
}
|
||||
|
||||
case RemoveSeek(_) => notifySeeks
|
||||
case RemoveSeek(_) => notifySeeks
|
||||
|
||||
case JoinHook(uid, hook, game, creatorColor) =>
|
||||
withMember(hook.uid)(notifyPlayerStart(game, creatorColor))
|
||||
withMember(uid)(notifyPlayerStart(game, !creatorColor))
|
||||
withMember(hook.uid) { member =>
|
||||
lila.mon.lobby.hook.joinMobile(member.mobile)()
|
||||
notifyPlayerStart(game, creatorColor)(member)
|
||||
}
|
||||
withMember(uid) { member =>
|
||||
lila.mon.lobby.hook.joinMobile(member.mobile)()
|
||||
if (hook.likePoolFiveO)
|
||||
lila.mon.lobby.hook.acceptedLikePoolFiveO(member.mobile)()
|
||||
notifyPlayerStart(game, !creatorColor)(member)
|
||||
}
|
||||
|
||||
case JoinSeek(userId, seek, game, creatorColor) =>
|
||||
membersByUserId(seek.user.id) foreach notifyPlayerStart(game, creatorColor)
|
||||
membersByUserId(userId) foreach notifyPlayerStart(game, !creatorColor)
|
||||
membersByUserId(seek.user.id) foreach { member =>
|
||||
lila.mon.lobby.seek.joinMobile(member.mobile)()
|
||||
notifyPlayerStart(game, creatorColor)(member)
|
||||
}
|
||||
membersByUserId(userId) foreach { member =>
|
||||
lila.mon.lobby.seek.joinMobile(member.mobile)()
|
||||
notifyPlayerStart(game, !creatorColor)(member)
|
||||
}
|
||||
|
||||
case HookIds(ids) => notifyVersion("hli", ids mkString ",", Messadata())
|
||||
case lila.pool.PoolApi.Pairing(game, whiteUid, blackUid) =>
|
||||
withMember(whiteUid.value)(notifyPlayerStart(game, chess.White))
|
||||
withMember(blackUid.value)(notifyPlayerStart(game, chess.Black))
|
||||
|
||||
case HookIds(ids) => Future {
|
||||
val msg = makeMessage("hli", ids mkString ",")
|
||||
hookSubscriberUids.foreach { uid =>
|
||||
withActiveMember(uid)(_ push msg)
|
||||
}
|
||||
}
|
||||
|
||||
case lila.hub.actorApi.StreamsOnAir(html) => notifyAllAsync(makeMessage("streams", html))
|
||||
|
||||
|
@ -99,17 +131,22 @@ private[lobby] final class Socket(
|
|||
|
||||
case SetIdle(uid, true) => idleUids += uid
|
||||
case SetIdle(uid, false) => idleUids -= uid
|
||||
case SocketLeave(uid, _) => idleUids -= uid
|
||||
|
||||
case HookSub(member, true) => hookSubscriberUids += member.uid
|
||||
case HookSub(member, false) => hookSubscriberUids -= member.uid
|
||||
case AllHooksFor(member, hooks) =>
|
||||
notifyMember("hooks", JsArray(hooks.map(_.render)))(member)
|
||||
hookSubscriberUids += member.uid
|
||||
}
|
||||
|
||||
private def notifyPlayerStart(game: lila.game.Game, color: chess.Color) =
|
||||
private def notifyPlayerStart(game: Game, color: chess.Color) =
|
||||
notifyMember("redirect", Json.obj(
|
||||
"id" -> (game fullIdOf color),
|
||||
"url" -> playerUrl(game fullIdOf color),
|
||||
"cookie" -> AnonCookie.json(game, color)
|
||||
).noNull) _
|
||||
|
||||
def notifyAllActiveAsync(msg: JsObject) = scala.concurrent.Future {
|
||||
def notifyAllActiveAsync(msg: JsObject) = Future {
|
||||
members.foreach {
|
||||
case (uid, member) => if (!idleUids(uid)) member push msg
|
||||
}
|
||||
|
@ -119,20 +156,14 @@ private[lobby] final class Socket(
|
|||
if (!idleUids(uid)) members get uid foreach f
|
||||
}
|
||||
|
||||
override def sendMessage(message: Message)(member: Member) =
|
||||
if (!idleUids(member.uid)) member push {
|
||||
if (shouldSkipMessageFor(message, member)) message.skipMsg
|
||||
else message.fullMsg
|
||||
}
|
||||
|
||||
protected def shouldSkipMessageFor(message: Message, member: Member) =
|
||||
message.metadata.hook ?? { hook =>
|
||||
hook.uid != member.uid && !Biter.canJoin(hook, member.user)
|
||||
}
|
||||
|
||||
private def playerUrl(fullId: String) = s"/$fullId"
|
||||
|
||||
private def notifySeeks() {
|
||||
notifyAll(makeMessage("reload_seeks"))
|
||||
override def quit(uid: String) {
|
||||
super.quit(uid)
|
||||
idleUids -= uid
|
||||
hookSubscriberUids -= uid
|
||||
}
|
||||
|
||||
def playerUrl(fullId: String) = s"/$fullId"
|
||||
|
||||
def notifySeeks =
|
||||
notifyAllActiveAsync(makeMessage("reload_seeks"))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import scala.concurrent.duration._
|
|||
import actorApi._
|
||||
import lila.common.PimpedJson._
|
||||
import lila.hub.actorApi.lobby._
|
||||
import lila.pool.{ PoolApi, PoolConfig }
|
||||
import lila.rating.RatingRange
|
||||
import lila.socket.actorApi.{ Connected => _, _ }
|
||||
import lila.socket.Handler
|
||||
import lila.user.User
|
||||
|
@ -18,19 +20,17 @@ private[lobby] final class SocketHandler(
|
|||
hub: lila.hub.Env,
|
||||
lobby: ActorRef,
|
||||
socket: ActorRef,
|
||||
poolApi: PoolApi,
|
||||
blocking: String => Fu[Set[String]]) {
|
||||
|
||||
private def controller(
|
||||
socket: ActorRef,
|
||||
uid: String,
|
||||
member: Member): Handler.Controller = {
|
||||
case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) }
|
||||
private def controller(socket: ActorRef, member: Member): Handler.Controller = {
|
||||
case ("join", o) =>
|
||||
o str "d" foreach { id =>
|
||||
lobby ! BiteHook(id, uid, member.user)
|
||||
lobby ! BiteHook(id, member.uid, member.user)
|
||||
}
|
||||
case ("cancel", o) =>
|
||||
lobby ! CancelHook(uid) case ("joinSeek", o) => for {
|
||||
case ("cancel", _) =>
|
||||
lobby ! CancelHook(member.uid)
|
||||
case ("joinSeek", o) => for {
|
||||
id <- o str "d"
|
||||
user <- member.user
|
||||
} lobby ! BiteSeek(id, user)
|
||||
|
@ -38,7 +38,34 @@ private[lobby] final class SocketHandler(
|
|||
id <- o str "d"
|
||||
user <- member.user
|
||||
} lobby ! CancelSeek(id, user)
|
||||
case ("idle", o) => socket ! SetIdle(uid, ~(o boolean "d"))
|
||||
case ("idle", o) => socket ! SetIdle(member.uid, ~(o boolean "d"))
|
||||
// entering a pool
|
||||
case ("poolIn", o) => for {
|
||||
user <- member.user
|
||||
d <- o obj "d"
|
||||
id <- d str "id"
|
||||
ratingRange = d str "range" flatMap RatingRange.apply
|
||||
} {
|
||||
lobby ! CancelHook(member.uid) // in case there's one...
|
||||
poolApi.join(
|
||||
PoolConfig.Id(id),
|
||||
PoolApi.Joiner(
|
||||
userId = user.id,
|
||||
socketId = lila.socket.Socket.Uid(member.uid),
|
||||
ratingMap = user.ratingMap,
|
||||
ratingRange = ratingRange,
|
||||
engine = user.engine,
|
||||
blocking = user.blocking))
|
||||
}
|
||||
// leaving a pool
|
||||
case ("poolOut", o) => for {
|
||||
id <- o str "d"
|
||||
user <- member.user
|
||||
} poolApi.leave(PoolConfig.Id(id), user.id)
|
||||
// entering the hooks view
|
||||
case ("hookIn", _) => lobby ! HookSub(member, true)
|
||||
// leaving the hooks view
|
||||
case ("hookOut", _) => socket ! HookSub(member, false)
|
||||
}
|
||||
|
||||
def apply(uid: String, user: Option[User], mobile: Boolean): Fu[JsSocketHandler] =
|
||||
|
@ -46,7 +73,7 @@ private[lobby] final class SocketHandler(
|
|||
val join = Join(uid = uid, user = user, blocking = blockedUserIds, mobile = mobile)
|
||||
Handler(hub, socket, uid, join) {
|
||||
case Connected(enum, member) =>
|
||||
(controller(socket, uid, member), enum, member)
|
||||
(controller(socket, member), enum, member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,18 @@ import lila.socket.SocketMember
|
|||
import lila.user.User
|
||||
|
||||
private[lobby] case class LobbyUser(
|
||||
id: String,
|
||||
username: String,
|
||||
troll: Boolean,
|
||||
engine: Boolean,
|
||||
booster: Boolean,
|
||||
ratingMap: Map[String, Int],
|
||||
blocking: Set[String]) {
|
||||
def lame = engine || booster
|
||||
}
|
||||
id: String,
|
||||
username: String,
|
||||
engine: Boolean,
|
||||
ratingMap: Map[String, Int],
|
||||
blocking: Set[String])
|
||||
|
||||
private[lobby] object LobbyUser {
|
||||
|
||||
def make(user: User, blocking: Set[String]) = LobbyUser(
|
||||
id = user.id,
|
||||
username = user.username,
|
||||
troll = user.troll,
|
||||
engine = user.engine,
|
||||
booster = user.booster,
|
||||
ratingMap = user.perfs.ratingMap,
|
||||
blocking = blocking)
|
||||
}
|
||||
|
@ -34,8 +28,8 @@ private[lobby] case class Member(
|
|||
uid: String,
|
||||
mobile: Boolean) extends SocketMember {
|
||||
|
||||
val userId = user map (_.id)
|
||||
val troll = user ?? (_.troll)
|
||||
val userId = user.map(_.id)
|
||||
val troll = false
|
||||
}
|
||||
|
||||
private[lobby] object Member {
|
||||
|
@ -70,9 +64,11 @@ private[lobby] case class HookIds(ids: Vector[String])
|
|||
|
||||
private[lobby] case class SetIdle(uid: String, value: Boolean)
|
||||
|
||||
private[lobby] case class HookSub(member: Member, value: Boolean)
|
||||
private[lobby] case class AllHooksFor(member: Member, hooks: Vector[Hook])
|
||||
|
||||
private[lobby] case object GetUids
|
||||
private[lobby] case class SocketUids(uids: Set[String])
|
||||
|
||||
case class AddHook(hook: Hook)
|
||||
case class AddSeek(seek: Seek)
|
||||
case class HooksFor(user: Option[User])
|
||||
|
|
|
@ -129,7 +129,7 @@ final class AssessApi(
|
|||
}
|
||||
}
|
||||
|
||||
private val assessableSources: Set[Source] = Set(Source.Lobby, Source.Tournament)
|
||||
private val assessableSources: Set[Source] = Set(Source.Lobby, Source.Pool, Source.Tournament)
|
||||
|
||||
def onGameReady(game: Game, white: User, black: User): Funit = {
|
||||
|
||||
|
|
|
@ -26,8 +26,11 @@ final class PlaybanApi(
|
|||
|
||||
private case class Blame(player: Player, outcome: Outcome)
|
||||
|
||||
private def blameableSource(source: Source) =
|
||||
source == Source.Lobby || source == Source.Pool
|
||||
|
||||
private def blameable(game: Game): Fu[Boolean] =
|
||||
(game.source.contains(Source.Lobby) && game.hasClock && !isRematch(game.id)) ?? {
|
||||
(game.source.exists(blameableSource) && game.hasClock && !isRematch(game.id)) ?? {
|
||||
if (game.rated) fuccess(true)
|
||||
else UserRepo.containsEngine(game.userIds) map (!_)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package lila.pool
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor._
|
||||
|
||||
final class Env(
|
||||
lobbyActor: ActorSelection,
|
||||
system: akka.actor.ActorSystem,
|
||||
onStart: String => Unit) {
|
||||
|
||||
private lazy val hookThieve = new HookThieve(lobbyActor)
|
||||
|
||||
lazy val api = new PoolApi(
|
||||
configs = PoolList.all,
|
||||
hookThieve = hookThieve,
|
||||
gameStarter = gameStarter,
|
||||
system = system)
|
||||
|
||||
private lazy val gameStarter = new GameStarter(
|
||||
bus = system.lilaBus,
|
||||
onStart = onStart,
|
||||
sequencer = system.actorOf(Props(
|
||||
classOf[lila.hub.Sequencer], none, 10.seconds.some, logger
|
||||
), name = "pool-sequencer")
|
||||
)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current: Env = "pool" boot new Env(
|
||||
lobbyActor = lila.hub.Env.current.actor.lobby,
|
||||
system = lila.common.PlayApp.system,
|
||||
onStart = lila.game.Env.current.onStart)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package lila.pool
|
||||
|
||||
import akka.actor._
|
||||
import scala.concurrent.Promise
|
||||
|
||||
import lila.game.{ Game, Player, GameRepo }
|
||||
import lila.hub.Sequencer
|
||||
import lila.rating.Perf
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private final class GameStarter(
|
||||
bus: lila.common.Bus,
|
||||
onStart: Game.ID => Unit,
|
||||
sequencer: ActorRef) {
|
||||
|
||||
def apply(pool: PoolConfig, couples: Vector[MatchMaking.Couple]): Funit = {
|
||||
val promise = Promise[Unit]()
|
||||
sequencer ! Sequencer.work(all(pool, couples), promise.some)
|
||||
promise.future
|
||||
}
|
||||
|
||||
private def all(pool: PoolConfig, couples: Vector[MatchMaking.Couple]): Funit =
|
||||
couples.nonEmpty ?? {
|
||||
val userIds = couples.flatMap(_.userIds)
|
||||
UserRepo.perfOf(userIds, pool.perfType) flatMap { perfs =>
|
||||
couples.map(one(pool, perfs)).sequenceFu.void
|
||||
}
|
||||
}
|
||||
|
||||
private def one(pool: PoolConfig, perfs: Map[User.ID, Perf])(couple: MatchMaking.Couple): Funit = {
|
||||
import couple._
|
||||
(perfs.get(p1.userId) |@| perfs.get(p2.userId)).tupled ?? {
|
||||
case (perf1, perf2) => for {
|
||||
p1White <- UserRepo.firstGetsWhite(p1.userId, p2.userId)
|
||||
(whitePerf, blackPerf) = p1White.fold(perf1 -> perf2, perf2 -> perf1)
|
||||
(whiteMember, blackMember) = p1White.fold(p1 -> p2, p2 -> p1)
|
||||
game = makeGame(
|
||||
pool,
|
||||
whiteMember.userId -> whitePerf,
|
||||
blackMember.userId -> blackPerf
|
||||
).start
|
||||
_ <- GameRepo insertDenormalized game
|
||||
} yield {
|
||||
|
||||
bus.publish(PoolApi.Pairing(
|
||||
game,
|
||||
whiteUid = whiteMember.socketId,
|
||||
blackUid = blackMember.socketId
|
||||
), 'poolGame)
|
||||
|
||||
onStart(game.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def makeGame(
|
||||
pool: PoolConfig,
|
||||
whiteUser: (User.ID, Perf),
|
||||
blackUser: (User.ID, Perf)) = Game.make(
|
||||
game = chess.Game(
|
||||
board = chess.Board init chess.variant.Standard,
|
||||
clock = pool.clock.toClock.some),
|
||||
whitePlayer = Player.white.withUser(whiteUser._1, whiteUser._2),
|
||||
blackPlayer = Player.black.withUser(blackUser._1, blackUser._2),
|
||||
mode = chess.Mode.Rated,
|
||||
variant = chess.variant.Standard,
|
||||
source = lila.game.Source.Pool,
|
||||
pgnImport = None)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package lila.pool
|
||||
|
||||
import akka.actor.ActorSelection
|
||||
import akka.pattern.ask
|
||||
|
||||
private final class HookThieve(lobby: ActorSelection) {
|
||||
|
||||
import HookThieve._
|
||||
|
||||
def candidates(clock: chess.Clock.Config, monId: String): Fu[PoolHooks] = {
|
||||
import makeTimeout.short
|
||||
lobby ? GetCandidates(clock) mapTo manifest[PoolHooks] addEffect { res =>
|
||||
lila.mon.lobby.pool.thieve.candidates(monId)(res.hooks.size)
|
||||
}
|
||||
} recover {
|
||||
case _ =>
|
||||
lila.mon.lobby.pool.thieve.timeout(monId)()
|
||||
PoolHooks(Vector.empty)
|
||||
}
|
||||
|
||||
def stolen(poolHooks: Vector[PoolHook], monId: String) = {
|
||||
lila.mon.lobby.pool.thieve.stolen(monId)(poolHooks.size)
|
||||
if (poolHooks.nonEmpty) lobby ! StolenHookIds(poolHooks.map(_.hookId))
|
||||
}
|
||||
}
|
||||
|
||||
object HookThieve {
|
||||
|
||||
case class GetCandidates(clock: chess.Clock.Config)
|
||||
case class StolenHookIds(ids: Vector[String])
|
||||
|
||||
case class PoolHook(hookId: String, member: PoolMember) {
|
||||
|
||||
def is(m: PoolMember) = member.userId == m.userId
|
||||
}
|
||||
|
||||
case class PoolHooks(hooks: Vector[PoolHook])
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package lila.pool
|
||||
|
||||
import lila.common.WMMatching
|
||||
|
||||
object MatchMaking {
|
||||
|
||||
case class Couple(p1: PoolMember, p2: PoolMember) {
|
||||
def members = Vector(p1, p2)
|
||||
def userIds = members.map(_.userId)
|
||||
def ratingDiff = p1 ratingDiff p2
|
||||
}
|
||||
|
||||
def apply(members: Vector[PoolMember]): Vector[Couple] = members.partition(_.engine) match {
|
||||
case (engines, fairs) => naive(engines) ++ (wmMatching(fairs) | naive(fairs))
|
||||
}
|
||||
|
||||
private def naive(members: Vector[PoolMember]): Vector[Couple] =
|
||||
members.sortBy(-_.rating) grouped 2 collect {
|
||||
case Vector(p1, p2) => Couple(p1, p2)
|
||||
} toVector
|
||||
|
||||
private object wmMatching {
|
||||
|
||||
// above that, no pairing is allowed
|
||||
private val MaxScore = 300
|
||||
|
||||
// quality of a potential pairing. Lower is better.
|
||||
private def pairScore(a: PoolMember, b: PoolMember) =
|
||||
a.ratingDiff(b) - {
|
||||
missBonus(a) + missBonus(b)
|
||||
} + {
|
||||
rangeMalus(a, b) + rangeMalus(b, a)
|
||||
} + {
|
||||
blockMalus(a, b) + blockMalus(b, a)
|
||||
}
|
||||
|
||||
// score bonus based on how many waves the member missed
|
||||
private def missBonus(p: PoolMember) = (p.misses * 30) atMost 1000
|
||||
|
||||
// big malus if players have conflicting rating ranges
|
||||
private def rangeMalus(a: PoolMember, b: PoolMember) =
|
||||
if (a.ratingRange.exists(!_.contains(b.rating))) 2000 else 0
|
||||
|
||||
// huge malus if players block each other
|
||||
private def blockMalus(a: PoolMember, b: PoolMember) =
|
||||
if (a.blocking.ids contains b.userId) 5000 else 0
|
||||
|
||||
def apply(members: Vector[PoolMember]): Option[Vector[Couple]] = {
|
||||
WMMatching(members.toArray, pairScore).fold(
|
||||
err => {
|
||||
logger.error("WMMatching", err)
|
||||
none
|
||||
},
|
||||
_.collect {
|
||||
case (a, b) if pairScore(a, b) < MaxScore => Couple(a, b)
|
||||
}.toVector.some
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package lila.pool
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
import akka.actor._
|
||||
import akka.pattern.pipe
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.user.User
|
||||
|
||||
private final class PoolActor(
|
||||
config: PoolConfig,
|
||||
hookThieve: HookThieve,
|
||||
gameStarter: GameStarter) extends Actor {
|
||||
|
||||
import PoolActor._
|
||||
|
||||
var members = Vector[PoolMember]()
|
||||
|
||||
var nextWave: Cancellable = _
|
||||
|
||||
def scheduleWave =
|
||||
nextWave = context.system.scheduler.scheduleOnce(
|
||||
config.wave.every + Random.nextInt(1000).millis,
|
||||
self, ScheduledWave)
|
||||
|
||||
scheduleWave
|
||||
|
||||
def receive = {
|
||||
|
||||
case Join(joiner) =>
|
||||
members.find(joiner.is) match {
|
||||
case None =>
|
||||
members = members :+ PoolMember(joiner, config)
|
||||
if (members.size >= config.wave.players.value) self ! FullWave
|
||||
monitor.join.count(monId)()
|
||||
case Some(member) if member.ratingRange != joiner.ratingRange =>
|
||||
members = members.map {
|
||||
case m if m == member => m withRange joiner.ratingRange
|
||||
case m => m
|
||||
}
|
||||
case _ => // no change
|
||||
}
|
||||
|
||||
case Leave(userId) => members.find(_.userId == userId) foreach { member =>
|
||||
members = members.filter(member !=)
|
||||
monitor.leave.count(monId)()
|
||||
monitor.leave.wait(monId)(member.waitMillis)
|
||||
}
|
||||
|
||||
case ScheduledWave =>
|
||||
monitor.wave.scheduled(monId)()
|
||||
self ! RunWave
|
||||
|
||||
case FullWave =>
|
||||
monitor.wave.full(monId)()
|
||||
self ! RunWave
|
||||
|
||||
case RunWave =>
|
||||
nextWave.cancel()
|
||||
hookThieve.candidates(config.clock, monId) pipeTo self
|
||||
|
||||
case HookThieve.PoolHooks(hooks) => {
|
||||
|
||||
monitor.wave.withRange(monId)(members.count(_.hasRange))
|
||||
|
||||
val pairings = lila.mon.measure(_.lobby.pool.matchMaking.duration(monId)) {
|
||||
MatchMaking(members ++ hooks.map(_.member))
|
||||
}
|
||||
|
||||
val pairedMembers = pairings.flatMap(_.members)
|
||||
|
||||
hookThieve.stolen(hooks.filter { h =>
|
||||
pairedMembers.exists(h.is)
|
||||
}, monId)
|
||||
|
||||
members = members.diff(pairedMembers).map(_.incMisses)
|
||||
|
||||
if (pairings.nonEmpty)
|
||||
gameStarter(config, pairings).mon(_.lobby.pool.gameStart.duration(monId))
|
||||
|
||||
logger.debug(s"${config.id.value} wave: ${pairings.size} pairings, ${members.size} missed")
|
||||
|
||||
monitor.wave.paired(monId)(pairedMembers.size)
|
||||
monitor.wave.missed(monId)(members.size)
|
||||
pairedMembers.foreach { m =>
|
||||
monitor.wave.wait(monId)(m.waitMillis)
|
||||
}
|
||||
pairings.foreach { p =>
|
||||
monitor.wave.ratingDiff(monId)(p.ratingDiff)
|
||||
}
|
||||
|
||||
scheduleWave
|
||||
}
|
||||
|
||||
case SocketIds(ids) =>
|
||||
members = members.filter { m =>
|
||||
ids contains m.socketId.value
|
||||
}
|
||||
}
|
||||
|
||||
val monitor = lila.mon.lobby.pool
|
||||
val monId = config.id.value.replace("+", "_")
|
||||
}
|
||||
|
||||
private object PoolActor {
|
||||
|
||||
case class Join(joiner: PoolApi.Joiner) extends AnyVal
|
||||
case class Leave(userId: User.ID) extends AnyVal
|
||||
case class SocketIds(ids: Set[String])
|
||||
|
||||
case object ScheduledWave
|
||||
case object FullWave
|
||||
case object RunWave
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package lila.pool
|
||||
|
||||
import akka.actor._
|
||||
|
||||
import lila.game.Game
|
||||
import lila.rating.RatingRange
|
||||
import lila.socket.Socket.{ Uid => SocketId }
|
||||
import lila.user.User
|
||||
|
||||
final class PoolApi(
|
||||
val configs: List[PoolConfig],
|
||||
hookThieve: HookThieve,
|
||||
gameStarter: GameStarter,
|
||||
system: ActorSystem) {
|
||||
|
||||
import PoolApi._
|
||||
import PoolActor._
|
||||
|
||||
private val actors: Map[PoolConfig.Id, ActorRef] = configs.map { config =>
|
||||
config.id -> system.actorOf(
|
||||
Props(new PoolActor(config, hookThieve, gameStarter)),
|
||||
name = s"pool-${config.id.value}")
|
||||
}.toMap
|
||||
|
||||
def join(poolId: PoolConfig.Id, joiner: Joiner) = actors foreach {
|
||||
case (id, actor) if id == poolId => actor ! Join(joiner)
|
||||
case (_, actor) => actor ! Leave(joiner.userId)
|
||||
}
|
||||
|
||||
def leave(poolId: PoolConfig.Id, userId: User.ID) = sendTo(poolId, Leave(userId))
|
||||
|
||||
def socketIds(ids: Set[String]) = {
|
||||
val msg = SocketIds(ids)
|
||||
actors.values.foreach(_ ! msg)
|
||||
}
|
||||
|
||||
private def sendTo(poolId: PoolConfig.Id, msg: Any) =
|
||||
actors get poolId foreach { _ ! msg }
|
||||
}
|
||||
|
||||
object PoolApi {
|
||||
|
||||
case class Joiner(
|
||||
userId: User.ID,
|
||||
socketId: SocketId,
|
||||
ratingMap: Map[String, Int],
|
||||
ratingRange: Option[RatingRange],
|
||||
engine: Boolean,
|
||||
blocking: Set[String]) {
|
||||
|
||||
def is(member: PoolMember) = userId == member.userId
|
||||
}
|
||||
|
||||
case class Pairing(game: Game, whiteUid: SocketId, blackUid: SocketId)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package lila.pool
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import chess.Clock
|
||||
import lila.rating.PerfType
|
||||
|
||||
case class PoolConfig(
|
||||
clock: chess.Clock.Config,
|
||||
wave: PoolConfig.Wave) {
|
||||
|
||||
val perfType = PerfType(chess.Speed(clock).key) | PerfType.Classical
|
||||
|
||||
val id = PoolConfig clockToId clock
|
||||
}
|
||||
|
||||
object PoolConfig {
|
||||
|
||||
case class Id(value: String) extends AnyVal
|
||||
case class NbPlayers(value: Int) extends AnyVal
|
||||
|
||||
case class Wave(every: FiniteDuration, players: NbPlayers)
|
||||
|
||||
def clockToId(clock: chess.Clock.Config) = Id(clock.show)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package lila.pool
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object PoolList {
|
||||
|
||||
import PoolConfig._
|
||||
|
||||
val all: List[PoolConfig] = List(
|
||||
PoolConfig(1 ++ 0, Wave(13 seconds, 20 players)),
|
||||
PoolConfig(2 ++ 1, Wave(18 seconds, 20 players)),
|
||||
PoolConfig(3 ++ 0, Wave(15 seconds, 30 players)),
|
||||
PoolConfig(3 ++ 2, Wave(22 seconds, 20 players)),
|
||||
PoolConfig(5 ++ 0, Wave(15 seconds, 30 players)),
|
||||
PoolConfig(5 ++ 3, Wave(25 seconds, 20 players)),
|
||||
PoolConfig(10 ++ 0, Wave(20 seconds, 20 players)),
|
||||
PoolConfig(15 ++ 15, Wave(60 seconds, 16 players))
|
||||
)
|
||||
|
||||
val clockStringSet: Set[String] = all.map(_.clock.show).toSet
|
||||
|
||||
private implicit class PimpedInt(self: Int) {
|
||||
def ++(increment: Int) = chess.Clock.Config(self * 60, increment)
|
||||
def players = NbPlayers(self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package lila.pool
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.rating.RatingRange
|
||||
import lila.user.User
|
||||
|
||||
case class PoolMember(
|
||||
userId: User.ID,
|
||||
socketId: lila.socket.Socket.Uid,
|
||||
rating: Int,
|
||||
ratingRange: Option[RatingRange],
|
||||
engine: Boolean,
|
||||
blocking: PoolMember.BlockedUsers,
|
||||
since: DateTime,
|
||||
misses: Int = 0 // how many waves they missed
|
||||
) {
|
||||
|
||||
def incMisses = copy(misses = misses + 1)
|
||||
|
||||
def waitMillis: Int = (DateTime.now.getMillis - since.getMillis).toInt
|
||||
|
||||
def ratingDiff(other: PoolMember) = Math.abs(rating - other.rating)
|
||||
|
||||
def withRange(r: Option[RatingRange]) =
|
||||
if (r == ratingRange) this
|
||||
else copy(ratingRange = r, misses = 0)
|
||||
|
||||
def hasRange = ratingRange.isDefined
|
||||
}
|
||||
|
||||
object PoolMember {
|
||||
|
||||
case class BlockedUsers(ids: Set[User.ID]) extends AnyVal
|
||||
|
||||
def apply(joiner: PoolApi.Joiner, config: PoolConfig): PoolMember =
|
||||
PoolMember(
|
||||
userId = joiner.userId,
|
||||
socketId = joiner.socketId,
|
||||
engine = joiner.engine,
|
||||
rating = joiner.ratingMap.getOrElse(config.perfType.key, 1500),
|
||||
ratingRange = joiner.ratingRange,
|
||||
blocking = BlockedUsers(joiner.blocking),
|
||||
since = DateTime.now)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package lila
|
||||
|
||||
package object pool extends PackageObject with WithPlay {
|
||||
|
||||
private[pool] val logger = lila.log("pool")
|
||||
}
|
|
@ -24,7 +24,10 @@ private final class OneSignalPush(
|
|||
"include_player_ids" -> devices.map(_.deviceId),
|
||||
"headings" -> Map("en" -> data.title),
|
||||
"contents" -> Map("en" -> data.body),
|
||||
"data" -> data.payload
|
||||
"data" -> data.payload,
|
||||
"android_group" -> data.stacking.key,
|
||||
"android_group_message" -> Map("en" -> data.stacking.message),
|
||||
"collapse_id" -> data.stacking.key
|
||||
)).flatMap {
|
||||
case res if res.status == 200 =>
|
||||
(res.json \ "errors").asOpt[List[String]] match {
|
||||
|
|
|
@ -31,6 +31,7 @@ private final class PushApi(
|
|||
case _ => "It's a draw."
|
||||
},
|
||||
body = s"Your game with ${opponentName(pov)} is over.",
|
||||
stacking = Stacking.GameFinish,
|
||||
payload = Json.obj(
|
||||
"userId" -> userId,
|
||||
"userData" -> Json.obj(
|
||||
|
@ -55,6 +56,7 @@ private final class PushApi(
|
|||
pushToAll(userId, _.move, PushApi.Data(
|
||||
title = "It's your turn!",
|
||||
body = s"${opponentName(pov)} played $sanMove",
|
||||
stacking = Stacking.GameMove,
|
||||
payload = Json.obj(
|
||||
"userId" -> userId,
|
||||
"userData" -> Json.obj(
|
||||
|
@ -80,6 +82,7 @@ private final class PushApi(
|
|||
pushToAll(userId, _.corresAlarm, PushApi.Data(
|
||||
title = "Time is almost up!",
|
||||
body = s"You are about to lose on time against ${opponentName(pov)}",
|
||||
stacking = Stacking.GameMove,
|
||||
payload = Json.obj(
|
||||
"userId" -> userId,
|
||||
"userData" -> Json.obj(
|
||||
|
@ -100,6 +103,7 @@ private final class PushApi(
|
|||
pushToAll(t.receiverOf(p), _.message, PushApi.Data(
|
||||
title = s"${sender.titleName}: ${t.name}",
|
||||
body = p.text take 140,
|
||||
stacking = Stacking.NewMessage,
|
||||
payload = Json.obj(
|
||||
"userId" -> t.receiverOf(p),
|
||||
"userData" -> Json.obj(
|
||||
|
@ -114,6 +118,7 @@ private final class PushApi(
|
|||
pushToAll(dest.id, _.challenge.create, PushApi.Data(
|
||||
title = s"${lightChallenger.titleName} (${challenger.rating.show}) challenges you!",
|
||||
body = describeChallenge(c),
|
||||
stacking = Stacking.ChallengeCreate,
|
||||
payload = Json.obj(
|
||||
"userId" -> dest.id,
|
||||
"userData" -> Json.obj(
|
||||
|
@ -129,6 +134,7 @@ private final class PushApi(
|
|||
pushToAll(challenger.id, _.challenge.accept, PushApi.Data(
|
||||
title = s"${lightJoiner.fold("Anonymous")(_.titleName)} accepts your challenge!",
|
||||
body = describeChallenge(c),
|
||||
stacking = Stacking.ChallengeAccept,
|
||||
payload = Json.obj(
|
||||
"userId" -> challenger.id,
|
||||
"userData" -> Json.obj(
|
||||
|
@ -186,5 +192,6 @@ private object PushApi {
|
|||
case class Data(
|
||||
title: String,
|
||||
body: String,
|
||||
stacking: Stacking,
|
||||
payload: JsObject)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package lila.push
|
||||
|
||||
private sealed abstract class Stacking(val key: String, val message: String)
|
||||
|
||||
private object Stacking {
|
||||
|
||||
case object GameFinish extends Stacking("gameFinish", "$[notif_count] games are over")
|
||||
case object GameMove extends Stacking("gameMove", "It's your turn in $[notif_count] games")
|
||||
case object NewMessage extends Stacking("newMessage", "You have $[notif_count] new messages")
|
||||
case object ChallengeCreate extends Stacking("challengeCreate", "You have $[notif_count] new challenges")
|
||||
case object ChallengeAccept extends Stacking("challengeAccept", "$[notif_count] players accepted your challenges")
|
||||
}
|
|
@ -42,6 +42,7 @@ private[puzzle] final class Finisher(
|
|||
} inject (a -> none)
|
||||
}
|
||||
case _ =>
|
||||
incPuzzleAttempts(puzzle)
|
||||
val a = new Round(
|
||||
puzzleId = puzzle.id,
|
||||
userId = user.id,
|
||||
|
@ -56,6 +57,9 @@ private[puzzle] final class Finisher(
|
|||
private val TAU = 0.75d
|
||||
private val system = new RatingCalculator(VOLATILITY, TAU)
|
||||
|
||||
def incPuzzleAttempts(puzzle: Puzzle) =
|
||||
puzzleColl.incFieldUnchecked($id(puzzle.id), Puzzle.BSONFields.attempts)
|
||||
|
||||
private def mkRating(perf: Perf) = new Rating(
|
||||
math.max(1000, perf.glicko.rating),
|
||||
perf.glicko.deviation,
|
||||
|
|
|
@ -80,7 +80,7 @@ private[puzzle] final class Selector(
|
|||
Puzzle.BSONFields.rating $gt
|
||||
(rating - tolerance) $lt
|
||||
(rating + tolerance),
|
||||
Puzzle.BSONFields.voteSum $gt -10
|
||||
Puzzle.BSONFields.voteSum $gt -100
|
||||
)).uno[Puzzle] flatMap {
|
||||
case None if (tolerance + step) <= toleranceMax =>
|
||||
tryRange(rating, tolerance + step, step,
|
||||
|
|
|
@ -41,6 +41,8 @@ case object Glicko {
|
|||
|
||||
val default = Glicko(1500d, 350d, 0.06d)
|
||||
|
||||
val defaultIntRating = default.rating.toInt
|
||||
|
||||
val provisionalDeviation = 110
|
||||
|
||||
def range(rating: Double, deviation: Double) = (
|
||||
|
|
|
@ -196,9 +196,9 @@ final class Env(
|
|||
Props(classOf[Titivate], roundMap, hub.actor.bookmark),
|
||||
name = "titivate")
|
||||
|
||||
// system.lilaBus.subscribe(system.actorOf(
|
||||
// Props(classOf[CorresAlarm], db(CollectionAlarm)),
|
||||
// name = "corres-alarm"), 'moveEvent, 'finishGame)
|
||||
system.lilaBus.subscribe(system.actorOf(
|
||||
Props(classOf[CorresAlarm], db(CollectionAlarm)),
|
||||
name = "corres-alarm"), 'moveEvent, 'finishGame)
|
||||
|
||||
lazy val takebacker = new Takebacker(
|
||||
messenger = messenger,
|
||||
|
|
|
@ -79,7 +79,6 @@ private[round] final class Socket(
|
|||
override def postStop() {
|
||||
super.postStop()
|
||||
lilaBus.unsubscribe(self)
|
||||
lilaBus.publish(lila.hub.actorApi.round.SocketEvent.Stop(gameId), 'roundDoor)
|
||||
}
|
||||
|
||||
private def refreshSubscriptions {
|
||||
|
@ -149,9 +148,6 @@ private[round] final class Socket(
|
|||
playerDo(color, _.ping)
|
||||
sender ! Connected(enumerator, member)
|
||||
if (member.userTv.isDefined) refreshSubscriptions
|
||||
if (member.owner) lilaBus.publish(
|
||||
lila.hub.actorApi.round.SocketEvent.OwnerJoin(gameId, color, ip),
|
||||
'roundDoor)
|
||||
|
||||
case Nil =>
|
||||
case eventList: EventList => notify(eventList.events)
|
||||
|
|
|
@ -40,7 +40,7 @@ final class DataForm(
|
|||
Constraints.pattern(
|
||||
regex = """^[^\d].+$""".r,
|
||||
error = "The username must not start with a number")
|
||||
).verifying("This user already exists", u => !UserRepo.nameExists(u).awaitSeconds(2))
|
||||
).verifying("This user already exists", u => !UserRepo.nameExists(u).awaitSeconds(4))
|
||||
.verifying("This username is not acceptable", u => !LameName(u))
|
||||
|
||||
val website = Form(mapping(
|
||||
|
|
|
@ -23,7 +23,8 @@ final class DisposableEmailDomain(
|
|||
|
||||
private[security] def setDomains(domains: List[String]): Unit = try {
|
||||
matchers = ("lichess.org" :: domains).map { d =>
|
||||
val regex = s"""(.+\\.|)${d.replace(".", "\\.")}"""
|
||||
val r = d.replace("\\w", "[\\w-]").replace(".", "\\.")
|
||||
val regex = s"""(.+\\.|)$r"""
|
||||
makeMatcher(regex)
|
||||
}
|
||||
failed = false
|
||||
|
|
|
@ -41,6 +41,9 @@ class DisposableEmailDomainTest extends Specification {
|
|||
d("guerrillamail.com") must beTrue
|
||||
d("jetable.fr.nf") must beTrue
|
||||
d("notjetable.fr") must beFalse
|
||||
d("disposable-email.ml") must beTrue
|
||||
d("disposableemailaddresses.emailmiser.com") must beTrue
|
||||
d("dispose.it") must beTrue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,16 +244,7 @@ digitalsanctuary.com
|
|||
dingbone.com
|
||||
directbox.com
|
||||
discar(d|t)(mail)?.\w+
|
||||
disposable-email.ml
|
||||
disposable.\+
|
||||
disposableaddress.com
|
||||
disposableemailaddresses.com
|
||||
disposableemailaddresses.emailmiser.com
|
||||
disposableinbox.com
|
||||
dispose.it
|
||||
disposeamail.com
|
||||
disposemail.com
|
||||
dispostable.com
|
||||
dispos\w+.\w+
|
||||
divermail.com
|
||||
diwaq.com
|
||||
dlemail.ru
|
||||
|
|
|
@ -31,7 +31,7 @@ private[setup] trait Config {
|
|||
lazy val creatorColor = color.resolve
|
||||
|
||||
def makeGame(v: chess.variant.Variant): ChessGame =
|
||||
ChessGame(board = Board init v, clock = makeClock)
|
||||
ChessGame(board = Board init v, clock = makeClock.map(_.toClock))
|
||||
|
||||
def makeGame: ChessGame = makeGame(variant)
|
||||
|
||||
|
@ -42,7 +42,7 @@ private[setup] trait Config {
|
|||
def makeClock = hasClock option justMakeClock
|
||||
|
||||
protected def justMakeClock =
|
||||
Clock((time * 60).toInt, clockHasTime.fold(increment, 1))
|
||||
Clock.Config((time * 60).toInt, clockHasTime.fold(increment, 1))
|
||||
|
||||
def makeDaysPerTurn: Option[Int] = (timeMode == TimeMode.Correspondence) option days
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ trait Positional { self: Config =>
|
|||
player = color,
|
||||
turns = sit.turns,
|
||||
startedAtTurn = sit.turns,
|
||||
clock = makeClock)
|
||||
clock = makeClock.map(_.toClock))
|
||||
if (Forsyth.>>(game) == Forsyth.initial) makeGame(chess.variant.Standard) -> none
|
||||
else game -> baseState
|
||||
}
|
||||
|
|
|
@ -67,6 +67,9 @@ private[setup] final class Processor(
|
|||
def saveFriendConfig(config: FriendConfig)(implicit ctx: UserContext) =
|
||||
saveConfig(_ withFriend config)
|
||||
|
||||
def saveHookConfig(config: HookConfig)(implicit ctx: UserContext) =
|
||||
saveConfig(_ withHook config)
|
||||
|
||||
private def saveConfig(map: UserConfig => UserConfig)(implicit ctx: UserContext): Funit =
|
||||
ctx.me.fold(AnonConfigRepo.update(ctx.req) _)(user => UserConfigRepo.update(user) _)(map)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ final class JsonView(
|
|||
},
|
||||
"name" -> simul.name,
|
||||
"fullName" -> simul.fullName,
|
||||
"variants" -> simul.variants.map(variantJson(chess.Speed(simul.clock.chessClock.some))),
|
||||
"variants" -> simul.variants.map(variantJson(chess.Speed(simul.clock.config.some))),
|
||||
"applicants" -> simul.applicants.sortBy(-_.player.rating).map(applicantJson),
|
||||
"pairings" -> simul.pairings.sortBy(-_.player.rating).map(pairingJson(games, simul.hostId)),
|
||||
"isCreated" -> simul.isCreated,
|
||||
|
|
|
@ -95,7 +95,7 @@ case class Simul(
|
|||
|
||||
def perfTypes: List[lila.rating.PerfType] = variants.flatMap { variant =>
|
||||
lila.game.PerfPicker.perfType(
|
||||
speed = chess.Speed(clock.chessClock.some),
|
||||
speed = chess.Speed(clock.config.some),
|
||||
variant = variant,
|
||||
daysPerTurn = none)
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ object Simul {
|
|||
hostRating = host.perfs.bestRatingIn {
|
||||
variants flatMap { variant =>
|
||||
lila.game.PerfPicker.perfType(
|
||||
speed = chess.Speed(clock.chessClock.some),
|
||||
speed = chess.Speed(clock.config.some),
|
||||
variant = variant,
|
||||
daysPerTurn = none)
|
||||
}
|
||||
|
|
|
@ -40,8 +40,7 @@ private[simul] final class SimulApi(
|
|||
def create(setup: SimulSetup, me: User): Fu[Simul] = {
|
||||
val simul = Simul.make(
|
||||
clock = SimulClock(
|
||||
limit = setup.clockTime * 60,
|
||||
increment = setup.clockIncrement,
|
||||
config = chess.Clock.Config(setup.clockTime * 60, setup.clockIncrement),
|
||||
hostExtraTime = setup.clockExtra * 60),
|
||||
variants = setup.variants.flatMap { chess.variant.Variant(_) },
|
||||
host = me,
|
||||
|
@ -199,7 +198,7 @@ private[simul] final class SimulApi(
|
|||
|
||||
private object publish {
|
||||
private val siteMessage = SendToFlag("simul", Json.obj("t" -> "reload"))
|
||||
private val debouncer = system.actorOf(Props(new Debouncer(2 seconds, {
|
||||
private val debouncer = system.actorOf(Props(new Debouncer(5 seconds, {
|
||||
(_: Debouncer.Nothing) =>
|
||||
site ! siteMessage
|
||||
repo.allCreated foreach { simuls =>
|
||||
|
|
|
@ -2,18 +2,11 @@ package lila.simul
|
|||
|
||||
// All durations are expressed in seconds
|
||||
case class SimulClock(
|
||||
limit: Int,
|
||||
increment: Int,
|
||||
config: chess.Clock.Config,
|
||||
hostExtraTime: Int) {
|
||||
|
||||
def limitInMinutes = limit / 60
|
||||
|
||||
def show = s"${limitInMinutes}+${increment}"
|
||||
|
||||
def chessClock = chess.Clock(limit, increment)
|
||||
|
||||
def chessClockOf(hostColor: chess.Color) =
|
||||
chessClock.giveTime(hostColor, hostExtraTime)
|
||||
config.toClock.giveTime(hostColor, hostExtraTime)
|
||||
|
||||
def hostExtraMinutes = hostExtraTime / 60
|
||||
}
|
||||
|
|
|
@ -24,7 +24,11 @@ private[simul] final class SimulRepo(simulColl: Coll) {
|
|||
def read(bsonInt: BSONInteger): Variant = Variant(bsonInt.value) err s"No such variant: ${bsonInt.value}"
|
||||
def write(x: Variant) = BSONInteger(x.id)
|
||||
}
|
||||
private implicit val ClockBSONHandler = Macros.handler[SimulClock]
|
||||
private implicit val ClockBSONHandler = {
|
||||
import chess.Clock.Config
|
||||
implicit val clockHandler = Macros.handler[Config]
|
||||
Macros.handler[SimulClock]
|
||||
}
|
||||
private implicit val PlayerBSONHandler = Macros.handler[SimulPlayer]
|
||||
private implicit val ApplicantBSONHandler = Macros.handler[SimulApplicant]
|
||||
private implicit val SimulPairingBSONHandler = new BSON[SimulPairing] {
|
||||
|
|
|
@ -6,7 +6,7 @@ package object simul extends PackageObject with WithPlay with WithSocket {
|
|||
|
||||
private[simul] object RandomName {
|
||||
|
||||
private val names = IndexedSeq("Actinium", "Aluminium", "Americium", "Antimony", "Argon", "Arsenic", "Astatine", "Barium", "Berkelium", "Beryllium", "Bismuth", "Bohrium", "Boron", "Bromine", "Cadmium", "Caesium", "Calcium", "Californium", "Carbon", "Cerium", "Chlorine", "Chromium", "Cobalt", "Copernicium", "Copper", "Curium", "Darmstadtium", "Dubnium", "Dysprosium", "Einsteinium", "Erbium", "Europium", "Fermium", "Flerovium", "Fluorine", "Francium", "Gadolinium", "Gallium", "Germanium", "Gold", "Hafnium", "Hassium", "Helium", "Holmium", "Hydrogen", "Indium", "Iodine", "Iridium", "Iron", "Krypton", "Lanthanum", "Lawrencium", "Lead", "Lithium", "Livermorium", "Lutetium", "Magnesium", "Manganese", "Meitnerium", "Mendelevium", "Mercury", "Molybdenum", "Neodymium", "Neon", "Neptunium", "Nickel", "Niobium", "Nitrogen", "Nobelium", "Osmium", "Oxygen", "Palladium", "Phosphorus", "Platinum", "Plutonium", "Polonium", "Potassium", "Praseodymium", "Promethium", "Protactinium", "Radium", "Radon", "Rhenium", "Rhodium", "Roentgenium", "Rubidium", "Ruthenium", "Rutherfordium", "Samarium", "Scandium", "Seaborgium", "Selenium", "Silicon", "Silver", "Sodium", "Strontium", "Sulfur", "Tantalum", "Technetium", "Tellurium", "Terbium", "Thallium", "Thorium", "Thulium", "Tin", "Titanium", "Tungsten", "Ununoctium", "Ununpentium", "Ununseptium", "Ununtrium", "Uranium", "Vanadium", "Xenon", "Ytterbium", "Yttrium", "Zinc", "Zirconium")
|
||||
private val names = IndexedSeq("Actinium", "Aluminium", "Americium", "Antimony", "Argon", "Arsenic", "Astatine", "Barium", "Berkelium", "Beryllium", "Bismuth", "Bohrium", "Boron", "Bromine", "Cadmium", "Caesium", "Calcium", "Californium", "Carbon", "Cerium", "Chlorine", "Chromium", "Cobalt", "Copernicium", "Copper", "Curium", "Darmstadtium", "Dubnium", "Dysprosium", "Einsteinium", "Erbium", "Europium", "Fermium", "Flerovium", "Fluorine", "Francium", "Gadolinium", "Gallium", "Germanium", "Gold", "Hafnium", "Hassium", "Helium", "Holmium", "Hydrogen", "Indium", "Iodine", "Iridium", "Iron", "Krypton", "Lanthanum", "Lawrencium", "Lead", "Lithium", "Livermorium", "Lutetium", "Magnesium", "Manganese", "Meitnerium", "Mendelevium", "Mercury", "Molybdenum", "Moscovium", "Neodymium", "Neon", "Neptunium", "Nickel", "Nihonium", "Niobium", "Nitrogen", "Nobelium", "Oganesson", "Osmium", "Oxygen", "Palladium", "Phosphorus", "Platinum", "Plutonium", "Polonium", "Potassium", "Praseodymium", "Promethium", "Protactinium", "Radium", "Radon", "Rhenium", "Rhodium", "Roentgenium", "Rubidium", "Ruthenium", "Rutherfordium", "Samarium", "Scandium", "Seaborgium", "Selenium", "Silicon", "Silver", "Sodium", "Strontium", "Sulfur", "Tantalum", "Technetium", "Tellurium", "Tennessine", "Terbium", "Thallium", "Thorium", "Thulium", "Tin", "Titanium", "Tungsten", "Uranium", "Vanadium", "Xenon", "Ytterbium", "Yttrium", "Zinc", "Zirconium")
|
||||
private val size = names.size
|
||||
|
||||
def apply(): String = names(scala.util.Random nextInt size)
|
||||
|
|
|
@ -2,7 +2,7 @@ package lila.socket
|
|||
|
||||
import play.api.libs.json.JsValue
|
||||
|
||||
trait SocketMember extends Ordered[SocketMember] {
|
||||
trait SocketMember {
|
||||
|
||||
protected val channel: JsChannel
|
||||
val userId: Option[String]
|
||||
|
@ -10,24 +10,7 @@ trait SocketMember extends Ordered[SocketMember] {
|
|||
|
||||
def isAuth = userId.isDefined
|
||||
|
||||
def compare(other: SocketMember) = ~userId compare ~other.userId
|
||||
def push(msg: JsValue) = channel push msg
|
||||
|
||||
def push(msg: JsValue) {
|
||||
channel push msg
|
||||
}
|
||||
|
||||
def end {
|
||||
channel.end
|
||||
}
|
||||
}
|
||||
|
||||
object SocketMember {
|
||||
|
||||
def apply(c: JsChannel): SocketMember = apply(c, none, false)
|
||||
|
||||
def apply(c: JsChannel, uid: Option[String], tr: Boolean): SocketMember = new SocketMember {
|
||||
val channel = c
|
||||
val userId = uid
|
||||
val troll = tr
|
||||
}
|
||||
def end = channel.end
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package lila.socket
|
||||
|
||||
import ornicar.scalalib.Zero
|
||||
import play.api.libs.iteratee.{ Iteratee, Enumerator }
|
||||
import play.api.libs.json._
|
||||
|
||||
import play.api.libs.iteratee.{ Iteratee, Enumerator, Concurrent }
|
||||
import play.api.libs.json.JsValue
|
||||
|
||||
trait WithSocket {
|
||||
|
||||
type JsChannel = play.api.libs.iteratee.Concurrent.Channel[JsValue]
|
||||
type JsChannel = Concurrent.Channel[JsValue]
|
||||
type JsEnumerator = Enumerator[JsValue]
|
||||
type JsIteratee = Iteratee[JsValue, _]
|
||||
type JsSocketHandler = (JsIteratee, JsEnumerator)
|
||||
|
|
|
@ -33,7 +33,7 @@ final class AutoPairing(
|
|||
) |> { g =>
|
||||
val turns = g.player.fold(0, 1)
|
||||
g.copy(
|
||||
clock = tour.clock.chessClock.some,
|
||||
clock = tour.clock.toClock.some,
|
||||
turns = turns,
|
||||
startedAtTurn = turns)
|
||||
},
|
||||
|
|
|
@ -28,7 +28,10 @@ object BSONHandlers {
|
|||
def write(x: Schedule.Speed) = BSONString(x.name)
|
||||
}
|
||||
|
||||
private implicit val tournamentClockBSONHandler = Macros.handler[TournamentClock]
|
||||
private implicit val tournamentClockBSONHandler = {
|
||||
import chess.Clock.Config
|
||||
Macros.handler[Config]
|
||||
}
|
||||
|
||||
private implicit val spotlightBSONHandler = Macros.handler[Spotlight]
|
||||
|
||||
|
@ -50,7 +53,7 @@ object BSONHandlers {
|
|||
name = r str "name",
|
||||
status = r.get[Status]("status"),
|
||||
system = r.intO("system").fold[System](System.default)(System.orDefault),
|
||||
clock = r.get[TournamentClock]("clock"),
|
||||
clock = r.get[chess.Clock.Config]("clock"),
|
||||
minutes = r int "minutes",
|
||||
variant = variant,
|
||||
position = position,
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.joda.time.format.ISODateTimeFormat
|
|||
import play.api.libs.json._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import chess.Clock.{ Config => TournamentClock }
|
||||
import lila.common.LightUser
|
||||
import lila.common.PimpedJson._
|
||||
import lila.game.{ Game, GameRepo, Pov }
|
||||
|
@ -59,7 +60,7 @@ final class JsonView(
|
|||
"perf" -> tour.perfType,
|
||||
"nbPlayers" -> tour.nbPlayers,
|
||||
"minutes" -> tour.minutes,
|
||||
"clock" -> clockJson(tour.clock),
|
||||
"clock" -> tour.clock,
|
||||
"position" -> tour.position.some.filterNot(_.initial).map(positionJson),
|
||||
"private" -> tour.`private`.option(true),
|
||||
"verdicts" -> verdicts,
|
||||
|
@ -318,9 +319,7 @@ object JsonView {
|
|||
"freq" -> s.freq.name,
|
||||
"speed" -> s.speed.name)
|
||||
|
||||
private[tournament] def clockJson(c: TournamentClock) = Json.obj(
|
||||
"limit" -> c.limit,
|
||||
"increment" -> c.increment)
|
||||
private[tournament] implicit val clockWriter: Writes[TournamentClock] = Json.writes[TournamentClock]
|
||||
|
||||
private[tournament] def positionJson(s: chess.StartingPosition) = Json.obj(
|
||||
"eco" -> s.eco,
|
||||
|
|
|
@ -103,8 +103,8 @@ object Schedule {
|
|||
case (Bullet, HyperBullet) => true
|
||||
case _ => false
|
||||
}
|
||||
def fromClock(clock: TournamentClock) = {
|
||||
val time = clock.chessClock.estimateTotalTime
|
||||
def fromClock(clock: chess.Clock.Config) = {
|
||||
val time = clock.estimateTotalTime
|
||||
if (time < 60) HyperBullet
|
||||
else if (time < 180) Bullet
|
||||
else if (time < 480) Blitz
|
||||
|
@ -180,7 +180,7 @@ object Schedule {
|
|||
import Freq._, Speed._
|
||||
import chess.variant._
|
||||
|
||||
val TC = TournamentClock
|
||||
val TC = chess.Clock.Config
|
||||
|
||||
(s.freq, s.variant, s.speed) match {
|
||||
// Special cases.
|
||||
|
|
|
@ -24,7 +24,7 @@ final class ScheduleJsonView(
|
|||
"createdBy" -> tour.createdBy,
|
||||
"system" -> tour.system.toString.toLowerCase,
|
||||
"minutes" -> tour.minutes,
|
||||
"clock" -> clockJson(tour.clock),
|
||||
"clock" -> tour.clock,
|
||||
"position" -> tour.position.some.filterNot(_.initial).map(positionJson),
|
||||
"rated" -> tour.mode.rated,
|
||||
"fullName" -> tour.fullName,
|
||||
|
|
|
@ -8,7 +8,6 @@ import scala.concurrent.duration._
|
|||
|
||||
import actorApi._
|
||||
import lila.common.LightUser
|
||||
import lila.hub.actorApi.WithUserIds
|
||||
import lila.hub.TimeBomb
|
||||
import lila.memo.ExpireSetMemo
|
||||
import lila.socket.actorApi.{ Connected => _, _ }
|
||||
|
@ -18,7 +17,7 @@ private[tournament] final class Socket(
|
|||
tournamentId: String,
|
||||
val history: History[Messadata],
|
||||
jsonView: JsonView,
|
||||
lightUser: String => Option[LightUser],
|
||||
lightUser: LightUser.Getter,
|
||||
uidTimeout: Duration,
|
||||
socketTimeout: Duration) extends SocketActor[Member](uidTimeout) with Historical[Member, Messadata] {
|
||||
|
||||
|
@ -27,7 +26,7 @@ private[tournament] final class Socket(
|
|||
private var delayedCrowdNotification = false
|
||||
private var delayedReloadNotification = false
|
||||
|
||||
private var clock = none[chess.Clock]
|
||||
private var clock = none[chess.Clock.Config]
|
||||
|
||||
private var waitingUsers = WaitingUsers.empty
|
||||
|
||||
|
@ -44,8 +43,7 @@ private[tournament] final class Socket(
|
|||
|
||||
def receiveSpecific = ({
|
||||
|
||||
case SetTournament(Some(tour)) =>
|
||||
clock = tour.clock.chessClock.some
|
||||
case SetTournament(Some(tour)) => clock = tour.clock.some
|
||||
|
||||
case StartGame(game) =>
|
||||
game.players foreach { player =>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue