lila/modules/insight/src/main/Dimension.scala

384 lines
12 KiB
Scala
Raw Normal View History

2015-11-26 21:05:59 -07:00
package lila.insight
2020-02-15 09:27:34 -07:00
import play.api.i18n.Lang
2017-04-10 12:08:44 -06:00
import play.api.libs.json._
2020-02-15 09:27:34 -07:00
import reactivemongo.api.bson._
import scalatags.Text.all._
2015-11-25 03:27:01 -07:00
2016-06-29 03:35:35 -06:00
import chess.opening.EcopeningDB
2017-04-10 12:08:44 -06:00
import chess.{ Color, Role }
2016-04-02 00:11:09 -06:00
import lila.db.dsl._
import lila.rating.PerfType
2019-11-29 11:08:13 -07:00
sealed abstract class Dimension[A: BSONHandler](
2015-11-25 03:27:01 -07:00
val key: String,
val name: String,
val dbKey: String,
2015-11-26 02:30:31 -07:00
val position: Position,
val description: String
) {
2015-11-25 03:27:01 -07:00
2019-11-29 11:08:13 -07:00
def bson = implicitly[BSONHandler[A]]
2015-11-25 03:27:01 -07:00
2015-11-26 02:30:31 -07:00
def isInGame = position == Position.Game
def isInMove = position == Position.Move
2015-11-25 03:27:01 -07:00
}
object Dimension {
2015-11-25 03:27:01 -07:00
import BSONHandlers._
2015-11-26 02:30:31 -07:00
import Position._
2021-01-22 01:18:06 -07:00
import InsightEntry.{ BSONFields => F }
2017-07-18 05:35:59 -06:00
import lila.rating.BSONHandlers.perfTypeIdHandler
2015-11-25 03:27:01 -07:00
2019-12-13 07:30:20 -07:00
case object Period
extends Dimension[Period](
"period",
"Date",
F.date,
Game,
"The date at which the game was played"
2019-12-13 07:30:20 -07:00
)
case object Date
extends Dimension[lila.insight.DateRange](
"date",
"Date",
F.date,
Game,
"The date at which the game was played"
2019-12-13 07:30:20 -07:00
)
case object Perf
extends Dimension[PerfType](
"variant",
"Variant",
F.perf,
Game,
"The rating category of the game, like Bullet, Blitz, or Chess960."
2019-12-13 07:30:20 -07:00
)
case object Phase
extends Dimension[Phase](
"phase",
"Game phase",
F.moves("p"),
Move,
"The portion of the game: Opening, Middlegame, or Endgame."
2019-12-13 07:30:20 -07:00
)
case object Result
extends Dimension[Result](
"result",
"Game result",
F.result,
Game,
"Whether you won, lost, or drew the game."
2019-12-13 07:30:20 -07:00
)
case object Termination
extends Dimension[Termination](
"termination",
"Game termination",
F.termination,
Game,
"The way that the game ended, like Checkmate or Resignation."
2019-12-13 07:30:20 -07:00
)
case object Color
extends Dimension[Color](
"color",
"Color",
F.color,
Game,
"The side you are playing: White or Black."
2019-12-13 07:30:20 -07:00
)
case object Opening
extends Dimension[chess.opening.Ecopening](
"opening",
"Opening",
F.eco,
Game,
"ECO identification of the initial moves, like \"A58 Benko Gambit\"."
2019-12-13 07:30:20 -07:00
)
case object OpponentStrength
extends Dimension[RelativeStrength](
"opponentStrength",
"Opponent strength",
F.opponentStrength,
Game,
"Rating of your opponent compared to yours. Much weaker:-200, Weaker:-100, Stronger:+100, Much stronger:+200."
2019-12-13 07:30:20 -07:00
)
case object PieceRole
extends Dimension[Role](
"piece",
"Piece moved",
F.moves("r"),
Move,
"The type of piece you move."
2019-12-13 07:30:20 -07:00
)
case object MovetimeRange
extends Dimension[MovetimeRange](
"movetime",
"Move time",
F.moves("t"),
Move,
"The amount of time you spend thinking on each move, in seconds."
2019-12-13 07:30:20 -07:00
)
case object MyCastling
extends Dimension[Castling](
"myCastling",
"My castling side",
F.myCastling,
Game,
"The side you castled on during the game: kingside, queenside, or none."
2019-12-13 07:30:20 -07:00
)
case object OpCastling
extends Dimension[Castling](
"opCastling",
"Opponent castling side",
F.opponentCastling,
Game,
"The side your opponent castled on during the game: kingside, queenside, or none."
2019-12-13 07:30:20 -07:00
)
case object QueenTrade
extends Dimension[QueenTrade](
"queenTrade",
"Queen trade",
F.queenTrade,
Game,
"Whether queens were traded before the endgame or not."
2019-12-13 07:30:20 -07:00
)
case object MaterialRange
extends Dimension[MaterialRange](
"material",
"Material imbalance",
F.moves("i"),
Move,
"Value of your pieces compared to your opponent's. Pawn=1, Bishop/Knight=3, Rook=5, Queen=9."
2019-12-13 07:30:20 -07:00
)
2015-12-02 01:15:24 -07:00
2020-05-27 12:41:50 -06:00
case object Blur
extends Dimension[Blur](
"blur",
"Move blur",
F.moves("b"),
Move,
"Whether a window blur happened before that move or not."
2020-05-27 12:41:50 -06:00
)
2020-05-28 12:47:06 -06:00
case object TimeVariance
extends Dimension[TimeVariance](
"timeVariance",
"Time variance",
F.moves("v"),
Move,
"Move time variance. Very consistent: < 0.25, Consistent: < 0.4, Medium, Variable: > 0.6, Very variable > 0.75."
2020-05-28 12:47:06 -06:00
)
case object CplRange
extends Dimension[CplRange](
"cpl",
"Centipawn loss",
F.moves("c"),
Move,
"Centipawns lost by each move, according to Stockfish evaluation."
)
case object EvalRange
extends Dimension[EvalRange](
"eval",
"Evaluation",
F.moves("e"),
Move,
"Stockfish evaluation of the position, relative to the player, in centipawns."
)
2020-05-05 22:11:15 -06:00
def requiresStableRating(d: Dimension[_]) =
d match {
case OpponentStrength => true
case _ => false
}
2015-11-30 23:31:41 -07:00
2020-05-05 22:11:15 -06:00
def valuesOf[X](d: Dimension[X]): List[X] =
d match {
case Period => lila.insight.Period.selector
case Date => Nil // Period is used instead
case Perf => PerfType.nonPuzzle
case Phase => lila.insight.Phase.all
case Result => lila.insight.Result.all
case Termination => lila.insight.Termination.all
case Color => chess.Color.all
case Opening => EcopeningDB.all
case OpponentStrength => RelativeStrength.all
case PieceRole => chess.Role.all.reverse
case MovetimeRange => lila.insight.MovetimeRange.all
case CplRange => lila.insight.CplRange.all
2020-05-05 22:11:15 -06:00
case MyCastling | OpCastling => lila.insight.Castling.all
case QueenTrade => lila.insight.QueenTrade.all
case MaterialRange => lila.insight.MaterialRange.all
case EvalRange => lila.insight.EvalRange.all
2020-05-27 12:41:50 -06:00
case Blur => lila.insight.Blur.all
2020-05-28 12:47:06 -06:00
case TimeVariance => lila.insight.TimeVariance.all
2020-05-05 22:11:15 -06:00
}
2015-11-25 22:20:25 -07:00
2020-05-05 22:11:15 -06:00
def valueByKey[X](d: Dimension[X], key: String): Option[X] =
d match {
case Period => key.toIntOption map lila.insight.Period.apply
case Date => None
case Perf => PerfType.byKey get key
case Phase => key.toIntOption flatMap lila.insight.Phase.byId.get
case Result => key.toIntOption flatMap lila.insight.Result.byId.get
case Termination => key.toIntOption flatMap lila.insight.Termination.byId.get
2020-09-21 03:31:16 -06:00
case Color => chess.Color.fromName(key)
2020-05-05 22:11:15 -06:00
case Opening => EcopeningDB.allByEco get key
case OpponentStrength => key.toIntOption flatMap RelativeStrength.byId.get
case PieceRole => chess.Role.all.find(_.name == key)
case MovetimeRange => key.toIntOption flatMap lila.insight.MovetimeRange.byId.get
case CplRange => key.toIntOption flatMap lila.insight.CplRange.byId.get
2020-05-05 22:11:15 -06:00
case MyCastling | OpCastling => key.toIntOption flatMap lila.insight.Castling.byId.get
case QueenTrade => lila.insight.QueenTrade(key == "true").some
case MaterialRange => key.toIntOption flatMap lila.insight.MaterialRange.byId.get
case EvalRange => key.toIntOption flatMap lila.insight.EvalRange.byId.get
2020-05-27 12:41:50 -06:00
case Blur => lila.insight.Blur(key == "true").some
2020-05-28 12:47:06 -06:00
case TimeVariance => key.toFloatOption map lila.insight.TimeVariance.byId
2020-05-05 22:11:15 -06:00
}
2015-11-26 08:17:35 -07:00
2020-02-15 09:27:34 -07:00
def valueToJson[X](d: Dimension[X])(v: X)(implicit lang: Lang): play.api.libs.json.JsObject = {
2015-12-05 05:32:37 -07:00
play.api.libs.json.Json.obj(
2019-12-13 07:30:20 -07:00
"key" -> valueKey(d)(v),
2017-04-10 12:08:44 -06:00
"name" -> valueJson(d)(v)
)
2015-11-25 22:20:25 -07:00
}
2019-12-13 07:30:20 -07:00
def valueKey[X](d: Dimension[X])(v: X): String =
(d match {
case Date => v.toString
case Period => v.days.toString
case Perf => v.key
case Phase => v.id
case Result => v.id
case Termination => v.id
case Color => v.name
case Opening => v.eco
case OpponentStrength => v.id
case PieceRole => v.name
case MovetimeRange => v.id
case CplRange => v.cpl
2019-12-13 07:30:20 -07:00
case MyCastling | OpCastling => v.id
case QueenTrade => v.id
case MaterialRange => v.id
case EvalRange => v.id
2020-05-27 12:41:50 -06:00
case Blur => v.id
2020-05-28 12:47:06 -06:00
case TimeVariance => v.id
2019-12-13 07:30:20 -07:00
}).toString
2015-12-05 05:32:37 -07:00
2020-05-05 22:11:15 -06:00
def valueJson[X](d: Dimension[X])(v: X)(implicit lang: Lang): JsValue =
d match {
case Date => JsNumber(v.min.getSeconds)
case Period => JsString(v.toString)
case Perf => JsString(v.trans)
case Phase => JsString(v.name)
case Result => JsString(v.name)
case Termination => JsString(v.name)
case Color => JsString(v.toString)
case Opening => JsString(v.ecoName)
case OpponentStrength => JsString(v.name)
case PieceRole => JsString(v.toString)
case MovetimeRange => JsString(v.name)
case CplRange => JsString(v.name)
2020-05-05 22:11:15 -06:00
case MyCastling | OpCastling => JsString(v.name)
case QueenTrade => JsString(v.name)
case MaterialRange => JsString(v.name)
case EvalRange => JsString(v.name)
2020-05-27 12:41:50 -06:00
case Blur => JsString(v.name)
2020-05-28 12:47:06 -06:00
case TimeVariance => JsString(v.name)
2020-05-05 22:11:15 -06:00
}
2017-04-10 12:08:44 -06:00
def filtersOf[X](d: Dimension[X], selected: List[X]): Bdoc = {
import cats.implicits._
2020-05-05 22:11:15 -06:00
d match {
case Dimension.MovetimeRange =>
selected match {
case Nil => $empty
case many =>
$doc(
"$or" -> many.map(lila.insight.MovetimeRange.toRange).map { range =>
$doc(d.dbKey $gte range._1 $lt range._2)
}
)
2020-05-05 22:11:15 -06:00
}
case Dimension.Period =>
selected.maximumByOption(_.days).fold($empty) { period =>
2020-05-05 22:11:15 -06:00
$doc(d.dbKey $gt period.min)
}
case Dimension.MaterialRange =>
selected match {
case Nil => $empty
case many =>
$doc(
"$or" -> many.map { range =>
val intRange = lila.insight.MaterialRange.toRange(range)
if (intRange._1 == intRange._2) $doc(d.dbKey -> intRange._1)
else if (range.negative)
$doc(d.dbKey $gte intRange._1 $lt intRange._2)
else
$doc(d.dbKey $gt intRange._1 $lte intRange._2)
}
)
}
case Dimension.EvalRange =>
selected match {
case Nil => $empty
case many =>
$doc(
"$or" -> many.map { range =>
val intRange = lila.insight.EvalRange.toRange(range)
if (range.eval < 0)
$doc(d.dbKey $gte intRange._1 $lt intRange._2)
else
$doc(d.dbKey $gt intRange._1 $lte intRange._2)
}
)
}
case Dimension.TimeVariance =>
selected match {
case Nil => $empty
case many =>
$doc(
"$or" -> many.map(lila.insight.TimeVariance.toRange).map { range =>
$doc(d.dbKey $gt range._1 $lte range._2)
}
)
}
2020-05-05 22:11:15 -06:00
case _ =>
selected flatMap d.bson.writeOpt match {
case Nil => $empty
case List(x) => $doc(d.dbKey -> x)
case xs => d.dbKey $in xs
2020-05-05 22:11:15 -06:00
}
}
}
2017-04-10 12:08:44 -06:00
def requiresAnalysis(d: Dimension[_]) =
d match {
case CplRange => true
case EvalRange => true
case _ => false
}
2020-05-05 22:11:15 -06:00
def dataTypeOf[X](d: Dimension[X]): String =
d match {
case Date => "date"
case _ => "text"
}
}