satisfying AI move and analysis

This commit is contained in:
Thibault Duplessis 2013-06-06 15:15:58 +02:00
parent 5e78d3d26b
commit 42abc09f58
8 changed files with 42 additions and 154 deletions

View file

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

View file

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

View file

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

View file

@ -3,8 +3,6 @@ package stockfish
import scala.concurrent.duration.FiniteDuration
import model._
import actorApi._
private[ai] case class Config(

View file

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

View file

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

View file

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

View file

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