static void Search( State root, MtdfIds searcher, GoSettings settings, Program program, CancellationToken ct) { ISearchInfo info; { using var _ = searcher.IterationCompleted.Subscribe(icInfo => { // todo: even if we have cached tt info, we should be outputting the full pv // todo: output other info fields (see stockfish) var output = $"info depth {icInfo.Depth} time {(int)icInfo.Elapsed.TotalMilliseconds} nodes {icInfo.NodesSearched} score cp {icInfo.Score} pv {string.Join(' ', icInfo.Pv)}"; WriteLine(output); }); info = searcher.Search(root, ct); } // if we finished but we're in ponder or infinite mode, wait until we receive "ponderhit" or "stop" while (!ct.IsCancellationRequested && (settings.Infinite || program._ponder)) { Thread.Sleep(500); } // output the best move. if the pv contains more than 1 move (eg. there's not a mate in 1 and we searched more than depth 1), // output that too as the next move we expect the user to play. Debug.Assert(!info.Pv.IsEmpty); var output = $"bestmove {info.Pv[0]}"; if (info.Pv.Length > 1) { output += $" ponder {info.Pv[1]}"; } WriteLine(output); }
void Go(Stack <string> tokens) { if (_root == null) { Error.WriteLine("Position not set, use the 'position' command to set it"); return; } var settings = new GoSettings(); while (tokens.TryPop(out var paramName)) { switch (paramName) { case "searchmoves": // needs to be the last command on the line var searchMoves = new List <Move>(); while (tokens.TryPop(out var move)) { searchMoves.Add(Move.ParseLong(move)); } settings.SearchMoves = searchMoves.ToImmutableArray(); break; case "ponder": _ponder = true; break; case "depth": settings.Depth = int.Parse(tokens.Pop()); break; case "nodes": settings.Nodes = int.Parse(tokens.Pop()); break; case "mate": // todo settings.Mate = int.Parse(tokens.Pop()); break; case "movetime": // todo settings.MoveTime = TimeSpan.FromMilliseconds(int.Parse(tokens.Pop())); break; case "infinite": settings.Infinite = true; break; } } // go ahead with the search var searcher = new MtdfIds(); searcher.Depth = settings.Depth ?? ((settings.Infinite || settings.Nodes != null) ? int.MaxValue : DefaultSearchDepth); searcher.MaxNodes = settings.Nodes ?? int.MaxValue; int ttCapacity = _options.Get <int>("Hash") * EntriesPerMb; if (_tt == null || _tt.Capacity != ttCapacity) { _tt = searcher.MakeTt(ttCapacity); } searcher.Tt = _tt; _ponder = false; var cts = new CancellationTokenSource(); // define the task and either run it straight away, or add it to the queue if there's one in progress var rootCopy = _root; // if a new search is requested before this one is started, we don't want to pick up on a new position var searchTask = new Task(() => { try { _cts = cts; Search(rootCopy, searcher, settings, this, cts.Token); } // exceptions don't propagate to the main thread unless they are explicitly handled like this catch (Exception e) { Error.WriteLine(e); } finally { cts.Dispose(); _cts = null; // run the next task if one is queued if (_searchQueue.TryDequeue(out var nextTask)) { nextTask.Start(); } else { _searchInProgress = false; } } }); if (!_searchInProgress) { Debug.Assert(_searchQueue.IsEmpty); _searchInProgress = true; searchTask.Start(); } else { _searchQueue.Enqueue(searchTask); } }