// ThreadsManager::start_searching() wakes up the main thread sleeping in // main_loop() so to start a new search, then returns immediately. internal static void start_searching(Position pos, LimitsType limits, List <Move> searchMoves) { wait_for_search_finished(); Search.SearchTime.Reset(); Search.SearchTime.Start(); // As early as possible Search.SignalsStopOnPonderhit = Search.SignalsFirstRootMove = false; Search.SignalsStop = Search.SignalsFailedLowAtRoot = false; Search.RootPosition.copy(pos); Search.Limits = limits; Search.RootMoves.Clear(); MList mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); for (int i = 0; i < mlist.pos; i++) { Move move = mlist.moves[i].move; if ((searchMoves.Count == 0) || Utils.existSearchMove(searchMoves, move)) { Search.RootMoves.Add(new RootMove(move)); } } MListBroker.Free(); main_thread().do_sleep = false; main_thread().wake_up(); }
// start_thinking() wakes up the main thread sleeping in MainThread::idle_loop() // so to start a new search, then returns immediately. public void start_thinking(Position pos, LimitsType limits, StateStackPtr states) { wait_for_think_finished(); Search.SearchTime = Time.now(); // As early as possible Search.Signals.stopOnPonderhit = Search.Signals.firstRootMove = false; Search.Signals.stop = Search.Signals.failedLowAtRoot = false; Search.RootMoves.Clear(); Search.RootPos = pos; Search.Limits = limits; if (states.Count > 0) // If we don't set a new position, preserve current state { Search.SetupStates = states; // Ownership transfer here //Debug.Assert(states==null); } for (MoveList it = new MoveList(pos, GenTypeS.LEGAL); it.move()!= 0; ++it) if (limits.searchmoves.Count == 0 || Misc.existSearchMove(limits.searchmoves, it.move())) Search.RootMoves.Add(new RootMove(it.move())); main().thinking = true; main().notify_one(); // Starts main thread }
internal static void init(LimitsType limits, int currentPly, Color us) { /* We support four different kind of time controls: * * increment == 0 && movesToGo == 0 means: x basetime [sudden death!] * increment == 0 && movesToGo != 0 means: x moves in y minutes * increment > 0 && movesToGo == 0 means: x basetime + z increment * increment > 0 && movesToGo != 0 means: x moves in y minutes + z increment * * Time management is adjusted by following UCI parameters: * * emergencyMoveHorizon: Be prepared to always play at least this many moves * emergencyBaseTime : Always attempt to keep at least this much time (in ms) at clock * emergencyMoveTime : Plus attempt to keep at least this much time for each remaining emergency move * minThinkingTime : No matter what, use at least this much thinking before doing the move */ int hypMTG, hypMyTime, t1, t2; // Read uci parameters int emergencyMoveHorizon = int.Parse(OptionMap.Instance["Emergency Move Horizon"].v); int emergencyBaseTime = int.Parse(OptionMap.Instance["Emergency Base Time"].v); int emergencyMoveTime = int.Parse(OptionMap.Instance["Emergency Move Time"].v); int minThinkingTime = int.Parse(OptionMap.Instance["Minimum Thinking Time"].v); int slowMover = int.Parse(OptionMap.Instance["Slow Mover"].v); // Initialize to maximum values but unstablePVExtraTime that is reset unstablePVExtraTime = 0; optimumSearchTime = maximumSearchTime = limits.time[us]; // We calculate optimum time usage for different hypothetic "moves to go"-values and choose the // minimum of calculated search time values. Usually the greatest hypMTG gives the minimum values. for (hypMTG = 1; hypMTG <= ((limits.movesToGo != 0) ? Math.Min(limits.movesToGo, MoveHorizon) : MoveHorizon); hypMTG++) { // Calculate thinking time for hypothetic "moves to go"-value hypMyTime = limits.time[us] + limits.inc[us] * (hypMTG - 1) - emergencyBaseTime - emergencyMoveTime * Math.Min(hypMTG, emergencyMoveHorizon); hypMyTime = Math.Max(hypMyTime, 0); t1 = minThinkingTime + remaining(TimeTypeC.OptimumTime, hypMyTime, hypMTG, currentPly, slowMover); t2 = minThinkingTime + remaining(TimeTypeC.MaxTime, hypMyTime, hypMTG, currentPly, slowMover); optimumSearchTime = Math.Min(optimumSearchTime, t1); maximumSearchTime = Math.Min(maximumSearchTime, t2); } if (bool.Parse(OptionMap.Instance["Ponder"].v)) { optimumSearchTime += optimumSearchTime / 4; } // Make sure that maxSearchTime is not over absoluteMaxSearchTime optimumSearchTime = Math.Min(optimumSearchTime, maximumSearchTime); }
public static void Save(IValueSink sink, LimitsType value) { sink.EnterSequence(); Value <uint> .Save(sink, value.DeviceInstanceRangeLowLimit); Value <uint> .Save(sink, value.DeviceInstanceRangeHighLimit); sink.LeaveSequence(); }
/// init() is called at the beginning of the search and calculates the allowed /// thinking time out of the time control and current game ply. We support four /// different kinds of time controls, passed in 'limits': /// /// inc == 0 && movestogo == 0 means: x basetime [sudden death!] /// inc == 0 && movestogo != 0 means: x moves in y minutes /// inc > 0 && movestogo == 0 means: x basetime + z increment /// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment internal static void init(LimitsType limits, ColorT us, int ply, DateTime now) { var minThinkingTime = int.Parse(OptionMap.Instance["Minimum Thinking Time"].v); var moveOverhead = int.Parse(OptionMap.Instance["Move Overhead"].v); var slowMover = int.Parse(OptionMap.Instance["Slow Mover"].v); var npmsec = int.Parse(OptionMap.Instance["nodestime"].v); // If we have to play in 'nodes as time' mode, then convert from time // to nodes, and use resulting values in time management formulas. // WARNING: Given npms (nodes per millisecond) must be much lower then // real engine speed to avoid time losses. if (npmsec != 0) { if (availableNodes == 0) // Only once at game start { availableNodes = npmsec*limits.time[us]; // Time is in msec } // Convert from millisecs to nodes limits.time[us] = (int) availableNodes; limits.inc[us] *= npmsec; limits.npmsec = npmsec; } start = now; unstablePvFactor = 1; optimumTime = maximumTime = Math.Max(limits.time[us], minThinkingTime); var MaxMTG = limits.movestogo != 0 ? Math.Min(limits.movestogo, MoveHorizon) : MoveHorizon; // We calculate optimum time usage for different hypothetical "moves to go"-values // and choose the minimum of calculated search time values. Usually the greatest // hypMTG gives the minimum values. for (var hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG) { // Calculate thinking time for hypothetical "moves to go"-value var hypMyTime = limits.time[us] + limits.inc[us] *(hypMTG - 1) - moveOverhead*(2 + Math.Min(hypMTG, 40)); hypMyTime = Math.Max(hypMyTime, 0); var t1 = minThinkingTime + remaining(TimeType.OptimumTime, hypMyTime, hypMTG, ply, slowMover); var t2 = minThinkingTime + remaining(TimeType.MaxTime, hypMyTime, hypMTG, ply, slowMover); optimumTime = Math.Min(t1, optimumTime); maximumTime = Math.Min(t2, maximumTime); } if (bool.Parse(OptionMap.Instance["Ponder"].v)) { optimumTime += optimumTime/4; } optimumTime = Math.Min(optimumTime, maximumTime); }
// go() is called when engine receives the "go" UCI command. The function sets // the thinking time and other parameters from the input string, and then starts // the main searching thread. internal static void go(Position pos, Stack <string> stack) { string token = string.Empty; LimitsType limits = new LimitsType(); List <Move> searchMoves = new List <Phase>(); while (stack.Count > 0) { token = stack.Pop(); if (token == "wtime") { limits.time[ColorC.WHITE] = int.Parse(stack.Pop()); } else if (token == "btime") { limits.time[ColorC.BLACK] = int.Parse(stack.Pop()); } else if (token == "winc") { limits.inc[ColorC.WHITE] = int.Parse(stack.Pop()); } else if (token == "binc") { limits.inc[ColorC.BLACK] = int.Parse(stack.Pop()); } else if (token == "movestogo") { limits.movesToGo = int.Parse(stack.Pop()); } else if (token == "depth") { limits.depth = int.Parse(stack.Pop()); } else if (token == "nodes") { limits.nodes = int.Parse(stack.Pop()); } else if (token == "movetime") { limits.movetime = int.Parse(stack.Pop()); } else if (token == "infinite") { limits.infinite = 1; } else if (token == "ponder") { limits.ponder = true; } else if (token == "searchmoves") { while ((token = stack.Pop()) != null) { searchMoves.Add(Utils.move_from_uci(pos, token)); } } } Threads.start_searching(pos, limits, searchMoves); }
// ThreadPool::start_thinking() wakes up the main thread sleeping in // MainThread::idle_loop() and starts a new search, then returns immediately. internal static void start_thinking(Position pos, LimitsType limits, StateInfoWrapper states) { main().join(); Search.Signals.stopOnPonderhit = Search.Signals.firstRootMove = false; Search.Signals.stop = Search.Signals.failedLowAtRoot = false; Search.RootMoves.Clear(); Search.RootPos = new Position(pos); Search.Limits = limits; var current = states[states.current]; if (current != null) // If we don't set a new position, preserve current state { Search.SetupStates = states; // Ownership transfer here Debug.Assert(current != null); } var ml = new MoveList(GenType.LEGAL, pos); for (var index = ml.begin(); index < ml.end(); index++) { var m = ml.moveList.table[index]; if (limits.searchmoves.Count == 0 || limits.searchmoves.FindAll(move => move == m.Move).Count > 0) { Search.RootMoves.Add(new RootMove(m)); } } main().thinking = true; main().notify_one(); // Wake up main thread: 'thinking' must be already set }
// go() is called when engine receives the "go" UCI command. The function sets // the thinking time and other parameters from the input string, then starts // the search. internal static void go(Position pos, Stack<string> stack) { var token = string.Empty; var limits = new LimitsType(); while (stack.Count > 0) { token = stack.Pop(); if (token == "wtime") { limits.time[Color.WHITE] = int.Parse(stack.Pop()); } else if (token == "btime") { limits.time[Color.BLACK] = int.Parse(stack.Pop()); } else if (token == "winc") { limits.inc[Color.WHITE] = int.Parse(stack.Pop()); } else if (token == "binc") { limits.inc[Color.BLACK] = int.Parse(stack.Pop()); } else if (token == "movestogo") { limits.movestogo = int.Parse(stack.Pop()); } else if (token == "depth") { limits.depth = int.Parse(stack.Pop()); } else if (token == "nodes") { limits.nodes = ulong.Parse(stack.Pop()); } else if (token == "movetime") { limits.movetime = int.Parse(stack.Pop()); } else if (token == "mate") { limits.mate = int.Parse(stack.Pop()); } else if (token == "infinite") { limits.infinite = 1; } else if (token == "ponder") { limits.ponder = true; } else if (token == "searchmoves") { while ((token = stack.Pop()) != null) { limits.searchmoves.Add(to_move(pos, token)); } } } ThreadPool.start_thinking(pos, limits, SetupStates); }
/// benchmark() runs a simple benchmark by letting Stockfish analyze a set /// of positions for a given limit each. There are five parameters; the /// transposition table size, the number of search threads that should /// be used, the limit value spent for each position (optional, default is /// depth 12), an optional file name where to look for positions in fen /// format (defaults are the positions defined above) and the type of the /// limit value: depth (default), time in secs or number of nodes. internal static void benchmark(Position current, Stack <string> stack) { List <string> fens = new List <string>(); LimitsType limits = new LimitsType(); Int64 nodes = 0; Int64 nodesAll = 0; long e = 0; long eAll = 0; // Assign default values to missing arguments string ttSize = (stack.Count > 0) ? (stack.Pop()) : "128"; string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; string limit = (stack.Count > 0) ? (stack.Pop()) : "12"; string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; OptionMap.Instance["Hash"].v = ttSize; OptionMap.Instance["Threads"].v = threads; TT.clear(); if (limitType == "time") { limits.movetime = 1000 * int.Parse(limit); // maxTime is in ms } else if (limitType == "nodes") { limits.nodes = int.Parse(limit); } else { limits.depth = int.Parse(limit); } if (fenFile == "default") { fens.AddRange(Defaults); } else if (fenFile == "current") { fens.Add(current.to_fen()); } else { #if PORTABLE throw new Exception("File cannot be read."); #else System.IO.StreamReader sr = new System.IO.StreamReader(fenFile, true); string fensFromFile = sr.ReadToEnd(); sr.Close(); sr.Dispose(); string[] split = fensFromFile.Replace("\r", "").Split('\n'); foreach (string fen in split) { if (fen.Trim().Length > 0) { fens.Add(fen.Trim()); } } #endif } Stopwatch time = new Stopwatch(); long[] res = new long[fens.Count]; for (int i = 0; i < fens.Count; i++) { time.Reset(); time.Start(); Position pos = new Position(fens[i], bool.Parse(OptionMap.Instance["UCI_Chess960"].v), Threads.main_thread()); Plug.Write("\nPosition: "); Plug.Write((i + 1).ToString()); Plug.Write("/"); Plug.Write(fens.Count.ToString()); Plug.Write(Constants.endl); if (limitType == "perft") { Int64 cnt = Search.perft(pos, limits.depth * DepthC.ONE_PLY); Plug.Write("\nPerft "); Plug.Write(limits.depth.ToString()); Plug.Write(" leaf nodes: "); Plug.Write(cnt.ToString()); Plug.Write(Constants.endl); nodes = cnt; } else { Threads.start_searching(pos, limits, new List <Move>()); Threads.wait_for_search_finished(); nodes = Search.RootPosition.nodes; res[i] = nodes; } e = time.ElapsedMilliseconds; nodesAll += nodes; eAll += e; Plug.Write("\n==========================="); Plug.Write("\nTotal time (ms) : "); Plug.Write(e.ToString()); Plug.Write("\nNodes searched : "); Plug.Write(nodes.ToString()); Plug.Write("\nNodes/second : "); Plug.Write(((int)(nodes / (e / 1000.0))).ToString()); Plug.Write(Constants.endl); } Plug.Write("\n==========================="); Plug.Write("\nTotal time (ms) : "); Plug.Write(eAll.ToString()); Plug.Write("\nNodes searched : "); Plug.Write(nodesAll.ToString()); Plug.Write("\nNodes/second : "); Plug.Write(((int)(nodesAll / (eAll / 1000.0))).ToString()); Plug.Write(Constants.endl); //for (int i = 0; i < res.Length; i++) //{ // Plug.Write(string.Format("{0}: {1}", i, res[i])); // Plug.Write(Constants.endl); //} }
public static void Save(IValueSink sink, LimitsType value) { sink.EnterSequence(); Value<uint>.Save(sink, value.DeviceInstanceRangeLowLimit); Value<uint>.Save(sink, value.DeviceInstanceRangeHighLimit); sink.LeaveSequence(); }
/// benchmark() runs a simple benchmark by letting Stockfish analyze a set /// of positions for a given limit each. There are five parameters: the /// transposition table size, the number of search threads that should /// be used, the limit value spent for each position (optional, default is /// depth 13), an optional file name where to look for positions in FEN /// format (defaults are the positions defined above) and the type of the /// limit value: depth (default), time in secs or number of nodes. public static void benchmark(Position current, Stack <string> stack) { LimitsType limits = new LimitsType(); List <string> fens = new List <string>(); // Assign default values to missing arguments string ttSize = (stack.Count > 0) ? (stack.Pop()) : "32"; string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; string limit = (stack.Count > 0) ? (stack.Pop()) : "14"; string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; string cantidad = (stack.Count > 0) ? (stack.Pop()) : "20"; string desde = (stack.Count > 0) ? (stack.Pop()) : "1"; //string desde = (stack.Count > 0) ? (stack.Pop()) : "1"; //string cantidad = (stack.Count > 0) ? (stack.Pop()) : "100000"; //string limit = (stack.Count > 0) ? (stack.Pop()) : "21"; //string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; //string ttSize = (stack.Count > 0) ? (stack.Pop()) : "32"; //string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; //string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; Options["Hash"].setCurrentValue(ttSize); Options["Threads"].setCurrentValue(threads); TT.clear(); if (limitType == "time") { limits.movetime = 1000 * int.Parse(limit); // movetime is in ms } else if (limitType == "nodes") { limits.nodes = int.Parse(limit); } else if (limitType == "mate") { limits.mate = int.Parse(limit); } else { limits.depth = int.Parse(limit); } if (fenFile == "default") { fens.AddRange(Defaults); } else if (fenFile == "current") { fens.Add(current.fen()); } else { String fen; System.IO.StreamReader sr = new System.IO.StreamReader(fenFile, true); int leidos = 0; int ingresados = 0; int init = Int32.Parse(desde); int n = Int32.Parse(cantidad); while (!sr.EndOfStream) { fen = sr.ReadLine().Trim(); leidos++; if (leidos >= init && ingresados <= n) { fens.Add(fen); ingresados++; if (ingresados >= n) { break; } } } sr.Close(); sr.Dispose(); } Int64 nodes = 0; StateStackPtr st = new StateStackPtr(); long elapsed = Time.now(); for (int i = 0; i < fens.Count; ++i) { //time.Reset(); time.Start(); Position pos = new Position(fens[i], Options["UCI_Chess960"].getInt() != 0 ? 1 : 0, Threads.main()); inOut.Write(Types.newline + "Position: " + (i + 1).ToString() + "/" + fens.Count.ToString() + Types.newline); inOut.Write(": "); inOut.Write(fens[i]); inOut.Write(Types.newline); if (limitType == "divide") { /** * for (MoveList<LEGAL> it(pos); *it; ++it) * { * StateInfo si; * pos.do_move(*it, si); * uint64_t cnt = limits.depth > 1 ? Search::perft(pos, (limits.depth - 1) * ONE_PLY) : 1; * pos.undo_move(*it); * cerr << move_to_uci(*it, pos.is_chess960()) << ": " << cnt << endl; * nodes += cnt; * } */ } if (limitType == "perft") { /** * uint64_t cnt = Search::perft(pos, limits.depth * ONE_PLY); * cerr << "\nPerft " << limits.depth << " leaf nodes: " << cnt << endl; * nodes += cnt; */ } else { Engine.Threads.start_thinking(pos, limits, st); Threads.wait_for_think_finished(); nodes += (Int64)Search.RootPos.nodes_searched(); } } elapsed = Time.now() - elapsed + 1; // Assure positive to avoid a 'divide by zero' inOut.Write(Types.newline + "==========================="); inOut.Write(Types.newline + "Total time (ms) : " + elapsed.ToString()); inOut.Write(Types.newline + "Nodes searched : " + nodes.ToString()); inOut.Write(Types.newline + "Nodes/second : " + (1000 * nodes / elapsed).ToString() + Types.newline); }
public static void benchfile(Position current, Stack <string> stack) { LimitsType limits = new LimitsType(); // Assign default values to missing arguments //string ttSize = (stack.Count > 0) ? (stack.Pop()) : "32"; //string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; //string limit = (stack.Count > 0) ? (stack.Pop()) : "14"; //string fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; //string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; //string cantidad = (stack.Count > 0) ? (stack.Pop()) : "20"; string desde = (stack.Count > 0) ? (stack.Pop()) : "17771192"; string limit = (stack.Count > 0) ? (stack.Pop()) : "1"; string fenFile = (stack.Count > 0) ? (stack.Pop()) : "c:\\fen\\unique00.fen"; string desFile = (stack.Count > 0) ? (stack.Pop()) : fenFile + ".dat2"; string ttSize = (stack.Count > 0) ? (stack.Pop()) : "32"; string threads = (stack.Count > 0) ? (stack.Pop()) : "1"; string limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; Options["Hash"].setCurrentValue(ttSize); Options["Threads"].setCurrentValue(threads); TT.clear(); if (limitType == "time") { limits.movetime = 1000 * int.Parse(limit); // movetime is in ms } else if (limitType == "nodes") { limits.nodes = int.Parse(limit); } else if (limitType == "mate") { limits.mate = int.Parse(limit); } else { limits.depth = int.Parse(limit); } String fen; System.IO.StreamReader sr = new System.IO.StreamReader(fenFile, true); System.IO.StreamWriter outfile = new System.IO.StreamWriter(desFile, false); Int64 i = 0; Int64 nodes = 0; StateStackPtr st = new StateStackPtr(); //Stopwatch time = new Stopwatch(); long elapsed = Time.now(); int inicio = Int32.Parse(desde); while (!sr.EndOfStream) { fen = sr.ReadLine().Trim(); i++; if (i < inicio) { continue; } Position pos = new Position(fen, Options["UCI_Chess960"].getInt() != 0 ? 1 : 0, Threads.main()); inOut.Write(Types.newline); inOut.Write("Position: "); inOut.Write((i + 1).ToString()); inOut.Write(": "); inOut.Write(fen); inOut.Write(Types.newline); if (limitType == "perft") { //int cnt = Search.perft(pos, limits.depth * DepthS.ONE_PLY); //inOut.Write(Types.newline); //inOut.Write("Perft "); //inOut.Write(limits.depth.ToString()); //inOut.Write(" leaf nodes: "); //inOut.Write(cnt.ToString()); //inOut.Write(Types.newline); //nodes += cnt; } else { Engine.Threads.start_thinking(pos, limits, st); Threads.wait_for_think_finished(); nodes += (Int64)Search.RootPos.nodes_searched(); outfile.WriteLine(i + "\t" + Search.RootPos.nodes_searched() + "\t" + Search.RootMoves[0].pv[0] + "\t" + Search.RootMoves[0].pv[1] + "\t" + nodes + "\t" + fen); } if (i % 10000 == 0) { outfile.Flush(); inOut.Write(Types.newline); inOut.Write(i.ToString()); } } sr.Close(); sr.Dispose(); outfile.Close(); outfile.Dispose(); elapsed = Time.now() - elapsed + 1; // Assure positive to avoid a 'divide by zero' inOut.Write(Types.newline); inOut.Write("==========================="); inOut.Write(Types.newline); inOut.Write("Total time (ms) : "); inOut.Write(elapsed.ToString()); inOut.Write(Types.newline); inOut.Write("Nodes searched : "); inOut.Write(nodes.ToString()); inOut.Write(Types.newline); inOut.Write("Nodes/second : "); inOut.Write((1000 * nodes / elapsed).ToString()); inOut.Write(Types.newline); }
/// benchmark() runs a simple benchmark by letting Stockfish analyze a set /// of positions for a given limit each. There are five parameters: the /// transposition table size, the number of search threads that should /// be used, the limit value spent for each position (optional, default is /// depth 13), an optional file name where to look for positions in FEN /// format (defaults are the positions defined above) and the type of the /// limit value: depth (default), time in millisecs or number of nodes. internal static void benchmark(Position current, Stack<string> stack) { var fens = new List<string>(); var limits = new LimitsType(); long nodes = 0; // Assign default values to missing arguments var ttSize = (stack.Count > 0) ? (stack.Pop()) : "16"; var threads = (stack.Count > 0) ? (stack.Pop()) : "1"; #if DEBUG var limit = (stack.Count > 0) ? (stack.Pop()) : "7"; #else var limit = (stack.Count > 0) ? (stack.Pop()) : "13"; #endif var fenFile = (stack.Count > 0) ? (stack.Pop()) : "default"; var limitType = (stack.Count > 0) ? (stack.Pop()) : "depth"; OptionMap.Instance["Hash"].v = ttSize; OptionMap.Instance["Threads"].v = threads; Search.reset(); if (limitType == "time") { limits.movetime = int.Parse(limit); // movetime is in ms } else if (limitType == "nodes") { limits.nodes = ulong.Parse(limit); } else if (limitType == "mate") { limits.mate = int.Parse(limit); } else { limits.depth = int.Parse(limit); } if (fenFile == "default") { fens.AddRange(Defaults); } else if (fenFile == "current") { fens.Add(current.fen()); } else { var sr = new StreamReader(fenFile, true); var fensFromFile = sr.ReadToEnd(); sr.Close(); sr.Dispose(); var split = fensFromFile.Replace("\r", "").Split('\n'); fens.AddRange(from fen in split where fen.Trim().Length > 0 select fen.Trim()); } var time = Stopwatch.StartNew(); for (var i = 0; i < fens.Count; ++i) { var pos = new Position(fens[i], bool.Parse(OptionMap.Instance["UCI_Chess960"].v), ThreadPool.main()); Output.WriteLine($"\nPosition: {i + 1} / {fens.Count}"); if (limitType == "perft") { nodes += Search.perft(true, pos, limits.depth*Depth.ONE_PLY); } else { var st = new StateInfoWrapper(); ThreadPool.start_thinking(pos, limits, st); ThreadPool.main().join(); nodes += Search.RootPos.nodes_searched(); } } var elapsed = time.ElapsedMilliseconds; // Ensure positivity to avoid a 'divide by zero' Output.Write("\n==========================="); Output.Write($"\nTotal time (ms) : {elapsed}"); Output.Write($"\nNodes searched : {nodes}"); Output.WriteLine($"\nNodes/second : {1000*nodes/elapsed}"); }