Patch analyzes field after SEE exchanges concluded with a recapture by
the opponent:
if opponent Queen/Rook/King results under attack after the exchanges, we
consider the move sharp and don't prune it.
Important note:
By accident I forgot to adjust 'occupied' when the king takes part in
the exchanges.
As result of this a move is considered sharp too, when opponent king
apparently can evade check by recapturing.
Surprisingly this seems contribute to patch's strength.
STC:
https://tests.stockfishchess.org/tests/view/
640b16132644b62c33947397
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 116400 W: 31239 L: 30817 D: 54344
Ptnml(0-2): 350, 12742, 31618, 13116, 374
LTC:
https://tests.stockfishchess.org/tests/view/
640c88092644b62c3394c1c5
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 177600 W: 47988 L: 47421 D: 82191
Ptnml(0-2): 62, 16905, 54317, 17436, 80
closes https://github.com/official-stockfish/Stockfish/pull/4453
bench:
5012145
stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm)
&& pos.pseudo_legal(ttm)
- && pos.see_ge(ttm, threshold));
+ && pos.see_ge(ttm, occupied, threshold));
}
/// MovePicker::score() assigns a numerical value to each move in a list, used
case GOOD_CAPTURE:
if (select<Next>([&](){
- return pos.see_ge(*cur, Value(-cur->value)) ?
+ return pos.see_ge(*cur, occupied, Value(-cur->value)) ?
// Move losing capture to endBadCaptures to be tried later
true : (*endBadCaptures++ = *cur, false); }))
return *(cur - 1);
return select<Best>([](){ return true; });
case PROBCUT:
- return select<Next>([&](){ return pos.see_ge(*cur, threshold); });
+ return select<Next>([&](){ return pos.see_ge(*cur, occupied, threshold); });
case QCAPTURE:
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
Value threshold;
Depth depth;
ExtMove moves[MAX_MOVES];
+ Bitboard occupied;
};
} // namespace Stockfish
/// SEE value of move is greater or equal to the given threshold. We'll use an
/// algorithm similar to alpha-beta pruning with a null window.
-bool Position::see_ge(Move m, Value threshold) const {
+bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const {
assert(is_ok(m));
return true;
assert(color_of(piece_on(from)) == sideToMove);
- Bitboard occupied = pieces() ^ from ^ to;
+ occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic
Color stm = sideToMove;
Bitboard attackers = attackers_to(to, occupied);
Bitboard stmAttackers, bb;
// the bitboard 'attackers' any X-ray attackers behind it.
if ((bb = stmAttackers & pieces(PAWN)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = PawnValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
}
else if ((bb = stmAttackers & pieces(KNIGHT)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = KnightValueMg - swap) < res)
break;
-
- occupied ^= least_significant_square_bb(bb);
}
else if ((bb = stmAttackers & pieces(BISHOP)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = BishopValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
}
else if ((bb = stmAttackers & pieces(ROOK)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = RookValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
}
else if ((bb = stmAttackers & pieces(QUEEN)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = QueenValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
}
void undo_null_move();
// Static Exchange Evaluation
- bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
+ bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const;
// Accessing hash keys
Key key() const;
+ captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha)
continue;
+ Bitboard occupied;
// SEE based pruning (~11 Elo)
- if (!pos.see_ge(move, Value(-206) * depth))
- continue;
+ if (!pos.see_ge(move, occupied, Value(-206) * depth))
+ {
+ if (depth < 2 - capture)
+ continue;
+ // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges
+ Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied;
+ Bitboard attacks = 0;
+ occupied |= to_sq(move);
+ while (leftEnemies && !attacks)
+ {
+ Square sq = pop_lsb(leftEnemies);
+ attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied;
+ // exclude Queen/Rook(s) which were already threatened before SEE
+ if (attacks && (sq != pos.square<KING>(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))))
+ attacks = 0;
+ }
+ if (!attacks)
+ continue;
+ }
}
else
{
lmrDepth = std::max(lmrDepth, 0);
+ Bitboard occupied;
// Prune moves with negative SEE (~4 Elo)
- if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth)))
+ if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth)))
continue;
}
}
prevSq);
int quietCheckEvasions = 0;
+ Bitboard occupied;
// Step 5. Loop through all pseudo-legal moves until no moves remain
// or a beta cutoff occurs.
continue;
}
- if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
+ if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1))
{
bestValue = std::max(bestValue, futilityBase);
continue;
continue;
// Do not search moves with bad enough SEE values (~5 Elo)
- if (!pos.see_ge(move, Value(-110)))
+ if (!pos.see_ge(move, occupied, Value(-110)))
continue;
}