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

304 lines
10 KiB
Scala

package lila.insight
import chess.opening.Ecopening
import chess.{ Color, Role }
import org.joda.time.DateTime
import lila.game.{ Game, Pov }
import lila.rating.PerfType
import lila.user.User
case class InsightEntry(
id: String, // gameId + w/b
number: Int, // auto increment over userId
userId: User.ID,
color: Color,
perf: PerfType,
eco: Option[Ecopening],
myCastling: Castling,
opponentRating: Int,
opponentStrength: RelativeStrength,
opponentCastling: Castling,
moves: List[InsightMove],
queenTrade: QueenTrade,
result: Result,
termination: Termination,
ratingDiff: Int,
analysed: Boolean,
provisional: Boolean,
date: DateTime
) {
def gameId = id take Game.gameIdSize
}
case object InsightEntry {
def povToId(pov: Pov) = pov.gameId + pov.color.letter
object BSONFields {
val id = "_id"
val number = "n"
val userId = "u"
val color = "c"
val perf = "p"
val eco = "e"
val myCastling = "mc"
val opponentRating = "or"
val opponentStrength = "os"
val opponentCastling = "oc"
val moves: String = "m"
def moves(f: String): String = s"$moves.$f"
val queenTrade = "q"
val result = "r"
val termination = "t"
val ratingDiff = "rd"
val analysed = "a"
val provisional = "pr"
val date = "d"
}
}
case class InsightMove(
phase: Phase,
tenths: Int,
role: Role,
eval: Option[Int], // before the move was played, relative to player
mate: Option[Int], // before the move was played, relative to player
cpl: Option[Int], // eval diff caused by the move, relative to player, mate ~= 10
material: Int, // material imbalance, relative to player
opportunism: Option[Boolean],
luck: Option[Boolean],
blur: Boolean,
timeCv: Option[Float] // time coefficient variation
)
sealed abstract class Termination(val id: Int, val name: String)
object Termination {
case object ClockFlag extends Termination(1, "Clock flag")
case object Disconnect extends Termination(2, "Disconnect")
case object Resignation extends Termination(3, "Resignation")
case object Draw extends Termination(4, "Draw")
case object Stalemate extends Termination(5, "Stalemate")
case object Checkmate extends Termination(6, "Checkmate")
val all = List(ClockFlag, Disconnect, Resignation, Draw, Stalemate, Checkmate)
val byId = all map { p =>
(p.id, p)
} toMap
import chess.{ Status => S }
def fromStatus(s: chess.Status) =
s match {
case S.Timeout => Disconnect
case S.Outoftime => ClockFlag
case S.Resign => Resignation
case S.Draw => Draw
case S.Stalemate => Stalemate
case S.Mate | S.VariantEnd => Checkmate
case S.Cheat => Resignation
case S.Created | S.Started | S.Aborted | S.NoStart | S.UnknownFinish =>
logger.error(s"Unfinished game in the insight indexer: $s")
Resignation
}
}
sealed abstract class Result(val id: Int, val name: String)
object Result {
case object Win extends Result(1, "Victory")
case object Draw extends Result(2, "Draw")
case object Loss extends Result(3, "Defeat")
val all = List(Win, Draw, Loss)
val byId = all map { p =>
(p.id, p)
} toMap
val idList = all.map(_.id)
}
sealed abstract class Phase(val id: Int, val name: String)
object Phase {
case object Opening extends Phase(1, "Opening")
case object Middle extends Phase(2, "Middlegame")
case object End extends Phase(3, "Endgame")
val all = List(Opening, Middle, End)
val byId = all map { p =>
(p.id, p)
} toMap
def of(div: chess.Division, ply: Int): Phase =
div.middle.fold[Phase](Opening) {
case m if m > ply => Opening
case _ =>
div.end.fold[Phase](Middle) {
case e if e > ply => Middle
case _ => End
}
}
}
sealed abstract class Castling(val id: Int, val name: String)
object Castling {
object Kingside extends Castling(1, "Kingside castling")
object Queenside extends Castling(2, "Queenside castling")
object None extends Castling(3, "No castling")
val all = List(Kingside, Queenside, None)
val byId = all map { p =>
(p.id, p)
} toMap
def fromMoves(moves: Iterable[String]) =
moves.find(_ startsWith "O") match {
case Some("O-O") => Kingside
case Some("O-O-O") => Queenside
case _ => None
}
}
sealed abstract class QueenTrade(val id: Boolean, val name: String)
object QueenTrade {
object Yes extends QueenTrade(true, "Queen trade")
object No extends QueenTrade(false, "No queen trade")
val all = List(Yes, No)
def apply(v: Boolean): QueenTrade = if (v) Yes else No
}
sealed abstract class RelativeStrength(val id: Int, val name: String)
object RelativeStrength {
case object MuchWeaker extends RelativeStrength(10, "Much weaker")
case object Weaker extends RelativeStrength(20, "Weaker")
case object Similar extends RelativeStrength(30, "Similar")
case object Stronger extends RelativeStrength(40, "Stronger")
case object MuchStronger extends RelativeStrength(50, "Much stronger")
val all = List(MuchWeaker, Weaker, Similar, Stronger, MuchStronger)
val byId = all map { p =>
(p.id, p)
} toMap
def apply(diff: Int) =
diff match {
case d if d < -200 => MuchWeaker
case d if d < -100 => Weaker
case d if d > 200 => MuchStronger
case d if d > 100 => Stronger
case _ => Similar
}
}
sealed abstract class MovetimeRange(val id: Int, val name: String, val tenths: Int)
object MovetimeRange {
case object MTR1 extends MovetimeRange(1, "0 to 1 second", 10)
case object MTR3 extends MovetimeRange(3, "1 to 3 seconds", 30)
case object MTR5 extends MovetimeRange(5, "3 to 5 seconds", 50)
case object MTR10 extends MovetimeRange(10, "5 to 10 seconds", 100)
case object MTR30 extends MovetimeRange(30, "10 to 30 seconds", 300)
case object MTRInf extends MovetimeRange(60, "More than 30 seconds", Int.MaxValue)
val all = List(MTR1, MTR3, MTR5, MTR10, MTR30, MTRInf)
def reversedNoInf = all.reverse drop 1
val byId = all map { p =>
(p.id, p)
} toMap
def toRange(mr: MovetimeRange): (Int, Int) =
(
all.indexOf(mr).some.filter(-1 !=).map(_ - 1).flatMap(all.lift).fold(0)(_.tenths),
mr.tenths
)
}
sealed abstract class MaterialRange(val id: Int, val name: String, val imbalance: Int) {
def negative = imbalance <= 0
}
object MaterialRange {
case object Down4 extends MaterialRange(1, "Less than -6", -6)
case object Down3 extends MaterialRange(2, "-3 to -6", -3)
case object Down2 extends MaterialRange(3, "-1 to -3", -1)
case object Down1 extends MaterialRange(4, "0 to -1", 0)
case object Equal extends MaterialRange(5, "Equal", 0)
case object Up1 extends MaterialRange(6, "0 to +1", 1)
case object Up2 extends MaterialRange(7, "+1 to +3", 3)
case object Up3 extends MaterialRange(8, "+3 to +6", 6)
case object Up4 extends MaterialRange(9, "More than +6", Int.MaxValue)
val all = List(Down4, Down3, Down2, Down1, Equal, Up1, Up2, Up3, Up4)
def reversedButEqualAndLast = all.diff(List(Equal, Up4)).reverse
val byId = all map { p =>
(p.id, p)
} toMap
def toRange(mr: MaterialRange): (Int, Int) =
if (mr.id == Equal.id) (0, 0)
else
(
byId.get(mr.id - 1).fold(Int.MinValue)(_.imbalance),
mr.imbalance
)
}
sealed abstract class Blur(val id: Boolean, val name: String)
object Blur {
object Yes extends Blur(true, "Blur")
object No extends Blur(false, "No blur")
val all = List(Yes, No)
def apply(v: Boolean): Blur = if (v) Yes else No
}
sealed abstract class TimeVariance(val id: Float, val name: String) {
lazy val intFactored = (id * TimeVariance.intFactor).toInt
}
object TimeVariance {
case object VeryConsistent extends TimeVariance(0.25f, "Very consistent")
case object QuiteConsistent extends TimeVariance(0.4f, "Quite consistent")
case object Medium extends TimeVariance(0.6f, "Medium")
case object QuiteVariable extends TimeVariance(0.75f, "Quite variable")
case object VeryVariable extends TimeVariance(1f, "Very variable")
val all = List(VeryConsistent, QuiteConsistent, Medium, QuiteVariable, VeryVariable)
val byId = all map { p =>
(p.id, p)
} toMap
def apply(v: Float) = all.find(_.id >= v) | VeryVariable
val intFactor: Int = 100_000 // multiply variance by that to get an Int for storage
def toRange(tv: TimeVariance): (Int, Int) =
if (tv == VeryVariable) (QuiteVariable.intFactored, Int.MaxValue)
else
(
all.indexOf(tv).some.filter(-1 !=).map(_ - 1).flatMap(all.lift).fold(0)(_.intFactored),
tv.intFactored
)
}
final class CplRange(val name: String, val cpl: Int)
object CplRange {
val all = List(0, 10, 25, 50, 100, 200, 500, 99999).map { cpl =>
new CplRange(
name =
if (cpl == 0) "Perfect"
else if (cpl == 99999) "> 500 CPL"
else s"$cpl CPL",
cpl = cpl
)
}
val byId = all.map { p =>
(p.cpl, p)
}.toMap
val worse = all.last
}
sealed abstract class EvalRange(val id: Int, val name: String, val eval: Int)
object EvalRange {
case object Down5 extends EvalRange(1, "Less than -600", -600)
case object Down4 extends EvalRange(2, "-350 to -600", -350)
case object Down3 extends EvalRange(3, "-175 to -350", -175)
case object Down2 extends EvalRange(4, "-80 to -175", -80)
case object Down1 extends EvalRange(5, "-25 to -80", -25)
case object Equal extends EvalRange(6, "Equality", 25)
case object Up1 extends EvalRange(7, "+25 to +80", 80)
case object Up2 extends EvalRange(8, "+80 to +175", 175)
case object Up3 extends EvalRange(9, "+175 to +350", 350)
case object Up4 extends EvalRange(10, "+350 to +600", 600)
case object Up5 extends EvalRange(11, "More than +600", Int.MaxValue)
val all = List(Down5, Down4, Down3, Down2, Down1, Equal, Up1, Up2, Up3, Up4, Up5)
def reversedButLast = all.init.reverse
val byId = all map { p =>
(p.id, p)
} toMap
def toRange(er: EvalRange): (Int, Int) = (
byId.get(er.id - 1).fold(Int.MinValue)(_.eval),
er.eval
)
}