Merge branch 'master' into serverchess
* master: fix A.I. variant support, add support for threecheck play & analysis fix AI BC for chess960 remove AI debug prepare multiple variant support for A.I. nodes ignore new stockfish extra output remove stockfish OwnBook option more analysis tweaks properly render equal eval in analysis UI Fix pluralization
This commit is contained in:
commit
76b7a6db12
|
@ -2,6 +2,7 @@ package controllers
|
|||
|
||||
import play.api.mvc._
|
||||
|
||||
import lila.ai.actorApi.{ Chess960, KingOfTheHill, Variant }
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
||||
|
@ -10,12 +11,17 @@ import play.api.Play.current
|
|||
|
||||
object Ai extends LilaController {
|
||||
|
||||
private def requestVariant(req: RequestHeader): Variant =
|
||||
if (get("initialFen", req).isDefined) Chess960
|
||||
else if (getBool("kingOfTheHill", req)) KingOfTheHill
|
||||
else Variant(~get("variant", req))
|
||||
|
||||
def move = Action.async { req =>
|
||||
Env.ai.server.move(
|
||||
uciMoves = get("uciMoves", req) ?? (_.split(' ').toList),
|
||||
initialFen = get("initialFen", req),
|
||||
level = getInt("level", req) | 1,
|
||||
kingOfTheHill = getBool("kingOfTheHill", req)
|
||||
variant = requestVariant(req)
|
||||
) fold (
|
||||
err => {
|
||||
logwarn("[ai] stockfish server play: " + err)
|
||||
|
@ -27,12 +33,11 @@ object Ai extends LilaController {
|
|||
|
||||
def analyse = Action.async { req =>
|
||||
get("replyUrl", req) foreach { replyToUrl =>
|
||||
println(s"analyse gameId ${get("gameId", req)}")
|
||||
Env.ai.server.analyse(
|
||||
uciMoves = get("uciMoves", req) ?? (_.split(' ').toList),
|
||||
initialFen = get("initialFen", req),
|
||||
requestedByHuman = getBool("human", req),
|
||||
kingOfTheHill = getBool("kingOfTheHill", req)
|
||||
variant = requestVariant(req)
|
||||
).effectFold(
|
||||
err => WS.url(replyToUrl).post(err.toString),
|
||||
infos => WS.url(replyToUrl).post(lila.analyse.Info encodeList infos)
|
||||
|
|
|
@ -44,8 +44,8 @@ object Analyse extends LilaController {
|
|||
}
|
||||
|
||||
def postAnalysis(id: String) = Action.async(parse.text) { req =>
|
||||
env.analyser.complete(id, req.body, req.remoteAddress) recoverWith {
|
||||
case e: lila.common.LilaException => fufail(s"${req.remoteAddress} ${e.message}")
|
||||
env.analyser.complete(id, req.body, req.remoteAddress) recover {
|
||||
case e: lila.common.LilaException => logwarn(s"AI ${req.remoteAddress} ${e.message}")
|
||||
} andThenAnyway {
|
||||
Env.hub.socket.round ! Tell(id, AnalysisAvailable)
|
||||
} inject Ok
|
||||
|
|
|
@ -54,9 +54,10 @@ trait SetupHelper { self: I18nHelper =>
|
|||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(chess.variant.FromPosition)
|
||||
|
||||
def translatedVariantChoicesWithFenAndKingOfTheHill(implicit ctx: Context) =
|
||||
def translatedAiVariantChoices(implicit ctx: Context) =
|
||||
translatedVariantChoices(ctx) :+
|
||||
variantTuple(chess.variant.KingOfTheHill) :+
|
||||
variantTuple(chess.variant.ThreeCheck) :+
|
||||
variantTuple(chess.variant.FromPosition)
|
||||
|
||||
def translatedVariantChoicesWithVariantsAndFen(implicit ctx: Context) =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@(form: Form[_], ratings: Map[Int, Int])(implicit ctx: Context)
|
||||
|
||||
@fields = {
|
||||
@setup.variant(form, translatedVariantChoicesWithFenAndKingOfTheHill)
|
||||
@setup.variant(form, translatedAiVariantChoices)
|
||||
@fenInput(form("fen"), true)
|
||||
@setup.timeMode(form, lila.setup.AiConfig)
|
||||
@trans.level()
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
<h2>Is it rated?</h2>
|
||||
@rated.map { r =>
|
||||
@if(r) {
|
||||
This tournaments is rated and will affect your rating.
|
||||
This tournament is rated and will affect your rating.
|
||||
} else {
|
||||
This tournaments is *not* rated and will *not* affect your rating.
|
||||
This tournament is *not* rated and will *not* affect your rating.
|
||||
}
|
||||
}.getOrElse {
|
||||
Some tournaments are rated and will affect your rating.
|
||||
|
|
|
@ -28,11 +28,13 @@ private[ai] final class ActorFSM(
|
|||
config.init foreach process.write
|
||||
loginfo(s"[$name] stockfish is ready")
|
||||
job.fold(goto(Idle))(start)
|
||||
case Event(req: Req, none) => stay using Job(req, sender, Nil).some
|
||||
case Event(req: Req, none) => stay using Job(req, sender, None).some
|
||||
}
|
||||
when(Idle) {
|
||||
case Event(Out(t), _) => sys error s"[$name] Unexpected engine output $t"
|
||||
case Event(req: Req, _) => start(Job(req, sender, Nil))
|
||||
case Event(Out(t), _) if t.nonEmpty =>
|
||||
logwarn(s"""[$name] Unexpected engine output: "$t"""")
|
||||
stay
|
||||
case Event(req: Req, _) => start(Job(req, sender, None))
|
||||
}
|
||||
when(IsReady) {
|
||||
case Event(Out("readyok"), Some(Job(req, _, _))) =>
|
||||
|
@ -45,7 +47,7 @@ private[ai] final class ActorFSM(
|
|||
goto(Running)
|
||||
}
|
||||
when(Running) {
|
||||
case Event(Out(t), Some(job)) if t startsWith "info depth" =>
|
||||
case Event(Out(t), Some(job)) if (t startsWith "info depth") && relevantLine(t) =>
|
||||
stay using (job + t).some
|
||||
case Event(Out(t), Some(job)) if t startsWith "bestmove" =>
|
||||
job.sender ! (job complete t)
|
||||
|
@ -60,9 +62,14 @@ private[ai] final class ActorFSM(
|
|||
case Job(req, sender, _) =>
|
||||
config prepare req foreach process.write
|
||||
process write "isready"
|
||||
goto(IsReady) using Job(req, sender, Nil).some
|
||||
goto(IsReady) using Job(req, sender, None).some
|
||||
}
|
||||
|
||||
private def relevantLine(l: String) =
|
||||
!(l contains "currmovenumber") &&
|
||||
!(l contains "lowerbound") &&
|
||||
!(l contains "upperbound")
|
||||
|
||||
override def postStop() {
|
||||
println(s"======== $name\n${lastWrite mkString "\n"}\n========")
|
||||
process.destroy()
|
||||
|
|
|
@ -26,7 +26,7 @@ final class Client(
|
|||
for {
|
||||
fen ← GameRepo initialFen game
|
||||
uciMoves ← uciMemo get game
|
||||
moveResult ← move(uciMoves.toList, fen, level, game.variant.kingOfTheHill)
|
||||
moveResult ← move(uciMoves.toList, fen, level, Variant(game.variant))
|
||||
uciMove ← (UciMove(moveResult.move) toValid s"${game.id} wrong bestmove: $moveResult").future
|
||||
result ← game.toChess(uciMove.orig, uciMove.dest, uciMove.promotion).future
|
||||
(c, move) = result
|
||||
|
@ -37,25 +37,25 @@ final class Client(
|
|||
|
||||
private val networkLatency = 1 second
|
||||
|
||||
def move(uciMoves: List[String], initialFen: Option[String], level: Int, kingOfTheHill: Boolean): Fu[MoveResult] = {
|
||||
def move(uciMoves: List[String], initialFen: Option[String], level: Int, variant: Variant): Fu[MoveResult] = {
|
||||
implicit val timeout = makeTimeout(config.playTimeout + networkLatency)
|
||||
sendRequest(true) {
|
||||
WS.url(s"$endpoint/move").withQueryString(
|
||||
"uciMoves" -> uciMoves.mkString(" "),
|
||||
"initialFen" -> ~initialFen,
|
||||
"level" -> level.toString,
|
||||
"kingOfTheHill" -> (kingOfTheHill ?? "1"))
|
||||
"variant" -> variant.toString)
|
||||
} map MoveResult.apply
|
||||
}
|
||||
|
||||
def analyse(gameId: String, uciMoves: List[String], initialFen: Option[String], requestedByHuman: Boolean, kingOfTheHill: Boolean) {
|
||||
def analyse(gameId: String, uciMoves: List[String], initialFen: Option[String], requestedByHuman: Boolean, variant: Variant) {
|
||||
WS.url(s"$endpoint/analyse").withQueryString(
|
||||
"replyUrl" -> callbackUrl.replace("%", gameId),
|
||||
"uciMoves" -> uciMoves.mkString(" "),
|
||||
"initialFen" -> ~initialFen,
|
||||
"human" -> requestedByHuman.fold("1", "0"),
|
||||
"gameId" -> gameId,
|
||||
"kingOfTheHill" -> (kingOfTheHill ?? "1")).post("go")
|
||||
"variant" -> variant.toString).post("go")
|
||||
}
|
||||
|
||||
private def sendRequest(retriable: Boolean)(req: WSRequestHolder): Fu[String] =
|
||||
|
|
|
@ -39,18 +39,13 @@ private[ai] case class Config(
|
|||
setoption("Threads", nbThreads),
|
||||
setoption("Ponder", false))
|
||||
|
||||
def prepare(req: Req) = req match {
|
||||
case r: PlayReq => List(
|
||||
setoption("Skill Level", skill(r.level)),
|
||||
setoption("UCI_Chess960", r.chess960),
|
||||
setoption("UCI_KingOfTheHill", r.kingOfTheHill),
|
||||
setoption("OwnBook", ownBook(r.level)))
|
||||
case r: AnalReq => List(
|
||||
setoption("Skill Level", skillMax),
|
||||
setoption("UCI_Chess960", r.chess960),
|
||||
setoption("UCI_KingOfTheHill", r.kingOfTheHill),
|
||||
setoption("OwnBook", true))
|
||||
}
|
||||
def prepare(req: Req) = (req match {
|
||||
case r: PlayReq => setoption("Skill Level", skill(r.level))
|
||||
case r: AnalReq => setoption("Skill Level", skillMax)
|
||||
}) :: List(
|
||||
setoption("UCI_Chess960", req.variant == Chess960),
|
||||
setoption("UCI_KingOfTheHill", req.variant == KingOfTheHill),
|
||||
setoption("UCI_3Check", req.variant == ThreeCheck))
|
||||
|
||||
def go(req: Req): List[String] = req match {
|
||||
case r: PlayReq => List(
|
||||
|
|
|
@ -43,8 +43,8 @@ final class Env(
|
|||
// api actor
|
||||
system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case lila.hub.actorApi.ai.Analyse(gameId, uciMoves, fen, requestedByHuman, kingOfTheHill) =>
|
||||
client.analyse(gameId, uciMoves, fen, requestedByHuman, kingOfTheHill)
|
||||
case lila.hub.actorApi.ai.Analyse(gameId, uciMoves, fen, requestedByHuman, variant) =>
|
||||
client.analyse(gameId, uciMoves, fen, requestedByHuman, actorApi.Variant(variant))
|
||||
}
|
||||
}), name = ActorName)
|
||||
|
||||
|
|
|
@ -58,14 +58,14 @@ private[ai] final class Queue(config: Config) extends Actor {
|
|||
tasks += Task(req, sender, timeout)
|
||||
}
|
||||
|
||||
case FullAnalReq(moves, fen, requestedByHuman, kingOfTheHill) if (requestedByHuman || tasks.size < maxTasks) =>
|
||||
case FullAnalReq(moves, fen, requestedByHuman, variant) if (requestedByHuman || tasks.size < maxTasks) =>
|
||||
val mrSender = sender
|
||||
val size = moves.size
|
||||
implicit val timeout = makeTimeout {
|
||||
if (requestedByHuman) 1.hour else 24.hours
|
||||
}
|
||||
val futures = (0 to size) map moves.take map { serie =>
|
||||
self ? AnalReq(serie, fen, size, requestedByHuman, kingOfTheHill) mapTo manifest[Option[Evaluation]]
|
||||
self ? AnalReq(serie, fen, size, requestedByHuman, variant) mapTo manifest[Option[Evaluation]]
|
||||
}
|
||||
Future.fold(futures)(Vector[Option[Evaluation]]())(_ :+ _) addFailureEffect {
|
||||
case e => mrSender ! Status.Failure(e)
|
||||
|
|
|
@ -15,17 +15,17 @@ private[ai] final class Server(
|
|||
config: Config,
|
||||
uciMemo: lila.game.UciMemo) {
|
||||
|
||||
def move(uciMoves: List[String], initialFen: Option[String], level: Int, kingOfTheHill: Boolean): Fu[MoveResult] = {
|
||||
def move(uciMoves: List[String], initialFen: Option[String], level: Int, variant: Variant): Fu[MoveResult] = {
|
||||
implicit val timeout = makeTimeout(config.playTimeout)
|
||||
queue ? PlayReq(uciMoves, initialFen map chess960Fen, level, kingOfTheHill) mapTo
|
||||
queue ? PlayReq(uciMoves, initialFen map chess960Fen, level, variant) mapTo
|
||||
manifest[Option[String]] flatten "[stockfish] play failed" map MoveResult.apply
|
||||
}
|
||||
|
||||
def analyse(uciMoves: List[String], initialFen: Option[String], requestedByHuman: Boolean, kingOfTheHill: Boolean): Fu[List[Info]] = {
|
||||
def analyse(uciMoves: List[String], initialFen: Option[String], requestedByHuman: Boolean, variant: Variant): Fu[List[Info]] = {
|
||||
implicit val timeout = makeTimeout {
|
||||
if (requestedByHuman) 1.hour else 24.hours
|
||||
}
|
||||
(queue ? FullAnalReq(uciMoves take config.analyseMaxPlies, initialFen map chess960Fen, requestedByHuman, kingOfTheHill)) mapTo manifest[List[Info]]
|
||||
(queue ? FullAnalReq(uciMoves take config.analyseMaxPlies, initialFen map chess960Fen, requestedByHuman, variant)) mapTo manifest[List[Info]]
|
||||
}
|
||||
|
||||
private def chess960Fen(fen: String) = (Forsyth << fen).fold(fen) { situation =>
|
||||
|
|
|
@ -18,22 +18,36 @@ sealed trait Stream { def text: String }
|
|||
case class Out(text: String) extends Stream
|
||||
case class Err(text: String) extends Stream
|
||||
|
||||
sealed trait Variant
|
||||
case object Standard extends Variant
|
||||
case object Chess960 extends Variant
|
||||
case object KingOfTheHill extends Variant
|
||||
case object ThreeCheck extends Variant
|
||||
|
||||
object Variant {
|
||||
def apply(str: String): Variant = str.toLowerCase match {
|
||||
case "chess960" => Chess960
|
||||
case "kingofthehill" => KingOfTheHill
|
||||
case "threecheck" => ThreeCheck
|
||||
case _ => Standard
|
||||
}
|
||||
def apply(v: chess.variant.Variant): Variant = apply(v.key)
|
||||
}
|
||||
|
||||
sealed trait Req {
|
||||
def moves: List[String]
|
||||
def fen: Option[String]
|
||||
def analyse: Boolean
|
||||
def requestedByHuman: Boolean
|
||||
def priority: Int
|
||||
def kingOfTheHill: Boolean
|
||||
|
||||
def chess960 = fen.isDefined
|
||||
def variant: Variant
|
||||
}
|
||||
|
||||
case class PlayReq(
|
||||
moves: List[String],
|
||||
fen: Option[String],
|
||||
level: Int,
|
||||
kingOfTheHill: Boolean) extends Req {
|
||||
variant: Variant) extends Req {
|
||||
|
||||
val analyse = false
|
||||
val requestedByHuman = true
|
||||
|
@ -46,7 +60,7 @@ case class AnalReq(
|
|||
fen: Option[String],
|
||||
totalSize: Int,
|
||||
requestedByHuman: Boolean,
|
||||
kingOfTheHill: Boolean) extends Req {
|
||||
variant: Variant) extends Req {
|
||||
|
||||
val priority =
|
||||
if (requestedByHuman) -totalSize
|
||||
|
@ -61,15 +75,15 @@ case class FullAnalReq(
|
|||
moves: List[String],
|
||||
fen: Option[String],
|
||||
requestedByHuman: Boolean,
|
||||
kingOfTheHill: Boolean)
|
||||
variant: Variant)
|
||||
|
||||
case class Job(req: Req, sender: akka.actor.ActorRef, buffer: List[String]) {
|
||||
case class Job(req: Req, sender: akka.actor.ActorRef, lastLine: Option[String]) {
|
||||
|
||||
def +(str: String) = if (req.analyse) copy(buffer = str :: buffer) else this
|
||||
def +(str: String) = if (req.analyse) copy(lastLine = str.some) else this
|
||||
|
||||
// bestmove xyxy ponder xyxy
|
||||
def complete(str: String): Option[Any] = req match {
|
||||
case _: PlayReq => str split ' ' lift 1
|
||||
case _: AnalReq => buffer.headOption map EvaluationParser.apply
|
||||
case _: AnalReq => lastLine map EvaluationParser.apply
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ final class Analyser(
|
|||
chess.Replay(game.pgnMoves, initialFen, game.variant).fold(
|
||||
fufail(_),
|
||||
replay => {
|
||||
ai ! lila.hub.actorApi.ai.Analyse(game.id, UciDump(replay), initialFen, requestedByHuman = !auto, game.variant.kingOfTheHill)
|
||||
ai ! lila.hub.actorApi.ai.Analyse(game.id, UciDump(replay), initialFen, requestedByHuman = !auto, game.variant)
|
||||
AnalysisRepo byId id flatten "Missing analysis"
|
||||
}
|
||||
)
|
||||
|
|
|
@ -9,6 +9,8 @@ case class Evaluation(
|
|||
|
||||
def checkMate = mate == Some(0)
|
||||
|
||||
def invalid = score.isEmpty && mate.isEmpty
|
||||
|
||||
override def toString = s"Evaluation ${score.fold("?")(_.showPawns)} ${mate | 0} ${line.mkString(" ")}"
|
||||
}
|
||||
|
||||
|
|
|
@ -44,13 +44,13 @@ object Info {
|
|||
|
||||
lazy val start = Info(0, Evaluation.start.score, none, Nil)
|
||||
|
||||
def decode(ply: Int, str: String): Option[Info] = str.split(separator).toList match {
|
||||
case Nil => Info(ply).some
|
||||
case List(cp) => Info(ply, Score(cp)).some
|
||||
case List(cp, ma) => Info(ply, Score(cp), parseIntOption(ma)).some
|
||||
case List(cp, ma, va) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList).some
|
||||
case List(cp, ma, va, be) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList, UciMove piotr be).some
|
||||
case _ => none
|
||||
def decode(ply: Int, str: String): Option[Info] = str.split(separator) match {
|
||||
case Array() => Info(ply).some
|
||||
case Array(cp) => Info(ply, Score(cp)).some
|
||||
case Array(cp, ma) => Info(ply, Score(cp), parseIntOption(ma)).some
|
||||
case Array(cp, ma, va) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList).some
|
||||
case Array(cp, ma, va, be) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList, UciMove piotr be).some
|
||||
case _ => none
|
||||
}
|
||||
|
||||
def decodeList(str: String): Option[List[Info]] = {
|
||||
|
|
|
@ -426,7 +426,8 @@ object Game {
|
|||
val analysableVariants: Set[Variant] = Set(
|
||||
chess.variant.Standard,
|
||||
chess.variant.Chess960,
|
||||
chess.variant.KingOfTheHill)
|
||||
chess.variant.KingOfTheHill,
|
||||
chess.variant.ThreeCheck)
|
||||
|
||||
val unanalysableVariants: Set[Variant] = Variant.all.toSet -- analysableVariants
|
||||
|
||||
|
|
|
@ -49,11 +49,11 @@ case class Shutup(userId: String, text: String)
|
|||
}
|
||||
|
||||
package shutup {
|
||||
case class RecordPublicForumMessage(userId: String, text: String)
|
||||
case class RecordTeamForumMessage(userId: String, text: String)
|
||||
case class RecordPrivateMessage(userId: String, toUserId: String, text: String)
|
||||
case class RecordPrivateChat(chatId: String, userId: String, text: String)
|
||||
case class RecordPublicChat(chatId: String, userId: String, text: String)
|
||||
case class RecordPublicForumMessage(userId: String, text: String)
|
||||
case class RecordTeamForumMessage(userId: String, text: String)
|
||||
case class RecordPrivateMessage(userId: String, toUserId: String, text: String)
|
||||
case class RecordPrivateChat(chatId: String, userId: String, text: String)
|
||||
case class RecordPublicChat(chatId: String, userId: String, text: String)
|
||||
}
|
||||
|
||||
package mod {
|
||||
|
@ -163,7 +163,12 @@ case class MakeTeam(id: String, name: String)
|
|||
}
|
||||
|
||||
package ai {
|
||||
case class Analyse(gameId: String, uciMoves: List[String], initialFen: Option[String], requestedByHuman: Boolean, kingOfTheHill: Boolean)
|
||||
case class Analyse(
|
||||
gameId: String,
|
||||
uciMoves: List[String],
|
||||
initialFen: Option[String],
|
||||
requestedByHuman: Boolean,
|
||||
variant: chess.variant.Variant)
|
||||
case class AutoAnalyse(gameId: String)
|
||||
}
|
||||
|
||||
|
|
|
@ -103,8 +103,8 @@ trait BaseConfig {
|
|||
val variantDefault = chess.variant.Standard
|
||||
|
||||
val variantsWithFen = variants :+ chess.variant.FromPosition.id
|
||||
val variantsWithFenAndKingOfTheHill =
|
||||
variants :+ chess.variant.KingOfTheHill.id :+ chess.variant.FromPosition.id
|
||||
val aiVariants =
|
||||
variants :+ chess.variant.KingOfTheHill.id :+ chess.variant.ThreeCheck.id :+ chess.variant.FromPosition.id
|
||||
val variantsWithVariants =
|
||||
variants :+ chess.variant.KingOfTheHill.id :+ chess.variant.ThreeCheck.id :+ chess.variant.Antichess.id :+ chess.variant.Atomic.id :+ chess.variant.Horde.id
|
||||
val variantsWithFenAndVariants =
|
||||
|
|
|
@ -35,7 +35,7 @@ private[setup] final class FormFactory(casualOnly: Boolean) {
|
|||
|
||||
def ai(ctx: UserContext) = Form(
|
||||
mapping(
|
||||
"variant" -> variantWithFenAndKingOfTheHill,
|
||||
"variant" -> aiVariants,
|
||||
"timeMode" -> timeMode,
|
||||
"time" -> time,
|
||||
"increment" -> increment,
|
||||
|
|
|
@ -11,7 +11,7 @@ object Mappings {
|
|||
|
||||
val variant = number.verifying(Config.variants contains _)
|
||||
val variantWithFen = number.verifying(Config.variantsWithFen contains _)
|
||||
val variantWithFenAndKingOfTheHill = number.verifying(Config.variantsWithFenAndKingOfTheHill contains _)
|
||||
val aiVariants = number.verifying(Config.aiVariants contains _)
|
||||
val variantWithVariants = number.verifying(Config.variantsWithVariants contains _)
|
||||
val variantWithFenAndVariants = number.verifying(Config.variantsWithFenAndVariants contains _)
|
||||
val time = number.verifying(HookConfig validateTime _)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var treePath = require('./path');
|
||||
var defined = require('./util').defined;
|
||||
|
||||
module.exports = function(game, analysis) {
|
||||
|
||||
|
@ -20,8 +21,8 @@ module.exports = function(game, analysis) {
|
|||
var applyAnalysis = function(tree, analysed) {
|
||||
analysed.forEach(function(ana, i) {
|
||||
if (!tree[i]) return;
|
||||
if (ana.mate) tree[i].mate = ana.mate;
|
||||
else if (ana.eval) tree[i].eval = ana.eval;
|
||||
if (defined(ana.mate)) tree[i].mate = ana.mate;
|
||||
else if (defined(ana.eval)) tree[i].eval = ana.eval;
|
||||
if (ana.comment) tree[i].comments.push(ana.comment);
|
||||
if (ana.variation) tree[i].variations.push(makeTree(ana.variation.split(' '), i + 1));
|
||||
});
|
||||
|
|
|
@ -9,5 +9,8 @@ module.exports = {
|
|||
});
|
||||
});
|
||||
return dests;
|
||||
},
|
||||
defined: function(v) {
|
||||
return typeof v !== 'undefined';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var m = require('mithril');
|
||||
var chessground = require('chessground');
|
||||
var classSet = require('chessground').util.classSet;
|
||||
var defined = require('./util').defined;
|
||||
var game = require('game').game;
|
||||
var partial = require('chessground').util.partial;
|
||||
var renderStatus = require('game').view.status;
|
||||
|
@ -43,8 +44,8 @@ function renderMove(ctrl, move, path) {
|
|||
'href': '#' + path[0].ply
|
||||
},
|
||||
children: [
|
||||
move.eval ? renderEvalTag(renderEval(move.eval)) : (
|
||||
move.mate ? renderEvalTag('#' + move.mate) : null
|
||||
defined(move.eval) ? renderEvalTag(renderEval(move.eval)) : (
|
||||
defined(move.mate) ? renderEvalTag('#' + move.mate) : null
|
||||
),
|
||||
move.san
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue