satisfying AI move and analysis
This commit is contained in:
parent
5e78d3d26b
commit
42abc09f58
|
@ -2,8 +2,6 @@ package lila.ai
|
|||
package stockfish
|
||||
|
||||
import akka.actor.{ Props, Actor, ActorRef, Status, FSM ⇒ AkkaFSM }
|
||||
import model._
|
||||
import model.analyse._
|
||||
|
||||
import actorApi._
|
||||
import lila.analyse.Analysis
|
||||
|
|
|
@ -6,8 +6,6 @@ import lila.analyse.AnalysisMaker
|
|||
|
||||
final class Ai(server: Server) extends lila.ai.Ai {
|
||||
|
||||
import model._
|
||||
|
||||
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)] =
|
||||
withValidSituation(game) {
|
||||
server.play(pgn, initialFen, level) flatMap {
|
||||
|
|
|
@ -8,11 +8,10 @@ import lila.analyse.Info
|
|||
object AnalyseParser {
|
||||
|
||||
// info depth 4 seldepth 5 score cp -3309 nodes 1665 nps 43815 time 38 multipv 1 pv f2e3 d4c5 c1d1 c5g5 d1d2 g5g2 d2c1 e8e3
|
||||
def apply(lines: List[String]): String ⇒ Valid[Int ⇒ Info] =
|
||||
move ⇒
|
||||
findBestMove(lines) toValid "Analysis bestmove not found" flatMap { best ⇒
|
||||
Info(move, best, findCp(lines), findMate(lines))
|
||||
}
|
||||
def apply(lines: List[String], move: String): Valid[Int ⇒ Info] =
|
||||
findBestMove(lines) toValid "Analysis bestmove not found" flatMap { best ⇒
|
||||
Info(move, best, findCp(lines), findMate(lines))
|
||||
}
|
||||
|
||||
private val bestMoveRegex = """^bestmove\s(\w+).*$""".r
|
||||
private def findBestMove(lines: List[String]) =
|
||||
|
@ -24,7 +23,7 @@ object AnalyseParser {
|
|||
private def findCp(lines: List[String]) =
|
||||
lines.tail.headOption map { line ⇒
|
||||
cpRegex.replaceAllIn(line, m ⇒ quoteReplacement(m group 1))
|
||||
} flatMap parseIntOption
|
||||
} flatMap parseIntOption
|
||||
|
||||
private val mateRegex = """^info.*\smate\s(\-?\d+).*$""".r
|
||||
private def findMate(lines: List[String]) =
|
||||
|
|
|
@ -3,8 +3,6 @@ package stockfish
|
|||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
import model._
|
||||
|
||||
import actorApi._
|
||||
|
||||
private[ai] case class Config(
|
||||
|
|
|
@ -19,32 +19,30 @@ private[ai] final class Queue(config: Config) extends Actor {
|
|||
|
||||
case req: PlayReq ⇒ {
|
||||
implicit def timeout = makeTimeout((config moveTime req.level).millis + 100.millis)
|
||||
actor ? req mapTo manifest[Valid[String]] map {
|
||||
case scalaz.Success(move) ⇒ sender ! move
|
||||
case failure ⇒ logwarn("[ai] stockfish play " + failure.toString)
|
||||
} await timeout
|
||||
actor ? req mapTo manifest[Valid[String]] map sender.! await timeout
|
||||
}
|
||||
|
||||
case req: AnalReq ⇒ {
|
||||
implicit def timeout = makeTimeout(config.analyseMoveTime + 100.millis)
|
||||
(actor ? req) mapTo manifest[Valid[Int ⇒ Info]] map {
|
||||
case scalaz.Success(info) ⇒ sender ! info
|
||||
case failure ⇒ logwarn("[ai] stockfish analyse " + failure.toString)
|
||||
} await timeout
|
||||
(actor ? req) mapTo manifest[Valid[Int ⇒ Info]] map sender.! await timeout
|
||||
}
|
||||
|
||||
case FullAnalReq(moveString, fen) ⇒ {
|
||||
implicit def timeout = makeTimeout(config.analyseTimeout)
|
||||
val moves = moveString.split(' ').toList
|
||||
((1 to moves.size - 1).toList map moves.take map { serie ⇒
|
||||
self ? AnalReq(serie mkString " ", fen)
|
||||
}).sequence mapTo manifest[List[Int ⇒ Info]] map { infos ⇒
|
||||
AnalysisMaker(infos.zipWithIndex map (x ⇒ x._1 -> (x._2 + 1)) map {
|
||||
case (info, turn) ⇒ (turn % 2 == 0).fold(
|
||||
info(turn),
|
||||
info(turn) |> { i ⇒ i.copy(score = i.score map (_.negate)) }
|
||||
).pp
|
||||
}, true, none)
|
||||
self ? AnalReq(serie.init mkString " ", serie.last, fen)
|
||||
}).sequence addFailureEffect {
|
||||
case e ⇒ sender ! Status.Failure(e)
|
||||
} mapTo manifest[List[Valid[Int ⇒ Info]]] map {
|
||||
_.sequence map { infos ⇒
|
||||
AnalysisMaker(infos.zipWithIndex map (x ⇒ x._1 -> (x._2 + 1)) map {
|
||||
case (info, turn) ⇒ (turn % 2 == 1).fold(
|
||||
info(turn),
|
||||
info(turn) |> { i ⇒ i.copy(score = i.score map (_.negate)) }
|
||||
)
|
||||
}, true, none)
|
||||
}
|
||||
} pipeTo sender
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import scala.concurrent.duration._
|
|||
|
||||
import akka.actor.{ Props, Actor, ActorRef, Kill }
|
||||
import akka.pattern.{ ask, AskTimeoutException }
|
||||
import model.{ GetQueueSize, QueueSize }
|
||||
import play.api.libs.concurrent.Akka.system
|
||||
import play.api.Play.current
|
||||
|
||||
|
@ -18,18 +17,18 @@ private[ai] final class Server(queue: ActorRef, config: Config) {
|
|||
|
||||
def play(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
|
||||
implicit val timeout = makeTimeout(config.playTimeout)
|
||||
UciDump(pgn, initialFen) fold (
|
||||
err ⇒ fufail(err),
|
||||
moves ⇒ queue ? PlayReq(moves, initialFen map chess960Fen, level) mapTo manifest[String]
|
||||
)
|
||||
UciDump(pgn, initialFen).future flatMap { moves ⇒
|
||||
queue ? PlayReq(moves, initialFen map chess960Fen, level) mapTo
|
||||
manifest[Valid[String]] flatMap (_.future)
|
||||
}
|
||||
}
|
||||
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] = {
|
||||
implicit val timeout = makeTimeout(config.analyseTimeout)
|
||||
UciDump(pgn, initialFen).fold(
|
||||
err ⇒ fufail(err),
|
||||
moves ⇒ queue ? FullAnalReq(moves, initialFen map chess960Fen) mapTo manifest[AnalysisMaker]
|
||||
)
|
||||
UciDump(pgn, initialFen).future flatMap { moves ⇒
|
||||
queue ? FullAnalReq(moves, initialFen map chess960Fen) mapTo
|
||||
manifest[Valid[AnalysisMaker]] flatMap (_.future)
|
||||
}
|
||||
}
|
||||
|
||||
private def chess960Fen(fen: String) = (Forsyth << fen).fold(fen) { situation ⇒
|
||||
|
|
|
@ -4,6 +4,16 @@ package actorApi
|
|||
|
||||
import akka.actor.ActorRef
|
||||
|
||||
sealed trait State
|
||||
case object Starting extends State
|
||||
case object Idle extends State
|
||||
case object IsReady extends State
|
||||
case object Running extends State
|
||||
|
||||
sealed trait Stream { def text: String }
|
||||
case class Out(text: String) extends Stream
|
||||
case class Err(text: String) extends Stream
|
||||
|
||||
sealed trait Req {
|
||||
def moves: String
|
||||
def fen: Option[String]
|
||||
|
@ -23,15 +33,10 @@ case class PlayReq(
|
|||
|
||||
case class AnalReq(
|
||||
moves: String,
|
||||
playedMove: String,
|
||||
fen: Option[String]) extends Req {
|
||||
|
||||
def analyse = true
|
||||
// def nextMove: Option[String] = moves lift analysis.size
|
||||
|
||||
// def flush = for {
|
||||
// move ← nextMove toValid "No move to flush"
|
||||
// info ← AnalyseParser(infoBuffer)(move)
|
||||
// } yield copy(analysis = analysis + info, infoBuffer = Nil)
|
||||
}
|
||||
|
||||
case class FullAnalReq(moves: String, fen: Option[String])
|
||||
|
@ -41,12 +46,9 @@ case class Job(req: Req, sender: akka.actor.ActorRef, buffer: List[String]) {
|
|||
def +(str: String) = req.analyse.fold(copy(buffer = str :: buffer), this)
|
||||
|
||||
// bestmove xyxy ponder xyxy
|
||||
def complete(str: String): Valid[Any] =
|
||||
req.analyse.fold(
|
||||
(this + str) |> {
|
||||
case Job(req, _, buffer) ⇒
|
||||
req.moveList.lastOption toValid "empty move list" flatMap AnalyseParser(buffer)
|
||||
},
|
||||
str.split(' ') lift 1 toValid "no bestmove found in " + str)
|
||||
def complete(str: String): Valid[Any] = req match {
|
||||
case r: PlayReq ⇒ str.split(' ') lift 1 toValid "no bestmove found in " + str
|
||||
case AnalReq(_, played, _) ⇒ AnalyseParser(str :: buffer, played)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
package lila.ai
|
||||
package stockfish
|
||||
|
||||
import akka.actor.ActorRef
|
||||
|
||||
import chess.format.UciMove
|
||||
import chess.Pos.posAt
|
||||
import lila.analyse.{ Analysis, AnalysisBuilder }
|
||||
|
||||
object model {
|
||||
|
||||
type Task = Either[play.Task, analyse.Task]
|
||||
|
||||
sealed trait Data {
|
||||
def queue: Vector[Task]
|
||||
def enqueue(task: Task): Data
|
||||
def enqueue(task: play.Task): Data = enqueue(Left(task))
|
||||
def enqueue(task: analyse.Task): Data = enqueue(Right(task))
|
||||
def size = queue.size
|
||||
def dequeue: Option[(Task, Vector[Task])] =
|
||||
queue find (_.isLeft) orElse queue.headOption map { task ⇒
|
||||
task -> queue.filter(task !=)
|
||||
}
|
||||
def fold[A](todo: Todo ⇒ A, doing: Doing ⇒ A): A
|
||||
|
||||
override def toString = getClass.getName + " = " + queue.size
|
||||
}
|
||||
case class Todo(queue: Vector[Task] = Vector.empty) extends Data {
|
||||
def doing[A](withTask: Doing ⇒ A, without: Todo ⇒ A) = dequeue.fold(without(this)) {
|
||||
case (task, rest) ⇒ withTask(Doing(task, rest))
|
||||
}
|
||||
def fold[A](todo: Todo ⇒ A, doing: Doing ⇒ A): A = todo(this)
|
||||
def enqueue(task: Task) = copy(queue :+ task)
|
||||
}
|
||||
case class Doing(current: Task, queue: Vector[Task]) extends Data {
|
||||
def done = Todo(queue)
|
||||
def fold[A](todo: Todo ⇒ A, doing: Doing ⇒ A): A = doing(this)
|
||||
def enqueue(task: Task) = copy(queue = queue :+ task)
|
||||
def map(f: Task ⇒ Task): Doing = copy(current = f(current))
|
||||
}
|
||||
|
||||
object play {
|
||||
|
||||
case class Task(
|
||||
moves: String,
|
||||
fen: Option[String],
|
||||
level: Int,
|
||||
ref: ActorRef) {
|
||||
def chess960 = fen.isDefined
|
||||
}
|
||||
|
||||
object Task {
|
||||
case class Builder(moves: String, fen: Option[String], level: Int) {
|
||||
def apply(sender: ActorRef) = new Task(moves, fen, level, sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object analyse {
|
||||
|
||||
case class Task(
|
||||
moves: IndexedSeq[String],
|
||||
fen: Option[String],
|
||||
analysis: AnalysisBuilder,
|
||||
infoBuffer: List[String],
|
||||
ref: ActorRef) {
|
||||
def pastMoves: String = moves take analysis.size mkString " "
|
||||
def nextMove: Option[String] = moves lift analysis.size
|
||||
def isDone = nextMove.isEmpty
|
||||
def buffer(str: String) = copy(infoBuffer = str :: infoBuffer)
|
||||
def flush = for {
|
||||
move ← nextMove toValid "No move to flush"
|
||||
info ← AnalyseParser(infoBuffer)(move)
|
||||
} yield copy(analysis = analysis + info, infoBuffer = Nil)
|
||||
def chess960 = fen.isDefined
|
||||
}
|
||||
|
||||
object Task {
|
||||
case class Builder(moves: String, fen: Option[String]) {
|
||||
def apply(sender: ActorRef) = new Task(
|
||||
moves = moves.split(' ').toIndexedSeq,
|
||||
fen = fen,
|
||||
analysis = new AnalysisBuilder(Nil),
|
||||
infoBuffer = Nil,
|
||||
sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait State
|
||||
case object Starting extends State
|
||||
case object Idle extends State
|
||||
case object IsReady extends State
|
||||
case object Running extends State
|
||||
|
||||
sealed trait Stream { def text: String }
|
||||
case class Out(text: String) extends Stream
|
||||
case class Err(text: String) extends Stream
|
||||
|
||||
case object GetQueueSize
|
||||
case class QueueSize(i: Int)
|
||||
|
||||
case object RebootException extends RuntimeException("The actor timed out")
|
||||
}
|
Loading…
Reference in a new issue