internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(verify_material(pos, strongSide, Value.BishopValueMg, 2)); Debug.Assert(verify_material(pos, weakSide, Value.BishopValueMg, 0)); var wbsq = pos.square(PieceType.BISHOP, strongSide); var bbsq = pos.square(PieceType.BISHOP, weakSide); if (!Square.opposite_colors(wbsq, bbsq)) { return ScaleFactor.SCALE_FACTOR_NONE; } var ksq = pos.square(PieceType.KING, weakSide); var psq1 = pos.square(PieceType.PAWN, strongSide, 0); var psq2 = pos.square(PieceType.PAWN, strongSide, 1); var r1 = Square.rank_of(psq1); var r2 = Square.rank_of(psq2); SquareT blockSq1, blockSq2; if (Rank.relative_rank_CtSt(strongSide, psq1) > Rank.relative_rank_CtSt(strongSide, psq2)) { blockSq1 = psq1 + Square.pawn_push(strongSide); blockSq2 = Square.make_square(Square.file_of(psq2), Square.rank_of(psq1)); } else { blockSq1 = psq2 + Square.pawn_push(strongSide); blockSq2 = Square.make_square(Square.file_of(psq1), Square.rank_of(psq2)); } switch (Utils.distance_File(psq1, psq2)) { case 0: // Both pawns are on the same file. It's an easy draw if the defender firmly // controls some square in the frontmost pawn's path. if (Square.file_of(ksq) == Square.file_of(blockSq1) && Rank.relative_rank_CtSt(strongSide, ksq) >= Rank.relative_rank_CtSt(strongSide, blockSq1) && Square.opposite_colors(ksq, wbsq)) { return ScaleFactor.SCALE_FACTOR_DRAW; } return ScaleFactor.SCALE_FACTOR_NONE; case 1: // Pawns on adjacent files. It's a draw if the defender firmly controls the // square in front of the frontmost pawn's path, and the square diagonally // behind this square on the file of the other pawn. if (ksq == blockSq1 && Square.opposite_colors(ksq, wbsq) && (bbsq == blockSq2 || (pos.attacks_from_PtS(PieceType.BISHOP, blockSq2) & pos.pieces_CtPt(weakSide, PieceType.BISHOP))!=0 || Utils.distance_Rank(r1, r2) >= 2)) { return ScaleFactor.SCALE_FACTOR_DRAW; } if (ksq == blockSq2 && Square.opposite_colors(ksq, wbsq) && (bbsq == blockSq1 || (pos.attacks_from_PtS(PieceType.BISHOP, blockSq1) & pos.pieces_CtPt(weakSide, PieceType.BISHOP))!=0)) { return ScaleFactor.SCALE_FACTOR_DRAW; } return ScaleFactor.SCALE_FACTOR_NONE; default: // The pawns are not on the same file or adjacent files. No scaling. return ScaleFactor.SCALE_FACTOR_NONE; } }
internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(pos.non_pawn_material(strongSide) == Value.VALUE_ZERO); Debug.Assert(pos.count(PieceType.PAWN, strongSide) >= 2); Debug.Assert(verify_material(pos, weakSide, Value.VALUE_ZERO, 0)); var ksq = pos.square(PieceType.KING, weakSide); var pawns = pos.pieces_CtPt(strongSide, PieceType.PAWN); // If all pawns are ahead of the king, on a single rook file and // the king is within one file of the pawns, it's a draw. if ((pawns & ~Utils.in_front_bb(weakSide, Square.rank_of(ksq)))==0 && !((pawns & ~Bitboard.FileABB)!=0 && (pawns & ~Bitboard.FileHBB)!=0) && Utils.distance_File(ksq, Utils.lsb(pawns)) <= 1) { return ScaleFactor.SCALE_FACTOR_DRAW; } return ScaleFactor.SCALE_FACTOR_NONE; }
internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(verify_material(pos, strongSide, Value.BishopValueMg, 1)); Debug.Assert(verify_material(pos, weakSide, Value.BishopValueMg, 0)); var pawnSq = pos.square(PieceType.PAWN, strongSide); var strongBishopSq = pos.square(PieceType.BISHOP, strongSide); var weakBishopSq = pos.square(PieceType.BISHOP, weakSide); var weakKingSq = pos.square(PieceType.KING, weakSide); // Case 1: Defending king blocks the pawn, and cannot be driven away if (Square.file_of(weakKingSq) == Square.file_of(pawnSq) && Rank.relative_rank_CtSt(strongSide, pawnSq) < Rank.relative_rank_CtSt(strongSide, weakKingSq) && (Square.opposite_colors(weakKingSq, strongBishopSq) || Rank.relative_rank_CtSt(strongSide, weakKingSq) <= Rank.RANK_6)) { return ScaleFactor.SCALE_FACTOR_DRAW; } // Case 2: Opposite colored bishops if (Square.opposite_colors(strongBishopSq, weakBishopSq)) { // We assume that the position is drawn in the following three situations: // // a. The pawn is on rank 5 or further back. // b. The defending king is somewhere in the pawn's path. // c. The defending bishop attacks some square along the pawn's path, // and is at least three squares away from the pawn. // // These rules are probably not perfect, but in practice they work // reasonably well. if (Rank.relative_rank_CtSt(strongSide, pawnSq) <= Rank.RANK_5) { return ScaleFactor.SCALE_FACTOR_DRAW; } var path = Utils.forward_bb(strongSide, pawnSq); if ((path & pos.pieces_CtPt(weakSide, PieceType.KING))!=0) { return ScaleFactor.SCALE_FACTOR_DRAW; } if ((pos.attacks_from_PtS(PieceType.BISHOP, weakBishopSq) & path)!=0 && Utils.distance_Square(weakBishopSq, pawnSq) >= 3) { return ScaleFactor.SCALE_FACTOR_DRAW; } } return ScaleFactor.SCALE_FACTOR_NONE; }
internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(pos.non_pawn_material(strongSide) == Value.BishopValueMg); Debug.Assert(pos.count(PieceType.PAWN, strongSide) >= 1); // No assertions about the material of weakSide, because we want draws to // be detected even when the weaker side has some pawns. var pawns = pos.pieces_CtPt(strongSide, PieceType.PAWN); var pawnsFile = Square.file_of(Utils.lsb(pawns)); // All pawns are on a single rook file? if ((pawnsFile == File.FILE_A || pawnsFile == File.FILE_H) && (pawns & ~Utils.file_bb_Ft(pawnsFile))==0) { var bishopSq = pos.square(PieceType.BISHOP, strongSide); var queeningSq = Square.relative_square(strongSide, Square.make_square(pawnsFile, Rank.RANK_8)); var kingSq = pos.square(PieceType.KING, weakSide); if (Square.opposite_colors(queeningSq, bishopSq) && Utils.distance_Square(queeningSq, kingSq) <= 1) { return ScaleFactor.SCALE_FACTOR_DRAW; } } // If all the pawns are on the same B or G file, then it's potentially a draw if ((pawnsFile == File.FILE_B || pawnsFile == File.FILE_G) && (pos.pieces_Pt(PieceType.PAWN) & ~Utils.file_bb_Ft(pawnsFile))==0 && pos.non_pawn_material(weakSide) == 0 && pos.count(PieceType.PAWN, weakSide) >= 1) { // Get weakSide pawn that is closest to the home rank var weakPawnSq = Utils.backmost_sq(weakSide, pos.pieces_CtPt(weakSide, PieceType.PAWN)); var strongKingSq = pos.square(PieceType.KING, strongSide); var weakKingSq = pos.square(PieceType.KING, weakSide); var bishopSq = pos.square(PieceType.BISHOP, strongSide); // There's potential for a draw if our pawn is blocked on the 7th rank, // the bishop cannot attack it or they only have one pawn left if (Rank.relative_rank_CtSt(strongSide, weakPawnSq) == Rank.RANK_7 && Bitboard.AndWithSquare(pos.pieces_CtPt(strongSide, PieceType.PAWN), (weakPawnSq + Square.pawn_push(weakSide)))!=0 && (Square.opposite_colors(bishopSq, weakPawnSq) || pos.count(PieceType.PAWN, strongSide) == 1)) { var strongKingDist = Utils.distance_Square(weakPawnSq, strongKingSq); var weakKingDist = Utils.distance_Square(weakPawnSq, weakKingSq); // It's a draw if the weak king is on its back two ranks, within 2 // squares of the blocking pawn and the strong king is not // closer. (I think this rule only fails in practically // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w // and positions where qsearch will immediately correct the // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w) if (Rank.relative_rank_CtSt(strongSide, weakKingSq) >= Rank.RANK_7 && weakKingDist <= 2 && weakKingDist <= strongKingDist) { return ScaleFactor.SCALE_FACTOR_DRAW; } } } return ScaleFactor.SCALE_FACTOR_NONE; }
internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(verify_material(pos, strongSide, Value.QueenValueMg, 0)); Debug.Assert(pos.count(PieceType.ROOK, weakSide) == 1); Debug.Assert(pos.count(PieceType.PAWN, weakSide) >= 1); var kingSq = pos.square(PieceType.KING, weakSide); var rsq = pos.square(PieceType.ROOK, weakSide); if (Rank.relative_rank_CtSt(weakSide, kingSq) <= Rank.RANK_2 && Rank.relative_rank_CtSt(weakSide, pos.square(PieceType.KING, strongSide)) >= Rank.RANK_4 && Rank.relative_rank_CtSt(weakSide, rsq) == Rank.RANK_3 && (pos.pieces_CtPt(weakSide, PieceType.PAWN) & pos.attacks_from_PtS(PieceType.KING, kingSq) & pos.attacks_from_PS(PieceType.PAWN, rsq, strongSide))!=0) { return ScaleFactor.SCALE_FACTOR_DRAW; } return ScaleFactor.SCALE_FACTOR_NONE; }
internal static ScoreT evaluate(ColorT Us, Position pos, Entry e) { var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var Up = (Us == Color.WHITE ? Square.DELTA_N : Square.DELTA_S); var Right = (Us == Color.WHITE ? Square.DELTA_NE : Square.DELTA_SW); var Left = (Us == Color.WHITE ? Square.DELTA_NW : Square.DELTA_SE); BitboardT b; var score = Score.SCORE_ZERO; var ourPawns = pos.pieces_CtPt(Us, PieceType.PAWN); var theirPawns = pos.pieces_CtPt(Them, PieceType.PAWN); e.passedPawns[Us] = Bitboard.Create(0); e.kingSquares[Us] = Square.SQ_NONE; e.semiopenFiles[Us] = 0xFF; e.pawnAttacks[Us] = Bitboard.shift_bb(Right, ourPawns) | Bitboard.shift_bb(Left, ourPawns); e.pawnsOnSquares[Us, Color.BLACK] = Bitcount.popcount_Max15(ourPawns & Bitboard.DarkSquares); e.pawnsOnSquares[Us, Color.WHITE] = pos.count(PieceType.PAWN, Us) - e.pawnsOnSquares[Us, Color.BLACK]; // Loop through all pawns of the current color and score each pawn for (var idx = 0; idx < 16; idx++) { var s = pos.square(PieceType.PAWN, Us, idx); if (s == Square.SQ_NONE) { break; } Debug.Assert(pos.piece_on(s) == Piece.make_piece(Us, PieceType.PAWN)); var f = Square.file_of(s); // This file cannot be semi-open e.semiopenFiles[Us] &= ~(1 << f); // Flag the pawn var neighbours = ourPawns & Utils.adjacent_files_bb(f); var doubled = ourPawns & Utils.forward_bb(Us, s); bool opposed = (theirPawns & Utils.forward_bb(Us, s)) != 0; var passed = (theirPawns & Utils.passed_pawn_mask(Us, s)) == 0; bool lever = (theirPawns & Utils.StepAttacksBB[Piece.make_piece(Us, PieceType.PAWN), s]) != 0; var phalanx = neighbours & Utils.rank_bb_St(s); var supported = neighbours & Utils.rank_bb_St(s - Up); bool connected = (supported | phalanx) != 0; var isolated = neighbours == 0; // Test for backward pawn. // If the pawn is passed, isolated, lever or connected it cannot be // backward. If there are friendly pawns behind on adjacent files // or if it is sufficiently advanced, it cannot be backward either. bool backward; if ((passed | isolated | lever | connected) || (ourPawns & Utils.pawn_attack_span(Them, s)) != 0 || (Rank.relative_rank_CtSt(Us, s) >= Rank.RANK_5)) { backward = false; } else { // We now know there are no friendly pawns beside or behind this // pawn on adjacent files. We now check whether the pawn is // backward by looking in the forward direction on the adjacent // files, and picking the closest pawn there. b = Utils.pawn_attack_span(Us, s) & (ourPawns | theirPawns); b = Utils.pawn_attack_span(Us, s) & Utils.rank_bb_St(Utils.backmost_sq(Us, b)); // If we have an enemy pawn in the same or next rank, the pawn is // backward because it cannot advance without being captured. backward = ((b | Bitboard.shift_bb(Up, b)) & theirPawns) != 0; } Debug.Assert(opposed | passed | (Utils.pawn_attack_span(Us, s) & theirPawns) != 0); // Passed pawns will be properly scored in evaluation because we need // full attack info to evaluate passed pawns. Only the frontmost passed // pawn on each file is considered a true passed pawn. if (passed && doubled == 0) { e.passedPawns[Us] = Bitboard.OrWithSquare(e.passedPawns[Us], s); } // Score this pawn if (isolated) { score -= Isolated[opposed ? 1 : 0][f]; } else if (backward) { score -= Backward[opposed ? 1 : 0]; } else if (supported == 0) { score -= UnsupportedPawnPenalty; } if (connected) { score += Connected[ opposed ? 1 : 0, phalanx != 0 ? 1 : 0, Bitboard.more_than_one(supported) ? 1 : 0, Rank.relative_rank_CtSt(Us, s)]; } if (doubled != 0) { score -= Score.Divide(Doubled[f], Utils.distance_Rank_StSt(s, Utils.frontmost_sq(Us, doubled))); } if (lever) { score += Lever[Rank.relative_rank_CtSt(Us, s)]; } } b = Bitboard.Create((uint) (e.semiopenFiles[Us] ^ 0xFF)); e.pawnSpan[Us] = b != 0 ? Utils.msb(b) - (int)Utils.lsb(b) : 0; // Center binds: Two pawns controlling the same central square b = Bitboard.shift_bb(Right, ourPawns) & Bitboard.shift_bb(Left, ourPawns) & CenterBindMask[Us]; score += Bitcount.popcount_Max15(b)*CenterBind; return score; }
/// 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. private ScoreT do_king_safety(ColorT Us, Position pos, SquareT ksq) { kingSquares[Us] = ksq; castlingRights[Us] = pos.can_castle(Us); var minKingPawnDistance = 0; var pawns = pos.pieces_CtPt(Us, PieceType.PAWN); if (pawns != 0) { while ((Utils.DistanceRingBB[ksq, minKingPawnDistance++] & pawns) == 0) { } } if (Rank.relative_rank_CtSt(Us, ksq) > Rank.RANK_4) { return Score.make_score(0, -16*minKingPawnDistance); } var bonus = shelter_storm(Us, pos, ksq); // If we can castle use the bonus after the castling if it is bigger if (pos.can_castle(Movegen.MakeCastling(Us, CastlingSide.KING_SIDE))) { bonus = Value.Create( Math.Max(bonus, shelter_storm(Us, pos, Square.relative_square(Us, Square.SQ_G1)))); } if (pos.can_castle(Movegen.MakeCastling(Us, CastlingSide.QUEEN_SIDE))) { bonus = Value.Create( Math.Max(bonus, shelter_storm(Us, pos, Square.relative_square(Us, Square.SQ_C1)))); } return Score.make_score(bonus, -16*minKingPawnDistance); }
internal static ExtMoveArrayWrapper generate_pawn_moves( ColorT Us, GenType Type, Position pos, ExtMoveArrayWrapper moveList, BitboardT target, CheckInfo ci) { // Compute our parametrized parameters at compile time, named according to // the point of view of white side. var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var TRank8BB = (Us == Color.WHITE ? Bitboard.Rank8BB : Bitboard.Rank1BB); var TRank7BB = (Us == Color.WHITE ? Bitboard.Rank7BB : Bitboard.Rank2BB); var TRank3BB = (Us == Color.WHITE ? Bitboard.Rank3BB : Bitboard.Rank6BB); var Up = (Us == Color.WHITE ? Square.DELTA_N : Square.DELTA_S); var Right = (Us == Color.WHITE ? Square.DELTA_NE : Square.DELTA_SW); var Left = (Us == Color.WHITE ? Square.DELTA_NW : Square.DELTA_SE); var emptySquares = Bitboard.Create(0); var pawnsOn7 = pos.pieces_CtPt(Us, PieceType.PAWN) & TRank7BB; var pawnsNotOn7 = pos.pieces_CtPt(Us, PieceType.PAWN) & ~TRank7BB; var enemies = (Type == GenType.EVASIONS ? pos.pieces_Ct(Them) & target : Type == GenType.CAPTURES ? target : pos.pieces_Ct(Them)); // Single and double pawn pushes, no promotions if (Type != GenType.CAPTURES) { emptySquares = (Type == GenType.QUIETS || Type == GenType.QUIET_CHECKS ? target : ~pos.pieces()); var b1 = Bitboard.shift_bb(Up, pawnsNotOn7) & emptySquares; var b2 = Bitboard.shift_bb(Up, b1 & TRank3BB) & emptySquares; if (Type == GenType.EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; } if (Type == GenType.QUIET_CHECKS) { b1 &= pos.attacks_from_PS(PieceType.PAWN, ci.ksq, Them); b2 &= pos.attacks_from_PS(PieceType.PAWN, ci.ksq, Them); // Add pawn pushes which give discovered check. This is possible only // if the pawn is not on the same file as the enemy king, because we // don't generate captures. Note that a possible discovery check // promotion has been already generated amongst the captures. if ((pawnsNotOn7 & ci.dcCandidates) != 0) { var dc1 = Bitboard.shift_bb(Up, pawnsNotOn7 & ci.dcCandidates) & emptySquares & ~Utils.file_bb_St(ci.ksq); var dc2 = Bitboard.shift_bb(Up, dc1 & TRank3BB) & emptySquares; b1 |= dc1; b2 |= dc2; } } while (b1 != 0) { var to = Utils.pop_lsb(ref b1); (moveList).Add(Move.make_move(to - Up, to)); } while (b2 != 0) { var to = Utils.pop_lsb(ref b2); (moveList).Add(Move.make_move(to - Up - Up, to)); } } // Promotions and underpromotions if (pawnsOn7 != 0 && (Type != GenType.EVASIONS || ((target & TRank8BB) != 0))) { if (Type == GenType.CAPTURES) { emptySquares = ~pos.pieces(); } if (Type == GenType.EVASIONS) { emptySquares &= target; } var b1 = Bitboard.shift_bb(Right, pawnsOn7) & enemies; var b2 = Bitboard.shift_bb(Left, pawnsOn7) & enemies; var b3 = Bitboard.shift_bb(Up, pawnsOn7) & emptySquares; while (b1 != 0) { moveList = make_promotions(Type, Right, moveList, Utils.pop_lsb(ref b1), ci); } while (b2 != 0) { moveList = make_promotions(Type, Left, moveList, Utils.pop_lsb(ref b2), ci); } while (b3 != 0) { moveList = make_promotions(Type, Up, moveList, Utils.pop_lsb(ref b3), ci); } } // Standard and en-passant captures if (Type == GenType.CAPTURES || Type == GenType.EVASIONS || Type == GenType.NON_EVASIONS) { var b1 = Bitboard.shift_bb(Right, pawnsNotOn7) & enemies; var b2 = Bitboard.shift_bb(Left, pawnsNotOn7) & enemies; while (b1 != 0) { var to = Utils.pop_lsb(ref b1); (moveList).Add(Move.make_move(to - Right, to)); } while (b2 != 0) { var to = Utils.pop_lsb(ref b2); (moveList).Add(Move.make_move(to - Left, to)); } if (pos.ep_square() != Square.SQ_NONE) { Debug.Assert(Square.rank_of(pos.ep_square()) == Rank.relative_rank_CtRt(Us, Rank.RANK_6)); // An en passant capture can be an evasion only if the checking piece // is the double pushed pawn and so is in the target. Otherwise this // is a discovery check and we are forced to do otherwise. if (Type == GenType.EVASIONS && Bitboard.AndWithSquare(target, (pos.ep_square() - Up))==0) { return moveList; } b1 = pawnsNotOn7 & pos.attacks_from_PS(PieceType.PAWN, pos.ep_square(), Them); Debug.Assert(b1 != 0); while (b1 != 0) { (moveList).Add(Move.make(MoveType.ENPASSANT, Utils.pop_lsb(ref b1), pos.ep_square())); } } } return moveList; }
/// evaluate() is the main evaluation function. It returns a static evaluation /// of the position always from the point of view of the side to move. internal static ValueT evaluate(bool DoTrace, Position pos) { Debug.Assert(pos.checkers() == 0); var ei = new EvalInfo(); ScoreT[] mobility = {Score.SCORE_ZERO, Score.SCORE_ZERO}; // Initialize score by reading the incrementally updated scores included // in the position object (material + piece square tables). // Score is computed from the point of view of white. var score = pos.psq_score(); // Probe the material hash table var me = Material.probe(pos); score += me.imbalance(); // If we have a specialized evaluation function for the current material // configuration, call it and return. if (me.specialized_eval_exists()) { return me.evaluate(pos); } // Probe the pawn hash table ei.pi = Pawns.probe(pos); score += Score.Multiply(ei.pi.pawns_score(), Weights[PawnStructure]); // Initialize attack and king safety bitboards ei.attackedBy[Color.WHITE, PieceType.ALL_PIECES] = ei.attackedBy[Color.BLACK, PieceType.ALL_PIECES] = Bitboard.Create(0); init_eval_info(Color.WHITE, pos, ei); init_eval_info(Color.BLACK, pos, ei); // Pawns blocked or on ranks 2 and 3. Will be excluded from the mobility area BitboardT[] blockedPawns = { pos.pieces_CtPt(Color.WHITE, PieceType.PAWN) & (Bitboard.shift_bb(Square.DELTA_S, pos.pieces()) | Bitboard.Rank2BB | Bitboard.Rank3BB), pos.pieces_CtPt(Color.BLACK, PieceType.PAWN) & (Bitboard.shift_bb(Square.DELTA_N, pos.pieces()) | Bitboard.Rank7BB | Bitboard.Rank6BB) }; // Do not include in mobility squares protected by enemy pawns, or occupied // by our blocked pawns or king. BitboardT[] mobilityArea = { ~(Bitboard.OrWithSquare(ei.attackedBy[Color.BLACK, PieceType.PAWN] | blockedPawns[Color.WHITE] , pos.square(PieceType.KING, Color.WHITE))), ~(Bitboard.OrWithSquare(ei.attackedBy[Color.WHITE, PieceType.PAWN] | blockedPawns[Color.BLACK] , pos.square(PieceType.KING, Color.BLACK))) }; // Evaluate pieces and mobility score += evaluate_pieces(PieceType.KNIGHT, Color.WHITE, DoTrace, pos, ei, mobility, mobilityArea); score += Score.Multiply(mobility[Color.WHITE] - mobility[Color.BLACK], Weights[Mobility]); // Evaluate kings after all other pieces because we need complete attack // information when computing the king safety evaluation. score += evaluate_king(Color.WHITE, DoTrace, pos, ei) - evaluate_king(Color.BLACK, DoTrace, pos, ei); // Evaluate tactical threats, we need full attack information including king score += evaluate_threats(Color.WHITE, DoTrace, pos, ei) - evaluate_threats(Color.BLACK, DoTrace, pos, ei); // Evaluate passed pawns, we need full attack information including king score += evaluate_passed_pawns(Color.WHITE, DoTrace, pos, ei) - evaluate_passed_pawns(Color.BLACK, DoTrace, pos, ei); // If both sides have only pawns, score for potential unstoppable pawns if (pos.non_pawn_material(Color.WHITE) == 0 && pos.non_pawn_material(Color.BLACK) == 0) { BitboardT b; if ((b = ei.pi.passed_pawns(Color.WHITE)) != 0) { score += Rank.relative_rank_CtSt(Color.WHITE, Utils.frontmost_sq(Color.WHITE, b)) * Unstoppable; } if ((b = ei.pi.passed_pawns(Color.BLACK)) != 0) { score -= Rank.relative_rank_CtSt(Color.BLACK, Utils.frontmost_sq(Color.BLACK, b)) * Unstoppable; } } // Evaluate space for both sides, only during opening if (pos.non_pawn_material(Color.WHITE) + pos.non_pawn_material(Color.BLACK) >= 12222) { score += Score.Multiply(evaluate_space(Color.WHITE, pos, ei) - evaluate_space(Color.BLACK, pos, ei), Weights[Space]); } // Scale winning side if position is more drawish than it appears var strongSide = Score.eg_value(score) > Value.VALUE_DRAW ? Color.WHITE : Color.BLACK; var sf = me.scale_factor(pos, strongSide); // If we don't already have an unusual scale factor, check for certain // types of endgames, and use a lower scale for those. if (me.game_phase() < Phase.PHASE_MIDGAME && (sf == ScaleFactor.SCALE_FACTOR_NORMAL || sf == ScaleFactor.SCALE_FACTOR_ONEPAWN)) { if (pos.opposite_bishops()) { // Endgame with opposite-colored bishops and no other pieces (ignoring pawns) // is almost a draw, in case of KBP vs KB is even more a draw. if (pos.non_pawn_material(Color.WHITE) == Value.BishopValueMg && pos.non_pawn_material(Color.BLACK) == Value.BishopValueMg) { sf = Bitboard.more_than_one(pos.pieces_Pt(PieceType.PAWN)) ? (ScaleFactor) (31) : (ScaleFactor) (9); } // Endgame with opposite-colored bishops, but also other pieces. Still // a bit drawish, but not as drawish as with only the two bishops. else { sf = (ScaleFactor) (46*(int) sf/(int) ScaleFactor.SCALE_FACTOR_NORMAL); } } // Endings where weaker side can place his king in front of the opponent's // pawns are drawish. else if (Math.Abs(Score.eg_value(score)) <= Value.BishopValueEg && ei.pi.pawn_span(strongSide) <= 1 && !pos.pawn_passed(Color.opposite(strongSide), pos.square(PieceType.KING, Color.opposite(strongSide)))) { sf = ei.pi.pawn_span(strongSide) != 0 ? (ScaleFactor) (51) : (ScaleFactor) (37); } } // Scale endgame by number of pawns var p = pos.count(PieceType.PAWN, Color.WHITE) + pos.count(PieceType.PAWN, Color.BLACK); var vEg = 1 + Math.Abs(Score.eg_value(score)); sf = (ScaleFactor) (Math.Max((int) sf/2, (int) sf - 8*(int) ScaleFactor.SCALE_FACTOR_NORMAL*(12 - p)/vEg)); // Interpolate between a middlegame and a (scaled by 'sf') endgame score var v = Score.mg_value(score)*(int) (me.game_phase()) + Score.eg_value(score)*(Phase.PHASE_MIDGAME - me.game_phase())*(int) sf /(int) ScaleFactor.SCALE_FACTOR_NORMAL; v /= (int) (Phase.PHASE_MIDGAME); // In case of tracing add all single evaluation terms if (DoTrace) { add_IdxSt((int) Term.MATERIAL, pos.psq_score()); add_IdxSt((int) Term.IMBALANCE, me.imbalance()); add_IdxSt(PieceType.PAWN, ei.pi.pawns_score()); add_IdxStSt( (int) Term.MOBILITY, Score.Multiply(mobility[Color.WHITE], Weights[Mobility]), Score.Multiply(mobility[Color.BLACK], Weights[Mobility])); add_IdxStSt( (int) Term.SPACE, Score.Multiply(evaluate_space(Color.WHITE, pos, ei), Weights[Space]), Score.Multiply(evaluate_space(Color.BLACK, pos, ei), Weights[Space])); add_IdxSt((int) Term.TOTAL, score); } return (pos.side_to_move() == Color.WHITE ? v : -v) + Tempo; // Side to move point of view }
// evaluate_space() computes the space evaluation for a given side. The // space evaluation is a simple bonus based on the number of safe squares // available for minor pieces on the central four files on ranks 2--4. Safe // squares one, two or three squares behind a friendly pawn are counted // twice. Finally, the space bonus is multiplied by a weight. The aim is to // improve play on game opening. private static ScoreT evaluate_space(ColorT Us, Position pos, EvalInfo ei) { var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); // Find the safe squares for our pieces inside the area defined by // SpaceMask[]. A square is unsafe if it is attacked by an enemy // pawn, or if it is undefended and attacked by an enemy piece. var safe = SpaceMask[Us] & ~pos.pieces_CtPt(Us, PieceType.PAWN) & ~ei.attackedBy[Them, PieceType.PAWN] & (ei.attackedBy[Us, PieceType.ALL_PIECES] | ~ei.attackedBy[Them, PieceType.ALL_PIECES]); // Find all squares which are at most three squares behind some friendly pawn var behind = pos.pieces_CtPt(Us, PieceType.PAWN); behind |= (Us == Color.WHITE ? behind >> 8 : behind << 8); behind |= (Us == Color.WHITE ? behind >> 16 : behind << 16); // Since SpaceMask[Us.Value] is fully on our half of the board... Debug.Assert((uint) (safe >> (Us == Color.WHITE ? 32 : 0)) == 0); // ...count safe + (behind & safe) with a single popcount var bonus = Bitcount.popcount_Full((Us == Color.WHITE ? safe << 32 : safe >> 32) | (behind & safe)); var weight = pos.count(PieceType.KNIGHT, Us) + pos.count(PieceType.BISHOP, Us) + pos.count(PieceType.KNIGHT, Them) + pos.count(PieceType.BISHOP, Them); return Score.make_score(bonus*weight*weight, 0); }
// evaluate_threats() assigns bonuses according to the type of attacking piece // and the type of attacked one. private static ScoreT evaluate_threats(ColorT Us, bool DoTrace, Position pos, EvalInfo ei) { var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var Up = (Us == Color.WHITE ? Square.DELTA_N : Square.DELTA_S); var Left = (Us == Color.WHITE ? Square.DELTA_NW : Square.DELTA_SE); var Right = (Us == Color.WHITE ? Square.DELTA_NE : Square.DELTA_SW); var TRank2BB = (Us == Color.WHITE ? Bitboard.Rank2BB : Bitboard.Rank7BB); var TRank7BB = (Us == Color.WHITE ? Bitboard.Rank7BB : Bitboard.Rank2BB); const int Defended = 0; const int Weak = 1; const int Minor = 0; const int Rook = 1; BitboardT b; var score = Score.SCORE_ZERO; // Non-pawn enemies attacked by a pawn var weak = (pos.pieces_Ct(Them) ^ pos.pieces_CtPt(Them, PieceType.PAWN)) & ei.attackedBy[Us, PieceType.PAWN]; if (weak!=0) { b = pos.pieces_CtPt(Us, PieceType.PAWN) & (~ei.attackedBy[Them, PieceType.ALL_PIECES] | ei.attackedBy[Us, PieceType.ALL_PIECES]); var safeThreats = (Bitboard.shift_bb(Right, b) | Bitboard.shift_bb(Left, b)) & weak; if ((weak ^ safeThreats)!=0) { score += ThreatenedByHangingPawn; } while (safeThreats!=0) { score += ThreatenedByPawn[Piece.type_of(pos.piece_on(Utils.pop_lsb(ref safeThreats)))]; } } // Non-pawn enemies defended by a pawn var defended = (pos.pieces_Ct(Them) ^ pos.pieces_CtPt(Them, PieceType.PAWN)) & ei.attackedBy[Them, PieceType.PAWN]; // Add a bonus according to the kind of attacking pieces if (defended!=0) { b = defended & (ei.attackedBy[Us, PieceType.KNIGHT] | ei.attackedBy[Us, PieceType.BISHOP]); while (b!=0) { score += Threat[Defended][Minor][Piece.type_of(pos.piece_on(Utils.pop_lsb(ref b)))]; } b = defended & ei.attackedBy[Us, PieceType.ROOK]; while (b!=0) { score += Threat[Defended][Rook][Piece.type_of(pos.piece_on(Utils.pop_lsb(ref b)))]; } } // Enemies not defended by a pawn and under our attack weak = pos.pieces_Ct(Them) & ~ei.attackedBy[Them, PieceType.PAWN] & ei.attackedBy[Us, PieceType.ALL_PIECES]; // Add a bonus according to the kind of attacking pieces if (weak!=0) { b = weak & (ei.attackedBy[Us, PieceType.KNIGHT] | ei.attackedBy[Us, PieceType.BISHOP]); while (b!=0) { score += Threat[Weak][Minor][Piece.type_of(pos.piece_on(Utils.pop_lsb(ref b)))]; } b = weak & ei.attackedBy[Us, PieceType.ROOK]; while (b!=0) { score += Threat[Weak][Rook][Piece.type_of(pos.piece_on(Utils.pop_lsb(ref b)))]; } b = weak & ~ei.attackedBy[Them, PieceType.ALL_PIECES]; if (b!=0) { score += Hanging*Bitcount.popcount_Max15(b); } b = weak & ei.attackedBy[Us, PieceType.KING]; if (b!=0) { score += Bitboard.more_than_one(b) ? KingOnMany : KingOnOne; } } // Bonus if some pawns can safely push and attack an enemy piece b = pos.pieces_CtPt(Us, PieceType.PAWN) & ~TRank7BB; b = Bitboard.shift_bb(Up, b | (Bitboard.shift_bb(Up, b & TRank2BB) & ~pos.pieces())); b &= ~pos.pieces() & ~ei.attackedBy[Them, PieceType.PAWN] & (ei.attackedBy[Us, PieceType.ALL_PIECES] | ~ei.attackedBy[Them, PieceType.ALL_PIECES]); b = (Bitboard.shift_bb(Left, b) | Bitboard.shift_bb(Right, b)) & pos.pieces_Ct(Them) & ~ei.attackedBy[Us, PieceType.PAWN]; if (b!=0) { score += Bitcount.popcount_Max15(b)*PawnAttackThreat; } if (DoTrace) { add_IdxCtSt((int) Term.THREAT, Us, score); } return score; }
// evaluate_pieces() assigns bonuses and penalties to the pieces of a given color private static ScoreT evaluate_pieces( PieceTypeT pieceType, ColorT Us, bool DoTrace, Position pos, EvalInfo ei, ScoreT[] mobility, BitboardT[] mobilityArea) { int Pt = pieceType; if (Pt == PieceType.KING) { return Score.SCORE_ZERO; } var score = Score.SCORE_ZERO; var NextPt = (Us == Color.WHITE ? pieceType : pieceType + 1); var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); ei.attackedBy[Us, Pt] = Bitboard.Create(0); for(var idx=0; idx<16;idx++) { var s = pos.square(pieceType, Us, idx); if (s == Square.SQ_NONE) { break; } // Find attacked squares, including x-ray attacks for bishops and rooks var b = Pt == PieceType.BISHOP ? Utils.attacks_bb_PtSBb(PieceType.BISHOP, s, pos.pieces() ^ pos.pieces_CtPt(Us, PieceType.QUEEN)) : Pt == PieceType.ROOK ? Utils.attacks_bb_PtSBb( PieceType.ROOK, s, pos.pieces() ^ pos.pieces_CtPtPt(Us, PieceType.ROOK, PieceType.QUEEN)) : pos.attacks_from_PtS(pieceType, s); if (Bitboard.AndWithSquare(ei.pinnedPieces[Us], s)!=0) { b &= Utils.LineBB[pos.square(PieceType.KING, Us), s]; } ei.attackedBy[Us, PieceType.ALL_PIECES] |= ei.attackedBy[Us, Pt] |= b; if ((b & ei.kingRing[Them])!=0) { ei.kingAttackersCount[Us]++; ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; var bb = b & ei.attackedBy[Them, PieceType.KING]; if (bb!=0) { ei.kingAdjacentZoneAttacksCount[Us] += Bitcount.popcount_Max15(bb); } } if (Pt == PieceType.QUEEN) { b &= ~(ei.attackedBy[Them, PieceType.KNIGHT] | ei.attackedBy[Them, PieceType.BISHOP] | ei.attackedBy[Them, PieceType.ROOK]); } var mob = Pt == PieceType.QUEEN ? Bitcount.popcount_Full(b & mobilityArea[Us]) : Bitcount.popcount_Max15(b & mobilityArea[Us]); mobility[Us] += MobilityBonus[Pt][mob]; if (Pt == PieceType.BISHOP || Pt == PieceType.KNIGHT) { // Bonus for outpost square if (Rank.relative_rank_CtSt(Us, s) >= Rank.RANK_4 && Rank.relative_rank_CtSt(Us, s) <= Rank.RANK_6 && (pos.pieces_CtPt(Them, PieceType.PAWN) & Utils.pawn_attack_span(Us, s))==0) { score += Outpost[Pt == PieceType.BISHOP ? 1 : 0][Bitboard.AndWithSquare(ei.attackedBy[Us, PieceType.PAWN], s)!=0 ? 1 : 0]; } // Bonus when behind a pawn if (Rank.relative_rank_CtSt(Us, s) < Rank.RANK_5 && Bitboard.AndWithSquare(pos.pieces_Pt(PieceType.PAWN), (s + Square.pawn_push(Us)))!=0) { score += MinorBehindPawn; } // Penalty for pawns on same color square of bishop if (Pt == PieceType.BISHOP) { score -= BishopPawns*ei.pi.pawns_on_same_color_squares(Us, s); } // An important Chess960 pattern: A cornered bishop blocked by a friendly // pawn diagonally in front of it is a very serious problem, especially // when that pawn is also blocked. if (Pt == PieceType.BISHOP && pos.is_chess960() && (s == Square.relative_square(Us, Square.SQ_A1) || s == Square.relative_square(Us, Square.SQ_H1))) { var d = Square.pawn_push(Us) + (Square.file_of(s) == File.FILE_A ? Square.DELTA_E : Square.DELTA_W); if (pos.piece_on(s + d) == Piece.make_piece(Us, PieceType.PAWN)) { score -= !pos.empty(s + d + Square.pawn_push(Us)) ? TrappedBishopA1H1*4 : pos.piece_on(s + d + d) == Piece.make_piece(Us, PieceType.PAWN) ? TrappedBishopA1H1*2 : TrappedBishopA1H1; } } } if (Pt == PieceType.ROOK) { // Bonus for aligning with enemy pawns on the same rank/file if (Rank.relative_rank_CtSt(Us, s) >= Rank.RANK_5) { var alignedPawns = pos.pieces_CtPt(Them, PieceType.PAWN) & Utils.PseudoAttacks[PieceType.ROOK, s]; if (alignedPawns!=0) { score += Bitcount.popcount_Max15(alignedPawns)*RookOnPawn; } } // Bonus when on an open or semi-open file if (ei.pi.semiopen_file(Us, Square.file_of(s)) != 0) { score += ei.pi.semiopen_file(Them, Square.file_of(s)) != 0 ? RookOnOpenFile : RookOnSemiOpenFile; } // Penalize when trapped by the king, even more if king cannot castle if (mob <= 3 && 0 == ei.pi.semiopen_file(Us, Square.file_of(s))) { var ksq = pos.square(PieceType.KING, Us); if (((Square.file_of(ksq) < File.FILE_E) == (Square.file_of(s) < Square.file_of(ksq))) && (Square.rank_of(ksq) == Square.rank_of(s) || Rank.relative_rank_CtSt(Us, ksq) == Rank.RANK_1) && 0 == ei.pi.semiopen_side(Us, Square.file_of(ksq), Square.file_of(s) < Square.file_of(ksq))) { score -= (TrappedRook - Score.make_score(mob*22, 0))*(1 + (pos.can_castle(Us) == 0 ? 1 : 0)); } } } } if (DoTrace) { add_IdxCtSt(Pt, Us, score); } // Recursively call evaluate_pieces() of next piece type until KING excluded return score - evaluate_pieces(NextPt, Them, DoTrace, pos, ei, mobility, mobilityArea); }