diff --git a/app/Env.scala b/app/Env.scala index 22aa519e95..5ee89dbdae 100644 --- a/app/Env.scala +++ b/app/Env.scala @@ -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 } diff --git a/app/controllers/Api.scala b/app/controllers/Api.scala index 78ca5d1d6b..2c06ee46c2 100644 --- a/app/controllers/Api.scala +++ b/app/controllers/Api.scala @@ -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)) } diff --git a/app/controllers/ForumTopic.scala b/app/controllers/ForumTopic.scala index 11feb02e2a..bc3ee0375c 100644 --- a/app/controllers/ForumTopic.scala +++ b/app/controllers/ForumTopic.scala @@ -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)) } } diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index 8180505b36..6b33daa304 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -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 diff --git a/app/controllers/LilaSocket.scala b/app/controllers/LilaSocket.scala index 9b566d0817..c9d2a18dc0 100644 --- a/app/controllers/LilaSocket.scala +++ b/app/controllers/LilaSocket.scala @@ -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 } diff --git a/app/controllers/Lobby.scala b/app/controllers/Lobby.scala index 755cae1194..89d24abcca 100644 --- a/app/controllers/Lobby.scala +++ b/app/controllers/Lobby.scala @@ -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 } } diff --git a/app/controllers/Puzzle.scala b/app/controllers/Puzzle.scala index 2bc510b09d..50e2736210 100644 --- a/app/controllers/Puzzle.scala +++ b/app/controllers/Puzzle.scala @@ -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) } diff --git a/app/controllers/Report.scala b/app/controllers/Report.scala index 89f47ddbbb..7bf257bba2 100644 --- a/app/controllers/Report.scala +++ b/app/controllers/Report.scala @@ -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) diff --git a/app/controllers/Setup.scala b/app/controllers/Setup.scala index 3d0a56baea..9733088325 100644 --- a/app/controllers/Setup.scala +++ b/app/controllers/Setup.scala @@ -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 + } ) } } diff --git a/app/controllers/WorldMap.scala b/app/controllers/WorldMap.scala deleted file mode 100644 index 9ffbde72e3..0000000000 --- a/app/controllers/WorldMap.scala +++ /dev/null @@ -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" - } - } -} diff --git a/app/templating/GameHelper.scala b/app/templating/GameHelper.scala index 821b7603c0..3ae57fd781 100644 --- a/app/templating/GameHelper.scala +++ b/app/templating/GameHelper.scala @@ -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}" diff --git a/app/views/base/fpmenu.scala.html b/app/views/base/fpmenu.scala.html index c5dc8954f1..3ec6dce73b 100644 --- a/app/views/base/fpmenu.scala.html +++ b/app/views/base/fpmenu.scala.html @@ -79,7 +79,6 @@ @trans.mobileApp() ı } @trans.blog() ı - World map ı @NotForKids { @trans.webmasters() ı @trans.contribute() ı diff --git a/app/views/challenge/explanation.scala.html b/app/views/challenge/explanation.scala.html index e147df5b85..416e4aa483 100644 --- a/app/views/challenge/explanation.scala.html +++ b/app/views/challenge/explanation.scala.html @@ -5,6 +5,6 @@ @c.daysPerTurn.map { days => @{(days == 1).fold(trans.oneDay(), trans.nbDays(days))} }.getOrElse { - @shortClockName(c.clock.map(_.chessClock)) + @shortClockName(c.clock.map(_.config)) }

diff --git a/app/views/game/vstext.scala.html b/app/views/game/vstext.scala.html index b4b77b4824..e62394195c 100644 --- a/app/views/game/vstext.scala.html +++ b/app/views/game/vstext.scala.html @@ -13,7 +13,7 @@ @lightUser(pov.opponent.userId).flatMap(_.title).map(" " + _) @pov.game.clock.map { c => -
@shortClockName(c)
+
@shortClockName(c.config)
}.getOrElse { @ctxOption.map { ctx => @pov.game.daysPerTurn.map { days => diff --git a/app/views/simul/allCreated.scala.html b/app/views/simul/allCreated.scala.html index 1b9a8c2ff5..3f5114b971 100644 --- a/app/views/simul/allCreated.scala.html +++ b/app/views/simul/allCreated.scala.html @@ -15,7 +15,7 @@ @userIdLink(simul.hostId.some) - @simul.clock.show + @simul.clock.config.show @simul.applicants.size diff --git a/app/views/simul/setup.scala.html b/app/views/simul/setup.scala.html index 655ba839c2..1e7b412ca9 100644 --- a/app/views/simul/setup.scala.html +++ b/app/views/simul/setup.scala.html @@ -1,5 +1,5 @@ @(sim: lila.simul.Simul)(implicit ctx: Context) - @sim.clock.show • + @sim.clock.config.show • @sim.variants.map(_.name).mkString(", ") diff --git a/app/views/simul/side.scala.html b/app/views/simul/side.scala.html index d5ed6a62ae..8a86980f50 100644 --- a/app/views/simul/side.scala.html +++ b/app/views/simul/side.scala.html @@ -7,7 +7,7 @@ } - @sim.clock.show
+ @sim.clock.config.show
@sim.variants.map(_.name).mkString(", ") • @trans.casual()
diff --git a/app/views/site/worldMap.scala.html b/app/views/site/worldMap.scala.html deleted file mode 100644 index 5af35cc81e..0000000000 --- a/app/views/site/worldMap.scala.html +++ /dev/null @@ -1,25 +0,0 @@ -@() - - - - - - Lichess World Map - - @cssAt("worldMap/main.css") - - - -

lichess.org network

-
-
-

Map of live games

- Orange: games in progress
- White: game creations -
- @jQueryTag - @jsAt("worldMap/raphael-min.js") - @jsAt("worldMap/world.js") - @jsAt("worldMap/app.js") - - diff --git a/app/views/tv/side.scala.html b/app/views/tv/side.scala.html index 624e514754..a31f788e7e 100644 --- a/app/views/tv/side.scala.html +++ b/app/views/tv/side.scala.html @@ -10,7 +10,7 @@ @playerLink(pov.game.blackPlayer, withRating = false, withOnline = false, withDiff = false, variant = pov.game.variant)
- @shortClockName(pov.game.clock), @game.variantLink(pov.game.variant, variantName(pov.game.variant)) + @shortClockName(pov.game.clock.map(_.config)), @game.variantLink(pov.game.variant, variantName(pov.game.variant)) @if(pov.game.rated) { , @trans.rated() } diff --git a/app/views/user/mod.scala.html b/app/views/user/mod.scala.html index 7a4bf15e33..392d3b6949 100644 --- a/app/views/user/mod.scala.html +++ b/app/views/user/mod.scala.html @@ -208,7 +208,7 @@ @p.game.perfType.map { pt => } - @shortClockName(p.game.clock) + @shortClockName(p.game.clock.map(_.config)) } diff --git a/bin/build-deps.sh b/bin/build-deps.sh index f7cb169088..5870094e4b 100755 --- a/bin/build-deps.sh +++ b/bin/build-deps.sh @@ -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 diff --git a/conf/base.conf b/conf/base.conf index e671e3ab6f..a41e88edf6 100644 --- a/conf/base.conf +++ b/conf/base.conf @@ -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. diff --git a/conf/prod-logger.xml b/conf/prod-logger.xml index de086c9408..caed6a261f 100644 --- a/conf/prod-logger.xml +++ b/conf/prod-logger.xml @@ -75,6 +75,16 @@ + + + /var/log/lichess/pool.log + %date %-5level %logger{30} %message%n%xException + + /var/log/lichess/pool-log-%d{yyyy-MM-dd}.gz + 7 + + + /var/log/lichess/tournament.log diff --git a/conf/routes b/conf/routes index ad5a17914b..7c0d7f114b 100644 --- a/conf/routes +++ b/conf/routes @@ -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 diff --git a/modules/api/src/main/Env.scala b/modules/api/src/main/Env.scala index 211e2a7869..b35f9465f9 100644 --- a/modules/api/src/main/Env.scala +++ b/modules/api/src/main/Env.scala @@ -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) } diff --git a/modules/api/src/main/InfluxEvent.scala b/modules/api/src/main/InfluxEvent.scala new file mode 100644 index 0000000000..a13cfc38ff --- /dev/null +++ b/modules/api/src/main/InfluxEvent.scala @@ -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) +} diff --git a/modules/api/src/main/LobbyApi.scala b/modules/api/src/main/LobbyApi.scala index 6d3b3ab3ca..9b41f77999 100644 --- a/modules/api/src/main/LobbyApi.scala +++ b/modules/api/src/main/LobbyApi.scala @@ -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( diff --git a/modules/api/src/test/JsonStringifyPerfTest.scala b/modules/api/src/test/JsonStringifyPerfTest.scala new file mode 100644 index 0000000000..b7ffe2b1c3 --- /dev/null +++ b/modules/api/src/test/JsonStringifyPerfTest.scala @@ -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 + } + } +} diff --git a/modules/challenge/src/main/BSONHandlers.scala b/modules/challenge/src/main/BSONHandlers.scala index d42d43c9d2..b1f345d453 100644 --- a/modules/challenge/src/main/BSONHandlers.scala +++ b/modules/challenge/src/main/BSONHandlers.scala @@ -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] { diff --git a/modules/challenge/src/main/Challenge.scala b/modules/challenge/src/main/Challenge.scala index bd6d0070c0..824c4e2328 100644 --- a/modules/challenge/src/main/Challenge.scala +++ b/modules/challenge/src/main/Challenge.scala @@ -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 = diff --git a/modules/challenge/src/main/ChallengeApi.scala b/modules/challenge/src/main/ChallengeApi.scala index ebd8ab6f74..081440ad13 100644 --- a/modules/challenge/src/main/ChallengeApi.scala +++ b/modules/challenge/src/main/ChallengeApi.scala @@ -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 }, diff --git a/modules/challenge/src/main/Joiner.scala b/modules/challenge/src/main/Joiner.scala index 70a60c4669..ce0a91c022 100644 --- a/modules/challenge/src/main/Joiner.scala +++ b/modules/challenge/src/main/Joiner.scala @@ -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 } diff --git a/modules/challenge/src/main/JsonView.scala b/modules/challenge/src/main/JsonView.scala index fadc22df3e..6ca51c35ed 100644 --- a/modules/challenge/src/main/JsonView.scala +++ b/modules/challenge/src/main/JsonView.scala @@ -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) diff --git a/modules/chess b/modules/chess index 731ac97872..fd402c6243 160000 --- a/modules/chess +++ b/modules/chess @@ -1 +1 @@ -Subproject commit 731ac9787254ddddb0c91389a6b4ad535da278a3 +Subproject commit fd402c6243e2491c325700961047afed70444edd diff --git a/modules/common/src/main/Bus.scala b/modules/common/src/main/Bus.scala index 3e560e10c6..2685efeaff 100644 --- a/modules/common/src/main/Bus.scala +++ b/modules/common/src/main/Bus.scala @@ -78,4 +78,3 @@ object Bus extends ExtensionId[Bus] with ExtensionIdProvider { override def createExtension(system: ExtendedActorSystem) = new Bus(system) } - diff --git a/modules/tournament/src/main/arena/WMMatching.scala b/modules/common/src/main/WMMatching.scala similarity index 84% rename from modules/tournament/src/main/arena/WMMatching.scala rename to modules/common/src/main/WMMatching.scala index 4260aba1df..6e585410ff 100644 --- a/modules/tournament/src/main/arena/WMMatching.scala +++ b/modules/common/src/main/WMMatching.scala @@ -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 } diff --git a/modules/common/src/main/WindowCount.scala b/modules/common/src/main/WindowCount.scala deleted file mode 100644 index b46f820ea3..0000000000 --- a/modules/common/src/main/WindowCount.scala +++ /dev/null @@ -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 - -} diff --git a/modules/common/src/main/mon.scala b/modules/common/src/main/mon.scala index b914cfedcd..07ba8c42b7 100644 --- a/modules/common/src/main/mon.scala +++ b/modules/common/src/main/mon.scala @@ -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 { diff --git a/modules/game/src/main/BinaryFormat.scala b/modules/game/src/main/BinaryFormat.scala index efde5fd623..e95a493cfc 100644 --- a/modules/game/src/main/BinaryFormat.scala +++ b/modules/game/src/main/BinaryFormat.scala @@ -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, diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index c8ce6512b1..73dfbad210 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -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 diff --git a/modules/game/src/main/PerfPicker.scala b/modules/game/src/main/PerfPicker.scala index 49d5f54ada..4756fce48a 100644 --- a/modules/game/src/main/PerfPicker.scala +++ b/modules/game/src/main/PerfPicker.scala @@ -1,6 +1,6 @@ package lila.game -import chess.Speed +import chess.{Speed,Clock} import lila.rating.{ Perf, PerfType } import lila.user.Perfs diff --git a/modules/game/src/main/Source.scala b/modules/game/src/main/Source.scala index 5240f818b9..9065bce110 100644 --- a/modules/game/src/main/Source.scala +++ b/modules/game/src/main/Source.scala @@ -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 } diff --git a/modules/hub/src/main/SequentialProvider.scala b/modules/hub/src/main/SequentialProvider.scala index 1bbb2fd493..6b16831481 100644 --- a/modules/hub/src/main/SequentialProvider.scala +++ b/modules/hub/src/main/SequentialProvider.scala @@ -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 diff --git a/modules/hub/src/main/actorApi.scala b/modules/hub/src/main/actorApi.scala index f554afc423..1d315a3de3 100644 --- a/modules/hub/src/main/actorApi.scala +++ b/modules/hub/src/main/actorApi.scala @@ -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) } diff --git a/modules/i18n/messages/messages.af b/modules/i18n/messages/messages.af index 70d9819be3..8e8ee29108 100644 --- a/modules/i18n/messages/messages.af +++ b/modules/i18n/messages/messages.af @@ -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: diff --git a/modules/i18n/messages/messages.es b/modules/i18n/messages/messages.es index ce783c45df..cb73010cf1 100644 --- a/modules/i18n/messages/messages.es +++ b/modules/i18n/messages/messages.es @@ -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 diff --git a/modules/i18n/messages/messages.ia b/modules/i18n/messages/messages.ia new file mode 100644 index 0000000000..b574ce323f --- /dev/null +++ b/modules/i18n/messages/messages.ia @@ -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 diff --git a/modules/i18n/messages/messages.nb b/modules/i18n/messages/messages.nb index d711e60eed..a7b9e744d4 100644 --- a/modules/i18n/messages/messages.nb +++ b/modules/i18n/messages/messages.nb @@ -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 diff --git a/modules/i18n/messages/messages.pl b/modules/i18n/messages/messages.pl index decf592da2..be96dbce15 100644 --- a/modules/i18n/messages/messages.pl +++ b/modules/i18n/messages/messages.pl @@ -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 diff --git a/modules/i18n/messages/messages.zu b/modules/i18n/messages/messages.zu index 1697385edd..0d19337c32 100644 --- a/modules/i18n/messages/messages.zu +++ b/modules/i18n/messages/messages.zu @@ -5,3 +5,4 @@ gameOver=Game Over waitingForOpponent=Elinde isitha waiting=Ukulinda yourTurn=Ithuba lakho +aiNameLevelAiLevel=%s Izinga %s diff --git a/modules/i18n/src/main/Contributors.scala b/modules/i18n/src/main/Contributors.scala index a0a48f3e56..f1b328fb9f 100644 --- a/modules/i18n/src/main/Contributors.scala +++ b/modules/i18n/src/main/Contributors.scala @@ -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) } diff --git a/modules/i18n/src/main/I18nKeys.scala b/modules/i18n/src/main/I18nKeys.scala index ab1d34bca0..730a4cb8f7 100644 --- a/modules/i18n/src/main/I18nKeys.scala +++ b/modules/i18n/src/main/I18nKeys.scala @@ -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 diff --git a/modules/lobby/src/main/Biter.scala b/modules/lobby/src/main/Biter.scala index c472b94ebb..f0c11ae5bc 100644 --- a/modules/lobby/src/main/Biter.scala +++ b/modules/lobby/src/main/Biter.scala @@ -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 => diff --git a/modules/lobby/src/main/Env.scala b/modules/lobby/src/main/Env.scala index ecdc482f93..65f6cfbd58 100644 --- a/modules/lobby/src/main/Env.scala +++ b/modules/lobby/src/main/Env.scala @@ -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) } diff --git a/modules/lobby/src/main/Hook.scala b/modules/lobby/src/main/Hook.scala index 850fc14f5a..f4cfc5d21c 100644 --- a/modules/lobby/src/main/Hook.scala +++ b/modules/lobby/src/main/Hook.scala @@ -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, diff --git a/modules/lobby/src/main/HookRepo.scala b/modules/lobby/src/main/HookRepo.scala index 5a66977864..33a19f0c2b 100644 --- a/modules/lobby/src/main/HookRepo.scala +++ b/modules/lobby/src/main/HookRepo.scala @@ -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] = { diff --git a/modules/lobby/src/main/Lobby.scala b/modules/lobby/src/main/Lobby.scala index f9a68c093f..3a338e84b7 100644 --- a/modules/lobby/src/main/Lobby.scala +++ b/modules/lobby/src/main/Lobby.scala @@ -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) { diff --git a/modules/lobby/src/main/Socket.scala b/modules/lobby/src/main/Socket.scala index 1d4bf027d3..874a8b0750 100644 --- a/modules/lobby/src/main/Socket.scala +++ b/modules/lobby/src/main/Socket.scala @@ -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")) } diff --git a/modules/lobby/src/main/SocketHandler.scala b/modules/lobby/src/main/SocketHandler.scala index 789eee5622..5411ae6bf5 100644 --- a/modules/lobby/src/main/SocketHandler.scala +++ b/modules/lobby/src/main/SocketHandler.scala @@ -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) } } } diff --git a/modules/lobby/src/main/actorApi.scala b/modules/lobby/src/main/actorApi.scala index ad8d7671d8..e882470836 100644 --- a/modules/lobby/src/main/actorApi.scala +++ b/modules/lobby/src/main/actorApi.scala @@ -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]) diff --git a/modules/mod/src/main/AssessApi.scala b/modules/mod/src/main/AssessApi.scala index 89e871d7af..ae167a2014 100644 --- a/modules/mod/src/main/AssessApi.scala +++ b/modules/mod/src/main/AssessApi.scala @@ -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 = { diff --git a/modules/playban/src/main/PlaybanApi.scala b/modules/playban/src/main/PlaybanApi.scala index 97422698df..c1a20e2098 100644 --- a/modules/playban/src/main/PlaybanApi.scala +++ b/modules/playban/src/main/PlaybanApi.scala @@ -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 (!_) } diff --git a/modules/pool/src/main/Env.scala b/modules/pool/src/main/Env.scala new file mode 100644 index 0000000000..27d3003fc4 --- /dev/null +++ b/modules/pool/src/main/Env.scala @@ -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) +} diff --git a/modules/pool/src/main/GameStarter.scala b/modules/pool/src/main/GameStarter.scala new file mode 100644 index 0000000000..939df2970c --- /dev/null +++ b/modules/pool/src/main/GameStarter.scala @@ -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) +} diff --git a/modules/pool/src/main/HookThieve.scala b/modules/pool/src/main/HookThieve.scala new file mode 100644 index 0000000000..26bfaf58c2 --- /dev/null +++ b/modules/pool/src/main/HookThieve.scala @@ -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]) +} diff --git a/modules/pool/src/main/MatchMaking.scala b/modules/pool/src/main/MatchMaking.scala new file mode 100644 index 0000000000..e5c0b7a442 --- /dev/null +++ b/modules/pool/src/main/MatchMaking.scala @@ -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 + ) + } + } +} diff --git a/modules/pool/src/main/PoolActor.scala b/modules/pool/src/main/PoolActor.scala new file mode 100644 index 0000000000..662c6ca6b6 --- /dev/null +++ b/modules/pool/src/main/PoolActor.scala @@ -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 +} diff --git a/modules/pool/src/main/PoolApi.scala b/modules/pool/src/main/PoolApi.scala new file mode 100644 index 0000000000..a18f7388ad --- /dev/null +++ b/modules/pool/src/main/PoolApi.scala @@ -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) +} diff --git a/modules/pool/src/main/PoolConfig.scala b/modules/pool/src/main/PoolConfig.scala new file mode 100644 index 0000000000..d9d9c0254b --- /dev/null +++ b/modules/pool/src/main/PoolConfig.scala @@ -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) +} diff --git a/modules/pool/src/main/PoolList.scala b/modules/pool/src/main/PoolList.scala new file mode 100644 index 0000000000..14a6cb4359 --- /dev/null +++ b/modules/pool/src/main/PoolList.scala @@ -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) + } +} diff --git a/modules/pool/src/main/PoolMember.scala b/modules/pool/src/main/PoolMember.scala new file mode 100644 index 0000000000..a97159917c --- /dev/null +++ b/modules/pool/src/main/PoolMember.scala @@ -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) +} diff --git a/modules/pool/src/main/package.scala b/modules/pool/src/main/package.scala new file mode 100644 index 0000000000..c57a9fc0b0 --- /dev/null +++ b/modules/pool/src/main/package.scala @@ -0,0 +1,6 @@ +package lila + +package object pool extends PackageObject with WithPlay { + + private[pool] val logger = lila.log("pool") +} diff --git a/modules/push/src/main/OneSignalPush.scala b/modules/push/src/main/OneSignalPush.scala index e7d0608bae..5a946213df 100644 --- a/modules/push/src/main/OneSignalPush.scala +++ b/modules/push/src/main/OneSignalPush.scala @@ -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 { diff --git a/modules/push/src/main/PushApi.scala b/modules/push/src/main/PushApi.scala index fbc73d2bd3..e94b850254 100644 --- a/modules/push/src/main/PushApi.scala +++ b/modules/push/src/main/PushApi.scala @@ -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) } diff --git a/modules/push/src/main/Stacking.scala b/modules/push/src/main/Stacking.scala new file mode 100644 index 0000000000..ad2413649d --- /dev/null +++ b/modules/push/src/main/Stacking.scala @@ -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") +} diff --git a/modules/puzzle/src/main/Finisher.scala b/modules/puzzle/src/main/Finisher.scala index aca77fb16b..b8bc58d524 100644 --- a/modules/puzzle/src/main/Finisher.scala +++ b/modules/puzzle/src/main/Finisher.scala @@ -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, diff --git a/modules/puzzle/src/main/Selector.scala b/modules/puzzle/src/main/Selector.scala index 540c379476..d6ec4218e5 100644 --- a/modules/puzzle/src/main/Selector.scala +++ b/modules/puzzle/src/main/Selector.scala @@ -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, diff --git a/modules/rating/src/main/Glicko.scala b/modules/rating/src/main/Glicko.scala index 3994403889..97be706a75 100644 --- a/modules/rating/src/main/Glicko.scala +++ b/modules/rating/src/main/Glicko.scala @@ -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) = ( diff --git a/modules/round/src/main/Env.scala b/modules/round/src/main/Env.scala index ae0c4d2ced..a08fd24a02 100644 --- a/modules/round/src/main/Env.scala +++ b/modules/round/src/main/Env.scala @@ -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, diff --git a/modules/round/src/main/Socket.scala b/modules/round/src/main/Socket.scala index dbbaac3abc..4b2f0e09bc 100644 --- a/modules/round/src/main/Socket.scala +++ b/modules/round/src/main/Socket.scala @@ -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) diff --git a/modules/security/src/main/DataForm.scala b/modules/security/src/main/DataForm.scala index fc4a4b6c5e..b5c9930e7e 100644 --- a/modules/security/src/main/DataForm.scala +++ b/modules/security/src/main/DataForm.scala @@ -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( diff --git a/modules/security/src/main/DisposableEmailDomain.scala b/modules/security/src/main/DisposableEmailDomain.scala index 2b55121345..c0c01979a6 100644 --- a/modules/security/src/main/DisposableEmailDomain.scala +++ b/modules/security/src/main/DisposableEmailDomain.scala @@ -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 diff --git a/modules/security/src/test/DisposableEmailDomainTest.scala b/modules/security/src/test/DisposableEmailDomainTest.scala index 6db3b37a79..d847f1cf4c 100644 --- a/modules/security/src/test/DisposableEmailDomainTest.scala +++ b/modules/security/src/test/DisposableEmailDomainTest.scala @@ -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 } } } diff --git a/modules/security/src/test/Fixtures.scala b/modules/security/src/test/Fixtures.scala index 71c9a3b7b7..4412a60526 100644 --- a/modules/security/src/test/Fixtures.scala +++ b/modules/security/src/test/Fixtures.scala @@ -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 diff --git a/modules/setup/src/main/Config.scala b/modules/setup/src/main/Config.scala index 516983a1d8..0b5a48d54d 100644 --- a/modules/setup/src/main/Config.scala +++ b/modules/setup/src/main/Config.scala @@ -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 } diff --git a/modules/setup/src/main/Processor.scala b/modules/setup/src/main/Processor.scala index 5d67c7dab7..623580fb4e 100644 --- a/modules/setup/src/main/Processor.scala +++ b/modules/setup/src/main/Processor.scala @@ -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) } diff --git a/modules/simul/src/main/JsonView.scala b/modules/simul/src/main/JsonView.scala index aa9a78cf6d..5a4dc8635b 100644 --- a/modules/simul/src/main/JsonView.scala +++ b/modules/simul/src/main/JsonView.scala @@ -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, diff --git a/modules/simul/src/main/Simul.scala b/modules/simul/src/main/Simul.scala index 6d61f0edf1..7172b4ebad 100644 --- a/modules/simul/src/main/Simul.scala +++ b/modules/simul/src/main/Simul.scala @@ -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) } diff --git a/modules/simul/src/main/SimulApi.scala b/modules/simul/src/main/SimulApi.scala index e0ee200c7d..5dc6ea916e 100644 --- a/modules/simul/src/main/SimulApi.scala +++ b/modules/simul/src/main/SimulApi.scala @@ -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 => diff --git a/modules/simul/src/main/SimulClock.scala b/modules/simul/src/main/SimulClock.scala index 4f5b7dacbd..f42a72cd56 100644 --- a/modules/simul/src/main/SimulClock.scala +++ b/modules/simul/src/main/SimulClock.scala @@ -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 } diff --git a/modules/simul/src/main/SimulRepo.scala b/modules/simul/src/main/SimulRepo.scala index 6fb871c0d3..989bc3377e 100644 --- a/modules/simul/src/main/SimulRepo.scala +++ b/modules/simul/src/main/SimulRepo.scala @@ -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] { diff --git a/modules/simul/src/main/package.scala b/modules/simul/src/main/package.scala index 13f7516d7e..cadaf2d4e7 100644 --- a/modules/simul/src/main/package.scala +++ b/modules/simul/src/main/package.scala @@ -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) diff --git a/modules/socket/src/main/SocketMember.scala b/modules/socket/src/main/SocketMember.scala index 9d544729a4..f7be9968b6 100644 --- a/modules/socket/src/main/SocketMember.scala +++ b/modules/socket/src/main/SocketMember.scala @@ -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 } diff --git a/modules/socket/src/main/WithSocket.scala b/modules/socket/src/main/WithSocket.scala index 6467c900b3..3fce70757f 100644 --- a/modules/socket/src/main/WithSocket.scala +++ b/modules/socket/src/main/WithSocket.scala @@ -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) diff --git a/modules/tournament/src/main/AutoPairing.scala b/modules/tournament/src/main/AutoPairing.scala index 0333eae832..21ea60d0b4 100644 --- a/modules/tournament/src/main/AutoPairing.scala +++ b/modules/tournament/src/main/AutoPairing.scala @@ -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) }, diff --git a/modules/tournament/src/main/BSONHandlers.scala b/modules/tournament/src/main/BSONHandlers.scala index dce957752a..9841f2549b 100644 --- a/modules/tournament/src/main/BSONHandlers.scala +++ b/modules/tournament/src/main/BSONHandlers.scala @@ -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, diff --git a/modules/tournament/src/main/JsonView.scala b/modules/tournament/src/main/JsonView.scala index 43b6da8f4a..007ebbccf2 100644 --- a/modules/tournament/src/main/JsonView.scala +++ b/modules/tournament/src/main/JsonView.scala @@ -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, diff --git a/modules/tournament/src/main/Schedule.scala b/modules/tournament/src/main/Schedule.scala index 25239f5742..2fb05bed8f 100644 --- a/modules/tournament/src/main/Schedule.scala +++ b/modules/tournament/src/main/Schedule.scala @@ -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. diff --git a/modules/tournament/src/main/ScheduleJsonView.scala b/modules/tournament/src/main/ScheduleJsonView.scala index cd1f6b209a..4b3ccbf69b 100644 --- a/modules/tournament/src/main/ScheduleJsonView.scala +++ b/modules/tournament/src/main/ScheduleJsonView.scala @@ -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, diff --git a/modules/tournament/src/main/Socket.scala b/modules/tournament/src/main/Socket.scala index bee1ef26fd..d532942c41 100644 --- a/modules/tournament/src/main/Socket.scala +++ b/modules/tournament/src/main/Socket.scala @@ -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 => diff --git a/modules/tournament/src/main/Tournament.scala b/modules/tournament/src/main/Tournament.scala index dd4c3a5713..ea18c20ce4 100644 --- a/modules/tournament/src/main/Tournament.scala +++ b/modules/tournament/src/main/Tournament.scala @@ -3,6 +3,7 @@ package lila.tournament import org.joda.time.{ DateTime, Duration, Interval } import ornicar.scalalib.Random +import chess.Clock.{ Config => TournamentClock } import chess.{ Speed, Mode, StartingPosition } import lila.game.{ PovRef, PerfPicker } import lila.user.User @@ -82,7 +83,7 @@ case class Tournament( case _ => false } - def speed = Speed(clock.chessClock.some) + def speed = Speed(clock) def perfType = PerfPicker.perfType(speed, variant, none) def perfLens = PerfPicker.mainOrDefault(speed, variant, none) @@ -91,7 +92,7 @@ case class Tournament( if (minutes < 60) s"${minutes}m" else s"${minutes / 60}h" + (if (minutes % 60 != 0) s" ${(minutes % 60)}m" else "") - def berserkable = system.berserkable && clock.chessClock.berserkable + def berserkable = system.berserkable && clock.berserkable def clockStatus = secondsToFinish |> { s => "%02d:%02d".format(s / 60, s % 60) } diff --git a/modules/tournament/src/main/TournamentApi.scala b/modules/tournament/src/main/TournamentApi.scala index a60cdc675c..60a47ed764 100644 --- a/modules/tournament/src/main/TournamentApi.scala +++ b/modules/tournament/src/main/TournamentApi.scala @@ -44,7 +44,7 @@ final class TournamentApi( var variant = chess.variant.Variant orDefault setup.variant val tour = Tournament.make( createdByUserId = me.id, - clock = TournamentClock((setup.clockTime * 60).toInt, setup.clockIncrement), + clock = chess.Clock.Config((setup.clockTime * 60).toInt, setup.clockIncrement), minutes = setup.minutes, waitMinutes = setup.waitMinutes, mode = setup.mode.fold(Mode.default)(Mode.orDefault), @@ -385,7 +385,7 @@ final class TournamentApi( } private object publish { - private val debouncer = system.actorOf(Props(new Debouncer(10 seconds, { + private val debouncer = system.actorOf(Props(new Debouncer(15 seconds, { (_: Debouncer.Nothing) => fetchVisibleTournaments foreach { vis => site ! SendToFlag("tournament", Json.obj( diff --git a/modules/tournament/src/main/TournamentClock.scala b/modules/tournament/src/main/TournamentClock.scala deleted file mode 100644 index a54907c08a..0000000000 --- a/modules/tournament/src/main/TournamentClock.scala +++ /dev/null @@ -1,15 +0,0 @@ -package lila.tournament - -// All durations are expressed in seconds -case class TournamentClock(limit: Int, increment: Int) { - - def limitInMinutes = chessClock.limitInMinutes - - def show = chessClock.show - - lazy val chessClock = chess.Clock(limit, increment) - - def hasIncrement = increment > 0 - - override def toString = show -} diff --git a/modules/tournament/src/main/WaitingUsers.scala b/modules/tournament/src/main/WaitingUsers.scala index 35c5bbf2d2..52cb43192a 100644 --- a/modules/tournament/src/main/WaitingUsers.scala +++ b/modules/tournament/src/main/WaitingUsers.scala @@ -2,11 +2,12 @@ package lila.tournament import org.joda.time.DateTime +import chess.Clock.{ Config => TournamentClock } import lila.user.User private[tournament] case class WaitingUsers( hash: Map[User.ID, DateTime], - clock: Option[chess.Clock], + clock: Option[TournamentClock], date: DateTime) { // hyperbullet -> 10 @@ -43,13 +44,13 @@ private[tournament] case class WaitingUsers( }.toList } - def update(us: Set[User.ID], clock: Option[chess.Clock]) = { + def update(us: Set[User.ID], clock: Option[TournamentClock]) = { val newDate = DateTime.now copy( date = newDate, clock = clock, hash = hash.filterKeys(us.contains) ++ - us.filterNot(hash.contains).map { _ -> newDate } + us.filterNot(hash.contains).map { _ -> newDate } ) } diff --git a/modules/tournament/src/main/arena/AntmaPairing.scala b/modules/tournament/src/main/arena/AntmaPairing.scala index 34e2c5d85f..33294dabfe 100644 --- a/modules/tournament/src/main/arena/AntmaPairing.scala +++ b/modules/tournament/src/main/arena/AntmaPairing.scala @@ -1,6 +1,7 @@ package lila.tournament package arena +import lila.common.WMMatching import PairingSystem.{ Data, url } private object AntmaPairing { @@ -8,32 +9,27 @@ private object AntmaPairing { def apply(data: Data, players: RankedPlayers): List[Pairing.Prep] = players.nonEmpty ?? { import data._ - val a: Array[RankedPlayer] = players.toArray - val n: Int = a.length - def playedTogether(u1: String, u2: String) = if (lastOpponents.hash.get(u1).contains(u2)) 1 else 0 def f(x: Int): Int = (11500000 - 3500000 * x) * x - def pairScore(i: Int, j: Int): Int = - Math.abs(a(i).rank - a(j).rank) * 1000 + - Math.abs(a(i).player.rating - a(j).player.rating) + + def pairScore(a: RankedPlayer, b: RankedPlayer): Int = { + Math.abs(a.rank - b.rank) * 1000 + + Math.abs(a.player.rating - b.player.rating) + f { - playedTogether(a(i).player.userId, a(j).player.userId) + - playedTogether(a(j).player.userId, a(i).player.userId) + playedTogether(a.player.userId, b.player.userId) + + playedTogether(b.player.userId, a.player.userId) } + } - try { - val mate = WMMatching.minWeightMatching(WMMatching.fullGraph(n, pairScore)) - WMMatching.mateToEdges(mate).map { x => - Pairing.prep(tour, a(x._1).player, a(x._2).player) - } - } - catch { - case e: Exception => - logger.error("AntmaPairing", e) + WMMatching(players.toArray, pairScore).fold( + err => { + logger.error("WMMatching", err) Nil - } + }, + _ map { + case (a, b) => Pairing.prep(tour, a.player, b.player) + }) } } diff --git a/modules/tournament/src/main/crud/CrudApi.scala b/modules/tournament/src/main/crud/CrudApi.scala index 530319b9e9..53fa135dc0 100644 --- a/modules/tournament/src/main/crud/CrudApi.scala +++ b/modules/tournament/src/main/crud/CrudApi.scala @@ -36,7 +36,7 @@ final class CrudApi { private def empty = Tournament.make( createdByUserId = "lichess", - clock = TournamentClock(0, 0), + clock = chess.Clock.Config(0, 0), minutes = 0, system = System.Arena, variant = chess.variant.Standard, @@ -48,7 +48,7 @@ final class CrudApi { private def updateTour(tour: Tournament, data: CrudForm.Data) = { import data._ - val clock = TournamentClock((clockTime * 60).toInt, clockIncrement) + val clock = chess.Clock.Config((clockTime * 60).toInt, clockIncrement) val v = chess.variant.Variant.orDefault(variant) tour.copy( name = name, diff --git a/modules/user/src/main/JsonView.scala b/modules/user/src/main/JsonView.scala index e776afee99..e382c5604b 100644 --- a/modules/user/src/main/JsonView.scala +++ b/modules/user/src/main/JsonView.scala @@ -34,6 +34,10 @@ final class JsonView(isOnline: String => Boolean) { "online" -> isOnline(u.id), "engine" -> u.engine.option(true), "booster" -> u.booster.option(true), + "language" -> u.lang, + "profile" -> u.profile.flatMap(_.country).map { country => + Json.obj("country" -> country) + }, "perfs" -> perfs(u, onlyPerf), "patron" -> u.isPatron.option(true) ).noNull diff --git a/modules/user/src/main/RankingApi.scala b/modules/user/src/main/RankingApi.scala index 21575eeef7..fa36e1fc52 100644 --- a/modules/user/src/main/RankingApi.scala +++ b/modules/user/src/main/RankingApi.scala @@ -2,10 +2,10 @@ package lila.user import org.joda.time.DateTime import play.api.libs.iteratee._ -import reactivemongo.api.{ Cursor, ReadPreference } import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Match, Project, Group, GroupField, SumField, SumValue } import reactivemongo.api.Cursor import reactivemongo.api.ReadPreference +import reactivemongo.api.{ Cursor, ReadPreference } import reactivemongo.bson._ import scala.concurrent.duration._ @@ -80,7 +80,8 @@ final class RankingApi( private val cache = AsyncCache[Perf.ID, Map[User.ID, Rank]]( name = "rankingApi.weeklyStableRanking", f = compute, - timeToLive = 15 minutes) + timeToLive = 15 minutes, + resultTimeout = 10 seconds) private def compute(perfId: Perf.ID): Fu[Map[User.ID, Rank]] = coll.find( @@ -88,7 +89,7 @@ final class RankingApi( $doc("user" -> true, "_id" -> false) ).sort($doc("rating" -> -1)).cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred). fold(1 -> Map.newBuilder[User.ID, Rank]) { - case (state @ (rank, b), doc) => + case (state@(rank, b), doc) => doc.getAs[User.ID]("user").fold(state) { user => b += (user -> rank) (rank + 1) -> b @@ -113,7 +114,8 @@ final class RankingApi( lila.rating.PerfType(perfId).exists(lila.rating.PerfType.leaderboardable.contains) ?? { coll.aggregate( Match($doc("perf" -> perfId)), - List(Project($doc( + List( + Project($doc( "_id" -> false, "r" -> $doc( "$subtract" -> $arr( diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index 52979e4003..9302898bb6 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -101,19 +101,21 @@ object UserRepo { } } - def firstGetsWhite(u1O: Option[String], u2O: Option[String]): Fu[Boolean] = + def firstGetsWhite(u1: User.ID, u2: User.ID): Fu[Boolean] = coll.find( + $inIds(List(u1, u2)), + $id(true) + ).sort($doc(F.colorIt -> 1)).uno[Bdoc].map { + _.fold(scala.util.Random.nextBoolean) { doc => + doc.getAs[User.ID]("_id") contains u1 + } + }.addEffect { v => + incColor(u1, v.fold(1, -1)) + incColor(u2, v.fold(-1, 1)) + } + + def firstGetsWhite(u1O: Option[User.ID], u2O: Option[User.ID]): Fu[Boolean] = (u1O |@| u2O).tupled.fold(fuccess(scala.util.Random.nextBoolean)) { - case (u1, u2) => coll.find( - $inIds(List(u1, u2)), - $id(true) - ).sort($doc(F.colorIt -> 1)).uno[Bdoc].map { - _.fold(scala.util.Random.nextBoolean) { doc => - doc.getAs[String]("_id") contains u1 - } - }.addEffect { v => - incColor(u1, v.fold(1, -1)) - incColor(u2, v.fold(-1, 1)) - } + case (u1, u2) => firstGetsWhite(u1, u2) } def incColor(userId: User.ID, value: Int): Unit = @@ -189,8 +191,8 @@ object UserRepo { case _ => none }) ifFalse ai ).flatten.map(k => BSONElement(k, BSONInteger(1))) ::: List( - totalTime map BSONInteger.apply map(v => BSONElement(s"${F.playTime}.total", v)), - tvTime map BSONInteger.apply map(v => BSONElement(s"${F.playTime}.tv", v)) + totalTime map BSONInteger.apply map (v => BSONElement(s"${F.playTime}.total", v)), + tvTime map BSONInteger.apply map (v => BSONElement(s"${F.playTime}.tv", v)) ).flatten coll.update($id(id), $inc(incs)) @@ -325,11 +327,24 @@ object UserRepo { coll.updateField($id(user.id), "plan", plan).void } + private def docPerf(doc: Bdoc, perfType: PerfType): Option[Perf] = + doc.getAs[Bdoc](F.perfs).flatMap(_.getAs[Perf](perfType.key)) + def perfOf(id: ID, perfType: PerfType): Fu[Option[Perf]] = coll.find( $id(id), $doc(s"${F.perfs}.${perfType.key}" -> true) ).uno[Bdoc].map { - _.flatMap(_.getAs[Bdoc](F.perfs)).flatMap(_.getAs[Perf](perfType.key)) + _.flatMap { docPerf(_, perfType) } + } + + def perfOf(ids: Iterable[ID], perfType: PerfType): Fu[Map[ID, Perf]] = coll.find( + $inIds(ids), + $doc(s"${F.perfs}.${perfType.key}" -> true) + ).cursor[Bdoc]() + .collect[List](Int.MaxValue, err = Cursor.FailOnError[List[Bdoc]]()).map { docs => + docs.map { doc => + ~doc.getAs[ID]("_id") -> docPerf(doc, perfType).getOrElse(Perf.default) + }.toMap } def setSeenAt(id: ID) { diff --git a/modules/worldMap/src/main/Env.scala b/modules/worldMap/src/main/Env.scala deleted file mode 100644 index c6be9b8f9d..0000000000 --- a/modules/worldMap/src/main/Env.scala +++ /dev/null @@ -1,37 +0,0 @@ -package lila.worldMap - -import com.typesafe.config.Config - -import akka.actor._ -import com.sanoma.cda.geoip.MaxMindIpGeo -import lila.common.PimpedConfig._ - -final class Env( - system: akka.actor.ActorSystem, - config: Config) { - - private val GeoIPFile = config getString "geoip.file" - private val GeoIPCacheTtl = config duration "geoip.cache_ttl" - - private val stream = system.actorOf( - Props(new Stream( - geoIp = MaxMindIpGeo(GeoIPFile, 0), - geoIpCacheTtl = GeoIPCacheTtl))) - system.lilaBus.subscribe(stream, 'roundDoor) - - def getStream = { - import play.api.libs.iteratee._ - import play.api.libs.json._ - import akka.pattern.ask - import makeTimeout.short - stream ? Stream.Get mapTo manifest[Enumerator[JsValue]] - } -} - -object Env { - - lazy val current: Env = "worldMap" boot new Env( - system = lila.common.PlayApp.system, - config = lila.common.PlayApp loadConfig "worldMap") -} - diff --git a/modules/worldMap/src/main/Stream.scala b/modules/worldMap/src/main/Stream.scala deleted file mode 100644 index fcdb91c520..0000000000 --- a/modules/worldMap/src/main/Stream.scala +++ /dev/null @@ -1,90 +0,0 @@ -package lila.worldMap - -import akka.actor._ -import com.sanoma.cda.geoip.{ MaxMindIpGeo, IpLocation } -import java.security.MessageDigest -import lila.hub.actorApi.round.SocketEvent -import play.api.libs.iteratee._ -import play.api.libs.json._ -import scala.concurrent.duration._ - -import lila.rating.PerfType - -private final class Stream( - geoIp: MaxMindIpGeo, - geoIpCacheTtl: Duration) extends Actor { - - import Stream.game2json - - val games = scala.collection.mutable.Map.empty[String, Stream.Game] - - private def makeMd5 = MessageDigest getInstance "MD5" - - private val loadCompleteJson = Json.obj("loadComplete" -> true) - - def receive = { - case SocketEvent.OwnerJoin(id, color, ip) => - ipCache get ip foreach { point => - val game = games get id match { - case Some(game) => game withPoint point - case None => Stream.Game(id, List(point)) - } - games += (id -> game) - channel push Stream.Event.Add(game) - } - case SocketEvent.Stop(id) => - games -= id - channel push Stream.Event.Remove(id) - case Stream.Get => sender ! { - Enumerator.enumerate(games.values.map(game2json(makeMd5))) andThen - Enumerator.enumerate(List(loadCompleteJson)) andThen - producer - } - } - - val (enumerator, channel) = Concurrent.broadcast[Stream.Event] - - val producer = enumerator &> Enumeratee.map[Stream.Event].apply[JsValue] { - case Stream.Event.Add(game) => game2json(makeMd5)(game) - case Stream.Event.Remove(id) => Json.obj("id" -> id) - } - - val ipCache = lila.memo.Builder.cache(geoIpCacheTtl, ipToPoint) - def ipToPoint(ip: String): Option[Stream.Point] = - geoIp getLocation ip flatMap Stream.toPoint -} - -object Stream { - - case object Get - - case class Game(id: String, points: List[Point]) { - - def withPoint(point: Point) = - if (points contains point) this - else copy(points = point :: points.take(1)) - } - - private def truncate(d: Double) = lila.common.Maths.truncateAt(d, 4) - - private val bytes2base64 = java.util.Base64.getEncoder.encodeToString _ - private def game2json(md5: MessageDigest)(game: Game): JsValue = Json.obj( - "id" -> bytes2base64(md5.digest(game.id getBytes "UTF-8") take 6), - "ps" -> Json.toJson { - game.points.map { p => - List(p.lat, p.lon) map truncate - } - } - ) - - case class Point(lat: Double, lon: Double) - def toPoint(ipLoc: IpLocation): Option[Point] = ipLoc.geoPoint map { p => - Point(p.latitude, p.longitude) - } - - sealed trait Event - object Event { - case class Add(game: Game) extends Event - case class Remove(id: String) extends Event - } -} diff --git a/modules/worldMap/src/main/package.scala b/modules/worldMap/src/main/package.scala deleted file mode 100644 index 35eac80541..0000000000 --- a/modules/worldMap/src/main/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package lila - -package object worldMap extends PackageObject with WithPlay diff --git a/project/Build.scala b/project/Build.scala index 8d636e618a..3867ca92ab 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -53,10 +53,10 @@ object ApplicationBuild extends Build { chess, common, db, rating, user, security, hub, socket, message, notifyModule, i18n, game, bookmark, search, gameSearch, timeline, forum, forumSearch, team, teamSearch, - analyse, mod, site, round, lobby, setup, + analyse, mod, site, round, pool, lobby, setup, importer, tournament, simul, relation, report, pref, // simulation, evaluation, chat, puzzle, tv, coordinate, blog, qa, - history, worldMap, video, shutup, push, + history, video, shutup, push, playban, insight, perfStat, slack, quote, challenge, study, studySearch, fishnet, explorer, learn, plan, event, coach) @@ -92,10 +92,6 @@ object ApplicationBuild extends Build { libraryDependencies ++= provided(play.api, reactivemongo.driver) ) - lazy val worldMap = project("worldMap", Seq(common, hub, memo, rating)).settings( - libraryDependencies ++= provided(play.api, maxmind) - ) - lazy val qa = project("qa", Seq(common, db, memo, user, security, notifyModule)).settings( libraryDependencies ++= provided(play.api, reactivemongo.driver) ) @@ -187,8 +183,13 @@ object ApplicationBuild extends Build { reactivemongo.driver, reactivemongo.iteratees) ) + lazy val pool = project("pool", Seq(common, game, user)).settings( + libraryDependencies ++= provided(play.api, reactivemongo.driver) + ) + lazy val lobby = project("lobby", Seq( - common, db, memo, hub, socket, chess, game, user, round, timeline, relation, playban, security)).settings( + common, db, memo, hub, socket, chess, game, user, + round, timeline, relation, playban, security, pool)).settings( libraryDependencies ++= provided(play.api, reactivemongo.driver) ) @@ -201,16 +202,19 @@ object ApplicationBuild extends Build { libraryDependencies ++= provided(play.api, reactivemongo.driver) ) - lazy val insight = project("insight", + lazy val insight = project( + "insight", Seq(common, chess, game, user, analyse, relation, pref, socket, round, security) ).settings( - libraryDependencies ++= provided(play.api, - reactivemongo.driver, reactivemongo.iteratees) + libraryDependencies ++= provided( + play.api, + reactivemongo.driver, reactivemongo.iteratees) ) lazy val tournament = project("tournament", Seq( common, hub, socket, chess, game, round, security, chat, memo, quote, history, notifyModule)).settings( - libraryDependencies ++= provided(play.api, + libraryDependencies ++= provided( + play.api, reactivemongo.driver, reactivemongo.iteratees) ) @@ -240,7 +244,8 @@ object ApplicationBuild extends Build { ) lazy val studySearch = project("studySearch", Seq(common, hub, study, search)).settings( - libraryDependencies ++= provided(play.api, + libraryDependencies ++= provided( + play.api, reactivemongo.driver, reactivemongo.iteratees) ) @@ -281,7 +286,8 @@ object ApplicationBuild extends Build { ) lazy val forumSearch = project("forumSearch", Seq(common, hub, forum, search)).settings( - libraryDependencies ++= provided(play.api, + libraryDependencies ++= provided( + play.api, reactivemongo.driver, reactivemongo.iteratees) ) @@ -290,7 +296,8 @@ object ApplicationBuild extends Build { ) lazy val teamSearch = project("teamSearch", Seq(common, hub, team, search)).settings( - libraryDependencies ++= provided(play.api, + libraryDependencies ++= provided( + play.api, reactivemongo.driver, reactivemongo.iteratees) ) @@ -315,7 +322,8 @@ object ApplicationBuild extends Build { ) lazy val explorer = project("explorer", Seq(common, db, game)).settings( - libraryDependencies ++= provided(play.api, + libraryDependencies ++= provided( + play.api, reactivemongo.driver, reactivemongo.iteratees) ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ba461145b8..b350b72b25 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -62,7 +62,7 @@ object Dependencies { val slf4j = "com.typesafe.akka" %% "akka-slf4j" % version } object kamon { - val version = "0.6.3" + val version = "0.6.4.2-LILA" val core = "io.kamon" %% "kamon-core" % version val statsd = "io.kamon" %% "kamon-statsd" % version val influxdb = "io.kamon" %% "kamon-influxdb" % version diff --git a/public/javascripts/forum-post.js b/public/javascripts/forum-post.js index c4e988864e..f60f263d40 100644 --- a/public/javascripts/forum-post.js +++ b/public/javascripts/forum-post.js @@ -1,4 +1,15 @@ $(function() { + + $('#lichess_forum').on('click', 'a.delete', function() { + $.post($(this).attr("href")); + $(this).closest(".post").slideUp(100); + return false; + }).on('click', 'form.unsub button', function() { + var $form = $(this).parent().toggleClass('on off'); + $.post($form.attr("action") + '?unsub=' + $(this).data('unsub')); + return false; + }); + $('.edit.button').add('.edit-post-cancel').click(function(e) { e.preventDefault(); diff --git a/public/javascripts/main.js b/public/javascripts/main.js index 0bdbdf6a37..521fa98aea 100644 --- a/public/javascripts/main.js +++ b/public/javascripts/main.js @@ -351,11 +351,10 @@ lichess.notifyApp = (function() { })(); $(function() { - if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse); + if (lichess.lobby) LichessLobby.legacy(document.getElementById('hooks_wrap'), lichess.lobby); + else if (lichess.analyse) startAnalyse(document.getElementById('lichess'), lichess.analyse); else if (lichess.user_analysis) startUserAnalysis(document.getElementById('lichess'), lichess.user_analysis); else if (lichess.study) startStudy(document.getElementById('lichess'), lichess.study); - else if (lichess.lobby) startLobby(document.getElementById('hooks_wrap'), lichess.lobby); - else if (lichess.puzzle) startPuzzle(lichess.puzzle); else if (lichess.tournament) startTournament(document.getElementById('tournament'), lichess.tournament); else if (lichess.simul) startSimul(document.getElementById('simul'), lichess.simul); @@ -918,11 +917,6 @@ lichess.notifyApp = (function() { return str; }; - $.urlToLink = function(text) { - var exp = /\bhttps?:\/\/(?:[a-z]{0,3}\.)?(lichess\.org[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; - return text.replace(exp, "$1"); - } - function startTournamentClock() { $("div.game_tournament div.clock").each(function() { $(this).clock({ @@ -1279,418 +1273,6 @@ lichess.notifyApp = (function() { }, 200); }); - function startLobby(element, cfg) { - var lobby; - var nbRoundSpread = $.spreadNumber( - document.querySelector('#nb_games_in_play span'), - 8, - function() { - return lichess.socket.pingInterval(); - }); - var nbUserSpread = $.spreadNumber( - document.querySelector('#nb_connected_players > strong'), - 10, - function() { - return lichess.socket.pingInterval(); - }); - var onFirstConnect = function() { - var gameId = lichess.getParameterByName('hook_like'); - if (!gameId) return; - $.post('/setup/hook/' + lichess.StrongSocket.sri + '/like/' + gameId); - lobby.setTab('real_time'); - window.history.replaceState(null, null, '/'); - }; - lichess.socket = lichess.StrongSocket( - '/lobby/socket/v2', - cfg.data.version, { - receive: function(t, d) { - lobby.socketReceive(t, d); - }, - events: { - n: function(nbUsers, msg) { - nbUserSpread(msg.d); - setTimeout(function() { - nbRoundSpread(msg.r); - }, lichess.socket.pingInterval() / 2); - }, - reload_timeline: function() { - $.ajax({ - url: $("#timeline").data('href'), - success: function(html) { - $('#timeline').html(html); - lichess.pubsub.emit('content_loaded')(); - } - }); - }, - streams: function(html) { - $('#streams_on_air').html(html); - }, - featured: function(o) { - $('#featured_game').html(o.html); - lichess.pubsub.emit('content_loaded')(); - }, - redirect: function(e) { - lobby.setRedirecting(); - $.redirect(e); - }, - tournaments: function(data) { - $("#enterable_tournaments").html(data); - lichess.pubsub.emit('content_loaded')(); - }, - simuls: function(data) { - $("#enterable_simuls").html(data).parent().toggle($('#enterable_simuls tr').length > 0); - lichess.pubsub.emit('content_loaded')(); - }, - reload_forum: function() { - var $newposts = $("div.new_posts"); - setTimeout(function() { - $.ajax({ - url: $newposts.data('url'), - success: function(data) { - $newposts.find('ol').html(data).end().scrollTop(0); - lichess.pubsub.emit('content_loaded')(); - } - }); - }, Math.round(Math.random() * 5000)); - }, - fen: function(e) { - lichess.StrongSocket.defaults.events.fen(e); - lobby.gameActivity(e.id); - } - }, - options: { - name: 'lobby', - onFirstConnect: onFirstConnect - } - }); - - cfg.socketSend = lichess.socket.send; - lobby = LichessLobby(element, cfg); - - var $startButtons = $('#start_buttons'); - - var sliderTimes = [ - 0, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 25, 30, 35, 40, 45, 60, 90, 120, 150, 180 - ]; - - function sliderTime(v) { - return v < sliderTimes.length ? sliderTimes[v] : 180; - } - - function showTime(v) { - if (v === 1 / 2) return '½'; - if (v === 3 / 4) return '¾'; - return v; - } - - function sliderIncrement(v) { - if (v <= 20) return v; - switch (v) { - case 21: - return 25; - case 22: - return 30; - case 23: - return 35; - case 24: - return 40; - case 25: - return 45; - case 26: - return 60; - case 27: - return 90; - case 28: - return 120; - case 29: - return 150; - default: - return 180; - } - } - - function sliderDays(v) { - if (v <= 3) return v; - switch (v) { - case 4: - return 5; - case 5: - return 7; - case 6: - return 10; - default: - return 14; - } - } - - function sliderInitVal(v, f, max) { - for (var i = 0; i < max; i++) { - if (f(i) === v) return i; - } - } - - function prepareForm() { - var $form = $('.lichess_overboard'); - var $timeModeSelect = $form.find('#timeMode'); - var $modeChoicesWrap = $form.find('.mode_choice'); - var $modeChoices = $modeChoicesWrap.find('input'); - var $casual = $modeChoices.eq(0), - $rated = $modeChoices.eq(1); - var $variantSelect = $form.find('#variant'); - var $fenPosition = $form.find(".fen_position"); - var $timeInput = $form.find('.time_choice input'); - var $incrementInput = $form.find('.increment_choice input'); - var $daysInput = $form.find('.days_choice input'); - var isHook = $form.hasClass('game_config_hook'); - var $ratings = $form.find('.ratings > div'); - var randomColorVariants = $form.data('random-color-variants').split(','); - var toggleButtons = function() { - var timeMode = $timeModeSelect.val(); - var rated = $rated.prop('checked'); - var timeOk = timeMode != '1' || $timeInput.val() > 0 || $incrementInput.val() > 0; - var ratedOk = !isHook || !rated || timeMode != '0'; - if (timeOk && ratedOk) { - $form.find('.color_submits button').toggleClass('nope', false); - $form.find('.color_submits button:not(.random)').toggle(!rated || randomColorVariants.indexOf($variantSelect.val()) === -1); - } else - $form.find('.color_submits button').toggleClass('nope', true); - }; - var showRating = function() { - var timeMode = $timeModeSelect.val(); - var key; - switch ($variantSelect.val()) { - case '1': - if (timeMode == '1') { - var time = $timeInput.val() * 60 + $incrementInput.val() * 40; - if (time < 180) key = 'bullet'; - else if (time < 480) key = 'blitz'; - else key = 'classical'; - } else key = 'correspondence'; - break; - case '10': - key = 'crazyhouse'; - break; - case '2': - key = 'chess960'; - break; - case '4': - key = 'kingOfTheHill'; - break; - case '5': - key = 'threeCheck'; - break; - case '6': - key = 'antichess' - break; - case '7': - key = 'atomic' - break; - case '8': - key = "horde" - break; - case '9': - key = "racingKings" - break; - } - $ratings.hide().filter('.' + key).show(); - }; - if (isHook) { - var $formTag = $form.find('form'); - if ($form.data('anon')) { - $timeModeSelect.val(1) - .children('.timeMode_2, .timeMode_0') - .prop('disabled', true) - .attr('title', $.trans('You need an account to do that')); - } - var ajaxSubmit = function(color) { - $.ajax({ - url: $formTag.attr('action').replace(/uid-placeholder/, lichess.StrongSocket.sri), - data: $formTag.serialize() + "&color=" + color, - type: 'post' - }); - $form.find('a.close').click(); - lobby.setTab($timeModeSelect.val() === '1' ? 'real_time' : 'seeks'); - return false; - }; - $formTag.find('.color_submits button').click(function() { - return ajaxSubmit($(this).val()); - }).attr('disabled', false); - $formTag.submit(function() { - return ajaxSubmit('random'); - }); - } else - $form.find('form').one('submit', function() { - $(this).find('.color_submits').find('button').hide().end().append(lichess.spinnerHtml); - }); - lichess.slider().done(function() { - $timeInput.add($incrementInput).each(function() { - var $input = $(this), - $value = $input.siblings('span'); - var isTimeSlider = $input.parent().hasClass('time_choice'); - $input.hide().after($('
').slider({ - value: sliderInitVal(parseFloat($input.val()), isTimeSlider ? sliderTime : sliderIncrement, 100), - min: 0, - max: isTimeSlider ? 33 : 30, - range: 'min', - step: 1, - slide: function(event, ui) { - var time = (isTimeSlider ? sliderTime : sliderIncrement)(ui.value); - $value.text(isTimeSlider ? showTime(time) : time); - $input.attr('value', time); - showRating(); - toggleButtons(); - } - })); - }); - $daysInput.each(function() { - var $input = $(this), - $value = $input.siblings('span'); - $input.hide().after($('
').slider({ - value: sliderInitVal(parseInt($input.val()), sliderDays, 20), - min: 1, - max: 7, - range: 'min', - step: 1, - slide: function(event, ui) { - var days = sliderDays(ui.value); - $value.text(days); - $input.attr('value', days); - } - })); - }); - $form.find('.rating_range').each(function() { - var $this = $(this); - var $input = $this.find("input"); - var $span = $this.siblings("span.range"); - var min = $input.data("min"); - var max = $input.data("max"); - var values = $input.val() ? $input.val().split("-") : [min, max]; - - $span.text(values.join(' - ')); - $this.slider({ - range: true, - min: min, - max: max, - values: values, - step: 50, - slide: function(event, ui) { - $input.val(ui.values[0] + "-" + ui.values[1]); - $span.text(ui.values[0] + " - " + ui.values[1]); - } - }); - }); - }); - $modeChoices.add($form.find('.members_only input')).on('change', function() { - var rated = $rated.prop('checked'); - var membersOnly = $form.find('.members_only input').prop('checked'); - $form.find('.rating_range_config').toggle(rated || membersOnly); - $form.find('.members_only').toggle(!rated); - toggleButtons(); - }).trigger('change'); - $timeModeSelect.on('change', function() { - var timeMode = $(this).val(); - $form.find('.time_choice, .increment_choice').toggle(timeMode == '1'); - $form.find('.days_choice').toggle(timeMode == '2'); - toggleButtons(); - showRating(); - }).trigger('change'); - - var $fenInput = $fenPosition.find('input'); - var validateFen = $.fp.debounce(function() { - $fenInput.removeClass("success failure"); - var fen = $fenInput.val(); - if (fen) { - $.ajax({ - url: $fenInput.parent().data('validate-url'), - data: { - fen: fen - }, - success: function(data) { - $fenInput.addClass("success"); - $fenPosition.find('.preview').html(data); - $fenPosition.find('a.board_editor').each(function() { - $(this).attr('href', $(this).attr('href').replace(/editor\/.+$/, "editor/" + fen)); - }); - $form.find('.color_submits button').removeClass('nope'); - lichess.pubsub.emit('content_loaded')(); - }, - error: function() { - $fenInput.addClass("failure"); - $fenPosition.find('.preview').html(""); - $form.find('.color_submits button').addClass('nope'); - } - }); - } - }, 200); - $fenInput.on('keyup', validateFen); - - $variantSelect.on('change', function() { - var fen = $(this).val() == '3'; - $fenPosition.toggle(fen); - $modeChoicesWrap.toggle(!fen); - if (fen) { - $casual.click(); - document.body.dispatchEvent(new Event('chessground.resize')); - } - showRating(); - toggleButtons(); - }).trigger('change'); - - $form.find('div.level').each(function() { - var $infos = $(this).find('.ai_info > div'); - $(this).find('label').mouseenter(function() { - $infos.hide().filter('.' + $(this).attr('for')).show(); - }); - $(this).find('#config_level').mouseleave(function() { - var level = $(this).find('input:checked').val(); - $infos.hide().filter('.level_' + level).show(); - }).trigger('mouseout'); - }); - - $form.find('a.close.icon').click(function() { - $form.remove(); - $startButtons.find('a.active').removeClass('active'); - return false; - }); - } - - $startButtons.find('a').not('.disabled').on('mousedown', function() { - $.ajax({ - url: $(this).attr('href'), - success: function(html) { - $('.lichess_overboard').remove(); - $('#hooks_wrap').prepend(html); - prepareForm(); - lichess.pubsub.emit('content_loaded')(); - }, - error: function() { - lichess.reload(); - } - }); - $(this).addClass('active').siblings().removeClass('active'); - $('.lichess_overboard').remove(); - return false; - }); - - if (['#ai', '#friend', '#hook'].indexOf(location.hash) !== -1) { - $startButtons - .find('a.config_' + location.hash.replace('#', '')) - .each(function() { - $(this).attr("href", $(this).attr("href") + location.search); - }).trigger('mousedown'); - - if (location.hash === '#hook') { - if (/time=realTime/.test(location.search)) - lobby.setTab('real_time'); - else if (/time=correspondence/.test(location.search)) - lobby.setTab('seeks'); - } - - window.history.replaceState(null, null, '/'); - } - }; - /////////////////// // tournament.js // /////////////////// @@ -2002,40 +1584,4 @@ lichess.notifyApp = (function() { history.pushState('', document.title, location.pathname); } } - - //////////////// - // puzzle.js // - //////////////// - - function startPuzzle(cfg) { - var puzzle; - cfg.element = document.querySelector('#puzzle'); - cfg.sideElement = document.querySelector('#site_header .side_box'); - lichess.socket = lichess.StrongSocket('/socket', 0, { - options: { - name: "puzzle" - }, - params: { - ran: "--ranph--" - }, - receive: function(t, d) { - puzzle.socketReceive(t, d); - } - }); - cfg.socketSend = lichess.socket.send; - puzzle = LichessPuzzle(cfg); - topMenuIntent(); - } - - /////////////// forum.js //////////////////// - - $('#lichess_forum').on('click', 'a.delete', function() { - $.post($(this).attr("href")); - $(this).closest(".post").slideUp(100); - return false; - }).on('click', 'form.unsub button', function() { - var $form = $(this).parent().toggleClass('on off'); - $.post($form.attr("action") + '?unsub=' + $(this).data('unsub')); - return false; - }); })(); diff --git a/public/javascripts/socket.js b/public/javascripts/socket.js index c77cb267ab..b8660cfcd0 100644 --- a/public/javascripts/socket.js +++ b/public/javascripts/socket.js @@ -72,8 +72,8 @@ lichess.StrongSocket = function(url, version, settings) { scheduleConnect(options.pingMaxLag); }; - var send = function(t, d, o, again) { - var data = d || {}, + var send = function(t, d, o, noRetry) { + var data = (d == null) ? {} : d, options = o || {}; if (options.withLag) d.l = Math.round(averageLag); if (options.ackable) ackableMessages.push({ @@ -90,7 +90,7 @@ lichess.StrongSocket = function(url, version, settings) { } catch (e) { // maybe sent before socket opens, // try again a second later,once. - if (!again) setTimeout(function() { + if (!noRetry) setTimeout(function() { send(t, d, o, true); }, 1000); } @@ -104,7 +104,7 @@ lichess.StrongSocket = function(url, version, settings) { }; var scheduleConnect = function(delay) { - if (options.idle) delay = delay * 3; + if (options.idle) delay = 10 * 1000 + Math.random() * 10 * 1000; // debug('schedule connect ' + delay); clearTimeout(pingSchedule); clearTimeout(connectSchedule); diff --git a/public/stylesheets/dark.css b/public/stylesheets/dark.css index 1f0bdfd8b3..d4acfa135d 100644 --- a/public/stylesheets/dark.css +++ b/public/stylesheets/dark.css @@ -272,6 +272,7 @@ body.dark #hooks_wrap .table_wrap tr.cancel td { body.dark #site_header div.side_menu a:hover, body.dark div.doc_box a.variant:hover, body.dark #hooks_wrap .table_wrap tr.join:hover td, +body.dark #hooks_wrap .pools > div:hover, body.dark #now_playing > a:hover, body.dark .explorer_box tr:hover, body.dark .pv_box .pv[data-uci]:hover, diff --git a/public/stylesheets/home.css b/public/stylesheets/home.css index cd1bdb5d35..78130bb5c3 100644 --- a/public/stylesheets/home.css +++ b/public/stylesheets/home.css @@ -37,9 +37,12 @@ div.lobby_and_ground { #lobby_playban h2, #lobby_current_game h2 { font-size: 1.5em; - line-height: 3; + line-height: 2.5em; margin-top: 1em; } +#lobby_playban a { + color: #3893E8; +} .current_game #hooks_wrap { visibility: hidden; } @@ -64,6 +67,12 @@ body.dark #hooks_wrap { #hooks_wrap .lobby_box.seeks { max-height: 510px; } +#hooks_wrap .lobby_box .no_hooks { + text-align: center; + line-height: 2em; + margin-top: 80px; + font-size: 1.3em; +} #hooks_wrap .tabs { position: absolute; top: -23px; @@ -92,6 +101,9 @@ body.dark #hooks_wrap { border-top: 2px solid #d85000; margin-top: -1px; } +#hooks_wrap .tabs a.glow { + animation: fbt-glowing 1s ease-in-out infinite; +} #hooks_wrap .tabs .unread { margin-left: 5px; } @@ -374,6 +386,68 @@ body.dark #hooks_wrap .hook_filter { font-size: 30px; margin-right: 10px; } +/*********** POOLS *********/ + +#hooks_wrap .pools { + padding: 12px 12px 0 12px; + max-height: 510px; + box-sizing: border-box; + display: flex; + flex-flow: row wrap; + justify-content: space-between; + font-family: Roboto; + -webkit-user-select: none; +} +#hooks_wrap .pools > div { + margin-bottom: 12px; + flex: 0 0 31%; + height: 152px; + cursor: pointer; + background: rgba(255,255,255,0.7); + border: 1px solid #ccc; + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + border-radius: 4px; + transition: opacity 0.7s; +} +body.dark #hooks_wrap .pools > div { + background: rgba(20,20,20,0.5); + border: 1px solid #3d3d3d; +} +#hooks_wrap .pools .active { + background: #fff; + border-radius: 0; + box-shadow: 0 2px 5px rgba(0,0,0,0.3); +} +body.dark #hooks_wrap .pools .active { + background: #202020; + box-shadow-color: #000; +} +#hooks_wrap .pools .transp { + opacity: 0.5; +} +#hooks_wrap .pools .spinner { + margin: 14px 0 0 0; +} +#hooks_wrap .pools .clock { + display: block; + font-size: 2.5em; + line-height: 1.5em; + letter-spacing: 5px; +} +#hooks_wrap .pools .perf, +#hooks_wrap .pools .range { + font-size: 1.5em; +} +#hooks_wrap .pools .custom { + font-size: 1.5em; +} +#hooks_wrap .pools > div:hover { + background: rgba(191, 231, 255, 0.7); +} + /*********** MISC *********/ #site_header .board_left { diff --git a/public/vendor/stockfish.js b/public/vendor/stockfish.js index c970bfb079..474fe245d8 160000 --- a/public/vendor/stockfish.js +++ b/public/vendor/stockfish.js @@ -1 +1 @@ -Subproject commit c970bfb07955d508a0bee38a5fcee5c32f381c89 +Subproject commit 474fe245d87c6fd8fc202a32118f4917724da6b1 diff --git a/public/vendor/stockfish.pexe b/public/vendor/stockfish.pexe index b9e40a1241..619efd54c0 160000 --- a/public/vendor/stockfish.pexe +++ b/public/vendor/stockfish.pexe @@ -1 +1 @@ -Subproject commit b9e40a124197e33f870dbf3cb4033599e0aa6274 +Subproject commit 619efd54c02bcc02dadc468c1fde5c6d745eb73a diff --git a/public/worldMap/app.js b/public/worldMap/app.js deleted file mode 100644 index b8b275b03a..0000000000 --- a/public/worldMap/app.js +++ /dev/null @@ -1,119 +0,0 @@ -$(function() { - var loading = true, - worldWith = window.innerWidth - 30, - mapRatio = 0.4, - worldHeight = worldWith * mapRatio, - scale = worldWith / 1000; - var paper = Raphael(document.getElementById("worldmap"), worldWith, worldHeight); - paper.rect(0, 0, worldWith, worldHeight, 10).attr({ - stroke: "none" - }); - paper.setStart(); - worldShapes.forEach(function(shape) { - paper.path(shape).attr({ - 'stroke': '#05121b', - 'stroke-width': 0.5, - 'stroke-opacity': 0.25, - fill: "#1d2b33" - }).transform("s" + scale + "," + scale + " 0,0"); - }); - var world = paper.setFinish(); - world.getXY = function(lat, lon) { - return { - cx: lon * (2.6938 * scale) + (465.4 * scale), - cy: lat * (-2.6938 * scale) + (227.066 * scale) - }; - }; - - var randomPoint = function() { - return [ - Math.random() * 100 - 50, - Math.random() * 200 - 100 - ]; - }; - - var point2pos = function(point) { - // point = randomPoint(); - return world.getXY( - point[0] + Math.random() - 0.5, - point[1] + Math.random() - 0.5); - }; - - var source = new EventSource("//en.lichess.org/network/stream"); - - var removeFunctions = {}; - - var appearPoint = function(pos) { - var appear = paper.circle().attr({ - opacity: 0.4, - fill: "#fff", - r: 3, - 'stroke-width': 0 - }).attr(pos); - setTimeout(function() { - appear.remove(); - }, 130); - } - - var drawPoint = function(pos) { - var dot = paper.circle().attr({ - opacity: 0.5, - fill: "#fff", - stroke: "#FE7727", - r: 1.5, - 'stroke-width': 1 - }).attr(pos); - if (!loading) appearPoint(pos); - return function() { - dot.remove(); - appearPoint(pos); - }; - }; - - var appearLine = function(pos) { - var appear = paper.path( - "M" + pos[0].cx + "," + pos[0].cy + "T" + pos[1].cx + "," + pos[1].cy - ).attr({ - opacity: 0.25, - stroke: "#fff", - 'stroke-width': 1 - }); - setTimeout(function() { - appear.remove(); - }, 130); - } - - var drawLine = function(pos) { - var line = paper.path( - "M" + pos[0].cx + "," + pos[0].cy + "T" + pos[1].cx + "," + pos[1].cy - ).attr({ - opacity: 0.12, - stroke: "#FE7727", - 'stroke-width': 1 - }); - if (!loading) appearLine(pos); - return function() { - line.remove(); - appearLine(pos); - }; - } - - source.addEventListener('message', function(e) { - var data = JSON.parse(e.data); - if (data.loadComplete) loading = false; - if (removeFunctions[data.id]) { - removeFunctions[data.id].forEach(function(f) { - f(); - }); - delete removeFunctions[data.id]; - } - if (!data.ps) return; - var pos = data.ps.map(point2pos); - removeFunctions[data.id] = pos.map(drawPoint); - if (data.ps[1]) removeFunctions[data.id].push(drawLine(pos)); - }, false); - - setTimeout(function() { - loading = false; - }, 10000); -}); diff --git a/public/worldMap/bluegrid.jpg b/public/worldMap/bluegrid.jpg deleted file mode 100644 index 6e235a10d5..0000000000 Binary files a/public/worldMap/bluegrid.jpg and /dev/null differ diff --git a/public/worldMap/main.css b/public/worldMap/main.css deleted file mode 100644 index b32c04e255..0000000000 --- a/public/worldMap/main.css +++ /dev/null @@ -1,48 +0,0 @@ -body { - /* based on lichess dark theme */ - font: 12px 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, Sans-Serif; - color: #909090; - background: #05121b; /* url("bluegrid.jpg") center repeat; */ -} - -h1 { - text-align: center; - font-family: 'Trebuchet MS', 'Helvetica Neue', Arial, Sans-Serif; - font-size: 30px; - font-weight: normal; - margin-bottom: 50px; -} - -h1 a { - color: inherit; - text-decoration: none; -} - -a { - color: inherit; -} - -.extension { - color: #666; -} - -#legend { - position: absolute; - bottom: 20px; - left: 20px; - padding: 20px; - background: #19252c; - border-radius: 3px; - line-height: 30px; -} -#legend h1 { - font-size: 1.4em; - font-weight: bold; - margin: 0 0 10px 0; -} - -#sound { - position: absolute; - top: 10px; - right: 10px; -} diff --git a/public/worldMap/raphael-min.js b/public/worldMap/raphael-min.js deleted file mode 100644 index 404f8b24b3..0000000000 --- a/public/worldMap/raphael-min.js +++ /dev/null @@ -1,11 +0,0 @@ -// ┌────────────────────────────────────────────────────────────────────┐ \\ -// │ Raphaël 2.1.2 - JavaScript Vector Library │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\ -// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\ -// ├────────────────────────────────────────────────────────────────────┤ \\ -// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\ -// └────────────────────────────────────────────────────────────────────┘ \\ -!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g="*",h=function(){},i=function(a,b){return a-b},j={n:{}},k=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=k.listeners(a),j=0,l=[],m={},n=[],o=b;b=a,c=0;for(var p=0,q=h.length;q>p;p++)"zIndex"in h[p]&&(l.push(h[p].zIndex),h[p].zIndex<0&&(m[h[p].zIndex]=h[p]));for(l.sort(i);l[j]<0;)if(e=m[l[j++]],n.push(e.apply(d,g)),c)return c=f,n;for(p=0;q>p;p++)if(e=h[p],"zIndex"in e)if(e.zIndex==l[j]){if(n.push(e.apply(d,g)),c)break;do if(j++,e=m[l[j]],e&&n.push(e.apply(d,g)),c)break;while(e)}else m[e.zIndex]=e;else if(n.push(e.apply(d,g)),c)break;return c=f,b=o,n.length?n:null};k._events=j,k.listeners=function(a){var b,c,d,e,h,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,h=m.length;h>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[g]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},k.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(f),d=j,e=0,g=c.length;g>e;e++)d=d.n,d=d.hasOwnProperty(c[e])&&d[c[e]]||(d[c[e]]={n:{}});for(d.f=d.f||[],e=0,g=d.f.length;g>e;e++)if(d.f[e]==b)return h;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},k.f=function(a){var b=[].slice.call(arguments,1);return function(){k.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},k.stop=function(){c=1},k.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},k.nts=function(){return b.split(f)},k.off=k.unbind=function(a,b){if(!a)return k._events=j={n:{}},void 0;var c,d,h,i,l,m,n,o=a.split(f),p=[j];for(i=0,l=o.length;l>i;i++)for(m=0;mi;i++)for(c=p[i];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)if(c.f[m]==b){c.f.splice(m,1);break}!c.f.length&&delete c.f}for(d in c.n)if(c.n[e](d)&&c.n[d].f){var q=c.n[d].f;for(m=0,n=q.length;n>m;m++)if(q[m]==b){q.splice(m,1);break}!q.length&&delete c.n[d].f}}else{delete c.f;for(d in c.n)c.n[e](d)&&c.n[d].f&&delete c.n[d].f}c=c.n}},k.once=function(a,b){var c=function(){return k.unbind(a,c),b.apply(this,arguments)};return k.on(a,c)},k.version=d,k.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=k:"undefined"!=typeof define?define("eve",[],function(){return k}):a.eve=k}(this),function(a,b){"function"==typeof define&&define.amd?define(["eve"],function(c){return b(a,c)}):b(a,a.eve)}(this,function(a,b){function c(a){if(c.is(a,"function"))return u?a():b.on("raphael.DOMload",a);if(c.is(a,V))return c._engine.create[D](c,a.splice(0,3+c.is(a[0],T))).add(a);var d=Array.prototype.slice.call(arguments,0);if(c.is(d[d.length-1],"function")){var e=d.pop();return u?e.call(c._engine.create[D](c,d)):b.on("raphael.DOMload",function(){e.call(c._engine.create[D](c,d))})}return c._engine.create[D](c,arguments)}function d(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=d(a[c]));return b}function e(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function f(a,b,c){function d(){var f=Array.prototype.slice.call(arguments,0),g=f.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];return h[z](g)?(e(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[D](b,f),c?c(h[g]):h[g])}return d}function g(){return this.hex}function h(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function i(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function j(a,b,c,d,e,f,g,h,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=i(q,a,c,e,g),s=i(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return k*o}function k(a,b,c,d,e,f,g,h,i){if(!(0>i||j(a,b,c,d,e,f,g,h)o;)m/=2,n+=(i>k?1:-1)*m,k=j(a,b,c,d,e,f,g,h,n);return n}}function l(a,b,c,d,e,f,g,h){if(!(O(a,c)O(e,g)||O(b,d)O(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+O(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+O(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+O(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+O(f,h).toFixed(2)))return{x:l,y:m}}}}function m(a,b,d){var e=c.bezierBBox(a),f=c.bezierBBox(b);if(!c.isBBoxIntersect(e,f))return d?0:[];for(var g=j.apply(0,a),h=j.apply(0,b),i=O(~~(g/5),1),k=O(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;i+1>q;q++){var r=c.findDotsAtSegment.apply(c,a.concat(q/i));m.push({x:r.x,y:r.y,t:q/i})}for(q=0;k+1>q;q++)r=c.findDotsAtSegment.apply(c,b.concat(q/k)),n.push({x:r.x,y:r.y,t:q/k});for(q=0;i>q;q++)for(var s=0;k>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=Q(u.x-t.x)<.001?"y":"x",y=Q(w.x-v.x)<.001?"y":"x",z=l(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+Q((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+Q((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:P(A,1),t2:P(B,1)}))}}return p}function n(a,b,d){a=c._path2curve(a),b=c._path2curve(b);for(var e,f,g,h,i,j,k,l,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=b.length;u>t;t++){var v=b[t];if("M"==v[0])g=k=v[1],h=l=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,l,k,l],g=k,h=l);var w=m(n,o,d);if(d)p+=w;else{for(var x=0,y=w.length;y>x;x++)w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}return p}function o(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function p(){return this.x+H+this.y+H+this.width+" × "+this.height}function q(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,Q(f)e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),Q(f-a)f?c=e:d=e,e=(d-c)/2+c}return e}var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}function r(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)a[z](e)&&(d[_(e)]=a[e],c.push(_(e)));c.sort(lb)}this.anim=d,this.top=c[c.length-1],this.percents=c}function s(a,d,e,f,g,h){e=_(e);var i,j,k,l,m,n,p=a.ms,r={},s={},t={};if(f)for(v=0,x=ic.length;x>v;v++){var u=ic[v];if(u.el.id==d.id&&u.anim==a){u.percent!=e?(ic.splice(v,1),k=1):j=u,d.attr(u.totalOrigin);break}}else f=+s;for(var v=0,x=a.percents.length;x>v;v++){if(a.percents[v]==e||a.percents[v]>f*a.top){e=a.percents[v],m=a.percents[v-1]||0,p=p/a.top*(e-m),l=a.percents[v+1],i=a.anim[e];break}f&&d.attr(a.anim[a.percents[v]])}if(i){if(j)j.initstatus=f,j.start=new Date-j.ms*f;else{for(var y in i)if(i[z](y)&&(db[z](y)||d.paper.customAttributes[z](y)))switch(r[y]=d.attr(y),null==r[y]&&(r[y]=cb[y]),s[y]=i[y],db[y]){case T:t[y]=(s[y]-r[y])/p;break;case"colour":r[y]=c.getRGB(r[y]);var A=c.getRGB(s[y]);t[y]={r:(A.r-r[y].r)/p,g:(A.g-r[y].g)/p,b:(A.b-r[y].b)/p};break;case"path":var B=Kb(r[y],s[y]),C=B[1];for(r[y]=B[0],t[y]=[],v=0,x=r[y].length;x>v;v++){t[y][v]=[0];for(var D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(C[v][D]-r[y][v][D])/p}break;case"transform":var G=d._,H=Pb(G[y],s[y]);if(H)for(r[y]=H.from,s[y]=H.to,t[y]=[],t[y].real=!0,v=0,x=r[y].length;x>v;v++)for(t[y][v]=[r[y][v][0]],D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(s[y][v][D]-r[y][v][D])/p;else{var K=d.matrix||new o,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[y]=[K.a,K.b,K.c,K.d,K.e,K.f],Nb(L,s[y]),s[y]=L._.transform,t[y]=[(L.matrix.a-K.a)/p,(L.matrix.b-K.b)/p,(L.matrix.c-K.c)/p,(L.matrix.d-K.d)/p,(L.matrix.e-K.e)/p,(L.matrix.f-K.f)/p]}break;case"csv":var M=I(i[y])[J](w),N=I(r[y])[J](w);if("clip-rect"==y)for(r[y]=N,t[y]=[],v=N.length;v--;)t[y][v]=(M[v]-r[y][v])/p;s[y]=M;break;default:for(M=[][E](i[y]),N=[][E](r[y]),t[y]=[],v=d.paper.customAttributes[y].length;v--;)t[y][v]=((M[v]||0)-(N[v]||0))/p}var O=i.easing,P=c.easing_formulas[O];if(!P)if(P=I(O).match(Z),P&&5==P.length){var Q=P;P=function(a){return q(a,+Q[1],+Q[2],+Q[3],+Q[4],p)}}else P=nb;if(n=i.start||a.start||+new Date,u={anim:a,percent:e,timestamp:n,start:n+(a.del||0),status:0,initstatus:f||0,stop:!1,ms:p,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||a.times,origin:d.attr(),totalOrigin:g},ic.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-p*f,1==ic.length))return kc();k&&(u.start=new Date-u.ms*f),1==ic.length&&jc(kc)}b("raphael.anim.start."+d.id,d,a)}}function t(a){for(var b=0;be;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(c._g=A,c.type=A.win.SVGAngle||A.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==c.type){var sb,tb=A.doc.createElement("div");if(tb.innerHTML='',sb=tb.firstChild,sb.style.behavior="url(#default#VML)",!sb||"object"!=typeof sb.adj)return c.type=G;tb=null}c.svg=!(c.vml="VML"==c.type),c._Paper=C,c.fn=v=C.prototype=c.prototype,c._id=0,c._oid=0,c.is=function(a,b){return b=M.call(b),"finite"==b?!Y[z](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||W.call(a).slice(8,-1).toLowerCase()==b},c.angle=function(a,b,d,e,f,g){if(null==f){var h=a-d,i=b-e;return h||i?(180+180*N.atan2(-i,-h)/S+360)%360:0}return c.angle(a,b,f,g)-c.angle(d,e,f,g)},c.rad=function(a){return a%360*S/180},c.deg=function(a){return 180*a/S%360},c.snapTo=function(a,b,d){if(d=c.is(d,"finite")?d:10,c.is(a,V)){for(var e=a.length;e--;)if(Q(a[e]-b)<=d)return a[e]}else{a=+a;var f=b%a;if(d>f)return b-f;if(f>a-d)return b-f+a}return b},c.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=0|16*N.random(),c="x"==a?b:8|3&b;return c.toString(16)}),c.setWindow=function(a){b("raphael.setWindow",c,A.win,a),A.win=a,A.doc=A.win.document,c._engine.initWin&&c._engine.initWin(A.win)};var ub=function(a){if(c.vml){var b,d=/^\s+|\s+$/g;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),b=e.body}catch(g){b=createPopup().document.body}var h=b.createTextRange();ub=f(function(a){try{b.style.color=I(a).replace(d,G);var c=h.queryCommandValue("ForeColor");return c=(255&c)<<16|65280&c|(16711680&c)>>>16,"#"+("000000"+c.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=A.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",A.doc.body.appendChild(i),ub=f(function(a){return i.style.color=a,A.doc.defaultView.getComputedStyle(i,G).getPropertyValue("color")})}return ub(a)},vb=function(){return"hsb("+[this.h,this.s,this.b]+")"},wb=function(){return"hsl("+[this.h,this.s,this.l]+")"},xb=function(){return this.hex},yb=function(a,b,d){if(null==b&&c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&c.is(a,U)){var e=c.getRGB(a);a=e.r,b=e.g,d=e.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},zb=function(a,b,d,e){a*=255,b*=255,d*=255;var f={r:a,g:b,b:d,hex:c.rgb(a,b,d),toString:xb};return c.is(e,"finite")&&(f.opacity=e),f};c.color=function(a){var b;return c.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):c.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):(c.is(a,"string")&&(a=c.getRGB(a)),c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=xb,a},c.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Q(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-Q(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.rgb2hsb=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=O(a,b,c),g=f-P(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=60*((d+360)%6)/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:vb}},c.rgb2hsl=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=O(a,b,c),h=P(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=60*((d+360)%6)/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:wb}},c._path2string=function(){return this.join(",").replace(gb,"$1")},c._preload=function(a,b){var c=A.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,A.doc.body.removeChild(this)},c.onerror=function(){A.doc.body.removeChild(this)},A.doc.body.appendChild(c),c.src=a},c.getRGB=f(function(a){if(!a||(a=I(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:g};!(fb[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=ub(a));var b,d,e,f,h,i,j=a.match(X);return j?(j[2]&&(e=ab(j[2].substring(5),16),d=ab(j[2].substring(3,5),16),b=ab(j[2].substring(1,3),16)),j[3]&&(e=ab((h=j[3].charAt(3))+h,16),d=ab((h=j[3].charAt(2))+h,16),b=ab((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100)),j[5]?(i=j[5][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsb2rgb(b,d,e,f)):j[6]?(i=j[6][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsl2rgb(b,d,e,f)):(j={r:b,g:d,b:e,toString:g},j.hex="#"+(16777216|e|d<<8|b<<16).toString(16).slice(1),c.is(f,"finite")&&(j.opacity=f),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g}},c),c.hsb=f(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=f(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=f(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),c.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},c.getColor.reset=function(){delete this.start},c.parsePathString=function(a){if(!a)return null;var b=Ab(a);if(b.arr)return Cb(b.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return c.is(a,V)&&c.is(a[0],V)&&(e=Cb(a)),e.length||I(a).replace(hb,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(jb,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][E](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)e.push([b][E](f));else for(;f.length>=d[g]&&(e.push([b][E](f.splice(0,d[g]))),d[g]););}),e.toString=c._path2string,b.arr=Cb(e),e},c.parseTransformString=f(function(a){if(!a)return null;var b=[];return c.is(a,V)&&c.is(a[0],V)&&(b=Cb(a)),b.length||I(a).replace(ib,function(a,c,d){var e=[];M.call(c),d.replace(jb,function(a,b){b&&e.push(+b)}),b.push([c][E](e))}),b.toString=c._path2string,b});var Ab=function(a){var b=Ab.ps=Ab.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[z](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};c.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/S;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},c.bezierBBox=function(a,b,d,e,f,g,h,i){c.is(a,"array")||(a=[a,b,d,e,f,g,h,i]);var j=Jb.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},c.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},c.isBBoxIntersect=function(a,b){var d=c.isPointInsideBBox;return d(b,a.x,a.y)||d(b,a.x2,a.y)||d(b,a.x,a.y2)||d(b,a.x2,a.y2)||d(a,b.x,b.y)||d(a,b.x2,b.y)||d(a,b.x,b.y2)||d(a,b.x2,b.y2)||(a.xb.x||b.xa.x)&&(a.yb.y||b.ya.y)},c.pathIntersection=function(a,b){return n(a,b)},c.pathIntersectionNumber=function(a,b){return n(a,b,1)},c.isPointInsidePath=function(a,b,d){var e=c.pathBBox(a);return c.isPointInsideBBox(e,b,d)&&1==n(a,[["M",b,d],["H",e.x2+10]],1)%2},c._removedFactory=function(a){return function(){b("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var Bb=c.pathBBox=function(a){var b=Ab(a);if(b.bbox)return d(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Kb(a);for(var c,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)if(c=a[i],"M"==c[0])e=c[1],f=c[2],g.push(e),h.push(f);else{var k=Jb(e,f,c[1],c[2],c[3],c[4],c[5],c[6]);g=g[E](k.min.x,k.max.x),h=h[E](k.min.y,k.max.y),e=c[5],f=c[6]}var l=P[D](0,g),m=P[D](0,h),n=O[D](0,g),o=O[D](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=d(r),r},Cb=function(a){var b=d(a);return b.toString=c._path2string,b},Db=c._pathToRelative=function(a){var b=Ab(a);if(b.rel)return Cb(b.rel);c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=M.call(m[0]))switch(l[0]=M.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}return d.toString=c._path2string,b.rel=Cb(d),d},Eb=c._pathToAbsolute=function(a){var b=Ab(a);if(b.abs)return Cb(b.abs);if(c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a)),!a||!a.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],g=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=bb.call(l[0]))switch(k[0]=bb.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][E](l.slice(1)),q=2,r=p.length;r>q;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[E](h(p,m));break;case"M":g=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)k[q]=+l[q]+(q%2?e:f)}else if("R"==l[0])p=[e,f][E](l.slice(1)),d.pop(),d=d[E](h(p,m)),k=["R"][E](l.slice(-2));else for(var s=0,t=l.length;t>s;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}return d.toString=c._path2string,b.abs=Cb(d),d},Fb=function(a,b,c,d){return[a,b,c,d,c,d]},Gb=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Hb=function(a,b,c,d,e,g,h,i,j,k){var l,m=120*S/180,n=S/180*(+e||0),o=[],p=f(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(S/180*e),N.sin(S/180*e),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=N.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*N.sqrt(Q((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v*-d*q/c+(b+j)/2,y=N.asin(((b-x)/d).toFixed(9)),z=N.asin(((j-x)/d).toFixed(9));y=w>a?S-y:y,z=w>i?S-z:z,0>y&&(y=2*S+y),0>z&&(z=2*S+z),h&&y>z&&(y-=2*S),!h&&z>y&&(z-=2*S)}var A=z-y;if(Q(A)>m){var B=z,C=i,D=j;z=y+m*(h&&z>y?1:-1),i=w+c*N.cos(z),j=x+d*N.sin(z),o=Hb(i,j,c,d,e,0,h,C,D,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),K=N.tan(A/4),L=4/3*c*K,M=4/3*d*K,O=[a,b],P=[a+L*G,b-M*F],R=[i+L*I,j-M*H],T=[i,j];if(P[0]=2*O[0]-P[0],P[1]=2*O[1]-P[1],k)return[P,R,T][E](o);o=[P,R,T][E](o).join()[J](",");for(var U=[],V=0,W=o.length;W>V;V++)U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ib=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:R(j,3)*a+3*R(j,2)*i*c+3*j*i*i*e+R(i,3)*g,y:R(j,3)*b+3*R(j,2)*i*d+3*j*i*i*f+R(i,3)*h}},Jb=f(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:P[D](0,p),y:P[D](0,o)},max:{x:O[D](0,p),y:O[D](0,o)}}}),Kb=c._path2curve=f(function(a,b){var c=!b&&Ab(a);if(!b&&c.curve)return Cb(c.curve);for(var d=Eb(a),e=b&&Eb(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][E](Hb[D](0,[b.x,b.y][E](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][E](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][E](Gb(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][E](Gb(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][E](Fb(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][E](Fb(b.x,b.y,a[1],b.y));break;case"V":a=["C"][E](Fb(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][E](Fb(b.x,b.y,b.X,b.Y))}return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)a.splice(b++,0,["C"][E](c.splice(0,6)));a.splice(b,1),l=O(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=O(d.length,e&&e.length||0))},k=0,l=O(d.length,e&&e.length||0);l>k;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var m=d[k],n=e&&e[k],o=m.length,p=e&&n.length;f.x=m[o-2],f.y=m[o-1],f.bx=_(m[o-4])||f.x,f.by=_(m[o-3])||f.y,g.bx=e&&(_(n[p-4])||g.x),g.by=e&&(_(n[p-3])||g.y),g.x=e&&n[p-2],g.y=e&&n[p-1]}return e||(c.curve=Cb(d)),e?[d,e]:d},null,Cb),Lb=(c._parseDots=f(function(a){for(var b=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=c.getRGB(g[1]),f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),b.push(f)}for(d=1,e=b.length-1;e>d;d++)if(!b[d].offset){for(var h=_(b[d-1].offset||0),i=0,j=d+1;e>j;j++)if(b[j].offset){i=b[j].offset;break}i||(i=100,j=e),i=_(i);for(var k=(i-h)/(j-d+1);j>d;d++)h+=k,b[d].offset=h+"%"}return b}),c._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Mb=(c._tofront=function(a,b){b.top!==a&&(Lb(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},c._toback=function(a,b){b.bottom!==a&&(Lb(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},c._insertafter=function(a,b,c){Lb(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},c._insertbefore=function(a,b,c){Lb(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},c.toMatrix=function(a,b){var c=Bb(a),d={_:{transform:G},getBBox:function(){return c}};return Nb(d,b),d.matrix}),Nb=(c.transformPath=function(a,b){return rb(a,Mb(a,b))},c._extractTransform=function(a,b){if(null==b)return a._.transform;b=I(b).replace(/\.{3}|\u2026/g,a._.transform||G);var d=c.parseTransformString(b),e=0,f=0,g=0,h=1,i=1,j=a._,k=new o;if(j.transform=d||[],d)for(var l=0,m=d.length;m>l;l++){var n,p,q,r,s,t=d[l],u=t.length,v=I(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(n=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-n,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ob=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Pb=c._equaliseTransform=function(a,b){b=I(b).replace(/\.{3}|\u2026/g,a),a=c.parseTransformString(a)||[],b=c.parseTransformString(b)||[];for(var d,e,f,g,h=O(a.length,b.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ob(b[k]),g=b[k]||Ob(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))return;for(i[k]=[],j[k]=[],d=0,e=O(f.length,g.length);e>d;d++)d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d]) -}return{from:i,to:j}};c._getContainer=function(a,b,d,e){var f;return f=null!=e||c.is(a,"object")?a:A.doc.getElementById(a),null!=f?f.tagName?null==b?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:b,height:d}:{container:1,x:a,y:b,width:d,height:e}:void 0},c.pathToRelative=Db,c._engine={},c.path2curve=Kb,c.matrix=function(a,b,c,d,e,f){return new o(a,b,c,d,e,f)},function(a){function b(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var c=N.sqrt(b(a));a[0]&&(a[0]/=c),a[1]&&(a[1]/=c)}a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof o&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)j+=l[g][i]*m[i][h];k[g][h]=j}this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new o(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new o(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,b,d){a=c.rad(a),b=b||0,d=d||0;var e=+N.cos(a).toFixed(9),f=+N.sin(a).toFixed(9);this.add(e,f,-f,e,b,d),this.add(1,0,0,1,-b,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[I.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return c.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=N.sqrt(b(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=N.sqrt(b(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=c.deg(N.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=c.deg(N.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[J]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:G)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:G)+(b.rotate?"r"+[b.rotate,0,0]:G)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(o.prototype);var Qb=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);v.safari="Apple Computer, Inc."==navigator.vendor&&(Qb&&Qb[1]<4||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Qb&&Qb[1]<8?function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:mb;for(var Rb=function(){this.returnValue=!1},Sb=function(){return this.originalEvent.preventDefault()},Tb=function(){this.cancelBubble=!0},Ub=function(){return this.originalEvent.stopPropagation()},Vb=function(a){var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,c=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Wb=function(){return A.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Vb(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),F&&L[b]){var f=function(b){for(var e=Vb(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Sb,b.stopPropagation=Ub;break}return c.call(d,b,e.x,e.y)};a.addEventListener(L[b],f,!1)}return function(){return a.removeEventListener(b,e,!1),F&&L[b]&&a.removeEventListener(L[b],e,!1),!0}}:A.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||A.win.event;var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,e=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Rb,a.stopPropagation=a.stopPropagation||Tb,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}(),Xb=[],Yb=function(a){for(var c,d=a.clientX,e=a.clientY,f=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,g=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,h=Xb.length;h--;){if(c=Xb[h],F&&a.touches){for(var i,j=a.touches.length;j--;)if(i=a.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;A.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,A.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&b("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,b("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},Zb=function(a){c.unmousemove(Yb).unmouseup(Zb);for(var d,e=Xb.length;e--;)d=Xb[e],d.el._drag={},b("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,a);Xb=[]},$b=c.el={},_b=K.length;_b--;)!function(a){c[a]=$b[a]=function(b,d){return c.is(b,"function")&&(this.events=this.events||[],this.events.push({name:a,f:b,unbind:Wb(this.shape||this.node||A.doc,a,b,d||this)})),this},c["un"+a]=$b["un"+a]=function(b){for(var d=this.events||[],e=d.length;e--;)d[e].name!=a||!c.is(b,"undefined")&&d[e].f!=b||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}(K[_b]);$b.data=function(a,d){var e=kb[this.id]=kb[this.id]||{};if(0==arguments.length)return e;if(1==arguments.length){if(c.is(a,"object")){for(var f in a)a[z](f)&&this.data(f,a[f]);return this}return b("raphael.data.get."+this.id,this,e[a],a),e[a]}return e[a]=d,b("raphael.data.set."+this.id,this,d,a),this},$b.removeData=function(a){return null==a?kb[this.id]={}:kb[this.id]&&delete kb[this.id][a],this},$b.getData=function(){return d(kb[this.id]||{})},$b.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},$b.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var ac=[];$b.drag=function(a,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,m=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;if(this._drag.id=i.identifier,F&&i.touches)for(var n,o=i.touches.length;o--;)if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}this._drag.x=j+m,this._drag.y=k+l,!Xb.length&&c.mousemove(Yb).mouseup(Zb),Xb.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("raphael.drag.start."+this.id,d),a&&b.on("raphael.drag.move."+this.id,a),e&&b.on("raphael.drag.end."+this.id,e),b("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}return this._drag={},ac.push({el:this,start:i}),this.mousedown(i),this},$b.onDragOver=function(a){a?b.on("raphael.drag.over."+this.id,a):b.unbind("raphael.drag.over."+this.id)},$b.undrag=function(){for(var a=ac.length;a--;)ac[a].el==this&&(this.unmousedown(ac[a].start),ac.splice(a,1),b.unbind("raphael.drag.*."+this.id));!ac.length&&c.unmousemove(Yb).unmouseup(Zb),Xb=[]},v.circle=function(a,b,d){var e=c._engine.circle(this,a||0,b||0,d||0);return this.__set__&&this.__set__.push(e),e},v.rect=function(a,b,d,e,f){var g=c._engine.rect(this,a||0,b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.ellipse=function(a,b,d,e){var f=c._engine.ellipse(this,a||0,b||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},v.path=function(a){a&&!c.is(a,U)&&!c.is(a[0],V)&&(a+=G);var b=c._engine.path(c.format[D](c,arguments),this);return this.__set__&&this.__set__.push(b),b},v.image=function(a,b,d,e,f){var g=c._engine.image(this,a||"about:blank",b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.text=function(a,b,d){var e=c._engine.text(this,a||0,b||0,I(d));return this.__set__&&this.__set__.push(e),e},v.set=function(a){!c.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var b=new mc(a);return this.__set__&&this.__set__.push(b),b.paper=this,b.type="set",b},v.setStart=function(a){this.__set__=a||this.set()},v.setFinish=function(){var a=this.__set__;return delete this.__set__,a},v.setSize=function(a,b){return c._engine.setSize.call(this,a,b)},v.setViewBox=function(a,b,d,e,f){return c._engine.setViewBox.call(this,a,b,d,e,f)},v.top=v.bottom=null,v.raphael=c;var bc=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(A.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(A.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};v.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=A.doc.elementFromPoint(a,b);if(A.win.opera&&"svg"==e.tagName){var f=bc(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}if(!e)return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},v.getElementsByBBox=function(a){var b=this.set();return this.forEach(function(d){c.isBBoxIntersect(d.getBBox(),a)&&b.push(d)}),b},v.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)return b;b=b.next}return null},v.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)return this;c=c.next}return this},v.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},$b.isPointInside=function(a,b){var d=this.realPath=qb[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=c.transformPath(d,this.attr("transform"))),c.isPointInsidePath(d,a,b)},$b.getBBox=function(a){if(this.removed)return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=qb[this.type](this),b.bboxwt=Bb(this.realPath),b.bboxwt.toString=p,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=qb[this.type](this)),b.bbox=Bb(rb(this.realPath,this.matrix)),b.bbox.toString=p,b.dirty=b.dirtyT=0),b.bbox)},$b.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},$b.glow=function(a){if("text"==this.type)return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||qb[this.type](this);f=this.matrix?rb(f,this.matrix):f;for(var g=1;c+1>g;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cc=function(a,b,d,e,f,g,h,i,l){return null==l?j(a,b,d,e,f,g,h,i):c.findDotsAtSegment(a,b,d,e,f,g,h,i,k(a,b,d,e,f,g,h,i,l))},dc=function(a,b){return function(d,e,f){d=Kb(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])g=+i[1],h=+i[2];else{if(j=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(b&&!m.start){if(k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!a&&!b)return k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}n+=j,g=+i[5],h=+i[6]}l+=i.shift()+i}return m.end=l,k=a?n:b?m:c.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},ec=dc(1),fc=dc(),gc=dc(0,1);c.getTotalLength=ec,c.getPointAtLength=fc,c.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return gc(a,b).end;var d=gc(a,c,1);return b?gc(d,b).end:d},$b.getTotalLength=function(){var a=this.getPath();if(a)return this.node.getTotalLength?this.node.getTotalLength():ec(a)},$b.getPointAtLength=function(a){var b=this.getPath();if(b)return fc(b,a)},$b.getPath=function(){var a,b=c._getPath[this.type];if("text"!=this.type&&"set"!=this.type)return b&&(a=b(this)),a},$b.getSubpath=function(a,b){var d=this.getPath();if(d)return c.getSubpath(d,a,b)};var hc=c.easing_formulas={linear:function(a){return a},"<":function(a){return R(a,1.7)},">":function(a){return R(a,.48)},"<>":function(a){var b=.48-a/1.04,c=N.sqrt(.1734+b*b),d=c-b,e=R(Q(d),1/3)*(0>d?-1:1),f=-c-b,g=R(Q(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:R(2,-10*a)*N.sin((a-.075)*2*S/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};hc.easeIn=hc["ease-in"]=hc["<"],hc.easeOut=hc["ease-out"]=hc[">"],hc.easeInOut=hc["ease-in-out"]=hc["<>"],hc["back-in"]=hc.backIn,hc["back-out"]=hc.backOut;var ic=[],jc=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},kc=function(){for(var a=+new Date,d=0;dh))if(i>h){var q=j(h/i);for(var r in k)if(k[z](r)){switch(db[r]){case T:f=+k[r]+q*i*l[r];break;case"colour":f="rgb("+[lc($(k[r].r+q*i*l[r].r)),lc($(k[r].g+q*i*l[r].g)),lc($(k[r].b+q*i*l[r].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[r].length;u>t;t++){f[t]=[k[r][t][0]];for(var v=1,w=k[r][t].length;w>v;v++)f[t][v]=+k[r][t][v]+q*i*l[r][t][v];f[t]=f[t].join(H)}f=f.join(H);break;case"transform":if(l[r].real)for(f=[],t=0,u=k[r].length;u>t;t++)for(f[t]=[k[r][t][0]],v=1,w=k[r][t].length;w>v;v++)f[t][v]=k[r][t][v]+q*i*l[r][t][v];else{var x=function(a){return+k[r][a]+q*i*l[r][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}break;case"csv":if("clip-rect"==r)for(f=[],t=4;t--;)f[t]=+k[r][t]+q*i*l[r][t];break;default:var y=[][E](k[r]);for(f=[],t=n.paper.customAttributes[r].length;t--;)f[t]=+y[t]+q*i*l[r][t]}o[r]=f}n.attr(o),function(a,c,d){setTimeout(function(){b("raphael.anim.frame."+a,c,d)})}(n.id,n,e.anim)}else{if(function(a,d,e){setTimeout(function(){b("raphael.anim.frame."+d.id,d,e),b("raphael.anim.finish."+d.id,d,e),c.is(a,"function")&&a.call(d)})}(e.callback,n,e.anim),n.attr(m),ic.splice(d--,1),e.repeat>1&&!e.next){for(g in m)m[z](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),s(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}e.next&&!e.stop&&s(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}c.svg&&n&&n.paper&&n.paper.safari(),ic.length&&jc(kc)},lc=function(a){return a>255?255:0>a?0:a};$b.animateWith=function(a,b,d,e,f,g){var h=this;if(h.removed)return g&&g.call(h),h;var i=d instanceof r?d:c.animation(d,e,f,g);s(i,h,i.percents[0],null,h.attr());for(var j=0,k=ic.length;k>j;j++)if(ic[j].anim==b&&ic[j].el==a){ic[k-1].start=ic[j].start;break}return h},$b.onAnimation=function(a){return a?b.on("raphael.anim.frame."+this.id,a):b.unbind("raphael.anim.frame."+this.id),this},r.prototype.delay=function(a){var b=new r(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},r.prototype.repeat=function(a){var b=new r(this.anim,this.ms);return b.del=this.del,b.times=N.floor(O(a,0))||1,b},c.animation=function(a,b,d,e){if(a instanceof r)return a;(c.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),b=+b||0;var f,g,h={};for(g in a)a[z](g)&&_(g)!=g&&_(g)+"%"!=g&&(f=!0,h[g]=a[g]);return f?(d&&(h.easing=d),e&&(h.callback=e),new r({100:h},b)):new r(a,b)},$b.animate=function(a,b,d,e){var f=this;if(f.removed)return e&&e.call(f),f;var g=a instanceof r?a:c.animation(a,b,d,e);return s(g,f,g.percents[0],null,f.attr()),f},$b.setTime=function(a,b){return a&&null!=b&&this.status(a,P(b,a.ms)/a.ms),this},$b.status=function(a,b){var c,d,e=[],f=0;if(null!=b)return s(a,this,-1,P(b,1)),this;for(c=ic.length;c>f;f++)if(d=ic[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)return d.status;e.push({anim:d.anim,status:d.status})}return a?0:e},$b.pause=function(a){for(var c=0;cb;b++)!a[b]||a[b].constructor!=$b.constructor&&a[b].constructor!=mc||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},nc=mc.prototype;nc.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],!a||a.constructor!=$b.constructor&&a.constructor!=mc||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},nc.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},nc.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var oc in $b)$b[z](oc)&&(nc[oc]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][D](c,b)})}}(oc));return nc.attr=function(a,b){if(a&&c.is(a,V)&&c.is(a[0],"object"))for(var d=0,e=a.length;e>d;d++)this.items[d].attr(a[d]);else for(var f=0,g=this.items.length;g>f;f++)this.items[f].attr(a,b);return this},nc.clear=function(){for(;this.length;)this.pop()},nc.splice=function(a,b){a=0>a?O(this.length+a,0):a,b=O(0,P(this.length-a,b));var c,d=[],e=[],f=[];for(c=2;cc;c++)e.push(this[a+c]);for(;cc?f[c]:d[c-g];for(c=this.items.length=this.length-=b-g;this[c];)delete this[c++];return new mc(e)},nc.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0},nc.animate=function(a,b,d,e){(c.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)return this;e&&(g=function(){!--h&&e.call(j)}),d=c.is(d,U)?d:g;var k=c.animation(a,b,d,g);for(f=this.items[--i].animate(k);i--;)this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},nc.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},nc.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=P[D](0,a),b=P[D](0,b),c=O[D](0,c),d=O[D](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},nc.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},nc.toString=function(){return"Raphaël‘s set"},nc.glow=function(a){var b=this.paper.set();return this.forEach(function(c){var d=c.glow(a);null!=d&&d.forEach(function(a){b.push(a)})}),b},nc.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(console.log("runned"),c=!0,!1):void 0}),c},c.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[z](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=ab(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[z](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"},f.k)for(var g in f.k)f[z](g)&&(b.glyphs[e].k[g]=f.k[g])}}return a},v.getFont=function(a,b,d,e){if(e=e||"normal",d=d||"normal",b=+b||{normal:400,bold:700,lighter:300,bolder:800}[b]||400,c.fonts){var f=c.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,G)+"(\\s|$)","i");for(var h in c.fonts)if(c.fonts[z](h)&&g.test(h)){f=c.fonts[h];break}}var i;if(f)for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=b||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},v.print=function(a,b,d,e,f,g,h,i){g=g||"middle",h=O(P(h||0,1),-1),i=O(P(i||1,3),1);var j,k=I(d)[J](G),l=0,m=0,n=G;if(c.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[J](w),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])l=0,x=0,m=0,r+=q*i;else{var v=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(v.w||e.w)+(v.k&&v.k[k[t]]||0)+e.w*h:0,m=1}x&&x.d&&(n+=c.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(b-s)/j]))}}return this.path(n).attr({fill:"#000",stroke:"none"})},v.add=function(a){if(c.is(a,"array"))for(var b,d=this.set(),e=0,f=a.length;f>e;e++)b=a[e]||{},x[z](b.type)&&d.push(this[b.type]().attr(b));return d},c.format=function(a,b){var d=c.is(b,V)?[0][E](b):arguments;return a&&c.is(a,U)&&d.length-1&&(a=a.replace(y,function(a,b){return null==d[++b]?G:d[b]})),a||G},c.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),c.ninja=function(){return B.was?A.win.Raphael=B.is:delete Raphael,c},c.st=nc,function(a,b,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):c.eve("raphael.DOMload")}null==a.readyState&&a.addEventListener&&(a.addEventListener(b,d=function(){a.removeEventListener(b,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}(document,"DOMContentLoaded"),b.on("raphael.DOMload",function(){u=!0}),function(){if(c.svg){var a="hasOwnProperty",b=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=c.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};c.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)e[a](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),b(e[f])):d.setAttribute(f,b(e[f])))}else d=c._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(a,e){var j="linear",k=a.id+e,m=.5,n=.5,o=a.node,p=a.paper,r=o.style,s=c._g.doc.getElementById(k);if(!s){if(e=b(e).replace(c._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))return null;var u=[0,0,f.cos(c.rad(t)),f.sin(c.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=c._parseDots(e);if(!w)return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),a.gradient&&k!=a.gradient.id&&(p.defs.removeChild(a.gradient),delete a.gradient),!a.gradient){s=q(j+"Gradient",{id:k}),a.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:a.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}return q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=b(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y;c._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=c._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-E*u):(g=E*u,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=c.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-g):(g=0,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:c.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)if(p[a](k)&&!p[k]){var F=c._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,c,d){if(c=u[b(c).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=c.length;h--;)g[h]=c[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[a](o)){if(!c._availableAttrs[a](o))continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"href":case"title":var u=q("title"),w=c._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u);break;case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var u=q("a");x.insertBefore(u,i),u.appendChild(i),x=u}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var z=b(p).split(j);if(4==z.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var A=q("clipPath"),B=q("rect");A.id=c.createUUID(),q(B,{x:z[0],y:z[1],width:z[2],height:z[3]}),A.appendChild(B),d.paper.defs.appendChild(A),q(i,{"clip-path":"url(#"+A.id+")"}),d.clip=B}if(!p){var C=i.getAttribute("clip-path");if(C){var D=c._g.doc.getElementById(C.replace(/(^url\(#|\)$)/g,l));D&&D.parentNode.removeChild(D),q(i,{"clip-path":l}),delete d.clip}}break;case"path":"path"==d.type&&(q(i,{d:p?k.path=c._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var E=b(p).match(c._ISURL);if(E){A=q("pattern");var F=q("image");A.id=c.createUUID(),q(A,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":E[1]}),A.appendChild(F),function(a){c._preload(E[1],function(){var b=this.offsetWidth,c=this.offsetHeight;q(a,{width:b,height:c}),q(F,{width:b,height:c}),d.paper.safari()})}(A),d.paper.defs.appendChild(A),q(i,{fill:"url(#"+A.id+")"}),d.pattern=A,d.pattern&&s(d);break}var G=c.getRGB(p);if(G.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}}else delete f.gradient,delete k.gradient,!c.is(k.opacity,"undefined")&&c.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!c.is(k["fill-opacity"],"undefined")&&c.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});G[a]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=c.getRGB(p),i.setAttribute(o,G.hex),"stroke"==o&&G[a]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p);break;case"opacity":k.gradient&&!k[a]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break}default:"font-size"==o&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[a]("text")||f[a]("font")||f[a]("font-size")||f[a]("x")||f[a]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(c._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10; -if(f[a]("text")){for(g.text=f.text;h.firstChild;)h.removeChild(h.firstChild);for(var j,k=b(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(c._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&c.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.matrix=c.matrix(),this.realPath=null,this.paper=b,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},A=c.el;z.prototype=A,A.constructor=z,c._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},A.rotate=function(a,c,e){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this.transform(this._.transform.concat([["r",a,c,e]])),this},A.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this},A.translate=function(a,c){return this.removed?this:(a=b(a).split(j),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this.transform(this._.transform.concat([["t",a,c]])),this)},A.transform=function(b){var d=this._;if(null==b)return d.transform;if(c._extractTransform(this,b),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[a]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},A.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},A.remove=function(){if(!this.removed&&this.node.parentNode){var a=this.paper;a.__set__&&a.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&a.defs.removeChild(this.gradient),c._tear(this,a),"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var b in this)this[b]="function"==typeof this[b]?c._removedFactory(b):null;this.removed=!0}},A._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}return a&&this.hide(),b},A.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if("fill"==b&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==b)return this._.transform;for(var g=b.split(j),h={},i=0,l=g.length;l>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return l-1?h:h[g[0]]}if(null==d&&c.is(b,"array")){for(h={},i=0,l=b.length;l>i;i++)h[b[i]]=this.attr(b[i]);return h}if(null!=d){var m={};m[b]=d}else null!=b&&c.is(b,"object")&&(m=b);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[a](n)&&m[a](n)&&c.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[a](p)&&(m[p]=o[p])}return w(this,m),this},A.toFront=function(){if(this.removed)return this;"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var a=this.paper;return a.top!=this&&c._tofront(this,a),this},A.toBack=function(){if(this.removed)return this;var a=this.node.parentNode;return"a"==a.tagName.toLowerCase()?a.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):a.firstChild!=this.node&&a.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper),this.paper,this},A.insertAfter=function(a){if(this.removed)return this;var b=a.node||a[a.length-1].node;return b.nextSibling?b.parentNode.insertBefore(this.node,b.nextSibling):b.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this},A.insertBefore=function(a){if(this.removed)return this;var b=a.node||a[0].node;return b.parentNode.insertBefore(this.node,b),c._insertbefore(this,a,this.paper),this},A.blur=function(a){var b=this;if(0!==+a){var d=q("filter"),e=q("feGaussianBlur");b.attrs.blur=a,d.id=c.createUUID(),q(e,{stdDeviation:+a||1.5}),d.appendChild(e),b.paper.defs.appendChild(d),b._blur=d,q(b.node,{filter:"url(#"+d.id+")"})}else b._blur&&(b._blur.parentNode.removeChild(b._blur),delete b._blur,delete b.attrs.blur),b.node.removeAttribute("filter");return b},c._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},c._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},c._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},c._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},c._engine.text=function(a,b,d,e){var f=q("text");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={x:b,y:d,"text-anchor":"middle",text:e,font:c._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},c._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a&&a.container,d=a.x,e=a.y,f=a.width,g=a.height;if(!b)throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),1==b?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",c._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i)),b=new c._Paper,b.width=f,b.height=g,b.canvas=i,b.clear(),b._left=b._top=0,h&&(b.renderfix=function(){}),b.renderfix(),b},c._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=g(c/this.width,d/this.height),j=this.top,l=e?"meet":"xMinYMin";for(null==a?(this._vbSize&&(i=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=i,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:l});i&&j;)h="stroke-width"in j.attrs?j.attrs["stroke-width"]:1,j.attr({"stroke-width":h}),j._.dirty=1,j._.dirtyT=1,j=j.prev;return this._viewBox=[a,b,c,d,!!e],this},c.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},c.prototype.clear=function(){c.eve("raphael.clear",this);for(var a=this.canvas;a.firstChild;)a.removeChild(a.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(c._g.doc.createTextNode("Created with Raphaël "+c.version)),a.appendChild(this.desc),a.appendChild(this.defs=q("defs"))},c.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null};var B=c.st;for(var C in A)A[a](C)&&!B[a](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}}(),function(){if(c.vml){var a="hasOwnProperty",b=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=c.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(a){var d=/[ahqstv]/gi,e=c._pathToAbsolute;if(b(a).match(d)&&(e=c._path2curve),d=/[clmz]/g,e==c._pathToAbsolute&&!b(a).match(d)){var g=b(a).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}var h,i,j=e(a);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}return g.join(n)},y=function(a,b,d){var e=c.matrix();return e.rotate(-a,.5,.5),{dx:e.x(b,d),dy:e.y(b,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-q+n+e*-r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}m.visibility="visible"}};c.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,c,d){for(var e=b(c).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)i[a](t)&&(m[t]=i[t]);if(q&&(m.path=c._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~b(m.path).toLowerCase().indexOf("r")?c._pathToAbsolute(m.path):m.path),"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=c.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}if("clip-rect"in i){var G=b(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||c._g.doc.createElement("div"),I=H.style;I.clip=c.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=b(i.fill).match(c._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],c._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else K.color=c.getRGB(i.fill).hex,K.src=o,K.type="solid",c.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=b(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+c.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=c.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),i["stroke-dasharray"]){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[a](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}Q&&l.appendChild(P)}if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=b(s.textpath.string).replace(/"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=c.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)if(Y[Z]in i){s._.dirty=1;break}switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}s.textpath.style["v-text-kern"]=!0}},C=function(a,f,g){a.attrs=a.attrs||{};var h=(a.attrs,Math.pow),i="linear",j=".5 .5";if(a.attrs.gradient=f,f=b(f).replace(c._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))return null}var l=c._parseDots(f);if(!l)return null;if(a=a.shape||a.node,l.length){a.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),a.appendChild(g)}return 1},D=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=b,this.matrix=c.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},E=c.el;D.prototype=E,E.constructor=D,E.transform=function(a){if(null==a)return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=a=b(a).replace(/\.{3}|\u2026/g,this._.transform||o)),c._extractTransform(this,f+a);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~b(this.attrs.fill).indexOf("-"),l=!b(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else j.style.filter=o,i.matrix=b(h),i.offset=h.offset();return d&&(this._.transform=d),this},E.rotate=function(a,c,e){if(this.removed)return this;if(null!=a){if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,c,e]])),this}},E.translate=function(a,c){return this.removed?this:(a=b(a).split(k),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=c),this.transform(this._.transform.concat([["t",a,c]])),this)},E.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),c.eve.unbind("raphael.*.*."+this.id),c._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;this.removed=!0}},E.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if(b==j&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var g=b.split(k),h={},i=0,m=g.length;m>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return m-1?h:h[g[0]]}if(this.attrs&&null==d&&c.is(b,"array")){for(h={},i=0,m=b.length;m>i;i++)h[b[i]]=this.attr(b[i]);return h}var n;null!=d&&(n={},n[b]=d),null==d&&c.is(b,"object")&&(n=b);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[a](o)&&n[a](o)&&c.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[a](q)&&(n[q]=p[q])}n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&c._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper)),this)},E.insertAfter=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[a.length-1]),a.node.nextSibling?a.node.parentNode.insertBefore(this.node,a.node.nextSibling):a.node.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this)},E.insertBefore=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[0]),a.node.parentNode.insertBefore(this.node,a.node),c._insertbefore(this,a,this.paper),this)},E.blur=function(a){var b=this.node.runtimeStyle,d=b.filter;return d=d.replace(r,o),0!==+a?(this.attrs.blur=a,b.filter=d+n+m+".Blur(pixelradius="+(+a||1.5)+")",b.margin=c.format("-{0}px 0 0 -{0}px",f(+a||1.5))):(b.filter=d,b.margin=0,delete this.attrs.blur),this},c._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},c._engine.rect=function(a,b,d,e,f,g){var h=c._rectPath(b,d,e,f,g),i=a.path(h),j=i.attrs;return i.X=j.x=b,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},c._engine.ellipse=function(a,b,c,d,e){var f=a.path();return f.attrs,f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},c._engine.circle=function(a,b,c,d){var e=a.path();return e.attrs,e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},c._engine.image=function(a,b,d,e,f,g){var h=c._rectPath(d,e,f,g),i=a.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=b,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=b,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},c._engine.text=function(a,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=c.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=b(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,a),l={fill:"#000",stroke:"none",font:c._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=b(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),a.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},c._engine.setSize=function(a,b){var d=this.canvas.style;return this.width=a,this.height=b,a==+a&&(a+="px"),b==+b&&(b+="px"),d.width=a,d.height=b,d.clip="rect(0 "+a+" "+b+" 0)",this._viewBox&&c._engine.setViewBox.apply(this,this._viewBox),this},c._engine.setViewBox=function(a,b,d,e,f){c.eve("raphael.setViewBox",this,this._viewBox,[a,b,d,e,f]);var h,i,j=this.width,k=this.height,l=1/g(d/j,e/k);return f&&(h=k/e,i=j/d,j>d*h&&(a-=(j-d*h)/2/h),k>e*i&&(b-=(k-e*i)/2/i)),this._viewBox=[a,b,d,e,!!f],this._viewBoxShift={dx:-a,dy:-b,scale:l},this.forEach(function(a){a.transform("...")}),this};var F;c._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},c._engine.initWin(c._g.win),c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a.container,d=a.height,e=a.width,f=a.x,g=a.y;if(!b)throw new Error("VML container not found.");var h=new c._Paper,i=h.canvas=c._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=c._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=c.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==b?(c._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i),h.renderfix=function(){},h},c.prototype.clear=function(){c.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=c._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},c.prototype.remove=function(){c.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;return!0};var G=c.st;for(var H in E)E[a](H)&&!G[a](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}}(),B.was?A.win.Raphael=c:Raphael=c,c}); \ No newline at end of file diff --git a/public/worldMap/stats.js b/public/worldMap/stats.js deleted file mode 100644 index de6487700b..0000000000 --- a/public/worldMap/stats.js +++ /dev/null @@ -1,27 +0,0 @@ -$(function() { - - var topCountries = function() { - var toSort = []; - for (var c in stats.countries) { - toSort.push([c, stats.countries[c]]); - } - toSort.sort(function(a, b) { return b[1] - a[1]; }); - var top = toSort.slice(0, 20); - $('#topCountries').html(''); - $.each(top, function(i, v) { - $('#topCountries').append( - '
'+v[0]+': '+v[1]+'
' - ); - }); - }; - - var updateStats = function() { - $('#countries > span').text(Object.keys(stats.countries).length); - topCountries(); - - setTimeout(function() { updateStats(); }, 200); - }; - - updateStats(); - -}); diff --git a/public/worldMap/world.js b/public/worldMap/world.js deleted file mode 100644 index 14da1faeb4..0000000000 --- a/public/worldMap/world.js +++ /dev/null @@ -1,171 +0,0 @@ -var worldShapes = [ -"M604.196,161.643l0.514-0.129l0,0.772l2.188-0.386l2.189,0l1.672,0.129l1.803-1.802l2.058-1.802l1.674-1.673l0.518,0.900l0.385,2.189l-1.417,0l-0.258,1.802l0.517,0.386l-1.159,0.515l-0.129,1.029l-0.773,1.159l0,1.030l-0.514,0.644l-8.110-1.416l-1.031-2.704l0.127,0.643z", -"M630.069,130.876l2.832,1.030l2.059-0.257l0.517-1.288l2.058-0.386l1.546-0.772l0.515-2.188l2.317-0.516l0.387-1.030l1.285,0.774l0.902,0.128l1.416,0l2.059,0.515l0.773,0.385l2.059-0.900l0.901,0.515l0.773-1.287l1.674,0.128l0.386-0.387l0.256-1.157l1.160-0.903l1.543,0.645l-0.384,0.772l0.901,0.129l-0.259,2.317l1.030,0.900l0.904-0.643l1.285-0.257l1.674-1.159l1.802,0.129l2.832,0l0.387,0.773l-1.545,0.385l-1.416,0.516l-3.090,0.256l-2.833,0.517l-1.545,1.287l0.645,1.029l0.257,1.416l-1.287,1.159l0.129,1.029l-0.773,0.902l-2.575,0l1.030,1.673l-1.673,0.772l-1.158,1.545l0.129,1.674l-1.031,0.772l-1.029-0.257l-2.061,0.386l-0.257,0.644l-2.058,0l-1.417,1.544l-0.129,2.317l-3.476,1.159l-1.931-0.257l-0.514,0.643l-1.674-0.386l-2.704,0.386l-4.504-1.415l2.445-2.447l-0.129-1.673l-2.060-0.515l-0.256-1.674l-0.902-2.188l1.158-1.416l-1.158-0.386l0.773-1.930l-1.029,3.477z", -"M520.651,114.27l-0.257,0.900l0.385,1.160l1.029,0.643l0,0.644l-0.901,0.386l-0.128,0.901l-1.288,1.287l-0.386-0.128l-0.127-0.644l-1.417-0.900l-0.259-1.288l0.259-1.803l0.256-0.901l-0.384-0.386l-0.258-0.901l1.287-1.288l0.129,0.516l0.771-0.258l0.516,0.773l0.643,0.257l-0.130-1.030z", -"M582.697,116.33l3.605,-0.515l0.642,0.772l1.032,0.386l-0.516,0.773l1.416,0.900l-0.772,0.902l1.159,0.643l1.158,0.516l0.129,1.801l-1.029,0.129l-1.032,-1.544l0,-0.515l-1.287,0.129l-0.771,-0.772l-0.516,0l-1.029,-0.773l-2.059,-0.643l0.256,-1.288l0.386,0.901z", -"M497.994,242.615l-0.643-2.060l1.030-1.159l0.900-0.515l0.902,1.031l-0.902,0.516l-0.514,0.642l0,1.159l0.773-0.386zM496.836,273.64l-0.257-1.804l0.385-2.317l0.900-2.445l0.130-1.158l0.901-2.447l0.643-1.157l1.545-1.674l0.902-1.288l0.257-1.931l-0.129-1.544l-0.771-0.902l-0.775-1.673l-0.642-1.674l0.129-0.515l0.772-1.029l-0.772-2.704l-0.516-1.802l-1.414-1.674l0.257-0.515l1.157-0.387l0.774,0.131l0.900-0.389l7.982,0.131l0.643,1.930l0.771,1.674l0.645,0.773l1.031,1.415l1.801-0.128l0.900-0.387l1.418,0.387l0.514-0.772l0.644-1.545l1.673-0.128l0.128-0.388l1.417,0l-0.258,0.902l3.219,0l0.128,1.672l0.514,1.031l-0.385,1.673l0.129,1.674l0.900,1.030l-0.129,3.091l0.645-0.131l1.158,0l1.674-0.385l1.287,0.128l0.257,0.902l-0.257,1.286l0.387,1.287l-0.387,0.903l0.257,1.028l-5.536-0.127l-0.128,8.625l1.804,2.187l1.673,1.674l-4.892,1.158l-6.566-0.385l-1.801-1.287l-10.944,0.128l-0.384,0.128l-1.674-1.159l-1.672-0.128l-1.674,0.515l1.288-0.516z", -"M319.448,295.781l1.288,1.544v2.188l-2.319,1.416l-1.801,1.158l-2.961,2.576l-3.605,3.732l-0.771,2.188l-0.645,2.702v2.705l-0.643,0.643l-0.129,1.674l-0.257,1.418l3.475,2.316l-0.387,1.802l1.675,1.287l-0.129,1.288l-2.574,3.475l-3.991,1.418l-5.406,0.512l-2.961-0.256l0.514,1.674l-0.514,1.931l0.514,1.415l-1.673,0.902l-2.703,0.385l-2.575-1.027l-1.029,0.77l0.386,2.705l1.801,0.771l1.417-0.9l0.901,1.416l-2.575,0.901l-2.188,1.673l-0.386,2.705l-0.643,1.414h-2.448l-2.188,1.416l-0.772,1.932l2.704,2.06l2.574,0.517l-0.901,2.444l-3.218,1.545l-1.803,3.09l-2.445,1.03l-1.031,1.287l0.774,2.832l1.802,1.543l-1.03-0.127l-2.574-0.387l-6.436-0.386l-1.16-1.545v-2.06l-1.801,0.129l-0.902-0.902l-0.258-2.831l2.06-1.288l0.901-1.674l-0.386-1.288l1.546-2.315l0.9-3.605l-0.257-1.545l1.158-0.516l-0.258-1.029l-1.287-0.514l0.901-1.158l-1.157-1.03l-0.645-3.089l1.03-0.516l-0.385-3.348l0.513-2.703l0.773-2.447l1.673-1.029l-0.9-2.574v-2.445l2.06-1.803v-2.189l1.415-2.701l0.129-2.447l-0.772-0.514l-1.287-4.637l1.672-2.83l-0.257-2.575l1.03-2.446l1.802-2.574l1.802-1.673l-0.772-1.03l0.515-0.9v-4.379l2.96-1.414l0.902-2.704l-0.386-0.772l2.316-2.447l3.477,0.645l1.544,2.061l1.03-2.188l3.089,0.127l0.515,0.516l4.892,4.377l2.188,0.387l3.348,2.059l2.703,1.03l0.386,1.157l-2.574,4.121l2.702,0.771l2.961,0.387l2.189-0.387l2.446-2.059l0.386-2.445L319.448,295.781zM282.761,371.99l3.475,1.674l3.733,0.643l-1.159,1.416l-2.574,0.131l-1.416-1.031h-1.546h-2.96l0.129-5.924l0.901,1.16l-1.417-1.931L282.761,371.99z", -"M510.996,97.278l-0.257,1.158l-1.545,0l0.643,0.643l-0.900,1.674l-0.515,0.515l-2.446,0l-1.289,0.644l-2.315-0.258l-3.734-0.644l-0.644-0.900l-2.703,0.386l-0.258,0.514l-1.672-0.386l-1.416,0l-1.160-0.514l0.385-0.644l-0.128-0.515l0.903-0.128l1.285,0.772l0.387-0.772l2.446,0.128l1.931-0.515l1.287,0.128l0.773,0.515l0.258-0.386l-0.387-1.802l1.030-0.386l0.901-1.158l2.058,0.772l1.417-1.030l1.030-0.258l2.061,0.901l1.286-0.129l1.158,0.516l-0.127,0.256l-0.257-0.903z", -"M863.067,336.975l1.674,0.129l0.129,3.218l-0.900,0.901l-0.258,2.188l-0.900-0.772l-1.934,1.931l-0.514-0.129l-1.672-0.129l-1.675-2.316l-0.385-1.803l-1.545-2.318l0.127-1.287l1.674,0.259l2.576,0.901l1.545-0.258l-2.058,0.515zM805.011,313.803l-2.832,1.288l-2.317,0.643l-0.513,1.416l-1.034,1.159l-2.185,0l-1.803,0.256l-2.318-0.513l-1.930,0.386l-1.930,0.127l-1.546,1.417l-0.772-0.128l-1.416,0.772l-1.287,0.772l-1.932-0.128l-1.800,0l-2.834-1.674l-1.416-0.514l0-1.545l1.289-0.387l0.515-0.515l-0.131-1.029l0.387-1.932l-0.256-1.545l-1.547-2.702l-0.386-1.546l0.129-1.545l-1.030-1.801l-0.127-0.773l-1.160-1.030l-0.387-2.058l-1.545-2.189l-0.384-1.160l1.287,1.160l-1.029-2.447l1.416,0.774l0.771,1.030l0-1.417l-1.416-2.061l-0.258-0.900l-0.644-0.773l0.386-1.545l0.516-0.644l0.387-1.415l-0.258-1.546l1.029-1.930l0.258,2.060l1.158-1.932l2.188-0.900l1.287-1.160l2.060-0.901l1.159-0.257l0.773,0.387l2.188-1.029l1.544-0.258l0.516-0.644l0.643-0.257l1.545,0.128l2.832-0.901l1.418-1.160l0.640-1.414l1.676-1.416l0.129-1.030l0-1.417l1.930-2.318l1.158,2.318l1.031-0.514l-0.902-1.287l0.902-1.287l1.156,0.516l0.260-2.061l1.545-1.289l0.643-1.028l1.289-0.516l0.127-0.773l1.158,0.386l0-0.643l1.158-0.387l1.416-0.385l1.930,1.157l1.547,1.675l1.671,0l1.676,0.258l-0.515-1.545l1.287-2.060l1.158-0.772l-0.385-0.643l1.158-1.545l1.672-1.031l1.289,0.385l2.317-0.514l-0.129-1.416l-1.932-0.900l1.418-0.388l1.801,0.775l1.416,1.029l2.316,0.772l0.774-0.387l1.674,0.902l1.544-0.773l1.030,0.258l0.644-0.516l1.158,1.289l-0.644,1.416l-1.029,1.157l-0.903,0l0.260,1.160l-0.773,1.286l-0.901,1.289l0.127,0.772l2.190,1.545l2.058,0.900l1.418,0.902l1.930,1.544l0.771,0l1.418,0.773l0.387,0.772l2.574,0.900l1.801-0.900l0.516-1.416l0.513-1.289l0.387-1.415l0.772-2.188l-0.385-1.286l0.127-0.775l-0.256-1.542l0.387-2.062l0.513-0.514l-0.386-0.901l0.644-1.417l0.516-1.414l0-0.772l1.029-1.032l0.772,1.288l0.130,1.674l0.641,0.385l0.131,1.029l1.029,1.417l0.258,1.544l-0.129,1.031l0.902,2.061l1.801-1.031l0.903,1.158l1.285,1.031l-0.256,1.158l0.515,2.317l0.387,1.416l0.641,0.257l0.773,2.319l-0.256,1.414l0.901,1.805l2.961,1.414l1.800,1.288l1.803,1.159l-0.258,0.642l1.545,1.674l1.030,2.961l1.031-0.642l1.158,1.286l0.643-0.516l0.386,2.961l1.932,1.544l1.287,1.030l2.061,2.189l0.771,2.189l0.129,1.545l-0.260,1.674l1.289,2.316l-0.129,2.317l-0.515,1.287l-0.645,2.447l0,1.545l-0.513,1.930l-1.159,2.446l-2.058,1.288l-0.903,2.060l-0.900,1.415l-0.902,2.317l-1.030,1.288l-0.642,2.060l-0.387,1.802l0.129,0.900l-1.545,0.902l-2.961,0.128l-2.445,1.031l-1.287,1.030l-1.674,1.157l-2.188-1.157l-1.675-0.515l0.517-1.287l-1.547,0.516l-2.316,1.929l-2.316-0.773l-1.547-0.385l-1.545-0.258l-2.572-0.772l-1.803-1.674l-0.516-2.060l-0.644-1.288l-1.287-1.157l-2.575-0.258l0.903-1.287l-0.645-2.060l-1.287,1.931l-2.445,0.387l1.416-1.416l0.386-1.545l1.030-1.288l-0.258-2.059l-2.188,2.316l-1.673,0.902l-1.032,2.189l-2.058-1.159l0.129-1.416l-1.674-1.932l-1.545-1.029l0.516-0.643l-3.348-1.675l-1.932,0l-2.574-1.286l-4.893,0.256l-3.474,0.902l-3.090,0.902l2.574,0.130z", -"M590.292,114.27l0.643,0l1.931,1.673l1.158,0.129l0.516-0.644l1.545-1.030l1.416,1.417l1.417,1.802l1.286,0.129l0.774,0.773l-2.190,0.257l-0.514,2.059l-0.386,0.901l-1.031,0.644l0,1.416l-0.643,0.129l-1.674-1.417l0.902-1.415l-0.773-0.773l-1.030,0.258l-3.089,1.930l-0.129-1.801l-1.158-0.516l-1.159-0.643l0.772-0.902l-1.416-0.900l0.516-0.773l-1.032-0.386l-0.642-0.772l0.129,0l0.644-0.387l1.930,0.772l1.545,0.130l0.258-0.258l-1.287-1.545l-0.771,0.257zM589.521,122.637l-1.804-0.386l-1.415-1.288l-0.387-1.028l0.516,0l0.771,0.772l1.287-0.129l0,0.515l-1.032-1.544z", -"M516.403,106.159l1.030,0l-0.645,1.159l1.289,1.030l-0.389,1.287l-1.158,0.387l-0.900,0.515l-0.387,1.545l-2.445-1.030l-1.031-1.159l-0.901-0.514l-1.286-1.031l-0.643-0.901l-1.290-1.158l0.516-1.159l1.031,0.643l0.643-0.643l1.159,0l2.316,0.386l1.931,0l-1.160-0.643z", -"M714.901,167.564l-0.13,1.932l-0.899-0.387l0.127,2.189l-0.771-1.417l-0.129-1.415l-0.514-1.287l-1.031-1.545l-2.575-0.129l0.259,1.159l-0.771,1.544l-1.158-0.644l-0.389,0.516l-0.772-0.258l-1.028-0.258l-0.516-2.188l-0.9-2.059l0.514-1.674L702.544,161l0.514-1.031l1.803-1.03l-2.061-1.415l1.031-1.803l2.061,1.159l1.285,0.128l0.26,1.931l2.574,0.386l2.574-0.128l1.545,0.515l-1.289,2.317l-1.158,0.129l-0.9,1.545l1.545,1.416l0.387-1.802h0.771L714.901,167.564z", -"M474.179,88.652l1.932,0.258l2.574-0.643l1.673,1.158l1.416,0.644l-0.258,1.930l-0.644,0l-0.385,1.544l-2.318-1.286l-1.416,0.257l-1.801-1.287l-1.288-1.029l-1.287,0l-0.385-1.031l-2.187,0.515z", -"M457.573,201.035l-1.802,-0.773l-1.287,0.129l-0.902,0.644l-1.286,-0.515l-0.387,-0.902l-1.287,-0.643l-0.128,-1.545l0.771,-1.159l-0.128,-0.900l2.189,-2.189l0.385,-1.802l0.773,-0.644l1.287,0.257l1.159,-0.514l0.257,-0.645l2.189,-1.285l0.514,-0.774l2.446,-1.158l1.545,-0.387l0.644,0.516l1.673,0l-0.129,1.287l0.258,1.287l1.545,1.673l0.128,1.417l3.091,0.515l0,1.930l-0.645,0.774l-1.287,0.257l-0.515,1.159l-1.030,0.256l-2.317,0l-1.288,-0.256l-0.770,0.514l-1.289,-0.258l-4.634,0.129l-0.129,1.545l-0.386,-2.060z", -"M526.314,107.833l0.773,1.030l1.031-0.129l2.059,0.386l3.990,0.130l1.287-0.644l3.219-0.644l1.930,1.030l1.544,0.258l-1.416,1.158l-0.900,1.931l0.772,1.416l-2.317-0.257l-2.705,0.772l0,1.417l-2.445,0.256l-1.930-1.029l-2.187,0.773l-1.932-0.130l-0.258-1.674l-1.287-0.900l0.385-0.387l-0.256-0.386l0.515-0.772l1.030-0.901l-1.415-1.158l-0.259-0.902l-0.772,0.644z", -"M544.208,239.14l-0.130-3.347l-0.643-1.159l1.673,0.258l0.773-1.545l1.415,0.128l0.131,1.030l0.642,0.643l0,0.903l-0.642,0.513l-1.030,1.416l-1.031,1.032l1.158-0.128z", -"M472.505,210.174l-2.188,0.258l-0.773-1.803l0.131-6.307l-0.516-0.515l-0.129-1.287l-0.900-0.902l-0.775-0.900l0.259-1.417l1.030-0.256l0.515-1.159l1.287-0.257l0.645-0.774l0.901-0.773l0.901-0.127l2.059,1.674l-0.129,0.771l0.643,1.673l-0.514,1.031l0.258,0.773l-1.288,1.672l-0.901,0.773l-0.386,1.674l0,1.802l0.130-4.376z", -"M772.829,214.809l1.16-1.029l2.314-1.416l-0.127,1.287l-0.26,1.674h-1.285l-0.516,0.902L772.829,214.809z", -"M295.89,286.383l-3.089-0.127l-1.030,2.187l-1.544-2.060l-3.477-0.644l-2.316,2.447l-1.932,0.386l-1.028-3.733l-1.417-2.960l0.773-2.576l-1.417-1.157l-0.387-1.933l-1.286-1.932l1.673-2.830l-1.158-2.318l0.643-0.901l-0.515-1.029l1.159-1.287l0-2.317l0.128-1.931l0.644-0.901l-2.445-4.248l2.060,0.127l1.415,0l0.515-0.771l2.446-1.160l1.416-1.029l3.476-0.386l-0.258,1.930l0.387,1.159l-0.258,1.802l2.960,2.317l2.962,0.515l1.030,1.030l1.801,0.515l1.159,0.772l1.673,0l1.545,0.773l0.128,1.544l0.516,0.773l0.128,1.158l-0.772,0l1.031,3.219l5.148,0.131l-0.386,1.542l0.258,1.030l1.416,0.771l0.643,1.676l-0.386,2.061l-0.772,1.158l0.257,1.544l-0.901,0.643l0-0.902l-2.575-1.285l-2.446-0.130l-4.634,0.772l-1.416,2.447l0,1.414l-1.030,3.219l0.515,0.515z", -"M310.05,308.396l3.605-3.732l2.961-2.576l1.801-1.158l2.319-1.416v-2.188l-1.288-1.544l-1.416,0.516l0.515-1.546l0.386-1.545v-1.544l-0.9-0.516l-1.031,0.516l-1.028-0.129l-0.259-1.031l-0.256-2.443l-0.516-0.902l-1.802-0.643l-1.159,0.514l-2.831-0.514l0.128-3.736l-0.772-1.414l0.901-0.643l-0.257-1.545l0.771-1.158l0.386-2.061l-0.643-1.676l-1.416-0.771l-0.258-1.029l0.386-1.543l-5.148-0.131l-1.031-3.219h0.772l-0.128-1.158l-0.516-0.772l-0.128-1.544l-1.545-0.773h-1.673l-1.159-0.771l-1.801-0.516l-1.03-1.029l-2.962-0.516l-2.96-2.316l0.258-1.803l-0.387-1.158l0.258-1.931l-3.476,0.386l-1.416,1.029l-2.446,1.16l-0.515,0.771h-1.415l-2.06-0.127l-1.416,0.383l-1.287-0.256l0.256-4.119l-2.317,1.545h-2.317l-1.03-1.416l-1.801-0.129l0.644-1.158l-1.546-1.674l-1.158-2.445l0.772-0.516v-1.158l1.545-0.773l-0.257-1.416l0.772-0.9l0.129-1.289l3.089-1.801l2.188-0.516l0.386-0.514l2.446,0.129l1.159-7.338l0.129-1.159l-0.515-1.544l-1.159-1.03v-1.931l1.545-0.387l0.515,0.258l0.129-1.029l-1.545-0.258l-0.129-1.674h5.278l0.9-0.902l0.773,0.902l0.515,1.545l0.516-0.387l1.544,1.416l2.06-0.129l0.515-0.771l1.93-0.645l1.159-0.515l0.257-1.159l1.931-0.771l-0.128-0.514l-2.188-0.26l-0.387-1.672v-1.805l-1.158-0.643l0.514-0.257l2.06,0.257l2.059,0.773l0.774-0.643l1.93-0.516l3.09-0.902l0.9-1.029l-0.257-0.772l1.287-0.129l0.644,0.644l-0.257,1.158l0.9,0.387l0.644,1.287l-0.772,0.902l-0.515,2.316l0.773,1.287l0.128,1.287l1.674,1.287l1.288,0.129l0.386-0.516l0.771-0.128l1.288-0.515l0.901-0.645l1.416,0.26l0.643-0.131l1.546,0.131l0.258-0.518l-0.517-0.514l0.259-0.773l1.158,0.26l1.159-0.26l1.545,0.516l1.287,0.516l0.771-0.645l0.644,0.129l0.387,0.771l1.287-0.256l1.03-1.031l0.771-1.93l1.545-2.446l1.029-0.128l0.646,1.415l1.544,4.763l1.416,0.387v1.931l-1.932,2.188l0.773,0.772l4.763,0.388l0.128,2.701l2.06-1.674l3.348,0.902l4.505,1.674l1.288,1.545l-0.387,1.545l3.09-0.9l5.277,1.414h3.991l3.99,2.189l3.476,2.961l2.06,0.771l2.317,0.129l0.9,0.901l0.901,3.476l0.516,1.545l-1.159,4.504l-1.287,1.676l-3.861,3.863l-1.674,2.959l-2.06,2.316l-0.643,0.129l-0.773,1.932l0.257,5.02l-0.772,4.25l-0.256,1.672l-0.902,1.158l-0.515,3.605l-2.703,3.475l-0.388,2.833l-2.187,1.158l-0.645,1.546h-2.96l-4.249,1.027l-1.931,1.289l-2.96,0.772l-3.219,2.06l-2.188,2.703l-0.386,2.061l0.386,1.416l-0.515,2.703l-0.645,1.416l-1.803,1.416l-2.96,4.764l-2.446,2.189l-1.802,1.156l-1.287,2.574l-1.673,1.545l-0.771-1.545l1.157-1.286l-1.545-1.804l-2.188-1.414l-2.702-1.805l-1.03,0.129l-2.704-2.059L310.05,308.396z", -"M712.198,152.117l1.158,0.901l-0.257,1.674l-2.188,0l-2.189,-0.129l-1.672,0.386l-2.447,-1.029l-0.129,-0.516l1.804,-1.931l1.414,-0.773l1.930,0.645l1.416,0.128l-1.160,-0.644z", -"M534.296,276.857l0.516,0.516l0.900,1.544l3.089,2.962l1.158,0.256l0,1.030l0.772,1.674l2.061,0.385l1.673,1.290l-3.734,1.929l-2.445,2.059l-0.901,1.804l-0.773,1.030l-1.545,0.128l-0.386,1.287l-0.258,0.901l-1.801,0.645l-2.188,-0.129l-1.288,-0.773l-1.159,-0.387l-1.287,0.644l-0.642,1.286l-1.287,0.775l-1.290,1.287l-1.929,0.256l-0.645,-0.901l0.258,-1.673l-1.544,-2.575l-0.772,-0.386l0,-7.852l2.574,-0.130l0.129,-9.654l2.060,0l4.119,-1.030l1.029,1.158l1.674,-1.028l0.901,0l1.416,-0.645l0.515,0.259l-1.030,-2.058z", -"M528.503,81.701l2.574,0l2.961,-0.901l0.643,-1.545l2.189,-0.901l-0.258,-1.159l1.674,-0.514l2.831,-1.031l2.833,0.644l0.387,0.772l1.416,-0.385l2.703,0.643l0.258,1.287l-0.645,0.644l1.672,1.802l1.160,0.515l-0.129,0.515l1.803,0.387l0.772,0.772l-1.030,0.643l-2.187,-0.128l-0.516,0.257l0.644,0.901l0.643,1.674l-2.318,0.129l-0.900,0.643l-0.128,1.416l-1.031,-0.258l-2.446,0.129l-0.772,-0.643l-1.030,0.386l-0.900,-0.386l-2.189,0l-2.959,-0.644l-2.706,-0.258l-2.187,0.129l-1.417,0.644l-1.286,0.129l-0.129,-1.159l-0.772,-1.287l1.672,-0.516l0,-1.029l-0.771,-1.029l0.129,1.288z", -"M225.09,179.022l0,-0.387l0.257,-0.129l0.515,0.258l1.030,-1.544l0.515,-0.130l0,0.387l0.515,0.128l-0.129,0.645l-0.386,1.159l0.258,0.513l-0.258,0.902l0.128,0.258l-0.256,1.416l-0.644,0.643l-0.387,0.129l-0.643,0.901l-0.772,0l0.257,-3.089l0,2.060z", -"M212.989,24.93l-1.416,1.159l-3.862-0.257l-3.347-0.644l1.417-1.288l3.99-0.772l2.317,1.03l-0.901-0.772L212.989,24.93zM212.474,18.107l-1.287,0.13l-5.02-0.13l-0.772-0.772h5.535l1.802,0.515l0.258-0.257L212.474,18.107zM204.622,14.761l3.218,0.901l-0.772,1.03l-3.991,0.515l-2.188-0.644l-1.159-0.901l-0.257-1.159l3.604,0.129l-1.545-0.129L204.622,14.761zM227.793,26.604l-4.377-0.387l-7.208-0.9l-0.901-1.417l-0.258-1.287l-2.703-1.287l-5.664-0.257l-3.09-0.901l1.03-1.031l5.535,0.13l2.962,0.901h5.406l2.317,0.901l-0.643,1.029l3.089,0.515l1.673,0.643l3.605,0.13l3.99,0.257L236.804,23l5.535-0.129L246.716,23l2.832,1.029l0.644,1.159l-1.674,0.644l-3.991,0.644l-3.475-0.387l-7.724,0.387l5.535-0.128L227.793,26.604zM165.489,16.434l3.862,0.386l-0.902,0.901l-5.02,0.772l-3.991-0.9l2.188-0.901L165.489,16.434zM166.261,14.632l3.604,0.644l-3.347,0.515h-4.505l0.128-0.387l2.704-0.901L166.261,14.632zM205.137,40.636l2.703,1.158l-1.673,0.902l-3.605-1.031l-2.188,0.516l-3.09-0.387l1.803-1.673l1.931-1.159l2.059,0.643l-2.06-1.031L205.137,40.636zM315.458,88.781l-1.417,1.673l-1.802,2.317l1.802-0.9l1.802,0.643l-1.029,0.902l2.446,0.772l1.287-0.772l2.574,0.901l-0.772,1.93l1.932-0.386l0.257,1.417l0.9,1.673l-1.157,2.317l-1.288,0.129l-1.673-0.515l0.515-2.189l-0.771-0.386l-3.09,2.317h-1.545l1.801-1.287l-2.573-0.644l-2.832,0.13l-5.278-0.13l-0.386-0.772l1.674-0.901l-1.159-0.773l2.317-1.673l2.702-4.248l1.675-1.545l2.316-0.901l1.288,0.129l0.516-0.772L315.458,88.781zM239.25,51.578l2.96,0.901l3.09,0.901l0.258,1.287l1.93-0.257l1.931,0.9l-2.316,0.903l-4.249-0.774l-1.544-1.158l-2.575,1.416l-3.861,1.416l-0.902-1.544l-3.733,0.257l2.317-1.416l0.386-2.06l0.901-2.445l1.931,0.257l0.515,1.158l1.417-0.514l-1.544-0.772L239.25,51.578zM218.525,6.393l7.08-0.643l5.278-0.386l5.921-0.13l3.604-1.415l11.199-0.773l9.656,0.129l7.723-0.386l18.924,0.514l10.555,1.802L291.9,6.264l-6.437,0.515l-2.445,0.644h5.792L278.126,9.74l-10.169,2.704l-9.913,0.9l3.734,0.258l-1.931,0.515l2.317,1.287l-6.694,1.674l-1.287,1.159l-3.863,0.772l0.387,0.643l3.604,0.258v0.644l-6.049,1.158l-7.081-0.643l-7.981,0.386l-9.012-0.515l-0.385-1.288l5.02-0.643l-1.158-0.902l2.187-0.9l6.437,0.9l-7.981-2.316l2.188-1.03l4.763-0.644l0.773-0.901l-3.862-1.03l-1.159-1.416l7.338,0.129l6.437-0.644l-15.577-0.128l-4.762-1.031l-5.407-1.802l0.515,0.901L218.525,6.393zM253.024,32.01l2.574-1.03l5.922,1.417l3.734,1.287l0.385,1.158l5.02-0.643l2.833,1.674l6.437,1.158l2.317,1.03l2.574,2.575l-4.891,1.158l6.307,1.803l4.248,0.643l3.862,2.446l4.248,0.128l-0.773,1.932l-4.763,3.089l-3.347-1.158l-4.248-2.575l-3.476,0.386l-0.257,1.545l2.832,1.545l3.605,1.287l1.159,0.644l1.673,2.704l-0.902,1.93l-3.347-0.772l-6.821-2.061l3.862,2.318l2.702,1.545l0.516,1.03l-7.339-1.159l-5.793-1.545l-3.218-1.286l0.903-0.774l-3.991-1.415l-3.992-1.287l0.129,0.772l-7.853,0.386l-2.188-0.901l1.675-1.931l5.149-0.129l5.535-0.257l-0.901-1.031l0.901-1.287l3.475-2.702l-0.772-1.159l-1.03-0.901l-4.12-1.288l-5.406-0.901l1.674-0.772l-2.832-1.674l-2.317-0.129l-2.189-0.9l-1.416,0.772l-4.891,0.385l-9.784-0.643l-5.664-0.772l-4.377-0.386l-2.317-0.901l2.832-1.287h-3.862l-0.772-2.704l2.059-2.446l2.704-1.03l6.951-0.772l-1.931,1.802l2.188,1.674l2.447-2.189l6.823-1.159l4.633,2.832l-0.386,1.675l-5.278,0.774L253.024,32.01zM210.672,27.248l5.536,0.128l5.148,0.645l-3.989,2.445l-3.219,0.514l-2.833,1.932l-3.088-0.128l-1.675-2.318v-1.287l1.417-1.158L210.672,27.248zM206.552,9.869l1.931-0.901l2.704-0.128l-1.159-0.644l6.308-0.129l3.348,1.416l8.753,1.673l5.664,2.06l-3.733,0.772l-5.021,2.06l-4.763,0.258l-5.535-0.386l-2.961-1.031l0.129-1.03l2.059-0.772l-4.891,0.129l-2.961-0.902l-1.673-1.287L206.552,9.869zM194.71,31.109l-2.832-2.574l2.961-0.514l3.218,0.643l4.119-0.258l0.515,1.03l-1.544,0.901l3.604,1.803l-0.644,1.416l-3.862,1.415l-2.574-0.257l-1.803-1.03l-5.535-1.544l-1.673-1.16L194.71,31.109zM178.233,30.08l3.089,1.158l1.674,2.574l0.772,1.932l4.634,1.287l4.764,1.287l-0.258,1.159l-4.377,0.257l1.673,1.03l-0.9,1.03h-6.436l-1.804-0.644l-4.376-0.386l-5.278,1.545l-6.565,0.644l-3.604,0.128l-2.704-2.059l-6.05-0.386l-4.505-1.674l2.96-0.772l4.119-0.386l3.863,0.129l3.475-0.516l-5.149-0.644l-5.793,0.258l-3.862-0.129l-1.416-0.901l6.308-1.159l-4.249,0.129l-4.634-0.772l2.189-2.059l1.932-1.031l7.208-1.673l2.703,0.515l-1.287,1.287l5.922-0.772l3.861,1.287l2.961-1.287l2.446,0.901l2.189,2.574l1.416-1.157l-1.932-2.704l2.446-0.387L178.233,30.08zM174.757,22.613l2.446-0.385l2.832,0.128l0.385,1.287l-1.543,1.287l-9.141,0.387l-6.822,1.159l-4.12,0.128l-0.257-0.901l5.535-1.159l-12.228,0.257l-3.734-0.514l3.734-2.575l2.445-0.772l7.596,0.901l4.891,1.673l4.634,0.129l-3.862-2.574l2.446-1.03l1.803,0.643l0.9,1.287l-2.06-0.644L174.757,22.613zM134.336,21.969l4.506-2.059l5.535-1.803l4.12,0.13l3.732-0.387l-0.385,2.06l-2.06,0.901l-2.575,0.129l-5.02,1.158l-4.248,0.386l3.605,0.515L134.336,21.969zM137.812,26.476l3.862,0.514l6.823,0.129l2.703,0.772l2.832,1.158l-3.347,0.644l-6.694,1.674L140,33.427l-0.643,1.287l-5.664,1.287l-1.802-1.03l-5.922-1.544l0.129-0.902l2.188-2.317l2.06-1.159l-1.673-2.188L137.812,26.476zM107.69,81.443l2.574-0.256l-0.773,3.088l2.318,2.188h-1.03l-1.674-1.287l-0.9-1.287l-1.416-0.772l-0.516-1.158l0.13-0.902l-1.287-0.386L107.69,81.443zM199.73,20.682l1.288,0.901V23l-1.416,1.801l-3.218,0.387l-2.961-0.387l0.129-1.545l-4.507,0.13l-0.128-2.06l2.961,0.129l3.99-0.901l-3.862-0.128L199.73,20.682zM181.064,13.344l5.279,0.387l7.337,0.901l2.06,1.288l1.03,1.158l-4.377-0.258l-4.506-0.9l-5.922-0.129l2.576-0.773l-3.348-0.644l0.129,1.03L181.064,13.344zM127.385,92.386l1.288,1.287l2.702,1.158l1.16,1.416l-1.417,0.387l-4.376-1.159l-0.773-1.029l-2.446-0.903l-0.515-0.772l-2.703-0.514l-1.03-1.416l0.129-0.643l2.832,0.643l1.673,0.386l2.575,0.257l-0.901-0.902L127.385,92.386zM315.071,83.502l0.129,2.961l-1.932,1.031l-1.932,0.901l-4.376,1.03l-3.476,2.188l-4.505,0.386l-5.793-0.515h-3.99l-2.832,0.129l-2.318,1.93l-3.346,1.288l-3.863,3.476l-3.089,2.575l2.189-0.515l4.376-3.476l5.664-2.317l3.991-0.257l2.445,1.286l-2.573,1.932l0.772,2.832l0.901,2.06l3.476,1.287l4.504-0.387l2.704-2.96l0.258,1.931l1.673,1.029l-3.347,1.674l-5.921,1.674l-2.703,1.029l-2.961,1.931l-2.06-0.128l-0.128-2.317l4.633-2.189h-4.247l-2.961,0.387l-1.803-1.545v-3.605l-1.157-0.772l-1.804,0.386l-0.9-0.644l-2.06,1.932l-0.901,2.187l-0.902,1.159l-1.158,0.515h-0.901l-0.258,0.772h-4.891h-4.12l-1.287,0.516l-2.703,1.801l-0.387,0.258l-0.256,0.258l-0.387,0.386l-0.257,0.515h-0.643h-0.516h-0.901l-0.772-0.128h-0.902h-0.643l-0.772,0.128h-0.258l-0.515,0.257l-0.386,0.129l0.257,0.386v0.129l0.387,0.772v0.258v0.128l-0.258,0.13l-0.386,0.128l-0.772,0.258l-0.902,0.257l-0.643,0.257l-0.643,0.258l-0.644,0.129h-0.128h-0.387l-0.9,0.128l-0.645,0.129l-0.644,0.258l-0.643,0.385l-0.644,0.258l-0.644,0.257l-0.643,0.258h-0.644l-0.514-0.129l-0.387-0.257l-0.257-0.257v-0.13v-0.257l0.644-0.9l1.286-1.546v-0.128v-0.129l0.259-0.515l0.385-0.515l0.129-0.258l-0.258-0.771l-0.129-0.515v-0.386l-0.127-0.515l-0.13-0.515l-0.129-0.515l-0.128-0.386l-0.13-0.515v-0.257l-0.128-0.387l-0.515-0.386l-0.514-0.128l-0.644-0.258l-0.643-0.257l-0.516-0.257l0.386-0.515v-0.129h-0.128l-0.258-0.258h-0.128l-0.258,0.128l-0.386-0.128l-0.258-0.129h-0.128l-0.129-0.257h-0.129v-0.258v-0.128v-0.129v-0.129h-0.257l-0.258,0.258h-0.772l0.128-0.258h-0.257l-0.386-0.257l-0.128-0.387l-0.13-0.386l-0.514-0.257l-0.515-0.129l-0.515-0.258l-0.515-0.257l-0.515-0.128l-0.515-0.258l-0.515-0.258l-0.514-0.128l-0.258-0.128l-0.387-0.13l-0.643-0.257l-0.772-0.386l-0.772-0.258l-0.773-0.257l-0.386-0.257h-0.258l-0.386-0.258l-0.644-0.129l-0.643,0.129l-0.772,0.258l-0.387,0.128l-0.386,0.129l-0.258,0.129h-0.515h-0.385l-3.219-0.773l-2.188,0.387l-2.703-0.773l-2.704-0.515l-1.93-0.129l-0.772-0.514l-0.516-1.417h-0.901v1.03h-5.536h-9.139h-9.397h-32.182h-2.704H133.95l-5.149-2.574l-1.931-1.287l-4.891-1.03l-1.545-2.446l0.385-1.673l-3.474-1.031l-0.387-2.188l-3.348-2.061v-1.287l1.417-1.287v-1.802l-4.634-1.673l-2.703-3.09l-1.674-1.93l-2.446-1.159l-1.802-1.159l-1.545-1.417l-2.703,0.902l-2.575,1.545L92.5,66.51l-1.802-1.157l-2.704-0.774H85.42V49.133V39.22l5.019,0.644l4.249,1.286l2.832,0.258l2.317-1.158l3.347-0.901l3.99,0.385l3.992-1.157l4.376-0.644l1.931,1.029l1.931-0.644l0.643-1.158l1.803,0.257l4.634,2.447l3.604-1.931l0.387,2.059l3.218-0.387l1.029-0.772l3.219,0.129l4.12,1.159l6.307,0.901l3.733,0.515l2.704-0.129l3.604,1.288l-3.734,1.415l4.763,0.515l7.338-0.257l2.317-0.515l2.832,1.544l2.96-1.287l-2.832-1.158l1.803-0.901l3.218-0.129l2.189-0.258l2.188,0.644l2.703,1.417l2.961-0.258l4.763,1.287l4.248-0.386h3.862l-0.258-1.673l2.446-0.515l4.12,0.9v2.576l1.673-2.06h2.188l1.288-2.704l-2.962-1.673l-3.088-1.03l0.128-2.961l3.218-2.06l3.605,0.515l2.703,1.158l3.604,3.091l-2.317,1.287l5.02,0.514v2.832l3.605-2.189l3.218,1.804l-0.9,1.93l2.702,1.802l2.704-1.931l2.06-2.317l0.129-2.96l3.861,0.257l3.862,0.387l3.733,1.287l0.128,1.416l-2.059,1.416l1.931,1.416l-0.386,1.286l-5.277,1.932l-3.734,0.386l-2.704-0.772l-0.901,1.287l-2.574,2.317l-0.773,1.159l-3.089,1.802l-3.862,0.257l-2.188,1.031l-0.13,1.802l-3.089,0.386l-3.347,2.188l-2.961,2.961l-1.028,2.188l-0.13,3.09l3.991,0.386l1.159,2.576l1.287,2.059l3.733-0.515l5.02,1.159l2.704,1.029l1.93,1.288l3.347,0.643l2.832,1.158l4.507,0.129l2.959,0.258l-0.514,2.446l0.901,2.702l1.931,2.961l3.991,2.576l2.059-0.902l1.545-2.703l-1.416-4.247l-1.931-1.545l4.247-1.159l3.09-1.931l1.545-1.931l-0.257-1.803l-1.802-2.188l-3.348-2.06l3.219-2.832l-1.158-2.445l-0.902-4.249l1.931-0.514l4.506,0.643l2.832,0.257l2.188-0.644l2.575,0.902l3.347,1.545l0.772,1.029l4.763,0.259v2.187l0.901,3.476l2.446,0.386l1.931,1.545l3.862-1.416l2.574-2.961l1.802-1.287l2.06,2.446l3.605,3.347l2.96,3.218l-1.159,1.802l3.604,1.417l2.446,1.545l4.25,0.772l1.802,0.772l1.03,2.317l2.06,0.387l-1.158-1.028L315.071,83.502z", -"M500.183,239.912l-0.902,-1.031l-0.900,0.515l-1.030,1.159l-2.189,-2.832l2.059,-1.544l-1.029,-1.804l0.901,-0.643l1.802,-0.257l0.256,-1.287l1.416,1.287l2.319,0.129l0.900,-1.288l0.258,-1.802l-0.258,-2.059l-1.286,-1.545l1.157,-3.219l-0.642,-0.515l-2.060,0.258l-0.643,-1.415l0.127,-1.160l3.475,0.129l2.062,0.644l2.187,0.643l0.259,-1.416l1.415,-2.446l1.545,-1.544l1.803,0.514l1.800,0.130l-0.257,1.673l-0.770,1.416l-0.517,1.673l-0.386,2.448l0.257,1.414l-0.514,1.030l0,0.901l-0.385,0.901l-1.805,1.287l-1.156,1.417l-1.160,2.575l0,2.190l-0.645,0.898l-1.543,1.288l-1.544,1.804l-1.032,-0.516l-0.128,-0.772l-1.544,0l-0.901,1.030l0.772,0.258z", -"M506.361,206.957l2.318,-0.129l0.384,-0.773l0.517,0.129l0.642,0.515l3.349,-1.029l1.157,-1.031l1.416,-0.901l-0.256,-0.900l0.772,-0.259l2.574,0.130l2.574,-1.287l1.932,-2.962l1.417,-1.030l1.672,-0.514l0.258,1.157l1.545,1.674l0,1.159l-0.387,1.159l0.129,0.773l1.029,0.771l2.059,1.159l1.419,1.159l0,0.901l1.800,1.287l1.159,1.287l0.643,1.544l2.059,1.031l0.389,0.901l-0.903,0.257l-1.674,0l-2.058,-0.257l-0.901,0.129l-0.514,0.643l-0.775,0.129l-1.158,-0.514l-2.961,1.287l-1.287,-0.258l-0.258,0.258l-0.900,1.544l-1.930,-0.514l-2.060,-0.258l-1.674,-1.030l-2.190,-0.900l-1.415,0.900l-1.030,1.417l-0.258,1.802l-1.800,-0.130l-1.803,-0.514l-1.545,1.544l-1.415,2.446l-0.387,-0.772l-0.129,-1.287l-1.157,-0.773l-1.031,-1.416l-0.257,-1.029l-1.288,-1.416l0.258,-0.772l-0.258,-1.160l0.258,-2.060l0.643,-0.515l-1.287,2.702z", -"M548.327,217.513l-0.258,3.217l1.159,0.258l-0.901,1.031l-1.031,0.643l-1.029,1.416l-0.514,1.287l-0.131,2.189l-0.643,1.028l0,2.061l-0.901,0.643l0,1.674l-0.386,0.128l-0.257,1.546l0.643,1.159l0.130,3.347l0.514,2.445l-0.257,1.415l0.514,1.546l1.545,1.546l1.545,3.346l-1.030,-0.258l-3.733,0.386l-0.643,0.387l-0.771,1.673l0.642,1.288l-0.514,3.088l-0.387,2.705l0.772,0.514l1.932,1.031l0.642,-0.516l0.258,2.961l-2.058,0l-1.159,-1.545l-0.903,-1.158l-2.058,-0.387l-0.644,-1.416l-1.674,0.901l-2.187,-0.385l-0.903,-1.158l-1.672,-0.258l-1.289,0l-0.128,-0.772l-0.901,-0.130l-1.287,-0.128l-1.674,0.385l-1.158,0l-0.645,0.131l0.129,-3.091l-0.900,-1.030l-0.129,-1.674l0.385,-1.673l-0.514,-1.031l-0.128,-1.672l-3.219,0l0.258,-0.902l-1.417,0l-0.128,0.388l-1.673,0.128l-0.644,1.545l-0.514,0.772l-1.418,-0.387l-0.900,0.387l-1.801,0.128l-1.031,-1.415l-0.645,-0.773l-0.771,-1.674l-0.643,-1.930l-7.982,-0.131l-0.900,0.389l-0.774,-0.131l-1.157,0.387l-0.387,-0.772l0.773,-0.386l0,-1.159l0.514,-0.642l0.902,-0.516l0.772,0.258l0.901,-1.030l1.544,0l0.128,0.772l1.032,0.516l1.544,-1.804l1.543,-1.288l0.645,-0.898l0,-2.190l1.160,-2.575l1.156,-1.417l1.805,-1.287l0.385,-0.901l0,-0.901l0.514,-1.030l-0.257,-1.414l0.386,-2.448l0.517,-1.673l0.770,-1.416l0.257,-1.673l0.258,-1.802l1.030,-1.417l1.415,-0.900l2.190,0.900l1.674,1.030l2.060,0.258l1.930,0.514l0.900,-1.544l0.258,-0.258l1.287,0.258l2.961,-1.287l1.158,0.514l0.775,-0.129l0.514,-0.643l0.901,-0.129l2.058,0.257l1.674,0l0.903,-0.257l1.672,2.188l1.158,0.387l0.773,-0.515l1.287,0.257l1.416,-0.643l0.644,1.159l-2.446,-1.802z", -"M491.042,98.951l0.128,0.515l-0.385,0.644l1.160,0.514l1.416,0l-0.258,1.159l-1.158,0.386l-1.932,-0.257l-0.643,1.030l-1.288,0.129l-0.387,-0.516l-1.543,1.031l-1.287,0.128l-1.160,-0.643l-0.901,-1.159l-1.288,0.386l0,-1.158l1.932,-1.545l-0.130,-0.644l1.288,0.257l0.772,-0.515l2.317,0l0.515,-0.514l-2.832,-0.772z", -"M457.573,213.521l-1.287,0l-1.802,-0.514l-1.802,0l-3.219,0.514l-1.802,0.773l-2.703,1.030l-0.516,-0.129l0.259,-2.188l0.257,-0.387l-0.129,-1.030l-1.159,-1.158l-0.772,-0.129l-0.901,-0.772l0.644,-1.158l-0.258,-1.287l0.129,-0.773l0.386,0l0.129,-1.159l-0.129,-0.644l0.258,-0.257l1.030,-0.387l-0.772,-2.187l-0.516,-1.031l0.129,-0.901l0.515,-0.257l0.387,-0.258l0.772,0.386l2.059,0l0.514,-0.772l0.516,0.129l0.772,-0.385l0.387,1.157l0.643,-0.257l1.030,-0.515l1.287,0.643l0.387,0.902l1.286,0.515l0.902,-0.644l1.287,-0.129l1.802,0.773l0.772,3.861l-1.158,2.190l-0.644,3.088l1.159,2.317l0.129,-1.030z", -"M266.669,369.286l-3.347-1.544l-0.772-1.676l0.644-1.543l-1.288-1.803l-0.386-4.634l1.158-2.573l2.832-2.062l-3.99-0.772l2.445-2.445l1.03-4.506l2.962,1.031l1.416-5.666l-1.802-0.642l-0.902,3.345l-1.674-0.386l0.902-3.862l0.901-5.02l1.159-1.801l-0.773-2.576l-0.129-3.09l1.03-0.129l1.673-4.248l1.932-4.377l1.158-3.99l-0.643-3.99l0.772-2.316l-0.387-3.348l1.674-3.218l0.386-5.278l0.901-5.535l0.902-6.051l-0.259-4.378l-0.513-3.862l1.415-0.644l0.644-1.417l1.286,1.932l0.387,1.934l1.417,1.156l-0.773,2.576l1.417,2.96l1.028,3.733l1.932-0.387l0.386,0.772l-0.902,2.704l-2.96,1.415v4.378l-0.515,0.9l0.772,1.031l-1.802,1.672l-1.802,2.574l-1.03,2.446l0.257,2.575l-1.672,2.831l1.287,4.636l0.772,0.514l-0.129,2.447l-1.415,2.702v2.188l-2.06,1.803v2.445l0.9,2.574l-1.673,1.03l-0.773,2.446l-0.513,2.703l0.385,3.348l-1.03,0.516l0.645,3.09l1.157,1.029l-0.901,1.158l1.287,0.514l0.258,1.03l-1.158,0.515l0.257,1.545l-0.9,3.605l-1.546,2.316l0.386,1.287l-0.901,1.674l-2.06,1.289l0.258,2.83l0.902,0.902l1.801-0.129v2.061l1.16,1.545l6.436,0.385l2.574,0.387h-2.446l-1.288,0.643l-2.444,1.029l-0.387,2.447l-1.159,0.129l-3.09-0.902L266.669,369.286zM283.274,374.822h1.546l-0.902,1.156l-2.316,0.774h-1.288l-1.544-0.256l-1.932-0.774l-2.831-0.386l-3.476-1.545l-2.704-1.416l-3.732-3.09l2.188,0.646l3.862,1.801l3.476,0.901l1.416-1.159l0.901-1.932l2.445-1.029l1.931,0.258l0.129,0.127l-0.129,5.924H283.274z", -"M500.439,220.859l-0.256,-0.129l-1.674,0.387l-1.673,-0.387l-1.288,0.129l-4.378,0l0.387,-2.188l-1.029,-1.802l-1.158,-0.387l-0.516,-1.287l-0.772,-0.386l0,-0.643l0.772,-1.932l1.289,-2.575l0.771,-0.128l1.544,-1.545l1.029,0l1.546,1.030l1.803,-0.901l0.257,-1.029l0.644,-1.159l0.387,-1.288l1.414,-1.159l0.645,-1.931l0.513,-0.514l0.387,-1.417l0.773,-1.673l2.188,-2.189l0.129,-0.901l0.387,-0.386l-1.160,-1.158l0.128,-0.773l0.774,-0.257l1.029,1.801l0.258,1.804l-0.128,1.802l1.415,2.446l-1.415,-0.129l-0.774,0.257l-1.287,-0.257l-0.514,1.287l1.545,1.546l1.158,0.385l0.387,1.160l0.900,1.930l-0.515,0.644l-1.287,2.702l-0.643,0.515l-0.258,2.060l0.258,1.160l-0.258,0.772l1.288,1.416l0.257,1.029l1.031,1.416l1.157,0.773l0.129,1.287l0.387,0.772l-0.259,1.416l-2.187,-0.643l-2.062,-0.644l3.475,0.129z", -"M760.085,177.992l-2.188-0.902v-2.317l1.288-1.158l2.961-0.773h1.544l0.645,1.031l-1.289,1.287l-0.514,1.545L760.085,177.992zM712.198,152.117l-1.16-0.644l-1.416-0.128l-1.93-0.645l-1.414,0.773l-1.805,1.931l-0.258-2.059l-1.543,0.514l-3.221-0.257l-2.959-0.644l-2.189-1.158l-2.188-0.515l-0.9-1.288l-1.545-0.386l-2.703-1.802l-2.061-0.772l-1.158,0.643l-3.732-1.93l-2.704-1.674l-0.772-2.96l1.932,0.385l0.129-1.416l-1.029-1.416l0.256-2.189l-2.961-3.089l-4.375-1.159l-0.773-2.059l-2.059-1.287l-0.388-0.773l-0.515-1.416l0.129-1.158l-1.674-0.515l-0.772,0.256l-0.772-2.573l0.772-0.516l-0.386-0.643l2.574-1.288l1.93-0.514l2.834,0.257l1.029-1.673l3.476-0.258l0.901-1.158l4.248-1.416l0.387-0.644l-0.26-1.545l1.931-0.643l-2.444-4.635l5.278-1.159l1.415-0.514l1.932-4.892l5.408,0.901l1.416-1.288l0.127-2.704l2.316-0.128l2.061-1.801l1.029-0.258l0.645,1.802l2.317,1.545l3.862,0.901l1.803,2.188l-1.031,3.219l1.031,1.158l3.217,0.387l3.605,0.385l3.217,1.674l1.673,0.386l1.159,2.446l1.672,1.545h2.962l5.536,0.644l3.605-0.386l2.701,0.386l3.861,1.673h3.348l1.159,0.773l3.091-1.416l4.375-0.902l4.121-0.128l3.088-1.03l1.932-1.416l1.931-0.902l-0.515-0.9l-0.774-1.03l1.416-1.674l1.416,0.257l2.832,0.516l2.704-1.417l4.119-1.029l1.932-1.803l1.932-0.772l3.861-0.386l2.189,0.258l0.258-0.902l-2.447-1.931l-2.189-0.772l-2.059,0.901l-2.701-0.386l-1.42,0.386l-0.771-1.158l1.932-2.704l1.286-1.931l3.22,0.9l3.861-1.672v-1.159l2.447-2.832l1.414-0.901v-1.416l-1.545-0.644l2.316-1.417l3.35-0.513h3.475l4.119,0.772l2.316,1.03l1.674,2.703l1.031,1.158l0.9,1.674l1.029,2.574l4.635,0.902l3.219,1.93l1.158,2.447h3.99l2.447-1.03l4.375-0.774l-1.414,2.448l-1.031,1.029l-0.9,2.832l-1.803,2.704l-3.346-0.516l-2.318,0.901l0.771,2.317l-0.385,3.219l-1.416,0.129v1.288l-1.675-1.546l-1.028,1.546l-4.248,1.157l0.387,1.417l-2.319-0.13l-1.286-0.9l-1.803,1.93l-2.961,1.546l-2.189,1.673l-3.732,0.772l-2.059,1.288l-2.832,0.772l1.416-1.288l-0.513-1.028l2.058-1.803l-1.418-1.417l-2.314,0.902l-3.09,1.931l-1.674,1.673l-2.576,0.129l-1.414,1.287l1.414,1.802l2.189,0.387l0.129,1.287l2.061,0.773l3.088-1.931l2.447,1.029l1.672,0.129l0.388,1.416l-3.733,0.772l-1.287,1.416l-2.574,1.288l-1.418,1.931l2.834,1.417l1.158,2.702l1.545,2.446l1.93,2.06l-0.129,2.059l-1.674,0.773l0.645,1.416l1.545,0.773l-0.387,2.187l-0.643,2.189l-1.545,0.258l-1.933,2.832l-2.188,3.604l-2.443,3.219l-3.734,2.446l-3.732,2.317l-3.09,0.258l-1.674,1.157l-0.9-0.772l-1.545,1.287l-3.733,1.416l-2.831,0.386l-0.9,2.833l-1.547,0.129l-0.643-1.931l0.643-1.031l-3.605-0.9l-1.284,0.387l-2.704-0.645l-1.289-1.029l0.387-1.545l-2.445-0.515l-1.287-1.03l-2.316,1.416l-2.576,0.257h-2.187l-1.416,0.644l-1.416,0.386l0.386,3.089h-1.418l-0.256-0.643l-0.128-1.158l-1.931,0.773l-1.16-0.387l-2.059-1.03l0.771-2.317l-1.674-0.515l-0.645-2.446l-2.832,0.386l0.387-3.089l2.445-2.318l0.131-2.188v-2.06l-1.289-0.644l-0.9-1.545l-1.545,0.13l-2.832-0.386l0.9-1.159l-1.285-1.674l-1.934,1.158l-2.314-0.643l-3.092,1.674l-2.445,2.059L712.198,152.117z", -"M262.164,227.425l-1.159,-0.644l-1.287,-0.901l-0.772,0.386l-2.318,-0.386l-0.643,-1.157l-0.515,0.127l-2.704,-1.544l-0.386,-0.902l1.031,-0.129l-0.130,-1.416l0.644,-1.029l1.417,-0.129l1.029,-1.674l1.030,-1.416l-0.901,-0.644l0.515,-1.545l-0.644,-2.445l0.515,-0.772l-0.386,-2.318l-1.030,-1.416l0.258,-1.287l0.900,0.257l0.515,-0.901l-0.643,-1.544l0.386,-0.387l1.416,0.129l1.931,-1.931l1.158,-0.258l0,-0.901l0.515,-2.317l1.545,-1.158l1.674,-0.128l0.257,-0.516l2.059,0.257l2.189,-1.415l1.029,-0.644l1.288,-1.288l0.901,0.258l0.773,0.644l-0.516,0.901l-1.802,0.514l-0.644,1.289l-1.029,0.771l-0.772,1.030l-0.387,1.931l-0.772,1.545l1.415,0.129l0.387,1.287l0.644,0.645l0.128,1.028l-0.257,1.030l0,0.516l0.772,0.257l0.644,0.901l3.475,-0.258l1.546,0.387l1.802,2.317l1.158,-0.258l1.931,0.129l1.545,-0.386l0.902,0.515l-0.517,1.416l-0.513,0.901l-0.259,1.931l0.516,1.802l0.773,0.772l0.127,0.644l-1.416,1.287l1.031,0.644l0.772,0.901l0.773,2.703l-0.516,0.387l-0.515,-1.545l-0.773,-0.902l-0.900,0.902l-5.278,0l0.129,1.674l1.545,0.258l-0.129,1.029l-0.515,-0.258l-1.545,0.387l0,1.931l1.159,1.030l0.515,1.544l-0.129,1.159l-1.159,7.338l-1.416,-1.417l-0.772,0l1.802,-2.704l-2.060,-1.287l-1.673,0.259l-1.030,-0.516l-1.416,0.644l-2.060,-0.257l-1.544,-2.832l-1.288,-0.644l-0.772,-1.287l-1.802,-1.288l0.772,-0.258z", -"M241.695,204.768l-1.415,-0.515l-0.515,-0.644l0.257,-0.386l-0.128,-0.644l-0.644,-0.643l-1.159,-0.514l-0.901,-0.387l-0.128,-0.773l-0.773,-0.515l0.257,0.901l-0.643,0.644l-0.515,-0.772l-0.901,-0.258l-0.386,-0.644l0,-0.772l0.386,-0.901l-0.772,-0.257l0.644,-0.643l0.386,-0.259l1.801,0.644l0.644,-0.257l0.773,0.128l0.515,0.644l0.772,0.128l0.644,-0.514l0.644,1.416l1.029,1.030l1.287,1.157l-1.029,0.260l0,1.157l0.514,0.387l-0.385,0.257l0.128,0.515l-0.257,0.515l0.130,-0.515z", -"M243.626,164.475l2.318,0.257l2.059,0l2.576,0.902l1.028,1.030l2.576,-0.387l0.900,0.644l2.318,1.673l1.673,1.287l0.901,-0.128l1.545,0.644l-0.129,0.772l1.931,0l2.060,1.159l-0.257,0.644l-1.803,0.385l-1.802,0.129l-1.931,-0.257l-3.861,0.257l1.801,-1.544l-1.029,-0.644l-1.802,-0.258l-0.902,-0.772l-0.643,-1.415l-1.546,0l-2.445,-0.645l-0.772,-0.644l-3.604,-0.385l-0.902,-0.515l1.030,-0.644l-2.704,-0.128l-1.930,1.416l-1.030,0l-0.386,0.643l-1.417,0.257l-1.158,-0.257l1.417,-0.772l0.643,-1.030l1.159,-0.515l1.415,-0.515l2.059,-0.257l-0.644,0.387z", -"M556.694,132.549l0.129,0.259l-2.704,1.028l-1.417,-0.385l-0.514,-1.030l1.159,-0.129l0.258,0.129l0.127,0l0.130,0l0.257,0l0.257,-0.129l0.260,-0.128l0.127,0.128l0.258,0l0.128,0l0.128,0l0.130,0.129l0,0.258l0.129,-0.130l0.257,0.130l0.128,0l0.131,-0.130l0.128,0l0.128,0l0.129,-0.128l0.128,0l-0.129,-0.128z", -"M510.866,96.119l-1.158,-0.516l-1.286,0.129l-2.061,-0.901l-1.030,0.258l-1.417,1.030l-2.058,-0.772l-1.544,-1.159l-1.288,-0.645l-0.386,-1.157l-0.387,-0.773l1.932,-0.643l1.029,-0.644l1.932,-0.515l0.642,-0.516l0.645,0.259l1.287,-0.259l1.287,0.903l1.932,0.256l-0.129,0.645l1.414,0.644l0.517,-0.773l1.802,0.386l0.257,0.772l1.930,0.129l1.289,1.416l-0.774,0l-0.385,0.515l-0.644,0l-0.256,0.643l-0.517,0.129l0,0.257l-0.900,0.258l-1.288,0l0.387,-0.644z", -"M491.945,78.87l0.127,1.028l2.703,0.644l-0.128,0.901l2.831,-0.514l1.417,-0.644l3.090,1.029l1.287,0.901l0.642,1.287l-0.770,0.773l1.029,0.901l0.644,1.417l-0.257,1.030l1.158,1.672l-1.287,0.259l-0.645,-0.259l-0.642,0.516l-1.932,0.515l-1.029,0.644l-1.932,0.643l0.387,0.773l0.386,1.157l1.288,0.645l1.544,1.159l-0.901,1.158l-1.030,0.386l0.387,1.802l-0.258,0.386l-0.773,-0.515l-1.287,-0.128l-1.931,0.515l-2.446,-0.128l-0.387,0.772l-1.285,-0.772l-0.903,0.128l-2.832,-0.772l-0.515,0.514l-2.317,0l0.257,-1.931l1.416,-1.802l-3.861,-0.514l-1.287,-0.773l0.129,-1.159l-0.516,-0.515l0.258,-1.930l-0.386,-2.833l1.544,0l0.773,-0.901l0.644,-2.574l-0.515,-0.902l0.515,-0.515l2.317,-0.129l0.385,0.516l1.933,-1.288l-0.645,-1.029l-0.129,-1.544l2.060,0.385l-1.675,0.385z", -"M581.28,192.797l0.645,0.771l-0.129,1.159l-1.545,0.644l1.158,0.772l-0.900,1.416l-0.645,-0.514l-0.642,0.256l-1.545,-0.128l0,-0.773l-0.257,-0.771l0.901,-1.288l1.030,-1.159l1.158,0.257l-0.771,0.642z", -"M488.21,78.87l-1.159,-1.417l0,-2.832l0.387,-0.644l0.772,-0.901l2.447,-0.130l0.900,-0.772l2.188,-0.771l-0.128,1.415l-0.772,0.902l0.385,0.772l1.417,0.386l-0.644,1.029l-0.773,-0.257l-2.060,1.932l0.775,1.288l-1.675,0.385l2.060,0.385zM498.509,75.779l0.900,1.416l-1.545,2.188l-2.831,-1.544l-0.386,-1.158l-3.862,0.902z", -"M272.075,173.873l0.259,-0.516l2.187,0l1.545,0.772l0.772,-0.128l0.387,1.030l1.545,-0.129l-0.129,0.901l1.288,0l1.286,1.030l-1.030,1.159l-1.287,-0.644l-1.287,0.129l-0.773,-0.129l-0.514,0.515l-1.030,0.129l-0.387,-0.644l-0.900,0.386l-1.159,1.803l-0.643,-0.387l-0.130,-0.772l0,-0.773l-0.643,-0.772l0.643,-0.515l0.259,-1.029l0.259,1.416z", -"M497.608,163.703l-9.269,5.150l-7.852,5.276l-3.734,1.288l-2.961,0.257l-0.128,-1.801l-1.159,-0.387l-1.672,-0.772l-0.645,-1.288l-9.139,-5.792l-9.140,-5.922l-10.040,-6.566l0,-0.514l0,-3.347l4.377,-1.931l2.703,-0.514l2.188,-0.644l1.030,-1.417l3.090,-1.029l0.128,-2.061l1.545,-0.128l1.287,-1.030l3.476,-0.515l0.515,-1.030l-0.772,-0.514l-0.902,-2.832l-0.128,-1.674l-1.030,-1.674l2.574,-1.545l2.962,-0.515l1.673,-1.029l2.574,-0.902l4.633,-0.385l4.377,-0.258l1.416,0.385l2.575,-1.028l2.833,0l1.029,0.643l1.930,-0.258l-0.642,1.416l0.514,2.575l-0.642,2.189l-1.674,1.545l0.257,2.059l2.187,1.545l0,0.643l1.674,1.159l1.159,4.763l0.903,2.446l0.126,1.158l-0.513,2.318l0.256,1.158l-0.387,1.546l0.259,1.673l-1.030,1.030l1.546,2.059l0.127,1.159l0.902,1.415l1.286,-0.385l2.060,1.158l-1.288,-1.674z", -"M248.905,236.179l1.415,-2.060l-0.514,-1.159l-1.031,1.288l-1.672,-1.160l0.515,-0.772l-0.387,-2.445l0.901,-0.516l0.515,-1.673l1.030,-1.674l-0.258,-1.158l1.545,-0.514l1.802,-1.030l2.704,1.544l0.515,-0.127l0.643,1.157l2.318,0.386l0.772,-0.386l1.287,0.901l1.159,0.644l0.386,2.059l-0.772,1.674l-2.961,2.832l-3.219,1.030l-1.673,2.446l-0.514,1.802l-1.545,1.030l-1.159,-1.286l-1.030,-0.388l-1.159,0.257l0,-1.029l0.773,-0.643l0.386,1.030z", -"M530.69,71.273l0.387-1.544l-1.029,0.257l-1.674-0.9l-0.256-1.545l3.344-0.773l3.478-0.386l2.833,0.515l2.831-0.129l0.386,0.515l-1.931,1.544l0.9,2.446l-1.158,0.901h-2.317l-2.316-1.028l-1.158-0.387L530.69,71.273z", -"M559.269,147.483l-0.773,1.158l-0.514,1.931l-0.771,1.417l-0.645,0.514l-0.901-0.901l-1.159-1.158l-1.93-3.862l-0.258,0.258l1.158,2.831l1.546,2.703l2.059,4.119l1.03,1.545l0.902,1.545l2.316,2.961l-0.517,0.386l0.13,1.802l3.089,2.447l0.259,0.514h-10.299h-10.557h-10.812v-9.912v-9.526l-0.901-2.189l0.772-1.673l-0.388-1.159l0.903-1.287h3.604l2.574,0.644l2.705,0.773l1.287,0.514l2.059-0.901l1.029-0.772l2.447-0.258l1.93,0.386l0.643,1.287l0.646-0.9l2.187,0.644l2.061,0.128l1.415-0.644L559.269,147.483z", -"M441.482,153.92l0,-1.417l0.387,0l0,0.129l0,0.514l0,4.120l-8.883,-0.129l0.129,6.823l-2.574,0.257l-0.644,1.417l0.515,3.862l-10.557,0l-0.643,0.901l0.129,-1.159l0.129,0l6.050,-0.129l0.257,-1.029l1.159,-1.159l0.901,-3.733l3.733,-2.961l1.287,-3.347l0.773,-0.257l0.900,-2.060l2.319,-0.257l0.900,0.257l1.288,0l0.901,-0.515l-1.544,0.128z", -"M579.351,193.182l-0.901,-0.901l-1.160,-1.545l-1.158,-0.902l-0.773,-0.900l-2.317,-1.030l-1.801,-0.129l-0.644,-0.514l-1.674,0.643l-1.544,-1.287l-0.900,2.059l-3.091,-0.514l-0.258,-1.160l1.160,-3.861l0.258,-1.802l0.770,-0.901l2.061,-0.386l1.288,-1.546l1.543,3.090l0.773,2.446l1.545,1.288l3.604,2.574l1.545,1.545l1.415,1.544l0.903,0.902l1.285,0.902l-0.771,0.642l1.158,0.257z", -"M440.838,114.141l0.129,-1.931l-1.029,-1.158l3.861,-1.932l3.219,0.515l3.604,0l2.960,0.387l2.189,-0.129l4.377,0.129l1.029,1.030l5.021,1.158l0.901,-0.514l3.089,1.158l3.090,-0.258l0.129,1.545l-2.574,1.802l-3.478,0.516l-0.127,0.900l-1.672,1.545l-1.031,2.189l1.031,1.544l-1.547,1.159l-0.642,1.803l-2.061,0.514l-1.802,2.060l-3.476,0l-2.574,0l-1.673,0.901l-1.031,1.030l-1.287,-0.129l-1.030,-1.030l-0.772,-1.545l-2.446,-0.385l-0.257,-0.902l1.030,-1.030l0.258,-0.644l-0.902,-0.900l0.772,-1.674l-1.030,-1.674l1.160,-0.256l0,-1.159l0.514,-0.387l0,-2.189l1.287,-0.643l-0.773,-1.416l-1.545,-0.128l-0.514,0.385l-1.545,0l-0.643,-1.287l-1.158,0.387l1.031,-0.643z", -"M579.351,193.182l-1.030,1.159l-0.901,1.288l0.257,0.771l0,0.773l1.545,0.128l0.642,-0.256l0.645,0.514l-0.645,0.901l1.032,1.545l1.029,1.287l1.029,0.901l8.754,3.218l2.316,0l-7.722,8.110l-3.475,0.129l-2.318,1.932l-1.803,0l-1.029,0.644l-1.030,0.256l-1.931,-1.158l-2.445,1.287l-1.030,1.159l-1.031,-0.387l-0.900,0.258l-1.159,-0.385l-0.772,-0.130l-3.089,-2.574l-2.318,0l-0.129,-0.644l-0.772,-1.288l-1.159,-0.515l-1.158,-2.832l-1.286,-0.644l-0.388,-1.158l-1.416,-1.287l-1.673,-0.129l0.901,-1.545l1.416,-0.127l0.386,-0.774l0,-2.447l0.774,-2.831l1.286,-0.772l0.259,-1.030l1.158,-2.060l1.672,-1.415l1.158,-2.575l0.387,-2.317l3.091,0.514l0.900,-2.059l1.544,1.287l1.674,-0.643l0.644,0.514l1.801,0.129l2.317,1.030l0.773,0.900l1.158,0.902l1.160,1.545l-0.901,-0.901z", -"M542.276,40.893l-0.384,1.932l4.119,1.801l-2.448,2.060l3.089,2.960l-1.801,2.318l2.445,2.060l-1.157,1.802l3.991,1.802l-1.030,1.416l-2.448,1.545l-5.792,3.347l-4.890,0.257l-4.764,1.030l-4.377,0.515l-1.545,-1.416l-2.574,-0.901l0.514,-2.704l-1.286,-2.445l1.286,-1.545l2.447,-1.673l6.180,-2.961l1.800,-0.515l-0.256,-1.159l-3.734,-1.286l-0.901,-1.031l-0.128,-4.120l-4.250,-1.801l-3.475,-1.417l1.545,-0.643l2.961,1.416l3.606,-0.129l2.832,0.644l2.572,-1.159l1.289,-2.060l4.247,-0.900l3.476,1.157l1.159,-1.803z", -"M946.097,274.154l0.773,-0.514l0.901,0.772l-0.516,1.416l-1.672,0.385l-1.418,-0.256l-0.256,-1.289l1.029,-0.900l-1.159,-0.386zM950.089,271.579l-1.160,0.773l-1.545,0.644l-0.385,-1.287l1.031,-1.030l0.899,-0.130l1.160,-0.256l-0.001,0l0.515,-0.129l-0.387,1.287l-0.128,0.128l-0.001,0z", -"M302.584,365.296l-0.129,1.159l-1.03,1.416l2.188-1.031l1.158-1.286L302.584,365.296zM307.733,365.037l1.159,0.388l-0.902,1.415l-2.188,0.772l-0.257-0.9l1.288-1.416L307.733,365.037z", -"M481.903,93.673l1.287,0.773l3.861,0.514l-1.416,1.802l-0.257,1.931l-0.772,0.515l-1.288,-0.257l0.130,0.644l-1.932,1.545l0,1.158l1.288,-0.386l0.901,1.159l-0.128,0.772l0.772,1.029l-0.901,0.774l0.642,2.058l1.418,0.386l-0.258,1.160l-2.446,1.544l-5.277,-0.772l-3.992,0.901l-0.257,1.673l-3.090,0.258l-3.089,-1.158l-0.901,0.514l-5.021,-1.158l-1.029,-1.030l1.416,-1.674l0.515,-5.277l-2.832,-2.833l-2.060,-1.415l-3.991,-1.031l-0.386,-1.931l3.604,-0.644l4.506,0.773l-0.901,-3.090l2.575,1.159l6.306,-2.060l0.775,-2.317l2.317,-0.515l0.385,1.031l1.287,0l1.288,1.029l1.801,1.287l1.416,-0.257l2.318,1.286l0.643,0.259l-0.773,0.129zM488.854,112.082l1.674,-1.030l0.514,2.317l-0.899,2.188l-1.289,-0.643l-0.644,-1.803l-0.644,1.029z", -"M495.162,237.723l-2.833-2.703l-1.801-2.316l-1.544-2.704V229.1l0.642-0.902l0.644-1.932l0.516-2.06l0.903-0.128h3.987l-0.128-3.219l1.288-0.129l1.673,0.387l1.674-0.387l0.257,0.129l-0.127,1.16l0.643,1.414l2.06-0.258l0.643,0.516l-1.157,3.219l1.286,1.545l0.258,2.059l-0.258,1.803l-0.9,1.287l-2.318-0.129l-1.416-1.287l-0.256,1.287l-1.803,0.258l-0.9,0.643l1.028,1.805L495.162,237.723z", -"M444.829,78.483l2.317-0.129l2.831,1.673l-1.415,1.803l-2.061-0.516h-1.673l0.515-1.416L444.829,78.483zM453.84,69.214l3.347-0.257l-2.961,2.96l2.832-0.386h2.832l-0.643,2.189l-2.446,2.446l2.832,0.256l2.575,3.348l1.801,0.515l1.674,3.089l0.773,1.03l3.347,0.515l-0.387,1.674L468,87.365l1.159,1.416l-2.446,1.417h-3.604l-4.634,0.772l-1.158-0.516l-1.804,1.159l-2.573-0.257l-1.803,1.03l-1.415-0.515l3.86-2.832l2.446-0.644l-4.247-0.386l-0.772-1.03l2.831-0.901l-1.416-1.416l0.516-1.803l3.99,0.258l0.387-1.545l-1.804-1.674l-3.346-0.515l-0.646-0.772l1.031-1.158l-0.9-0.772l-1.416,1.286l-0.259-2.573l-1.286-1.417l0.9-2.704l2.189-2.187L453.84,69.214z", -"M577.161,115.042l0.387-1.159l-0.643-1.801l-1.546-1.03l-1.544-0.258l-0.9-0.772l0.256-0.387l2.318,0.516l3.989,0.386l3.604,1.287l0.517,0.515l1.672-0.387l2.445,0.516l0.772,1.158l1.803,0.644l-0.771,0.257l1.287,1.545l-0.258,0.258l-1.545-0.13l-1.93-0.772l-0.645,0.387l-3.733,0.515l-2.702-1.416L577.161,115.042z", -"M319.834,211.463l0.902,0.256l2.058,0.645l2.833,2.316l0.386,1.159l-1.545,2.446l-0.771,1.93l-1.03,1.031l-1.287,0.256l-0.387-0.771l-0.644-0.129l-0.771,0.645l-1.287-0.516l0.772-1.158l0.257-1.159l0.386-1.157l-1.029-1.674l-0.259-1.803L319.834,211.463z", -"M468.13,210.946l-4.249,1.674l-1.545,0.901l-2.446,0.773l-2.317,-0.773l0.129,-1.030l-1.159,-2.317l0.644,-3.088l1.158,-2.190l-0.772,-3.861l-0.386,-2.060l0.129,-1.545l4.634,-0.129l1.289,0.258l0.770,-0.514l1.288,0.256l-0.258,0.772l1.159,1.417l0,1.932l0.258,2.187l0.643,1.030l-0.514,2.318l0.128,1.416l0.773,1.673l-0.644,-0.900z", -"M339.272,4.333l9.011,-1.544l9.525,0.128l3.348,-1.029l9.526,-0.258l21.497,0.386l16.864,2.060l-4.892,1.029l-10.298,0.129l-14.546,0.258l1.287,0.515l9.654,-0.257l8.110,0.901l5.149,-0.773l2.317,0.901l-2.961,1.545l6.824,-1.030l13.130,-1.030l7.981,0.515l1.545,1.159l-10.942,1.931l-1.546,0.644l-8.625,0.514l6.180,0.129l-3.089,1.931l-2.189,1.802l0.129,2.961l3.218,1.674l-4.249,0.128l-4.376,0.902l4.893,1.415l0.643,2.318l-2.832,0.257l3.476,2.317l-5.923,0.129l3.091,1.159l-0.902,0.900l-3.733,0.387l-3.862,0l3.476,1.931l0,1.158l-5.407,-1.158l-1.287,0.773l3.604,0.644l3.476,1.673l1.030,2.188l-4.763,0.515l-2.060,-1.031l-3.347,-1.544l0.901,1.803l-3.090,1.416l7.081,0.129l3.733,0.128l-7.208,2.316l-7.338,2.189l-7.852,0.902l-2.962,0l-2.831,1.030l-3.734,2.832l-5.793,1.931l-1.930,0.128l-3.604,0.644l-3.862,0.644l-2.317,1.673l0,1.802l-1.288,1.802l-4.505,2.189l1.158,2.060l-1.287,2.188l-1.287,2.703l-3.863,0.129l-3.989,-2.188l-5.278,0l-2.704,-1.545l-1.802,-2.574l-4.635,-3.347l-1.415,-1.803l-0.258,-2.316l-3.732,-2.576l0.900,-1.930l-1.802,-1.031l2.703,-3.088l3.991,-1.031l1.159,-1.158l0.515,-2.059l-3.476,-0.259l-6.179,-1.416l2.189,0l6.049,0l-4.634,-1.801l-2.446,-0.902l-4.892,-0.258l2.960,-2.445l-1.544,-1.030l-2.188,-1.931l-3.218,-2.832l-3.475,-1.030l0.128,-1.159l-7.338,-1.545l-5.664,-0.257l-7.208,0.129l-6.565,0.257l-3.090,-0.901l-4.763,-1.673l7.081,-0.901l5.405,-0.130l-11.457,-0.643l-6.050,-1.158l0.387,-1.030l10.169,-1.288l9.784,-1.287l1.030,-1.030l-7.210,-0.901l2.318,-1.029l9.397,-1.931l3.862,-0.258l-1.159,-1.287l6.437,-0.644l8.238,-0.387l8.368,-0.128l2.832,0.901l7.209,-1.545l6.436,1.030l3.347,1.159l6.050,0l-6.436,-1.545l-0.386,1.159z", -"M419.855,191.51l0.387,-1.160l2.961,-0.129l0.515,-0.643l0.901,0l1.030,0.643l0.901,0l0.900,-0.387l0.516,0.773l-1.159,0.644l-1.158,-0.128l-1.159,-0.516l-1.030,0.644l-0.514,0l-0.644,0.386l2.447,0.127z", -"M442.512,206.313l-0.772,-0.129l-0.515,1.158l-0.772,-0.128l-0.515,-0.515l0.128,-1.029l-1.158,-1.674l-0.644,0.257l-0.643,0.130l-0.644,0.127l0,-1.030l-0.387,-0.642l0,-0.773l-0.515,-1.159l-0.772,-1.029l-2.188,0l-0.644,0.514l-0.772,0.129l-0.386,0.515l-0.387,0.772l-1.415,1.159l-1.159,-1.544l-1.030,-1.031l-0.644,-0.386l-0.772,-0.515l-0.257,-1.159l-0.386,-0.643l-0.773,-0.515l1.159,-1.287l0.901,0.128l0.644,-0.515l0.643,0l0.386,-0.386l-0.257,-0.901l0.257,-0.257l0.129,-0.901l1.287,0l1.931,0.643l0.643,0l0.130,-0.258l1.544,0.129l0.387,-0.129l0.128,1.030l0.387,0l0.772,-0.387l0.386,0.130l0.772,0.643l1.159,0.258l0.772,-0.644l0.773,-0.387l0.643,-0.385l0.515,0.129l0.644,0.643l0.386,0.644l1.030,1.158l-0.516,0.645l-0.128,0.900l0.644,-0.257l0.257,0.386l-0.128,0.773l0.772,0.772l-0.515,0.257l-0.129,0.901l0.516,1.031l0.772,2.187l-1.030,0.387l-0.258,0.257l0.129,0.644l-0.129,1.159l0.386,0z", -"M490.785,224.206l-0.515,-0.387l0.900,-2.960l4.378,0l0.128,3.219l-3.988,0l0.903,-0.128z", -"M536.099,131.906l-0.387,0.773l-3.861,0.257l0,-0.515l-3.219,-0.515l0.387,-1.159l1.543,0.902l2.060,-0.129l2.059,0.257l-0.127,0.387l-1.545,0.258zM521.808,116.973l1.804,-0.258l1.029,-0.643l1.417,0.128l0.515,-0.513l0.514,-0.130l1.932,0.130l2.187,-0.773l1.930,1.029l2.445,-0.256l0,-1.417l1.289,0.772l-0.771,1.673l-0.645,0.258l-1.674,0l-1.416,-0.258l-3.218,0.644l1.802,1.545l-1.287,0.387l-1.543,0l-1.418,-1.417l-0.514,0.645l0.643,1.672l1.289,1.159l-1.031,0.644l1.545,1.286l1.286,0.774l0.130,1.545l-2.575,-0.773l0.772,1.417l-1.672,0.256l1.028,2.317l-1.800,0.129l-2.189,-1.287l-1.030,-2.059l-0.516,-1.803l-1.030,-1.288l-1.287,-1.545l-0.258,-0.772l1.288,-1.287l0.128,-0.901l0.901,-0.386l0,0.644z", -"M222.516,189.963l-1.417,-0.514l-1.673,0l-1.159,-0.515l-1.544,-1.159l0.128,-0.773l0.257,-0.643l-0.385,-0.514l1.416,-2.188l3.347,0l0.128,-0.903l-0.385,-0.128l-0.387,-0.644l-1.030,-0.643l-0.901,-0.901l1.158,0l0,-1.416l2.575,0l2.446,0l0,2.060l-0.257,3.089l0.772,0l0.901,0.515l0.258,-0.386l0.771,0.257l-1.158,1.030l-1.287,0.772l-0.257,0.516l0.257,0.514l-0.515,0.773l-0.644,0.129l0.129,0.258l-0.515,0.385l-0.901,0.644l0.128,-0.385z", -"M424.49,197.173l-1.416,-1.030l-1.159,-0.257l-0.643,-0.773l0,-0.386l-0.772,-0.515l-0.258,-0.644l1.545,-0.386l0.901,0.129l0.644,-0.386l5.020,0.129l-0.129,0.901l-0.257,0.257l0.257,0.901l-0.386,0.386l-0.643,0l-0.644,0.515l-0.901,-0.128l1.159,-1.287z", -"M304.257,204.383l1.804,1.028l1.672,1.803l0,1.415l1.030,0l1.417,1.289l1.157,1.028l-0.514,2.319l-1.545,0.772l0.129,0.643l-0.514,1.416l1.157,1.931l0.902,0l0.385,1.545l1.545,2.317l-0.643,0.130l-1.416,-0.259l-0.901,0.644l-1.288,0.515l-0.772,0.128l-0.386,0.515l-1.287,-0.128l-1.674,-1.288l-0.128,-1.287l-0.773,-1.287l0.515,-2.316l0.772,-0.902l-0.644,-1.288l-0.900,-0.386l0.257,-1.159l-0.644,-0.643l-1.287,0.129l-1.930,-2.061l0.772,-0.772l0,-1.287l1.673,-0.385l0.644,-0.516l-0.902,-1.029l0.130,-0.902l-2.187,1.672z", -"M229.981,192.023l-0.385,-0.900l-0.902,-0.258l0.258,-1.031l-0.386,-0.256l-0.515,-0.258l-1.287,0.386l0,-0.386l-0.902,-0.386l-0.515,-0.643l-0.772,-0.129l0.515,-0.773l-0.257,-0.514l0.257,-0.516l1.287,-0.772l1.158,-1.030l0.258,0.129l0.644,-0.386l0.772,-0.129l0.257,0.258l0.386,-0.129l1.288,0.257l1.288,-0.128l0.772,-0.258l0.386,-0.258l0.773,0.129l0.643,0.129l0.772,0l0.515,-0.258l1.287,0.387l0.387,0l0.772,0.515l0.773,0.643l1.030,0.387l0.643,0.772l-0.901,-0.128l-0.386,0.386l-0.902,0.386l-0.643,0l-0.643,0.385l-0.516,-0.127l-0.514,-0.517l-0.258,0.130l-0.258,0.643l-0.257,0l-0.129,0.516l-0.900,0.771l-0.515,0.258l-0.258,0.386l-0.773,-0.515l-0.643,0.643l-0.515,0l-0.643,0.129l0,1.288l-0.387,0l-0.257,0.644l0.902,-0.128z", -"M516.017,103.327l0.643,1.031l0.773,0.772l-1.030,1.029l-1.160,-0.643l-1.931,0l-2.316,-0.386l-1.159,0l-0.643,0.643l-1.031,-0.643l-0.516,1.159l1.290,1.158l0.643,0.901l1.286,1.031l0.901,0.514l1.031,1.159l2.445,1.030l-0.258,0.514l-2.572,-1.030l-1.547,-1.029l-2.444,-0.773l-2.318,-1.931l0.514,-0.257l-1.157,-1.159l-0.130,-0.901l-1.674,-0.386l-0.898,1.159l-0.774,-0.901l0.128,-0.902l0.129,-0.128l1.802,0.128l0.516,-0.386l0.901,0.386l1.030,0l0,-0.772l0.901,-0.257l0.255,-1.030l2.190,-0.773l0.902,0.386l1.930,1.159l2.316,0.515l-1.032,0.387zM502.372,101.654l2.315,0.258l1.289,-0.644l2.446,0l0.515,-0.515l0.385,0l0.515,0.901l-2.190,0.773l-0.255,1.030l-0.901,0.257l0,0.772l-1.030,0l-0.901,-0.386l-0.516,0.386l-1.802,-0.128l0.517,-0.258l-0.646,-1.029l-0.259,1.417z", -"M268.085,173.357l1.673,0.129l2.317,0.387l0.259,1.416l-0.259,1.029l-0.643,0.515l0.643,0.772l0,0.773l-1.802,-0.515l-1.287,0.257l-1.673,-0.257l-1.159,0.515l-1.545,-0.773l0.258,-0.900l2.446,0.385l2.060,0.258l1.029,-0.643l-1.288,-1.159l0,-1.030l-1.673,-0.387l-0.644,0.772z", -"M508.937,100.753l0.900,-1.674l-0.643,-0.643l1.545,0l0.257,-1.158l1.288,0.772l1.028,0.257l2.318,-0.257l0.129,-0.644l1.158,0l1.287,-0.515l0.258,0.258l1.287,-0.387l0.645,-0.643l0.900,-0.129l2.832,0.772l0.645,-0.257l1.415,0.773l0.256,0.643l-1.671,0.643l-1.290,1.803l-1.673,1.802l-2.059,0.515l-1.672,-0.129l-2.060,0.772l-1.032,0.387l-2.316,-0.515l-1.930,-1.159l-0.902,-0.386l-0.515,-0.901l0.385,0z", -"M801.921,250.982l0.258,0.515v0.772l-1.674,2.061l-2.317,0.516l-0.386-0.258l0.258-0.902l1.158-1.674L801.921,250.982zM826.767,245.576l-0.258-2.059l0.516-0.902l0.516-1.03l0.643,0.901v1.285L826.767,245.576zM845.175,242.742v8.755l-2.447-2.188l-2.701-0.514l-0.645,0.771l-3.475,0.129l1.155-2.189l1.677-0.771l-0.645-2.963l-1.287-2.316l-5.279-2.188l-2.188-0.256l-3.992-2.447l-0.898,1.287l-1.031,0.258l-0.516-1.03v-1.157l-2.059-1.288l2.832-1.03h1.932l-0.26-0.644h-3.859l-1.16-1.674l-2.314-0.515l-1.16-1.287l3.605-0.644l1.414-0.901l4.248,1.16l0.516,1.027l0.771,4.248l2.705,1.676l2.316-2.833l3.091-1.674h2.315l2.318,0.901l2.059,1.029l2.832,0.516L845.175,242.742zM761.116,223.434l1.801,1.416l1.803-0.514l1.672,0.257l1.546-1.417l1.288-0.257l2.574,0.772l2.189-0.516l1.414-3.861l1.031-0.901l0.9-3.089h3.09l2.316,0.515l-1.545,2.446l2.059,2.574l-0.514,1.16l3.09,2.573l-3.217,0.257l-0.902,1.803l0.129,2.447l-2.575,1.93l-0.13,2.574l-1.029,4.119l-0.387-0.9l-3.088,1.158l-1.03-1.543l-1.931-0.258l-1.287-0.773l-3.219,0.9l-1.029-1.287l-1.801,0.129l-2.188-0.256l-0.388-3.606l-1.416-0.772l-1.287-2.316l-0.26-2.317l0.26-2.573l1.546-1.675L761.116,223.434zM813.765,234.505l2.961,0.772l0.902,2.059l-2.19-1.029l-2.317-0.256l-1.545,0.129h-1.801l0.643-1.546L813.765,234.505zM807.069,237.209l-1.93-0.516l-0.516-1.158l2.705-0.129l0.643,0.9L807.069,237.209zM809.903,221.117l0.129,1.416l1.674,0.258l0.256,1.158l-0.256,2.316l-1.289-0.258l-0.514,1.674l1.159,1.418l-0.774,0.256l-1.029-1.674l-0.771-3.476l0.514-2.06L809.903,221.117zM796.386,224.593l3.09-0.13l2.703-1.93l0.387,0.643l-2.061,2.704l-2.059,0.515l-2.574-0.644l-4.506,0.257l-2.316,0.387l-0.387,1.932l2.315,2.445l1.546-1.158l5.021-1.031l-0.258,1.289l-1.158-0.387l-1.16,1.545l-2.445,1.029l2.574,3.477l-0.514,0.902l2.445,3.217v1.674l-1.416,0.771l-1.029-0.901l1.287-2.187l-2.703,1.028l-0.645-0.772l0.385-1.031l-1.931-1.543l0.132-2.574l-1.805,0.773l0.257,3.088l0.13,3.861l-1.801,0.387l-1.16-0.773l0.773-2.443l-0.389-2.574l-1.156-0.131l-0.771-1.802l1.158-1.802l0.385-2.061l1.288-4.119l0.515-1.029l2.317-2.061l2.188,0.772L796.386,224.593zM789.306,254.588l-3.604-1.804l2.574-0.644l1.416,0.902l0.902,0.771l-0.131,0.773H789.306zM792.138,249.953l1.803-0.129l2.316-1.029l-0.385,1.544l-3.992,0.644l-3.604-0.258v-1.029l2.188-0.516L792.138,249.953zM783.771,249.566l1.673-0.258l0.645,1.158l-3.09,0.516l-1.803,0.387h-1.545l1.029-1.674h1.416l0.773-0.9L783.771,249.566zM757.511,244.287l0.386,0.902l5.149,0.258l0.514-1.031l5.021,1.288l1.029,1.674l3.99,0.515l3.35,1.674l-3.092,1.031l-2.962-1.16l-2.444,0.129l-2.832-0.258l-2.445-0.514l-3.219-0.902l-1.932-0.387l-1.158,0.387l-4.891-1.158l-0.387-1.158l-2.574-0.129l1.93-2.574l3.219,0.127l2.189,1.031l-1.157-0.256L757.511,244.287zM746.438,229.871l0.388,1.932l0.903,1.415l2.058,0.257l1.289,1.803l-0.645,3.347l-0.129,4.118h-2.961l-2.316-2.188l-3.477-2.188l-1.158-1.674l-2.059-2.188l-1.289-2.06l-2.062-3.733l-2.313-2.188l-0.775-2.317l-1.027-2.187l-2.447-1.674l-1.416-2.318l-2.06-1.416l-2.705-3.09l-0.256-1.287l1.675,0.129l4.247,0.515l2.317,2.575l2.058,1.803l1.548,1.157l2.571,2.962h2.706l2.188,1.801l1.674,2.318l2.06,1.158l-1.157,2.188l1.545,1.03h-1.027H746.438z", -"M448.562,81.83l0.387,1.931l-2.061,2.445l-4.764,1.544l-3.732-0.385l2.188-2.832l-1.415-2.703l3.604-2.06l2.06-1.287l0.515,1.415l-0.515,1.416h1.673L448.562,81.83z", -"M561.458,138.857l-0.516,0.902l-0.900,-0.387l-0.645,1.803l0.774,0.258l-0.774,0.385l-0.128,0.644l1.287,-0.257l0.130,1.029l-1.417,4.249l-1.674,-4.635l0.773,-0.901l-0.258,-0.129l0.772,-1.287l0.515,-1.931l0.385,-0.773l0.130,0l0.900,0l0.259,-0.515l0.643,0l0,1.160l0.256,-0.385z", -"M674.866,131.391l2.961,3.089l-0.256,2.189l1.03,1.416l-0.13,1.416l-1.932-0.385l0.773,2.96l2.703,1.674l3.732,1.93l-1.672,1.16l-1.16,2.573l2.703,1.031l2.447,1.287l3.604,1.545l3.604,0.386l1.674,1.287l2.059,0.257l3.22,0.644h2.188l0.385-1.158l-0.385-1.674l0.258-1.159l1.543-0.514l0.259,2.059l0.129,0.516l2.446,1.029l1.673-0.386l2.188,0.129h2.188l0.257-1.674l-1.158-0.901l2.188-0.258l2.444-2.059l3.092-1.674l2.314,0.643l1.934-1.158l1.285,1.674l-0.899,1.159l2.832,0.386l0.258,1.029l-1.03,0.515l0.256,1.674l-1.93-0.515l-3.475,1.802l0.127,1.545l-1.545,2.317l-0.127,1.287l-1.159,2.189l-2.188-0.515v2.704l-0.642,0.9l0.255,1.159l-1.287,0.643l-1.416-4.247h-0.771l-0.387,1.802l-1.545-1.416l0.901-1.545l1.157-0.129l1.289-2.317l-1.545-0.515l-2.573,0.128l-2.574-0.386l-0.26-1.931l-1.285-0.128l-2.062-1.159l-1.03,1.803l2.06,1.415l-1.802,1.03L702.544,161l1.673,0.643l-0.515,1.674l0.901,2.059l0.515,2.188l-0.387,1.03l-1.931-0.128l-3.218,0.643l0.129,1.931l-1.416,1.674l-3.86,1.802l-3.092,3.218l-2.06,1.675l-2.574,1.673l-0.129,1.287l-1.287,0.644l-2.446,1.029l-1.287,0.129l-0.772,2.059l0.646,3.476l0.127,2.189l-1.159,2.574v4.635l-1.414,0.128l-1.289,2.06l0.903,0.901l-2.448,0.772l-0.9,1.802l-1.157,0.772l-2.576-2.574l-1.159-3.734l-1.027-2.703l-1.03-1.287l-1.416-2.575l-0.646-3.347l-0.513-1.674l-2.448-3.733l-1.029-5.278l-0.899-3.346v-3.347l-0.517-2.446l-3.86,1.544l-1.932-0.257l-3.476-3.347l1.287-0.901l-0.772-1.159l-3.218-2.188l1.801-1.802h5.922l-0.514-2.317l-1.545-1.417l-0.258-2.059l-1.802-1.159l2.961-2.832l3.218,0.129l2.704-2.833l1.802-2.702l2.575-2.704v-1.931l2.187-1.545l-2.059-1.287l-1.031-1.802l-0.899-2.447l1.286-1.157l4.121,0.643l2.961-0.386L674.866,131.391z", -"M585.658,126.628l0.128,0l1.803,3.476l1.802,0.772l0.130,1.545l-1.289,0.902l-0.643,2.187l1.802,2.575l3.347,1.416l1.415,2.060l-0.514,1.931l0.901,0l0,1.416l1.545,1.416l-1.674,-0.128l-1.803,-0.258l-1.930,2.703l-5.020,-0.258l-7.596,-5.406l-3.990,-1.931l-3.218,-0.773l-1.158,-3.218l6.051,-2.832l1.029,-3.218l-0.258,-1.931l1.417,-0.773l1.416,-1.673l1.158,-0.385l3.091,0.385l0.899,0.643l1.287,-0.385l0.128,0.258z", -"M610.502,126.756l2.317,-0.513l1.932,-1.546l1.803,0.129l1.157,-0.515l1.932,0.257l2.961,1.288l2.188,0.387l3.088,2.317l2.060,0.128l0.129,2.188l-1.029,3.477l-0.773,1.930l1.158,0.386l-1.158,1.416l0.902,2.188l0.256,1.674l2.060,0.515l0.129,1.673l-2.445,2.447l1.414,1.415l1.031,1.674l2.574,1.159l0.128,2.446l1.288,0.386l0.259,1.287l-3.992,1.288l-1.030,3.218l-5.020,-0.902l-2.961,-0.515l-2.961,-0.386l-1.160,-3.346l-1.285,-0.515l-2.058,0.515l-2.706,1.287l-3.345,-0.901l-2.705,-2.060l-2.575,-0.773l-1.800,-2.446l-2.061,-3.604l-1.416,0.387l-1.674,-0.902l-1.029,1.030l-1.545,-1.416l0,-1.416l-0.901,0l0.514,-1.931l-1.415,-2.060l-3.347,-1.416l-1.802,-2.575l0.643,-2.187l1.289,-0.902l-0.130,-1.545l-1.802,-0.772l-1.803,-3.476l-0.128,0l-1.288,-1.931l0.516,-0.901l-0.773,-3.089l1.802,-0.772l0.387,1.028l1.415,1.288l1.804,0.386l1.029,-0.129l3.089,-1.930l1.030,-0.258l0.773,0.773l-0.902,1.415l1.674,1.417l0.643,-0.129l0.901,1.931l2.575,0.516l1.803,1.415l3.862,0.385l4.247,-0.643l-0.257,0.644z", -"M426.163,47.974l-0.644,1.672l3.09,1.932l-3.604,2.059l-7.723,1.802l-2.318,0.515l-3.475-0.385l-7.596-0.902l2.703-1.158l-5.922-1.287l4.763-0.516l-0.128-0.9l-5.663-0.644l1.93-1.674l3.991-0.386l4.248,1.803l4.118-1.417l3.349,0.645l4.376-1.417L426.163,47.974z", -"M493.361,100.624l1.672,0.386l0.258,-0.514l2.703,-0.386l0.644,0.900l3.734,0.644l-0.259,1.417l0.646,1.029l-2.063,-0.386l-2.315,1.030l0.257,1.287l-0.387,0.772l0.900,1.417l2.577,1.287l1.287,2.317l2.961,2.189l2.187,-0.130l0.645,0.644l-0.773,0.515l2.445,1.030l1.933,0.772l2.315,1.416l0.257,0.516l-0.513,0.900l-1.417,-1.157l-2.316,-0.516l-1.159,1.803l1.931,0.901l-0.387,1.416l-1.030,0.257l-1.544,2.317l-1.029,0.129l0,-0.772l0.514,-1.417l0.644,-0.643l-1.158,-1.545l-0.772,-1.417l-1.160,-0.256l-0.772,-1.159l-1.673,-0.515l-1.159,-1.030l-2.060,-0.257l-2.061,-1.159l-2.444,-1.802l-1.933,-1.545l-0.772,-2.703l-1.286,-0.258l-2.189,-0.901l-1.288,0.386l-1.545,1.287l-1.157,0.130l0.258,-1.160l-1.418,-0.386l-0.642,-2.058l0.901,-0.774l-0.772,-1.029l0.128,-0.772l1.160,0.643l1.287,-0.128l1.543,-1.031l0.387,0.516l1.288,-0.129l0.643,-1.030l1.932,0.257l1.158,-0.386l-0.258,1.159zM504.944,124.183l2.061,-0.258l-0.901,2.188l0.387,0.773l-0.644,1.415l-2.061,-1.030l-1.286,-0.256l-3.733,-1.416l0.384,-1.288l3.091,0.257l-2.702,0.385zM488.726,116.844l1.287,-0.901l1.675,1.931l-0.387,3.605l-1.288,-0.258l-1.029,0.902l-1.032,-0.644l-0.128,-3.219l-0.642,-1.545l-1.544,-0.129z", -"M256.242,177.22l1.802,0.128l1.416,0.644l0.515,0.772l-1.931,0.129l-0.772,0.386l-1.544,-0.386l-1.545,-1.030l0.385,-0.643l1.030,-0.130l-0.644,-0.130z", -"M560.942,139.759l0.516,-0.902l2.960,1.031l5.278,-2.833l1.158,3.218l-0.514,0.516l-5.407,1.287l2.703,2.703l-0.901,0.515l-0.515,0.902l-2.060,0.386l-0.643,0.901l-1.160,0.900l-2.960,-0.514l-0.128,-0.386l1.417,-4.249l-0.130,-1.029l0.386,-0.902l0,1.544z", -"M847.491,121.479l-2.574,2.704l0.129,2.703l-1.031,2.188l0.387,1.287l-1.287,1.931l-3.477,1.288l-4.762,0.128l-3.861,3.09l-1.801-1.03l-0.129-1.932l-4.635,0.517l-3.22,1.287h-3.089l2.703,2.059l-1.803,4.506l-1.801,1.159l-1.287-1.031l0.643-2.445l-1.672-0.772l-1.031-1.804l2.445-0.9l1.416-1.674l2.705-1.415l2.06-1.803l5.276-0.773l2.961,0.516l2.832-4.764l1.803,1.288l3.861-2.704l1.545-1.029l1.674-3.347l-0.387-2.961l1.158-1.803l2.832-0.386l1.416,3.734v-2.188V121.479zM854.829,108.606l1.93-1.159l0.516,2.961l-3.99,0.772l-2.316,2.703l-4.25-1.931l-1.414,2.962l-3.09,0.128l-0.387-2.703l1.416-2.06l2.832-0.128l0.773-3.734l0.771-2.188l3.219,2.832l2.06,0.901l-1.93-0.644L854.829,108.606zM821.874,136.798l1.416-1.545l1.545,0.257l1.16-1.157l1.93,0.643l0.388,0.9l-1.546,1.674l-1.158-0.901l-1.287,0.643l-0.773,1.545l-1.801-0.772L821.874,136.798z", -"M561.972,214.552l2.318,0l3.089,2.574l0.772,0.130l1.159,0.385l0.900,-0.258l1.031,0.387l1.030,-1.159l2.445,-1.287l1.931,1.158l1.030,-0.256l-2.188,2.960l-0.130,10.169l1.931,2.189l-1.931,1.030l-0.514,1.416l-1.030,0.258l-0.515,1.545l-0.902,1.158l-0.513,1.673l-1.031,1.157l-4.119,-2.445l-0.256,-2.059l-10.042,-5.793l0,-2.832l0,-0.772l1.931,-1.674l1.029,-1.931l-0.771,-1.930l-1.031,-2.704l-1.287,-1.930l1.416,-1.159l2.188,-2.447l1.159,0.515l0.772,1.288l-0.129,-0.644z", -"M656.46,113.111l0.514,-1.159l1.801,-0.386l4.378,0.902l0.514,-1.545l1.545,-0.644l3.732,1.159l1.030,-0.258l4.505,0l3.993,0.386l1.287,0.902l1.674,0.386l-0.387,0.644l-4.248,1.416l-0.901,1.158l-3.476,0.258l-1.029,1.673l-2.834,-0.257l-1.930,0.514l-2.574,1.288l0.386,0.643l-0.773,0.516l-5.020,0.514l-3.347,-0.901l-2.961,0.129l0.257,-1.545l2.961,0.515l1.030,-0.900l2.060,0.257l3.346,-1.932l-3.090,-1.416l-1.929,0.772l-2.061,-1.030l2.317,-1.801l0.770,0.258z", -"M743.995,198.331l-1.031,-1.415l-1.416,-2.834l-0.643,-3.217l1.801,-2.189l3.475,-0.514l2.447,0.387l2.316,1.029l1.160,-1.803l2.446,0.901l0.644,1.803l-0.386,3.218l-4.506,2.059l1.160,1.674l-2.834,0.258l-2.316,1.030l2.317,0.387z", -"M817.112,112.726l0.385,0.514l-1.029-0.129l-1.158,0.902l-0.773,0.901l0.131,1.93l-1.418,0.644l-0.516,0.386l-1.027,0.772l-1.803,0.516l-1.158,0.773v1.158l-0.387,0.257l1.156,0.386l1.418,1.159l-0.385,0.772l-1.033,0.129l-1.93,0.129l-1.029,1.158h-1.284l-0.132,0.257l-1.286-0.514l-0.386,0.386l-0.773,0.257l-0.129-0.514l-0.645-0.258l-0.771-0.386l0.771-1.159l0.645-0.385l-0.256-0.387l0.641-1.545l-0.127-0.386l-1.545-0.258l-1.289-0.772l2.189-1.673l2.961-1.546l1.803-1.93l1.286,0.9l2.319,0.13l-0.387-1.417l4.248-1.157l1.028-1.546L817.112,112.726z", -"M810.933,122.895l2.447,3.218l0.642,1.803l0,3.089l-1.029,1.416l-2.445,0.515l-2.190,1.159l-2.445,0.258l-0.258,-1.545l0.514,-1.932l-1.158,-2.833l1.931,-0.514l-1.802,-2.189l0.131,-0.257l1.285,0l1.029,-1.158l1.930,-0.129l1.033,-0.129l-0.385,0.772z", -"M594.411,146.196l0.645,1.158l-0.257,0.643l0.9,2.06l-1.93,0.129l-0.645-1.288l-2.447-0.257l1.931-2.703L594.411,146.196z", -"M656.46,113.111l-1.547,0.515l-3.603,1.802l-1.160,1.931l-1.030,0.129l-0.771,-1.288l-3.347,-0.128l-0.644,-2.189l-1.287,0l0.258,-2.703l-3.219,-2.060l-4.633,0.259l-3.219,0.385l-2.574,-2.446l-2.189,-1.029l-4.120,-1.931l-0.515,-0.129l-6.951,1.544l0.130,9.914l-1.419,0.128l-1.930,-2.060l-1.800,-0.772l-3.090,0.515l-1.160,0.900l-0.127,-0.643l0.642,-1.159l-0.515,-0.900l-3.089,-0.902l-1.286,-2.446l-1.416,-0.644l-0.130,-0.901l2.702,0.258l0,-1.931l2.320,-0.514l2.316,0.385l0.515,-2.574l-0.387,-1.674l-2.704,0.129l-2.316,-0.644l-3.090,1.159l-2.574,0.643l-1.416,-0.514l0.387,-1.416l-1.803,-1.803l-1.931,0.129l-2.317,-1.802l1.545,-2.060l-0.772,-0.515l2.186,-2.960l2.705,1.544l0.387,-1.931l5.535,-2.962l4.248,-0.127l5.922,1.931l3.088,1.029l2.961,-1.158l4.250,0l3.474,1.416l0.773,-0.772l3.732,0l0.644,-1.159l-4.376,-1.931l2.702,-1.288l-0.515,-0.772l2.575,-0.644l-1.929,-1.931l1.158,-0.901l10.039,-0.901l1.418,-0.644l6.693,-1.028l2.446,-1.160l4.763,0.644l0.901,2.833l2.832,-0.645l3.474,0.901l-0.258,1.416l2.577,-0.128l6.822,-2.575l-1.029,0.901l3.474,2.060l5.922,6.822l1.545,-1.416l3.605,1.546l3.860,-0.644l1.545,0.514l1.289,1.545l1.930,0.515l1.158,1.159l3.478,-0.387l1.414,1.675l-2.060,1.801l-2.317,0.128l-0.127,2.704l-1.416,1.288l-5.408,-0.901l-1.931,4.892l-1.415,0.514l-5.279,1.159l2.445,4.635l-1.931,0.643l0.260,1.545l-1.674,-0.386l-1.287,-0.902l-3.993,-0.386l-4.505,0l-1.030,0.258l-3.732,-1.159l-1.545,0.644l-0.514,1.545l-4.378,-0.902l-1.801,0.386l0.514,-1.159z", -"M748.628,188.549l0.902,-1.288l0.127,-2.189l-2.187,-2.446l-0.129,-2.574l-2.059,-2.189l-2.060,-0.258l-0.516,1.030l-1.545,0l-0.900,-0.385l-2.832,1.544l0,-2.446l0.642,-2.832l-1.800,-0.128l-0.129,-1.546l-1.161,-0.900l0.516,-0.902l2.318,-1.802l0.256,0.643l1.418,0l-0.386,-3.089l1.416,-0.386l1.544,2.188l1.159,2.446l3.347,0l1.028,2.317l-1.672,0.772l-0.774,0.902l3.219,1.674l2.188,3.217l1.673,2.318l2.061,1.931l0.645,1.803l-0.387,2.702l-2.446,-0.901l-1.160,1.803l2.316,1.029z", -"M561.714,137.312l-0.643,0l-0.259,0.515l-0.900,0l0.900,-2.187l1.289,-1.932l0.128,0l1.159,0.128l0.515,1.031l-1.546,1.029l-0.514,1.416l0.129,0z", -"M685.552,206.699l-0.387,2.832l-1.158,0.771l-2.317,0.643l-1.288-2.187l-0.514-3.862l1.285-4.377l1.805,1.545l1.285,1.802L685.552,206.699z", -"M444.442,215.195l-0.643,0l-2.832,-1.287l-2.446,-2.060l-2.317,-1.416l-1.802,-1.673l0.644,-0.902l0.129,-0.771l1.287,-1.546l1.159,-1.157l0.643,-0.130l0.644,-0.257l1.158,1.674l-0.128,1.029l0.515,0.515l0.772,0.128l0.515,-1.158l0.772,0.129l-0.129,0.773l0.258,1.287l-0.644,1.158l0.901,0.772l0.772,0.129l1.159,1.158l0.129,1.030l-0.257,0.387l0.259,-2.188z", -"M543.306,304.922l0.902,0.900l-0.773,1.287l-0.515,0.902l-1.417,0.385l-0.514,0.901l-1.030,0.258l-1.931,-2.059l1.416,-1.803l1.416,-1.029l1.287,-0.516l-1.159,-0.774z", -"M526.442,80.67l-0.128,-0.772l0.259,-0.643l-1.289,-0.515l-2.702,-0.386l-0.644,-2.317l3.088,-0.773l4.506,0.130l2.705,-0.259l0.385,0.515l1.416,0.257l2.574,1.288l0.258,1.159l-2.189,0.901l-0.643,1.545l-2.961,0.901l-2.574,0l-0.644,-0.772l1.417,0.259z", -"M481.516,91.999l0.516,0.515l-0.129,1.159l-0.773,0.129l-0.643,-0.259l0.385,-1.544l-0.644,0z", -"M521.938,76.037l0.128,-2.060l1.288,-1.674l2.573,-0.900l2.060,2.059l2.190,-0.128l0.513,-2.061l2.319,-0.514l1.158,0.387l2.316,1.028l2.318,0l1.286,0.644l0.129,1.287l0.901,1.545l-2.831,1.031l-1.674,0.514l-2.574,-1.288l-1.416,-0.257l-0.385,-0.515l-2.705,0.259l-4.506,-0.130l3.088,-0.773z", -"M505.204,165.376l-1.932,1.03l-1.416-1.544l-4.248-1.159l-1.288-1.674l-2.061-1.158l-1.286,0.385l-0.901-1.415l-0.127-1.159l-1.546-2.059l1.029-1.03l-0.259-1.673l0.387-1.546l-0.256-1.158l0.514-2.318l-0.127-1.158l-0.902-2.446l1.287-0.643l0.257-1.031l-0.257-1.158l1.803-1.029l0.9-0.902l1.287-0.772l0.13-2.06l3.217,0.901l1.03-0.257l2.319,0.514l3.603,1.159l1.159,2.446l2.446,0.515l3.86,1.158l2.833,1.288l1.286-0.644l1.288-1.287l-0.644-2.059l0.9-1.288l1.932-1.288l1.801-0.385l3.734,0.514l0.901,1.287h1.028l0.773,0.516l2.703,0.257l0.645,0.901l-0.902,1.287l0.387,1.159l-0.772,1.673l0.901,2.189v9.526v9.912v5.408h-2.832v1.415l-11.069-5.407l-10.814-5.149L505.204,165.376z", -"M461.436,138.472l0.772,0.514l-0.515,1.030l-3.476,0.515l-1.287,1.030l-1.545,0.128l-0.128,2.061l-3.090,1.029l-1.030,1.417l-2.188,0.644l-2.703,0.514l-4.377,1.931l0,3.218l-0.387,0l0,1.417l-1.544,0.128l-0.901,0.515l-1.288,0l-0.900,-0.257l-2.319,0.257l-0.900,2.060l-0.773,0.257l-1.287,3.347l-3.733,2.961l-0.901,3.733l-1.159,1.159l-0.257,1.029l-6.050,0.129l-0.129,0l0.129,-1.158l1.030,-0.772l0.901,-1.416l-0.129,-0.902l0.901,-1.930l1.545,-1.674l0.901,-0.515l0.644,-1.546l0.128,-1.415l0.901,-1.673l1.802,-1.031l1.803,-2.703l1.287,-1.030l2.574,-0.386l2.060,-1.802l1.416,-0.644l2.189,-2.317l-0.644,-3.347l1.031,-2.317l0.384,-1.416l1.675,-1.803l2.703,-1.287l2.059,-1.029l1.802,-2.833l0.773,-1.673l2.059,0l1.545,1.158l2.575,-0.128l2.832,0.515l1.159,0.128l1.030,1.674l0.128,1.674l-0.902,-2.832z", -"M536.998,97.02l0.644,-0.386l1.675,-0.259l2.059,0.903l1.029,0.128l1.287,0.644l-0.257,0.901l1.030,0.515l0.386,1.030l0.902,0.772l-0.131,0.386l0.517,0.258l-0.773,0.257l-1.545,-0.129l-0.258,-0.386l-0.513,0.258l0.129,0.386l-0.774,0.901l-0.385,0.901l-0.773,0.386l-0.387,-1.287l0.257,-1.159l-0.128,-1.158l-1.545,-1.674l-0.902,-1.029l-0.770,-0.901l0.774,0.258z", -"M598.66,260.508l0.772,1.160l0.643,1.801l0.385,3.219l0.775,1.287l-0.257,1.287l-0.518,0.773l-0.898,-1.545l-0.516,0.772l0.516,2.060l-0.258,1.158l-0.774,0.516l-0.129,2.316l-1.028,3.219l-1.417,3.604l-1.545,5.149l-1.031,3.734l-1.285,3.089l-2.188,0.644l-2.318,1.157l-1.544,-0.641l-2.189,-1.031l-0.773,-1.417l-0.129,-2.317l-0.900,-2.188l-0.258,-1.931l0.387,-1.930l1.287,-0.515l0,-0.901l1.288,-1.932l0.257,-1.802l-0.645,-1.285l-0.514,-1.676l-0.128,-2.446l0.900,-1.544l0.387,-1.673l1.287,-0.130l1.544,-0.514l0.901,-0.516l1.289,0l1.544,-1.544l2.189,-1.674l0.771,-1.415l-0.387,-1.159l1.159,0.386l1.545,-1.931l0,-1.544l0.901,-1.288l-0.902,-1.158z", -"M520.651,114.27l0.385,0l0.129,-0.515l1.545,-0.515l1.544,-0.257l1.288,0l1.287,0.900l0.258,1.674l-0.514,0.130l-0.515,0.513l-1.417,-0.128l-1.029,0.643l-1.804,0.258l-1.029,-0.643l-0.385,-1.160l-0.257,0.900z", -"M432.471,187.646l0.902,-0.514l0.385,-1.674l0.902,0l1.930,0.772l1.416,-0.514l1.160,0.129l0.385,-0.644l10.814,0l0.514,-1.931l-0.385,-0.257l-1.288,-11.587l-1.416,-11.714l4.119,0l9.140,5.922l9.139,5.792l0.645,1.288l1.672,0.772l1.159,0.387l0.128,1.801l2.961,-0.257l0,6.179l-1.543,1.802l-0.130,1.674l-2.445,0.386l-3.735,0.258l-0.900,0.901l-1.802,0.129l-1.673,0l-0.644,-0.516l-1.545,0.387l-2.446,1.158l-0.514,0.774l-2.189,1.285l-0.257,0.645l-1.159,0.514l-1.287,-0.257l-0.773,0.644l-0.385,1.802l-2.189,2.189l0.128,0.900l-0.771,1.159l0.128,1.545l-1.030,0.515l-0.643,0.257l-0.387,-1.157l-0.772,0.385l-0.516,-0.129l-0.514,0.772l-2.059,0l-0.772,-0.386l-0.387,0.258l-0.772,-0.772l0.128,-0.773l-0.257,-0.386l-0.644,0.257l0.128,-0.900l0.516,-0.645l-1.030,-1.158l-0.386,-0.644l-0.644,-0.643l-0.515,-0.129l-0.643,0.385l-0.773,0.387l-0.772,0.644l-1.159,-0.258l-0.772,-0.643l-0.386,-0.130l-0.772,0.387l-0.387,0l-0.128,-1.030l0.128,-0.772l-0.257,-1.030l-1.030,-0.772l-0.515,-1.545l0.129,1.674z", -"M733.437,172.585l-1.672,1.159l-1.803,0.129l-1.287,2.960l-1.158,0.515l1.287,2.317l1.802,1.931l1.030,1.802l-0.901,2.318l-1.029,0.514l0.643,1.416l1.802,2.060l0.387,1.545l-0.129,1.287l1.158,2.447l-1.545,2.445l-1.287,2.832l-0.257,-2.059l0.773,-2.060l-0.902,-1.544l0.257,-2.962l-1.158,-1.416l-0.773,-3.219l-0.516,-3.345l-1.158,-2.318l-1.803,1.415l-3.088,1.932l-1.416,-0.257l-1.673,-0.644l0.902,-3.347l-0.647,-2.575l-2.058,-3.090l0.386,-0.900l-1.671,-0.387l-1.934,-2.188l-0.127,-2.189l0.900,0.387l0.129,-1.932l1.287,-0.643l-0.255,-1.159l0.642,-0.900l0,-2.704l2.188,0.515l1.160,-2.189l0.127,-1.287l1.545,-2.317l-0.127,-1.545l3.474,-1.802l1.930,0.515l-0.256,-1.674l1.030,-0.515l-0.258,-1.029l1.545,-0.130l0.900,1.545l1.289,0.644l0,2.060l-0.131,2.188l-2.445,2.318l-0.387,3.089l2.832,-0.386l0.645,2.446l1.674,0.515l-0.772,2.317l2.059,1.030l1.160,0.387l1.930,-0.773l0.128,1.158l-2.318,1.802l-0.516,0.902l1.544,-0.643z", -"M701.642,94.188l2.832-0.515l5.148-2.317l4.121-1.287l2.316,0.901h2.832l1.803,1.287l2.702,0.129l3.862,0.644l2.574-1.931l-1.029-1.545l2.703-2.832l3.09,1.158l2.445,0.257l3.09,0.773l0.516,1.931l3.861,1.158l2.574-0.515l3.348-0.257l2.705,0.257l2.701,1.287l1.674,1.417h2.445l3.348,0.386l2.574-0.644l3.477-0.387l3.99-1.93l1.545,0.258l1.414,0.9l3.219-0.128l-1.287,1.931l-1.932,2.704l0.771,1.158l1.42-0.386l2.701,0.386l2.059-0.901l2.189,0.772l2.448,1.931l-0.259,0.902l-2.189-0.258l-3.861,0.386l-1.932,0.772l-1.932,1.803l-4.119,1.029l-2.703,1.417l-2.832-0.516l-1.416-0.257l-1.416,1.674l0.773,1.03l0.516,0.9l-1.932,0.902l-1.932,1.416l-3.088,1.03l-4.121,0.128l-4.375,0.902l-3.09,1.416l-1.16-0.773h-3.347l-3.862-1.673l-2.701-0.386l-3.604,0.386l-5.536-0.644h-2.962l-1.673-1.545l-1.158-2.446l-1.673-0.386l-3.218-1.674l-3.605-0.385l-3.216-0.387l-1.032-1.158l1.032-3.219l-1.804-2.188l-3.862-0.901l-2.317-1.545L701.642,94.188z", -"M432.471,187.646l-1.802,-1.930l-1.674,-1.931l-1.801,-0.772l-1.288,-0.773l-1.416,0l-1.287,0.643l-1.416,-0.257l-0.901,0.901l-0.258,-1.416l0.773,-1.416l0.386,-2.445l-0.386,-2.704l-0.258,-1.417l0.258,-1.287l-0.773,-1.287l-1.416,-1.158l0.643,-0.901l10.557,0l-0.515,-3.862l0.644,-1.417l2.574,-0.257l-0.129,-6.823l8.883,0.129l0,-4.120l10.040,6.566l-4.119,0l1.416,11.714l1.288,11.587l0.385,0.257l-0.514,1.931l-10.814,0l-0.385,0.644l-1.160,-0.129l-1.416,0.514l-1.930,-0.772l-0.902,0l-0.385,1.674l0.902,-0.514z", -"M558.368,258.062l-0.773,1.932l0.773,3.605l0.901,-0.130l1.030,0.902l1.030,1.930l0.258,3.476l-1.160,0.645l-0.772,1.801l-1.802,-1.674l-0.258,-1.931l0.645,-1.158l-0.130,-1.159l-1.159,-0.644l-0.643,0.259l-1.545,-1.289l-1.416,-0.770l0.772,-2.447l0.773,-0.902l-0.516,-2.317l0.645,-2.189l0.386,-0.644l-0.644,-2.315l-1.287,-1.160l2.704,0.514l1.415,1.933l-0.773,-3.732z", -"M203.592,157.266l-1.030,2.446l-0.515,1.931l-0.257,3.605l-0.257,1.287l0.514,1.416l0.773,1.287l0.644,2.188l1.802,1.931l0.515,1.545l1.158,1.416l2.832,0.643l1.029,1.159l2.447,-0.772l2.060,-0.258l1.930,-0.513l1.803,-0.388l1.672,-1.158l0.644,-1.545l0.258,-2.317l0.386,-0.772l1.803,-0.644l2.961,-0.644l2.316,0l1.674,-0.129l0.644,0.516l-0.129,1.287l-1.417,1.674l-0.643,1.544l0.515,0.515l-0.386,1.158l-0.772,2.060l-0.644,-0.644l-0.515,0l-0.515,0.130l-1.030,1.544l-0.515,-0.258l-0.257,0.129l0,0.387l-2.446,0l-2.575,0l0,1.416l-1.158,0l0.901,0.901l1.030,0.643l0.387,0.644l0.385,0.128l-0.128,0.903l-3.347,0l-1.416,2.188l0.385,0.514l-0.257,0.643l-0.128,0.773l-2.961,-2.832l-1.416,-0.901l-2.189,-0.772l-1.544,0.257l-2.189,1.030l-1.287,0.258l-1.930,-0.773l-2.060,-0.515l-2.446,-1.158l-2.061,-0.387l-3.088,-1.287l-2.189,-1.286l-0.644,-0.645l-1.545,-0.258l-2.702,-0.772l-1.159,-1.287l-2.961,-1.545l-1.288,-1.673l-0.644,-1.287l0.902,-0.258l-0.258,-0.772l0.644,-0.772l0,-0.902l-0.901,-1.158l-0.257,-1.159l-0.902,-1.287l-2.445,-2.704l-2.703,-2.059l-1.288,-1.674l-2.317,-1.159l-0.515,-0.643l0.386,-1.674l-1.287,-0.643l-1.673,-1.287l-0.644,-1.802l-1.416,-0.258l-1.545,-1.416l-1.287,-1.288l-0.129,-0.901l-1.416,-2.060l-1.029,-2.059l0.128,-1.030l-1.931,-1.030l-0.901,0.129l-1.544,-0.773l-0.515,1.159l0.515,1.288l0.257,1.930l0.901,1.160l1.931,1.801l0.515,0.644l0.386,0.257l0.386,0.902l0.515,0l0.515,1.673l0.773,0.644l0.643,1.030l1.673,1.415l0.902,2.446l0.772,1.159l0.773,1.287l0.128,1.416l1.287,0.129l1.030,1.158l1.029,1.288l-0.128,0.386l-1.029,1.030l-0.516,0l-0.772,-1.673l-1.673,-1.546l-1.931,-1.286l-1.416,-0.644l0.129,-1.931l-0.515,-1.545l-1.288,-0.773l-1.802,-1.287l-0.386,0.386l-0.644,-0.643l-1.673,-0.643l-1.545,-1.675l0.129,-0.128l1.158,0.128l1.030,-1.029l0,-1.159l-2.059,-1.931l-1.545,-0.772l-1.031,-1.674l-0.900,-1.802l-1.287,-2.189l-1.159,-2.317l3.090,-0.256l3.475,-0.259l-0.258,0.515l3.992,1.288l6.178,1.931l5.407,0l2.060,0l0.129,-1.158l4.633,0l0.902,0.900l1.416,0.901l1.545,1.159l0.900,1.416l0.772,1.545l1.288,0.772l2.316,0.772l1.674,-2.058l2.189,-0.130l1.930,1.159l1.288,1.802l1.030,1.545l1.545,1.545l0.515,1.931l0.772,1.287l2.188,0.773l1.931,0.643l-1.030,0.129z", -"M740.39,210.174l0.642,0.258l1.545,1.673l1.160,1.803l0.129,1.803l-0.258,1.287l0.258,0.900l0.129,1.545l1.029,0.772l1.030,2.318l0,0.901l-1.932,0.257l-2.574,-2.059l-3.217,-2.060l-0.260,-1.416l-1.543,-1.802l-0.386,-2.188l-1.032,-1.546l0.387,-1.931l-0.643,-1.158l0.516,-0.385l2.188,1.157l0.129,1.287l1.802,-0.257l-0.901,1.159zM760.601,221.632l2.058,0.901l2.061,-0.514l0.513,-2.318l1.159,-0.515l3.218,-0.515l1.932,-2.189l1.287,-1.673l1.287,1.417l0.516,-0.902l1.285,0l0.260,-1.674l0.127,-1.287l2.060,-1.931l1.287,-2.059l1.159,0l1.287,1.286l0.129,1.159l1.802,0.772l2.319,0.773l-0.258,1.158l-1.803,0.129l0.514,1.288l-2.059,0.901l-2.316,-0.515l-3.090,0l-0.900,3.089l-1.032,0.901l-1.414,3.862l-2.189,0.515l-2.574,-0.772l-1.289,0.257l-1.545,1.417l-1.672,-0.257l-1.803,0.514l-1.801,-1.416l0.515,1.802z", -"M558.368,258.062l1.931,-0.256l3.347,0.771l0.644,-0.386l1.930,0l0.902,-0.900l1.672,0.128l2.961,-1.030l2.060,-1.674l0.516,1.287l-0.129,2.705l0.257,2.316l0.128,4.248l0.516,1.289l-0.772,1.930l-1.160,1.803l-1.673,1.673l-2.446,1.030l-3.090,1.416l-2.961,2.830l-1.029,0.517l-1.930,1.930l-1.160,0.643l-0.128,1.803l1.288,2.060l0.514,1.674l-0.129,1.415l0.644,-0.770l-0.129,2.573l-0.386,1.288l0.643,0.514l-0.387,1.030l-1.157,1.030l-2.187,0.901l-3.349,1.417l-1.159,1.030l0.259,1.158l0.643,0.127l-0.130,1.418l-2.058,0l-0.259,-1.158l-0.385,-1.289l-0.258,-0.901l0.514,-3.090l-0.771,-1.801l-1.287,-3.863l2.832,-3.089l0.773,-1.930l0.386,-0.258l0.257,-1.545l-0.385,-0.773l0.128,-2.061l0.513,-1.801l0,-3.475l-1.415,-0.774l-1.287,-0.254l-0.515,-0.645l-1.287,-0.645l-2.317,0.129l-0.129,-1.029l-0.258,-1.932l8.239,-2.189l1.545,1.289l0.643,-0.259l1.159,0.644l0.130,1.159l-0.645,1.158l0.258,1.931l1.802,1.674l0.772,-1.801l1.160,-0.645l-0.258,-3.476l-1.030,-1.930l-1.030,-0.902l-0.901,0.130l-0.773,-3.605l-0.773,1.932z", -"M509.322,304.019l-2.059,-2.059l-1.030,-2.060l-0.644,-2.575l-0.645,-1.930l-0.900,-4.120l0,-3.216l-0.387,-1.545l-1.029,-1.032l-1.416,-2.317l-1.414,-3.218l-0.647,-1.674l-2.187,-2.575l-0.128,-2.058l1.288,-0.516l1.674,-0.515l1.672,0.128l1.674,1.159l0.384,-0.128l10.944,-0.128l1.801,1.287l6.566,0.385l4.892,-1.158l2.187,-0.644l1.803,0.258l1.030,0.513l0,0.259l-1.416,0.645l-0.901,0l-1.674,1.028l-1.029,-1.158l-4.119,1.030l-2.060,0l-0.129,9.654l-2.574,0.130l0,7.852l0,9.912l-2.446,1.416l-1.418,0.129l-1.673,-0.514l-1.288,-0.129l-0.383,-1.158l-1.033,-0.773l1.286,-1.415z", -"M911.856,283.809l2.188,1.673l1.416,1.159l-1.029,0.643l-1.545,-0.643l-1.932,-1.287l-1.672,-1.416l-1.803,-1.932l-0.386,-0.901l1.158,0.129l1.545,0.901l1.158,0.902l-0.902,-0.772z", -"M471.091,194.855l0,-1.930l-3.091,-0.515l-0.128,-1.417l-1.545,-1.673l-0.258,-1.287l0.129,-1.287l1.802,-0.129l0.900,-0.901l3.735,-0.258l2.445,-0.386l0.130,-1.674l1.543,-1.802l0,-6.179l3.734,-1.288l7.852,-5.276l9.269,-5.150l4.248,1.159l1.416,1.544l1.932,-1.030l0.643,4.249l1.029,0.643l0.129,0.901l1.030,0.901l-0.514,1.159l-1.030,5.406l-0.130,3.605l-3.475,2.446l-1.158,3.605l1.158,1.029l0,1.673l1.674,0.130l-0.258,1.158l-0.774,0.257l-0.128,0.773l-0.514,0.128l-1.803,-2.960l-0.644,-0.129l-2.058,1.545l-2.061,-0.772l-1.545,-0.258l-0.772,0.386l-1.545,0l-1.544,1.159l-1.416,0l-3.219,-1.417l-1.286,0.644l-1.416,0l-1.030,-1.030l-2.704,-1.029l-2.832,0.385l-0.772,0.516l-0.259,1.544l-0.770,1.159l-0.258,2.447l-2.059,-1.674l-0.901,0.127l0.901,-0.773z", -"M488.082,214.166l-2.704,0.900l-1.029,-0.128l-1.031,0.644l-2.188,-0.129l-1.415,-1.674l-0.902,-1.931l-1.931,-1.802l-2.059,0.128l-2.318,0l0.130,-4.376l0,-1.802l0.386,-1.674l0.901,-0.773l1.288,-1.672l-0.258,-0.773l0.514,-1.031l-0.643,-1.673l0.129,-0.771l0.258,-2.447l0.770,-1.159l0.259,-1.544l0.772,-0.516l2.832,-0.385l2.704,1.029l1.030,1.030l1.416,0l1.286,-0.644l3.219,1.417l1.416,0l1.544,-1.159l1.545,0l0.772,-0.386l1.545,0.258l2.061,0.772l2.058,-1.545l0.644,0.129l1.803,2.960l0.514,-0.128l1.160,1.158l-0.387,0.386l-0.129,0.901l-2.188,2.189l-0.773,1.673l-0.387,1.417l-0.513,0.514l-0.645,1.931l-1.414,1.159l-0.387,1.288l-0.644,1.159l-0.257,1.029l-1.803,0.901l-1.546,-1.030l-1.029,0l-1.544,1.545l-0.771,0.128l-1.289,2.575l0.772,-1.932z", -"M234.359,197.045l-0.902,-0.774l-1.287,-1.158l-0.643,-0.901l-1.159,-0.773l-1.288,-1.287l0.258,-0.386l0.514,0.386l0.129,-0.129l0.902,-0.128l0.257,-0.644l0.387,0l0,-1.288l0.643,-0.129l0.515,0l0.643,-0.643l0.773,0.515l0.258,-0.386l0.515,-0.258l0.900,-0.771l0.129,-0.516l0.257,0l0.258,-0.643l0.258,-0.130l0.514,0.517l0.516,0.127l0.643,-0.385l0.643,0l0.902,-0.386l0.386,-0.386l0.901,0.128l-0.129,0.258l-0.129,0.514l0.258,1.030l-0.643,0.901l-0.258,1.159l-0.129,1.158l0.129,0.644l0.128,1.287l-0.514,0.258l-0.129,1.159l0.129,0.644l-0.516,0.771l0.130,0.645l0.386,0.514l-0.644,0.514l-0.772,-0.128l-0.515,-0.644l-0.773,-0.128l-0.644,0.257l-1.801,-0.644l0.386,-0.259z", -"M481.646,82.859h2.188l0.515,0.902l-0.644,2.574l-0.773,0.901h-1.544l0.387,2.833l-1.416-0.644l-1.674-1.158l-2.573,0.643l-1.933-0.258l1.417-0.772l2.317-3.991L481.646,82.859z", -"M494.905,68.442l-1.802,-1.674l-5.279,3.090l-3.603,0.643l-3.734,-1.415l-0.902,-2.833l-0.900,-6.179l2.445,-1.802l7.080,-2.189l5.407,-2.832l4.892,-3.733l6.435,-5.278l4.508,-1.931l7.465,-3.476l5.922,-1.158l4.377,0.129l4.119,-2.188l4.893,0.128l4.889,-0.515l8.368,1.931l-3.474,0.773l2.961,1.672l-4.507,1.031l-2.189,0.257l1.159,-1.803l-3.476,-1.157l-4.247,0.900l-1.289,2.060l-2.572,1.159l-2.832,-0.644l-3.606,0.129l-2.961,-1.416l-1.545,0.643l-1.673,0.129l-0.513,1.803l-5.022,-0.387l-0.644,1.417l-2.702,0l-1.674,1.931l-2.703,2.960l-4.248,3.862l1.031,0.901l-0.903,1.030l-2.705,0l-1.800,2.446l0.127,3.605l1.803,1.415l-0.900,3.090l-2.318,1.931l1.158,-1.545z", -"M702.673,151.859l-0.258,1.159l0.385,1.674l-0.385,1.158l-2.189,0l-3.219,-0.644l-2.059,-0.257l-1.674,-1.287l-3.603,-0.386l-3.604,-1.545l-2.447,-1.287l-2.703,-1.031l1.160,-2.573l1.672,-1.160l1.158,-0.643l2.061,0.772l2.703,1.802l1.545,0.386l0.900,1.288l2.188,0.515l2.189,1.158l2.959,0.644l-3.221,-0.257z", -"M941.72,334.914l-1.030,1.417l-1.287,1.931l-2.058,1.030l-0.514,-0.772l-1.160,-0.386l1.545,-2.189l-0.774,-1.416l-2.961,-1.159l0.131,-0.901l1.930,-1.030l0.387,-2.059l-0.129,-1.674l-1.029,-1.803l0,-0.514l-1.290,-1.158l-2.058,-2.317l-1.158,-1.932l1.027,-0.256l1.418,1.544l2.187,0.773l0.774,2.315l1.930,2.834l0,-1.803l1.289,0.773l0.384,1.931l2.190,0.901l1.803,0.257l1.545,-1.030l1.285,0.258l-0.645,2.446l-0.771,1.544l-2.059,0l-0.771,0.773l0.255,1.158l0.386,-0.514zM922.282,344.312l2.319,-1.416l1.671,-1.416l1.161,-1.931l1.029,-0.772l0.387,-1.416l1.929,-1.287l0.514,1.158l0.645,1.030l1.933,-1.030l0.770,1.160l0,1.157l-1.028,1.160l-1.802,2.059l-1.289,1.029l1.029,1.288l-2.188,0l-2.316,1.030l-0.645,1.803l-1.545,2.703l-2.060,1.286l-1.414,0.773l-2.445,-0.128l-1.805,-0.901l-2.830,-0.130l-0.516,-1.030l1.416,-1.930l3.477,-2.704l1.672,-0.515l-1.931,1.030z", -"M617.197,159.841l1.157,1.802l1.545,1.030l1.932,0.387l1.674,0.385l1.158,1.545l0.772,0.902l0.902,0.385l0,0.644l-1.031,1.545l-0.387,0.772l-1.158,0.902l-1.029,1.802l-1.157,-0.130l-0.517,0.645l-0.514,1.416l0.385,1.673l-0.257,0.387l-1.286,0l-1.675,1.028l-0.257,1.289l-0.642,0.514l-1.675,0l-1.031,0.773l0,1.029l-1.287,0.773l-1.416,-0.257l-1.802,0.900l-1.286,0.129l-0.900,-1.930l-2.061,-4.378l7.981,-2.702l1.802,-5.408l-1.159,-1.931l0,-1.030l0.773,-1.159l0.129,-1.029l1.159,-0.515l-0.517,-0.386l0.258,-1.802l-1.417,0zM616.294,156.752l0.773,-0.902l0.387,0.257l-0.257,1.159l-0.385,0.386l0.518,0.900z", -"M255.47,207.471l-0.902,-0.772l-0.643,-1.416l0.643,-0.773l-0.643,-0.127l-0.514,-0.903l-1.288,-0.771l-1.159,0.257l-0.644,0.900l-1.029,0.644l-0.644,0.129l-0.257,0.515l1.287,1.545l-0.643,0.258l-0.387,0.385l-1.287,0.129l-0.515,-1.544l-0.387,0.386l-0.772,-0.129l-0.643,-1.030l-1.030,-0.258l-0.773,-0.257l-1.158,0l0,0.644l-0.387,-0.515l0.130,-0.515l0.257,-0.515l-0.128,-0.515l0.385,-0.257l-0.514,-0.387l0,-1.157l1.029,-0.260l1.030,1.031l-0.129,0.516l1.159,0.129l0.129,-0.129l0.772,0.643l1.416,-0.258l1.031,-0.643l1.673,-0.515l0.900,-0.901l1.545,0.257l-0.129,0.257l1.545,0l1.159,0.516l0.900,0.773l1.031,0.771l-0.386,0.387l0.643,1.544l-0.515,0.901l-0.900,-0.257l0.258,-1.287z", -"M277.74,274.281l-0.644,1.417l-1.415,0.644l-2.704-1.543l-0.258-1.031l-5.278-2.705l-4.891-2.959l-2.059-1.674l-1.159-2.188l0.515-0.773l-2.318-3.605l-2.703-4.891l-2.446-5.406l-1.158-1.289l-0.902-1.93l-2.058-1.802l-1.932-1.028l0.901-1.16l-1.287-2.576l0.772-1.93l2.189-1.672l0.386,1.029l-0.773,0.643v1.029l1.159-0.257l1.03,0.388l1.159,1.286l1.545-1.03l0.514-1.802l1.673-2.446l3.219-1.029l2.961-2.832l0.772-1.674l-0.386-2.06l0.772-0.258l1.802,1.288l0.772,1.287l1.288,0.644l1.544,2.832l2.06,0.257l1.416-0.644l1.03,0.517l1.673-0.26l2.06,1.287l-1.802,2.704h0.772l1.416,1.417l-2.446-0.129l-0.386,0.514l-2.188,0.516l-3.089,1.802l-0.129,1.288l-0.772,0.9l0.257,1.416l-1.545,0.773v1.158l-0.772,0.516l1.158,2.445l1.546,1.674l-0.644,1.158l1.801,0.129l1.03,1.416h2.317l2.317-1.545l-0.256,4.119l1.287,0.256l1.416-0.383l2.445,4.248l-0.644,0.9l-0.128,1.932v2.316l-1.159,1.287l0.515,1.029l-0.643,0.9l1.158,2.318L277.74,274.281z", -"M845.175,242.742l-0.129-8.752l4.635,1.803l5.02,1.543l1.932,1.417l1.416,1.417l0.385,1.544l4.505,1.673l0.646,1.416l-2.445,0.258l0.643,1.803l2.316,1.802l1.803,2.832l1.545-0.128l-0.129,1.287l2.059,0.387l-0.771,0.514l2.831,1.158l-0.258,0.773l-1.803,0.129l-0.641-0.645l-2.32-0.258l-2.701-0.385l-2.061-1.803l-1.545-1.416l-1.414-2.446l-3.479-1.159l-2.314,0.771l-1.673,0.902l0.386,2.06l-2.189,0.901l-1.416-0.515l-2.832-0.129V242.742zM876.454,236.822l1.031,0.9l0.258,1.418l-0.771,0.641l-0.518-1.545l-0.643-1.027l-1.288-0.902l-1.545-1.158l-1.931-0.773l0.773-0.643l1.416,0.772l1.029,0.515l1.031,0.643L876.454,236.822zM872.851,242.742l-1.545,0.645l-1.286,0.645h-1.546l-2.188-0.772l-1.545-0.772l0.256-0.901l2.447,0.388l1.416-0.131l0.387-1.287l0.385-0.127l0.261,1.414l1.543-0.128l0.772-0.901l1.543-1.031l-0.256-1.545l1.543-0.127l0.517,0.515v1.416l-0.901,1.674l-1.416,0.259L872.851,242.742zM882.118,241.328l0.775,0.645l1.414,1.674l1.158,0.899l-0.258,0.771l-0.771,0.26l-1.159-1.03l-1.287-1.673l-0.516-2.061l0.387-0.258L882.118,241.328z", -"M790.722,192.797l-1.416,-2.061l2.318,0l1.031,1.030l-0.775,2.316l1.158,1.285zM795.485,200.134l0.645,-0.773l0.256,-1.673l1.545,-0.129l-0.385,1.802l1.930,-2.703l-0.258,2.574l-0.903,0.902l-0.900,1.802l-0.900,0.773l-1.545,-1.932l-0.515,0.643zM805.655,204.253l0.258,1.802l0.256,1.545l-1.029,2.446l-0.901,-2.704l-1.289,1.287l0.903,2.060l-0.774,1.288l-3.217,-1.545l-0.771,-2.059l0.898,-1.287l-1.801,-1.159l-0.773,1.030l-1.285,-0.129l-2.061,1.545l-0.386,-0.773l1.031,-2.317l1.672,-0.773l1.545,-0.901l0.902,1.159l2.061,-0.772l0.384,-1.158l1.930,-0.129l-0.129,-2.061l2.192,1.288l0.255,1.416l-0.129,-0.901zM784.415,201.936l-3.477,2.447l1.288,-1.804l1.929,-1.673l1.676,-1.931l1.285,-2.575l0.518,2.190l-1.803,1.415l1.416,-1.931zM794.841,177.863l-0.514,1.159l0.901,1.931l-0.643,2.188l-1.545,0.901l-0.516,2.188l0.645,2.189l1.416,0.257l1.158,-0.257l3.348,1.415l-0.258,1.417l0.900,0.772l-0.257,1.159l-2.061,-1.287l-1.029,-1.416l-0.643,1.031l-1.803,-1.676l-2.445,0.387l-1.287,-0.515l0.127,-1.157l0.775,-0.645l-0.775,-0.643l-0.256,0.901l-1.416,-1.545l-0.387,-1.159l-0.127,-2.575l1.157,0.902l0.257,-4.248l0.901,-2.447l1.545,0l1.674,0.773l0.902,-0.643l-0.256,-0.643zM793.94,196.271l-0.386,-1.286l1.674,0.771l1.673,0l0,1.160l-1.287,1.157l-1.674,0.773l-0.128,-1.287l-0.128,1.288zM803.337,194.212l0.773,2.961l-2.060,-0.644l0,0.901l0.644,1.674l-1.287,0.514l-0.129,-1.802l-0.773,-0.128l-0.385,-1.674l1.545,0.257l0,-1.029l-1.676,-2.060l2.576,0l-0.772,-1.030z", -"M667.659,126.886l2.059,1.287l0.773,2.059l4.375,1.159l-2.572,2.189l-2.961,0.386l-4.121,-0.643l-1.287,1.157l0.900,2.447l1.031,1.802l2.059,1.287l-2.187,1.545l0,1.931l-2.575,2.704l-1.802,2.702l-2.704,2.833l-3.218,-0.129l-2.961,2.832l1.802,1.159l0.258,2.059l1.545,1.417l0.514,2.317l-5.922,0l-1.801,1.802l-1.931,-0.772l-0.774,-1.932l-2.187,-2.059l-4.891,0.514l-4.377,0.130l-3.863,0.386l1.030,-3.218l3.992,-1.288l-0.259,-1.287l-1.288,-0.386l-0.128,-2.446l-2.574,-1.159l-1.031,-1.674l-1.414,-1.415l4.504,1.415l2.704,-0.386l1.674,0.386l0.514,-0.643l1.931,0.257l3.476,-1.159l0.129,-2.317l1.417,-1.544l2.058,0l0.257,-0.644l2.061,-0.386l1.029,0.257l1.031,-0.772l-0.129,-1.674l1.158,-1.545l1.673,-0.772l-1.030,-1.673l2.575,0l0.773,-0.902l-0.129,-1.029l1.287,-1.159l-0.257,-1.416l-0.645,-1.029l1.545,-1.287l2.833,-0.517l3.090,-0.256l1.416,-0.516l-1.545,0.385z", -"M505.718,89.295l-1.158,-1.672l0.257,-1.030l-0.644,-1.417l-1.029,-0.901l0.770,-0.773l-0.642,-1.287l1.802,-0.901l4.248,-1.158l3.347,-0.901l2.703,0.387l0.258,0.643l2.574,0.129l3.348,0.256l4.890,0l1.417,0.259l0.644,0.772l0.129,1.288l0.771,1.029l0,1.029l-1.672,0.516l0.772,1.287l0.129,1.159l1.286,2.317l-0.257,0.773l-1.287,0.385l-2.447,2.189l0.646,1.287l-0.515,-0.257l-2.577,-1.030l-1.929,0.386l-1.289,-0.257l-1.672,0.644l-1.289,-1.030l-1.158,0.386l-0.127,-0.129l-1.289,-1.416l-1.930,-0.129l-0.257,-0.772l-1.802,-0.386l-0.517,0.773l-1.414,-0.644l0.129,-0.645l-1.932,-0.256l1.287,0.903z", -"M286.622,177.09l1.416,0.258l0.516,0.515l-0.644,0.643h-2.06l-1.545,0.129l-0.258-1.158l0.387-0.387H286.622z", -"M560.942,139.759l0,1.544l-0.386,0.902l-1.287,0.257l0.128,-0.644l0.774,-0.385l-0.774,-0.258l0.645,-1.803l-0.900,-0.387z", -"M440.838,114.141l1.031,-0.643l1.158,-0.387l0.643,1.287l1.545,0l0.514,-0.385l1.545,0.128l0.773,1.416l-1.287,0.643l0,2.189l-0.514,0.387l0,1.159l-1.160,0.256l1.030,1.674l-0.772,1.674l0.902,0.900l-0.258,0.644l-1.030,1.030l0.257,0.902l-1.158,0.772l-1.416,-0.387l-1.416,0.258l0.386,-2.059l-0.129,-1.674l-1.288,-0.258l-0.643,-1.030l0.259,-1.802l1.028,-0.900l0.259,-1.159l0.514,-1.545l0,-1.159l-0.644,-1.030l0.129,0.901z", -"M296.405,286.898l1.03-3.219v-1.414l1.416-2.447l4.634-0.772l2.446,0.13l2.575,1.285v0.902l0.772,1.414l-0.128,3.736l2.831,0.514l1.159-0.514l1.802,0.643l0.516,0.902l0.256,2.443l0.259,1.031l1.028,0.129l1.031-0.516l0.9,0.516v1.544l-0.386,1.545l-0.515,1.546l-0.386,2.445l-2.446,2.059l-2.189,0.387l-2.961-0.387l-2.702-0.771l2.574-4.121l-0.386-1.157l-2.703-1.03l-3.348-2.059l-2.188-0.387L296.405,286.898z", -"M602.136,160.227l-0.257,-1.931l0.770,-1.416l0.772,-0.257l0.775,0.901l0,1.545l-0.517,1.544l-0.772,0.258l0.771,0.644z", -"M526.442,97.921l1.159,-0.515l1.674,0.258l1.673,0l1.289,0.772l0.899,-0.515l1.931,-0.257l0.773,-0.644l1.158,0l0.774,0.258l0.770,0.901l0.902,1.029l1.545,1.674l0.128,1.158l-0.257,1.159l0.387,1.287l1.287,0.386l1.287,-0.386l1.158,0.515l0,0.645l-1.287,0.643l-0.772,-0.258l-0.773,3.219l-1.544,-0.258l-1.930,-1.030l-3.219,0.644l-1.287,0.644l-3.990,-0.130l-2.059,-0.386l-1.031,0.129l-0.773,-1.030l-0.513,-0.515l0.641,-0.386l-0.771,-0.386l-0.774,0.644l-1.543,-0.773l-0.257,-1.158l-1.674,-0.643l-0.258,-0.902l-1.416,-1.030l2.059,-0.515l1.673,-1.802l1.290,-1.803l-1.671,0.643z", -"M519.749,102.684l1.416,1.030l0.258,0.902l1.674,0.643l0.257,1.158l1.543,0.773l0.774,-0.644l0.771,0.386l-0.641,0.386l0.513,0.515l-0.772,0.644l0.259,0.902l1.415,1.158l-1.030,0.901l-0.515,0.772l0.256,0.386l-0.385,0.387l-1.288,0l-1.544,0.257l-1.545,0.515l-0.129,0.515l-0.385,0l-0.130,-1.030l-0.643,-0.257l-0.516,-0.773l-0.771,0.258l-0.129,-0.516l-1.287,1.288l0.258,0.901l-0.516,-0.128l-0.773,-0.902l-1.159,-0.515l0.258,-0.514l0.387,-1.545l0.900,-0.515l1.158,-0.387l0.389,-1.287l-1.289,-1.030l0.645,-1.159l-1.030,0l1.030,-1.029l-0.773,-0.772l-0.643,-1.031l2.060,-0.772l-1.672,-0.129z", -"M950.089,36.129l-0.258,0l-0.516,-1.801l0.774,-0.772l0.127,-0.129l6.308,1.158l6.435,-1.544zM586.045,9.869l5.276,-0.515l4.121,0l0.514,0.773l1.545,-0.644l2.574,-0.515l3.990,0.644l-1.028,0.386l-3.605,0.385l-2.447,0.130l-0.384,0.514l-3.221,0.386l-2.830,-0.643l1.545,-0.772l6.050,0.129zM950.089,51.964l-3.992,1.802l2.574,3.219l-0.641,2.188l-5.539,-0.773l-7.336,1.674l-6.177,2.703l-4.764,2.703l-3.990,-1.673l-7.725,1.803l-6.693,0.128l-4.377,4.506l3.088,0.772l0,4.634l-3.475,1.545l0.645,1.803l-4.506,1.544l-1.159,3.219l-4.250,1.158l-0.513,1.931l-4.119,3.089l-1.674,-6.178l-1.545,-5.922l1.545,-4.249l2.060,-1.157l0.127,-1.287l3.864,-0.773l5.148,-3.219l4.506,-2.832l5.019,-2.060l2.061,-3.732l-3.219,0.128l-1.672,2.317l-6.695,2.446l-2.187,-3.089l-7.081,0.901l-6.693,4.247l1.803,1.288l-6.309,1.416l-10.041,-1.416l-11.715,0l-6.564,1.159l-8.369,5.278l-9.781,5.665l3.861,0.643l0.771,2.317l3.092,0.385l1.672,-1.545l2.961,0.387l3.475,2.060l0.515,3.089l-1.543,2.189l-0.902,2.575l-1.031,5.535l-4.120,3.862l-0.900,1.802l-3.603,3.219l-3.735,3.089l-1.674,1.545l-3.603,1.674l-1.674,0l-1.674,-1.288l-3.601,1.931l-0.518,0.901l-0.385,-0.514l0,-1.288l1.416,-0.129l0.385,-3.219l-0.771,-2.317l2.318,-0.901l3.346,0.516l1.802,-2.704l0.901,-2.832l1.031,-1.029l1.414,-2.448l-4.375,0.774l-2.447,1.030l-3.990,0l-1.159,-2.447l-3.218,-1.930l-4.635,-0.902l-1.029,-2.574l-0.901,-1.674l-1.031,-1.158l-1.674,-2.703l-2.316,-1.030l-4.119,-0.772l-3.475,0l-3.350,0.513l-2.316,1.417l1.545,0.644l0,1.416l-1.414,0.901l-2.447,2.832l0,1.159l-3.862,1.672l-3.219,-0.900l-3.218,0.128l-1.414,-0.900l-1.545,-0.258l-3.991,1.930l-3.476,0.387l-2.574,0.644l-3.348,-0.386l-2.445,0l-1.674,-1.417l-2.701,-1.287l-2.705,-0.257l-3.348,0.257l-2.574,0.515l-3.862,-1.158l-0.515,-1.931l-3.090,-0.773l-2.445,-0.257l-3.090,-1.158l-2.703,2.832l1.029,1.545l-2.574,1.931l-3.862,-0.644l-2.703,-0.129l-1.802,-1.287l-2.832,0l-2.317,-0.901l-4.121,1.287l-5.148,2.317l-2.832,0.515l-1.030,0.258l-1.414,-1.675l-3.478,0.387l-1.158,-1.159l-1.930,-0.515l-1.289,-1.545l-1.545,-0.514l-3.860,0.644l-3.605,-1.546l-1.545,1.416l-5.922,-6.822l-3.474,-2.060l1.029,-0.901l-6.822,2.575l-2.577,0.128l0.258,-1.416l-3.474,-0.901l-2.832,0.645l-0.901,-2.833l-4.763,-0.644l-2.446,1.160l-6.693,1.028l-1.418,0.644l-10.039,0.901l-1.158,0.901l1.929,1.931l-2.575,0.644l0.515,0.772l-2.702,1.288l4.376,1.931l-0.644,1.159l-3.732,0l-0.773,0.772l-3.474,-1.416l-4.250,0l-2.961,1.158l-3.088,-1.029l-5.922,-1.931l-4.248,0.127l-5.535,2.962l-0.387,1.931l-2.705,-1.544l-2.186,2.960l0.772,0.515l-1.545,2.060l2.317,1.802l1.931,-0.129l1.803,1.803l-0.387,1.416l1.416,0.514l-1.287,1.546l-2.575,0.386l-2.704,2.831l2.445,2.576l-0.255,1.801l2.960,3.218l-1.545,1.030l-0.516,0.644l-1.158,-0.129l-1.931,-1.673l-0.643,0l-1.803,-0.644l-0.772,-1.158l-2.446,-0.516l-1.671,0.387l-0.517,-0.515l-3.604,-1.287l-3.990,-0.386l-2.318,-0.516l-0.256,0.387l-3.477,-2.318l-3.089,-1.029l-2.318,-1.545l1.931,-0.514l2.317,-2.189l-1.544,-1.030l3.991,-1.159l-0.129,-0.643l-2.446,0.515l0.128,-1.159l1.417,-0.772l2.575,-0.258l0.384,-0.901l-0.513,-1.417l1.029,-1.415l0,-0.772l-3.990,-0.902l-1.545,0l-1.674,-1.287l-2.059,0.386l-3.476,-0.901l0.129,-0.514l-1.030,-1.159l-2.058,-0.129l-0.258,-0.901l0.643,-0.515l-1.673,-1.544l-2.833,0.256l-0.772,-0.128l-0.773,0.644l-0.901,-0.129l-0.643,-1.674l-0.644,-0.901l0.516,-0.257l2.187,0.128l1.030,-0.643l-0.772,-0.772l-1.803,-0.387l0.129,-0.515l-1.160,-0.515l-1.672,-1.802l0.645,-0.644l-0.258,-1.287l-2.703,-0.643l-1.416,0.385l-0.387,-0.772l-2.833,-0.644l-0.901,-1.545l-0.129,-1.287l-1.286,-0.644l1.158,-0.901l-0.900,-2.446l1.930,-1.544l-0.386,-0.515l3.089,-1.545l-2.832,-1.287l5.792,-3.347l2.448,-1.545l1.030,-1.416l-3.991,-1.802l1.157,-1.802l-2.445,-2.060l1.801,-2.318l-3.089,-2.960l2.448,-2.060l-4.119,-1.801l0.384,-1.932l2.189,-0.257l4.507,-1.031l2.830,-0.900l4.378,1.545l7.466,0.643l10.169,3.089l2.059,1.288l0.129,1.802l-7.336,2.061l-12.102,-2.061l-1.929,0.386l4.504,3.219l0.772,2.060l2.961,1.544l3.218,-2.703l7.596,1.287l0,-2.960l7.465,-1.803l3.992,-0.901l-2.190,-1.674l-0.643,-3.218l7.466,0.772l-1.801,3.348l4.632,-0.129l7.210,-2.703l9.783,-2.318l2.060,1.417l9.397,-1.546l6.695,0.902l0.643,-3.219l7.853,0.772l10.684,2.832l1.673,-1.801l-3.991,-4.507l4.505,-2.702l2.190,-3.090l8.369,0.386l0.769,4.763l0.260,5.536l1.672,1.674l-0.516,1.802l-4.119,2.832l2.832,0.386l5.151,-2.961l1.029,-3.991l-2.832,-1.159l-1.029,-5.664l3.345,-3.346l2.190,1.802l0.644,2.060l1.672,-1.288l3.477,-0.901l5.535,-0.128l5.019,1.544l-2.445,-2.575l-0.256,-2.574l4.760,-0.514l6.437,0.128l5.793,-0.387l-2.189,-1.415l3.219,-1.674l3.090,-0.129l5.150,-1.288l0.385,-0.128l1.029,0l1.418,0l1.545,-0.128l1.416,-0.129l1.027,0l0.389,0l0.900,-0.773l7.080,-0.257l2.190,0.643l6.049,-1.415l4.890,0l0.774,-1.159l2.574,-1.159l6.309,-1.030l4.632,0.772l-3.603,0.644l6.051,0.515l0.771,1.288l2.447,-0.644l9.782,-0.257l5.023,1.673l-2.318,3.089l-7.082,1.546l1.031,1.544l6.180,-0.257l2.961,1.030l11.968,0l2.705,1.544l10.299,0.129l0.387,-1.673l16.603,1.673l0.518,4.892l4.246,1.030l8.111,-1.545l15.834,-0.515l1.930,-3.476l23.170,1.802l2.320,1.545l7.078,2.059l14.416,-0.385l6.438,3.733l10.170,-0.128l9.269,-0.259l6.178,2.447l0.774,-3.219l13.257,0.515l8.496,1.159l3.735,1.158l6.564,2.059l7.209,2.448l8.110,1.029l5.277,2.575l-6.178,1.416l-0.386,2.703l-4.506,0.129l-5.278,-2.317l-5.150,-0.644l-3.475,-1.674l-1.802,2.961l0.385,-0.129zM518.204,80.414l0.645,-1.288l3.733,-0.772l2.702,0.386l1.289,0.515l-0.259,0.643l0.128,0.772l-4.890,0l3.348,0.256zM861.522,24.158l5.666,0.515l-0.128,1.416l-7.725,-1.416l-2.187,0.515zM836.034,22.871l5.279,-0.387l10.426,0.772l1.803,2.189l-9.527,-0.128l-3.989,1.030l-5.021,-1.931l-1.029,1.545zM742.835,13.473l0.516,0.772l5.019,2.575l-14.287,0.387l3.604,-3.090l-5.148,0.644zM718.763,9.226l10.556,0.386l5.922,3.346l-7.853,1.674l-11.328,-1.030l-0.127,-2.446l-2.830,1.930zM609.345,28.277l6.435,-2.317l-0.643,-1.287l6.050,-1.417l8.882,-1.673l8.882,-0.514l4.634,-1.030l5.279,-0.387l1.801,1.159l-1.801,0.772l-9.526,1.417l-8.239,1.287l-8.367,2.445l-3.993,2.704l-4.246,2.574l0.644,2.189l5.149,2.317l-1.672,0.129l-8.756,-0.257l-0.771,-1.287l-4.891,-0.644l-0.386,-1.545l2.830,-0.515l-0.127,-1.417l5.277,-2.316l2.445,0.387zM850.194,82.344l0.901,2.575l0,2.575l1.158,2.832l2.705,4.763l-3.990,-0.901l-1.674,3.862l2.574,2.703l0,1.931l-2.058,-1.674l-1.803,2.189l-0.516,-2.317l0.258,-2.575l-0.258,-2.960l0.645,-2.061l0.127,-3.604l-1.545,-2.575l0.131,-3.733l2.574,-1.287l-1.160,-1.158l1.289,-0.387l-0.642,-1.802z", -"M547.169,229.999l1.028,1.545l-0.128,1.544l-0.773,0.387l-1.415,-0.128l-0.773,1.545l-1.673,-0.258l0.257,-1.546l0.386,-0.128l0,-1.674l0.901,-0.643l0.643,0.256l-1.547,0.900z", -"M580.509,182.883l-0.387,-1.157l-0.771,-0.773l-0.259,-1.031l-1.415,-1.029l-1.416,-2.188l-0.772,-2.189l-1.802,-1.931l-1.289,-0.386l-1.672,-2.574l-0.386,-1.932l0.128,-1.544l-1.545,-2.961l-1.287,-1.030l-1.416,-0.644l-0.902,-1.545l0.130,-0.514l-0.771,-1.417l-0.774,-0.643l-1.030,-2.060l-1.674,-2.059l-1.287,-1.931l-1.417,0l0.387,-1.417l0.131,-0.901l0.384,-1.158l2.960,0.514l1.160,-0.900l0.643,-0.901l2.060,-0.386l0.515,-0.902l0.901,-0.515l-2.703,-2.703l5.407,-1.287l0.514,-0.516l3.218,0.773l3.990,1.931l7.596,5.406l5.020,0.258l2.447,0.257l0.644,1.288l1.930,-0.129l1.030,2.317l1.288,0.644l0.513,0.902l1.803,1.158l0.127,1.159l-0.256,0.900l0.387,0.901l0.772,0.644l0.386,0.901l0.387,0.644l0.771,0.644l0.772,-0.258l0.517,1.030l0.127,0.643l1.031,2.704l8.110,1.416l0.514,-0.644l1.159,1.931l-1.802,5.408l-7.981,2.702l-7.853,1.030l-2.445,1.159l-1.931,2.832l-1.287,0.515l-0.644,-0.901l-1.031,0.128l-2.574,-0.257l-0.514,-0.257l-3.090,0l-0.773,0.257l-1.158,-0.644l-0.645,1.288l0.258,1.158l1.158,-0.772z", -"M901.944,255.23l0.772,0.903l-1.930,0l-1.031,-1.674l1.674,0.643l-0.515,-0.128zM900.786,252.784l-0.387,0.516l-2.060,-2.318l-0.514,-1.544l0.901,0l1.029,2.059l-1.031,-1.287zM898.597,253.557l-1.159,0.129l-1.544,-0.386l-0.643,-0.386l0.256,-1.031l1.674,0.387l0.900,0.644l-0.516,-0.643zM895.251,248.537l0.643,0.901l0.128,0.515l-2.060,-1.158l-1.543,-0.902l-1.031,-0.901l0.384,-0.258l1.290,0.644l-2.189,-1.159zM888.556,245.834l1.031,0.900l-0.516,0.129l-1.160,-0.515l-1.158,-1.159l0.129,-0.386l-1.674,-1.031z", -"M556.308,215.711l-1.416,1.028l-1.545,0l-2.189,0.644l-1.802,-0.644l-1.029,0.774l-2.446,-1.802l-0.644,-1.159l-1.416,0.643l-1.287,-0.257l-0.773,0.515l-1.158,-0.387l-1.672,-2.188l-0.389,-0.901l-2.059,-1.031l-0.643,-1.544l-1.159,-1.287l-1.800,-1.287l0,-0.901l-1.419,-1.159l-2.059,-1.159l-1.029,-0.771l-0.129,-0.773l0.387,-1.159l0,-1.159l-1.545,-1.674l-0.258,-1.157l0,-0.645l-0.902,-0.772l-0.126,-1.544l-0.517,-1.030l-0.902,0.129l0.259,-1.031l0.643,-1.030l-0.258,-1.159l0.901,-0.772l-0.643,-0.643l0.774,-1.673l1.158,-2.060l2.316,0.257l0.258,-10.427l0,-1.415l2.832,0l0,-5.408l10.813,0l10.556,0l10.299,0l1.158,2.704l-0.643,0.386l0.385,2.832l1.030,3.218l1.029,0.644l1.418,1.029l-1.288,1.546l-2.061,0.386l-0.770,0.901l-0.258,1.802l-1.160,3.861l0.258,1.160l-0.387,2.317l-1.158,2.575l-1.672,1.415l-1.158,2.060l-0.259,1.030l-1.286,0.772l-0.774,2.831l0,2.447l-0.386,0.774l-1.416,0.127l-0.901,1.545l1.673,0.129l1.416,1.287l0.388,1.158l1.286,0.644l1.158,2.832l-2.188,2.447l1.416,-1.159z", -"M525.026,49.905l-2.703,1.930l0.516,1.674l-4.377,2.060l-5.150,2.317l-1.930,3.861l1.930,1.932l2.576,1.415l-2.576,3.090l-2.704,0.643l-1.028,4.507l-1.545,2.445l-3.348,-0.257l-1.415,2.188l-3.218,0.129l-0.773,-2.575l-2.317,-3.090l-2.059,-3.732l1.158,-1.545l2.318,-1.931l0.900,-3.090l-1.803,-1.415l-0.127,-3.605l1.800,-2.446l2.705,0l0.903,-1.030l-1.031,-0.901l4.248,-3.862l2.703,-2.960l1.674,-1.931l2.702,0l0.644,-1.417l5.022,0.387l0.513,-1.803l1.673,-0.129l3.475,1.417l4.250,1.801l0.128,4.120l0.901,1.031l4.635,-0.772z", -"M539.059,11.285l-3.991,1.416l-7.852,0.387l-7.854-0.515l-0.514-0.644l-3.863-0.128l-2.961-1.159l8.369-0.772l3.86,0.644l2.704-0.772l6.822,0.643L539.059,11.285zM505.976,12.314h-3.733l-1.546-0.901l-7.335,1.031l2.059,2.06l5.276,2.317l3.99,0.772l-2.314,0.9l5.791,1.675l3.219-0.129l1.287-2.189l2.316-0.515l1.545-2.06l6.693-1.031l-8.881-1.931l-3.347-1.03l-3.991,0.128L505.976,12.314zM531.851,17.207l-3.863-0.515l-1.158-1.03l-5.535,0.515l1.674,0.901l-1.932,0.643l4.765,0.645L531.851,17.207z", -"M516.017,93.673l0.127,0.129l1.158,-0.386l1.289,1.030l1.672,-0.644l1.289,0.257l1.929,-0.386l2.577,1.030l-0.774,0.772l-0.513,1.030l-0.645,0.257l-2.832,-0.772l-0.900,0.129l-0.645,0.643l-1.287,0.387l-0.258,-0.258l-1.287,0.515l-1.158,0l-0.129,0.644l-2.318,0.257l-1.028,-0.257l-1.288,-0.772l-0.257,-0.903l0.127,-0.256l0.387,-0.644l1.288,0l0.900,-0.258l0,-0.257l0.517,-0.129l0.256,-0.643l0.644,0l0.385,-0.515l-0.774,0z", -"M434.402,208.759l-0.772,-0.257l-1.931,-1.031l-1.287,-1.544l-0.515,-0.902l-0.386,-2.059l1.415,-1.159l0.387,-0.772l0.386,-0.515l0.772,-0.129l0.644,-0.514l2.188,0l0.772,1.029l0.515,1.159l0,0.773l0.387,0.642l0,1.030l0.644,-0.127l-1.159,1.157l-1.287,1.546l-0.129,0.771l0.644,-0.902z", -"M420.242,190.35l-1.159,-2.059l-1.287,-1.030l1.159,-0.515l1.287,-1.803l0.644,-1.416l0.901,-0.901l1.416,0.257l1.287,-0.643l1.416,0l1.288,0.773l1.801,0.772l1.674,1.931l1.802,1.930l0.129,1.674l0.515,1.545l1.030,0.772l0.257,1.030l-0.128,0.772l-0.387,0.129l-1.544,-0.129l-0.130,0.258l-0.643,0l-1.931,-0.643l-1.287,0l-5.020,-0.129l-0.644,0.386l-0.901,-0.129l-1.545,0.386l-0.387,-2.058l2.447,0.127l0.644,-0.386l0.514,0l1.030,-0.644l1.159,0.516l1.158,0.128l1.159,-0.644l-0.516,-0.773l-0.900,0.387l-0.901,0l-1.030,-0.643l-0.901,0l-0.515,0.643l2.961,-0.129z", -"M591.708,205.411l-8.754-3.218l-1.029-0.901l-1.028-1.287l-1.032-1.545l0.645-0.901l0.9-1.416l0.902,0.515l0.516,1.03l1.286,1.158h1.288l2.575-0.643l2.959-0.387l2.318-0.771l1.286-0.259l1.029-0.515h1.545l0.902-0.128l1.158-0.387l1.416-0.257l1.288-0.9h1.028l-0.13-0.772l0.26,1.544l-0.258,1.545v1.415l-0.644,0.901l-0.772,2.961l-1.288,2.961l-1.674,3.475l-2.316,3.991l-2.189,2.962l-3.218,3.732l-2.702,2.188l-3.991,2.576l-2.575,2.059l-2.959,3.348l-0.645,1.414l-0.516,0.646l-1.931-2.188l0.13-10.17l2.188-2.959l1.029-0.645h1.803l2.318-1.932l3.475-0.129l7.722-8.11H591.708z", -"M311.337,210.946l3.219,0.517l0.257,-0.517l2.188,-0.257l2.833,0.774l-1.417,2.316l0.259,1.803l1.029,1.674l-0.386,1.157l-0.257,1.159l-0.772,1.158l-1.545,-0.515l-1.159,0.259l-1.158,-0.259l-0.259,0.773l0.516,0.514l-0.257,0.517l-1.546,-0.130l-1.545,-2.317l-0.385,-1.545l-0.902,0l-1.157,-1.931l0.514,-1.416l-0.129,-0.643l1.545,-0.772l-0.514,2.319z", -"M228.694,190.865l-0.257,0.645l-1.545,0l-1.030,-0.259l-1.029,-0.515l-1.545,-0.129l-0.772,-0.644l0.128,-0.385l0.901,-0.644l0.515,-0.385l-0.129,-0.258l0.644,-0.129l0.772,0.129l0.515,0.643l0.902,0.386l0,0.386l1.287,-0.386l0.515,0.258l0.386,0.256l0.258,-1.031z", -"M569.696,137.055l-5.278,2.833l-2.96-1.031l0.256-0.385v-1.16l0.644-1.416l1.546-1.029l-0.516-1.031l-1.159-0.128l-0.257-2.059l0.645-1.159l0.771-0.643l0.644-0.515l0.129-1.545l0.901,0.514l2.96-0.772l1.416,0.514h2.317l3.09-1.028l1.416,0.128l3.09-0.515l-1.416,1.673l-1.417,0.773l0.259,1.931l-1.029,3.218L569.696,137.055z", -"M551.674,299l-0.644,1.158l-1.545,0.386l-1.545,-1.544l-0.128,-0.902l0.771,-1.030l0.258,-0.771l0.773,-0.129l1.416,0.385l0.385,1.289l-0.259,-1.158z", -"M504.302,192.281l0.258,-1.158l-1.674,-0.130l0,-1.673l-1.158,-1.029l1.158,-3.605l3.475,-2.446l0.130,-3.605l1.030,-5.406l0.514,-1.159l-1.030,-0.901l-0.129,-0.901l-1.029,-0.643l-0.643,-4.249l2.702,-1.416l10.815,5.149l11.069,5.407l-0.258,10.427l-2.316,-0.257l-1.158,2.060l-0.774,1.673l0.643,0.643l-0.901,0.772l0.258,1.159l-0.643,1.030l-0.259,1.031l0.902,-0.129l0.517,1.030l0.126,1.544l0.902,0.772l0,0.645l-1.672,0.514l-1.417,1.030l-1.932,2.962l-2.574,1.287l-2.574,-0.130l-0.772,0.259l0.256,0.900l-1.416,0.901l-1.157,1.031l-3.349,1.029l-0.642,-0.515l-0.517,-0.129l-0.384,0.773l-2.318,0.129l0.515,-0.644l-0.900,-1.930l-0.387,-1.160l-1.158,-0.385l-1.545,-1.546l0.514,-1.287l1.287,0.257l0.774,-0.257l1.415,0.129l-1.415,-2.446l0.128,-1.802l-0.258,-1.804l1.029,1.801z", -"M470.317,210.432l-2.187,0.514l-0.644,-0.900l-0.773,-1.673l-0.128,-1.416l0.514,-2.318l-0.643,-1.030l-0.258,-2.187l0,-1.932l-1.159,-1.417l0.258,-0.772l2.317,0l-0.259,1.417l0.775,0.900l0.900,0.902l0.129,1.287l0.516,0.515l-0.131,6.307l-0.773,-1.803z", -"M741.548,194.082l-2.445-1.157h-2.188l0.385-2.06h-2.446l-0.128,2.961l-1.545,3.99l-0.902,2.318l0.26,1.931l1.801,0.129l1.031,2.445l0.514,2.318l1.416,1.545l1.674,0.257l1.416,1.415l-0.9,1.159l-1.802,0.257l-0.13-1.287l-2.188-1.156l-0.516,0.385l-1.029-1.029l-0.515-1.288l-1.419-1.544l-1.285-1.287l-0.516,1.544l-0.514-1.417l0.385-1.672l0.773-2.576l1.287-2.832l1.545-2.445l-1.158-2.447l0.129-1.287l-0.387-1.545l-1.803-2.06l-0.643-1.416l1.029-0.514l0.9-2.318l-1.03-1.802l-1.802-1.931l-1.287-2.317l1.158-0.515l1.287-2.96l1.803-0.129l1.672-1.159l1.544-0.643l1.161,0.9l0.13,1.546l1.8,0.128l-0.643,2.832v2.446l2.832-1.544l0.9,0.385h1.545l0.516-1.03l2.061,0.258l2.059,2.189l0.129,2.574l2.188,2.446l-0.127,2.189l-0.902,1.288l-2.447-0.387l-3.475,0.514l-1.801,2.189L741.548,194.082z", -"M656.587,118.519l-1.030,0.900l-2.961,-0.515l-0.257,1.545l2.961,-0.129l3.347,0.901l5.020,-0.514l0.773,2.573l0.772,-0.256l1.674,0.515l-0.129,1.158l0.515,1.416l-2.832,0l-1.802,-0.129l-1.674,1.159l-1.285,0.257l-0.904,0.643l-1.030,-0.900l0.259,-2.317l-0.901,-0.129l0.384,-0.772l-1.543,-0.645l-1.160,0.903l-0.256,1.157l-0.386,0.387l-1.674,-0.128l-0.773,1.287l-0.901,-0.515l-2.059,0.900l-0.773,-0.385l1.547,-2.703l-0.645,-2.060l-1.931,-0.644l0.643,-1.159l2.318,0.129l1.285,-1.417l0.775,-1.801l3.603,-0.644l-0.514,1.287l0.386,0.772l-1.158,0.127z", -"M801.921,250.982l0.258,-0.643l2.316,-0.644l1.934,-0.129l0.771,-0.257l1.029,0.257l-0.900,0.773l-2.834,1.158l-2.316,0.773l0,-0.773l0.258,0.515z", -"M630.069,130.876l-0.129,-2.188l-2.060,-0.128l-3.088,-2.317l-2.188,-0.387l-2.961,-1.288l-1.932,-0.257l-1.157,0.515l-1.803,-0.129l-1.932,1.546l-2.317,0.513l-0.515,-1.930l0.387,-2.832l-2.059,-0.901l0.644,-1.802l-1.804,-0.129l0.645,-2.318l2.574,0.644l2.316,-0.773l-1.931,-1.673l-0.772,-1.544l-2.187,0.643l-0.260,2.059l-0.900,-1.802l1.160,-0.900l3.090,-0.515l1.800,0.772l1.930,2.060l1.419,-0.128l2.959,0l-0.384,-1.417l2.315,-0.901l2.188,-1.545l3.735,1.416l0.256,2.188l1.030,0.515l2.832,-0.128l1.029,0.515l1.289,2.702l3.089,1.931l1.674,1.288l2.832,1.288l3.604,1.158l0,1.674l-0.902,-0.128l-1.285,-0.774l-0.387,1.030l-2.317,0.516l-0.515,2.188l-1.546,0.772l-2.058,0.386l-0.517,1.288l-2.059,0.257l2.832,1.030z", -"M490.785,145.294l-1.159,-4.763l-1.674,-1.159l0,-0.643l-2.187,-1.545l-0.257,-2.059l1.674,-1.545l0.642,-2.189l-0.514,-2.575l0.642,-1.416l2.962,-1.029l1.801,0.257l0,1.415l2.190,-1.028l0.257,0.514l-1.417,1.416l0,1.159l0.902,0.643l-0.258,2.446l-1.801,1.287l0.515,1.416l1.415,0.129l0.644,1.287l1.030,0.387l-0.129,2.060l-1.288,0.772l-0.900,0.902l-1.803,1.029l0.257,1.158l-0.257,1.031l1.287,-0.643z", -"M535.712,120.707l2.961-2.575l4.247-0.129l1.03-2.06l5.149,0.387l3.218-1.803l3.219-0.772h4.376l4.765,1.932l3.86,1.028l3.089-0.515l2.317,0.259l3.218-1.417l2.834-0.128l2.702,1.416l0.386,0.901l-0.256,1.288l2.059,0.643l1.029,0.773l-1.802,0.772l0.772,3.089l-0.516,0.901l1.416,2.189l-1.287,0.385l-0.899-0.643l-3.091-0.385l-1.157,0.385l-3.091,0.515l-1.416-0.128l-3.09,1.028h-2.317l-1.416-0.514l-2.96,0.772l-0.901-0.514l-0.129,1.545l-0.644,0.515l-0.771,0.643l-1.029-1.287l1.029-0.902l-1.674,0.129l-2.188-0.514l-1.803,1.544l-4.118,0.257l-2.189-1.416l-2.961-0.128l-0.644,1.159l-1.802,0.256l-2.574-1.415h-2.961l-1.545-2.574l-2.059-1.545l1.286-2.06L535.712,120.707zM535.581,114.27l2.705-0.772l2.317,0.257l0.386,1.03l2.316,0.902l-0.514,0.643l-3.219,0.257l-1.031,0.772l-2.313,1.417l-0.774-1.159v-0.644l0.645-0.258l0.771-1.673L535.581,114.27z", -"M793.296,161.256l-1.672,4.378l-1.16,2.188l-1.414-2.317l-0.26-1.932l1.545-2.702l2.189-2.06l1.288,0.772L793.296,161.256z", -"M551.03,229.742l5.407,-0.258l10.042,5.793l0.256,2.059l4.119,2.445l-1.289,3.092l0.131,1.543l1.802,1.031l0,0.645l-0.644,1.673l0.129,0.772l-0.129,1.287l0.901,1.673l1.160,2.704l0.900,0.514l-2.060,1.674l-2.961,1.030l-1.672,-0.128l-0.902,0.900l-1.930,0l-0.644,0.386l-3.347,-0.771l-1.931,0.256l-0.773,-3.732l-1.415,-1.933l-2.704,-0.514l-1.543,-0.901l-1.675,-0.384l-1.030,-0.387l-1.159,-0.773l-1.545,-3.346l-1.545,-1.546l-0.514,-1.546l0.257,-1.415l-0.514,-2.445l1.158,-0.128l1.031,-1.032l1.030,-1.416l0.642,-0.513l0,-0.903l-0.642,-0.643l-0.131,-1.030l0.773,-0.387l0.128,-1.544l-1.028,-1.545l0.900,-0.257l-2.961,0z", -"M550.901,86.593l0.901,0.129l0.773,-0.644l0.772,0.128l2.833,-0.256l1.673,1.544l-0.643,0.515l0.258,0.901l2.058,0.129l1.030,1.159l-0.129,0.514l3.476,0.901l2.059,-0.386l1.674,1.287l1.545,0l3.990,0.902l0,0.772l-1.029,1.415l0.513,1.417l-0.384,0.901l-2.575,0.258l-1.417,0.772l-0.128,1.159l-2.060,0.128l-1.801,0.902l-2.576,0.128l-2.317,1.030l0.129,1.674l1.416,0.644l2.704,-0.130l-0.516,0.902l-2.959,0.515l-3.606,1.544l-1.544,-0.515l0.643,-1.286l-2.962,-0.774l0.389,-0.514l2.573,-0.901l-0.772,-0.645l-4.120,-0.643l-0.257,-1.029l-2.446,0.386l-1.030,1.416l-2.060,2.059l-1.158,-0.515l-1.287,0.386l-1.287,-0.386l0.773,-0.386l0.385,-0.901l0.774,-0.901l-0.129,-0.386l0.513,-0.258l0.258,0.386l1.545,0.129l0.773,-0.257l-0.517,-0.258l0.131,-0.386l-0.902,-0.772l-0.386,-1.030l-1.030,-0.515l0.257,-0.901l-1.287,-0.644l-1.029,-0.128l-2.059,-0.903l-1.675,0.259l-0.644,0.386l-1.158,0l-0.773,0.644l-1.931,0.257l-0.899,0.515l-1.289,-0.772l-1.673,0l-1.674,-0.258l-1.159,0.515l-0.256,-0.643l-1.415,-0.773l0.513,-1.030l0.774,-0.772l0.515,0.257l-0.646,-1.287l2.447,-2.189l1.287,-0.385l0.257,-0.773l-1.286,-2.317l1.286,-0.129l1.417,-0.644l2.187,-0.129l2.706,0.258l2.959,0.644l2.189,0l0.900,0.386l1.030,-0.386l0.772,0.643l2.446,-0.129l1.031,0.258l0.128,-1.416l0.900,-0.643l-2.318,0.129z", -"M551.03,229.742h-2.961l-0.9,0.257l-1.547,0.899l-0.644-0.256v-2.061l0.644-1.027l0.131-2.189l0.514-1.287l1.029-1.416l1.031-0.643l0.9-1.031l-1.158-0.258l0.258-3.217l1.028-0.775l1.803,0.645l2.188-0.645h1.545l1.416-1.027l1.287,1.93l1.031,2.705l0.771,1.93l-1.028,1.931l-1.932,1.674v0.772v2.832L551.03,229.742z", -"M284.434,106.546l-2.704,0.772l-2.575,0.644l-3.089,1.673l-1.287,1.417l-0.258,0.386l-0.127,1.545l0.9,1.415h1.159l-0.259-0.9l0.773,0.515l-0.257,0.772l-1.803,0.515l-1.286-0.13l-1.931,0.517l-1.159,0.128l-1.673,0.128l-2.06,0.644l3.733-0.387h0.128l0.773,0.515l-3.733,0.773h-1.802l0.129-0.257l0.128-0.644l-0.9,1.416h0.643l-0.515,2.06l-1.931,2.06l-0.257-0.773l-0.516-0.129l-0.643-0.643h-0.129h-0.128l0.514,1.416l0.773,1.416l0.129,0.257l-1.03,0.901l-1.545,2.188l-0.258-0.128l1.03-1.802l-1.416-1.287l-0.128-2.06l-0.387,0.901v2.446l-1.673-0.901l1.802,1.544l0.515,1.417l0.772,1.674l0.387,2.703l-1.803,1.93l-2.574,1.03l-2.318,1.417l-0.901,0.128l-1.158,1.931l-2.317,1.673l-2.832,1.288l-1.158,2.06l-0.516,1.415l0.387,2.061l1.03,2.187l1.159,2.061v1.029l1.157,2.703l0.129,2.447l-0.514,2.316l-1.159,0.516l-1.287-0.386l-0.386-1.159l-1.031-0.644l-1.545-2.317l-1.287-1.931l-0.257-1.287l0.515-1.674l-0.643-1.544l-1.803-1.545l-1.416-1.03l-3.089,1.158l-0.644-0.772l-2.574-1.287l-2.962,0.386l-2.445-0.258l-1.674,0.515h-1.544l-0.258,1.16l0.772,1.543l-3.605,0.13l-2.316-0.516l-1.545-0.514l-2.059-0.387l-2.318-0.128l-2.317,0.643l-2.446,1.931l-2.702,1.158l-1.417,1.289l-0.644,1.287v1.802l0.129,1.287l0.515,0.901l-1.03,0.129l-1.931-0.643l-2.188-0.773l-0.772-1.287l-0.515-1.931l-1.545-1.545l-1.03-1.545l-1.288-1.802l-1.93-1.159l-2.189,0.13l-1.674,2.058l-2.316-0.772l-1.288-0.772l-0.772-1.545l-0.9-1.416l-1.545-1.159l-1.416-0.901l-0.902-0.9h-4.633l-0.129,1.158h-2.06h-5.407l-6.178-1.931l-3.992-1.288l0.258-0.515l-3.475,0.259l-3.09,0.256l-0.258-1.029l-1.159-1.416l-2.831-1.545l-1.158-0.129l-1.16-0.9l-2.059-0.13l-0.772-0.515L140,132.292l-2.702-2.704l-2.189-3.732l0.128-0.644l-1.287-0.901l-2.059-2.317l-0.386-2.188l-1.417-1.417l0.644-2.189l-0.129-2.317l-0.901-1.544l0.901-2.96l0.129-2.962l0.514-4.119l-0.771-2.188l-0.387-2.575l3.734,0.515l1.158,2.06l0.644-0.773l-0.387-2.188l-1.287-2.189h15.962h2.704h32.182h18.536h5.536v-1.03h0.901l0.516,1.417l0.772,0.514l1.93,0.129l2.704,0.515l2.703,0.773l2.188-0.387l3.219,0.773h0.385h0.515l0.258-0.129l0.386-0.129l0.387-0.128l0.772-0.258l0.643-0.129l0.644,0.129l0.386,0.258h0.258l0.386,0.257l0.773,0.257l0.772,0.258l0.772,0.386l0.643,0.257l0.387,0.13l0.258,0.128l0.514,0.128l0.515,0.258l0.515,0.258l0.515,0.128l0.515,0.257l0.515,0.258l0.515,0.129l0.514,0.257l0.13,0.386l0.128,0.387l0.386,0.257h0.257h0.902h0.257v0.129v0.129v0.128v0.258h0.129l0.129,0.257h0.128l0.258,0.129l0.386,0.128l0.258-0.128h0.128l0.258,0.258h0.128v0.129l-0.386,0.515l0.516,0.257l0.643,0.257l0.644,0.258l0.514,0.128l0.515,0.386l0.128,0.387v0.257l0.13,0.515l0.128,0.386l0.129,0.515l0.13,0.515l0.127,0.515v0.386l0.129,0.515l0.258,0.772l-0.129,0.258l-0.385,0.515l-0.259,0.515v0.129v0.128l-0.514,0.516l-0.772,1.03l-0.387,0.385l-0.257,0.515v0.257v0.13l0.257,0.257l0.387,0.257l0.514,0.129h0.644l0.643-0.258l0.644-0.257l0.644-0.258l0.643-0.385l0.644-0.258l0.645-0.129l0.9-0.128h0.387h0.128l0.644-0.129l0.643-0.258l0.643-0.257l0.902-0.257l0.772-0.258l0.386-0.128l0.258-0.13v-0.128v-0.258l-0.387-0.772v-0.129l-0.257-0.386l0.386-0.129l0.515-0.257h0.258l0.772-0.128h0.643h0.902l0.772,0.128h0.901h0.516h0.643l0.257-0.515l0.387-0.386l0.256-0.258l0.387-0.258l2.703-1.801l1.287-0.516h4.12h4.891l0.258-0.772h0.901l1.158-0.515l0.902-1.159l0.901-2.187l2.06-1.932l0.9,0.644l1.804-0.386l1.157,0.772v3.605l1.803,1.545v-1.158V106.546zM16.808,64.322l2.059,0.257l0.258,1.031l-1.545,0.386l-1.802-0.516l-1.673-0.772l-2.703,0.386L16.808,64.322zM52.465,70.759l1.803,0.257l1.157,0.774l-2.317,1.286l-2.703,1.029l-1.416-0.643l-0.385-1.288l2.445-0.901l-1.416,0.514L52.465,70.759zM85.42,39.22v9.913v15.446h2.574l2.704,0.774L92.5,66.51l2.445,1.803l2.575-1.545l2.703-0.902l1.545,1.417l1.802,1.159l2.446,1.159l1.674,1.93l2.703,3.09l4.634,1.673v1.802l-1.417,1.287l-1.544-1.029l-2.316-0.901l-0.773-2.318l-3.476-2.189l-1.415-2.573l-2.576-0.129l-4.376-0.129l-3.09-0.773l-5.535-2.703l-2.702-0.514l-4.636-1.031l-3.733,0.259l-5.278-1.288l-3.217-1.159l-2.962,0.643l0.515,1.804l-1.544,0.257l-3.09,0.515l-2.318,0.9l-2.961,0.644l-0.385-1.673l1.159-2.575l2.831-0.9l-0.771-0.645l-3.347,1.545l-1.802,1.802l-3.991,1.931l2.059,1.288l-2.574,1.931l-2.961,1.03l-2.704,0.9l-0.643,1.159l-4.119,1.416l-0.901,1.288l-3.09,1.158l-1.931-0.258l-2.445,0.773l-2.832,0.901l-2.189,0.902l-4.634,0.772l-0.387-0.516l2.962-1.158l2.574-0.901l2.832-1.417l3.347-0.385l1.416-1.03l3.734-1.673l0.514-0.516l2.059-0.901l0.386-2.059l1.418-1.545l-3.091,0.773l-0.901-0.516l-1.415,1.031l-1.803-1.417l-0.644,1.03l-1.029-1.417l-2.704,1.16h-1.673l-0.257-1.674l0.514-0.901l-1.673-1.029l-3.604,0.513l-2.189-1.287l-1.931-0.643v-1.545l-2.059-1.03l1.029-1.673l2.188-1.416l1.03-1.416l2.189-0.129l1.802,0.386l2.189-1.287l1.93,0.257l2.059-0.901l-0.513-1.158l-1.546-0.515l2.059-1.03h-1.673l-2.832,0.515l-0.772,0.643l-2.188-0.514l-3.863,0.257l-3.861-0.643l-1.158-1.159l-3.476-1.545l3.862-1.03l6.05-1.416h2.188l-0.386,1.416l5.665-0.129l-2.189-1.673l-3.347-1.031l-1.931-1.286l-2.574-1.158l-3.605-0.901l1.417-1.417l4.762-0.129l3.475-1.158l0.644-1.288l2.703-1.287l2.704-0.386l5.021-1.159l2.574,0.128l4.119-1.415l3.99,0.643l2.06,1.159l1.159-0.515l4.505,0.128l-0.128,0.644l4.119,0.516l2.703-0.258l5.664,0.773l5.278,0.257l2.06,0.386l3.604-0.514l3.991,0.9l-2.961-0.387L85.42,39.22zM2.647,55.182l1.673,0.515l1.674-0.258l2.189,0.644l2.574,0.386l-0.128,0.258l-2.061,0.644l-2.059-0.644l-1.03-0.514l-2.446,0.128L2.39,56.213l-0.257,1.031L2.647,55.182zM45.256,175.546v-0.773l-0.385-1.029l0.643-0.643l-0.258-0.516l0.129-0.128v-0.129l1.803,0.773l0.256,0.385v0.258l0.258,0.129l0.129,0.128l0.385,0.387l-0.643,0.514l-0.772,0.129l-0.515,0.515l-0.258,0.387L45.256,175.546zM43.067,170.01l-0.385,0.258l-1.158-0.128l0.128-0.387L43.067,170.01zM44.999,170.912v0.257l-0.258,0.129l-0.9,0.128l-0.13-0.514h-0.386l-0.258-0.387l0.13-0.128l0.257-0.129l0.257,0.385l0.516-0.128L44.999,170.912zM39.335,169.496l-0.515-0.643l0.386-0.13l0.515-0.257l0.386,0.643h0.257l0.258,0.516h-0.515l-0.257-0.129h-0.129H39.335zM34.829,167.564l0.129-0.256l0.386-0.259l0.643,0.13l0.13,0.129l-0.13,0.514l-0.256,0.258l-0.516-0.129L34.829,167.564z", -"M310.05,308.396l1.674,-0.257l2.704,2.059l1.030,-0.130l2.702,1.805l2.189,1.414l1.545,1.804l-1.158,1.286l0.772,1.545l-1.159,1.674l-3.089,1.545l-2.060,-0.515l-1.416,0.257l-2.447,-1.157l-1.801,0.128l-1.674,-1.545l0.129,-1.674l0.643,-0.643l0,-2.705l0.644,-2.702l-0.772,2.189z", -"M644.487,126.371l0,-1.674l-3.604,-1.158l-2.832,-1.288l-1.674,-1.288l-3.089,-1.931l-1.289,-2.702l-1.029,-0.515l-2.832,0.128l-1.030,-0.515l-0.256,-2.188l-3.735,-1.416l-2.188,1.545l-2.315,0.901l0.384,1.417l-2.959,0l-0.130,-9.914l6.951,-1.544l0.515,0.129l0.644,0.386l1.159,0.515l2.317,1.030l2.189,1.029l2.574,2.446l3.219,-0.385l4.633,-0.259l3.219,2.060l-0.258,2.703l1.287,0l0.644,2.189l3.347,0.128l0.771,1.288l1.030,-0.129l1.160,-1.931l3.603,-1.802l1.547,-0.515l0.770,0.258l-2.317,1.801l2.061,1.030l1.929,-0.772l3.090,1.416l-3.346,1.932l-2.060,-0.257l-1.158,0.127l-0.386,-0.772l0.514,-1.287l-3.603,0.644l-0.775,1.801l-1.285,1.417l-2.318,-0.129l-0.643,1.159l1.931,0.644l0.645,2.060l-1.547,2.703l-2.059,-0.515l1.416,0z", -"M273.105,195.242l-0.128,0.644l-1.545,0.257l0.902,1.287l-0.129,1.416l-1.160,1.545l1.030,2.188l1.159,-0.257l0.643,-1.931l-0.900,-0.901l-0.129,-2.060l3.346,-1.159l-0.385,-1.158l1.029,-0.901l0.902,1.931l1.930,0l1.804,1.545l0,0.902l2.444,0l2.962,-0.259l1.544,1.159l2.060,0.387l1.416,-0.902l0.128,-0.644l3.218,-0.128l3.348,-0.130l-2.317,0.773l0.900,1.288l2.189,0.257l2.060,1.288l0.386,2.187l1.417,-0.128l1.028,0.645l-2.187,1.672l-0.130,0.902l0.902,1.029l-0.644,0.516l-1.673,0.385l0,1.287l-0.772,0.772l1.930,2.061l0.257,0.772l-0.900,1.030l-3.090,0.902l-1.930,0.515l-0.774,0.643l-2.059,-0.773l-2.060,-0.257l-0.514,0.257l1.158,0.643l0,1.804l0.387,1.672l2.188,0.259l0.128,0.514l-1.931,0.772l-0.257,1.159l-1.159,0.515l-1.930,0.644l-0.515,0.772l-2.060,0.129l-1.544,-1.417l-0.773,-2.703l-0.772,-0.901l-1.031,-0.644l1.416,-1.287l-0.127,-0.644l-0.773,-0.772l-0.516,-1.802l0.259,-1.931l0.513,-0.901l0.517,-1.416l-0.902,-0.515l-1.545,0.386l-1.931,-0.129l-1.158,0.258l-1.802,-2.317l-1.546,-0.387l-3.475,0.258l-0.644,-0.901l-0.772,-0.257l0,-0.516l0.257,-1.030l-0.128,-1.028l-0.644,-0.645l-0.387,-1.287l-1.415,-0.129l0.772,-1.545l0.387,-1.931l0.772,-1.030l1.029,-0.771l0.644,-1.289l-1.802,0.514z", -"M756.353,168.853l-3.606,2.316l-2.316,2.575l-0.514,1.930l2.059,2.832l2.445,3.476l2.445,1.675l1.674,2.188l1.287,5.020l-0.385,4.634l-2.318,1.802l-3.090,1.803l-2.187,2.188l-3.348,2.574l-0.902,-1.801l0.644,-1.803l-1.929,-1.544l2.316,-1.030l2.834,-0.258l-1.160,-1.674l4.506,-2.059l0.386,-3.218l-0.644,-1.803l0.387,-2.702l-0.645,-1.803l-2.061,-1.931l-1.673,-2.318l-2.188,-3.217l-3.219,-1.674l0.774,-0.902l1.672,-0.772l-1.028,-2.317l-3.347,0l-1.159,-2.446l-1.544,-2.188l1.416,-0.644l2.187,0l2.576,-0.257l2.317,-1.416l1.287,1.030l2.445,0.515l-0.387,1.545l1.289,1.029l-2.704,-0.645z", -"M915.718,269.777l1.674,1.545l-0.901,0.387l-0.902,-1.160l-0.129,0.772zM914.56,269.133l-0.387,-0.643l0,-2.060l1.287,0.773l0.387,2.189l-0.774,-0.387l0.513,-0.128z", -"M608.315,182.111l-1.931,0.772l-0.516,1.159l-0.127,0.901l-2.704,1.159l-4.249,1.287l-2.445,1.802l-1.158,0.258l-0.774,-0.258l-1.673,1.159l-1.673,0.515l-2.189,0.128l-0.772,0.130l-0.515,0.772l-0.772,0.128l-0.386,0.774l-1.287,-0.130l-0.903,0.387l-1.930,-0.129l-0.644,-1.545l0.129,-1.546l-0.516,-0.772l-0.513,-1.930l-0.772,-1.158l0.514,-0.129l-0.258,-1.159l0.388,-0.515l-0.130,-1.288l1.158,-0.772l-0.258,-1.158l0.645,-1.288l1.158,0.644l0.773,-0.257l3.090,0l0.514,0.257l2.574,0.257l1.031,-0.128l0.644,0.901l1.287,-0.515l1.931,-2.832l2.445,-1.159l7.853,-1.030l2.061,4.378l-0.900,-1.930z", -"M550.13,305.822l-0.516,0.387l-1.158,1.287l-0.773,1.416l-1.544,1.93l-2.96,2.832l-1.932,1.545l-2.061,1.287l-2.832,1.031l-1.287,0.128l-0.387,0.772l-1.672-0.386l-1.288,0.514l-2.961-0.514l-1.544,0.257l-1.158-0.128l-2.834,1.028l-2.316,0.517l-1.545,1.028h-1.285l-1.16-0.9l-0.9-0.128l-1.158-1.16l-0.131,0.388l-0.385-0.772v-1.546l-0.771-1.801l0.771-0.516v-2.061l-1.802-2.445l-1.288-2.316l-1.931-3.478l1.286-1.415l1.032,0.773l0.384,1.158l1.288,0.129l1.673,0.514l1.418-0.129l2.445-1.416v-9.912l0.772,0.387l1.544,2.574l-0.258,1.674l0.645,0.9l1.93-0.256l1.289-1.287l1.287-0.774l0.643-1.286l1.287-0.645l1.158,0.387l1.288,0.773l2.188,0.129l1.801-0.645l0.258-0.901l0.387-1.287l1.545-0.128l0.772-1.03l0.901-1.804l2.445-2.059l3.733-1.93h1.157l1.289,0.514l0.9-0.387l1.416,0.258l1.287,3.862l0.771,1.802l-0.514,3.09l0.258,0.9l-1.416-0.385l-0.773,0.129l-0.258,0.771l-0.771,1.029L547.94,299l1.545,1.544l1.545-0.386l0.644-1.158h2.059l-0.772,1.93l-0.258,2.318l-0.77,1.157L550.13,305.822zM543.306,304.922l-1.158-0.773l-1.287,0.516l-1.416,1.029l-1.416,1.803l1.931,2.059l1.03-0.258l0.514-0.9l1.417-0.386l0.515-0.901l0.773-1.287L543.306,304.922z", -"M553.476,251.883l1.287,1.160l0.644,2.315l-0.386,0.644l-0.645,2.189l0.516,2.317l-0.773,0.902l-0.772,2.447l1.416,0.770l-8.239,2.189l0.258,1.932l-2.060,0.385l-1.543,1.031l-0.259,1.028l-1.028,0.130l-2.319,2.188l-1.545,1.802l-0.902,0l-0.772,-0.257l-3.088,-0.256l-0.515,-0.259l0,-0.259l-1.030,-0.513l-1.803,-0.258l-2.187,0.644l-1.673,-1.674l-1.804,-2.187l0.128,-8.625l5.536,0.127l-0.257,-1.028l0.387,-0.903l-0.387,-1.287l0.257,-1.286l-0.257,-0.902l0.901,0.130l0.128,0.772l1.289,0l1.672,0.258l0.903,1.158l2.187,0.385l1.674,-0.901l0.644,1.416l2.058,0.387l0.903,1.158l1.159,1.545l2.058,0l-0.258,-2.961l-0.642,0.516l-1.932,-1.031l-0.772,-0.514l0.387,-2.705l0.514,-3.088l-0.642,-1.288l0.771,-1.673l0.643,-0.387l3.733,-0.386l1.030,0.258l1.159,0.773l1.030,0.387l1.675,0.384l-1.543,-0.901z", -"M549.228,286.898l-1.416,-0.257l-0.901,0.386l-1.289,-0.513l-1.157,0l-1.673,-1.290l-2.061,-0.385l-0.772,-1.674l0,-1.030l-1.158,-0.256l-3.089,-2.962l-0.900,-1.544l-0.516,-0.516l-1.030,-2.058l3.088,0.256l0.772,0.257l0.902,0l1.545,-1.802l2.319,-2.188l1.028,-0.130l0.259,-1.028l1.543,-1.031l2.060,-0.385l0.129,1.029l2.317,-0.129l1.287,0.645l0.515,0.645l1.287,0.254l1.415,0.774l0,3.475l-0.513,1.801l-0.128,2.061l0.385,0.773l-0.257,1.545l-0.386,0.258l-0.773,1.930l2.832,-3.089z" -]; diff --git a/ui/analyse/src/study/studyView.js b/ui/analyse/src/study/studyView.js index 557c1ab51b..be299d8bbd 100644 --- a/ui/analyse/src/study/studyView.js +++ b/ui/analyse/src/study/studyView.js @@ -98,13 +98,19 @@ function renderTable(rows) { }))); } + +function urlToLink(text) { + var exp = /\bhttps?:\/\/(?:[a-z]{0,3}\.)?(lichess\.org[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return text.replace(exp, "$1"); +} + function renderPgn(setup) { var obj = setup.fromPgn || setup.game; if (obj) return renderTable([ ['Fen', m('pre#study_fen', '')], ].concat(obj.tags.map(function(tag) { if (tag.name.toLowerCase() !== 'fen') return [ - tag.name, m.trust($.urlToLink(tag.value)) + tag.name, m.trust(urlToLink(tag.value)) ]; }))); } diff --git a/ui/ceval/src/ctrl.js b/ui/ceval/src/ctrl.js index 1e43321fc2..c8dd2a9097 100644 --- a/ui/ceval/src/ctrl.js +++ b/ui/ceval/src/ctrl.js @@ -21,8 +21,8 @@ module.exports = function(opts) { var hoveringUci = m.prop(null); var pool = makePool(stockfishProtocol, { - asmjs: '/assets/vendor/stockfish.js/stockfish.js?v=5', - pnacl: pnaclSupported && '/assets/vendor/stockfish.pexe/nacl/stockfish.nmf?v=5', + asmjs: '/assets/vendor/stockfish.js/stockfish.js?v=6', + pnacl: pnaclSupported && '/assets/vendor/stockfish.pexe/nacl/stockfish.nmf?v=6', onCrash: opts.onCrash }, { minDepth: minDepth, diff --git a/ui/chat/src/enhance.js b/ui/chat/src/enhance.js index 945c8b9116..da131b03ab 100644 --- a/ui/chat/src/enhance.js +++ b/ui/chat/src/enhance.js @@ -29,6 +29,7 @@ function escapeHtml(html) { var movePattern = /\b(\d+)\s?(\.+)\s?(?:[o0-]+|[NBRQK]*[a-h]?[1-8]?x?@?[a-h][0-9]=?[NBRQK]?)\+?\#?[!\?=]*/gi; function moveReplacer(match, turn, dots) { + if (turn < 1 || turn > 200) return match; var ply = turn * 2 - (dots.length > 1 ? 0 : 1); return '' + match + ''; } diff --git a/ui/lobby/src/ctrl.js b/ui/lobby/src/ctrl.js index ba5c22a4f9..b560f309b3 100644 --- a/ui/lobby/src/ctrl.js +++ b/ui/lobby/src/ctrl.js @@ -3,13 +3,14 @@ var socket = require('./socket'); var variant = require('./variant'); var hookRepo = require('./hookRepo'); var seekRepo = require('./seekRepo'); -var store = require('./store'); +var makeStore = require('./store'); var xhr = require('./xhr'); var util = require('chessground').util; module.exports = function(env) { this.data = env.data; + this.data.hooks = []; // no longer preloaded! this.playban = env.playban; this.currentGame = env.currentGame; this.perfIcons = env.perfIcons; @@ -19,6 +20,14 @@ module.exports = function(env) { this.socket = new socket(env.socketSend, this); + var store = makeStore(this.data.me ? this.data.me.username.toLowerCase() : null); + var poolInStorage = lichess.storage.make('lobby.pool-in'); + poolInStorage.listen(function() { + // when another tab joins a pool + this.leavePool(); + m.redraw(); + }.bind(this)); + this.vm = { tab: store.tab.get(), mode: store.mode.get(), @@ -26,7 +35,8 @@ module.exports = function(env) { filterOpen: false, stepHooks: this.data.hooks.slice(0), stepping: false, - redirecting: false + redirecting: false, + poolMember: null }; var flushHooksTimeout; @@ -54,8 +64,18 @@ module.exports = function(env) { flushHooksSchedule(); this.setTab = function(tab) { - if (tab === 'seeks' && tab !== this.vm.tab) xhr.seeks().then(this.setSeeks); - this.vm.tab = store.tab.set(tab); + if (tab !== this.vm.tab) { + + if (tab === 'seeks') xhr.seeks().then(this.setSeeks); + + if (tab === 'real_time') this.socket.realTimeIn(); + else if (this.vm.tab === 'real_time') { + this.socket.realTimeOut(); + this.data.hooks = []; + } + + this.vm.tab = store.tab.set(tab); + } this.vm.filterOpen = false; }.bind(this); @@ -96,6 +116,37 @@ module.exports = function(env) { m.redraw(); }.bind(this); + this.clickPool = function(id) { + if (!this.data.me) { + xhr.anonPoolSeek(this.data.pools.filter(function(p) { + return p.id === id; + })[0]); + this.setTab('real_time'); + } else if (this.vm.poolMember && this.vm.poolMember.id === id) this.leavePool(); + else this.enterPool({ + id: id + }); + }.bind(this); + + this.enterPool = function(member) { + this.setTab('pools'); + this.vm.poolMember = member; + this.poolIn(); + m.redraw(); + }.bind(this); + + this.leavePool = function() { + if (!this.vm.poolMember) return; + this.socket.poolOut(this.vm.poolMember); + this.vm.poolMember = null; + m.redraw(); + }.bind(this); + + this.poolIn = function() { + poolInStorage.set(1); + this.socket.poolIn(this.vm.poolMember); + }.bind(this); + this.gameActivity = function(gameId) { if (this.data.nowPlaying.filter(function(p) { return p.gameId === gameId; @@ -131,5 +182,48 @@ module.exports = function(env) { this.trans = lichess.trans(env.i18n); + this.awake = function() { + switch (this.vm.tab) { + case 'real_time': + this.data.hooks = []; + this.socket.realTimeIn(); + break; + case 'seeks': + xhr.seeks().then(this.setSeeks); + break; + default: + } + }.bind(this); + if (this.playban) setTimeout(lichess.reload, this.playban.remainingSeconds * 1000); + else { + + setInterval(function() { + if (this.vm.poolMember) this.poolIn(); + else if (this.vm.tab === 'real_time' && !this.data.hooks.length) this.socket.realTimeIn(); + }.bind(this), 10 * 1000); + + if (location.hash.indexOf('#pool/') === 0) { + var regex = /^#pool\/(\d+\+\d+)$/ + var match = regex.exec(location.hash); + if (match) { + this.setTab('pools'); + this.enterPool({ + id: match[1] + }); + if (window.history.replaceState) window.history.replaceState(null, null, '/'); + } + } + } + + lichess.pubsub.on('socket.open', function() { + if (this.vm.tab === 'real_time') { + this.data.hooks = []; + this.socket.realTimeIn(); + } else if (this.vm.tab === 'pools' && this.vm.poolMember) this.poolIn(); + }.bind(this)); + + window.addEventListener('beforeunload', function() { + if (this.vm.poolMember) this.socket.poolOut(this.vm.poolMember); + }.bind(this)); }; diff --git a/ui/lobby/src/hookRepo.js b/ui/lobby/src/hookRepo.js index 3cefeceaf3..012933d0f8 100644 --- a/ui/lobby/src/hookRepo.js +++ b/ui/lobby/src/hookRepo.js @@ -27,6 +27,10 @@ module.exports = { init(hook); ctrl.data.hooks.push(hook); }, + setAll: function(ctrl, hooks) { + ctrl.data.hooks = hooks; + initAll(ctrl); + }, remove: function(ctrl, id) { ctrl.data.hooks = ctrl.data.hooks.filter(function(h) { return h.id !== id; diff --git a/ui/lobby/src/legacy.js b/ui/lobby/src/legacy.js new file mode 100644 index 0000000000..125d9330b8 --- /dev/null +++ b/ui/lobby/src/legacy.js @@ -0,0 +1,443 @@ +module.exports = function(element, cfg) { + var lobby; + var nbRoundSpread = $.spreadNumber( + document.querySelector('#nb_games_in_play span'), + 8, + function() { + return lichess.socket.pingInterval(); + }); + var nbUserSpread = $.spreadNumber( + document.querySelector('#nb_connected_players > strong'), + 10, + function() { + return lichess.socket.pingInterval(); + }); + var onFirstConnect = function() { + var gameId = lichess.getParameterByName('hook_like'); + if (!gameId) return; + $.post('/setup/hook/' + lichess.StrongSocket.sri + '/like/' + gameId); + lobby.setTab('real_time'); + window.history.replaceState(null, null, '/'); + }; + lichess.socket = lichess.StrongSocket( + '/lobby/socket/v1', + cfg.data.version, { + receive: function(t, d) { + lobby.socketReceive(t, d); + }, + events: { + n: function(nbUsers, msg) { + nbUserSpread(msg.d); + setTimeout(function() { + nbRoundSpread(msg.r); + }, lichess.socket.pingInterval() / 2); + }, + reload_timeline: function() { + $.ajax({ + url: $("#timeline").data('href'), + success: function(html) { + $('#timeline').html(html); + lichess.pubsub.emit('content_loaded')(); + } + }); + }, + streams: function(html) { + $('#streams_on_air').html(html); + }, + featured: function(o) { + $('#featured_game').html(o.html); + lichess.pubsub.emit('content_loaded')(); + }, + redirect: function(e) { + lobby.setRedirecting(); + $.redirect(e); + }, + tournaments: function(data) { + $("#enterable_tournaments").html(data); + lichess.pubsub.emit('content_loaded')(); + }, + simuls: function(data) { + $("#enterable_simuls").html(data).parent().toggle($('#enterable_simuls tr').length > 0); + lichess.pubsub.emit('content_loaded')(); + }, + reload_forum: function() { + var $newposts = $("div.new_posts"); + setTimeout(function() { + $.ajax({ + url: $newposts.data('url'), + success: function(data) { + $newposts.find('ol').html(data).end().scrollTop(0); + lichess.pubsub.emit('content_loaded')(); + } + }); + }, Math.round(Math.random() * 5000)); + }, + fen: function(e) { + lichess.StrongSocket.defaults.events.fen(e); + lobby.gameActivity(e.id); + } + }, + options: { + name: 'lobby', + onFirstConnect: onFirstConnect + } + }); + + cfg.socketSend = lichess.socket.send; + lobby = LichessLobby.mithril(element, cfg); + + var $startButtons = $('#start_buttons'); + + var sliderTimes = [ + 0, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 25, 30, 35, 40, 45, 60, 90, 120, 150, 180 + ]; + + function sliderTime(v) { + return v < sliderTimes.length ? sliderTimes[v] : 180; + } + + function showTime(v) { + if (v === 1 / 2) return '½'; + if (v === 3 / 4) return '¾'; + return v; + } + + function sliderIncrement(v) { + if (v <= 20) return v; + switch (v) { + case 21: + return 25; + case 22: + return 30; + case 23: + return 35; + case 24: + return 40; + case 25: + return 45; + case 26: + return 60; + case 27: + return 90; + case 28: + return 120; + case 29: + return 150; + default: + return 180; + } + } + + function sliderDays(v) { + if (v <= 3) return v; + switch (v) { + case 4: + return 5; + case 5: + return 7; + case 6: + return 10; + default: + return 14; + } + } + + function sliderInitVal(v, f, max) { + for (var i = 0; i < max; i++) { + if (f(i) === v) return i; + } + } + + function hookToPoolMember(color, data, $ratings) { + var find = function(key) { + for (var i in data) + if (data[i].name === key) return data[i].value; + }; + var valid = color == 'random' && + find('variant') == 1 && + find('mode') == 1 && + find('timeMode') == 1; + if (!valid) return false; + var id = parseInt(find('time')) + '+' + parseInt(find('increment')); + var exists = lichess.lobby.data.pools.filter(function(p) { + return p.id === id; + }).length; + if (!exists) return; + var rating = parseInt($ratings.find('strong:visible').text()); + var range = find('ratingRange').split('-'); + var ratingMin = parseInt(range[0]), + ratingMax = parseInt(range[1]); + var keepRange = (rating - ratingMin) < 300 || (ratingMax - rating) < 300; + return { + id: id, + range: keepRange ? range.join('-') : null + }; + } + + function prepareForm() { + var $form = $('.lichess_overboard'); + var $timeModeSelect = $form.find('#timeMode'); + var $modeChoicesWrap = $form.find('.mode_choice'); + var $modeChoices = $modeChoicesWrap.find('input'); + var $casual = $modeChoices.eq(0), + $rated = $modeChoices.eq(1); + var $variantSelect = $form.find('#variant'); + var $fenPosition = $form.find(".fen_position"); + var $timeInput = $form.find('.time_choice input'); + var $incrementInput = $form.find('.increment_choice input'); + var $daysInput = $form.find('.days_choice input'); + var isHook = $form.hasClass('game_config_hook'); + var $ratings = $form.find('.ratings > div'); + var randomColorVariants = $form.data('random-color-variants').split(','); + var toggleButtons = function() { + var timeMode = $timeModeSelect.val(); + var rated = $rated.prop('checked'); + var timeOk = timeMode != '1' || $timeInput.val() > 0 || $incrementInput.val() > 0; + var ratedOk = !isHook || !rated || timeMode != '0'; + if (timeOk && ratedOk) { + $form.find('.color_submits button').toggleClass('nope', false); + $form.find('.color_submits button:not(.random)').toggle(!rated || randomColorVariants.indexOf($variantSelect.val()) === -1); + } else + $form.find('.color_submits button').toggleClass('nope', true); + }; + var showRating = function() { + var timeMode = $timeModeSelect.val(); + var key; + switch ($variantSelect.val()) { + case '1': + if (timeMode == '1') { + var time = $timeInput.val() * 60 + $incrementInput.val() * 40; + if (time < 180) key = 'bullet'; + else if (time < 480) key = 'blitz'; + else key = 'classical'; + } else key = 'correspondence'; + break; + case '10': + key = 'crazyhouse'; + break; + case '2': + key = 'chess960'; + break; + case '4': + key = 'kingOfTheHill'; + break; + case '5': + key = 'threeCheck'; + break; + case '6': + key = 'antichess' + break; + case '7': + key = 'atomic' + break; + case '8': + key = "horde" + break; + case '9': + key = "racingKings" + break; + } + $ratings.hide().filter('.' + key).show(); + }; + if (isHook) { + var $formTag = $form.find('form'); + if ($form.data('anon')) { + $timeModeSelect.val(1) + .children('.timeMode_2, .timeMode_0') + .prop('disabled', true) + .attr('title', $.trans('You need an account to do that')); + } + var ajaxSubmit = function(color) { + var poolMember = hookToPoolMember(color, $formTag.serializeArray(), $ratings); + $form.find('a.close').click(); + var call = { + url: $formTag.attr('action').replace(/uid-placeholder/, lichess.StrongSocket.sri), + data: $formTag.serialize() + "&color=" + color, + type: 'post' + }; + if (poolMember) { + lobby.enterPool(poolMember); + call.url += '?pool=1'; + } else lobby.setTab($timeModeSelect.val() === '1' ? 'real_time' : 'seeks'); + $.ajax(call); + return false; + }; + $formTag.find('.color_submits button').click(function() { + return ajaxSubmit($(this).val()); + }).attr('disabled', false); + $formTag.submit(function() { + return ajaxSubmit('random'); + }); + } else + $form.find('form').one('submit', function() { + $(this).find('.color_submits').find('button').hide().end().append(lichess.spinnerHtml); + }); + lichess.slider().done(function() { + $timeInput.add($incrementInput).each(function() { + var $input = $(this), + $value = $input.siblings('span'); + var isTimeSlider = $input.parent().hasClass('time_choice'); + $input.hide().after($('
').slider({ + value: sliderInitVal(parseFloat($input.val()), isTimeSlider ? sliderTime : sliderIncrement, 100), + min: 0, + max: isTimeSlider ? 33 : 30, + range: 'min', + step: 1, + slide: function(event, ui) { + var time = (isTimeSlider ? sliderTime : sliderIncrement)(ui.value); + $value.text(isTimeSlider ? showTime(time) : time); + $input.attr('value', time); + showRating(); + toggleButtons(); + } + })); + }); + $daysInput.each(function() { + var $input = $(this), + $value = $input.siblings('span'); + $input.hide().after($('
').slider({ + value: sliderInitVal(parseInt($input.val()), sliderDays, 20), + min: 1, + max: 7, + range: 'min', + step: 1, + slide: function(event, ui) { + var days = sliderDays(ui.value); + $value.text(days); + $input.attr('value', days); + } + })); + }); + $form.find('.rating_range').each(function() { + var $this = $(this); + var $input = $this.find("input"); + var $span = $this.siblings("span.range"); + var min = $input.data("min"); + var max = $input.data("max"); + var values = $input.val() ? $input.val().split("-") : [min, max]; + + $span.text(values.join(' - ')); + $this.slider({ + range: true, + min: min, + max: max, + values: values, + step: 50, + slide: function(event, ui) { + $input.val(ui.values[0] + "-" + ui.values[1]); + $span.text(ui.values[0] + " - " + ui.values[1]); + } + }); + }); + }); + $modeChoices.add($form.find('.members_only input')).on('change', function() { + var rated = $rated.prop('checked'); + var membersOnly = $form.find('.members_only input').prop('checked'); + $form.find('.rating_range_config').toggle(rated || membersOnly); + $form.find('.members_only').toggle(!rated); + toggleButtons(); + }).trigger('change'); + $timeModeSelect.on('change', function() { + var timeMode = $(this).val(); + $form.find('.time_choice, .increment_choice').toggle(timeMode == '1'); + $form.find('.days_choice').toggle(timeMode == '2'); + toggleButtons(); + showRating(); + }).trigger('change'); + + var $fenInput = $fenPosition.find('input'); + var validateFen = $.fp.debounce(function() { + $fenInput.removeClass("success failure"); + var fen = $fenInput.val(); + if (fen) { + $.ajax({ + url: $fenInput.parent().data('validate-url'), + data: { + fen: fen + }, + success: function(data) { + $fenInput.addClass("success"); + $fenPosition.find('.preview').html(data); + $fenPosition.find('a.board_editor').each(function() { + $(this).attr('href', $(this).attr('href').replace(/editor\/.+$/, "editor/" + fen)); + }); + $form.find('.color_submits button').removeClass('nope'); + lichess.pubsub.emit('content_loaded')(); + }, + error: function() { + $fenInput.addClass("failure"); + $fenPosition.find('.preview').html(""); + $form.find('.color_submits button').addClass('nope'); + } + }); + } + }, 200); + $fenInput.on('keyup', validateFen); + + $variantSelect.on('change', function() { + var fen = $(this).val() == '3'; + $fenPosition.toggle(fen); + $modeChoicesWrap.toggle(!fen); + if (fen) { + $casual.click(); + document.body.dispatchEvent(new Event('chessground.resize')); + } + showRating(); + toggleButtons(); + }).trigger('change'); + + $form.find('div.level').each(function() { + var $infos = $(this).find('.ai_info > div'); + $(this).find('label').mouseenter(function() { + $infos.hide().filter('.' + $(this).attr('for')).show(); + }); + $(this).find('#config_level').mouseleave(function() { + var level = $(this).find('input:checked').val(); + $infos.hide().filter('.level_' + level).show(); + }).trigger('mouseout'); + }); + + $form.find('a.close.icon').click(function() { + $form.remove(); + $startButtons.find('a.active').removeClass('active'); + return false; + }); + } + + $startButtons.find('a').not('.disabled').on('mousedown', function() { + lobby.leavePool(); + $.ajax({ + url: $(this).attr('href'), + success: function(html) { + $('.lichess_overboard').remove(); + $('#hooks_wrap').prepend(html); + prepareForm(); + lichess.pubsub.emit('content_loaded')(); + }, + error: function() { + lichess.reload(); + } + }); + $(this).addClass('active').siblings().removeClass('active'); + $('.lichess_overboard').remove(); + return false; + }); + + if (['#ai', '#friend', '#hook'].indexOf(location.hash) !== -1) { + $startButtons + .find('a.config_' + location.hash.replace('#', '')) + .each(function() { + $(this).attr("href", $(this).attr("href") + location.search); + }).trigger('mousedown'); + + if (location.hash === '#hook') { + if (/time=realTime/.test(location.search)) + lobby.setTab('real_time'); + else if (/time=correspondence/.test(location.search)) + lobby.setTab('seeks'); + } + + window.history.replaceState(null, null, '/'); + } +}; diff --git a/ui/lobby/src/main.js b/ui/lobby/src/main.js index d92e99daa2..1463996eba 100644 --- a/ui/lobby/src/main.js +++ b/ui/lobby/src/main.js @@ -2,29 +2,35 @@ var m = require('mithril'); var ctrl = require('./ctrl'); var view = require('./view/main'); +var legacy = require('./legacy'); -module.exports = function(element, opts) { +module.exports = { + mithril: function(element, opts) { - opts.element = element; + opts.element = element; - var controller = new ctrl(opts); + var controller = new ctrl(opts); - m.module(element, { - controller: function() { - return controller; - }, - view: view - }); + m.module(element, { + controller: function() { + return controller; + }, + view: view + }); - return { - socketReceive: controller.socket.receive, - setTab: function(tab) { - controller.setTab(tab); - m.redraw(); - }, - gameActivity: controller.gameActivity, - setRedirecting: controller.setRedirecting - }; + return { + socketReceive: controller.socket.receive, + setTab: function(tab) { + controller.setTab(tab); + m.redraw(); + }, + gameActivity: controller.gameActivity, + setRedirecting: controller.setRedirecting, + enterPool: controller.enterPool, + leavePool: controller.leavePool + }; + }, + legacy: legacy }; // lol, that's for the rest of lichess to access mithril diff --git a/ui/lobby/src/socket.js b/ui/lobby/src/socket.js index a98cd40b2a..d816667bdc 100644 --- a/ui/lobby/src/socket.js +++ b/ui/lobby/src/socket.js @@ -17,6 +17,11 @@ module.exports = function(send, ctrl) { hookRepo.remove(ctrl, id); if (ctrl.vm.tab === 'real_time') m.redraw(); }, + hooks: function(hooks) { + hookRepo.setAll(ctrl, hooks); + ctrl.flushHooks(true); + m.redraw(); + }, hli: function(ids) { hookRepo.syncIds(ctrl, ids.split(',')); if (ctrl.vm.tab === 'real_time') m.redraw(); @@ -26,6 +31,26 @@ module.exports = function(send, ctrl) { } }; + this.realTimeIn = function() { + send('hookIn'); + }; + this.realTimeOut = function() { + send('hookOut'); + }; + + this.poolIn = function(member) { + // last arg=true: must not retry + // because if poolIn is sent before socket opens, + // then poolOut is sent, + // then poolIn shouldn't be sent again after socket opens. + // poolIn is sent anyway on socket open event. + send('poolIn', member, {}, true); + }; + + this.poolOut = function(member) { + send('poolOut', member.id); + }; + this.receive = function(type, data) { if (this.music) this.music.receive(type, data); if (handlers[type]) { @@ -35,8 +60,9 @@ module.exports = function(send, ctrl) { return false; }.bind(this); - lichess.idleTimer(5 * 60 * 1000, partial(send, 'idle', true), function() { - location.reload(); + lichess.idleTimer(3 * 60 * 1000, partial(send, 'idle', true), function() { + send('idle', false); + ctrl.awake(); }); this.music = null; diff --git a/ui/lobby/src/store.js b/ui/lobby/src/store.js index 13626b3716..cc6af5ca03 100644 --- a/ui/lobby/src/store.js +++ b/ui/lobby/src/store.js @@ -1,40 +1,47 @@ -var tab = { - key: 'lichess.lobby.tab', - fix: function(t) { - if (['real_time', 'seeks', 'now_playing'].indexOf(t) === -1) t = 'real_time'; - return t; - } +var tab = function(isAuth) { + var list = ['pools', 'real_time', 'seeks', 'now_playing']; + var defaultTab = isAuth ? 'pools' : 'real_time'; + return { + key: 'lobby.tab', + fix: function(t) { + if (list.indexOf(t) === -1) t = defaultTab; + return t; + } + }; }; var mode = { - key: 'lichess.lobby.mode', + key: 'lobby.mode', fix: function(m) { if (['list', 'chart'].indexOf(m) === -1) m = 'list'; return m; } }; var sort = { - key: 'lichess.lobby.sort', + key: 'lobby.sort', fix: function(m) { if (['rating', 'time'].indexOf(m) === -1) m = 'rating'; return m; } }; -function makeStore(conf) { +function makeStore(conf, userId) { + var fullKey = conf.key + ':' + (userId || '-'); return { set: function(t) { t = conf.fix(t); - lichess.storage.set(conf.key, t); + lichess.storage.set(fullKey, t); return t; }, get: function() { - return conf.fix(lichess.storage.get(conf.key)); + return conf.fix(lichess.storage.get(fullKey)); } }; } -module.exports = { - tab: makeStore(tab), - mode: makeStore(mode), - sort: makeStore(sort) +module.exports = function(userId) { + return { + tab: makeStore(tab(!!userId), userId), + mode: makeStore(mode, userId), + sort: makeStore(sort, userId) + } }; diff --git a/ui/lobby/src/view/main.js b/ui/lobby/src/view/main.js index e71ca9c73d..651fcaf9b8 100644 --- a/ui/lobby/src/view/main.js +++ b/ui/lobby/src/view/main.js @@ -1,6 +1,7 @@ var m = require('mithril'); var renderTabs = require('./tabs'); +var renderPools = require('./pools'); var renderRealTime = require('./realTime/main'); var renderSeeks = require('./correspondence'); var renderPlaying = require('./playing'); @@ -9,6 +10,9 @@ module.exports = function(ctrl) { var body; if (ctrl.playban || ctrl.currentGame) return; switch (ctrl.vm.tab) { + case 'pools': + body = renderPools(ctrl); + break; case 'real_time': body = renderRealTime(ctrl); break; diff --git a/ui/lobby/src/view/pools.js b/ui/lobby/src/view/pools.js new file mode 100644 index 0000000000..1d2bea3bd7 --- /dev/null +++ b/ui/lobby/src/view/pools.js @@ -0,0 +1,30 @@ +var m = require('mithril'); + +function renderRange(range) { + return m('div.range', range.replace('-', ' - ')); +} + +module.exports = function(ctrl) { + var member = ctrl.vm.poolMember; + return [ + ctrl.data.pools.map(function(pool) { + var active = member && member.id === pool.id; + var transp = member && !active; + return m('div.pool', { + class: active ? 'active' : (transp ? 'transp' : ''), + onclick: function() { + ctrl.clickPool(pool.id); + } + }, [ + m('div.clock', pool.lim + '+' + pool.inc), (active && member.range) ? renderRange(member.range) : m('div.perf', pool.perf), + active ? m.trust(lichess.spinnerHtml) : null + ]); + }), + m('div.custom', { + class: member ? 'transp' : '', + onclick: function() { + $('#start_buttons .config_hook').mousedown(); + } + }, 'Custom') + ]; +}; diff --git a/ui/lobby/src/view/tabs.js b/ui/lobby/src/view/tabs.js index db022035da..08c3863f6d 100644 --- a/ui/lobby/src/view/tabs.js +++ b/ui/lobby/src/view/tabs.js @@ -7,6 +7,7 @@ function tab(ctrl, key, active, content) { config: util.bindOnce('mousedown', partial(ctrl.setTab, key)) } if (key === active) attrs.class = 'active'; + else if (key === 'pools' && ctrl.vm.poolMember) attrs.class = 'glow'; return m('a', attrs, content); } @@ -16,7 +17,8 @@ module.exports = function(ctrl) { }).length; var active = ctrl.vm.tab; return [ - tab(ctrl, 'real_time', active, ctrl.trans('realTime')), + tab(ctrl, 'pools', active, 'Quick game'), + tab(ctrl, 'real_time', active, 'Lobby'), tab(ctrl, 'seeks', active, ctrl.trans('correspondence')), (active === 'now_playing' || ctrl.data.nbNowPlaying > 0) ? tab(ctrl, 'now_playing', active, [ ctrl.trans('nbGamesInPlay', ctrl.data.nbNowPlaying), diff --git a/ui/lobby/src/xhr.js b/ui/lobby/src/xhr.js index a210dcfe8c..bccaa5d602 100644 --- a/ui/lobby/src/xhr.js +++ b/ui/lobby/src/xhr.js @@ -2,7 +2,7 @@ var m = require('mithril'); var xhrConfig = function(xhr) { xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.setRequestHeader('Accept', 'application/vnd.lichess.v1+json'); + xhr.setRequestHeader('Accept', 'application/vnd.lichess.v2+json'); } function uncache(url) { @@ -26,5 +26,19 @@ module.exports = { }).then(function(o) { return o.nowPlaying; }); + }, + anonPoolSeek: function(pool) { + return m.request({ + method: 'POST', + url: '/setup/hook/' + lichess.StrongSocket.sri, + data: { + variant: 1, + timeMode: 1, + time: pool.lim, + increment: pool.inc, + days: 1, + color: 'random' + } + }); } }; diff --git a/ui/round/src/view/button.js b/ui/round/src/view/button.js index 8707ec6ae6..8be08dedf9 100644 --- a/ui/round/src/view/button.js +++ b/ui/round/src/view/button.js @@ -14,6 +14,10 @@ function analysisBoardOrientation(data) { } } +function poolUrl(clock) { + return '/#pool/' + (clock.initial / 60) + '+' + clock.increment; +} + module.exports = { standard: function(ctrl, condition, icon, hint, socketMsg, onclick) { // disabled if condition callback is provied and is falsy @@ -168,7 +172,9 @@ module.exports = { followUp: function(ctrl) { var d = ctrl.data; var rematchable = !d.game.rematch && (status.finished(d) || status.aborted(d)) && !d.tournament && !d.simul && !d.game.boosted && (d.opponent.onGame || (!d.clock && d.player.user && d.opponent.user)); - var newable = (status.finished(d) || status.aborted(d)) && d.game.source == 'lobby'; + var newable = (status.finished(d) || status.aborted(d)) && ( + d.game.source === 'lobby' || + d.game.source === 'pool'); return m('div.follow_up', [ ctrl.vm.challengeRematched ? m('div.suggestion.text[data-icon=j]', ctrl.trans('rematchOfferSent') @@ -186,7 +192,7 @@ module.exports = { href: '/tournament/' + d.tournament.id }, ctrl.trans('viewTournament')) : null, newable ? m('a.button', { - href: '/?hook_like=' + d.game.id, + href: d.game.source === 'pool' ? poolUrl(d.clock) : '/?hook_like=' + d.game.id, }, ctrl.trans('newOpponent')) : null, game.replayable(d) ? m('a.button', { href: router.game(d, analysisBoardOrientation(d)) + (ctrl.replaying() ? '#' + ctrl.vm.ply : '') diff --git a/ui/tournamentSchedule/src/view.js b/ui/tournamentSchedule/src/view.js index ed4c89edb1..71e57ced6a 100644 --- a/ui/tournamentSchedule/src/view.js +++ b/ui/tournamentSchedule/src/view.js @@ -82,27 +82,6 @@ function splitOverlaping(lanes) { return ret; } -// Tries to compress lanes by moving tours -// upwards until it hits another tournament. -// Should not bubble past existing ones. -function bubbleUp(lanes, tours) { - var returnLanes = lanes.concat([]); - tours.forEach(function(tour) { - var i = returnLanes.length - 1; - while (i >= 0) { - if (!fitLane(returnLanes[i], tour)) - break; - i--; - } - if (i + 1 >= returnLanes.length) { - returnLanes.push([tour]); - } else { - returnLanes[i + 1].push(tour); - } - }); - return returnLanes; -} - function tournamentClass(tour) { var classes = [ 'tournament', @@ -225,7 +204,7 @@ module.exports = function(ctrl) { // group system tournaments into dedicated lanes for PerfType var tourLanes = splitOverlaping( - bubbleUp(group(ctrl.data.systemTours, laneGrouper), ctrl.data.userTours)); + group(ctrl.data.systemTours, laneGrouper).concat([ctrl.data.userTours])); return m('div.schedule.dragscroll', { config: function(el, isUpdate) {