/// <summary> /// Runs the UCI loop. /// </summary> public void PlayUCI() { // Default to the startpos. curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN, null); curManager = null; gameMoveHistory = new List <GameMoveStat>(); while (true) { string command = InStream.ReadLine(); switch (command) { case null: case "": break; case "uci": Send("id name Ceres"); // TODO: Add executable version Send("id author David Elliott and the Ceres Authors"); // todo output options such as: // option name Logfile type check default false Send("uciok"); break; case "setoption": OutStream.WriteLine("Not processing option " + command); return; case "stop": if (taskSearchCurrentlyExecuting != null && !stopIsPending) { stopIsPending = true; // TODO: cleanup // Possible race condition, curManager is only set in search callback which may not have hit yet // Fix eventually by rewriting SerchManager to have a constructor and then non-static Search method, // os we can get the context in this class directly after construction while (curManager == null) { Thread.Sleep(1); // **** TEMPORARY *** } curManager.ExternalStopRequested = true; if (taskSearchCurrentlyExecuting != null) { taskSearchCurrentlyExecuting.Wait(); if (!debug && taskSearchCurrentlyExecuting != null) { taskSearchCurrentlyExecuting.Result?.Manager?.Dispose(); } taskSearchCurrentlyExecuting = null; } } curManager = null; stopIsPending = false; break; case "ponderhit": throw new NotImplementedException("Ceres does not yet support UCI ponder mode."); return; case "xboard": // ignore break; case "debug on": debug = true; break; case "debug off": debug = false; break; case "isready": InitializeEngineIfNeeded(); Send("readyok"); break; case "ucinewgame": gameMoveHistory = new List <GameMoveStat>(); CeresEngine?.ResetGame(); break; case "quit": if (curManager != null) { curManager.ExternalStopRequested = true; taskSearchCurrentlyExecuting?.Wait(); } if (CeresEngine != null) { CeresEngine.Dispose(); } System.Environment.Exit(0); break; case string c when c.StartsWith("go"): if (taskSearchCurrentlyExecuting != null) { throw new Exception("Received go command when another search was running and not stopped first"); } InitializeEngineIfNeeded(); taskSearchCurrentlyExecuting = ProcessGo(command); break; case string c when c.StartsWith("position"): try { ProcessPosition(c); } catch (Exception e) { Send($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString()); } break; // Proprietary commands case "lc0-config": if (curManager != null) { string netID = EvaluatorDef.Nets[0].Net.NetworkID; INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID); (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, curContext.EvaluatorDef, netDef, false, false); Console.WriteLine("info string " + exe + " " + options); } else { Console.WriteLine("info string No search manager created"); } break; case "dump-params": if (curManager != null) { curManager.DumpParams(); } else { Console.WriteLine("info string No search manager created"); } break; case "dump-processor": HardwareManager.DumpProcessorInfo(); break; case "dump-time": if (curManager != null) { curManager.DumpTimeInfo(); } else { Console.WriteLine("info string No search manager created"); } break; case "dump-store": if (curManager != null) { using (new SearchContextExecutionBlock(curContext)) curManager.Context.Tree.Store.Dump(true); } else { Console.WriteLine("info string No search manager created"); } break; case "dump-move-stats": if (curManager != null) { using (new SearchContextExecutionBlock(curContext)) curManager.Context.Root.Dump(1, 1, prefixString: "info string "); } else { Console.WriteLine("info string No search manager created"); } break; case "dump-pv": DumpPV(false); break; case "dump-pv-detail": DumpPV(true); break; case "dump-nvidia": NVML.DumpInfo(); break; case "waitdone": // proprietary verb taskSearchCurrentlyExecuting?.Wait(); break; default: Console.WriteLine($"error Unknown command: {command}"); break; } } }
/// <summary> /// Runs the UCI loop. /// </summary> public void PlayUCI() { // Default to the startpos. curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN); gameMoveHistory = new List <GameMoveStat>(); while (true) { string command = InStream.ReadLine(); if (uciLogWriter != null) { LogWriteLine("IN:", command); } switch (command) { case null: case "": break; case "uci": UCIWriteLine($"id name Ceres {CeresVersion.VersionString}"); UCIWriteLine("id author David Elliott and the Ceres Authors"); UCIWriteLine(SetOptionUCIDescriptions); UCIWriteLine("uciok"); break; case string c when c.StartsWith("setoption"): ProcessSetOption(command); break; case "stop": if (taskSearchCurrentlyExecuting != null && !stopIsPending) { stopIsPending = true; // Avoid race condition by mkaing sure the search is already created. while (CeresEngine.Search?.Manager == null) { Thread.Sleep(20); } CeresEngine.Search.Manager.ExternalStopRequested = true; if (taskSearchCurrentlyExecuting != null) { taskSearchCurrentlyExecuting.Wait(); // if (!debug && taskSearchCurrentlyExecuting != null) taskSearchCurrentlyExecuting.Result?.Search?.Manager?.Dispose(); taskSearchCurrentlyExecuting = null; } } stopIsPending = false; break; case "ponderhit": throw new NotImplementedException("Ceres does not yet support UCI ponder mode."); return; case "xboard": // ignore break; case "debug on": debug = true; break; case "debug off": debug = false; break; case "isready": InitializeEngineIfNeeded(); UCIWriteLine("readyok"); break; case "ucinewgame": gameMoveHistory = new List <GameMoveStat>(); CeresEngine?.ResetGame(); break; case "quit": if (taskSearchCurrentlyExecuting != null) { CeresEngine.Search.Manager.ExternalStopRequested = true; taskSearchCurrentlyExecuting?.Wait(); } if (CeresEngine != null) { CeresEngine.Dispose(); } System.Environment.Exit(0); break; case string c when c.StartsWith("go"): // Possibly another search is already executing. // The UCI specification is unclear about what to do in this situation. // Some engines seem to enqueue these for later execution (e.g. Stockfish) // whereas others (e.g. Python chess) report this as an error condition. // Currently Ceres waits only a short while for any possible pending search // to finish (e.g. to avoid a race condition if it is in the process of being shutdown) // and aborts with an error if search is still in progress. // It is not viable to wait indefinitely, since (among other reasons) // the engine needs to monitor for stop commands. const int MAX_MILLISECONDS_WAIT = 500; taskSearchCurrentlyExecuting?.Wait(MAX_MILLISECONDS_WAIT); if (taskSearchCurrentlyExecuting != null && !taskSearchCurrentlyExecuting.IsCompleted) { throw new Exception("Received go command when another search was running and not stopped first."); } InitializeEngineIfNeeded(); taskSearchCurrentlyExecuting = ProcessGo(command); break; case string c when c.StartsWith("position"): try { ProcessPosition(c); } catch (Exception e) { UCIWriteLine($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString()); } break; // Proprietary commands case "lc0-config": if (CeresEngine?.Search != null) { string netID = EvaluatorDef.Nets[0].Net.NetworkID; INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID); (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, CeresEngine.Search.Manager.Context.EvaluatorDef, netDef, false, false); UCIWriteLine("info string " + exe + " " + options); } else { UCIWriteLine("info string No search manager created"); } break; case "dump-params": if (CeresEngine?.Search != null) { CeresEngine?.Search.Manager.DumpParams(); } else { UCIWriteLine("info string No search manager created"); } break; case "dump-processor": HardwareManager.DumpProcessorInfo(); break; case "dump-time": if (CeresEngine?.Search != null) { CeresEngine?.Search.Manager.DumpTimeInfo(OutStream); } else { UCIWriteLine("info string No search manager created"); } break; case "dump-store": if (CeresEngine?.Search != null) { using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context)) CeresEngine.Search.Manager.Context.Tree.Store.Dump(true); } else { UCIWriteLine("info string No search manager created"); } break; case "dump-move-stats": if (CeresEngine?.Search != null) { OutputVerboseMoveStats(CeresEngine.Search.SearchRootNode); } else { UCIWriteLine("info string No search manager created"); } break; case "dump-pv": DumpPV(false); break; case "dump-pv-detail": DumpPV(true); break; case "dump-nvidia": NVML.DumpInfo(); break; case "show-tree-plot": if (CeresEngine?.Search != null) { using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context)) { TreePlot.Show(CeresEngine.Search.Manager.Context.Root.Ref); } } else { UCIWriteLine("info string No search manager created"); } break; case string c when c.StartsWith("save-tree-plot"): if (CeresEngine?.Search != null) { string[] parts = command.Split(" "); if (parts.Length == 2) { string fileName = parts[1]; using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context)) { TreePlot.Save(CeresEngine.Search.Manager.Context.Root.Ref, fileName); } } else if (parts.Length == 1) { UCIWriteLine("Filename was not provided"); } else { UCIWriteLine("Filename cannot contain spaces"); } } else { UCIWriteLine("info string No search manager created"); } break; case "waitdone": // proprietary verb used for test driver taskSearchCurrentlyExecuting?.Wait(); break; default: UCIWriteLine($"error Unknown command: {command}"); break; } } }