rewrite Ai interface and implementations

This commit is contained in:
Thibault Duplessis 2013-10-02 02:44:16 +02:00
parent 7e4cd6759c
commit 3087cd05d4
10 changed files with 41 additions and 79 deletions

View file

@ -36,7 +36,7 @@ private[app] final class AiStresser(env: lila.ai.Env, system: ActorSystem) {
if (loop) newGame pipeTo self
}
case Game(moves, it)
ai.getMove(moves take it mkString " ", none, level).effectFold(e {
ai.move(moves take it mkString " ", none, level).effectFold(e {
logwarn("[ai] play: " + e)
newGame pipeTo self
}, { _

View file

@ -12,7 +12,7 @@ object Ai extends LilaController {
def playStockfish = Action.async { req
IfServer {
stockfishServer.play(
stockfishServer.move(
pgn = ~get("pgn", req),
initialFen = get("initialFen", req),
level = getInt("level", req) | 1

View file

@ -1,11 +1,33 @@
package lila.ai
import chess.{ Game, Move }
import chess.format.UciMove
import chess.Move
import lila.analyse.AnalysisMaker
import lila.game.{ Game, Progress, GameRepo, PgnRepo }
trait Ai {
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)]
def play(game: Game, level: Int): Fu[Progress] = withValidSituation(game) {
for {
fen game.variant.exotic ?? { GameRepo initialFen game.id }
pgn PgnRepo get game.id
moveStr move(pgn, fen, level)
result (for {
uciMove UciMove(moveStr) toValid "Wrong bestmove: " + moveStr
result (game.toChess withPgnMoves pgn)(uciMove.orig, uciMove.dest)
} yield result).future
(c, m) = result
(progress, pgn2) = game.update(c, m)
_ (GameRepo save progress) >> PgnRepo.save(game.id, pgn2)
} yield progress
}
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String]
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker]
private def withValidSituation[A](game: Game)(op: Fu[A]): Fu[A] =
if (game.toChess.situation playable true) op
else fufail("[ai stockfish] invalid game situation: " + game.toChess.situation)
}

View file

@ -40,8 +40,8 @@ final class Env(
lazy val ai: Ai = (EngineName, IsClient) match {
case ("stockfish", true) stockfishClient
case ("stockfish", false) stockfishAi
case _ stupidAi
case ("stockfish", false) stockfishServer
case _ throw new Exception(s"Unsupported AI: $EngineName")
}
def isServer = IsServer
@ -58,8 +58,6 @@ final class Env(
}
}), name = ActorName)
private lazy val stockfishAi = new stockfish.Ai(stockfishServer)
lazy val stockfishClient = new stockfish.Client(
dispatcher = system.actorOf(
Props(new stockfish.remote.Dispatcher(
@ -71,7 +69,7 @@ final class Env(
loadRoute = StockfishLoadRoute) _,
scheduler = system.scheduler
)), name = "stockfish-dispatcher"),
fallback = stockfishAi,
fallback = stockfishServer,
config = stockfishConfig)
lazy val stockfishServer = new stockfish.Server(
@ -84,8 +82,6 @@ final class Env(
new stockfish.Queue(stockfishConfig, system)
) withDispatcher StockfishQueueDispatcher, name = StockfishQueueName)
private lazy val stupidAi = new StupidAi
private lazy val client = (EngineName, IsClient) match {
case ("stockfish", true) stockfishClient.some
case _ none

View file

@ -1,16 +0,0 @@
package lila.ai
import chess.{ Game, Move }
private[ai] final class StupidAi extends Ai {
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)] = (for {
destination game.situation.destinations.headOption toValid "Game is finished"
(orig, dests) = destination
dest dests.headOption toValid "No moves from " + orig
newChessGameAndMove game(orig, dest)
} yield newChessGameAndMove).future
def analyse(pgn: String, initialFen: Option[String]) =
throw new RuntimeException("Stupid analysis is not implemented")
}

View file

@ -1,22 +0,0 @@
package lila.ai
package stockfish
import chess.{ Game, Move }
import lila.analyse.AnalysisMaker
final class Ai(server: Server) extends lila.ai.Ai {
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)] =
withValidSituation(game) {
server.play(pgn, initialFen, level) flatMap {
Stockfish.applyMove(game, pgn, _)
}
}
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] =
server.analyse(pgn, initialFen)
private def withValidSituation[A](game: Game)(op: Fu[A]): Fu[A] =
if (game.situation playable true) op
else fufail("[ai stockfish] invalid game situation: " + game.situation)
}

