mirror of
https://github.com/sockspls/badfish
synced 2025-07-11 19:49:14 +00:00
Rewrite unstoppable pawns evaluation
Instead of current code, give a bonus according to the frontmost square among candidate + passed pawns. This is a big simplification that removes a lot of accurate code substituting it with a statistically based one using the common 'bonus' scheme, leaving to the search to sort out the details. Results are equivalent but code is much less and, as an added bonus, we now store candidates bitboard in pawns hash and allow this info to be used in evaluation. This paves the way to possible candidate pawns evaluations together with all the other pieces, as we do for passed. Patch passed short TC LLR: 2.96 (-2.94,2.94) [-1.50,4.50] Total: 16927 W: 3462 L: 3308 D: 10157 Then failed (quite quickly) at long TC LLR: -2.95 (-2.94,2.94) [0.00,6.00] Total: 8451 W: 1386 L: 1448 D: 5617 But when ran with a conclusive 40K fixed games at 60 secs it proved almost equivalent to original one. ELO: 1.08 +-2.0 (95%) LOS: 85.8% Total: 40000 W: 6739 L: 6615 D: 26646 bench: 3884003
This commit is contained in:
parent
21cbfafc03
commit
af750bd2ef
3 changed files with 22 additions and 157 deletions
170
src/evaluate.cpp
170
src/evaluate.cpp
|
@ -32,7 +32,7 @@
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum ExtendedPieceType { // Used for tracing
|
enum ExtendedPieceType { // Used for tracing
|
||||||
PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, UNSTOPPABLE, SPACE, TOTAL
|
PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Tracing {
|
namespace Tracing {
|
||||||
|
@ -175,6 +175,7 @@ namespace {
|
||||||
const Score MinorBehindPawn = make_score(16, 0);
|
const Score MinorBehindPawn = make_score(16, 0);
|
||||||
const Score UndefendedMinor = make_score(25, 10);
|
const Score UndefendedMinor = make_score(25, 10);
|
||||||
const Score TrappedRook = make_score(90, 0);
|
const Score TrappedRook = make_score(90, 0);
|
||||||
|
const Score Unstoppable = make_score( 0, 20);
|
||||||
|
|
||||||
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
|
// Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
|
||||||
// a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only
|
// a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only
|
||||||
|
@ -245,7 +246,7 @@ namespace {
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
int evaluate_space(const Position& pos, const EvalInfo& ei);
|
int evaluate_space(const Position& pos, const EvalInfo& ei);
|
||||||
|
|
||||||
Score evaluate_unstoppable_pawns(const Position& pos, const EvalInfo& ei);
|
Score evaluate_unstoppable_pawns(const Position& pos, Color us, const EvalInfo& ei);
|
||||||
|
|
||||||
Value interpolate(const Score& v, Phase ph, ScaleFactor sf);
|
Value interpolate(const Score& v, Phase ph, ScaleFactor sf);
|
||||||
Score apply_weight(Score v, Score w);
|
Score apply_weight(Score v, Score w);
|
||||||
|
@ -360,9 +361,10 @@ Value do_evaluate(const Position& pos, Value& margin) {
|
||||||
score += evaluate_passed_pawns<WHITE, Trace>(pos, ei)
|
score += evaluate_passed_pawns<WHITE, Trace>(pos, ei)
|
||||||
- evaluate_passed_pawns<BLACK, Trace>(pos, ei);
|
- evaluate_passed_pawns<BLACK, Trace>(pos, ei);
|
||||||
|
|
||||||
// If one side has only a king, check whether exists any unstoppable passed pawn
|
// If one side has only a king, score for potential unstoppable pawns
|
||||||
if (!pos.non_pawn_material(WHITE) || !pos.non_pawn_material(BLACK))
|
if (!pos.non_pawn_material(WHITE) || !pos.non_pawn_material(BLACK))
|
||||||
score += evaluate_unstoppable_pawns(pos, ei);
|
score += evaluate_unstoppable_pawns(pos, WHITE, ei)
|
||||||
|
- evaluate_unstoppable_pawns(pos, BLACK, ei);
|
||||||
|
|
||||||
// Evaluate space for both sides, only in middle-game.
|
// Evaluate space for both sides, only in middle-game.
|
||||||
if (ei.mi->space_weight())
|
if (ei.mi->space_weight())
|
||||||
|
@ -405,7 +407,6 @@ Value do_evaluate(const Position& pos, Value& margin) {
|
||||||
Tracing::add(PST, pos.psq_score());
|
Tracing::add(PST, pos.psq_score());
|
||||||
Tracing::add(IMBALANCE, ei.mi->material_value());
|
Tracing::add(IMBALANCE, ei.mi->material_value());
|
||||||
Tracing::add(PAWN, ei.pi->pawns_value());
|
Tracing::add(PAWN, ei.pi->pawns_value());
|
||||||
Tracing::add(UNSTOPPABLE, evaluate_unstoppable_pawns(pos, ei));
|
|
||||||
Score w = ei.mi->space_weight() * evaluate_space<WHITE>(pos, ei);
|
Score w = ei.mi->space_weight() * evaluate_space<WHITE>(pos, ei);
|
||||||
Score b = ei.mi->space_weight() * evaluate_space<BLACK>(pos, ei);
|
Score b = ei.mi->space_weight() * evaluate_space<BLACK>(pos, ei);
|
||||||
Tracing::add(SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space]));
|
Tracing::add(SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space]));
|
||||||
|
@ -894,160 +895,18 @@ Value do_evaluate(const Position& pos, Value& margin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// evaluate_unstoppable_pawns() evaluates the unstoppable passed pawns for both sides, this is quite
|
// evaluate_unstoppable_pawns() scores the most advanced among the passed and
|
||||||
// conservative and returns a winning score only when we are very sure that the pawn is winning.
|
// candidate pawns. In case opponent has no pieces but pawns, this is somewhat
|
||||||
|
// related to the possibility pawns are unstoppable.
|
||||||
|
|
||||||
Score evaluate_unstoppable_pawns(const Position& pos, const EvalInfo& ei) {
|
Score evaluate_unstoppable_pawns(const Position& pos, Color us, const EvalInfo& ei) {
|
||||||
|
|
||||||
Bitboard b, b2, blockers, supporters, queeningPath, candidates;
|
Bitboard b = ei.pi->passed_pawns(us) | ei.pi->candidate_pawns(us);
|
||||||
Square s, blockSq, queeningSquare;
|
|
||||||
Color c, winnerSide, loserSide;
|
|
||||||
bool pathDefended, opposed;
|
|
||||||
int pliesToGo, movesToGo, oppMovesToGo, sacptg, blockersCount, minKingDist, kingptg, d;
|
|
||||||
int pliesToQueen[] = { 256, 256 };
|
|
||||||
|
|
||||||
// Step 1. Hunt for unstoppable passed pawns. If we find at least one,
|
if (!b || pos.non_pawn_material(~us))
|
||||||
// record how many plies are required for promotion.
|
|
||||||
for (c = WHITE; c <= BLACK; ++c)
|
|
||||||
{
|
|
||||||
// Skip if other side has non-pawn pieces
|
|
||||||
if (pos.non_pawn_material(~c))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
b = ei.pi->passed_pawns(c);
|
|
||||||
|
|
||||||
while (b)
|
|
||||||
{
|
|
||||||
s = pop_lsb(&b);
|
|
||||||
queeningSquare = relative_square(c, file_of(s) | RANK_8);
|
|
||||||
queeningPath = forward_bb(c, s);
|
|
||||||
|
|
||||||
// Compute plies to queening and check direct advancement
|
|
||||||
movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(c, s) == RANK_2);
|
|
||||||
oppMovesToGo = square_distance(pos.king_square(~c), queeningSquare) - int(c != pos.side_to_move());
|
|
||||||
pathDefended = ((ei.attackedBy[c][ALL_PIECES] & queeningPath) == queeningPath);
|
|
||||||
|
|
||||||
if (movesToGo >= oppMovesToGo && !pathDefended)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Opponent king cannot block because path is defended and position
|
|
||||||
// is not in check. So only friendly pieces can be blockers.
|
|
||||||
assert(!pos.checkers());
|
|
||||||
assert((queeningPath & pos.pieces()) == (queeningPath & pos.pieces(c)));
|
|
||||||
|
|
||||||
// Add moves needed to free the path from friendly pieces and retest condition
|
|
||||||
movesToGo += popcount<Max15>(queeningPath & pos.pieces(c));
|
|
||||||
|
|
||||||
if (movesToGo >= oppMovesToGo && !pathDefended)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
pliesToGo = 2 * movesToGo - int(c == pos.side_to_move());
|
|
||||||
pliesToQueen[c] = std::min(pliesToQueen[c], pliesToGo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2. If either side cannot promote at least three plies before the other side then situation
|
|
||||||
// becomes too complex and we give up. Otherwise we determine the possibly "winning side"
|
|
||||||
if (abs(pliesToQueen[WHITE] - pliesToQueen[BLACK]) < 3)
|
|
||||||
return SCORE_ZERO;
|
return SCORE_ZERO;
|
||||||
|
|
||||||
winnerSide = (pliesToQueen[WHITE] < pliesToQueen[BLACK] ? WHITE : BLACK);
|
return Unstoppable * int(relative_rank(WHITE, frontmost_sq(us, b)));
|
||||||
loserSide = ~winnerSide;
|
|
||||||
|
|
||||||
// Step 3. Can the losing side possibly create a new passed pawn and thus prevent the loss?
|
|
||||||
b = candidates = pos.pieces(loserSide, PAWN);
|
|
||||||
|
|
||||||
while (b)
|
|
||||||
{
|
|
||||||
s = pop_lsb(&b);
|
|
||||||
|
|
||||||
// Compute plies from queening
|
|
||||||
queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
|
|
||||||
movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
|
|
||||||
pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
|
|
||||||
|
|
||||||
// Check if (without even considering any obstacles) we're too far away or doubled
|
|
||||||
if ( pliesToQueen[winnerSide] + 3 <= pliesToGo
|
|
||||||
|| (forward_bb(loserSide, s) & pos.pieces(loserSide, PAWN)))
|
|
||||||
candidates ^= s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any candidate is already a passed pawn it _may_ promote in time. We give up.
|
|
||||||
if (candidates & ei.pi->passed_pawns(loserSide))
|
|
||||||
return SCORE_ZERO;
|
|
||||||
|
|
||||||
// Step 4. Check new passed pawn creation through king capturing and pawn sacrifices
|
|
||||||
b = candidates;
|
|
||||||
|
|
||||||
while (b)
|
|
||||||
{
|
|
||||||
s = pop_lsb(&b);
|
|
||||||
sacptg = blockersCount = 0;
|
|
||||||
minKingDist = kingptg = 256;
|
|
||||||
|
|
||||||
// Compute plies from queening
|
|
||||||
queeningSquare = relative_square(loserSide, file_of(s) | RANK_8);
|
|
||||||
movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(loserSide, s) == RANK_2);
|
|
||||||
pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move());
|
|
||||||
|
|
||||||
// Generate list of blocking pawns and supporters
|
|
||||||
supporters = adjacent_files_bb(file_of(s)) & candidates;
|
|
||||||
opposed = forward_bb(loserSide, s) & pos.pieces(winnerSide, PAWN);
|
|
||||||
blockers = passed_pawn_mask(loserSide, s) & pos.pieces(winnerSide, PAWN);
|
|
||||||
|
|
||||||
assert(blockers);
|
|
||||||
|
|
||||||
// How many plies does it take to remove all the blocking pawns?
|
|
||||||
while (blockers)
|
|
||||||
{
|
|
||||||
blockSq = pop_lsb(&blockers);
|
|
||||||
movesToGo = 256;
|
|
||||||
|
|
||||||
// Check pawns that can give support to overcome obstacle, for instance
|
|
||||||
// black pawns: a4, b4 white: b2 then pawn in b4 is giving support.
|
|
||||||
if (!opposed)
|
|
||||||
{
|
|
||||||
b2 = supporters & in_front_bb(winnerSide, rank_of(blockSq + pawn_push(winnerSide)));
|
|
||||||
|
|
||||||
if (b2)
|
|
||||||
{
|
|
||||||
d = square_distance(blockSq, backmost_sq(winnerSide, b2)) - 2;
|
|
||||||
movesToGo = std::min(movesToGo, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check pawns that can be sacrificed against the blocking pawn
|
|
||||||
b2 = pawn_attack_span(winnerSide, blockSq) & candidates & ~SquareBB[s];
|
|
||||||
|
|
||||||
if (b2)
|
|
||||||
{
|
|
||||||
d = square_distance(blockSq, backmost_sq(winnerSide, b2)) - 2;
|
|
||||||
movesToGo = std::min(movesToGo, d);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If obstacle can be destroyed with an immediate pawn exchange / sacrifice,
|
|
||||||
// it's not a real obstacle and we have nothing to add to pliesToGo.
|
|
||||||
if (movesToGo <= 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Plies needed to sacrifice against all the blocking pawns
|
|
||||||
sacptg += movesToGo * 2;
|
|
||||||
blockersCount++;
|
|
||||||
|
|
||||||
// Plies needed for the king to capture all the blocking pawns
|
|
||||||
d = square_distance(pos.king_square(loserSide), blockSq);
|
|
||||||
minKingDist = std::min(minKingDist, d);
|
|
||||||
kingptg = (minKingDist + blockersCount) * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if pawn sacrifice or king capture plan _may_ save the day
|
|
||||||
if (pliesToQueen[winnerSide] + 3 > pliesToGo + std::min(kingptg, sacptg))
|
|
||||||
return SCORE_ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Winning pawn is unstoppable and will promote as first, return big score
|
|
||||||
Score score = make_score(0, (Value) 1280 - 32 * pliesToQueen[winnerSide]);
|
|
||||||
return winnerSide == WHITE ? score : -score;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1132,7 +991,7 @@ Value do_evaluate(const Position& pos, Value& margin) {
|
||||||
Score bScore = scores[BLACK][idx];
|
Score bScore = scores[BLACK][idx];
|
||||||
|
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case PST: case IMBALANCE: case PAWN: case UNSTOPPABLE: case TOTAL:
|
case PST: case IMBALANCE: case PAWN: case TOTAL:
|
||||||
stream << std::setw(20) << name << " | --- --- | --- --- | "
|
stream << std::setw(20) << name << " | --- --- | --- --- | "
|
||||||
<< std::setw(6) << to_cp(mg_value(wScore)) << " "
|
<< std::setw(6) << to_cp(mg_value(wScore)) << " "
|
||||||
<< std::setw(6) << to_cp(eg_value(wScore)) << " \n";
|
<< std::setw(6) << to_cp(eg_value(wScore)) << " \n";
|
||||||
|
@ -1176,7 +1035,6 @@ Value do_evaluate(const Position& pos, Value& margin) {
|
||||||
row("King safety", KING);
|
row("King safety", KING);
|
||||||
row("Threats", THREAT);
|
row("Threats", THREAT);
|
||||||
row("Passed pawns", PASSED);
|
row("Passed pawns", PASSED);
|
||||||
row("Unstoppable pawns", UNSTOPPABLE);
|
|
||||||
row("Space", SPACE);
|
row("Space", SPACE);
|
||||||
|
|
||||||
stream << "---------------------+-------------+-------------+---------------\n";
|
stream << "---------------------+-------------+-------------+---------------\n";
|
||||||
|
|
|
@ -99,7 +99,7 @@ namespace {
|
||||||
Bitboard ourPawns = pos.pieces(Us, PAWN);
|
Bitboard ourPawns = pos.pieces(Us, PAWN);
|
||||||
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
||||||
|
|
||||||
e->passedPawns[Us] = 0;
|
e->passedPawns[Us] = e->candidatePawns[Us] = 0;
|
||||||
e->kingSquares[Us] = SQ_NONE;
|
e->kingSquares[Us] = SQ_NONE;
|
||||||
e->semiopenFiles[Us] = 0xFF;
|
e->semiopenFiles[Us] = 0xFF;
|
||||||
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
|
e->pawnAttacks[Us] = shift_bb<Right>(ourPawns) | shift_bb<Left>(ourPawns);
|
||||||
|
@ -179,7 +179,12 @@ namespace {
|
||||||
value += ChainMember[f];
|
value += ChainMember[f];
|
||||||
|
|
||||||
if (candidate)
|
if (candidate)
|
||||||
|
{
|
||||||
value += CandidatePassed[relative_rank(Us, s)];
|
value += CandidatePassed[relative_rank(Us, s)];
|
||||||
|
|
||||||
|
if (!doubled)
|
||||||
|
e->candidatePawns[Us] |= s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -37,6 +37,7 @@ struct Entry {
|
||||||
Score pawns_value() const { return value; }
|
Score pawns_value() const { return value; }
|
||||||
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
|
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
|
||||||
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
||||||
|
Bitboard candidate_pawns(Color c) const { return candidatePawns[c]; }
|
||||||
int pawns_on_same_color_squares(Color c, Square s) const { return pawnsOnSquares[c][!!(DarkSquares & s)]; }
|
int pawns_on_same_color_squares(Color c, Square s) const { return pawnsOnSquares[c][!!(DarkSquares & s)]; }
|
||||||
int semiopen(Color c, File f) const { return semiopenFiles[c] & (1 << int(f)); }
|
int semiopen(Color c, File f) const { return semiopenFiles[c] & (1 << int(f)); }
|
||||||
int semiopen_on_side(Color c, File f, bool left) const {
|
int semiopen_on_side(Color c, File f, bool left) const {
|
||||||
|
@ -59,6 +60,7 @@ struct Entry {
|
||||||
|
|
||||||
Key key;
|
Key key;
|
||||||
Bitboard passedPawns[COLOR_NB];
|
Bitboard passedPawns[COLOR_NB];
|
||||||
|
Bitboard candidatePawns[COLOR_NB];
|
||||||
Bitboard pawnAttacks[COLOR_NB];
|
Bitboard pawnAttacks[COLOR_NB];
|
||||||
Square kingSquares[COLOR_NB];
|
Square kingSquares[COLOR_NB];
|
||||||
int minKPdistance[COLOR_NB];
|
int minKPdistance[COLOR_NB];
|
||||||
|
|
Loading…
Add table
Reference in a new issue