mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Currently SF’s quiescence search like most alpha-beta based engines
doesn’t verify for stalemate because doing it each leaf position is to
expensive and costs elo. However in certain positions this creates a
blindspot for SF, not recognizing soon enough that the opponent can
reach a stalemate by sacrifycing his last mobile heavy piece(s). This
tactical motif & it’s measure are similar to zugzwang & verification
search: the measure itself does not gain elo, but prevents SF from
loosing/drawing games in an awkward way.
The fix consists of 3 measures:
1. Make qsearch verify for stalemate on transitions to pure KP-material
for the side to move with our last Rook/Queen just been captured. In
fact this is the scenario where stalemate happens with highest
frequency. The stalemate-verification itself is optimized by merely
checking for pawn pushes & king mobility (captures were already
tried by qssearch)
2. Another culprit for the issue figured out to be SEE based pruning for
checks in step 14. Here often the move forcing the stalemate (or
forcing the opponent to not retake) get pruned away and it need to
much time to reach enough depth. To encounter this we verify
following conditions:
- a) side to move is happy with a draw (alpha < 0)
- b) we are about to sacrify our last heavy & unique mobile piece in
this position.
- c) this piece doesn’t move away from our kingring giving the king a
new square to move.
When all 3 conditions meet we don’t prune the move, because there
is a good chance that capturing the piece means stalemate.
3. Store terminal nodes (mates & stalemates) in TT with higher depth
than searched depth. This prevents SF from:
- reanalyzing the node (=trying to generate legal moves) in vain at
each iterative deepening step.
- overwriting an already correct draw-evaluation from a previous shallow
normal search by a qsearch which doesn’t recognize stalemate and might
store a verry erratic evaluation.
This is due to the 4 constant in the TT-overwrite condition: d -
DEPTH_ENTRY_OFFSET + 2 * pv > depth8 – 4 which allows qs to override
entries made by normal searches with depth <=4.
This 3hrd measure however is not essential for fixing the issue, but
tests (one of vdv & one of mine) seem to suggest that this measure
brings some small benefit.
Another other position where SF benefits from this fix is for instance
Position FEN 8/8/8/1B6/6p1/8/3K1Ppp/3N2kr w - - 0 1 bm f4 +M9
P.S.: Also this issue higly depends on the used net, how good the net is
at evaluate such mobility restricted positions. SF16 was pretty good in
solving 2rr4/5pBk/PqP3p1/1N3pPp/1PQ1bP1P/8/3R4/R4K2 b - - 0 40 bm Rxc6
(< 1 second) while SF16_1 with introduction of the dual net needs about
1,5 minutes and SF17.1 requires 3 minutes to find the drawing move Rxc6.
P.S.2: Using more threads produces indeterminism & using high hash
pressure makes SF reevaluate explored positions more often which makes
it more likely to solve the position. To have stable meaningful results
I tested therfore with one single thread and low hash pressure.
Preliminary LTC test at 30k games
https://tests.stockfishchess.org/tests/view/67ece7a931d7cf8afdc44e18 Elo: 0.04 ± 2.0 (95%) LOS: 51.7%
Total: 24416 W: 6226 L: 6223 D: 11967
Ptnml(0-2): 12, 2497, 7185, 2504, 10
nElo: 0.09 ± 4.4 (95%) PairsRatio: 1.00
Passed LTC no-regression sprt
https://tests.stockfishchess.org/tests/view/67ee8e4631d7cf8afdc452fb
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 401556 W: 101612 L: 101776 D: 198168
Ptnml(0-2): 152, 42241, 116170, 42049, 166
closes https://github.com/official-stockfish/Stockfish/pull/5983
fixes https://github.com/official-stockfish/Stockfish/issues/5899
Bench: 1721673
87 lines
2.9 KiB
C++
87 lines
2.9 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef MOVEPICK_H_INCLUDED
|
|
#define MOVEPICK_H_INCLUDED
|
|
|
|
#include <cstddef>
|
|
|
|
#include "history.h"
|
|
#include "movegen.h"
|
|
#include "types.h"
|
|
|
|
namespace Stockfish {
|
|
|
|
class Position;
|
|
|
|
template<typename T, std::size_t MaxSize>
|
|
class ValueList;
|
|
|
|
// The MovePicker class is used to pick one pseudo-legal move at a time from the
|
|
// current position. The most important method is next_move(), which emits one
|
|
// new pseudo-legal move on every call, until there are no moves left, when
|
|
// Move::none() is returned. In order to improve the efficiency of the alpha-beta
|
|
// algorithm, MovePicker attempts to return the moves which are most likely to get
|
|
// a cut-off first.
|
|
class MovePicker {
|
|
|
|
public:
|
|
MovePicker(const MovePicker&) = delete;
|
|
MovePicker& operator=(const MovePicker&) = delete;
|
|
MovePicker(const Position&,
|
|
Move,
|
|
Depth,
|
|
const ButterflyHistory*,
|
|
const LowPlyHistory*,
|
|
const CapturePieceToHistory*,
|
|
const PieceToHistory**,
|
|
const PawnHistory*,
|
|
int);
|
|
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
|
|
Move next_move();
|
|
void skip_quiet_moves();
|
|
bool otherPieceTypesMobile(PieceType pt, ValueList<Move, 32>& capturesSearched);
|
|
|
|
private:
|
|
template<typename Pred>
|
|
Move select(Pred);
|
|
template<GenType>
|
|
void score();
|
|
ExtMove* begin() { return cur; }
|
|
ExtMove* end() { return endMoves; }
|
|
|
|
const Position& pos;
|
|
const ButterflyHistory* mainHistory;
|
|
const LowPlyHistory* lowPlyHistory;
|
|
const CapturePieceToHistory* captureHistory;
|
|
const PieceToHistory** continuationHistory;
|
|
const PawnHistory* pawnHistory;
|
|
Move ttMove;
|
|
ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets;
|
|
int stage;
|
|
int threshold;
|
|
Depth depth;
|
|
int ply;
|
|
bool skipQuiets = false;
|
|
ExtMove moves[MAX_MOVES];
|
|
};
|
|
|
|
} // namespace Stockfish
|
|
|
|
#endif // #ifndef MOVEPICK_H_INCLUDED
|