View file

@ -19,18 +19,14 @@ final class Client(
fallback: lila.ai.Ai,
config: Config) extends lila.ai.Ai {
def play(game: Game, pgn: String, initialFen: Option[String], level: Int): Fu[(Game, Move)] = {
getMove(pgn, initialFen, level) flatMap {
Stockfish.applyMove(game, pgn, _)
} recoverWith {
case e: Exception fallback.play(game, pgn, initialFen, level)
}
}
def getMove(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
implicit val timeout = makeTimeout(config.playTimeout)
dispatcher ? Play(pgn, ~initialFen, level) mapTo manifest[String]
} recoverWith {
case e: Exception fallback.move( pgn, initialFen, level)
}
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] = {
implicit val timeout = makeTimeout(config.analyseTimeout)
dispatcher ? Analyse(pgn, ~initialFen) mapTo manifest[String] flatMap { str

View file

@ -15,9 +15,9 @@ import chess.Variant.Chess960
import lila.analyse.AnalysisMaker
import lila.hub.actorApi.ai.GetLoad
private[ai] final class Server(queue: ActorRef, config: Config) {
private[ai] final class Server(queue: ActorRef, config: Config) extends lila.ai.Ai {
def play(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
implicit val timeout = makeTimeout(config.playTimeout)
UciDump(pgn, initialFen, initialFen.isDefined option Chess960).future flatMap { moves
queue ? PlayReq(moves, initialFen map chess960Fen, level) mapTo

View file

@ -1,10 +1,10 @@
package lila.round
import actorApi.round.{ HumanPlay, AiPlay, DrawNo, TakebackNo, PlayResult, Cheat }
import chess.format.Forsyth
import chess.Pos.posAt
import chess.{ Status, Role, Color }
import actorApi.round.{ HumanPlay, AiPlay, DrawNo, TakebackNo, PlayResult, Cheat }
import lila.ai.Ai
import lila.game.{ Game, GameRepo, PgnRepo, Pov, Progress }
import lila.hub.actorApi.map.Tell
@ -51,19 +51,10 @@ private[round] final class Player(
def ai(play: AiPlay)(game: Game): Fu[Events] =
(game.playable && game.player.isAi).fold(
(game.variant.exotic ?? { GameRepo initialFen game.id }) zip
(PgnRepo get game.id) flatMap {
case (fen, pgn)
engine.play(game.toChess, pgn, fen, ~game.aiLevel) flatMap {
case (newChessGame, move) {
val (progress, pgn2) = game.update(newChessGame, move)
(GameRepo save progress) >>
PgnRepo.save(game.id, pgn2) >>-
notifyProgress(progress) >>
moveFinish(progress.game, game.turnColor) map { progress.events ::: _ }
}
}
} addFailureEffect play.onFailure,
engine.play(game, game.aiLevel | 1) flatMap { progress
notifyProgress(progress)
moveFinish(progress.game, game.turnColor) map { progress.events ::: _ }
} addFailureEffect play.onFailure,
fufail("not AI turn")
) logFailureErr "[ai play] game %s turn %d".format(game.id, game.turns)

View file

@ -33,12 +33,7 @@ private[setup] final class Processor(
(GameRepo insertDenormalized game) >>-
(timeline ! game) >>
game.player.isHuman.fold(fuccess(pov), for {
initialFen game.variant.exotic ?? (GameRepo initialFen game.id)
pgnString PgnRepo get game.id
aiResult engine.play(game.toChess, pgnString, initialFen, ~game.aiLevel)
(newChessGame, move) = aiResult
(progress, pgn) = game.update(newChessGame, move)
_ (GameRepo save progress) >> PgnRepo.save(game.id, pgn)
progress engine.play(game, game.aiLevel | 1)
} yield pov withGame progress.game)
}