internal static ExtMoveArrayWrapper generate_castling( CastlingRight Cr, bool Checks, bool Chess960, Position pos, ExtMoveArrayWrapper moveList, ColorT us, CheckInfo ci) { var KingSide = (Cr == CastlingRight.WHITE_OO || Cr == CastlingRight.BLACK_OO); if (pos.castling_impeded(Cr) || !pos.can_castle(Cr)) { return moveList; } // After castling, the rook and king final positions are the same in Chess960 // as they would be in standard chess. var kfrom = pos.square(PieceType.KING, us); var rfrom = pos.castling_rook_square(Cr); var kto = Square.relative_square(us, KingSide ? Square.SQ_G1 : Square.SQ_C1); var enemies = pos.pieces_Ct(Color.opposite(us)); Debug.Assert(pos.checkers() == 0); var K = Chess960 ? kto > kfrom ? Square.DELTA_W : Square.DELTA_E : KingSide ? Square.DELTA_W : Square.DELTA_E; for (var s = kto; s != kfrom; s += K) { if ((pos.attackers_to(s) & enemies) != 0) { return moveList; } } // Because we generate only legal castling moves we need to verify that // when moving the castling rook we do not discover some hidden checker. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. if (Chess960 && ((Utils.attacks_bb_PtSBb(PieceType.ROOK, kto, Bitboard.XorWithSquare(pos.pieces(), rfrom)) & pos.pieces_CtPtPt(Color.opposite(us), PieceType.ROOK, PieceType.QUEEN)))!= 0) { return moveList; } var m = Move.make(MoveType.CASTLING, kfrom, rfrom); if (Checks && !pos.gives_check(m, ci)) { return moveList; } moveList.Add(m); return moveList; }
/// Entry::shelter_storm() calculates shelter and storm penalties for the file /// the king is on, as well as the two adjacent files. private ValueT shelter_storm(ColorT Us, Position pos, SquareT ksq) { const int NoFriendlyPawn = 0; const int Unblocked = 1; const int BlockedByPawn = 2; const int BlockedByKing = 3; var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var b = pos.pieces_Pt(PieceType.PAWN) & (Utils.in_front_bb(Us, Square.rank_of(ksq)) | Utils.rank_bb_St(ksq)); var ourPawns = b & pos.pieces_Ct(Us); var theirPawns = b & pos.pieces_Ct(Them); var safety = MaxSafetyBonus; var center = File.Create(Math.Max(File.FILE_B, Math.Min(File.FILE_G, Square.file_of(ksq)))); for (var f = center - 1; f <= (int)center + 1; ++f) { b = ourPawns & Utils.file_bb_Ft(File.Create(f)); var rkUs = b != 0 ? Rank.relative_rank_CtSt(Us, Utils.backmost_sq(Us, b)) : Rank.RANK_1; b = theirPawns & Utils.file_bb_Ft(File.Create(f)); var rkThem = b != 0 ? Rank.relative_rank_CtSt(Us, Utils.frontmost_sq(Them, b)) : Rank.RANK_1; safety -= ShelterWeakness[Math.Min(f, File.FILE_H - f)][rkUs] + StormDanger[ f == (int)Square.file_of(ksq) && rkThem == Rank.relative_rank_CtSt(Us, ksq) + 1 ? BlockedByKing : (int)rkUs == Rank.RANK_1 ? NoFriendlyPawn : rkThem == rkUs + 1 ? BlockedByPawn : Unblocked][Math.Min(f, File.FILE_H - f)][rkThem]; } return safety; }
// Helper used to detect a given material distribution private static bool is_KXK(Position pos, ColorT us) { return !Bitboard.more_than_one(pos.pieces_Ct(Color.opposite(us))) && pos.non_pawn_material(us) >= Value.RookValueMg; }
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; }
/// generate /// EVASIONS /// generates all pseudo-legal check evasions when the side /// to move is in check. Returns a pointer to the end of the move list. private static ExtMoveArrayWrapper generate_EVASIONS(Position pos, ExtMoveArrayWrapper moveList) { Debug.Assert(pos.checkers() != 0); var us = pos.side_to_move(); var ksq = pos.square(PieceType.KING, us); var sliderAttacks = Bitboard.Create(0); var sliders = pos.checkers() & ~pos.pieces_PtPt(PieceType.KNIGHT, PieceType.PAWN); // Find all the squares attacked by slider checkers. We will remove them from // the king evasions in order to skip known illegal moves, which avoids any // useless legality checks later on. while (sliders != 0) { var checksq1 = Utils.pop_lsb(ref sliders); sliderAttacks |= Bitboard.XorWithSquare(Utils.LineBB[checksq1, ksq], checksq1); } // Generate evasions for king, capture and non capture moves var b = pos.attacks_from_PtS(PieceType.KING, ksq) & ~pos.pieces_Ct(us) & ~sliderAttacks; while (b != 0) { (moveList).Add(Move.make_move(ksq, Utils.pop_lsb(ref b))); } if (Bitboard.more_than_one(pos.checkers())) { return moveList; // Double check, only a king move can save the day } // Generate blocking evasions or captures of the checking piece var checksq = Utils.lsb(pos.checkers()); var target = Bitboard.OrWithSquare(Utils.between_bb(checksq, ksq), checksq); return us == Color.WHITE ? generate_all(Color.WHITE, GenType.EVASIONS, pos, moveList, target) : generate_all(Color.BLACK, GenType.EVASIONS, pos, moveList, target); }
internal static ExtMoveArrayWrapper generate(GenType Type, Position pos, ExtMoveArrayWrapper moveList) { switch (Type) { case GenType.EVASIONS: return generate_EVASIONS(pos, moveList); case GenType.LEGAL: return generate_LEGAL(pos, moveList); case GenType.QUIET_CHECKS: return generate_QUIET_CHECKS(pos, moveList); } Debug.Assert(Type == GenType.CAPTURES || Type == GenType.QUIETS || Type == GenType.NON_EVASIONS); Debug.Assert(pos.checkers() == 0); var us = pos.side_to_move(); var target = Type == GenType.CAPTURES ? pos.pieces_Ct(Color.opposite(us)) : Type == GenType.QUIETS ? ~pos.pieces() : Type == GenType.NON_EVASIONS ? ~pos.pieces_Ct(us) : Bitboard.Create(0); return us == Color.WHITE ? generate_all(Color.WHITE, Type, pos, moveList, target) : generate_all(Color.BLACK, Type, pos, moveList, target); }
// evaluate_passed_pawns() evaluates the passed pawns of the given color private static ScoreT evaluate_passed_pawns(ColorT Us, bool DoTrace, Position pos, EvalInfo ei) { var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var score = Score.SCORE_ZERO; var b = ei.pi.passed_pawns(Us); while (b!=0) { var s = Utils.pop_lsb(ref b); Debug.Assert(pos.pawn_passed(Us, s)); int r = Rank.relative_rank_CtSt(Us, s) - Rank.RANK_2; var rr = r*(r - 1); ValueT mbonus = Passed[(int) Phase.MG][r], ebonus = Passed[(int) Phase.EG][r]; if (rr != 0) { var blockSq = s + Square.pawn_push(Us); // Adjust bonus based on the king's proximity ebonus += Utils.distance_Square(pos.square(PieceType.KING, Them), blockSq)*5*rr - Utils.distance_Square(pos.square(PieceType.KING, Us), blockSq)*2*rr; // If blockSq is not the queening square then consider also a second push if (Rank.relative_rank_CtSt(Us, blockSq) != Rank.RANK_8) { ebonus -= Utils.distance_Square(pos.square(PieceType.KING, Us), blockSq + Square.pawn_push(Us))*rr; } // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) { // If there is a rook or queen attacking/defending the pawn from behind, // consider all the squaresToQueen. Otherwise consider only the squares // in the pawn's path attacked or occupied by the enemy. BitboardT squaresToQueen; BitboardT unsafeSquares; var defendedSquares = unsafeSquares = squaresToQueen = Utils.forward_bb(Us, s); var bb = Utils.forward_bb(Them, s) & pos.pieces_PtPt(PieceType.ROOK, PieceType.QUEEN) & pos.attacks_from_PtS(PieceType.ROOK, s); if ((pos.pieces_Ct(Us) & bb)==0) { defendedSquares &= ei.attackedBy[Us, PieceType.ALL_PIECES]; } if ((pos.pieces_Ct(Them) & bb) == 0) { unsafeSquares &= ei.attackedBy[Them, PieceType.ALL_PIECES] | pos.pieces_Ct(Them); } // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. var k = unsafeSquares == 0 ? 18 : Bitboard.AndWithSquare(unsafeSquares, blockSq)==0 ? 8 : 0; // If the path to queen is fully defended, assign a big bonus. // Otherwise assign a smaller bonus if the block square is defended. if (defendedSquares == squaresToQueen) { k += 6; } else if (Bitboard.AndWithSquare(defendedSquares, blockSq)!=0) { k += 4; } mbonus += k*rr; ebonus += k*rr; } else if (Bitboard.AndWithSquare(pos.pieces_Ct(Us), blockSq)!=0) { mbonus += rr*3 + r*2 + 3; ebonus += rr + r*2; } } // rr != 0 if (pos.count(PieceType.PAWN, Us) < pos.count(PieceType.PAWN, Them)) { ebonus += ebonus/4; } score += Score.make_score(mbonus, ebonus) + PassedFile[Square.file_of(s)]; } if (DoTrace) { add_IdxCtSt((int) Term.PASSED, Us, Score.Multiply(score, Weights[PassedPawns])); } // Add the scores to the middlegame and endgame eval return Score.Multiply(score, Weights[PassedPawns]); }
// 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_king() assigns bonuses and penalties to a king of a given color private static ScoreT evaluate_king(ColorT Us, bool DoTrace, Position pos, EvalInfo ei) { var Them = (Us == Color.WHITE ? Color.BLACK : Color.WHITE); var ksq = pos.square(PieceType.KING, Us); // King shelter and enemy pawns storm var score = ei.pi.king_safety(Us, pos, ksq); // Main king safety evaluation if (ei.kingAttackersCount[Them] != 0) { // Find the attacked squares around the king which have no defenders // apart from the king itself var undefended = ei.attackedBy[Them, PieceType.ALL_PIECES] & ei.attackedBy[Us, PieceType.KING] & ~(ei.attackedBy[Us, PieceType.PAWN] | ei.attackedBy[Us, PieceType.KNIGHT] | ei.attackedBy[Us, PieceType.BISHOP] | ei.attackedBy[Us, PieceType.ROOK] | ei.attackedBy[Us, PieceType.QUEEN]); // Initialize the 'attackUnits' variable, which is used later on as an // index into the KingDanger[] array. The initial value is based on the // number and types of the enemy's attacking pieces, the number of // attacked and undefended squares around our king and the quality of // the pawn shelter (current 'score' value). var attackUnits = Math.Min(72, ei.kingAttackersCount[Them] *ei.kingAttackersWeight[Them]) + 9*ei.kingAdjacentZoneAttacksCount[Them] + 27*Bitcount.popcount_Max15(undefended) + 11*((ulong)ei.pinnedPieces[Us] != 0 ? 1 : 0) - 64*(pos.count(PieceType.QUEEN, Them) == 0 ? 1 : 0) - Score.mg_value(score)/8; // Analyse the enemy's safe queen contact checks. Firstly, find the // undefended squares around the king reachable by the enemy queen... var b = undefended & ei.attackedBy[Them, PieceType.QUEEN] & ~pos.pieces_Ct(Them); if (b!=0) { // ...and then remove squares not supported by another enemy piece b &= ei.attackedBy[Them, PieceType.PAWN] | ei.attackedBy[Them, PieceType.KNIGHT] | ei.attackedBy[Them, PieceType.BISHOP] | ei.attackedBy[Them, PieceType.ROOK]; if (b!=0) { attackUnits += QueenContactCheck*Bitcount.popcount_Max15(b); } } // Analyse the enemy's safe distance checks for sliders and knights var safe = ~(ei.attackedBy[Us, PieceType.ALL_PIECES] | pos.pieces_Ct(Them)); var b1 = pos.attacks_from_PtS(PieceType.ROOK, ksq) & safe; var b2 = pos.attacks_from_PtS(PieceType.BISHOP, ksq) & safe; // Enemy queen safe checks b = (b1 | b2) & ei.attackedBy[Them, PieceType.QUEEN]; if (b!=0) { attackUnits += QueenCheck*Bitcount.popcount_Max15(b); score -= Checked; } // Enemy rooks safe checks b = b1 & ei.attackedBy[Them, PieceType.ROOK]; if (b!=0) { attackUnits += RookCheck*Bitcount.popcount_Max15(b); score -= Checked; } // Enemy bishops safe checks b = b2 & ei.attackedBy[Them, PieceType.BISHOP]; if (b!=0) { attackUnits += BishopCheck*Bitcount.popcount_Max15(b); score -= Checked; } // Enemy knights safe checks b = pos.attacks_from_PtS(PieceType.KNIGHT, ksq) & ei.attackedBy[Them, PieceType.KNIGHT] & safe; if (b!=0) { attackUnits += KnightCheck*Bitcount.popcount_Max15(b); score -= Checked; } // Finally, extract the king danger score from the KingDanger[] // array and subtract the score from evaluation. score -= KingDanger[Math.Max(Math.Min(attackUnits, 399), 0)]; } if (DoTrace) { add_IdxCtSt(PieceType.KING, Us, score); } return score; }