namespace {
enum ExtendedPieceType { // Used for tracing
- PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, UNSTOPPABLE, SPACE, TOTAL
+ PST = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL
};
namespace Tracing {
const Score MinorBehindPawn = make_score(16, 0);
const Score UndefendedMinor = make_score(25, 10);
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
// a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only
// based on how many squares inside this area are safe and available for
// friendly minor pieces.
const Bitboard SpaceMask[] = {
- (1ULL << SQ_C2) | (1ULL << SQ_D2) | (1ULL << SQ_E2) | (1ULL << SQ_F2) |
- (1ULL << SQ_C3) | (1ULL << SQ_D3) | (1ULL << SQ_E3) | (1ULL << SQ_F3) |
- (1ULL << SQ_C4) | (1ULL << SQ_D4) | (1ULL << SQ_E4) | (1ULL << SQ_F4),
- (1ULL << SQ_C7) | (1ULL << SQ_D7) | (1ULL << SQ_E7) | (1ULL << SQ_F7) |
- (1ULL << SQ_C6) | (1ULL << SQ_D6) | (1ULL << SQ_E6) | (1ULL << SQ_F6) |
- (1ULL << SQ_C5) | (1ULL << SQ_D5) | (1ULL << SQ_E5) | (1ULL << SQ_F5)
+ (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB),
+ (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB)
};
// King danger constants and variables. The king danger scores are taken
template<Color Us>
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);
Score apply_weight(Score v, Score w);
const int MaxSlope = 30;
const int Peak = 1280;
- for (int t = 0, i = 1; i < 100; i++)
+ for (int t = 0, i = 1; i < 100; ++i)
{
t = std::min(Peak, std::min(int(0.4 * i * i), t + MaxSlope));
score += evaluate_passed_pawns<WHITE, 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))
- 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.
if (ei.mi->space_weight())
Tracing::add(PST, pos.psq_score());
Tracing::add(IMBALANCE, ei.mi->material_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 b = ei.mi->space_weight() * evaluate_space<BLACK>(pos, ei);
Tracing::add(SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space]));
if (b & ei.kingRing[Them])
{
- ei.kingAttackersCount[Us]++;
+ ++ei.kingAttackersCount[Us];
ei.kingAttackersWeight[Us] += KingAttackWeights[Piece];
Bitboard bb = (b & ei.attackedBy[Them][KING]);
if (bb)
const enum Piece P = make_piece(Us, PAWN);
Square d = pawn_push(Us) + (file_of(s) == FILE_A ? DELTA_E : DELTA_W);
if (pos.piece_on(s + d) == P)
- score -= !pos.is_empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4
- : pos.piece_on(s + d + d) == P ? TrappedBishopA1H1 * 2
- : TrappedBishopA1H1;
+ score -= !pos.empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4
+ : pos.piece_on(s + d + d) == P ? TrappedBishopA1H1 * 2
+ : TrappedBishopA1H1;
}
}
// type of attacking piece, from knights to queens. Kings are not
// considered because are already handled in king evaluation.
if (weakEnemies)
- for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++)
+ for (PieceType pt1 = KNIGHT; pt1 < KING; ++pt1)
{
b = ei.attackedBy[Us][pt1] & weakEnemies;
if (b)
- for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
+ for (PieceType pt2 = PAWN; pt2 < KING; ++pt2)
if (b & pos.pieces(pt2))
score += Threat[pt1][pt2];
}
{
Square s = pop_lsb(&b);
- assert(pos.pawn_is_passed(Us, s));
+ assert(pos.pawn_passed(Us, s));
int r = int(relative_rank(Us, s) - RANK_2);
int rr = r * (r - 1);
Square blockSq = s + pawn_push(Us);
// Adjust bonus based on kings proximity
- ebonus += Value(square_distance(pos.king_square(Them), blockSq) * 5 * rr);
- ebonus -= Value(square_distance(pos.king_square(Us), blockSq) * 2 * rr);
+ ebonus += Value(square_distance(pos.king_square(Them), blockSq) * 5 * rr)
+ - Value(square_distance(pos.king_square(Us ), blockSq) * 2 * rr);
// If blockSq is not the queening square then consider also a second push
- if (rank_of(blockSq) != (Us == WHITE ? RANK_8 : RANK_1))
+ if (relative_rank(Us, blockSq) != RANK_8)
ebonus -= Value(square_distance(pos.king_square(Us), blockSq + pawn_push(Us)) * rr);
// If the pawn is free to advance, increase bonus
- if (pos.is_empty(blockSq))
+ if (pos.empty(blockSq))
{
squaresToQueen = forward_bb(Us, s);
{
if (pos.non_pawn_material(Them) <= KnightValueMg)
ebonus += ebonus / 4;
+
else if (pos.pieces(Them, ROOK, QUEEN))
ebonus -= ebonus / 4;
}
+
+ // Increase the bonus if we have more non-pawn pieces
+ if (pos.count<ALL_PIECES>( Us) - pos.count<PAWN>( Us) >
+ pos.count<ALL_PIECES>(Them) - pos.count<PAWN>(Them))
+ ebonus += ebonus / 4;
+
score += make_score(mbonus, ebonus);
}
}
- // evaluate_unstoppable_pawns() evaluates the unstoppable passed pawns for both sides, this is quite
- // conservative and returns a winning score only when we are very sure that the pawn is winning.
-
- Score evaluate_unstoppable_pawns(const Position& pos, const EvalInfo& ei) {
-
- Bitboard b, b2, blockers, supporters, queeningPath, candidates;
- 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,
- // 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);
+ // evaluate_unstoppable_pawns() scores the most advanced among the passed and
+ // candidate pawns. In case opponent has no pieces but pawns, this is somewhat
+ // related to the possibility pawns are unstoppable.
- // 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);
+ Score evaluate_unstoppable_pawns(const Position& pos, Color us, const EvalInfo& ei) {
- if (movesToGo >= oppMovesToGo && !pathDefended)
- continue;
+ Bitboard b = ei.pi->passed_pawns(us) | ei.pi->candidate_pawns(us);
- // 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)
+ if (!b || pos.non_pawn_material(~us))
return SCORE_ZERO;
- winnerSide = (pliesToQueen[WHITE] < pliesToQueen[BLACK] ? WHITE : BLACK);
- 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)));
-
- while (b2) // This while-loop could be replaced with LSB/MSB (depending on color)
- {
- d = square_distance(blockSq, pop_lsb(&b2)) - 2;
- movesToGo = std::min(movesToGo, d);
- }
- }
-
- // Check pawns that can be sacrificed against the blocking pawn
- b2 = pawn_attack_span(winnerSide, blockSq) & candidates & ~(1ULL << s);
-
- while (b2) // This while-loop could be replaced with LSB/MSB (depending on color)
- {
- d = square_distance(blockSq, pop_lsb(&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 plan _may_ save the day
- if (pliesToQueen[winnerSide] + 3 > pliesToGo + sacptg)
- return SCORE_ZERO;
-
- // Check if king capture plan _may_ save the day (contains some false positives)
- if (pliesToQueen[winnerSide] + 3 > pliesToGo + kingptg)
- 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;
+ return Unstoppable * int(relative_rank(us, frontmost_sq(us, b)));
}
Score bScore = scores[BLACK][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 << " | --- --- | --- --- | "
<< std::setw(6) << to_cp(mg_value(wScore)) << " "
<< std::setw(6) << to_cp(eg_value(wScore)) << " \n";
row("King safety", KING);
row("Threats", THREAT);
row("Passed pawns", PASSED);
- row("Unstoppable pawns", UNSTOPPABLE);
row("Space", SPACE);
stream << "---------------------+-------------+-------------+---------------\n";