more progress on AI rewrite
This commit is contained in:
parent
602ca57da0
commit
9db79ad555
|
@ -12,26 +12,30 @@ private[app] final class AiStresser(env: lila.ai.Env, system: ActorSystem) {
|
|||
|
||||
def apply {
|
||||
|
||||
(1 to 1024) foreach { i ⇒
|
||||
system.scheduler.scheduleOnce((i*97) millis) {
|
||||
play(i % 8 + 1)
|
||||
(1 to 64) foreach { i ⇒
|
||||
system.scheduler.scheduleOnce((i * 97) millis) {
|
||||
play(i % 8 + 1, true)
|
||||
}
|
||||
}
|
||||
(1 to 1) foreach { i ⇒
|
||||
system.scheduler.scheduleOnce((i * 131) millis) {
|
||||
analyse(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def play(level: Int) = system.actorOf(Props(new Actor {
|
||||
|
||||
def newGame = lila.game.PgnRepo getOneRandom 30000 map { pgn ⇒
|
||||
Game((~pgn).split(' ').toList, 1)
|
||||
}
|
||||
private def play(level: Int, loop: Boolean) = system.actorOf(Props(new Actor {
|
||||
|
||||
override def preStart {
|
||||
newGame pipeTo self
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case Game(moves, it) if it >= moves.size ⇒ newGame pipeTo self
|
||||
case Game(moves, it) if it >= moves.size ⇒ {
|
||||
loginfo("play complete")
|
||||
if (loop) newGame pipeTo self
|
||||
}
|
||||
case Game(moves, it) ⇒
|
||||
ai.play(moves take it mkString " ", none, level).effectFold(e ⇒ {
|
||||
logwarn("[ai] server play: " + e)
|
||||
|
@ -44,6 +48,34 @@ private[app] final class AiStresser(env: lila.ai.Env, system: ActorSystem) {
|
|||
}
|
||||
}))
|
||||
|
||||
private def analyse(loop: Boolean) = system.actorOf(Props(new Actor {
|
||||
|
||||
override def preStart {
|
||||
newGame pipeTo self
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case Game(moves, _) ⇒
|
||||
ai.analyse(moves mkString " ", none).effectFold(e ⇒ {
|
||||
logwarn("[ai] server analyse: " + e)
|
||||
if (loop) newGame pipeTo self
|
||||
}, { _ ⇒
|
||||
loginfo("analyse complete")
|
||||
if (loop) newGame pipeTo self
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
// private def newGame = fuccess {
|
||||
// val pgn = "e3 Nc6 Nf3 Nf6 Nc3 d6 d3 Be6 d4 Rb8 b3 Rg8 g3 g5 Nxg5 Rxg5 f4 Bg4 fxg5 Bxd1 Kxd1 Ng4"
|
||||
// // val pgn = "e3 Nc6 Nf3 Nf6"
|
||||
// Game(pgn.split(' ').toList, 1)
|
||||
// }
|
||||
|
||||
private def newGame = lila.game.PgnRepo getOneRandom 30000 map { pgn ⇒
|
||||
Game((~pgn).split(' ').toList, 1)
|
||||
}
|
||||
|
||||
private def randomize(d: FiniteDuration, ratio: Float = 0.1f): FiniteDuration =
|
||||
approximatly(ratio)(d.toMillis) millis
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package lila.ai
|
||||
|
||||
import chess.{ Game, Move }
|
||||
import lila.analyse.Analysis
|
||||
import lila.analyse.AnalysisMaker
|
||||
|
||||
trait Ai {
|
||||
|
||||
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)]
|
||||
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[String ⇒ Analysis]
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker]
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ final class Env(
|
|||
val IsClient = config getBoolean "client"
|
||||
val StockfishPlayUrl = config getString "stockfish.play.url"
|
||||
val StockfishAnalyseUrl = config getString "stockfish.analyse.url"
|
||||
val StockfishQueueName = config getString "stockfish.queue.name"
|
||||
val StockfishQueueDispatcher = config getString "stockfish.queue.dispatcher"
|
||||
val ActorName = config getString "actor.name"
|
||||
}
|
||||
import settings._
|
||||
|
@ -28,7 +30,7 @@ final class Env(
|
|||
playMaxMoveTime = config duration "stockfish.play.movetime",
|
||||
analyseMoveTime = config duration "stockfish.analyse.movetime",
|
||||
playTimeout = config duration "stockfish.play.timeout",
|
||||
analyseTimeout = config duration "stockfish.play.timeout",
|
||||
analyseTimeout = config duration "stockfish.analyse.timeout",
|
||||
debug = config getBoolean "stockfish.debug")
|
||||
|
||||
val ai: () ⇒ Ai = () ⇒ (EngineName, IsClient) match {
|
||||
|
@ -68,7 +70,16 @@ final class Env(
|
|||
playUrl = StockfishPlayUrl,
|
||||
analyseUrl = StockfishAnalyseUrl)
|
||||
|
||||
lazy val stockfishServer = new stockfish.Server(stockfishConfig)
|
||||
lazy val stockfishServer = new stockfish.Server(
|
||||
queue = stockfishQueue,
|
||||
config = stockfishConfig)
|
||||
|
||||
// preload stockfish
|
||||
if (!IsClient && EngineName == "stockfish") stockfishServer
|
||||
|
||||
private lazy val stockfishQueue = system.actorOf(Props(
|
||||
new stockfish.Queue(stockfishConfig)
|
||||
) withDispatcher StockfishQueueDispatcher, name = StockfishQueueName)
|
||||
|
||||
private lazy val stupidAi = new StupidAi
|
||||
|
||||
|
@ -76,11 +87,6 @@ final class Env(
|
|||
case ("stockfish", true) ⇒ stockfishClient.some
|
||||
case _ ⇒ none
|
||||
}
|
||||
|
||||
private lazy val server = (EngineName, IsServer) match {
|
||||
case ("stockfish", true) ⇒ stockfishServer.some
|
||||
case _ ⇒ none
|
||||
}
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
|
|
@ -6,11 +6,12 @@ import model._
|
|||
import model.analyse._
|
||||
|
||||
import actorApi._
|
||||
import lila.analyse.Analysis
|
||||
|
||||
final class ActorFSM(
|
||||
private[stockfish] final class ActorFSM(
|
||||
processBuilder: Process.Builder,
|
||||
config: Config)
|
||||
extends Actor with AkkaFSM[State, Option[(Req, ActorRef)]] {
|
||||
extends Actor with AkkaFSM[State, Option[Job]] {
|
||||
|
||||
private val process = processBuilder(
|
||||
out ⇒ self ! Out(out),
|
||||
|
@ -20,22 +21,23 @@ final class ActorFSM(
|
|||
startWith(Starting, none)
|
||||
|
||||
when(Starting) {
|
||||
case Event(Out(t), data) if t startsWith "Stockfish" ⇒ {
|
||||
case Event(Out(t), _) if t startsWith "Stockfish" ⇒ {
|
||||
process write "uci"
|
||||
stay
|
||||
}
|
||||
case Event(Out("uciok"), data) ⇒ {
|
||||
case Event(Out("uciok"), job) ⇒ {
|
||||
config.init foreach process.write
|
||||
data.fold(goto(Idle))(start)
|
||||
loginfo("[ai] stockfish is ready")
|
||||
job.fold(goto(Idle))(start)
|
||||
}
|
||||
case Event(req: Req, none) ⇒ stay using (req, sender).some
|
||||
case Event(req: Req, none) ⇒ stay using Job(req, sender, Nil).some
|
||||
}
|
||||
when(Idle) {
|
||||
case Event(Out(t), _) ⇒ { logwarn(t); stay }
|
||||
case Event(req: Req, _) ⇒ start(req, sender)
|
||||
case Event(req: Req, _) ⇒ start(Job(req, sender, Nil))
|
||||
}
|
||||
when(IsReady) {
|
||||
case Event(Out("readyok"), Some((req, _))) ⇒ {
|
||||
case Event(Out("readyok"), Some(Job(req, _, _))) ⇒ {
|
||||
val lines = config go req
|
||||
lines.lastOption foreach { line ⇒
|
||||
println(req.analyse.fold("A", "P") + line.replace("go movetime", ""))
|
||||
|
@ -45,13 +47,10 @@ final class ActorFSM(
|
|||
}
|
||||
}
|
||||
when(Running) {
|
||||
// TODO accumulate output for analysis parsing
|
||||
// case Event(Out(t), Some(req)) if t startsWith "info depth" ⇒
|
||||
// stay using (doing map (_.right map (_ buffer t)))
|
||||
case Event(Out(t), Some((req, sender))) if t startsWith "bestmove" ⇒ {
|
||||
sender ! req.analyse.fold(
|
||||
Status.Failure(new Exception("Not implemented")),
|
||||
BestMove(t.split(' ') lift 1))
|
||||
case Event(Out(t), Some(job)) if t startsWith "info depth" ⇒
|
||||
stay using (job + t).some
|
||||
case Event(Out(t), Some(job)) if t startsWith "bestmove" ⇒ {
|
||||
job.sender ! (job complete t)
|
||||
goto(Idle) using none
|
||||
}
|
||||
}
|
||||
|
@ -63,11 +62,11 @@ final class ActorFSM(
|
|||
case Event(Out(t), _) ⇒ stay
|
||||
}
|
||||
|
||||
def start(data: (Req, ActorRef)) = data match {
|
||||
case (req, sender) ⇒ {
|
||||
def start(job: Job) = job match {
|
||||
case Job(req, sender, _) ⇒ {
|
||||
config prepare req foreach process.write
|
||||
process write "isready"
|
||||
goto(IsReady) using (req, sender).some
|
||||
goto(IsReady) using Job(req, sender, Nil).some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package lila.ai
|
|||
package stockfish
|
||||
|
||||
import chess.{ Game, Move }
|
||||
import lila.analyse.Analysis
|
||||
import lila.analyse.AnalysisMaker
|
||||
|
||||
final class Ai(server: Server) extends lila.ai.Ai {
|
||||
|
||||
|
@ -15,7 +15,7 @@ final class Ai(server: Server) extends lila.ai.Ai {
|
|||
}
|
||||
}
|
||||
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[String ⇒ Analysis] =
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] =
|
||||
server.analyse(pgn, initialFen)
|
||||
|
||||
private def withValidSituation[A](game: Game)(op: ⇒ Fu[A]): Fu[A] =
|
||||
|
|
|
@ -9,7 +9,7 @@ import play.api.Play.current
|
|||
|
||||
import chess.format.UciMove
|
||||
import chess.{ Game, Move }
|
||||
import lila.analyse.{ Analysis, AnalysisMaker }
|
||||
import lila.analyse.AnalysisMaker
|
||||
|
||||
final class Client(
|
||||
val playUrl: String,
|
||||
|
@ -18,7 +18,7 @@ final class Client(
|
|||
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)] =
|
||||
fetchMove(pgn, ~initialFen, level) flatMap { Stockfish.applyMove(game, pgn, _) }
|
||||
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[String ⇒ Analysis] =
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] =
|
||||
fetchAnalyse(pgn, ~initialFen) flatMap { str ⇒
|
||||
(AnalysisMaker(str, true) toValid "Can't read analysis results").future
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package lila.ai
|
||||
package stockfish
|
||||
|
||||
import model._
|
||||
import actorApi._
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
import model._
|
||||
|
||||
import actorApi._
|
||||
|
||||
private[ai] case class Config(
|
||||
execPath: String,
|
||||
hashSize: Int,
|
||||
|
@ -30,8 +32,8 @@ private[ai] case class Config(
|
|||
4 -> 4,
|
||||
5 -> 6,
|
||||
6 -> 8,
|
||||
7 -> 10
|
||||
// 8 -> inf
|
||||
7 -> 10,
|
||||
8 -> 12
|
||||
) get levelBox(level)
|
||||
|
||||
def init = List(
|
||||
|
@ -39,28 +41,27 @@ private[ai] case class Config(
|
|||
setoption("Threads", nbThreads),
|
||||
setoption("Ponder", false))
|
||||
|
||||
def prepare(req: Req) = req.analyse.fold(
|
||||
List(
|
||||
def prepare(req: Req) = req match {
|
||||
case r: PlayReq ⇒ List(
|
||||
setoption("Uci_AnalyseMode", false),
|
||||
setoption("Skill Level", skill(r.level)),
|
||||
setoption("UCI_Chess960", r.chess960),
|
||||
setoption("OwnBook", ownBook(r.level)))
|
||||
case r: AnalReq ⇒ List(
|
||||
setoption("Uci_AnalyseMode", true),
|
||||
setoption("Skill Level", skillMax),
|
||||
setoption("UCI_Chess960", req.chess960),
|
||||
setoption("OwnBook", true)),
|
||||
List(
|
||||
setoption("Uci_AnalyseMode", false),
|
||||
setoption("Skill Level", skill(req.level)),
|
||||
setoption("UCI_Chess960", req.chess960),
|
||||
setoption("OwnBook", ownBook(req.level))))
|
||||
setoption("UCI_Chess960", r.chess960),
|
||||
setoption("OwnBook", true))
|
||||
}
|
||||
|
||||
def go(req: Req): List[String] = req.analyse.fold(
|
||||
List(
|
||||
position(req.fen, req.moves),
|
||||
"go movetime %d".format(analyseMoveTime.toMillis)),
|
||||
List(
|
||||
position(req.fen, req.moves),
|
||||
"go movetime %d%s".format(
|
||||
moveTime(req.level),
|
||||
~depth(req.level).map(" depth " + _)
|
||||
)))
|
||||
def go(req: Req): List[String] = req match {
|
||||
case r: PlayReq ⇒ List(
|
||||
position(r.fen, r.moves),
|
||||
"go movetime %d%s".format(moveTime(r.level), ~depth(r.level).map(" depth " + _)))
|
||||
case r: AnalReq ⇒ List(
|
||||
position(r.fen, r.moves),
|
||||
"go movetime %d".format(analyseMoveTime.toMillis))
|
||||
}
|
||||
|
||||
private def position(fen: Option[String], moves: String) =
|
||||
"position %s moves %s".format(fen.fold("startpos")("fen " + _), moves)
|
||||
|
|
18
modules/ai/src/main/stockfish/MailBox.scala
Normal file
18
modules/ai/src/main/stockfish/MailBox.scala
Normal file
|
@ -0,0 +1,18 @@
|
|||
package lila.ai
|
||||
package stockfish
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.dispatch.PriorityGenerator
|
||||
import akka.dispatch.UnboundedPriorityMailbox
|
||||
import com.typesafe.config.{ Config ⇒ TypesafeConfig }
|
||||
|
||||
import actorApi._
|
||||
|
||||
// We inherit, in this case, from UnboundedPriorityMailbox
|
||||
// and seed it with the priority generator
|
||||
final class MailBox(settings: ActorSystem.Settings, config: TypesafeConfig)
|
||||
extends UnboundedPriorityMailbox(PriorityGenerator {
|
||||
case _: PlayReq ⇒ 0
|
||||
case _: AnalReq ⇒ 1
|
||||
case _ ⇒ 3
|
||||
})
|
|
@ -5,7 +5,7 @@ import java.io.OutputStream
|
|||
import scala.io.Source.fromInputStream
|
||||
import scala.sys.process.{ Process ⇒ SProcess, ProcessBuilder, ProcessIO }
|
||||
|
||||
private [stockfish] final class Process(
|
||||
private[stockfish] final class Process(
|
||||
builder: ProcessBuilder,
|
||||
name: String,
|
||||
out: String ⇒ Unit,
|
||||
|
|
|
@ -7,21 +7,45 @@ import akka.actor._
|
|||
import akka.pattern.{ ask, pipe }
|
||||
|
||||
import actorApi._
|
||||
import lila.analyse.{ AnalysisMaker, Info }
|
||||
|
||||
private[stockfish] final class Queue(config: Config) extends Actor {
|
||||
private[ai] final class Queue(config: Config) extends Actor {
|
||||
|
||||
private val playTimeout = makeTimeout(config.playMaxMoveTime + 100.millis)
|
||||
private val process = Process(config.execPath, "StockFish") _
|
||||
private val process = Process(config.execPath, "stockfish") _
|
||||
private val actor = context.actorOf(Props(new ActorFSM(process, config)))
|
||||
private var actorReady = false
|
||||
|
||||
def receive = {
|
||||
|
||||
case req: Req ⇒ {
|
||||
implicit def timeout = playTimeout
|
||||
actor ? req map {
|
||||
case bestMove: BestMove ⇒ sender ! ~bestMove.move
|
||||
}
|
||||
} await playTimeout
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
} pipeTo sender
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,43 +12,33 @@ import play.api.Play.current
|
|||
import actorApi._
|
||||
import chess.format.Forsyth
|
||||
import chess.format.UciDump
|
||||
import chess.Rook
|
||||
import lila.analyse.Analysis
|
||||
import lila.analyse.AnalysisMaker
|
||||
|
||||
final class Server(config: Config) {
|
||||
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 ⇒ actor ? Req(moves, initialFen map chess960Fen, level, false) mapTo manifest[String]
|
||||
moves ⇒ queue ? PlayReq(moves, initialFen map chess960Fen, level) mapTo manifest[String]
|
||||
)
|
||||
}
|
||||
|
||||
def analyse(pgn: String, initialFen: Option[String]): Fu[String ⇒ Analysis] =
|
||||
fufail("not implemented")
|
||||
// UciDump(pgn, initialFen).fold(
|
||||
// err ⇒ fufail(err),
|
||||
// moves ⇒ {
|
||||
// val analyse = model.analyse.Task.Builder(moves, initialFen map chess960Fen)
|
||||
// implicit val timeout = makeTimeout(config.analyseTimeout)
|
||||
// (actor ? analyse).mapTo[String ⇒ Analysis] ~ { _ onFailure reboot }
|
||||
// }
|
||||
// )
|
||||
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]
|
||||
)
|
||||
}
|
||||
|
||||
private def chess960Fen(fen: String) = (Forsyth << fen).fold(fen) { situation ⇒
|
||||
fen.replace("KQkq", situation.board.pieces.toList filter {
|
||||
case (_, piece) ⇒ piece is Rook
|
||||
case (_, piece) ⇒ piece is chess.Rook
|
||||
} sortBy {
|
||||
case (pos, _) ⇒ (pos.y, pos.x)
|
||||
} map {
|
||||
case (pos, piece) ⇒ piece.color.fold(pos.file.toUpperCase, pos.file)
|
||||
} mkString "")
|
||||
}
|
||||
|
||||
private val reboot: PartialFunction[Throwable, Unit] = {
|
||||
case e: AskTimeoutException ⇒ actor ! model.RebootException
|
||||
}
|
||||
|
||||
private lazy val actor = system.actorOf(Props(new Queue(config)))
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package lila.ai
|
||||
package stockfish
|
||||
|
||||
import actorApi.BestMove
|
||||
|
||||
import chess.Game
|
||||
import chess.format.UciMove
|
||||
|
||||
object Stockfish {
|
||||
|
||||
def applyMove(game: Game, pgn: String, move: String): Fu[(Game, chess.Move)] = (for {
|
||||
bestMove ← UciMove(~BestMove(move.some filter ("" !=)).move) toValid "Wrong bestmove " + move
|
||||
bestMove ← UciMove(~(move.some filter (_.nonEmpty))) toValid "Wrong bestmove " + move
|
||||
result ← (game withPgnMoves pgn)(bestMove.orig, bestMove.dest)
|
||||
} yield result).future
|
||||
}
|
||||
|
|
|
@ -1,15 +1,52 @@
|
|||
package lila.ai.stockfish
|
||||
package lila.ai
|
||||
package stockfish
|
||||
package actorApi
|
||||
|
||||
import akka.actor.ActorRef
|
||||
|
||||
case class Req(
|
||||
moves: String,
|
||||
fen: Option[String],
|
||||
level: Int,
|
||||
analyse: Boolean) {
|
||||
sealed trait Req {
|
||||
def moves: String
|
||||
def fen: Option[String]
|
||||
def analyse: Boolean
|
||||
|
||||
def chess960 = fen.isDefined
|
||||
def moveList = moves.split(' ').toList
|
||||
}
|
||||
|
||||
case class PlayReq(
|
||||
moves: String,
|
||||
fen: Option[String],
|
||||
level: Int) extends Req {
|
||||
|
||||
def analyse = false
|
||||
}
|
||||
|
||||
case class AnalReq(
|
||||
moves: 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])
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
case class BestMove(move: Option[String])
|
||||
|
|
|
@ -37,11 +37,6 @@ object Analysis {
|
|||
|
||||
val separator = " "
|
||||
|
||||
def make(str: String, done: Boolean): Option[String ⇒ Analysis] =
|
||||
Analysis.decodeInfos(str) map { infos ⇒
|
||||
(id: String) ⇒ new Analysis(id, infos, done, none)
|
||||
}
|
||||
|
||||
def decodeInfos(enc: String): Option[List[Info]] =
|
||||
(enc.split(separator).toList.zipWithIndex map {
|
||||
case (info, index) ⇒ Info.decode(index + 1, info)
|
||||
|
@ -67,12 +62,14 @@ object Analysis {
|
|||
)
|
||||
}
|
||||
|
||||
// NICETOHAVE
|
||||
// this belongs to the Analysis object
|
||||
// but was moved here because of scala 2.10.1 compiler bug
|
||||
case class AnalysisMaker(infos: List[Info], done: Boolean, fail: Option[String]) {
|
||||
|
||||
def apply(id: String) = Analysis(id, infos, done, fail)
|
||||
}
|
||||
object AnalysisMaker {
|
||||
def apply(str: String, done: Boolean): Option[String ⇒ Analysis] =
|
||||
Analysis.make(str, done)
|
||||
|
||||
def apply(str: String, done: Boolean): Option[AnalysisMaker] =
|
||||
Analysis.decodeInfos(str) map { AnalysisMaker(_, done, none) }
|
||||
}
|
||||
|
||||
final class AnalysisBuilder(infos: List[Info]) {
|
||||
|
@ -81,7 +78,7 @@ final class AnalysisBuilder(infos: List[Info]) {
|
|||
|
||||
def +(info: Int ⇒ Info) = new AnalysisBuilder(info(infos.size + 1) :: infos)
|
||||
|
||||
def done: String ⇒ Analysis = id ⇒ new Analysis(id, infos.reverse.zipWithIndex map {
|
||||
def done: AnalysisMaker = AnalysisMaker(infos.reverse.zipWithIndex map {
|
||||
case (info, turn) ⇒ (turn % 2 == 0).fold(
|
||||
info,
|
||||
info.copy(score = info.score map (_.negate))
|
||||
|
@ -99,7 +96,7 @@ private[analyse] case class RawAnalysis(
|
|||
case (true, "") ⇒ new Analysis(id, Nil, false, fail orElse "No move infos".some).some
|
||||
case (true, en) ⇒ Analysis.decodeInfos(en) map { infos ⇒
|
||||
new Analysis(id, infos, done, none)
|
||||
}
|
||||
}
|
||||
case (false, _) ⇒ new Analysis(id, Nil, false, fail).some
|
||||
}
|
||||
}
|
||||
|
|
16
todo
16
todo
|
@ -61,6 +61,22 @@ account closed accounts in team counts
|
|||
IE10 no sound toggle http://en.lichess.org/forum/lichess-feedback/notification-of-game-creation#3
|
||||
filter current games for watching http://en.lichess.org/forum/lichess-feedback/viewing-current-games-suggestion#2
|
||||
stockfish hangs http://en.lichess.org/inbox/wtd62jrd#bottom
|
||||
IP troll
|
||||
|
||||
[Event "Casual game"]
|
||||
[Site "http://lichess.org/paf5gpiq"]
|
||||
[Date "2013-06-06"]
|
||||
[White "turf"]
|
||||
[Black "Anonymous"]
|
||||
[Result "1-0"]
|
||||
[WhiteElo "1226"]
|
||||
[BlackElo "?"]
|
||||
[PlyCount "45"]
|
||||
[Variant "Chess960"]
|
||||
[FEN "nrbbnqkr/pppppppp/8/8/8/8/PPPPPPPP/NRBBNQKR w KQkq - 0 1"]
|
||||
[SetUp "1"]
|
||||
|
||||
1. e4 e5 2. Nb3 c5 3. Nf3 d6 4. d3 Be6 5. Qe1 Bxb3 6. axb3 b5 7. Ra1 b4 8. Rxa7 Bb6 9. Ra4 Nac7 10. c3 Ra8 11. Rxa8 Nxa8 12. cxb4 cxb4 13. Qxb4 Nec7 14. Be3 Bxe3 15. fxe3 h6 16. O-O d5 17. Qxf8+ Kxf8 18. Nxe5 dxe4 19. dxe4 f6 20. Ng6+ Kg8 21. Nxh8 Kxh8 22. Bf3 Nb6 23. Rc1 1-0
|
||||
|
||||
---
|
||||
|
||||
|
|
Loading…
Reference in a new issue