From de24fcebc873ce2d65b30e039745dbc2e851f443 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 26 Jun 2020 17:26:46 -0700 Subject: [PATCH 01/18] Fix fragile code to use proper random 64 bit keys. This fixes an old issue where we want to make a position unique but only change a small number of bits in the key instead of all 64 of them randomly. This is fragile and can lead to non uniqueness issues in the TT. Key make_key(uint64_t seed) takes any integer and produces a unique random 64 bit key. It is computationally efficient and is based on a congruential pseudo random number generator using well tested constants by Donald Knuth (see https://en.wikipedia.org/wiki/Linear_congruential_generator) STC https://tests.stockfishchess.org/tests/view/5ef6c78f761b685b4c724bb6 LLR: 2.95 (-2.94,2.94) {-1.50,0.50} Total: 154320 W: 29343 L: 29376 D: 95601 Ptnml(0-2): 2543, 18170, 35891, 17889, 2667 LTC https://tests.stockfishchess.org/tests/view/5ef7d1a9020eec13834a940e LLR: 2.95 (-2.94,2.94) {-1.50,0.50} Total: 53488 W: 6629 L: 6584 D: 40275 Ptnml(0-2): 372, 4878, 16183, 4955, 356 closes https://github.com/official-stockfish/Stockfish/pull/2773 bench: 4626776 --- src/search.cpp | 2 +- src/types.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1e2980cb..0fa39988 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -662,7 +662,7 @@ namespace { // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ (Key(excludedMove) << 48); // Isn't a very good hash + posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] diff --git a/src/types.h b/src/types.h index 0c512f5b..c1598561 100644 --- a/src/types.h +++ b/src/types.h @@ -455,6 +455,11 @@ constexpr bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } +/// Based on a congruential pseudo random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; +} + #endif // #ifndef TYPES_H_INCLUDED #include "tune.h" // Global visibility to tuning setup From 547c4a216a9931e4d5ff95414f146cb6eb877611 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 25 Jun 2020 22:08:17 -0700 Subject: [PATCH 02/18] Remove old zobrist trick for castling rights Removes an 8 year old micro optimization aimed at 32-bit architectures because back then doing an xor of a Key could not be done in one instruction. See original commit here 821e1c7 STC https://tests.stockfishchess.org/tests/view/5ef5833dde213bf647527d0c LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 162648 W: 31053 L: 31097 D: 100498 Ptnml(0-2): 2841, 18966, 37715, 19000, 2802 LTC https://tests.stockfishchess.org/tests/view/5ef7b1bbf993893290cc1489 LLR: 2.93 (-2.94,2.94) {-1.50,0.50} Total: 62360 W: 7617 L: 7586 D: 47157 Ptnml(0-2): 423, 5662, 18994, 5663, 438 closes https://github.com/official-stockfish/Stockfish/pull/2775 bench: 4591425 --- src/position.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 471ef01f..6ef7aedc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -119,15 +119,7 @@ void Position::init() { Zobrist::enpassant[f] = rng.rand(); for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) - { - Zobrist::castling[cr] = 0; - Bitboard b = cr; - while (b) - { - Key k = Zobrist::castling[1ULL << pop_lsb(&b)]; - Zobrist::castling[cr] ^= k ? k : rng.rand(); - } - } + Zobrist::castling[cr] = rng.rand(); Zobrist::side = rng.rand(); Zobrist::noPawns = rng.rand(); @@ -780,9 +772,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update castling rights if needed if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) { - int cr = castlingRightsMask[from] | castlingRightsMask[to]; - k ^= Zobrist::castling[st->castlingRights & cr]; - st->castlingRights &= ~cr; + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; } // Move the piece. The tricky Chess960 castling is handled earlier From 2810a1ea85b3fbe62095fcb24442c08306f00af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 28 Jun 2020 06:00:28 +0200 Subject: [PATCH 03/18] Increase value of pawns on fifth rank This patch increases the endgame value of pawns on the fifth rank. The increase is very small (+1 evaluation point, about 0.005 pawn) for the pawns on external columns (a-b-c-f-g-h) and a bit bigger (+7 evaluation points, about 0.033 pawn) for the pawns on d5/e5. STC: LLR: 2.93 (-2.94,2.94) {-0.50,1.50} Total: 79864 W: 15331 L: 15027 D: 49506 Ptnml(0-2): 1336, 9284, 18433, 9498, 1381 https://tests.stockfishchess.org/tests/view/5ef73e2ef993893290cc0c47 LTC: LLR: 2.94 (-2.94,2.94) {0.25,1.75} Total: 47240 W: 5927 L: 5630 D: 35683 Ptnml(0-2): 320, 4133, 14440, 4384, 343 https://tests.stockfishchess.org/tests/view/5ef7c0c4f993893290cc14b7 closes https://github.com/official-stockfish/Stockfish/pull/2776 Bench: 4794633 --- src/psqt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psqt.cpp b/src/psqt.cpp index c5da9785..5e8dd2c7 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -92,7 +92,7 @@ constexpr Score PBonus[RANK_NB][FILE_NB] = { S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) }, { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) }, { S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) }, - { S( 13, 9), S( 0, 4), S(-13, 3), S( 1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S( 5, 8) }, + { S( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) }, { S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) }, { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } }; From 16836f39b295ec635c9883498400f7006ac2869f Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Jun 2020 16:28:55 +0200 Subject: [PATCH 04/18] Scale down eval for drawish rook endgames. STC: LLR: 2.96 (-2.94,2.94) {-0.50,1.50} Total: 82136 W: 15694 L: 15407 D: 51035 Ptnml(0-2): 1076, 8960, 20767, 9131, 1134 https://tests.stockfishchess.org/tests/view/5ef86cf8020eec13834a94dd LTC: LLR: 2.93 (-2.94,2.94) {0.25,1.75} Total: 70200 W: 8787 L: 8440 D: 52973 Ptnml(0-2): 325, 5983, 22170, 6264, 358 https://tests.stockfishchess.org/tests/view/5ef88225020eec13834a950a closes https://github.com/official-stockfish/Stockfish/pull/2780 Bench: 4478869 --- src/evaluate.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 60ec9c72..65f7bddc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -782,6 +782,13 @@ namespace { else sf = 22 + 3 * pos.count(strongSide); } + else if( pos.non_pawn_material(WHITE) == RookValueMg + && pos.non_pawn_material(BLACK) == RookValueMg + && !pe->passed_pawns(strongSide) + && pos.count(strongSide) - pos.count(~strongSide) <= 1 + && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) + && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) + sf = 36; else sf = std::min(sf, 36 + 7 * pos.count(strongSide)); } From c7194bd924a606ab75d582d30cb41749312ea94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 28 Jun 2020 22:24:57 +0200 Subject: [PATCH 05/18] Scale down eval for queen imbalance We lower the endgame value of the evaluation when we detect that there is only one queen left on the board (more precisely, we use a scale factor of 37/64, or about 0.58, for the endgame part of the evaluation). Hopefully this helps a little bit for the assessment of positions with queen imbalance, which are one of the well-known Stockfish weaknesses. STC: LLR: 2.94 (-2.94,2.94) {-0.50,1.50} Total: 21600 W: 4176 L: 3955 D: 13469 Ptnml(0-2): 351, 2457, 5003, 2598, 391 https://tests.stockfishchess.org/tests/view/5ef871b6020eec13834a94e8 LTC: LLR: 2.97 (-2.94,2.94) {0.25,1.75} Total: 248328 W: 30596 L: 29720 D: 188012 Ptnml(0-2): 1544, 22345, 75665, 22911, 1699 https://tests.stockfishchess.org/tests/view/5ef87aec020eec13834a94fe Closes https://github.com/official-stockfish/Stockfish/pull/2781 Bench: 4441323 --- src/evaluate.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 65f7bddc..d19cf34e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -767,7 +767,6 @@ namespace { eg += v; // Compute the scale factor for the winning side - Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; int sf = me->scale_factor(pos, strongSide); @@ -782,13 +781,15 @@ namespace { else sf = 22 + 3 * pos.count(strongSide); } - else if( pos.non_pawn_material(WHITE) == RookValueMg + else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg && !pe->passed_pawns(strongSide) && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; + else if (pos.count() == 1) + sf = 37; else sf = std::min(sf, 36 + 7 * pos.count(strongSide)); } From 69d3be42a112645a9e599df615f730d61a5dca8c Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 29 Jun 2020 19:35:24 +0200 Subject: [PATCH 06/18] Tweak single queen endgame scaling. Increase scaling factor for each minor of the opponent side of the queen. STC: LLR: 2.94 (-2.94,2.94) {-0.50,1.50} Total: 14528 W: 2860 L: 2653 D: 9015 Ptnml(0-2): 217, 1632, 3408, 1741, 266 https://tests.stockfishchess.org/tests/view/5ef98384020eec13834a96a0 LTC: LLR: 2.95 (-2.94,2.94) {0.25,1.75} Total: 34584 W: 4371 L: 4111 D: 26102 Ptnml(0-2): 205, 3080, 10501, 3262, 244 https://tests.stockfishchess.org/tests/view/5ef99972020eec13834a96c9 closes https://github.com/official-stockfish/Stockfish/pull/2782 Bench: 4523573 --- src/evaluate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d19cf34e..615df1ba 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -789,7 +789,8 @@ namespace { && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; else if (pos.count() == 1) - sf = 37; + sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) + : pos.count(WHITE) + pos.count(WHITE)); else sf = std::min(sf, 36 + 7 * pos.count(strongSide)); } From 110068808b51344ac59f8c6a0846f5dfdf670392 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 27 Jun 2020 21:29:29 +0200 Subject: [PATCH 07/18] Provide WDL statistics A number of engines, GUIs and tournaments start to report WDL estimates along or instead of scores. This patch enables reporting of those stats in a more or less standard way (http://www.talkchess.com/forum3/viewtopic.php?t=72140) The model this reporting uses is based on data derived from a few million fishtest LTC games, given a score and a game ply, a win rate is provided that matches rather closely, especially in the intermediate range [0.05, 0.95] that data. Some data is shown at https://github.com/glinscott/fishtest/wiki/UsefulData#win-loss-draw-statistics-of-ltc-games-on-fishtest Making the conversion game ply dependent is important for a good fit, and is in line with experience that a +1 score in the early midgame is more likely a win than in the late endgame. Even when enabled, the printing of the info causes no significant overhead. Passed STC: LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 197112 W: 37226 L: 37347 D: 122539 Ptnml(0-2): 2591, 21025, 51464, 20866, 2610 https://tests.stockfishchess.org/tests/view/5ef79ef4f993893290cc146b closes https://github.com/official-stockfish/Stockfish/pull/2778 No functional change --- Readme.md | 5 +++++ src/search.cpp | 3 +++ src/uci.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/uci.h | 1 + src/ucioption.cpp | 1 + 5 files changed, 49 insertions(+) diff --git a/Readme.md b/Readme.md index 2b1de86b..e60ac718 100644 --- a/Readme.md +++ b/Readme.md @@ -66,6 +66,11 @@ Currently, Stockfish has the following UCI options: If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo. This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4. + * #### UCI_ShowWDL + If enabled, show approximate WDL statistics as part of the engine output. + These WDL numbers model expected game outcomes for a given evaluation and + game ply for engine self-play at fishtest LTC conditions (60+0.6s per game). + * #### Move Overhead Assume a time delay of x ms due to network and GUI overheads. This is useful to avoid losses on time in those cases. diff --git a/src/search.cpp b/src/search.cpp index 0fa39988..f14bdf77 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1835,6 +1835,9 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { << " multipv " << i + 1 << " score " << UCI::value(v); + if (Options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + if (!tb && i == pvIdx) ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); diff --git a/src/uci.cpp b/src/uci.cpp index 11d5adc6..bb57c80b 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -182,6 +183,28 @@ namespace { << "\nNodes/second : " << 1000 * nodes / elapsed << endl; } + // The win rate model returns the probability (per mille) of winning given an eval + // and a game-ply. The model fits rather accurately the LTC fishtest statistics. + int win_rate_model(Value v, int ply) { + + // The model captures only up to 240 plies, so limit input (and rescale) + double m = std::min(240, ply) / 64.0; + + // Coefficients of a 3rd order polynomial fit based on fishtest data + // for two parameters needed to transform eval to the argument of a + // logistic function. + double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679}; + double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751}; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + // Transform eval to centipawns with limited range + double x = Utility::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0); + + // Return win rate in per mille (rounded to nearest) + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + } + } // namespace @@ -269,6 +292,22 @@ string UCI::value(Value v) { } +/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on +/// data gathered for fishtest LTC games. + +string UCI::wdl(Value v, int ply) { + + stringstream ss; + + int wdl_w = win_rate_model( v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + + /// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { diff --git a/src/uci.h b/src/uci.h index b845889b..ad954d9f 100644 --- a/src/uci.h +++ b/src/uci.h @@ -73,6 +73,7 @@ std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); +std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); } // namespace UCI diff --git a/src/ucioption.cpp b/src/ucioption.cpp index c268c975..4befa6ac 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -74,6 +74,7 @@ void init(OptionsMap& o) { o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); o["UCI_Elo"] << Option(1350, 1350, 2850); + o["UCI_ShowWDL"] << Option(true); o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); From 268c00b648ba4a48be79a849dde5733e6705ddbf Mon Sep 17 00:00:00 2001 From: Alain SAVARD Date: Wed, 1 Jul 2020 02:12:59 -0400 Subject: [PATCH 08/18] Use arrays for safe checks, outposts and king protectors in evaluate.cpp Tested for non regression on the safe checks https://tests.stockfishchess.org/tests/view/5ef8b75c020eec13834a9596 LLR: 2.95 (-2.94,2.94) {-1.50,0.50} Total: 22256 W: 4283 L: 4143 D: 13830 Ptnml(0-2): 291, 2439, 5588, 2459, 351 Tested for non regression on the safe checks, outposts and king protectors https://tests.stockfishchess.org/tests/view/5ef8e543020eec13834a95e7 LLR: 2.95 (-2.94,2.94) {-1.50,0.50} Total: 28400 W: 5382 L: 5253 D: 17765 Ptnml(0-2): 394, 3078, 7119, 3223, 386 closes https://github.com/official-stockfish/Stockfish/pull/2785 No functional change --- src/evaluate.cpp | 76 ++++++++++++++++++++++-------------------------- src/pawns.cpp | 4 ++- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 615df1ba..48db2b3b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -80,11 +80,11 @@ namespace { // KingAttackWeights[PieceType] contains king attack weights by piece type constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; - // Penalties for enemy's safe checks - constexpr int QueenSafeCheck = 772; - constexpr int RookSafeCheck = 1084; - constexpr int BishopSafeCheck = 645; - constexpr int KnightSafeCheck = 792; + // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, + // higher if multiple safe checks are possible for that piece type. + constexpr int SafeCheck[][2] = { + {}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119} + }; #define S(mg, eg) make_score(mg, eg) @@ -106,6 +106,18 @@ namespace { S(110,182), S(114,182), S(114,192), S(116,219) } }; + // KingProtector[knight/bishop] contains penalty for each distance unit to own king + constexpr Score KingProtector[] = { S(8, 9), S(6, 9) }; + + // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a + // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. + constexpr Score Outpost[] = { S(56, 36), S(30, 23) }; + + // PassedRank[Rank] contains a bonus according to the rank of a passed pawn + constexpr Score PassedRank[RANK_NB] = { + S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260) + }; + // RookOnFile[semiopen/open] contains bonuses for each rook when there is // no (friendly) pawn on the rook file. constexpr Score RookOnFile[] = { S(19, 7), S(48, 29) }; @@ -121,23 +133,14 @@ namespace { S(0, 0), S(3, 46), S(37, 68), S(42, 60), S(0, 38), S(58, 41) }; - // PassedRank[Rank] contains a bonus according to the rank of a passed pawn - constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260) - }; - // Assorted bonuses and penalties - constexpr Score BishopKingProtector = S( 6, 9); constexpr Score BishopOnKingRing = S( 24, 0); - constexpr Score BishopOutpost = S( 30, 23); constexpr Score BishopPawns = S( 3, 7); constexpr Score BishopXRayPawns = S( 4, 5); constexpr Score CorneredBishop = S( 50, 50); constexpr Score FlankAttacks = S( 8, 0); constexpr Score Hanging = S( 69, 36); - constexpr Score KnightKingProtector = S( 8, 9); constexpr Score KnightOnQueen = S( 16, 11); - constexpr Score KnightOutpost = S( 56, 36); constexpr Score LongDiagonalBishop = S( 45, 0); constexpr Score MinorBehindPawn = S( 18, 3); constexpr Score PassedFile = S( 11, 8); @@ -308,7 +311,7 @@ namespace { // Bonus if piece is on an outpost square or can reach one bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them); if (bb & s) - score += (Pt == KNIGHT) ? KnightOutpost : BishopOutpost; + score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) score += ReachableOutpost; @@ -317,8 +320,7 @@ namespace { score += MinorBehindPawn; // Penalty if the piece is far from the king - score -= (Pt == KNIGHT ? KnightKingProtector - : BishopKingProtector) * distance(pos.square(Us), s); + score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); if (Pt == BISHOP) { @@ -420,41 +422,33 @@ namespace { b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); // Enemy rooks checks - rookChecks = b1 & safe & attackedBy[Them][ROOK]; + rookChecks = b1 & attackedBy[Them][ROOK] & safe; if (rookChecks) - kingDanger += more_than_one(rookChecks) ? RookSafeCheck * 175/100 - : RookSafeCheck; + kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)]; else unsafeChecks |= b1 & attackedBy[Them][ROOK]; - // Enemy queen safe checks: we count them only if they are from squares from - // which we can't give a rook check, because rook checks are more valuable. - queenChecks = (b1 | b2) - & attackedBy[Them][QUEEN] - & safe - & ~attackedBy[Us][QUEEN] - & ~rookChecks; + // Enemy queen safe checks: count them only if the checks are from squares from + // which opponent cannot give a rook check, because rook checks are more valuable. + queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe + & ~(attackedBy[Us][QUEEN] | rookChecks); if (queenChecks) - kingDanger += more_than_one(queenChecks) ? QueenSafeCheck * 145/100 - : QueenSafeCheck; + kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; - // Enemy bishops checks: we count them only if they are from squares from - // which we can't give a queen check, because queen checks are more valuable. - bishopChecks = b2 - & attackedBy[Them][BISHOP] - & safe + // Enemy bishops checks: count them only if they are from squares from which + // opponent cannot give a queen check, because queen checks are more valuable. + bishopChecks = b2 & attackedBy[Them][BISHOP] & safe & ~queenChecks; if (bishopChecks) - kingDanger += more_than_one(bishopChecks) ? BishopSafeCheck * 3/2 - : BishopSafeCheck; + kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)]; + else unsafeChecks |= b2 & attackedBy[Them][BISHOP]; // Enemy knights checks knightChecks = attacks_bb(ksq) & attackedBy[Them][KNIGHT]; if (knightChecks & safe) - kingDanger += more_than_one(knightChecks & safe) ? KnightSafeCheck * 162/100 - : KnightSafeCheck; + kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)]; else unsafeChecks |= knightChecks; @@ -464,7 +458,7 @@ namespace { b2 = b1 & attackedBy2[Them]; b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - int kingFlankAttack = popcount(b1) + popcount(b2); + int kingFlankAttack = popcount(b1) + popcount(b2); int kingFlankDefense = popcount(b3); kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] @@ -741,8 +735,8 @@ namespace { bool almostUnwinnable = outflanking < 0 && !pawnsOnBothFlanks; - bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 - || rank_of(pos.square(BLACK)) < RANK_5; + bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 + || rank_of(pos.square(BLACK)) < RANK_5; // Compute the initiative bonus for the attacking side int complexity = 9 * pe->passed_count() diff --git a/src/pawns.cpp b/src/pawns.cpp index d741b2ef..d365ba12 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -38,7 +38,9 @@ namespace { constexpr Score WeakLever = S( 0, 56); constexpr Score WeakUnopposed = S(13, 27); - constexpr Score BlockedStorm[RANK_NB] = {S( 0, 0), S( 0, 0), S( 76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2)}; + constexpr Score BlockedStorm[RANK_NB] = { + S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) + }; // Connected pawn bonus constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 }; From fb83da0892c183690ddeb1f7c3dbf6779b12707a Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 2 Jul 2020 18:58:37 +0200 Subject: [PATCH 09/18] Set UCI_ShowWDL by default to false UCI_ShowWDL might not be shown by GUIs that don't know the option, but crash on the WDL output, effectively making it hard for users to turn it off and run the engine. This sets it by default to false. fixes https://github.com/official-stockfish/Stockfish/issues/2787 closes https://github.com/official-stockfish/Stockfish/pull/2788 No functional change. --- src/ucioption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 4befa6ac..ef54ef4e 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -74,7 +74,7 @@ void init(OptionsMap& o) { o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); o["UCI_Elo"] << Option(1350, 1350, 2850); - o["UCI_ShowWDL"] << Option(true); + o["UCI_ShowWDL"] << Option(false); o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); From 67818ee9481ba99369fa8a8d92e5c50428fb300e Mon Sep 17 00:00:00 2001 From: SFisGOD Date: Thu, 2 Jul 2020 00:11:23 +0800 Subject: [PATCH 10/18] Remove passed pawn condition. This will help scale down relatively high eval in drawish rook endgames with passed pawn like in TCEC S18 Superfinal Game 90. Passed STC LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 50456 W: 9644 L: 9540 D: 31272 Ptnml(0-2): 760, 5637, 12332, 5737, 762 https://tests.stockfishchess.org/tests/view/5efcb76e59f6f035328940ed Passed LTC LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 77264 W: 9518 L: 9518 D: 58228 Ptnml(0-2): 402, 6766, 24321, 6716, 427 https://tests.stockfishchess.org/tests/view/5efd2ad759f6f03532894143 closes https://github.com/official-stockfish/Stockfish/pull/2792 Bench: 4431626 --- src/evaluate.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 48db2b3b..bb1724a4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -777,7 +777,6 @@ namespace { } else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg - && !pe->passed_pawns(strongSide) && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) From c5b2a92cd17c65a639ec6739dd511767f65e188d Mon Sep 17 00:00:00 2001 From: protonspring Date: Tue, 30 Jun 2020 10:17:50 -0600 Subject: [PATCH 11/18] denormalize KRKP. a non-functional code style change that denormalizes the KRKP endgame, making it somewhat easier to read. closes https://github.com/official-stockfish/Stockfish/pull/2786 No functional change --- src/endgame.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/endgame.cpp b/src/endgame.cpp index be0755a8..40f49dce 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -181,15 +181,15 @@ Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - Square strongKing = relative_square(strongSide, pos.square(strongSide)); - Square weakKing = relative_square(strongSide, pos.square(weakSide)); - Square strongRook = relative_square(strongSide, pos.square(strongSide)); - Square weakPawn = relative_square(strongSide, pos.square(weakSide)); - Square queeningSquare = make_square(file_of(weakPawn), RANK_1); + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongRook = pos.square(strongSide); + Square weakPawn = pos.square(weakSide); + Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); Value result; // If the stronger side's king is in front of the pawn, it's a win - if (forward_file_bb(WHITE, strongKing) & weakPawn) + if (forward_file_bb(strongSide, strongKing) & weakPawn) result = RookValueEg - distance(strongKing, weakPawn); // If the weaker side's king is too far from the pawn and the rook, @@ -200,15 +200,15 @@ Value Endgame::operator()(const Position& pos) const { // If the pawn is far advanced and supported by the defending king, // the position is drawish - else if ( rank_of(weakKing) <= RANK_3 + else if ( relative_rank(strongSide, weakKing) <= RANK_3 && distance(weakKing, weakPawn) == 1 - && rank_of(strongKing) >= RANK_4 + && relative_rank(strongSide, strongKing) >= RANK_4 && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) result = Value(80) - 8 * distance(strongKing, weakPawn); else - result = Value(200) - 8 * ( distance(strongKing, weakPawn + SOUTH) - - distance(weakKing, weakPawn + SOUTH) + result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) + - distance(weakKing, weakPawn + pawn_push(weakSide)) - distance(weakPawn, queeningSquare)); return strongSide == pos.side_to_move() ? result : -result; From 7225d254f90c7b9d64d4adf85ec2d319c6cf75a0 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 6 Jul 2020 09:30:23 +0200 Subject: [PATCH 12/18] Add a rank based bonus for blocked pawns. Fix for overevaluated blocked pawns on the 5th and 6th rank. This is a rewrite of the original idea that uses only two parameters. Thanks to rocky640 for pointing this out. STC: LLR: 2.94 (-2.94,2.94) {-0.50,1.50} Total: 50800 W: 9707 L: 9446 D: 31647 Ptnml(0-2): 831, 5851, 11822, 6018, 878 https://tests.stockfishchess.org/tests/view/5f00b4f359f6f03532894304 LTC: LLR: 2.93 (-2.94,2.94) {0.25,1.75} Total: 52064 W: 6477 L: 6167 D: 39420 Ptnml(0-2): 331, 4628, 15834, 4878, 361 https://tests.stockfishchess.org/tests/view/5f0115fe59f6f03532894345 closes https://github.com/official-stockfish/Stockfish/pull/2794 Bench: 4882833 --- src/pawns.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pawns.cpp b/src/pawns.cpp index d365ba12..f18e0315 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -38,6 +38,9 @@ namespace { constexpr Score WeakLever = S( 0, 56); constexpr Score WeakUnopposed = S(13, 27); + // Bonus for blocked pawns at 5th or 6th rank + constexpr Score BlockedPawn[2] = { S(-10, -3), S(-3, 3) }; + constexpr Score BlockedStorm[RANK_NB] = { S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) }; @@ -169,6 +172,9 @@ namespace { if (!support) score -= Doubled * doubled + WeakLever * more_than_one(lever); + + if (blocked && r > RANK_4) + score += BlockedPawn[r-4]; } return score; From 76a039027d14640852f60bda6d62ca16bdac3b9e Mon Sep 17 00:00:00 2001 From: Alain SAVARD Date: Mon, 6 Jul 2020 22:43:54 -0400 Subject: [PATCH 13/18] Clean-up en passant processing the goal of this PR is to better document how we process the ep square (if any) given position fen command, and to output more meaningful (and consistent) debug fen on the "d" command. The implementation follows https://en.wikipedia.org/wiki/X-FEN#Encoding_en-passant following x-fen, it is "valid" to record ep even if ep would put king en prise. fixes #2784 closes https://github.com/official-stockfish/Stockfish/pull/2797 No functional change --- src/position.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 6ef7aedc..396bff5f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -178,9 +178,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. This is recorded only if there is a pawn - in position to make an en passant capture, and if there really is a pawn - that might have advanced two squares. + is the position "behind" the pawn. Following X-FEN standard, this is recorded only + if there is a pawn in position to make an en passant capture, and if there really + is a pawn that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -251,17 +251,25 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th set_castling_right(c, rsq); } - // 4. En passant square. Ignore if no pawn capture is possible + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; + if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == '3' || row == '6'))) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)) - || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))) - st->epSquare = SQ_NONE; + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } - else + + if (!enpassant) st->epSquare = SQ_NONE; // 5-6. Halfmove clock and fullmove number From 804a29c738847b7ea5f8a4bff001964bd234d332 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 8 Jul 2020 01:29:03 +0300 Subject: [PATCH 14/18] Connected / blocked pawns simplification There is no need to score blocked pawns at many places. The idea originated from: Rocky Tuning and testing by: Fauzi Passed STC: https://tests.stockfishchess.org/tests/view/5f04f8fd59f6f035328945d4 LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 6352 W: 1299 L: 1118 D: 3935 Ptnml(0-2): 89, 695, 1469, 792, 131 Passed LTC: https://tests.stockfishchess.org/tests/view/5f0527bd59f6f035328945e3 LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 27648 W: 3517 L: 3433 D: 20698 Ptnml(0-2): 177, 2561, 8301, 2571, 214 closes https://github.com/official-stockfish/Stockfish/pull/2799 Bench: 4734746 --- src/pawns.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pawns.cpp b/src/pawns.cpp index f18e0315..7f8d451a 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -39,7 +39,7 @@ namespace { constexpr Score WeakUnopposed = S(13, 27); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-10, -3), S(-3, 3) }; + constexpr Score BlockedPawn[2] = { S(-11, -4), S(-3, 4) }; constexpr Score BlockedStorm[RANK_NB] = { S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) @@ -148,7 +148,7 @@ namespace { // Score this pawn if (support | phalanx) { - int v = Connected[r] * (4 + 2 * bool(phalanx) - 2 * bool(opposed) - bool(blocked)) / 2 + int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) + 21 * popcount(support); score += make_score(v, v * (r - 2) / 4); From bf5ce1c214f8f8e3f98e5e3ac43db0dd28617e35 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 5 Jul 2020 15:17:04 -0700 Subject: [PATCH 15/18] Simplify make_promotions() Remove special case handling of QUIET_CHECKS in make_promotions() STC https://tests.stockfishchess.org/tests/view/5f055dbb59f6f035328945fb LLR: 2.98 (-2.94,2.94) {-1.50,0.50} Total: 42808 W: 8177 L: 8054 D: 26577 Ptnml(0-2): 665, 4890, 10201, 4953, 695 LTC https://tests.stockfishchess.org/tests/view/5f06231a59f6f03532894661 LLR: 2.96 (-2.94,2.94) {-1.50,0.50} Total: 9616 W: 1214 L: 1111 D: 7291 Ptnml(0-2): 53, 821, 2965, 908, 61 closes https://github.com/official-stockfish/Stockfish/pull/2800 Bench: 4576410 --- src/movegen.cpp | 22 ++++++++++------------ src/search.cpp | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 17203a95..4ff12fc6 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -29,22 +29,20 @@ namespace { ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { *moveList++ = make(to - D, to, QUEEN); + if (attacks_bb(to) & ksq) + *moveList++ = make(to - D, to, KNIGHT); + } if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); + if (!(attacks_bb(to) & ksq)) + *moveList++ = make(to - D, to, KNIGHT); } - // Knight promotion is the only promotion that can give a direct check - // that's not already included in the queen promotion. - if (Type == QUIET_CHECKS && (attacks_bb(to) & ksq)) - *moveList++ = make(to - D, to, KNIGHT); - else - (void)ksq; // Silence a warning under MSVC - return moveList; } @@ -263,8 +261,8 @@ namespace { } // namespace -/// Generates all pseudo-legal captures and queen promotions -/// Generates all pseudo-legal non-captures and underpromotions +/// Generates all pseudo-legal captures plus queen and checking knight promotions +/// Generates all pseudo-legal non-captures and underpromotions(except checking knight) /// Generates all pseudo-legal captures and non-captures /// /// Returns a pointer to the end of the move list. @@ -287,8 +285,8 @@ template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all pseudo-legal non-captures and knight -/// underpromotions that give check. Returns a pointer to the end of the move list. +/// generate generates all pseudo-legal non-captures. +/// Returns a pointer to the end of the move list. template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { diff --git a/src/search.cpp b/src/search.cpp index f14bdf77..1610c206 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1486,8 +1486,8 @@ moves_loop: // When in check, search starts from here // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, - // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will - // be generated. + // queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS) + // will be generated. MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, From 4006f2c9132db034a27a94be33070d6aaab75b24 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 9 Jul 2020 22:01:06 +0200 Subject: [PATCH 16/18] Small cleanups closes https://github.com/official-stockfish/Stockfish/pull/2772 No functional change --- Readme.md | 3 --- src/benchmark.cpp | 2 +- src/bitboard.h | 32 ++++++++++++++++---------------- src/evaluate.cpp | 10 +++++----- src/search.cpp | 14 +++++++++----- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Readme.md b/Readme.md index e60ac718..823518d1 100644 --- a/Readme.md +++ b/Readme.md @@ -75,9 +75,6 @@ Currently, Stockfish has the following UCI options: Assume a time delay of x ms due to network and GUI overheads. This is useful to avoid losses on time in those cases. - * #### Minimum Thinking Time - Search for at least x ms per move. - * #### Slow Mover Lower values will make Stockfish take less time in games, higher values will make it think longer. diff --git a/src/benchmark.cpp b/src/benchmark.cpp index f338cdda..3299f373 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -88,7 +88,7 @@ const vector Defaults = { // Chess 960 "setoption name UCI_Chess960 value true", - "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", "setoption name UCI_Chess960 value false" }; diff --git a/src/bitboard.h b/src/bitboard.h index 1c598108..afeb40ec 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -124,7 +124,7 @@ inline Bitboard operator&(Square s, Bitboard b) { return b & s; } inline Bitboard operator|(Square s, Bitboard b) { return b | s; } inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s, Square s2) { return square_bb(s) | s2; } +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } constexpr bool more_than_one(Bitboard b) { return b & (b - 1); @@ -138,19 +138,19 @@ constexpr bool opposite_colors(Square s1, Square s2) { /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. -inline Bitboard rank_bb(Rank r) { +constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); } -inline Bitboard rank_bb(Square s) { +constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); } -inline Bitboard file_bb(File f) { +constexpr Bitboard file_bb(File f) { return FileABB << f; } -inline Bitboard file_bb(Square s) { +constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } @@ -195,16 +195,16 @@ constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { /// adjacent_files_bb() returns a bitboard representing all the squares on the -/// adjacent files of the given one. +/// adjacent files of a given square. -inline Bitboard adjacent_files_bb(Square s) { +constexpr Bitboard adjacent_files_bb(Square s) { return shift(file_bb(s)) | shift(file_bb(s)); } -/// line_bb(Square, Square) returns a bitboard representing an entire line, -/// from board edge to board edge, that intersects the given squares. If the -/// given squares are not on a same file/rank/diagonal, returns 0. For instance, +/// line_bb() returns a bitboard representing an entire line (from board edge +/// to board edge) that intersects the two given squares. If the given squares +/// are not on a same file/rank/diagonal, the function returns 0. For instance, /// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. inline Bitboard line_bb(Square s1, Square s2) { @@ -215,8 +215,8 @@ inline Bitboard line_bb(Square s1, Square s2) { /// between_bb() returns a bitboard representing squares that are linearly -/// between the given squares (excluding the given squares). If the given -/// squares are not on a same file/rank/diagonal, return 0. For instance, +/// between the two given squares (excluding the given squares). If the given +/// squares are not on a same file/rank/diagonal, we return 0. For instance, /// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6. inline Bitboard between_bb(Square s1, Square s2) { @@ -229,7 +229,7 @@ inline Bitboard between_bb(Square s1, Square s2) { /// in front of the given one, from the point of view of the given color. For instance, /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. -inline Bitboard forward_ranks_bb(Color c, Square s) { +constexpr Bitboard forward_ranks_bb(Color c, Square s) { return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s) : ~Rank8BB >> 8 * relative_rank(BLACK, s); } @@ -238,7 +238,7 @@ inline Bitboard forward_ranks_bb(Color c, Square s) { /// forward_file_bb() returns a bitboard representing all the squares along the /// line in front of the given one, from the point of view of the given color. -inline Bitboard forward_file_bb(Color c, Square s) { +constexpr Bitboard forward_file_bb(Color c, Square s) { return forward_ranks_bb(c, s) & file_bb(s); } @@ -247,7 +247,7 @@ inline Bitboard forward_file_bb(Color c, Square s) { /// be attacked by a pawn of the given color when it moves along its file, starting /// from the given square. -inline Bitboard pawn_attack_span(Color c, Square s) { +constexpr Bitboard pawn_attack_span(Color c, Square s) { return forward_ranks_bb(c, s) & adjacent_files_bb(s); } @@ -255,7 +255,7 @@ inline Bitboard pawn_attack_span(Color c, Square s) { /// passed_pawn_span() returns a bitboard which can be used to test if a pawn of /// the given color and on the given square is a passed pawn. -inline Bitboard passed_pawn_span(Color c, Square s) { +constexpr Bitboard passed_pawn_span(Color c, Square s) { return pawn_attack_span(c, s) | forward_file_bb(c, s); } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bb1724a4..6f2dd69b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -719,9 +719,9 @@ namespace { } - // Evaluation::winnable() adjusts the mg and eg score components based on the - // known attacking/defending status of the players. A single value is derived - // by interpolation from the mg and eg values and returned. + // Evaluation::winnable() adjusts the midgame and endgame score components, based on + // the known attacking/defending status of the players. The final value is derived + // by interpolation from the midgame and endgame values. template Value Evaluation::winnable(Score score) const { @@ -764,7 +764,7 @@ namespace { Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; int sf = me->scale_factor(pos, strongSide); - // If scale is not already specific, scale down the endgame via general heuristics + // If scale factor is not already specific, scale down via general heuristics if (sf == SCALE_FACTOR_NORMAL) { if (pos.opposite_bishops()) @@ -779,7 +779,7 @@ namespace { && pos.non_pawn_material(BLACK) == RookValueMg && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) - && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) + && (attackedBy[~strongSide][KING] & pos.pieces(~strongSide, PAWN))) sf = 36; else if (pos.count() == 1) sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) diff --git a/src/search.cpp b/src/search.cpp index 1610c206..720a9100 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -263,10 +263,10 @@ void MainThread::search() { Thread* bestThread = this; - if (int(Options["MultiPV"]) == 1 && - !Limits.depth && - !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) && - rootMoves[0].pv[0] != MOVE_NONE) + if ( int(Options["MultiPV"]) == 1 + && !Limits.depth + && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) + && rootMoves[0].pv[0] != MOVE_NONE) bestThread = Threads.get_best_thread(); bestPreviousScore = bestThread->rootMoves[0].score; @@ -670,7 +670,11 @@ namespace { ttPv = PvNode || (ttHit && tte->is_pv()); formerPv = ttPv && !PvNode; - if (ttPv && depth > 12 && ss->ply - 1 < MAX_LPH && !priorCapture && is_ok((ss-1)->currentMove)) + if ( ttPv + && depth > 12 + && ss->ply - 1 < MAX_LPH + && !priorCapture + && is_ok((ss-1)->currentMove)) thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5); // thisThread->ttHitAverage can be used to approximate the running average of ttHit From 5e91c5dcc8066e9f346a10010ddce70f2d317ef6 Mon Sep 17 00:00:00 2001 From: Vizvezdenec Date: Sat, 11 Jul 2020 00:06:55 +0300 Subject: [PATCH 17/18] Maximize usage of transposition table in probcut Probcut is a heuristic that wasn't changed a lot in past years, all attempts to change it using information / writing info to transposition table failed. This patch has a number of differences that can be summarized as follows: * For TT write/read we use depth - 3. Because probcut search is depth - 4 but we actually do the move prior to it so effectively we do depth - 3 search; * In any case of depth of eval from transposition table being >= depth - 3 we either produce cutoff or refuse to even do probcut search, this is allowing us to write info of probcut to transposition table because we know that we wouldn't be overwriting some deeper data with our depth - 3 search - this is an important aspect of this patch; * For some not really known reason this patch completely ignores tte->bound() - which was the case for previous patch that made probcut interact with TT, maybe 2) is the reason, although it's unproven. A first version of this patch passed STC and LTC passed STC https://tests.stockfishchess.org/tests/view/5f05908a59f6f03532894613 LLR: 2.95 (-2.94,2.94) {-0.50,1.50} Total: 95776 W: 18300 L: 17973 D: 59503 Ptnml(0-2): 1646, 10944, 22377, 11279, 1642 passed LTC https://tests.stockfishchess.org/tests/view/5f06b54059f6f035328946bb LLR: 2.94 (-2.94,2.94) {0.25,1.75} Total: 57128 W: 7266 L: 6938 D: 42924 Ptnml(0-2): 372, 5163, 17217, 5389, 423 However, an additional bugfix was needed to avoid checking a condition on ttMove if was not available. This passed non-regression bounds on top of the first version: at STC https://tests.stockfishchess.org/tests/view/5f080e5059f6f03532894766 LLR: 2.94 (-2.94,2.94) {-1.50,0.50} Total: 14096 W: 2800 L: 2628 D: 8668 Ptnml(0-2): 225, 1620, 3238, 1688, 277 at LTC https://tests.stockfishchess.org/tests/view/5f0836a559f6f0353289479c LLR: 2.95 (-2.94,2.94) {-1.50,0.50} Total: 25352 W: 3228 L: 3139 D: 18985 Ptnml(0-2): 175, 2350, 7549, 2415, 187 closes https://github.com/official-stockfish/Stockfish/pull/2804 Bench 4540940 --- src/search.cpp | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 720a9100..6cf2f90d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -596,7 +596,7 @@ namespace { Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue; + Value bestValue, value, ttValue, eval, maxValue, probcutBeta; bool ttHit, ttPv, formerPv, givesCheck, improving, didLMR, priorCapture; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture, singularQuietLMR; @@ -871,23 +871,33 @@ namespace { } } + probcutBeta = beta + 176 - 49 * improving; + // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode && depth > 4 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && !( ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE + && ttValue < probcutBeta)) { - Value raisedBeta = beta + 176 - 49 * improving; - assert(raisedBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &captureHistory); + if ( ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE + && ttValue >= probcutBeta + && ttMove + && pos.capture_or_promotion(ttMove)) + return probcutBeta; + + assert(probcutBeta < VALUE_INFINITE); + MovePicker mp(pos, ttMove, probcutBeta - ss->staticEval, &captureHistory); int probCutCount = 0; while ( (move = mp.next_move()) != MOVE_NONE - && probCutCount < 2 + 2 * cutNode - && !( move == ttMove - && tte->depth() >= depth - 4 - && ttValue < raisedBeta)) + && probCutCount < 2 + 2 * cutNode) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_or_promotion(move)); @@ -905,16 +915,21 @@ namespace { pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss+1, -raisedBeta, -raisedBeta+1); + value = -qsearch(pos, ss+1, -probcutBeta, -probcutBeta+1); // If the qsearch held, perform the regular search - if (value >= raisedBeta) - value = -search(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4, !cutNode); + if (value >= probcutBeta) + value = -search(pos, ss+1, -probcutBeta, -probcutBeta+1, depth - 4, !cutNode); pos.undo_move(move); - if (value >= raisedBeta) + if (value >= probcutBeta) + { + tte->save(posKey, value_to_tt(value, ss->ply), ttPv, + BOUND_LOWER, + depth - 3, move, ss->staticEval); return value; + } } } From 1f3bd968bb194a1f42af661cca9ec445c13978e8 Mon Sep 17 00:00:00 2001 From: SFisGOD Date: Wed, 8 Jul 2020 10:09:32 +0800 Subject: [PATCH 18/18] Introduce bad outpost penalty In some French games, Stockfish likes to bring the Knight to a bad outpost spot. This is evident in TCEC S18 Superfinal Game 63, where there is a Knight outpost on the queenside but is actually useless. Stockfish is effectively playing a piece down while holding ground against Leela's break on the kingside. This patch turns the +56 mg bonus for a Knight outpost into a -7 mg penalty if it satisfies the following conditions: * The outpost square is not on the CenterFiles (i.e. not on files C,D,E and F) * The knight is not attacking non pawn enemies. * The side where the outpost is located contains only few enemies, with a particular conditional_more_than_two() implementation Thank you to apospa...@gmail.com for bringing this to our attention and for providing insights. See https://groups.google.com/forum/?fromgroups=#!topic/fishcooking/dEXNzSIBgZU Reference game: https://tcec-chess.com/#div=sf&game=63&season=18 Passed STC: LLR: 2.93 (-2.94,2.94) {-0.50,1.50} Total: 6960 W: 1454 L: 1247 D: 4259 Ptnml(0-2): 115, 739, 1610, 856, 160 https://tests.stockfishchess.org/tests/view/5f08221059f6f0353289477e Passed LTC: LLR: 2.98 (-2.94,2.94) {0.25,1.75} Total: 21440 W: 2767 L: 2543 D: 16130 Ptnml(0-2): 122, 1904, 6462, 2092, 140 https://tests.stockfishchess.org/tests/view/5f0838ed59f6f035328947a2 various related tests show strong test results, but so far no generalizations or simplifications of conditional_more_than_two() are found. See PR for details. closes https://github.com/official-stockfish/Stockfish/pull/2803 Bench: 4366686 --- src/bitboard.h | 7 +++++++ src/evaluate.cpp | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bitboard.h b/src/bitboard.h index afeb40ec..15ec4153 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -130,6 +130,13 @@ constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } +/// Counts the occupation of the bitboard depending on the occupation of SQ_A1 +/// as in `b & (1ULL << SQ_A1) ? more_than_two(b) : more_than_one(b)` + +constexpr bool conditional_more_than_two(Bitboard b) { + return b & (b - 1) & (b - 2); +} + constexpr bool opposite_colors(Square s1, Square s2) { return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6f2dd69b..ca6ea5c4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -134,6 +134,7 @@ namespace { }; // Assorted bonuses and penalties + constexpr Score BadOutpost = S( -7, 36); constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopPawns = S( 3, 7); constexpr Score BishopXRayPawns = S( 4, 5); @@ -310,7 +311,13 @@ namespace { { // Bonus if piece is on an outpost square or can reach one bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them); - if (bb & s) + if ( Pt == KNIGHT + && bb & s & ~CenterFiles + && !(b & pos.pieces(Them) & ~pos.pieces(PAWN)) + && !conditional_more_than_two( + pos.pieces(Them) & ~pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide))) + score += BadOutpost; + else if (bb & s) score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) score += ReachableOutpost;