diff --git a/src/Makefile b/src/Makefile index cec623f5217..d64f01cb16b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,15 +56,15 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ - engine.cpp score.cpp memory.cpp + engine.cpp score.cpp memory.cpp skill.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h position.h \ - search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h + search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h tt.h tune.h\ + types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h skill.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp index a4c0bb1ebeb..248dd4c98a4 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -37,6 +37,7 @@ #include "perft.h" #include "position.h" #include "search.h" +#include "skill.h" #include "syzygy/tbprobe.h" #include "types.h" #include "uci.h" diff --git a/src/search.cpp b/src/search.cpp index 40676e2e2b2..374c47af41e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -42,6 +42,7 @@ #include "nnue/network.h" #include "nnue/nnue_accumulator.h" #include "position.h" +#include "skill.h" #include "syzygy/tbprobe.h" #include "thread.h" #include "timeman.h" @@ -1875,38 +1876,6 @@ void update_quiet_histories( } -// When playing with strength handicap, choose the best move among a set of -// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. -Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { - static PRNG rng(now()); // PRNG sequence should be non-deterministic - - // RootMoves are already sorted by score in descending order - Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); - int maxScore = -VALUE_INFINITE; - double weakness = 120 - 2 * level; - - // Choose best move. For each move score we add two terms, both dependent on - // weakness. One is deterministic and bigger for weaker levels, and one is - // random. Then we choose the move with the resulting highest score. - for (size_t i = 0; i < multiPV; ++i) - { - // This is our magic formula - int push = int(weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) - / 128; - - if (rootMoves[i].score + push >= maxScore) - { - maxScore = rootMoves[i].score + push; - best = rootMoves[i].pv[0]; - } - } - - return best; -} - - // Used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { diff --git a/src/search.h b/src/search.h index 07fc743173f..1271a5362c6 100644 --- a/src/search.h +++ b/src/search.h @@ -19,7 +19,6 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED -#include #include #include #include @@ -183,34 +182,6 @@ struct InfoIteration { size_t currmovenumber; }; -// Skill structure is used to implement strength limit. If we have a UCI_Elo, -// we convert it to an appropriate skill level, anchored to the Stash engine. -// This method is based on a fit of the Elo results for games played between -// Stockfish at various skill levels and various versions of the Stash engine. -// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately -// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 -struct Skill { - // Lowest and highest Elo ratings used in the skill level calculation - constexpr static int LowestElo = 1320; - constexpr static int HighestElo = 3190; - - Skill(int skill_level, int uci_elo) { - if (uci_elo) - { - double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo); - level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); - } - else - level = double(skill_level); - } - bool enabled() const { return level < 20.0; } - bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } - Move pick_best(const RootMoves&, size_t multiPV); - - double level; - Move best = Move::none(); -}; - // SearchManager manages the search from the main thread. It is responsible for // keeping track of the time, and storing data strictly related to the main thread. class SearchManager: public ISearchManager { diff --git a/src/skill.cpp b/src/skill.cpp new file mode 100644 index 00000000000..ba156e9b571 --- /dev/null +++ b/src/skill.cpp @@ -0,0 +1,61 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "skill.h" + +#include +#include + +#include "misc.h" +#include "search.h" +#include "types.h" + +namespace Stockfish::Search { + +// When playing with strength handicap, choose the best move among a set of +// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { + static PRNG rng(now()); // PRNG sequence should be non-deterministic + + // RootMoves are already sorted by score in descending order + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); + int maxScore = -VALUE_INFINITE; + double weakness = 120 - 2 * level; + + // Choose best move. For each move score we add two terms, both dependent on + // weakness. One is deterministic and bigger for weaker levels, and the other + // is random. Then we choose the move with the resulting highest score. + for (size_t i = 0; i < multiPV; ++i) + { + // This is our magic formula + int push = int(weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128; + + if (rootMoves[i].score + push >= maxScore) + { + maxScore = rootMoves[i].score + push; + best = rootMoves[i].pv[0]; + } + } + + return best; +} + +} // namespace Stockfish::Search diff --git a/src/skill.h b/src/skill.h new file mode 100644 index 00000000000..3ebebe4d676 --- /dev/null +++ b/src/skill.h @@ -0,0 +1,61 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SKILL_H_INCLUDED +#define SKILL_H_INCLUDED + +#include +#include + +#include "search.h" +#include "syzygy/tbprobe.h" +#include "types.h" + +namespace Stockfish::Search { + +// Skill structure is used to implement strength limit. If we have a UCI_Elo, +// we convert it to an appropriate skill level, anchored to the Stash engine. +// This method is based on a fit of the Elo results for games played between +// Stockfish at various skill levels and various versions of the Stash engine. +// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately +// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 +struct Skill { + // Lowest and highest Elo ratings used in the skill level calculation + constexpr static int LowestElo = 1320; + constexpr static int HighestElo = 3190; + + Skill(int skill_level, int uci_elo) { + if (uci_elo) + { + double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo); + level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); + } + else + level = double(skill_level); + } + bool enabled() const { return level < 20.0; } + bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } + Move pick_best(const RootMoves&, size_t multiPV); + + double level; + Move best = Move::none(); +}; + +} // namespace Stockfish::Search + +#endif // #ifndef SKILL_H_INCLUDED