-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Advice.scala
118 lines (103 loc) · 3.79 KB
/
Advice.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package lila.analyse
import chess.format.pgn.Glyph
import lila.tree.Eval._
sealed trait Advice {
def judgment: Advice.Judgement
def info: Info
def prev: Info
def ply = info.ply
def turn = info.turn
def color = info.color
def cp = info.cp
def mate = info.mate
def makeComment(withEval: Boolean, withBestMove: Boolean): String =
withEval.??(evalComment ?? { c => s"($c) " }) +
(this match {
case MateAdvice(seq, _, _, _) => seq.desc
case CpAdvice(judgment, _, _) => judgment.toString
}) + "." + {
withBestMove ?? {
info.variation.headOption ?? { move => s" Best move was $move." }
}
}
def evalComment: Option[String] = {
List(prev.evalComment, info.evalComment).flatten mkString " → "
}.some filter (_.nonEmpty)
}
object Advice {
sealed abstract class Judgement(val glyph: Glyph, val name: String) {
override def toString = name
def isBlunder = this == Judgement.Blunder
}
object Judgement {
object Inaccuracy extends Judgement(Glyph.MoveAssessment.dubious, "Inaccuracy")
object Mistake extends Judgement(Glyph.MoveAssessment.mistake, "Mistake")
object Blunder extends Judgement(Glyph.MoveAssessment.blunder, "Blunder")
val all = List(Inaccuracy, Mistake, Blunder)
}
def apply(prev: Info, info: Info): Option[Advice] = CpAdvice(prev, info) orElse MateAdvice(prev, info)
}
private[analyse] case class CpAdvice(
judgment: Advice.Judgement,
info: Info,
prev: Info
) extends Advice
private[analyse] object CpAdvice {
private val cpJudgements = List(
300 -> Advice.Judgement.Blunder,
100 -> Advice.Judgement.Mistake,
50 -> Advice.Judgement.Inaccuracy
)
def apply(prev: Info, info: Info): Option[CpAdvice] = for {
cp ← prev.cp map (_.ceiled.centipawns)
infoCp ← info.cp map (_.ceiled.centipawns)
delta = (infoCp - cp) |> { d => info.color.fold(-d, d) }
judgment ← cpJudgements find { case (d, n) => d <= delta } map (_._2)
} yield CpAdvice(judgment, info, prev)
}
private[analyse] sealed abstract class MateSequence(val desc: String)
private[analyse] case object MateDelayed extends MateSequence(
desc = "Not the best checkmate sequence"
)
private[analyse] case object MateLost extends MateSequence(
desc = "Lost forced checkmate sequence"
)
private[analyse] case object MateCreated extends MateSequence(
desc = "Checkmate is now unavoidable"
)
private[analyse] object MateSequence {
def apply(prev: Option[Mate], next: Option[Mate]): Option[MateSequence] =
(prev, next).some collect {
case (None, Some(n)) if n.negative => MateCreated
case (Some(p), None) if p.positive => MateLost
case (Some(p), Some(n)) if p.positive && n.negative => MateLost
case (Some(p), Some(n)) if p.positive && n >= p && p <= Mate(5) => MateDelayed
}
}
private[analyse] case class MateAdvice(
sequence: MateSequence,
judgment: Advice.Judgement,
info: Info,
prev: Info
) extends Advice
private[analyse] object MateAdvice {
def apply(prev: Info, info: Info): Option[MateAdvice] = {
def invertCp(cp: Cp) = cp invertIf info.color.black
def invertMate(mate: Mate) = mate invertIf info.color.black
def prevCp = prev.cp.map(invertCp).??(_.centipawns)
def nextCp = info.cp.map(invertCp).??(_.centipawns)
MateSequence(prev.mate map invertMate, info.mate map invertMate) map { sequence =>
import Advice.Judgement._
val judgment = sequence match {
case MateCreated if prevCp < -999 => Inaccuracy
case MateCreated if prevCp < -700 => Mistake
case MateCreated => Blunder
case MateLost if nextCp > 999 => Inaccuracy
case MateLost if nextCp > 700 => Mistake
case MateLost => Blunder
case MateDelayed => Inaccuracy
}
MateAdvice(sequence, judgment, info, prev)
}
}
}