analysis support for forced mates, visual advantage
parent
86ba29f59f
commit
9cb8776f50
|
@ -4,13 +4,13 @@ package ai.stockfish
|
|||
import model._
|
||||
import model.play._
|
||||
|
||||
import akka.actor.{ Props, Actor, ActorRef, FSM ⇒ AkkaFSM, LoggingFSM }
|
||||
import akka.actor.{ Props, Actor, ActorRef, FSM ⇒ AkkaFSM }
|
||||
import scalaz.effects._
|
||||
|
||||
final class PlayFSM(
|
||||
processBuilder: (String ⇒ Unit, String ⇒ Unit) ⇒ Process,
|
||||
config: PlayConfig)
|
||||
extends Actor with LoggingFSM[State, Data] {
|
||||
extends Actor with AkkaFSM[State, Data] {
|
||||
|
||||
val process = processBuilder(out ⇒ self ! Out(out), err ⇒ self ! Err(err))
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package lila
|
||||
package analyse
|
||||
|
||||
import chess.{ Pos, Color }
|
||||
import chess.{ Pos, Color, White, Black }
|
||||
import ai.stockfish.Uci
|
||||
|
||||
case class Analysis(
|
||||
|
@ -17,38 +17,79 @@ case class Analysis(
|
|||
}).toList.flatten
|
||||
}
|
||||
|
||||
sealed abstract class Severity(val delta: Int)
|
||||
case object Blunder extends Severity(-300)
|
||||
case object Mistake extends Severity(-100)
|
||||
case object Inaccuracy extends Severity(-50)
|
||||
object Severity {
|
||||
val all = List(Inaccuracy, Mistake, Blunder)
|
||||
def apply(delta: Int): Option[Severity] = all.foldLeft(none[Severity]) {
|
||||
sealed trait Advice {
|
||||
def info: Info
|
||||
def next: Info
|
||||
def turn: Int
|
||||
def text: String
|
||||
|
||||
def color = Color(turn % 2 == 0)
|
||||
def fullMove = 1 + turn / 2
|
||||
}
|
||||
|
||||
sealed abstract class CpSeverity(val delta: Int, val name: String)
|
||||
case object CpBlunder extends CpSeverity(-300, "blunder")
|
||||
case object CpMistake extends CpSeverity(-100, "mistake")
|
||||
case object CpInaccuracy extends CpSeverity(-50, "inaccuracy")
|
||||
object CpSeverity {
|
||||
val all = List(CpInaccuracy, CpMistake, CpBlunder)
|
||||
def apply(delta: Int): Option[CpSeverity] = all.foldLeft(none[CpSeverity]) {
|
||||
case (_, severity) if severity.delta > delta ⇒ severity.some
|
||||
case (acc, _) ⇒ acc
|
||||
}
|
||||
}
|
||||
|
||||
case class Advice(
|
||||
severity: Severity,
|
||||
case class CpAdvice(
|
||||
severity: CpSeverity,
|
||||
info: Info,
|
||||
next: Info,
|
||||
turn: Int) {
|
||||
turn: Int) extends Advice {
|
||||
|
||||
def color = Color(turn % 2 == 0)
|
||||
def text = severity.name
|
||||
}
|
||||
|
||||
def fullMove = 1 + turn / 2
|
||||
sealed abstract class MateSeverity
|
||||
case object MateDelayed extends MateSeverity
|
||||
case object MateLost extends MateSeverity
|
||||
case object MateCreated extends MateSeverity
|
||||
object MateSeverity {
|
||||
def apply(current: Option[Int], next: Option[Int], turn: Int): Option[MateSeverity] =
|
||||
(current, next, Color(turn % 2 == 0)).some collect {
|
||||
case (None, Some(n), White) if n < 0 ⇒ MateCreated
|
||||
case (None, Some(n), Black) if n > 0 ⇒ MateCreated
|
||||
case (Some(c), None, White) if c > 0 ⇒ MateLost
|
||||
case (Some(c), None, Black) if c < 0 ⇒ MateLost
|
||||
case (Some(c), Some(n), White) if c > 0 && c >= n ⇒ MateDelayed
|
||||
case (Some(c), Some(n), Black) if c < 0 && c <= n ⇒ MateDelayed
|
||||
}
|
||||
}
|
||||
case class MateAdvice(
|
||||
severity: MateSeverity,
|
||||
info: Info,
|
||||
next: Info,
|
||||
turn: Int) extends Advice {
|
||||
|
||||
def text = severity.toString
|
||||
}
|
||||
|
||||
object Advice {
|
||||
|
||||
def apply(info: Info, next: Info, turn: Int): Option[Advice] = for {
|
||||
def apply(info: Info, next: Info, turn: Int): Option[Advice] = {
|
||||
for {
|
||||
cp ← info.score map (_.centipawns)
|
||||
nextCp ← next.score map (_.centipawns)
|
||||
color = Color(turn % 2 == 0)
|
||||
delta = nextCp - cp
|
||||
severity ← Severity(color.fold(delta, -delta))
|
||||
} yield Advice(severity, info, next, turn)
|
||||
severity ← CpSeverity(negate(turn)(delta))
|
||||
} yield CpAdvice(severity, info, next, turn)
|
||||
} orElse {
|
||||
val mate = info.mate
|
||||
val nextMate = next.mate
|
||||
MateSeverity(mate, nextMate, turn) map { severity ⇒
|
||||
MateAdvice(severity, info, next, turn)
|
||||
}
|
||||
}
|
||||
|
||||
private def negate(turn: Int)(v: Int) = (turn % 2 == 0).fold(v, -v)
|
||||
}
|
||||
|
||||
object Analysis {
|
||||
|
@ -127,8 +168,9 @@ case class Score(centipawns: Int) {
|
|||
def pawns: Float = centipawns / 100f
|
||||
def showPawns: String = "%.2f" format pawns
|
||||
|
||||
private val percentMax = 5
|
||||
def percent: Int = math.round(box(0, 100,
|
||||
50 + (pawns / 10) * 50
|
||||
50 + (pawns / percentMax) * 50
|
||||
))
|
||||
|
||||
def negate = Score(-centipawns)
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Severity</th>
|
||||
<th>Move</th>
|
||||
<th>Score</th>
|
||||
<th>Severity</th>
|
||||
<th>Advantage</th>
|
||||
<th>Better</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -14,9 +14,15 @@
|
|||
@analysis.advices.map { a =>
|
||||
<tr>
|
||||
<td>@a.fullMove</td>
|
||||
<td>@a.severity.toString</td>
|
||||
<td>@a.color.fold("", "..") @a.info.showMove</td>
|
||||
<td>@a.info.score.map(_.showPawns) ⇒ @a.next.score.map(_.showPawns)</td>
|
||||
<td><span class="user_link @a.color.name">@a.info.showMove</span></td>
|
||||
<td>@a.text</td>
|
||||
<td>
|
||||
@a.next.score.map { score =>
|
||||
<div class="advbar">
|
||||
<div style="width: @score.percent%"></div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>@a.info.showBest</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@ moreJs = moreJs) {
|
|||
</div>
|
||||
</div>
|
||||
<textarea id="pgnText" readonly="readonly">@Html(pgn)</textarea>
|
||||
@analysis.map { a =>
|
||||
<div class="undergame_box game_analysis">
|
||||
@analysis.map { a =>
|
||||
@if(a.done) {
|
||||
@analyse.analysis(pov, a)
|
||||
} else {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 8.2 KiB |
|
@ -677,6 +677,18 @@ div.game_analysis tr:nth-child(odd) td {
|
|||
div.game_analysis table {
|
||||
width: 100%;
|
||||
}
|
||||
div.game_analysis div.advbar {
|
||||
background: #404040;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 3px;
|
||||
min-width: 150px;
|
||||
}
|
||||
div.game_analysis div.advbar div {
|
||||
background: #d0d0d0;
|
||||
height: 12px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
}
|
||||
|
||||
div.game_more div.more_top {
|
||||
padding: 3px 10px;
|
||||
width: 492px;
|
||||
|
|
|
@ -125,6 +125,13 @@ body.dark div.undertable a.user_link {
|
|||
color: #808080;
|
||||
}
|
||||
|
||||
body.dark .user_link.white {
|
||||
background-position: 0 -288px;
|
||||
}
|
||||
body.dark .user_link.black {
|
||||
background-position: 0 -272px;
|
||||
}
|
||||
|
||||
body.dark div.hooks_wrap {
|
||||
box-shadow: 0 0 20px #444;
|
||||
background: rgba(10,10,10,0.8);
|
||||
|
|
3
todo
3
todo
|
@ -26,3 +26,6 @@ or show empty chat
|
|||
game chat box css issue http://en.lichess.org/forum/lichess-feedback/problem-with-the-chat-box#1
|
||||
show last move on miniboards
|
||||
analysis: replace scores with bar (adv+delta visible)
|
||||
legend feedback: http://en.lichess.org/forum/lichess-feedback/a-few-points-bugssuggestions#1
|
||||
http://en.lichess.org/forum/lichess-feedback/problems-with-yin-yang#1
|
||||
also translate websockets error message
|
||||
|
|
Loading…
Reference in New Issue