2016-02-08 08:42:33 -07:00
|
|
|
package lila.explorer
|
|
|
|
|
2019-12-08 20:20:36 -07:00
|
|
|
import akka.stream.scaladsl._
|
2016-02-18 06:04:15 -07:00
|
|
|
import org.joda.time.format.DateTimeFormat
|
2021-10-27 10:53:57 -06:00
|
|
|
import play.api.libs.json._
|
|
|
|
import play.api.libs.ws.JsonBodyWritables._
|
2020-08-18 13:31:32 -06:00
|
|
|
import lila.common.ThreadLocalRandom.nextFloat
|
2019-12-13 07:30:20 -07:00
|
|
|
import scala.util.{ Failure, Success, Try }
|
2016-02-08 08:42:33 -07:00
|
|
|
|
2019-12-03 14:30:53 -07:00
|
|
|
import lila.common.LilaStream
|
2016-04-01 11:50:57 -06:00
|
|
|
import lila.db.dsl._
|
2021-10-27 10:53:57 -06:00
|
|
|
import lila.game.{ Game, GameRepo, Player }
|
2018-10-18 01:31:34 -06:00
|
|
|
import lila.user.{ User, UserRepo }
|
2016-02-08 08:42:33 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
final private class ExplorerIndexer(
|
2019-12-03 14:30:53 -07:00
|
|
|
gameRepo: GameRepo,
|
|
|
|
userRepo: UserRepo,
|
|
|
|
getBotUserIds: lila.user.GetBotIds,
|
2020-08-07 08:22:26 -06:00
|
|
|
ws: play.api.libs.ws.StandaloneWSClient,
|
2019-12-03 14:30:53 -07:00
|
|
|
internalEndpoint: InternalEndpoint
|
2020-06-24 03:37:18 -06:00
|
|
|
)(implicit
|
|
|
|
ec: scala.concurrent.ExecutionContext,
|
|
|
|
mat: akka.stream.Materializer
|
|
|
|
) {
|
2016-02-08 08:42:33 -07:00
|
|
|
|
2020-08-16 07:27:35 -06:00
|
|
|
private val pgnDateFormat = DateTimeFormat forPattern "yyyy.MM.dd"
|
2016-06-12 03:14:14 -06:00
|
|
|
private val internalEndPointUrl = s"$internalEndpoint/import/lichess"
|
2016-02-08 08:42:33 -07:00
|
|
|
|
2020-05-05 22:11:15 -06:00
|
|
|
def apply(game: Game): Funit =
|
|
|
|
getBotUserIds() flatMap { botUserIds =>
|
2021-10-27 10:53:57 -06:00
|
|
|
makeJson(game, botUserIds) map {
|
2020-05-05 22:11:15 -06:00
|
|
|
_ foreach flowBuffer.apply
|
|
|
|
}
|
2018-10-18 01:31:34 -06:00
|
|
|
}
|
2016-02-18 06:04:15 -07:00
|
|
|
|
|
|
|
private object flowBuffer {
|
2016-02-22 22:24:32 -07:00
|
|
|
private val max = 30
|
2021-10-27 10:53:57 -06:00
|
|
|
private val buf = scala.collection.mutable.ArrayBuffer.empty[JsObject]
|
|
|
|
def apply(game: JsObject): Unit = {
|
|
|
|
buf += game
|
2016-02-18 22:39:25 -07:00
|
|
|
val startAt = nowMillis
|
2020-08-17 16:10:52 -06:00
|
|
|
if (buf.sizeIs >= max) {
|
2021-10-27 10:53:57 -06:00
|
|
|
ws.url(internalEndPointUrl).put(JsArray(buf)) andThen {
|
2016-02-18 06:04:15 -07:00
|
|
|
case Success(res) if res.status == 200 =>
|
2019-12-10 14:01:18 -07:00
|
|
|
lila.mon.explorer.index.time.record((nowMillis - startAt) / max)
|
|
|
|
lila.mon.explorer.index.count(true).increment(max)
|
2016-03-10 11:21:04 -07:00
|
|
|
case Success(res) =>
|
|
|
|
logger.warn(s"[${res.status}]")
|
2019-12-10 14:01:18 -07:00
|
|
|
lila.mon.explorer.index.count(false).increment(max)
|
2016-03-10 11:21:04 -07:00
|
|
|
case Failure(err) =>
|
2016-06-12 02:39:49 -06:00
|
|
|
logger.warn(s"$err", err)
|
2019-12-10 14:01:18 -07:00
|
|
|
lila.mon.explorer.index.count(false).increment(max)
|
2016-02-18 06:04:15 -07:00
|
|
|
}
|
2020-07-07 02:34:48 -06:00
|
|
|
buf.clear()
|
2016-02-18 06:04:15 -07:00
|
|
|
}
|
2016-02-08 22:00:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private def valid(game: Game) =
|
|
|
|
game.finished &&
|
|
|
|
game.rated &&
|
2016-02-10 09:55:01 -07:00
|
|
|
game.variant != chess.variant.FromPosition &&
|
2016-09-04 11:07:21 -06:00
|
|
|
!Game.isOldHorde(game)
|
2016-02-08 22:00:49 -07:00
|
|
|
|
|
|
|
private def stableRating(player: Player) = player.rating ifFalse player.provisional
|
|
|
|
|
2016-02-09 02:34:10 -07:00
|
|
|
// probability of the game being indexed, between 0 and 1
|
2021-10-27 10:53:57 -06:00
|
|
|
private def probability(game: Game, rating: Int): Float = {
|
2016-02-09 02:34:10 -07:00
|
|
|
import lila.rating.PerfType._
|
|
|
|
game.perfType ?? {
|
2021-10-27 10:53:57 -06:00
|
|
|
case Correspondence | Classical => 1.00f
|
|
|
|
|
|
|
|
case Rapid if rating >= 2200 => 1.00f
|
2021-11-07 09:50:37 -07:00
|
|
|
case Rapid if rating >= 2000 => 0.83f
|
|
|
|
case Rapid if rating >= 1800 => 0.46f
|
|
|
|
case Rapid if rating >= 1600 => 0.39f
|
2021-10-27 10:53:57 -06:00
|
|
|
case Rapid => 0.02f
|
|
|
|
|
|
|
|
case Blitz if rating >= 2500 => 1.00f
|
2021-11-07 09:50:37 -07:00
|
|
|
case Blitz if rating >= 2200 => 0.38f
|
|
|
|
case Blitz if rating >= 2000 => 0.18f
|
|
|
|
case Blitz if rating >= 1600 => 0.13f
|
2021-10-27 10:53:57 -06:00
|
|
|
case Blitz => 0.02f
|
|
|
|
|
|
|
|
case Bullet if rating >= 2500 => 1.00f
|
2021-11-07 09:50:37 -07:00
|
|
|
case Bullet if rating >= 2200 => 0.48f
|
|
|
|
case Bullet if rating >= 2000 => 0.27f
|
|
|
|
case Bullet if rating >= 1800 => 0.19f
|
|
|
|
case Bullet if rating >= 1600 => 0.18f
|
2021-10-27 10:53:57 -06:00
|
|
|
case Bullet => 0.02f
|
|
|
|
|
|
|
|
case UltraBullet => 1.00f
|
|
|
|
|
|
|
|
case _ if rating >= 1600 => 1.00f // variant games
|
|
|
|
case _ => 0.50f // noob variant games
|
2016-02-09 02:34:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 10:53:57 -06:00
|
|
|
private def makeJson(game: Game, botUserIds: Set[User.ID]): Fu[Option[JsObject]] =
|
2019-12-13 07:30:20 -07:00
|
|
|
~(for {
|
|
|
|
whiteRating <- stableRating(game.whitePlayer)
|
|
|
|
blackRating <- stableRating(game.blackPlayer)
|
2021-10-27 10:53:57 -06:00
|
|
|
if whiteRating >= 1501
|
|
|
|
if blackRating >= 1501
|
2019-12-13 07:30:20 -07:00
|
|
|
averageRating = (whiteRating + blackRating) / 2
|
2020-08-19 13:42:38 -06:00
|
|
|
if probability(game, averageRating) > nextFloat()
|
2019-12-13 07:30:20 -07:00
|
|
|
if !game.userIds.exists(botUserIds.contains)
|
|
|
|
if valid(game)
|
|
|
|
} yield gameRepo initialFen game flatMap { initialFen =>
|
|
|
|
userRepo.usernamesByIds(game.userIds) map { usernames =>
|
|
|
|
def username(color: chess.Color) =
|
|
|
|
game.player(color).userId flatMap { id =>
|
|
|
|
usernames.find(_.toLowerCase == id)
|
|
|
|
} orElse game.player(color).userId getOrElse "?"
|
2021-10-27 10:53:57 -06:00
|
|
|
Json
|
|
|
|
.obj(
|
|
|
|
"id" -> game.id,
|
|
|
|
"variant" -> game.variant.key,
|
|
|
|
"speed" -> game.speed.key,
|
|
|
|
"white" -> Json.obj(
|
|
|
|
"name" -> username(chess.White),
|
|
|
|
"rating" -> whiteRating
|
|
|
|
),
|
|
|
|
"black" -> Json.obj(
|
|
|
|
"name" -> username(chess.Black),
|
|
|
|
"rating" -> blackRating
|
|
|
|
),
|
|
|
|
"winner" -> game.winnerColor.map(_.name),
|
|
|
|
"date" -> pgnDateFormat.print(game.createdAt),
|
|
|
|
"fen" -> initialFen.map(_.value),
|
|
|
|
"moves" -> game.pgnMoves.mkString(" ")
|
|
|
|
)
|
|
|
|
.some
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
|
|
|
})
|
2016-02-08 22:00:49 -07:00
|
|
|
|
2016-03-20 03:31:09 -06:00
|
|
|
private val logger = lila.log("explorer")
|
2016-02-08 08:42:33 -07:00
|
|
|
}
|