1
0
Fork 0
stockfish/src/pawns.cpp

267 lines
9.5 KiB
C++
Raw Normal View History

2008-08-31 23:59:13 -06:00
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
2008-08-31 23:59:13 -06:00
Stockfish is free software: you can redistribute it and/or modify
2008-08-31 23:59:13 -06:00
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,
2008-08-31 23:59:13 -06:00
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.
2008-08-31 23:59:13 -06:00
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
2008-08-31 23:59:13 -06:00
#include <cassert>
#include "bitboard.h"
2008-08-31 23:59:13 -06:00
#include "pawns.h"
#include "position.h"
#include "thread.h"
2008-08-31 23:59:13 -06:00
namespace {
#define V Value
#define S(mg, eg) make_score(mg, eg)
2008-08-31 23:59:13 -06:00
// Pawn penalties
constexpr Score Backward = S( 9, 24);
constexpr Score Doubled = S(11, 56);
constexpr Score Isolated = S( 5, 15);
// Connected pawn bonus by opposed, phalanx, #support and rank
Score Connected[2][2][3][RANK_NB];
// Strength of pawn shelter for our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
{ V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) },
{ V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
{ V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) },
{ V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
};
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
// is behind our king.
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
{ V( 89), V(107), V(123), V(93), V(57), V( 45), V( 51) },
{ V( 44), V(-18), V(123), V(46), V(39), V( -7), V( 23) },
{ V( 4), V( 52), V(162), V(37), V( 7), V(-14), V( -2) },
{ V(-10), V(-14), V( 90), V(15), V( 2), V( -7), V(-16) }
};
// Danger of blocked enemy pawns storming our king, by rank
constexpr Value BlockedStorm[RANK_NB] =
{ V(0), V(0), V(66), V(6), V(5), V(1), V(15) };
#undef S
#undef V
template<Color Us>
Score evaluate(const Position& pos, Pawns::Entry* e) {
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
constexpr Direction Up = (Us == WHITE ? NORTH : SOUTH);
Bitboard b, neighbours, stoppers, doubled, supported, phalanx;
Bitboard lever, leverPush;
Square s;
bool opposed, backward;
Score score = SCORE_ZERO;
const Square* pl = pos.squares<PAWN>(Us);
Bitboard ourPawns = pos.pieces( Us, PAWN);
Bitboard theirPawns = pos.pieces(Them, PAWN);
e->passedPawns[Us] = e->pawnAttacksSpan[Us] = e->weakUnopposed[Us] = 0;
e->semiopenFiles[Us] = 0xFF;
e->kingSquares[Us] = SQ_NONE;
e->pawnAttacks[Us] = pawn_attacks_bb<Us>(ourPawns);
e->pawnsOnSquares[Us][BLACK] = popcount(ourPawns & DarkSquares);
e->pawnsOnSquares[Us][WHITE] = pos.count<PAWN>(Us) - e->pawnsOnSquares[Us][BLACK];
// Loop through all pawns of the current color and score each pawn
while ((s = *pl++) != SQ_NONE)
{
assert(pos.piece_on(s) == make_piece(Us, PAWN));
File f = file_of(s);
e->semiopenFiles[Us] &= ~(1 << f);
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// Flag the pawn
opposed = theirPawns & forward_file_bb(Us, s);
stoppers = theirPawns & passed_pawn_mask(Us, s);
lever = theirPawns & PawnAttacks[Us][s];
leverPush = theirPawns & PawnAttacks[Us][s + Up];
doubled = ourPawns & (s - Up);
neighbours = ourPawns & adjacent_files_bb(f);
phalanx = neighbours & rank_bb(s);
supported = neighbours & rank_bb(s - Up);
// A pawn is backward when it is behind all pawns of the same color
// on the adjacent files and cannot be safely advanced.
backward = !(ourPawns & pawn_attack_span(Them, s + Up))
&& (stoppers & (leverPush | (s + Up)));
// Passed pawns will be properly scored in evaluation because we need
// full attack info to evaluate them. Include also not passed pawns
// which could become passed after one or two pawn pushes when are
// not attacked more times than defended.
if ( !(stoppers ^ lever ^ leverPush)
Candidate Passed Pawn Include some not fully supported levers in the (candidate) passed pawns bitboard, if otherwise unblocked. Maybe levers are usually very short lived, and some inaccuracy in the lever balance for the definition of candidate passed pawns just triggers a deeper search. Here is a example of a case where the patch has an effect on the definition of candidate passers: White c5/e5 pawns, against Black d6 pawn. Let's say we want to test if e5 is a candidate passer. The previous master looks only at files d, e and f (which is already very good) and reject e5 as a candidate. However, the lever d6 is challenged by 2 pawns, so it should not fully count. Indirectly, this patch will view such case (and a few more) to be scored as candidates. STC http://tests.stockfishchess.org/tests/view/5abcd55d0ebc5902926cf1e1 LLR: 2.95 (-2.94,2.94) [0.00,4.00] Total: 16492 W: 3419 L: 3198 D: 9875 LTC http://tests.stockfishchess.org/tests/view/5abce1360ebc5902926cf1e6 LLR: 2.95 (-2.94,2.94) [0.00,4.00] Total: 21156 W: 3201 L: 2990 D: 14965 This was inspired by this test of Jerry Donald Watson, except the case of zero supporting pawns against two levers is excluded, and it seems that not excluding that case is bad, while excluding is it beneficial. See the following tests on fishtest: https://github.com/official-stockfish/Stockfish/pull/1519 http://tests.stockfishchess.org/tests/view/5abccd850ebc5902926cf1dd http://tests.stockfishchess.org/tests/view/5abcdd490ebc5902926cf1e4 Closes https://github.com/official-stockfish/Stockfish/pull/1521 Bench: 5568461 ---- Comments by Jerry Donald Watson: > My thinking as to why this works: > > The evaluation is either called in an interior node or in the qsearch. > The calls at the end of the qsearch are the more important as they > ultimately determine the scoring of each move, whereas the internal > values are mainly used for pruning decisions with a margin. Some strong > engines don't even call the eval at all nodes. Now the whole point of > the qsearch is to find quiet positions where captures do not change the > evaluation of the position with regards to the search bounds - i.e. if > there were good captures they would be tried.* So when a candidate lever > appears in the evaluation at the end of the qsearch, the qsearch has > guaranteed that it cannot just be captured, or if it can, this does not > take the score past the search bounds. Practically this may mean that > the side with the candidate lever has the turn, or perhaps the stopping > lever pawn is pinned, or that side is forced for other reasons to make > some other move (e.g. d6 can only take one of the pawns in the example > above). > > Hence granting the full score for only one lever defender makes some > sense, at least, to me. > > IMO this is also why huge bonuses for possible captures in the evaluation > (e.g. threat on queen and our turn), etc. don't tend to work. Such things > are best left to the search to figure out.
2018-03-29 05:59:35 -06:00
&& popcount(supported) >= popcount(lever) - 1
&& popcount(phalanx) >= popcount(leverPush))
e->passedPawns[Us] |= s;
else if ( stoppers == SquareBB[s + Up]
&& relative_rank(Us, s) >= RANK_5)
{
b = shift<Up>(supported) & ~theirPawns;
while (b)
if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)]))
e->passedPawns[Us] |= s;
}
// Score this pawn
if (supported | phalanx)
score += Connected[opposed][bool(phalanx)][popcount(supported)][relative_rank(Us, s)];
else if (!neighbours)
score -= Isolated, e->weakUnopposed[Us] += !opposed;
else if (backward)
score -= Backward, e->weakUnopposed[Us] += !opposed;
if (doubled && !supported)
score -= Doubled;
}
return score;
}
} // namespace
2008-08-31 23:59:13 -06:00
namespace Pawns {
2008-08-31 23:59:13 -06:00
/// Pawns::init() initializes some tables needed by evaluation. Instead of using
/// hard-coded tables, when makes sense, we prefer to calculate them with a formula
/// to reduce independent parameters and to allow easier tuning and better insight.
void init() {
static constexpr int Seed[RANK_NB] = { 0, 13, 24, 18, 65, 100, 175, 330 };
for (int opposed = 0; opposed <= 1; ++opposed)
for (int phalanx = 0; phalanx <= 1; ++phalanx)
for (int support = 0; support <= 2; ++support)
for (Rank r = RANK_2; r < RANK_8; ++r)
{
int v = 17 * support;
v += (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed;
Connected[opposed][phalanx][support][r] = make_score(v, v * (r - 2) / 4);
}
}
/// Pawns::probe() looks up the current position's pawns configuration in
/// the pawns hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't
/// have to recompute all when the same pawns configuration occurs again.
2008-08-31 23:59:13 -06:00
Entry* probe(const Position& pos) {
Key key = pos.pawn_key();
Entry* e = pos.this_thread()->pawnsTable[key];
2008-08-31 23:59:13 -06:00
if (e->key == key)
return e;
2008-08-31 23:59:13 -06:00
e->key = key;
e->scores[WHITE] = evaluate<WHITE>(pos, e);
e->scores[BLACK] = evaluate<BLACK>(pos, e);
e->openFiles = popcount(e->semiopenFiles[WHITE] & e->semiopenFiles[BLACK]);
e->asymmetry = popcount( (e->passedPawns[WHITE] | e->passedPawns[BLACK])
| (e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK]));
return e;
2008-08-31 23:59:13 -06:00
}
/// Entry::evaluate_shelter() calculates the shelter bonus and the storm
/// penalty for a king, looking at the king file and the two closest files.
template<Color Us>
Value Entry::evaluate_shelter(const Position& pos, Square ksq) {
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
constexpr Direction Down = (Us == WHITE ? SOUTH : NORTH);
constexpr Bitboard BlockRanks = (Us == WHITE ? Rank1BB | Rank2BB : Rank8BB | Rank7BB);
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
Bitboard ourPawns = b & pos.pieces(Us);
Bitboard theirPawns = b & pos.pieces(Them);
Value safety = (shift<Down>(theirPawns) & (FileABB | FileHBB) & BlockRanks & ksq) ?
Value(374) : Value(5);
Use special rule for BlockedByKing Simplification: remove BlockedByKing from storm array and use a special rule. The BlockedByKing section in the storm array is substantially similar to the Unopposed section except for two extreme values V(-290), V(-274). Turns out removing BlockedByKing and using a special rule for these two values shows no Elo loss. All the other values in the BlockedByKing section are apparently irrelevant. BlockedByKing now falls under unopposed which (to me) is a bit more logical since there is no defending pawn on this file. Also, retuning the Unopposed section may be another improvement. GOOD) This is a simplification because the entire BlockedByKing section of the storm array goes away reducing a few lines of code (and less values to tune). This also brings clarity because the special rule is self documenting. BAD) It takes execution time to apply the special rule. This should be negli- gible because it is based on a template parameter and is boiled down to two bitwise AND's. STC: LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 33470 W: 6820 L: 6721 D: 19929 http://tests.stockfishchess.org/tests/view/5ae7b6e60ebc5926dba90e13 LTC: LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47627 W: 7045 L: 6963 D: 33619 http://tests.stockfishchess.org/tests/view/5ae859ff0ebc5926dba90e85 Closes https://github.com/official-stockfish/Stockfish/pull/1574 Bench: 5512000 ----------- How to continue after this patch? This patch may open the possibility to move the special rule to evaluate.cpp in the evaluate::king() function, where we could refine the rule using king danger information. For instance, with a king in H2 blocking an opponent pawn in H3, it may be critical to know that the opponent has no safe check in G2 before giving the bonus :-)
2018-05-01 15:50:23 -06:00
File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq)));
for (File f = File(center - 1); f <= File(center + 1); ++f)
{
b = ourPawns & file_bb(f);
int ourRank = b ? relative_rank(Us, backmost_sq(Us, b)) : 0;
b = theirPawns & file_bb(f);
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
int d = std::min(f, ~f);
safety += ShelterStrength[d][ourRank];
safety -= (ourRank && (ourRank == theirRank - 1)) ? BlockedStorm[theirRank]
: UnblockedStorm[d][theirRank];
}
return safety;
}
/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
/// when king square changes, which is about 20% of total king_safety() calls.
template<Color Us>
Score Entry::do_king_safety(const Position& pos, Square ksq) {
kingSquares[Us] = ksq;
castlingRights[Us] = pos.can_castle(Us);
int minKingPawnDistance = 0;
Bitboard pawns = pos.pieces(Us, PAWN);
if (pawns)
while (!(DistanceRingBB[ksq][++minKingPawnDistance] & pawns)) {}
Value bonus = evaluate_shelter<Us>(pos, ksq);
// If we can castle use the bonus after the castling if it is bigger
if (pos.can_castle(Us | KING_SIDE))
bonus = std::max(bonus, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)));
if (pos.can_castle(Us | QUEEN_SIDE))
bonus = std::max(bonus, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)));
return make_score(bonus, -16 * minKingPawnDistance);
}
// Explicit template instantiation
template Score Entry::do_king_safety<WHITE>(const Position& pos, Square ksq);
template Score Entry::do_king_safety<BLACK>(const Position& pos, Square ksq);
} // namespace Pawns