feat(protocol): add uci protocol handler
This commit is contained in:
1 parent
32dcdcf10f
commit
a6bf7c3d74
1 file changed
+110
@@ -0,0 +1,110 @@
|
||||
package gambit
|
||||
|
||||
final class Uci:
|
||||
private val searcher = new Searcher
|
||||
private var pos = Fen.parse(Fen.startPosition)
|
||||
private val history = scala.collection.mutable.ArrayBuffer[Long](pos.hash)
|
||||
private var worker: Thread = null
|
||||
|
||||
def handle(line: String): List[String] =
|
||||
val tokens = line.trim.split("\\s+").filter(_.nonEmpty).toList
|
||||
tokens match
|
||||
case Nil => Nil
|
||||
case cmd :: rest =>
|
||||
cmd match
|
||||
case "uci" => idLines
|
||||
case "isready" => List("readyok")
|
||||
case "ucinewgame" => resetGame(); Nil
|
||||
case "position" => setPosition(rest); Nil
|
||||
case "go" =>
|
||||
val collected = scala.collection.mutable.ListBuffer[String]()
|
||||
runGo(rest, collected += _)
|
||||
collected.toList
|
||||
case "stop" => searcher.stop(); Nil
|
||||
case "quit" => Nil
|
||||
case _ => Nil
|
||||
|
||||
def run(input: Iterator[String], out: String => Unit): Unit =
|
||||
var quit = false
|
||||
while !quit && input.hasNext do
|
||||
val line = input.next()
|
||||
val tokens = line.trim.split("\\s+").filter(_.nonEmpty).toList
|
||||
tokens match
|
||||
case "quit" :: _ => searcher.stop(); joinWorker(); quit = true
|
||||
case "stop" :: _ => searcher.stop(); joinWorker()
|
||||
case "go" :: rest =>
|
||||
joinWorker()
|
||||
worker = new Thread(() => runGo(rest, out))
|
||||
worker.setDaemon(true)
|
||||
worker.start()
|
||||
case ("position" | "ucinewgame") :: _ =>
|
||||
joinWorker()
|
||||
handle(line).foreach(out)
|
||||
case _ => handle(line).foreach(out)
|
||||
|
||||
private def idLines: List[String] =
|
||||
List("id name gambit", "id author gambit", "uciok")
|
||||
|
||||
private def joinWorker(): Unit =
|
||||
if worker != null then
|
||||
worker.join()
|
||||
worker = null
|
||||
|
||||
private def resetGame(): Unit =
|
||||
searcher.tt.clear()
|
||||
pos = Fen.parse(Fen.startPosition)
|
||||
history.clear()
|
||||
history += pos.hash
|
||||
|
||||
private def setPosition(args: List[String]): Unit =
|
||||
val rest =
|
||||
args match
|
||||
case "startpos" :: tail =>
|
||||
pos = Fen.parse(Fen.startPosition)
|
||||
tail
|
||||
case "fen" :: tail =>
|
||||
val movesIdx = tail.indexOf("moves")
|
||||
val fenTokens = if movesIdx < 0 then tail else tail.take(movesIdx)
|
||||
pos = Fen.parse(fenTokens.mkString(" "))
|
||||
if movesIdx < 0 then Nil else tail.drop(movesIdx)
|
||||
case other => other
|
||||
history.clear()
|
||||
history += pos.hash
|
||||
rest match
|
||||
case "moves" :: moveTokens => applyMoves(moveTokens)
|
||||
case _ => ()
|
||||
|
||||
private def applyMoves(moveTokens: List[String]): Unit =
|
||||
for token <- moveTokens do
|
||||
val legal = new MoveList()
|
||||
MoveGen.generateLegal(pos, legal)
|
||||
var i = 0
|
||||
var found = -1
|
||||
while i < legal.size && found < 0 do
|
||||
if Move.uci(legal(i)) == token then found = legal(i)
|
||||
i += 1
|
||||
if found >= 0 then
|
||||
pos.make(found)
|
||||
history += pos.hash
|
||||
|
||||
private def runGo(args: List[String], emit: String => Unit): Unit =
|
||||
val limits = TimeControl.parse(args)
|
||||
val whiteToMove = pos.sideToMove == Color.White
|
||||
val plan = TimeControl.plan(limits, whiteToMove)
|
||||
searcher.onIteration = (d, score, nodes, ms, pv) => emit(infoLine(d, score, nodes, ms, pv))
|
||||
searcher.setGameHistory(history.toArray, history.size - 1)
|
||||
val result = searcher.search(pos, plan.depth, plan.timeMs)
|
||||
searcher.onIteration = (_, _, _, _, _) => ()
|
||||
val bestMove = if result.bestMove != 0 then Move.uci(result.bestMove) else "0000"
|
||||
emit(s"bestmove $bestMove")
|
||||
|
||||
private def infoLine(depth: Int, score: Int, nodes: Long, ms: Long, pv: List[Int]): String =
|
||||
val scoreStr =
|
||||
if math.abs(score) >= TT.MateBound then
|
||||
val plies = Searcher.MateValue - math.abs(score)
|
||||
val movesToMate = (plies + 1) / 2
|
||||
s"mate ${if score > 0 then movesToMate else -movesToMate}"
|
||||
else s"cp $score"
|
||||
val nps = if ms > 0 then nodes * 1000 / ms else nodes
|
||||
val pvStr = pv.map(Move.uci).mkString(" ")
|
||||
s"info depth $depth score $scoreStr nodes $nodes nps $nps time $ms pv $pvStr"
|
||||
Reference in new issue
Block a user