analysis support for forced mates, visual advantage

pull/7/merge
Thibault Duplessis 2012-06-30 15:34:30 +02:00
parent 86ba29f59f
commit 9cb8776f50
8 changed files with 102 additions and 32 deletions

View File

@ -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))

View File

@ -1,13 +1,13 @@
package lila
package analyse
import chess.{ Pos, Color }
import chess.{ Pos, Color, White, Black }
import ai.stockfish.Uci
case class Analysis(
infos: List[Info],
done: Boolean,
fail: Option[String] = None) {
infos: List[Info],
done: Boolean,
fail: Option[String] = None) {
def encode: String = infos map (_.encode) mkString Analysis.separator
@ -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 {
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)
def apply(info: Info, next: Info, turn: Int): Option[Advice] = {
for {
cp info.score map (_.centipawns)
nextCp next.score map (_.centipawns)
delta = nextCp - cp
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)

View File

@ -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>
}

View File

@ -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

View File

@ -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;

View File

@ -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
View File

@ -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