/// PawnTable::pawn_info() takes a position object as input, computes /// a PawnInfo object, and returns a pointer to it. The result is also stored /// in an hash table, so we don't have to recompute everything when the same /// pawn structure occurs again. internal void probe(Position pos, out PawnEntry e) { Key key = pos.pawn_key(); e = entries[((UInt32)key) & Constants.PawnTableMask]; // If pi.key matches the position's pawn hash key, it means that we // have analysed this pawn structure before, and we can simply return // the information we found the last time instead of recomputing it. if (e.key == key) return; // Initialize PawnInfo entry e.key = key; e.passedPawnsWHITE = e.passedPawnsBLACK = 0; e.kingSquaresWHITE = e.kingSquaresBLACK = SquareC.SQ_NONE; e.halfOpenFilesWHITE = e.halfOpenFilesBLACK = 0xFF; // Calculate pawn attacks Bitboard wPawns = pos.pieces_PTC(PieceTypeC.PAWN, ColorC.WHITE); Bitboard bPawns = pos.pieces_PTC(PieceTypeC.PAWN, ColorC.BLACK); e.pawnAttacksWHITE = ((wPawns & ~Constants.FileHBB) << 9) | ((wPawns & ~Constants.FileABB) << 7); e.pawnAttacksBLACK = ((bPawns & ~Constants.FileHBB) >> 7) | ((bPawns & ~Constants.FileABB) >> 9); // Evaluate pawns for both colors and weight the result e.value = evaluate_pawns(ColorC.WHITE, pos, wPawns, bPawns, e) - evaluate_pawns(ColorC.BLACK, pos, bPawns, wPawns, e); e.value = Utils.apply_weight(e.value, PawnStructureWeight); return; }
/// MaterialTable::material_info() takes a position object as input, /// computes or looks up a MaterialInfo object, and returns a pointer to it. /// If the material configuration is not already present in the table, it /// is stored there, so we don't have to recompute everything when the /// same material configuration occurs again. internal void probe(Position pos, out MaterialEntry e) { var key = pos.material_key(); e = this.entries[((uint)key) & Constants.MaterialTableMask]; // If mi->key matches the position's material hash key, it means that we // have analysed this material configuration before, and we can simply // return the information we found the last time instead of recomputing it. if (e.key == key) { return; } // Initialize MaterialInfo entry var npm = pos.non_pawn_material(ColorC.WHITE) + pos.non_pawn_material(ColorC.BLACK); e.value = 0; e.scalingFunctionWHITE = null; e.scalingFunctionBLACK = null; e.spaceWeight = 0; e.key = key; e.factorWHITE = e.factorBLACK = ScaleFactorC.SCALE_FACTOR_NORMAL; e.gamePhase = npm >= MidgameLimit ? PhaseC.PHASE_MIDGAME : npm <= EndgameLimit ? PhaseC.PHASE_ENDGAME : (((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit)); // Let's look if we have a specialized evaluation function for this // particular material configuration. First we look for a fixed // configuration one, then a generic one if previous search failed. if ((e.evaluationFunction = Endgame.probeValue(key, out e.evaluationFunctionColor)) != null) { return; } if (is_KXK(ColorC.WHITE, pos)) { e.evaluationFunction = Endgame.Endgame_KXK; e.evaluationFunctionColor = ColorC.WHITE; return; } if (is_KXK(ColorC.BLACK, pos)) { e.evaluationFunction = Endgame.Endgame_KXK; e.evaluationFunctionColor = ColorC.BLACK; return; } if ((pos.pieces_PT(PieceTypeC.PAWN) == 0) && (pos.pieces_PT(PieceTypeC.ROOK) == 0) && (pos.pieces_PT(PieceTypeC.QUEEN) == 0)) { // Minor piece endgame with at least one minor piece per side and // no pawns. Note that the case KmmK is already handled by KXK. Debug.Assert( (pos.pieces_PTC(PieceTypeC.KNIGHT, ColorC.WHITE) | pos.pieces_PTC(PieceTypeC.BISHOP, ColorC.WHITE)) != 0); Debug.Assert( (pos.pieces_PTC(PieceTypeC.KNIGHT, ColorC.BLACK) | pos.pieces_PTC(PieceTypeC.BISHOP, ColorC.BLACK)) != 0); if (pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) + pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT) <= 2 && pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP) + pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT) <= 2) { e.evaluationFunction = Endgame.Endgame_KmmKm; e.evaluationFunctionColor = pos.sideToMove; return; } } // OK, we didn't find any special evaluation function for the current // material configuration. Is there a suitable scaling function? // // We face problems when there are several conflicting applicable // scaling functions and we need to decide which one to use. EndgameScaleFactor sf; int c; if ((sf = Endgame.probeScaleFactor(key, out c)) != null) { if (c == ColorC.WHITE) { e.scalingFunctionWHITE = sf; } else { e.scalingFunctionBLACK = sf; } return; } // Generic scaling functions that refer to more then one material // distribution. Should be probed after the specialized ones. // Note that these ones don't return after setting the function. if (is_KBPsKs(ColorC.WHITE, pos)) { e.scalingFunctionWHITE = Endgame.Endgame_KBPsK; } if (is_KBPsKs(ColorC.BLACK, pos)) { e.scalingFunctionBLACK = Endgame.Endgame_KBPsK; } if (is_KQKRPs(ColorC.WHITE, pos)) { e.scalingFunctionWHITE = Endgame.Endgame_KQKRPs; } else if (is_KQKRPs(ColorC.BLACK, pos)) { e.scalingFunctionBLACK = Endgame.Endgame_KQKRPs; } var npm_w = pos.non_pawn_material(ColorC.WHITE); var npm_b = pos.non_pawn_material(ColorC.BLACK); if (npm_w + npm_b == ValueC.VALUE_ZERO) { if (pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 0) { Debug.Assert(pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) >= 2); e.scalingFunctionWHITE = Endgame.Endgame_KPsK; } else if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 0) { Debug.Assert(pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) >= 2); e.scalingFunctionBLACK = Endgame.Endgame_KPsK; } else if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 1 && pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 1) { // This is a special case because we set scaling functions // for both colors instead of only one. e.scalingFunctionWHITE = Endgame.Endgame_KPKP; e.scalingFunctionBLACK = Endgame.Endgame_KPKP; } } // No pawns makes it difficult to win, even with a material advantage if (pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN) == 0 && npm_w - npm_b <= Constants.BishopValueMidgame) { e.factorWHITE = (byte) (npm_w == npm_b || npm_w < Constants.RookValueMidgame ? 0 : NoPawnsSF[Math.Min(pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP), 2)]); } if (pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN) == 0 && npm_b - npm_w <= Constants.BishopValueMidgame) { e.factorBLACK = (byte) (npm_w == npm_b || npm_b < Constants.RookValueMidgame ? 0 : NoPawnsSF[Math.Min(pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP), 2)]); } // Compute the space weight if (npm_w + npm_b >= 2 * Constants.QueenValueMidgame + 4 * Constants.RookValueMidgame + 2 * Constants.KnightValueMidgame) { var minorPieceCount = pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT) + pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) + pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT) + pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP); e.spaceWeight = minorPieceCount * minorPieceCount; } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder // for the bishop pair "extended piece", this allow us to be more flexible // in defining bishop pair bonuses. this.pieceCount[0][0] = pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP) > 1 ? 1 : 0; this.pieceCount[0][1] = pos.piece_count(ColorC.WHITE, PieceTypeC.PAWN); this.pieceCount[0][2] = pos.piece_count(ColorC.WHITE, PieceTypeC.KNIGHT); this.pieceCount[0][3] = pos.piece_count(ColorC.WHITE, PieceTypeC.BISHOP); this.pieceCount[0][4] = pos.piece_count(ColorC.WHITE, PieceTypeC.ROOK); this.pieceCount[0][5] = pos.piece_count(ColorC.WHITE, PieceTypeC.QUEEN); this.pieceCount[1][0] = pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP) > 1 ? 1 : 0; this.pieceCount[1][1] = pos.piece_count(ColorC.BLACK, PieceTypeC.PAWN); this.pieceCount[1][2] = pos.piece_count(ColorC.BLACK, PieceTypeC.KNIGHT); this.pieceCount[1][3] = pos.piece_count(ColorC.BLACK, PieceTypeC.BISHOP); this.pieceCount[1][4] = pos.piece_count(ColorC.BLACK, PieceTypeC.ROOK); this.pieceCount[1][5] = pos.piece_count(ColorC.BLACK, PieceTypeC.QUEEN); e.value = (short)((this.imbalance(ColorC.WHITE) - this.imbalance(ColorC.BLACK)) / 16); }
/// move_to_san() takes a position and a legal Move as input and returns its /// short algebraic notation representation. internal static string move_to_san(Position pos, int m) { if (m == MoveC.MOVE_NONE) { return "(none)"; } if (m == MoveC.MOVE_NULL) { return "(null)"; } Debug.Assert(pos.move_is_legal(m)); Bitboard others, b; Color us = pos.sideToMove; var san = new StringBuilder(); Square from = from_sq(m); Square to = to_sq(m); Piece pc = pos.piece_on(from); PieceType pt = type_of(pc); if (type_of_move(m) == MoveTypeC.CASTLING) { san.Append(to > from ? "O-O" : "O-O-O"); } else { if (pt != PieceTypeC.PAWN) { san.Append(PieceToChar[ColorC.WHITE][pt]); // Upper case // Disambiguation if we have more then one piece of type 'pt' that can // reach 'to' with a legal move. others = b = (pos.attacks_from_PS(pc, to) & pos.pieces_PTC(pt, us)) ^ (ulong)from; while (others != 0) { Move move = make_move(pop_lsb(ref b), to); if (!pos.pl_move_is_legal(move, pos.pinned_pieces())) { others ^= (ulong)from_sq(move); } } if (others != 0) { if ((others & file_bb_S(from)) == 0) { san.Append(file_to_char(file_of(from))); } else if ((others & rank_bb_S(from)) == 0) { san.Append(rank_to_char(rank_of(from))); } else { san.Append(square_to_string(from)); } } } else if (pos.is_capture(m)) { san.Append(file_to_char(file_of(from))); } if (pos.is_capture(m)) { san.Append('x'); } san.Append(square_to_string(to)); if (type_of_move(m) == MoveTypeC.PROMOTION) { san.Append('='); san.Append(PieceToChar[ColorC.WHITE][promotion_type(m)]); } } var ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); if (pos.move_gives_check(m, ci)) { var st = new StateInfo(); pos.do_move(m, st); var mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); san.Append(mlist.pos > 0 ? "+" : "#"); MListBroker.Free(); pos.undo_move(m); } CheckInfoBroker.Free(); return san.ToString(); }
// evaluate_pieces_of_color<>() assigns bonuses and penalties to all the // pieces of a given color. private static int evaluate_pieces_of_color(int Us, bool Trace, Position pos, EvalInfo ei, ref int mobility) { var Them = (Us == ColorC.WHITE ? ColorC.BLACK : ColorC.WHITE); mobility = ScoreC.SCORE_ZERO; // Do not include in mobility squares protected by enemy pawns or occupied by our pieces var mobilityArea = ~(ei.attackedBy[Them][PieceTypeC.PAWN] | pos.byColorBB[Us]); #region Evaluate pieces ulong between = 0; var plPos = 0; int s, ksq; int mob; int f; int score, scores = ScoreC.SCORE_ZERO; var attackedByThemKing = ei.attackedBy[Them][PieceTypeC.KING]; var attackedByThemPawn = ei.attackedBy[Them][PieceTypeC.PAWN]; var kingRingThem = ei.kingRing[Them]; for (var Piece = PieceTypeC.KNIGHT; Piece < PieceTypeC.KING; Piece++) { score = ScoreC.SCORE_ZERO; ei.attackedBy[Us][Piece] = 0; var pl = pos.pieceList[Us][Piece]; plPos = 0; while ((s = pl[plPos++]) != SquareC.SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks if (Piece == PieceTypeC.KNIGHT) { between = Utils.StepAttacksBB_KNIGHT[s]; } else if (Piece == PieceTypeC.QUEEN) { #if X64 b = Utils.BAttacks[s][(((pos.occupied_squares & Utils.BMasks[s]) * Utils.BMagics[s]) >> Utils.BShifts[s])] | Utils.RAttacks[s][(((pos.occupied_squares & Utils.RMasks[s]) * Utils.RMagics[s]) >> Utils.RShifts[s])]; #else between = Utils.bishop_attacks_bb(s, pos.occupied_squares) | Utils.rook_attacks_bb(s, pos.occupied_squares); #endif } else if (Piece == PieceTypeC.BISHOP) { #if X64 b = Utils.BAttacks[s][((( (pos.occupied_squares ^ (pos.byTypeBB[PieceTypeC.QUEEN] & pos.byColorBB[Us])) & Utils.BMasks[s]) * Utils.BMagics[s]) >> Utils.BShifts[s])]; #else between = Utils.bishop_attacks_bb(s, pos.occupied_squares ^ pos.pieces_PTC(PieceTypeC.QUEEN, Us)); #endif } else if (Piece == PieceTypeC.ROOK) { #if X64 b = Utils.RAttacks[s][((( (pos.occupied_squares ^ ((pos.byTypeBB[PieceTypeC.ROOK] | pos.byTypeBB[PieceTypeC.QUEEN]) & pos.byColorBB[Us])) & Utils.RMasks[s]) * Utils.RMagics[s]) >> Utils.RShifts[s])]; #else between = Utils.rook_attacks_bb( s, pos.occupied_squares ^ pos.pieces(PieceTypeC.ROOK, PieceTypeC.QUEEN, Us)); #endif } // Update attack info ei.attackedBy[Us][Piece] |= between; // King attacks if ((between & kingRingThem) != 0) { ei.kingAttackersCount[Us]++; ei.kingAttackersWeight[Us] += KingAttackWeights[Piece]; var bb = (between & attackedByThemKing); //ei.attackedBy[Them][PieceTypeC.KING]); if (bb != 0) { #if X64 bb -= (bb >> 1) & 0x5555555555555555UL; bb = ((bb >> 2) & 0x3333333333333333UL) + (bb & 0x3333333333333333UL); ei.kingAdjacentZoneAttacksCount[Us] += (int)((bb * 0x1111111111111111UL) >> 60); #else ei.kingAdjacentZoneAttacksCount[Us] += Bitcount.popcount_1s_Max15(bb); #endif } } // Mobility #if X64 Bitboard bmob = b & mobilityArea; if (Piece != PieceTypeC.QUEEN) { bmob -= (bmob >> 1) & 0x5555555555555555UL; bmob = ((bmob >> 2) & 0x3333333333333333UL) + (bmob & 0x3333333333333333UL); mob = (int)((bmob * 0x1111111111111111UL) >> 60); } else { bmob -= ((bmob >> 1) & 0x5555555555555555UL); bmob = ((bmob >> 2) & 0x3333333333333333UL) + (bmob & 0x3333333333333333UL); bmob = ((bmob >> 4) + bmob) & 0x0F0F0F0F0F0F0F0FUL; mob = (int)((bmob * 0x0101010101010101UL) >> 56); } #else mob = (Piece != PieceTypeC.QUEEN ? Bitcount.popcount_1s_Max15(between & mobilityArea) : Bitcount.popcount_1s_Full(between & mobilityArea)); #endif mobility += MobilityBonus[Piece][mob]; // Decrease score if we are attacked by an enemy pawn. Remaining part // of threat evaluation must be done later when we have full attack info. if ((attackedByThemPawn & Utils.SquareBB[s]) != 0) { score -= ThreatenedByPawnPenalty[Piece]; } else if ((Piece == PieceTypeC.BISHOP) && ((Utils.PseudoAttacks[Piece][pos.pieceList[Them][PieceTypeC.KING][0]] & Utils.SquareBB[s]) != 0)) { between = Utils.BetweenBB[s][pos.pieceList[Them][PieceTypeC.KING][0]] & pos.occupied_squares; if (!Utils.more_than_one(between)) { score += Utils.make_score(15, 25); } } // Bishop and knight outposts squares if ((Piece == PieceTypeC.BISHOP || Piece == PieceTypeC.KNIGHT) && (((pos.byTypeBB[PieceTypeC.PAWN] & pos.byColorBB[Them]) & Utils.AttackSpanMask[Us][s]) == 0)) { #region Evaluate outposts inlined // evaluate_outposts() evaluates bishop and knight outposts squares // Initial bonus based on square var bonus = OutpostBonus[Piece == PieceTypeC.BISHOP ? 1 : 0][s ^ (Us * 56)]; // Increase bonus if supported by pawn, especially if the opponent has // no minor piece which can exchange the outpost piece. if ((bonus != 0) && ((ei.attackedBy[Us][PieceTypeC.PAWN] & Utils.SquareBB[s]) != 0)) { if (((pos.byTypeBB[PieceTypeC.KNIGHT] & pos.byColorBB[Them]) == 0) && (((((0xAA55AA55AA55AA55UL & Utils.SquareBB[s]) != 0) ? 0xAA55AA55AA55AA55UL : ~0xAA55AA55AA55AA55UL) & (pos.byTypeBB[PieceTypeC.BISHOP] & pos.byColorBB[Them])) == 0)) { bonus += bonus + bonus / 2; } else { bonus += bonus / 2; } } score += ((bonus << 16) + bonus); // Utils.make_score(bonus, bonus); #endregion } if ((Piece == PieceTypeC.ROOK || Piece == PieceTypeC.QUEEN) && Utils.relative_rank_CS(Us, s) >= RankC.RANK_5) { // Major piece on 7th rank if (Utils.relative_rank_CS(Us, s) == RankC.RANK_7 && Utils.relative_rank_CS(Us, pos.king_square(Them)) == RankC.RANK_8) score += (Piece == PieceTypeC.ROOK ? RookOn7thBonus : QueenOn7thBonus); // Major piece attacking pawns on the same rank Bitboard pawns = pos.pieces_PTC(PieceTypeC.PAWN, Them) & Utils.rank_bb_S(s); if (pawns != 0) { score += (Piece == PieceTypeC.ROOK ? RookOnPawnBonus : QueenOnPawnBonus) * Bitcount.popcount_1s_Max15(pawns); } } // Special extra evaluation for bishops if (pos.chess960 && (Piece == PieceTypeC.BISHOP)) { // 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 (s == Utils.relative_square(Us, SquareC.SQ_A1) || s == Utils.relative_square(Us, SquareC.SQ_H1)) { var d = Utils.pawn_push(Us) + (Utils.file_of(s) == FileC.FILE_A ? SquareC.DELTA_E : SquareC.DELTA_W); if (pos.piece_on(s + d) == Utils.make_piece(Us, PieceTypeC.PAWN)) { if (!pos.is_empty(s + d + Utils.pawn_push(Us))) { score -= 2 * TrappedBishopA1H1Penalty; } else if (pos.piece_on(s + 2 * d) == Utils.make_piece(Us, PieceTypeC.PAWN)) { score -= TrappedBishopA1H1Penalty; } else { score -= TrappedBishopA1H1Penalty / 2; } } } } // Special extra evaluation for rooks if (Piece == PieceTypeC.ROOK) { // Open and half-open files f = (s & 7); var halfOpenUs = ((Us == ColorC.WHITE) ? (ei.pi.halfOpenFilesWHITE & (1 << f)) : (ei.pi.halfOpenFilesBLACK & (1 << f))) != 0; if (halfOpenUs) { if (((Them == ColorC.WHITE) ? (ei.pi.halfOpenFilesWHITE & (1 << f)) : (ei.pi.halfOpenFilesBLACK & (1 << f))) != 0) { score += RookOpenFileBonus; } else { score += RookHalfOpenFileBonus; } } // Penalize rooks which are trapped inside a king. Penalize more if // king has lost right to castle. if (mob > 6 || halfOpenUs) { continue; } ksq = pos.pieceList[Us][PieceTypeC.KING][0]; if (((ksq >> 3) ^ (Us * 7)) == RankC.RANK_1 || (ksq >> 3) == (s >> 3)) { if ((ksq & 7) >= FileC.FILE_E) { if (f > (ksq & 7)) { // Is there a half-open file between the king and the edge of the board? if (((Us == ColorC.WHITE) ? (ei.pi.halfOpenFilesWHITE & ~((1 << ((ksq & 7) + 1)) - 1)) : (ei.pi.halfOpenFilesBLACK & ~((1 << ((ksq & 7) + 1)) - 1))) == 0) { score -= ((((pos.st.castleRights & (CastleRightC.WHITE_ANY << (Us << 1))) != 0) ? (TrappedRookPenalty - mob * 16) / 2 : (TrappedRookPenalty - mob * 16)) << 16); } } } else { if (f < (ksq & 7)) { // Is there a half-open file between the king and the edge of the board? if (((Us == ColorC.WHITE) ? (ei.pi.halfOpenFilesWHITE & ((1 << (ksq & 7)) - 1)) : (ei.pi.halfOpenFilesBLACK & ((1 << (ksq & 7)) - 1))) == 0) { score -= ((((pos.st.castleRights & (CastleRightC.WHITE_ANY << (Us << 1))) != 0) ? (TrappedRookPenalty - mob * 16) / 2 : (TrappedRookPenalty - mob * 16)) << 16); } } } } } } scores += score; if (Trace) { TracedScores[Us][Piece] = score; } } #endregion // Sum up all attacked squares ei.attackedBy[Us][0] = ei.attackedBy[Us][PieceTypeC.PAWN] | ei.attackedBy[Us][PieceTypeC.KNIGHT] | ei.attackedBy[Us][PieceTypeC.BISHOP] | ei.attackedBy[Us][PieceTypeC.ROOK] | ei.attackedBy[Us][PieceTypeC.QUEEN] | ei.attackedBy[Us][PieceTypeC.KING]; return scores; }
/// move_to_san() takes a position and a move as input, where it is assumed /// that the move is a legal move for the position. The return value is /// a string containing the move in short algebraic notation. internal static string move_to_san(Position pos, Move m) { if (m == MoveC.MOVE_NONE) return "(none)"; if (m == MoveC.MOVE_NULL) return "(null)"; Debug.Assert(is_ok_M(m)); Bitboard attackers; bool ambiguousMove, ambiguousFile, ambiguousRank; Square sq, from = from_sq(m); Square to = to_sq(m); PieceType pt = type_of(pos.piece_moved(m)); StringBuilder san = new StringBuilder(); if (is_castle(m)) san.Append((to_sq(m) < from_sq(m) ? "O-O-O" : "O-O")); else { if (pt != PieceTypeC.PAWN) { san.Append(piece_type_to_char(pt).ToString()); // Disambiguation if we have more then one piece with destination 'to' // note that for pawns is not needed because starting file is explicit. attackers = pos.attackers_to(to) & pos.pieces_PTC(pt, pos.sideToMove); xor_bit(ref attackers, from); ambiguousMove = ambiguousFile = ambiguousRank = false; while (attackers != 0) { sq = pop_1st_bit(ref attackers); // Pinned pieces are not included in the possible sub-set if (!pos.pl_move_is_legal(make_move(sq, to), pos.pinned_pieces())) continue; if (file_of(sq) == file_of(from)) ambiguousFile = true; if (rank_of(sq) == rank_of(from)) ambiguousRank = true; ambiguousMove = true; } if (ambiguousMove) { if (!ambiguousFile) san.Append(file_to_char(file_of(from))); else if (!ambiguousRank) san.Append(rank_to_char(rank_of(from))); else san.Append(square_to_string(from)); } } if (pos.is_capture(m)) { if (pt == PieceTypeC.PAWN) san.Append(file_to_char(file_of(from))); san.Append('x'); } san.Append(square_to_string(to)); if (is_promotion(m)) { san.Append('='); san.Append(piece_type_to_char(promotion_type(m))); } } CheckInfo ci = CheckInfoBroker.GetObject(); ci.CreateCheckInfo(pos); if (pos.move_gives_check(m, ci)) { StateInfo st = new StateInfo(); pos.do_move(m, st); MList mlist = MListBroker.GetObject(); mlist.pos = 0; Movegen.generate_legal(pos, mlist.moves, ref mlist.pos); san.Append(mlist.pos > 0 ? "+" : "#"); MListBroker.Free(); pos.undo_move(m); } CheckInfoBroker.Free(); return san.ToString(); }
/// K and queen vs K, rook and one or more pawns. It tests for fortress draws with /// a rook on the third rank defended by a pawn. internal static ScaleFactor Endgame_KQKRPs(Color strongerSide, Position pos) { Color weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == Constants.QueenValueMidgame); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.QUEEN) == 1); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) == 0); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.ROOK) == 1); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.PAWN) >= 1); Square kingSq = pos.king_square(weakerSide); if (Utils.relative_rank_CS(weakerSide, kingSq) <= RankC.RANK_2 && Utils.relative_rank_CS(weakerSide, pos.king_square(strongerSide)) >= RankC.RANK_4 && ((pos.pieces_PTC(PieceTypeC.ROOK, weakerSide) & Utils.rank_bb_R(Utils.relative_rank_CR(weakerSide, RankC.RANK_3))) != 0) && ((pos.pieces_PTC(PieceTypeC.PAWN, weakerSide) & Utils.rank_bb_R(Utils.relative_rank_CR(weakerSide, RankC.RANK_2))) != 0) && ((Position.attacks_from_KING(kingSq) & pos.pieces_PTC(PieceTypeC.PAWN, weakerSide)) != 0) ) { Square rsq = pos.pieceList[weakerSide][PieceTypeC.ROOK][0]; if ((Position.attacks_from_PAWN(rsq, strongerSide) & pos.pieces_PTC(PieceTypeC.PAWN, weakerSide)) != 0) return ScaleFactorC.SCALE_FACTOR_DRAW; } return ScaleFactorC.SCALE_FACTOR_NONE; }
// 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. private static int evaluate_unstoppable_pawns(Position pos, EvalInfo ei) { ulong b, b2, blockers, supporters, queeningPath, candidates; int s, blockSq, queeningSquare; int c, winnerSide, loserSide; bool pathDefended, opposed; int pliesToGo = 0, movesToGo, oppMovesToGo = 0, sacptg, blockersCount, minKingDist, kingptg, d; int pliesToQueenWHITE = 256, pliesToQueenBLACK = 256, pliesToQueenWinner = 256; // Step 1. Hunt for unstoppable passed pawns. If we find at least one, // record how many plies are required for promotion. for (c = ColorC.WHITE; c <= ColorC.BLACK; c++) { // Skip if other side has non-pawn pieces if (pos.non_pawn_material(Utils.flip_C(c)) != 0) { continue; } b = ei.pi.passed_pawns(c); while (b != 0) { s = Utils.pop_lsb(ref b); queeningSquare = Utils.relative_square(c, Utils.make_square(Utils.file_of(s), RankC.RANK_8)); queeningPath = Utils.forward_bb(c, s); // Compute plies to queening and check direct advancement movesToGo = Utils.rank_distance(s, queeningSquare) - (Utils.relative_rank_CS(c, s) == RankC.RANK_2 ? 1 : 0); oppMovesToGo = Utils.square_distance(pos.king_square(Utils.flip_C(c)), queeningSquare) - ((c != pos.sideToMove) ? 1 : 0); pathDefended = ((ei.attackedBy[c][0] & 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. Debug.Assert(!pos.in_check()); Debug.Assert((queeningPath & pos.occupied_squares) == (queeningPath & pos.pieces_C(c))); // Add moves needed to free the path from friendly pieces and retest condition movesToGo += Bitcount.popcount_1s_Max15(queeningPath & pos.pieces_C(c)); if (movesToGo >= oppMovesToGo && !pathDefended) { continue; } pliesToGo = 2 * movesToGo - ((c == pos.sideToMove) ? 1 : 0); if (c == ColorC.WHITE) { pliesToQueenWHITE = Math.Min(pliesToQueenWHITE, pliesToGo); } else { pliesToQueenBLACK = Math.Min(pliesToQueenBLACK, 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 (Math.Abs(pliesToQueenWHITE - pliesToQueenBLACK) < 3) { return ScoreC.SCORE_ZERO; } winnerSide = (pliesToQueenWHITE < pliesToQueenBLACK ? ColorC.WHITE : ColorC.BLACK); pliesToQueenWinner = (winnerSide == ColorC.WHITE) ? pliesToQueenWHITE : pliesToQueenBLACK; loserSide = Utils.flip_C(winnerSide); // Step 3. Can the losing side possibly create a new passed pawn and thus prevent the loss? b = candidates = pos.pieces_PTC(PieceTypeC.PAWN, loserSide); while (b != 0) { s = Utils.pop_lsb(ref b); // Compute plies from queening queeningSquare = Utils.relative_square(loserSide, Utils.make_square(Utils.file_of(s), RankC.RANK_8)); movesToGo = Utils.rank_distance(s, queeningSquare) - ((Utils.relative_rank_CS(loserSide, s) == RankC.RANK_2) ? 1 : 0); pliesToGo = 2 * movesToGo - ((loserSide == pos.sideToMove) ? 1 : 0); // Check if (without even considering any obstacles) we're too far away or doubled if ((pliesToQueenWinner + 3 <= pliesToGo) || ((Utils.forward_bb(loserSide, s) & pos.pieces_PTC(PieceTypeC.PAWN, loserSide)) != 0)) { Utils.xor_bit(ref 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)) != 0) { return ScoreC.SCORE_ZERO; } // Step 4. Check new passed pawn creation through king capturing and pawn sacrifices b = candidates; while (b != 0) { s = Utils.pop_lsb(ref b); sacptg = blockersCount = 0; minKingDist = kingptg = 256; // Compute plies from queening queeningSquare = Utils.relative_square(loserSide, Utils.make_square(Utils.file_of(s), RankC.RANK_8)); movesToGo = Utils.rank_distance(s, queeningSquare) - ((Utils.relative_rank_CS(loserSide, s) == RankC.RANK_2) ? 1 : 0); pliesToGo = 2 * movesToGo - ((loserSide == pos.sideToMove) ? 1 : 0); // Generate list of blocking pawns and supporters supporters = Utils.adjacent_files_bb(Utils.file_of(s)) & candidates; opposed = (Utils.forward_bb(loserSide, s) & pos.pieces_PTC(PieceTypeC.PAWN, winnerSide)) != 0; blockers = Utils.passed_pawn_mask(loserSide, s) & pos.pieces_PTC(PieceTypeC.PAWN, winnerSide); Debug.Assert(blockers != 0); // How many plies does it take to remove all the blocking pawns? while (blockers != 0) { blockSq = Utils.pop_lsb(ref 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 & Utils.in_front_bb_CS(winnerSide, blockSq + Utils.pawn_push(winnerSide)); while (b2 != 0) // This while-loop could be replaced with LSB/MSB (depending on color) { d = Utils.square_distance(blockSq, Utils.pop_lsb(ref b2)) - 2; movesToGo = Math.Min(movesToGo, d); } } // Check pawns that can be sacrificed against the blocking pawn b2 = Utils.attack_span_mask(winnerSide, blockSq) & candidates & ~(1UL << s); while (b2 != 0) // This while-loop could be replaced with LSB/MSB (depending on color) { d = Utils.square_distance(blockSq, Utils.pop_lsb(ref b2)) - 2; movesToGo = Math.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 = Utils.square_distance(pos.king_square(loserSide), blockSq); minKingDist = Math.Min(minKingDist, d); kingptg = (minKingDist + blockersCount) * 2; } // Check if pawn sacrifice plan _may_ save the day if (pliesToQueenWinner + 3 > pliesToGo + sacptg) { return ScoreC.SCORE_ZERO; } // Check if king capture plan _may_ save the day (contains some false positives) if (pliesToQueenWinner + 3 > pliesToGo + kingptg) { return ScoreC.SCORE_ZERO; } } // Winning pawn is unstoppable and will promote as first, return big score var score = Utils.make_score(0, 0x500 - 0x20 * pliesToQueenWinner); return winnerSide == ColorC.WHITE ? score : -score; }
/// K and two or more pawns vs K. There is just a single rule here: If all pawns /// are on the same rook file and are blocked by the defending king, it's a draw. internal static ScaleFactor Endgame_KPsK(Color strongerSide, Position pos) { Color weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == ValueC.VALUE_ZERO); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) >= 2); Debug.Assert(pos.non_pawn_material(weakerSide) == ValueC.VALUE_ZERO); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.PAWN) == 0); Square ksq = pos.king_square(weakerSide); Bitboard pawns = pos.pieces_PTC(PieceTypeC.PAWN, strongerSide); // Are all pawns on the 'a' file? if ((pawns & ~Constants.FileABB) == 0) { // Does the defending king block the pawns? if (Utils.square_distance(ksq, Utils.relative_square(strongerSide, SquareC.SQ_A8)) <= 1 || (Utils.file_of(ksq) == FileC.FILE_A && ((Utils.in_front_bb_CS(strongerSide, ksq)) & pawns) == 0)) return ScaleFactorC.SCALE_FACTOR_DRAW; } // Are all pawns on the 'h' file? else if ((pawns & ~Constants.FileHBB) == 0) { // Does the defending king block the pawns? if (Utils.square_distance(ksq, Utils.relative_square(strongerSide, SquareC.SQ_H8)) <= 1 || (Utils.file_of(ksq) == FileC.FILE_H && ((Utils.in_front_bb_CS(strongerSide, ksq)) & pawns) == 0)) return ScaleFactorC.SCALE_FACTOR_DRAW; } return ScaleFactorC.SCALE_FACTOR_NONE; }
/// K, bishop and one or more pawns vs K. It checks for draws with rook pawns and /// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW /// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling /// will be used. internal static ScaleFactor Endgame_KBPsK(Color strongerSide, Position pos) { Color weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) >= 1); // No Debug.Assertions about the material of weakerSide, because we want draws to // be detected even when the weaker side has some pawns. Bitboard pawns = pos.pieces_PTC(PieceTypeC.PAWN, strongerSide); File pawnFile = Utils.file_of(pos.pieceList[strongerSide][PieceTypeC.PAWN][0]); // All pawns are on a single rook file ? if ((pawnFile == FileC.FILE_A || pawnFile == FileC.FILE_H) && (((pawns & ~Utils.file_bb_F(pawnFile))) == 0)) { Square bishopSq = pos.pieceList[strongerSide][PieceTypeC.BISHOP][0]; Square queeningSq = Utils.relative_square(strongerSide, Utils.make_square(pawnFile, RankC.RANK_8)); Square kingSq = pos.king_square(weakerSide); if (Utils.opposite_colors(queeningSq, bishopSq) && Math.Abs(Utils.file_of(kingSq) - pawnFile) <= 1) { // The bishop has the wrong color, and the defending king is on the // file of the pawn(s) or the adjacent file. Find the rank of the // frontmost pawn. Rank rank; if (strongerSide == ColorC.WHITE) { for (rank = RankC.RANK_7; (((Utils.rank_bb_R(rank) & pawns)) == 0); rank--) { } Debug.Assert(rank >= RankC.RANK_2 && rank <= RankC.RANK_7); } else { for (rank = RankC.RANK_2; (((Utils.rank_bb_R(rank) & pawns)) == 0); rank++) { } rank = (rank ^ 7); // HACK to get the relative rank Debug.Assert(rank >= RankC.RANK_2 && rank <= RankC.RANK_7); } // If the defending king has distance 1 to the promotion square or // is placed somewhere in front of the pawn, it's a draw. if (Utils.square_distance(kingSq, queeningSq) <= 1 || Utils.relative_rank_CS(strongerSide, kingSq) >= rank) return ScaleFactorC.SCALE_FACTOR_DRAW; } } return ScaleFactorC.SCALE_FACTOR_NONE; }
/// K, bishop and two pawns vs K and bishop. It detects a few basic draws with /// opposite-colored bishops. internal static ScaleFactor Endgame_KBPPKB(Color strongerSide, Position pos) { Color weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) == 2); Debug.Assert(pos.non_pawn_material(weakerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.PAWN) == 0); Square wbsq = pos.pieceList[strongerSide][PieceTypeC.BISHOP][0]; Square bbsq = pos.pieceList[weakerSide][PieceTypeC.BISHOP][0]; if (!Utils.opposite_colors(wbsq, bbsq)) return ScaleFactorC.SCALE_FACTOR_NONE; Square ksq = pos.king_square(weakerSide); Square psq1 = pos.pieceList[strongerSide][PieceTypeC.PAWN][0]; Square psq2 = pos.pieceList[strongerSide][PieceTypeC.PAWN][1]; Rank r1 = Utils.rank_of(psq1); Rank r2 = Utils.rank_of(psq2); Square blockSq1, blockSq2; if (Utils.relative_rank_CS(strongerSide, psq1) > Utils.relative_rank_CS(strongerSide, psq2)) { blockSq1 = psq1 + Utils.pawn_push(strongerSide); blockSq2 = Utils.make_square(Utils.file_of(psq2), Utils.rank_of(psq1)); } else { blockSq1 = psq2 + Utils.pawn_push(strongerSide); blockSq2 = Utils.make_square(Utils.file_of(psq1), Utils.rank_of(psq2)); } switch (Utils.file_distance(psq1, psq2)) { case 0: // Both pawns are on the same file. Easy draw if defender firmly controls // some square in the frontmost pawn's path. if (Utils.file_of(ksq) == Utils.file_of(blockSq1) && Utils.relative_rank_CS(strongerSide, ksq) >= Utils.relative_rank_CS(strongerSide, blockSq1) && Utils.opposite_colors(ksq, wbsq)) return ScaleFactorC.SCALE_FACTOR_DRAW; else return ScaleFactorC.SCALE_FACTOR_NONE; case 1: // Pawns on adjacent files. Draw if 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 && Utils.opposite_colors(ksq, wbsq) && (bbsq == blockSq2 || (((pos.attacks_from_BISHOP(blockSq2) & pos.pieces_PTC(PieceTypeC.BISHOP, weakerSide))) != 0) || Math.Abs(r1 - r2) >= 2)) return ScaleFactorC.SCALE_FACTOR_DRAW; else if (ksq == blockSq2 && Utils.opposite_colors(ksq, wbsq) && (bbsq == blockSq1 || (((pos.attacks_from_BISHOP(blockSq1) & pos.pieces_PTC(PieceTypeC.BISHOP, weakerSide)))) != 0)) return ScaleFactorC.SCALE_FACTOR_DRAW; else return ScaleFactorC.SCALE_FACTOR_NONE; default: // The pawns are not on the same file or adjacent files. No scaling. return ScaleFactorC.SCALE_FACTOR_NONE; } }
/// K, bishop and a pawn vs K and a bishop. There are two rules: If the defending /// king is somewhere along the path of the pawn, and the square of the king is /// not of the same color as the stronger side's bishop, it's a draw. If the two /// bishops have opposite color, it's almost always a draw. internal static ScaleFactor Endgame_KBPKB(Color strongerSide, Position pos) { Color weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) == 1); Debug.Assert(pos.non_pawn_material(weakerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(weakerSide, PieceTypeC.PAWN) == 0); Square pawnSq = pos.pieceList[strongerSide][PieceTypeC.PAWN][0]; Square strongerBishopSq = pos.pieceList[strongerSide][PieceTypeC.BISHOP][0]; Square weakerBishopSq = pos.pieceList[weakerSide][PieceTypeC.BISHOP][0]; Square weakerKingSq = pos.king_square(weakerSide); // Case 1: Defending king blocks the pawn, and cannot be driven away if (Utils.file_of(weakerKingSq) == Utils.file_of(pawnSq) && Utils.relative_rank_CS(strongerSide, pawnSq) < Utils.relative_rank_CS(strongerSide, weakerKingSq) && (Utils.opposite_colors(weakerKingSq, strongerBishopSq) || Utils.relative_rank_CS(strongerSide, weakerKingSq) <= RankC.RANK_6)) return ScaleFactorC.SCALE_FACTOR_DRAW; // Case 2: Opposite colored bishops if (Utils.opposite_colors(strongerBishopSq, weakerBishopSq)) { // 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 (Utils.relative_rank_CS(strongerSide, pawnSq) <= RankC.RANK_5) return ScaleFactorC.SCALE_FACTOR_DRAW; else { Bitboard path = Utils.forward_bb(strongerSide, pawnSq); if ((path & pos.pieces_PTC(PieceTypeC.KING, weakerSide)) != 0) return ScaleFactorC.SCALE_FACTOR_DRAW; if (((pos.attacks_from_BISHOP(weakerBishopSq) & path) != 0) && Utils.square_distance(weakerBishopSq, pawnSq) >= 3) return ScaleFactorC.SCALE_FACTOR_DRAW; } } return ScaleFactorC.SCALE_FACTOR_NONE; }
/// K, bishop and one or more pawns vs K. It checks for draws with rook pawns and /// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW /// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling /// will be used. internal static int Endgame_KBPsK(int strongerSide, Position pos) { var weakerSide = strongerSide ^ 1; Debug.Assert(pos.non_pawn_material(strongerSide) == Constants.BishopValueMidgame); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.BISHOP) == 1); Debug.Assert(pos.piece_count(strongerSide, PieceTypeC.PAWN) >= 1); // No Debug.Assertions about the material of weakerSide, because we want draws to // be detected even when the weaker side has some pawns. var pawns = pos.pieces_PTC(PieceTypeC.PAWN, strongerSide); var pawnFile = Utils.file_of(pos.pieceList[strongerSide][PieceTypeC.PAWN][0]); // All pawns are on a single rook file ? if ((pawnFile == FileC.FILE_A || pawnFile == FileC.FILE_H) && (((pawns & ~Utils.file_bb_F(pawnFile))) == 0)) { var bishopSq = pos.pieceList[strongerSide][PieceTypeC.BISHOP][0]; var queeningSq = Utils.relative_square(strongerSide, Utils.make_square(pawnFile, RankC.RANK_8)); var kingSq = pos.king_square(weakerSide); if (Utils.opposite_colors(queeningSq, bishopSq) && Math.Abs(Utils.file_of(kingSq) - pawnFile) <= 1) { // The bishop has the wrong color, and the defending king is on the // file of the pawn(s) or the adjacent file. Find the rank of the // frontmost pawn. int rank; if (strongerSide == ColorC.WHITE) { for (rank = RankC.RANK_7; (((Utils.rank_bb_R(rank) & pawns)) == 0); rank--) { } Debug.Assert(rank >= RankC.RANK_2 && rank <= RankC.RANK_7); } else { for (rank = RankC.RANK_2; (((Utils.rank_bb_R(rank) & pawns)) == 0); rank++) { } rank = (rank ^ 7); // HACK to get the relative rank Debug.Assert(rank >= RankC.RANK_2 && rank <= RankC.RANK_7); } // If the defending king has distance 1 to the promotion square or // is placed somewhere in front of the pawn, it's a draw. if (Utils.square_distance(kingSq, queeningSq) <= 1 || Utils.relative_rank_CS(strongerSide, kingSq) >= rank) { return ScaleFactorC.SCALE_FACTOR_DRAW; } } } // All pawns on same B or G file? Then potential draw if ((pawnFile == FileC.FILE_B || pawnFile == FileC.FILE_G) && (pos.pieces_PT(PieceTypeC.PAWN) & ~Utils.file_bb_F(pawnFile)) == 0 && pos.non_pawn_material(weakerSide) == 0 && pos.piece_count(weakerSide, PieceTypeC.PAWN) >= 1) { // Get weaker pawn closest to opponent's queening square Bitboard wkPawns = pos.pieces_PTC(PieceTypeC.PAWN, weakerSide); Square weakerPawnSq = strongerSide == ColorC.WHITE ? Utils.msb(wkPawns) : Utils.lsb(wkPawns); Square strongerKingSq = pos.king_square(strongerSide); Square weakerKingSq = pos.king_square(weakerSide); Square bishopSq = pos.pieceList[strongerSide][PieceTypeC.BISHOP][0]; // Draw if weaker pawn is on rank 7, bishop can't attack the pawn, and // weaker king can stop opposing opponent's king from penetrating. if (Utils.relative_rank_CS(strongerSide, weakerPawnSq) == RankC.RANK_7 && Utils.opposite_colors(bishopSq, weakerPawnSq) && Utils.square_distance(weakerPawnSq, weakerKingSq) <= Utils.square_distance(weakerPawnSq, strongerKingSq)) return ScaleFactorC.SCALE_FACTOR_DRAW; } return ScaleFactorC.SCALE_FACTOR_NONE; }