From 42abc09f581d46a77cec9065e4b22af151ab5c6e Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Thu, 6 Jun 2013 15:15:58 +0200 Subject: [PATCH] satisfying AI move and analysis --- modules/ai/src/main/stockfish/ActorFSM.scala | 2 - modules/ai/src/main/stockfish/Ai.scala | 2 - .../ai/src/main/stockfish/AnalyseParser.scala | 11 +- modules/ai/src/main/stockfish/Config.scala | 2 - modules/ai/src/main/stockfish/Queue.scala | 30 +++-- modules/ai/src/main/stockfish/Server.scala | 17 ++- modules/ai/src/main/stockfish/actorApi.scala | 28 ++--- modules/ai/src/main/stockfish/model.scala | 104 ------------------ 8 files changed, 42 insertions(+), 154 deletions(-) delete mode 100644 modules/ai/src/main/stockfish/model.scala diff --git a/modules/ai/src/main/stockfish/ActorFSM.scala b/modules/ai/src/main/stockfish/ActorFSM.scala index 637aabf82a..2988f1d4e1 100644 --- a/modules/ai/src/main/stockfish/ActorFSM.scala +++ b/modules/ai/src/main/stockfish/ActorFSM.scala @@ -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 diff --git a/modules/ai/src/main/stockfish/Ai.scala b/modules/ai/src/main/stockfish/Ai.scala index 33f02ef1f7..54857ec169 100644 --- a/modules/ai/src/main/stockfish/Ai.scala +++ b/modules/ai/src/main/stockfish/Ai.scala @@ -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 { diff --git a/modules/ai/src/main/stockfish/AnalyseParser.scala b/modules/ai/src/main/stockfish/AnalyseParser.scala index 3ae89956fc..81df2248d2 100644 --- a/modules/ai/src/main/stockfish/AnalyseParser.scala +++ b/modules/ai/src/main/stockfish/AnalyseParser.scala @@ -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]) = diff --git a/modules/ai/src/main/stockfish/Config.scala b/modules/ai/src/main/stockfish/Config.scala index 54fe5d11c1..48704dc2ff 100644 --- a/modules/ai/src/main/stockfish/Config.scala +++ b/modules/ai/src/main/stockfish/Config.scala @@ -3,8 +3,6 @@ package stockfish import scala.concurrent.duration.FiniteDuration -import model._ - import actorApi._ private[ai] case class Config( diff --git a/modules/ai/src/main/stockfish/Queue.scala b/modules/ai/src/main/stockfish/Queue.scala index 2b0c573c9e..986f7e14ab 100644 --- a/modules/ai/src/main/stockfish/Queue.scala +++ b/modules/ai/src/main/stockfish/Queue.scala @@ -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 } } diff --git a/modules/ai/src/main/stockfish/Server.scala b/modules/ai/src/main/stockfish/Server.scala index 3afe6db0ea..a3878c4422 100644 --- a/modules/ai/src/main/stockfish/Server.scala +++ b/modules/ai/src/main/stockfish/Server.scala @@ -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 ⇒ diff --git a/modules/ai/src/main/stockfish/actorApi.scala b/modules/ai/src/main/stockfish/actorApi.scala index 8e7268aab8..5b36c91829 100644 --- a/modules/ai/src/main/stockfish/actorApi.scala +++ b/modules/ai/src/main/stockfish/actorApi.scala @@ -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) + } } diff --git a/modules/ai/src/main/stockfish/model.scala b/modules/ai/src/main/stockfish/model.scala deleted file mode 100644 index a138503e4c..0000000000 --- a/modules/ai/src/main/stockfish/model.scala +++ /dev/null @@ -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") -}