Files
gambit/Uci.scala
T

111 lines
4.0 KiB
Scala

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"