diff --git a/src/search.cpp b/src/search.cpp index c393b75b..a2424ddc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -243,7 +243,7 @@ namespace { RootMoveList Rml; // MultiPV mode - int MultiPV; + int MultiPV, UCIMultiPV; // Time management variables int SearchStartTime, MaxNodes, MaxDepth, ExactMaxTime; @@ -255,6 +255,10 @@ namespace { bool UseLogFile; std::ofstream LogFile; + // Skill level adjustment + int SkillLevel; + RKISS RK; + // Multi-threads manager object ThreadsManager ThreadsMgr; @@ -503,11 +507,16 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[ PawnEndgameExtension[0] = Options["Pawn Endgame Extension (non-PV nodes)"].value(); MateThreatExtension[1] = Options["Mate Threat Extension (PV nodes)"].value(); MateThreatExtension[0] = Options["Mate Threat Extension (non-PV nodes)"].value(); - MultiPV = Options["MultiPV"].value(); + UCIMultiPV = Options["MultiPV"].value(); + SkillLevel = Options["Skill level"].value(); UseLogFile = Options["Use Search Log"].value(); read_evaluation_uci_options(pos.side_to_move()); + // Do we have to play with skill handicap? In this case enable MultiPV that + // we will use behind the scenes to retrieve a set of possible moves. + MultiPV = (SkillLevel < 10 ? Max(UCIMultiPV, 4) : UCIMultiPV); + // Set the number of active threads ThreadsMgr.read_uci_options(); init_eval(ThreadsMgr.active_threads()); @@ -687,11 +696,12 @@ namespace { // Collect info about search result bestMove = Rml[0].pv[0]; + *ponderMove = Rml[0].pv[1]; bestValues[depth] = value; bestMoveChanges[depth] = Rml.bestMoveChanges; // Send PV line to GUI and to log file - for (int i = 0; i < Min(MultiPV, (int)Rml.size()); i++) + for (int i = 0; i < Min(UCIMultiPV, (int)Rml.size()); i++) cout << Rml[i].pv_info_to_uci(pos, depth, alpha, beta, i) << endl; if (UseLogFile) @@ -746,7 +756,47 @@ namespace { } } - *ponderMove = Rml[0].pv[1]; + // When playing with strength handicap choose best move among the MultiPV + // set using a statistical rule dependent on SkillLevel. + if (SkillLevel < 10) + { + assert(MultiPV > 1); + + // Rml list is already sorted by pv_score in descending order + int s; + int max_s = -VALUE_INFINITE; + int size = Min(MultiPV, (int)Rml.size()); + int max = Rml[0].pv_score; + int var = Min(max - Rml[size - 1].pv_score, PawnValueMidgame); + int wk = 128 - 8 * SkillLevel; + + // PRNG sequence should be non deterministic + for (int i = abs(get_system_time() % 50); i > 0; i--) + RK.rand(); + + // Choose best move. For each move's score we add two terms both dependent + // on wk, one deterministic and bigger for weaker moves, and one random, + // then we choose the move with the resulting highest score. + for (int i = 0; i < size; i++) + { + s = Rml[i].pv_score; + + // Don't allow crazy blunders even at very low skills + if (i > 0 && Rml[i-1].pv_score > s + EasyMoveMargin) + break; + + // This is our magical formula + s += ((max - s) * wk + var * (RK.rand() % wk)) / 128; + + if (s > max_s) + { + max_s = s; + bestMove = Rml[i].pv[0]; + *ponderMove = Rml[i].pv[1]; + } + } + } + return bestMove; } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 487778e5..cd7dc739 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -98,6 +98,7 @@ void init_uci_options() { Options["Ponder"] = Option(true); Options["OwnBook"] = Option(true); Options["MultiPV"] = Option(1, 1, 500); + Options["Skill level"] = Option(10, 0, 10); Options["Emergency Move Horizon"] = Option(40, 0, 50); Options["Emergency Base Time"] = Option(200, 0, 30000); Options["Emergency Move Time"] = Option(70, 0, 5000);