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
Thibault Duplessis 2016-12-06 12:33:06 +01:00
commit 1b78f74d4c
142 changed files with 2340 additions and 1616 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,4 +78,3 @@ object Bus extends ExtensionId[Bus] with ExtensionIdProvider {
override def createExtension(system: ExtendedActorSystem) = new Bus(system)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package lila.game
import chess.Speed
import chess.{Speed,Clock}
import lila.rating.{ Perf, PerfType }
import lila.user.Perfs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,3 +5,4 @@ gameOver=Game Over
waitingForOpponent=Elinde isitha
waiting=Ukulinda
yourTurn=Ithuba lakho
aiNameLevelAiLevel=%s Izinga %s

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 (!_)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package lila
package object pool extends PackageObject with WithPlay {
private[pool] val logger = lila.log("pool")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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