unify play and analyse stockfish FSM actors
This commit is contained in:
parent
2c0cb59309
commit
5c5300bfec
|
@ -25,11 +25,9 @@ final class AiEnv(settings: Settings) {
|
|||
|
||||
lazy val stockfishServer = new stockfish.Server(
|
||||
execPath = AiStockfishExecPath,
|
||||
playConfig = stockfishPlayConfig,
|
||||
analyseConfig = stockfishAnalyseConfig)
|
||||
config = stockfishConfig)
|
||||
|
||||
lazy val stockfishPlayConfig = new stockfish.PlayConfig(settings)
|
||||
lazy val stockfishAnalyseConfig = new stockfish.AnalyseConfig(settings)
|
||||
lazy val stockfishConfig = new stockfish.Config(settings)
|
||||
|
||||
lazy val stupidAi = new StupidAi
|
||||
|
||||
|
|
101
app/ai/stockfish/ActorFSM.scala
Normal file
101
app/ai/stockfish/ActorFSM.scala
Normal file
|
@ -0,0 +1,101 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
import model._
|
||||
import model.analyse._
|
||||
|
||||
import akka.actor.{ Props, Actor, ActorRef, FSM ⇒ AkkaFSM, LoggingFSM }
|
||||
import scalaz.effects._
|
||||
|
||||
final class ActorFSM(
|
||||
processBuilder: Process.Builder,
|
||||
config: Config)
|
||||
extends Actor with LoggingFSM[State, Data] {
|
||||
|
||||
var process: Process = _
|
||||
|
||||
override def preStart() {
|
||||
process = processBuilder(
|
||||
out ⇒ self ! Out(out),
|
||||
err ⇒ self ! Err(err),
|
||||
msg ⇒ !isNoise(msg))
|
||||
}
|
||||
|
||||
startWith(Starting, Todo())
|
||||
|
||||
when(Starting) {
|
||||
case Event(Out(t), _) if t startsWith "Stockfish" ⇒ {
|
||||
process write "uci"
|
||||
stay
|
||||
}
|
||||
case Event(Out("uciok"), data) ⇒ {
|
||||
config.init foreach process.write
|
||||
nextTask(data)
|
||||
}
|
||||
case Event(task: analyse.Task.Builder, data) ⇒
|
||||
stay using (data enqueue task(sender))
|
||||
case Event(task: play.Task.Builder, data) ⇒
|
||||
stay using (data enqueue task(sender))
|
||||
}
|
||||
when(Idle) {
|
||||
case Event(Out(t), _) ⇒ { log.warning(t); stay }
|
||||
}
|
||||
when(IsReady) {
|
||||
case Event(Out("readyok"), doing: Doing) ⇒ {
|
||||
config go doing.current foreach process.write
|
||||
goto(Running)
|
||||
}
|
||||
}
|
||||
when(Running) {
|
||||
case Event(Out(t), doing: Doing) if t startsWith "info depth" ⇒
|
||||
stay using (doing map (_.right map (_ buffer t)))
|
||||
case Event(Out(t), doing: Doing) if t startsWith "bestmove" ⇒
|
||||
doing.current.fold(
|
||||
play ⇒ {
|
||||
play.ref ! model.play.BestMove(t.split(' ') lift 1)
|
||||
nextTask(doing.done)
|
||||
},
|
||||
anal ⇒ (anal buffer t).flush.fold(
|
||||
err ⇒ {
|
||||
log error err.shows
|
||||
anal.ref ! failure(err)
|
||||
nextTask(doing.done)
|
||||
},
|
||||
task ⇒ task.isDone.fold({
|
||||
task.ref ! success(task.analysis.done)
|
||||
nextTask(doing.done)
|
||||
},
|
||||
nextTask(doing.done enqueue task)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
whenUnhandled {
|
||||
case Event(task: analyse.Task.Builder, data) ⇒ nextTask(data enqueue task(sender))
|
||||
case Event(task: play.Task.Builder, data) ⇒ nextTask(data enqueue task(sender))
|
||||
case Event(GetQueueSize, data) ⇒ sender ! QueueSize(data.size); stay
|
||||
case Event(Out(t), _) if isNoise(t) ⇒ stay
|
||||
case Event(Out(t), _) ⇒ { log.warning(t); stay }
|
||||
case Event(Err(t), _) ⇒ { log.error(t); stay }
|
||||
case Event(e @ RebootException, _) ⇒ throw e
|
||||
}
|
||||
|
||||
def nextTask(data: Data) = data.fold(
|
||||
todo ⇒ todo.doing(
|
||||
doing ⇒ {
|
||||
config prepare doing.current foreach process.write
|
||||
goto(IsReady) using doing
|
||||
},
|
||||
todo ⇒ goto(Idle) using todo
|
||||
),
|
||||
doing ⇒ stay using doing
|
||||
)
|
||||
|
||||
def isNoise(t: String) =
|
||||
t.isEmpty || (t startsWith "id ") || (t startsWith "info ") || (t startsWith "option name ")
|
||||
|
||||
override def postStop() {
|
||||
process.destroy()
|
||||
process = null
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
import model._
|
||||
import model.analyse._
|
||||
import core.Settings
|
||||
|
||||
final class AnalyseConfig(settings: Settings) extends Config {
|
||||
|
||||
type Instructions = List[String]
|
||||
|
||||
def init: Instructions = List(
|
||||
setoption("Uci_AnalyseMode", true),
|
||||
setoption("Hash", settings.AiStockfishAnalyseHashSize),
|
||||
setoption("Threads", 8),
|
||||
setoption("Ponder", false)
|
||||
)
|
||||
|
||||
def game(analyse: Analyse): Instructions = List(
|
||||
setoption("UCI_Chess960", analyse.chess960)
|
||||
)
|
||||
|
||||
def moveTime = settings.AiStockfishAnalyseMoveTime
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
import model._
|
||||
import model.analyse._
|
||||
|
||||
import akka.actor.{ Props, Actor, ActorRef, FSM ⇒ AkkaFSM, LoggingFSM }
|
||||
import scalaz.effects._
|
||||
|
||||
final class AnalyseFSM(
|
||||
processBuilder: Process.Builder,
|
||||
config: AnalyseConfig)
|
||||
extends Actor with LoggingFSM[State, Data] {
|
||||
|
||||
var process: Process = _
|
||||
|
||||
override def preStart() {
|
||||
process = processBuilder(
|
||||
out ⇒ self ! Out(out),
|
||||
err ⇒ self ! Err(err),
|
||||
msg ⇒ !isNoise(msg))
|
||||
}
|
||||
|
||||
startWith(Starting, Todo())
|
||||
|
||||
when(Starting) {
|
||||
case Event(Out(t), _) if t startsWith "Stockfish" ⇒ {
|
||||
process write "uci"
|
||||
stay
|
||||
}
|
||||
case Event(Out(t), data) if t contains "uciok" ⇒ {
|
||||
config.init foreach process.write
|
||||
nextAnalyse(data)
|
||||
}
|
||||
case Event(analyse: Analyse, data) ⇒
|
||||
stay using (data enqueue Task(analyse, sender))
|
||||
}
|
||||
when(Ready) {
|
||||
case Event(Out(t), _) ⇒ { log.warning(t); stay }
|
||||
}
|
||||
when(UciNewGame) {
|
||||
case Event(Out(t), data: Doing) if t contains "readyok" ⇒
|
||||
nextInfo(data)
|
||||
}
|
||||
when(Running) {
|
||||
case Event(Out(t), data: Doing) if t startsWith "info depth" ⇒ {
|
||||
goto(Running) using (data buffer t)
|
||||
}
|
||||
case Event(Out(t), data: Doing) if t contains "bestmove" ⇒
|
||||
(data buffer t).flush.fold(
|
||||
err ⇒ {
|
||||
log.error(err.shows)
|
||||
data.current.ref ! failure(err)
|
||||
nextAnalyse(data.done)
|
||||
},
|
||||
nextData ⇒ nextInfo(nextData)
|
||||
)
|
||||
}
|
||||
whenUnhandled {
|
||||
case Event(analyse: Analyse, data) ⇒ nextAnalyse(data enqueue Task(analyse, sender))
|
||||
case Event(GetQueueSize, data) ⇒ sender ! QueueSize(data.size); stay
|
||||
case Event(Out(t), _) if isNoise(t) ⇒ stay
|
||||
case Event(Out(t), _) ⇒ { log.warning(t); stay }
|
||||
case Event(Err(t), _) ⇒ { log.error(t); stay }
|
||||
case Event(e @ RebootException, _) ⇒ throw e
|
||||
}
|
||||
|
||||
def nextAnalyse(data: Data) = data match {
|
||||
case todo: Todo ⇒ todo.doing(
|
||||
doing ⇒ {
|
||||
config game doing.current.analyse foreach process.write
|
||||
process write "ucinewgame"
|
||||
process write "isready"
|
||||
goto(UciNewGame) using doing
|
||||
},
|
||||
t ⇒ goto(Ready) using t
|
||||
)
|
||||
case doing: Doing ⇒ stay using data
|
||||
}
|
||||
|
||||
def nextInfo(doing: Doing) = doing.current |> { task ⇒
|
||||
(task.analyse go config.moveTime).fold(
|
||||
instructions ⇒ {
|
||||
instructions foreach process.write
|
||||
goto(Running) using doing
|
||||
}, {
|
||||
task.ref ! success(task.analyse.analysis.done)
|
||||
nextAnalyse(doing.done)
|
||||
})
|
||||
}
|
||||
|
||||
def isNoise(t: String) =
|
||||
t.isEmpty || (t startsWith "id ") || (t startsWith "info ") || (t startsWith "option name ")
|
||||
|
||||
override def postStop() {
|
||||
process.destroy()
|
||||
process = null
|
||||
}
|
||||
}
|
|
@ -1,9 +1,58 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
trait Config {
|
||||
import model._
|
||||
import core.Settings
|
||||
|
||||
protected def setoption(name: String, value: Any) =
|
||||
final class Config(settings: Settings) {
|
||||
|
||||
import Config._
|
||||
|
||||
def moveTime(level: Int) = (levelBox(level) * playMaxMoveTime) / levels.end
|
||||
|
||||
def ownBook(level: Int) = levelBox(level) > 4
|
||||
|
||||
def skill(level: Int) = math.round((levelBox(level) - 1) * (skillMax / 7f))
|
||||
|
||||
def depth(level: Int) = levelBox(level)
|
||||
|
||||
def init = List(
|
||||
setoption("Hash", settings.AiStockfishHashSize),
|
||||
setoption("Threads", settings.AiStockfishThreads),
|
||||
setoption("Ponder", false))
|
||||
|
||||
def prepare(task: Task) = task.fold(
|
||||
play ⇒ List(
|
||||
setoption("Uci_AnalyseMode", false),
|
||||
setoption("Skill Level", skill(play.level)),
|
||||
setoption("UCI_Chess960", play.chess960),
|
||||
setoption("OwnBook", ownBook(play.level)),
|
||||
"ucinewgame",
|
||||
"isready"),
|
||||
anal ⇒ List(
|
||||
setoption("Uci_AnalyseMode", true),
|
||||
setoption("Skill Level", skillMax),
|
||||
setoption("UCI_Chess960", anal.chess960),
|
||||
setoption("OwnBook", true),
|
||||
"ucinewgame",
|
||||
"isready"))
|
||||
|
||||
def go(task: Task) = task.fold(
|
||||
play ⇒ List(
|
||||
position(play.fen, play.moves),
|
||||
"go movetime %d depth %d".format(moveTime(play.level), depth(play.level))),
|
||||
anal ⇒ List(
|
||||
position(anal.fen, anal.pastMoves),
|
||||
"go movetime %d".format(analyseMoveTime)))
|
||||
|
||||
private def playMaxMoveTime = settings.AiStockfishPlayMaxMoveTime
|
||||
|
||||
private def analyseMoveTime = settings.AiStockfishAnalyseMoveTime
|
||||
|
||||
private def position(fen: Option[String], moves: String) =
|
||||
"position %s moves %s".format(fen.fold("fen " + _, "startpos"), moves)
|
||||
|
||||
private def setoption(name: String, value: Any) =
|
||||
"setoption name %s value %s".format(name, value)
|
||||
}
|
||||
|
||||
|
@ -12,4 +61,6 @@ object Config {
|
|||
val levels = 1 to 8
|
||||
|
||||
val levelBox = intBox(1 to 8) _
|
||||
|
||||
val skillMax = 20
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
import model._
|
||||
import model.play._
|
||||
import core.Settings
|
||||
|
||||
final class PlayConfig(settings: Settings) extends Config {
|
||||
|
||||
import Config._
|
||||
|
||||
def moveTime(level: Int) = (levelBox(level) * maxMoveTime) / levels.end
|
||||
|
||||
def ownBook(level: Int) = levelBox(level) > 4
|
||||
|
||||
def skill(level: Int) = math.round((levelBox(level) -1) * (20 / 7f))
|
||||
|
||||
def depth(level: Int) = levelBox(level)
|
||||
|
||||
def init = List(
|
||||
setoption("Hash", settings.AiStockfishPlayHashSize),
|
||||
setoption("Threads", 8),
|
||||
setoption("Ponder", false),
|
||||
setoption("Aggressiveness", settings.AiStockfishPlayAggressiveness),
|
||||
setoption("Cowardice", settings.AiStockfishPlayCowardice)
|
||||
)
|
||||
|
||||
def game(play: Play) = List(
|
||||
setoption("Skill Level", skill(play.level)),
|
||||
setoption("UCI_Chess960", play.chess960),
|
||||
setoption("OwnBook", ownBook(play.level)),
|
||||
"ucinewgame",
|
||||
"isready"
|
||||
)
|
||||
|
||||
def move(fen: Option[String], moves: String, level: Int) = List(
|
||||
"position %s moves %s".format(fen.fold("fen " + _, "startpos"), moves),
|
||||
"go movetime %d depth %d".format(moveTime(level), depth(level))
|
||||
)
|
||||
|
||||
private def maxMoveTime = settings.AiStockfishPlayMaxMoveTime
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package lila
|
||||
package ai.stockfish
|
||||
|
||||
import model._
|
||||
import model.play._
|
||||
|
||||
import akka.actor.{ Props, Actor, ActorRef, FSM ⇒ AkkaFSM }
|
||||
import scalaz.effects._
|
||||
|
||||
final class PlayFSM(
|
||||
processBuilder: Process.Builder,
|
||||
config: PlayConfig)
|
||||
extends Actor with AkkaFSM[State, Data] {
|
||||
|
||||
var process: Process = _
|
||||
|
||||
override def preStart() {
|
||||
process = processBuilder(
|
||||
out ⇒ self ! Out(out),
|
||||
err ⇒ self ! Err(err),
|
||||
msg ⇒ !isNoise(msg))
|
||||
}
|
||||
|
||||
startWith(Starting, Todo())
|
||||
|
||||
when(Starting) {
|
||||
case Event(Out(t), _) if t startsWith "Stockfish" ⇒ {
|
||||
process write "uci"
|
||||
stay
|
||||
}
|
||||
case Event(Out(t), data) if t contains "uciok" ⇒ {
|
||||
config.init foreach process.write
|
||||
next(data)
|
||||
}
|
||||
case Event(play: Play, data) ⇒
|
||||
stay using (data enqueue Task(play, sender))
|
||||
}
|
||||
when(Ready) {
|
||||
case Event(Out(t), _) ⇒ { log.warning(t); stay }
|
||||
}
|
||||
when(UciNewGame) {
|
||||
case Event(Out(t), Doing(Task(play, _), _)) if t contains "readyok" ⇒ {
|
||||
play.go foreach process.write
|
||||
goto(Running)
|
||||
}
|
||||
}
|
||||
when(Running) {
|
||||
case Event(Out(t), doing: Doing) if t contains "bestmove" ⇒ {
|
||||
doing.current.ref ! BestMove(t.split(' ') lift 1)
|
||||
next(doing.done)
|
||||
}
|
||||
}
|
||||
whenUnhandled {
|
||||
case Event(play: Play, data) ⇒ next(data enqueue Task(play, sender))
|
||||
case Event(GetQueueSize, data) ⇒ sender ! QueueSize(data.size); stay
|
||||
case Event(Out(t), _) if isNoise(t) ⇒ stay
|
||||
case Event(Err(t), _) ⇒ { log.error(t); stay }
|
||||
case Event(e @ RebootException, _) ⇒ throw e
|
||||
}
|
||||
|
||||
def next(data: Data) = data match {
|
||||
case todo: Todo ⇒ todo.doing(
|
||||
doing ⇒ {
|
||||
config game doing.current.play foreach process.write
|
||||
goto(UciNewGame) using doing
|
||||
},
|
||||
t ⇒ goto(Ready) using t
|
||||
)
|
||||
case doing: Doing ⇒ stay using data
|
||||
}
|
||||
|
||||
def isNoise(t: String) =
|
||||
t.isEmpty || (t startsWith "id ") || (t startsWith "info ") || (t startsWith "option name ")
|
||||
|
||||
override def postStop() {
|
||||
process.destroy()
|
||||
process = null
|
||||
}
|
||||
}
|
|
@ -20,19 +20,18 @@ import scalaz.effects._
|
|||
|
||||
final class Server(
|
||||
execPath: String,
|
||||
playConfig: PlayConfig,
|
||||
analyseConfig: AnalyseConfig) {
|
||||
config: Config) {
|
||||
|
||||
def play(pgn: String, initialFen: Option[String], level: Int): Future[Valid[String]] = {
|
||||
implicit val timeout = new Timeout(playAtMost)
|
||||
(for {
|
||||
moves ← UciDump(pgn, initialFen)
|
||||
play = model.play.Play(moves, initialFen map chess960Fen, level, playConfig)
|
||||
play = model.play.Task.Builder(moves, initialFen map chess960Fen, level)
|
||||
} yield play).fold(
|
||||
err ⇒ Future(failure(err)),
|
||||
play ⇒ playActor ? play mapTo bestMoveManifest map { m ⇒
|
||||
play ⇒ actor ? play mapTo bestMoveManifest map { m ⇒
|
||||
success(m.move | "")
|
||||
} onFailure reboot(playActor)
|
||||
} onFailure reboot
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -40,16 +39,16 @@ final class Server(
|
|||
UciDump(pgn, initialFen).fold(
|
||||
err ⇒ Future(failure(err)),
|
||||
moves ⇒ {
|
||||
val analyse = model.analyse.Analyse(moves, initialFen map chess960Fen)
|
||||
val analyse = model.analyse.Task.Builder(moves, initialFen map chess960Fen)
|
||||
implicit val timeout = Timeout(analyseAtMost)
|
||||
analyseActor ? analyse mapTo analysisManifest onFailure reboot(analyseActor)
|
||||
actor ? analyse mapTo analysisManifest onFailure reboot
|
||||
}
|
||||
)
|
||||
|
||||
def report: Future[(Int, Int)] = {
|
||||
def report: Future[Int] = {
|
||||
implicit val timeout = new Timeout(playAtMost)
|
||||
(playActor ? GetQueueSize) zip (analyseActor ? GetQueueSize) map {
|
||||
case (QueueSize(play), QueueSize(analyse)) ⇒ play -> analyse
|
||||
actor ? GetQueueSize map {
|
||||
case QueueSize(s) ⇒ s
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +62,7 @@ final class Server(
|
|||
} mkString ""),
|
||||
fen)
|
||||
|
||||
private def reboot(actor: ActorRef): PartialFunction[Throwable, Unit] = {
|
||||
private val reboot: PartialFunction[Throwable, Unit] = {
|
||||
case e: AskTimeoutException ⇒ actor ! model.RebootException
|
||||
}
|
||||
private val analysisManifest = manifest[Valid[Analysis]]
|
||||
|
@ -72,12 +71,9 @@ final class Server(
|
|||
private implicit val executor = Akka.system.dispatcher
|
||||
|
||||
private val playAtMost = 5 seconds
|
||||
private lazy val playProcess = Process(execPath, "SFP") _
|
||||
private lazy val playActor = Akka.system.actorOf(Props(
|
||||
new PlayFSM(playProcess, playConfig)))
|
||||
|
||||
private val analyseAtMost = 10 minutes
|
||||
private lazy val analyseProcess = Process(execPath, "SFA") _
|
||||
private lazy val analyseActor = Akka.system.actorOf(Props(
|
||||
new AnalyseFSM(analyseProcess, analyseConfig)))
|
||||
|
||||
private lazy val process = Process(execPath, "StockFish") _
|
||||
private lazy val actor = Akka.system.actorOf(Props(
|
||||
new ActorFSM(process, config)))
|
||||
}
|
||||
|
|
|
@ -6,111 +6,95 @@ import chess.format.UciMove
|
|||
import analyse.{ Analysis, AnalysisBuilder }
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import collection.immutable.Queue
|
||||
|
||||
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({
|
||||
case (task, rest) ⇒ withTask(Doing(task, rest))
|
||||
}, without(this))
|
||||
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 Play(
|
||||
moves: String,
|
||||
fen: Option[String],
|
||||
level: Int,
|
||||
config: PlayConfig) {
|
||||
def go = config.move(fen, moves, level)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
case class BestMove(move: Option[String]) {
|
||||
def parse = UciMove(move | "")
|
||||
}
|
||||
|
||||
case class Task(play: Play, ref: ActorRef)
|
||||
|
||||
sealed trait Data {
|
||||
def queue: Queue[Task]
|
||||
def enqueue(task: Task): Data
|
||||
def size = queue.size
|
||||
}
|
||||
case class Todo(queue: Queue[Task] = Queue.empty) extends Data {
|
||||
def enqueue(task: Task) = copy(queue = queue :+ task)
|
||||
def doing[A](withTask: Doing ⇒ A, without: Todo ⇒ A) =
|
||||
queue.headOption.fold(
|
||||
task ⇒ withTask(Doing(task, queue.tail)),
|
||||
without(Todo(Queue.empty))
|
||||
)
|
||||
}
|
||||
case class Doing(current: Task, queue: Queue[Task]) extends Data {
|
||||
def enqueue(task: Task) = copy(queue = queue :+ task)
|
||||
def done = Todo(queue)
|
||||
}
|
||||
}
|
||||
|
||||
object analyse {
|
||||
|
||||
case class Analyse(
|
||||
case class Task(
|
||||
moves: IndexedSeq[String],
|
||||
fen: Option[String],
|
||||
analysis: AnalysisBuilder,
|
||||
infoBuffer: List[String]) {
|
||||
|
||||
def go(moveTime: Int) = nextMove.isDefined option List(
|
||||
"position %s moves %s".format(
|
||||
fen.fold("fen " + _, "startpos"),
|
||||
moves take analysis.size mkString " "),
|
||||
"go movetime %d".format(moveTime)
|
||||
)
|
||||
|
||||
def nextMove = moves lift analysis.size
|
||||
|
||||
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)
|
||||
|
||||
} yield copy(analysis = analysis + info, infoBuffer = Nil)
|
||||
def chess960 = fen.isDefined
|
||||
}
|
||||
object Analyse {
|
||||
def apply(moves: String, fen: Option[String]) = new Analyse(
|
||||
moves = moves.split(' ').toIndexedSeq,
|
||||
fen = fen,
|
||||
analysis = Analysis.builder,
|
||||
infoBuffer = Nil)
|
||||
}
|
||||
|
||||
case class Task(analyse: Analyse, ref: ActorRef) {
|
||||
def buffer(str: String) = copy(analyse = analyse buffer str)
|
||||
def flush = analyse.flush map { a ⇒ copy(analyse = a) }
|
||||
}
|
||||
|
||||
sealed trait Data {
|
||||
def queue: Queue[Task]
|
||||
def enqueue(task: Task): Data
|
||||
def size = queue.size
|
||||
}
|
||||
case class Todo(queue: Queue[Task] = Queue.empty) extends Data {
|
||||
def enqueue(task: Task) = copy(queue = queue :+ task)
|
||||
def doing[A](withTask: Doing ⇒ A, without: Todo ⇒ A) =
|
||||
queue.headOption.fold(
|
||||
task ⇒ withTask(Doing(task, queue.tail)),
|
||||
without(Todo(Queue.empty))
|
||||
)
|
||||
}
|
||||
case class Doing(current: Task, queue: Queue[Task]) extends Data {
|
||||
def enqueue(task: Task) = copy(queue = queue :+ task)
|
||||
def done = Todo(queue)
|
||||
def buffer(str: String) = copy(current = current buffer str)
|
||||
def flush = current.flush map { c ⇒ copy(current = c) }
|
||||
object Task {
|
||||
case class Builder(moves: String, fen: Option[String]) {
|
||||
def apply(sender: ActorRef) = new Task(
|
||||
moves = moves.split(' ').toIndexedSeq,
|
||||
fen = fen,
|
||||
analysis = Analysis.builder,
|
||||
infoBuffer = Nil,
|
||||
sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait State
|
||||
case object Starting extends State
|
||||
case object Ready extends State
|
||||
case object UciNewGame extends State
|
||||
case object Idle extends State
|
||||
case object IsReady extends State
|
||||
case object Running extends State
|
||||
|
||||
sealed trait Stream { def text: String }
|
||||
|
|
|
@ -51,7 +51,7 @@ object Ai extends LilaController {
|
|||
IfServer {
|
||||
Async {
|
||||
env.ai.stockfishServerReport.fold(
|
||||
_ map { case (play, analyse) ⇒ Ok("%d %d".format(play, analyse)) },
|
||||
_ map { Ok(_) },
|
||||
Future(NotFound)
|
||||
).asPromise
|
||||
}
|
||||
|
|
|
@ -79,15 +79,13 @@ final class Settings(config: Config) {
|
|||
val AiClientMode = getBoolean("ai.client")
|
||||
|
||||
val AiStockfishExecPath = getString("ai.stockfish.exec_path")
|
||||
val AiStockfishHashSize = getInt("ai.stockfish.hash_size")
|
||||
val AiStockfishThreads = getInt("ai.stockfish.threads")
|
||||
|
||||
val AiStockfishPlayUrl = getString("ai.stockfish.play.url")
|
||||
val AiStockfishPlayHashSize = getInt("ai.stockfish.play.hash_size")
|
||||
val AiStockfishPlayMaxMoveTime = getInt("ai.stockfish.play.movetime")
|
||||
val AiStockfishPlayAggressiveness = getInt("ai.stockfish.play.aggressiveness")
|
||||
val AiStockfishPlayCowardice = getInt("ai.stockfish.play.cowardice")
|
||||
|
||||
val AiStockfishAnalyseUrl = getString("ai.stockfish.analyse.url")
|
||||
val AiStockfishAnalyseHashSize = getInt("ai.stockfish.analyse.hash_size")
|
||||
val AiStockfishAnalyseMoveTime = getInt("ai.stockfish.analyse.movetime")
|
||||
|
||||
val MongoHost = getString("mongo.host")
|
||||
|
|
|
@ -114,16 +114,14 @@ ai {
|
|||
client = true
|
||||
stockfish {
|
||||
exec_path = "/usr/bin/stockfish"
|
||||
hash_size = 1024
|
||||
threads = 8
|
||||
play {
|
||||
hash_size = 1024
|
||||
movetime = 400
|
||||
aggressiveness = 150 # 0 - 200
|
||||
cowardice = 50 # 0 - 200
|
||||
url = "http://188.165.194.171:9072/ai/stockfish/play"
|
||||
}
|
||||
analyse {
|
||||
hash_size = 1024
|
||||
movetime = 300
|
||||
movetime = 400
|
||||
url = "http://188.165.194.171:9072/ai/stockfish/analyse"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,8 @@ ai {
|
|||
server = true
|
||||
client = false
|
||||
stockfish {
|
||||
play {
|
||||
hash_size = 4096
|
||||
}
|
||||
hash_size = 8192
|
||||
analyse {
|
||||
hash_size = 4096
|
||||
movetime = 500
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,8 @@ include "application_ai_server"
|
|||
config_name = "development AI server"
|
||||
ai {
|
||||
stockfish {
|
||||
play {
|
||||
hash_size = 64
|
||||
}
|
||||
analyse {
|
||||
hash_size = 64
|
||||
}
|
||||
hash_size = 64
|
||||
threads = 2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ include "application"
|
|||
config_name = "development"
|
||||
ai {
|
||||
server = false
|
||||
client = true
|
||||
client = false
|
||||
stockfish {
|
||||
hash_size = 64
|
||||
threads = 2
|
||||
play {
|
||||
#url = "http://localhost:9072/ai/stockfish/play"
|
||||
hash_size = 64
|
||||
}
|
||||
analyse {
|
||||
hash_size = 64
|
||||
movetime = 200
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue