Score Evaluation<T>::king() const {
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
- : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
+ Rank r = relative_rank(Us, std::min(Rank((pos.max_rank() - 1) / 2 + 1), pos.max_rank()), pos.max_rank());
+ Bitboard Camp = AllSquares ^ forward_ranks_bb(Us, r);
+
+ if (!pos.count<KING>(Us) || !pos.checking_permitted())
+ return SCORE_ZERO;
- Bitboard weak, b1, b2, safe, unsafeChecks = 0;
+ Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0;
- Bitboard rookChecks, queenChecks, bishopChecks, knightChecks;
+ Bitboard queenChecks, knightChecks, pawnChecks, otherChecks;
int kingDanger = 0;
const Square ksq = pos.square<KING>(Us);
b1 = attacks_bb<ROOK >(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
- // Enemy rooks checks
- rookChecks = b1 & safe & attackedBy[Them][ROOK];
+ std::function <Bitboard (Color, PieceType)> get_attacks = [this](Color c, PieceType pt) {
+ return attackedBy[c][pt] | (pos.captures_to_hand() && pos.count_in_hand(c, pt) ? ~pos.pieces() : Bitboard(0));
+ };
+ for (PieceType pt : pos.piece_types())
+ {
+ switch (pt)
+ {
+ case QUEEN:
+ // Enemy queen safe checks: we count them only if they are from squares from
+ // which we can't give a rook check, because rook checks are more valuable.
+ queenChecks = (b1 | b2)
+ & get_attacks(Them, QUEEN)
+ & safe
+ & ~attackedBy[Us][QUEEN]
+ & ~(b1 & attackedBy[Them][ROOK]);
+
+ if (queenChecks)
+ kingDanger += QueenSafeCheck;
+ break;
+ case ROOK:
+ case BISHOP:
+ case KNIGHT:
+ knightChecks = attacks_bb(Us, pt, ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)) & get_attacks(Them, pt) & pos.board_bb();
+ if (knightChecks & safe)
+ kingDanger += pt == ROOK ? RookSafeCheck
+ : pt == BISHOP ? BishopSafeCheck
+ : KnightSafeCheck;
+ else
+ unsafeChecks |= knightChecks;
+ break;
+ case PAWN:
+ if (pos.captures_to_hand() && pos.count_in_hand(Them, pt))
+ {
+ pawnChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & ~pos.pieces() & pos.board_bb();
+ if (pawnChecks & safe)
+ kingDanger += OtherSafeCheck;
+ else
+ unsafeChecks |= pawnChecks;
+ }
+ break;
+ case SHOGI_PAWN:
+ case KING:
+ break;
+ default:
+ otherChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & get_attacks(Them, pt) & pos.board_bb();
+ if (otherChecks & safe)
+ kingDanger += OtherSafeCheck;
+ else
+ unsafeChecks |= otherChecks;
+ }
+ }
- if (rookChecks)
- kingDanger += RookSafeCheck;
- else
- unsafeChecks |= b1 & attackedBy[Them][ROOK];
-
- // Enemy queen safe checks: we count them only if they are from squares from
- // which we can't give a rook check, because rook checks are more valuable.
- queenChecks = (b1 | b2)
- & attackedBy[Them][QUEEN]
- & safe
- & ~attackedBy[Us][QUEEN]
- & ~rookChecks;
-
- if (queenChecks)
- kingDanger += QueenSafeCheck;
-
- // Enemy bishops checks: we count them only if they are from squares from
- // which we can't give a queen check, because queen checks are more valuable.
- bishopChecks = b2
- & attackedBy[Them][BISHOP]
- & safe
- & ~queenChecks;
-
- if (bishopChecks)
- kingDanger += BishopSafeCheck;
- else
- unsafeChecks |= b2 & attackedBy[Them][BISHOP];
+ if (pos.check_counting())
+ kingDanger *= 2;
- // Enemy knights checks
- knightChecks = pos.attacks_from<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
-
- if (knightChecks & safe)
- kingDanger += KnightSafeCheck;
- else
- unsafeChecks |= knightChecks;
+ Square s = file_of(ksq) == FILE_A ? ksq + EAST : file_of(ksq) == pos.max_file() ? ksq + WEST : ksq;
+ Bitboard kingFlank = pos.max_file() == FILE_H ? KingFlank[file_of(ksq)] : file_bb(s) | adjacent_files_bb(s);
- // Find the squares that opponent attacks in our king flank, and the squares
- // which are attacked twice in that flank.
+ // Find the squares that opponent attacks in our king flank, the squares
+ // which they attack twice in that flank, and the squares that we defend.
- b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
+ b1 = attackedBy[Them][ALL_PIECES] & kingFlank & Camp;
b2 = b1 & attackedBy2[Them];
- b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
++ b3 = attackedBy[Us][ALL_PIECES] & kingFlank & Camp;
- int kingFlankAttacks = popcount(b1) + popcount(b2);
+ int kingFlankAttack = popcount(b1) + popcount(b2);
+ int kingFlankDefense = popcount(b3);
kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them]
- + 185 * popcount(kingRing[Us] & weak)
+ + kingAttackersCountInHand[Them] * kingAttackersWeight[Them]
+ + kingAttackersCount[Them] * kingAttackersWeightInHand[Them]
+ + 185 * popcount(kingRing[Us] & weak) * (1 + pos.captures_to_hand() + pos.check_counting())
+ 148 * popcount(unsafeChecks)
+ 98 * popcount(pos.blockers_for_king(Us))
- + 69 * kingAttacksCount[Them]
+ + 69 * kingAttacksCount[Them] * (2 + 8 * pos.check_counting() + pos.captures_to_hand()) / 2
- + 3 * kingFlankAttacks * kingFlankAttacks / 8
+ + 4 * (kingFlankAttack - kingFlankDefense)
+ + 3 * kingFlankAttack * kingFlankAttack / 8
+ mg_value(mobility[Them] - mobility[Us])
- - 873 * !pos.count<QUEEN>(Them)
+ - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand() || pos.xiangqi_general()) / (1 + pos.check_counting())
- 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
- 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING])
- 6 * mg_value(score) / 8
// Transform the kingDanger units into a Score, and subtract it from the evaluation
if (kingDanger > 100)
- score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16);
+ score -= make_score(std::min(kingDanger * kingDanger / 4096, 3000), kingDanger / 16);
// Penalty when our king is on a pawnless flank
- if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)]))
+ if (!(pos.pieces(PAWN) & kingFlank))
score -= PawnlessFlank;
- // King tropism bonus, to anticipate slow motion attacks on our king
- score -= FlankAttacks * kingFlankAttacks * (1 + 5 * pos.captures_to_hand() + pos.check_counting());
+ // Penalty if king flank is under attack, potentially moving toward the king
- score -= FlankAttacks * kingFlankAttack;
++ score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting());
+
+ if (pos.check_counting())
+ score += make_score(0, mg_value(score) / 2);
+
+ // For drop games, king danger is independent of game phase
+ if (pos.captures_to_hand())
+ score = make_score(mg_value(score), mg_value(score)) * 2 / (2 + 3 * !pos.shogi_doubled_pawn());
if (T)
Trace::add(KING, Us, score);
movedPiece = pos.moved_piece(move);
givesCheck = pos.gives_check(move);
- // Step 13. Extensions (~70 Elo)
+ // Calculate new depth for this move
+ newDepth = depth - 1;
+
+ // Step 13. Pruning at shallow depth (~170 Elo)
+ if ( !rootNode
- && pos.non_pawn_material(us)
++ && (pos.non_pawn_material(us) || !(pos.pieces(us) ^ pos.pieces(us, PAWN)))
+ && bestValue > VALUE_MATED_IN_MAX_PLY)
+ {
+ // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
- moveCountPruning = moveCount >= futility_move_count(improving, depth);
++ moveCountPruning = moveCount >= futility_move_count(improving, depth)
++ || (pos.must_capture() && (moveCountPruning || (pos.capture(move) && pos.legal(move))));
+
+ if ( !captureOrPromotion
+ && !givesCheck
- && (!PvNode || !pos.advanced_pawn_push(move) || pos.non_pawn_material(~us) > BishopValueMg))
++ && (!pos.must_capture() || !pos.attackers_to(to_sq(move), ~us))
++ && (!PvNode || !pos.advanced_pawn_push(move) || pos.non_pawn_material(~us) > BishopValueMg || pos.count<ALL_PIECES>(us) == pos.count<PAWN>(us)))
+ {
+ // Reduced depth of the next LMR search
+ int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
+
+ // Countermoves based pruning (~20 Elo)
+ if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
- && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
- && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
++ && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold
++ && (*contHist[1])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold)
+ continue;
+
+ // Futility pruning: parent node (~2 Elo)
+ if ( lmrDepth < 6
+ && !inCheck
++ && !( pos.extinction_value() == -VALUE_MATE
++ && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end())
+ && ss->staticEval + 250 + 211 * lmrDepth <= alpha)
+ continue;
+
+ // Prune moves with negative SEE (~10 Elo)
- if (!pos.see_ge(move, Value(-(31 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
++ if (!pos.must_capture() && !pos.see_ge(move, Value(-(31 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
+ continue;
+ }
- else if (!pos.see_ge(move, Value(-199) * depth)) // (~20 Elo)
++ else if ( !(givesCheck && extension)
++ && !pos.must_capture()
++ && !pos.see_ge(move, Value(-199 - 120 * pos.captures_to_hand()) * depth)) // (~20 Elo)
+ continue;
+ }
+
+ // Step 14. Extensions (~70 Elo)
// Singular extension search (~60 Elo). If all moves but one fail low on a
// search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
if (type_of(move) == CASTLING)
extension = 1;
+ // Losing chess capture extension
+ else if ( pos.must_capture()
+ && pos.capture(move)
+ && MoveList<CAPTURES>(pos).size() == 1)
+ extension = 1;
+
- // Calculate new depth for this move
- newDepth = depth - 1 + extension;
-
- // Step 14. Pruning at shallow depth (~170 Elo)
- if ( !rootNode
- && (pos.non_pawn_material(us) || !(pos.pieces(us) ^ pos.pieces(us, PAWN)))
- && bestValue > VALUE_MATED_IN_MAX_PLY)
- {
- // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
- moveCountPruning = moveCount >= futility_move_count(improving, depth)
- || (pos.must_capture() && (moveCountPruning || (pos.capture(move) && pos.legal(move))));
-
- if ( !captureOrPromotion
- && !givesCheck
- && (!pos.must_capture() || !pos.attackers_to(to_sq(move), ~us))
- && (!PvNode || !pos.advanced_pawn_push(move) || pos.non_pawn_material(~us) > BishopValueMg || pos.count<ALL_PIECES>(us) == pos.count<PAWN>(us)))
- {
- // Reduced depth of the next LMR search
- int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
-
- // Countermoves based pruning (~20 Elo)
- if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
- && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold
- && (*contHist[1])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold)
- continue;
-
- // Futility pruning: parent node (~2 Elo)
- if ( lmrDepth < 6
- && !inCheck
- && !( pos.extinction_value() == -VALUE_MATE
- && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end())
- && ss->staticEval + 250 + 211 * lmrDepth <= alpha)
- continue;
-
- // Prune moves with negative SEE (~10 Elo)
- if (!pos.must_capture() && !pos.see_ge(move, Value(-(31 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
- continue;
- }
- else if ( !(givesCheck && extension)
- && !pos.must_capture()
- && !pos.see_ge(move, Value(-199 - 120 * pos.captures_to_hand()) * depth)) // (~20 Elo)
- continue;
- }
+ // Add extension to new depth
+ newDepth += extension;
// Speculative prefetch as early as possible
prefetch(TT.first_entry(pos.key_after(move)));