internal override ScaleFactor GetScaleFactor(Position pos) { Debug.Assert(verify_material(pos, strongSide, Value.RookValueMg, 1)); Debug.Assert(verify_material(pos, weakSide, Value.BishopValueMg, 0)); // Test for a rook pawn if ((pos.pieces_Pt(PieceType.PAWN) & (Bitboard.FileABB | Bitboard.FileHBB))!=0) { var ksq = pos.square(PieceType.KING, weakSide); var bsq = pos.square(PieceType.BISHOP, weakSide); var psq = pos.square(PieceType.PAWN, strongSide); var rk = Rank.relative_rank_CtSt(strongSide, psq); var push = Square.pawn_push(strongSide); // If the pawn is on the 5th rank and the pawn (currently) is on // the same color square as the bishop then there is a chance of // a fortress. Depending on the king position give a moderate // reduction or a stronger one if the defending king is near the // corner but not trapped there. if (rk == Rank.RANK_5 && !Square.opposite_colors(bsq, psq)) { var d = Utils.distance_Square(psq + 3*push, ksq); if (d <= 2 && !(d == 0 && ksq == pos.square(PieceType.KING, strongSide) + 2*push)) { return (ScaleFactor) (24); } return (ScaleFactor) (48); } // When the pawn has moved to the 6th rank we can be fairly sure // it's drawn if the bishop attacks the square in front of the // pawn from a reasonable distance and the defending king is near // the corner if (rk == Rank.RANK_6 && Utils.distance_Square(psq + 2*push, ksq) <= 1 && Bitboard.AndWithSquare(Utils.PseudoAttacks[PieceType.BISHOP, bsq], (psq + push))!=0 && Utils.distance_File(bsq, psq) >= 2) { return (ScaleFactor) (8); } } 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; }
/// 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; }
/// Material::probe() looks up the current position's material configuration in /// the material hash table. It returns a pointer to the Entry if the position /// is found. Otherwise a new Entry is computed and stored there, so we don't /// have to recompute all when the same material configuration occurs again. internal static MaterialEntry probe(Position pos) { var key = pos.material_key(); MaterialEntry e; if (!pos.this_thread().materialTable.TryGetValue(key, out e)) { e = new MaterialEntry(); pos.this_thread().materialTable.Add(key, e); } else if (e.key == key) { return e; } e.reset(); e.key = key; e.factor[Color.WHITE] = e.factor[Color.BLACK] = (ushort) ScaleFactor.SCALE_FACTOR_NORMAL; e.gamePhase = pos.game_phase(); // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then // for a generic one if the previous search failed. if ((e.evaluationFunction = pos.this_thread().endgames.probeEndgameValue(key)) != null) { return e; } foreach (var c in Color.AllColors) { if (is_KXK(pos, c)) { e.evaluationFunction = EvaluateKXK[c]; return e; } } // OK, we didn't find any special evaluation function for the current material // configuration. Is there a suitable specialized scaling function? EndgameScaleFactor sf; if ((sf = pos.this_thread().endgames.probeEndgameScaleFactor(key)) != null) { e.scalingFunction[sf.strong_side()] = sf; // Only strong color assigned return e; } // We didn't find any specialized scaling function, so fall back on generic // ones that refer to more than one material distribution. Note that in this // case we don't return after setting the function. foreach (var c in Color.AllColors) { if (is_KBPsKs(pos, c)) { e.scalingFunction[c] = ScaleKBPsK[c]; } else if (is_KQKRPs(pos, c)) { e.scalingFunction[c] = ScaleKQKRPs[c]; } } var npm_w = pos.non_pawn_material(Color.WHITE); var npm_b = pos.non_pawn_material(Color.BLACK); if (npm_w + npm_b == Value.VALUE_ZERO && (pos.pieces_Pt(PieceType.PAWN) != 0)) // Only pawns on the board { if (pos.count(PieceType.PAWN, Color.BLACK) == 0) { Debug.Assert(pos.count(PieceType.PAWN, Color.WHITE) >= 2); e.scalingFunction[Color.WHITE] = ScaleKPsK[Color.WHITE]; } else if (pos.count(PieceType.PAWN, Color.WHITE) == 0) { Debug.Assert(pos.count(PieceType.PAWN, Color.BLACK) >= 2); e.scalingFunction[Color.BLACK] = ScaleKPsK[Color.BLACK]; } else if (pos.count(PieceType.PAWN, Color.WHITE) == 1 && pos.count(PieceType.PAWN, Color.BLACK) == 1) { // This is a special case because we set scaling functions // for both colors instead of only one. e.scalingFunction[Color.WHITE] = ScaleKPKP[Color.WHITE]; e.scalingFunction[Color.BLACK] = ScaleKPKP[Color.BLACK]; } } // Zero or just one pawn makes it difficult to win, even with a small material // advantage. This catches some trivial draws like KK, KBK and KNK and gives a // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). if (pos.count(PieceType.PAWN, Color.WHITE) == 0 && npm_w - npm_b <= Value.BishopValueMg) { e.factor[Color.WHITE] = (ushort) (npm_w < Value.RookValueMg ? (ushort) ScaleFactor.SCALE_FACTOR_DRAW : npm_b <= Value.BishopValueMg ? 4 : 14); } if (pos.count(PieceType.PAWN, Color.BLACK) == 0 && npm_b - npm_w <= Value.BishopValueMg) { e.factor[Color.BLACK] = (ushort) (npm_b < Value.RookValueMg ? (ushort) ScaleFactor.SCALE_FACTOR_DRAW : npm_w <= Value.BishopValueMg ? 4 : 14); } if (pos.count(PieceType.PAWN, Color.WHITE) == 1 && npm_w - npm_b <= Value.BishopValueMg) { e.factor[Color.WHITE] = (ushort) ScaleFactor.SCALE_FACTOR_ONEPAWN; } if (pos.count(PieceType.PAWN, Color.BLACK) == 1 && npm_b - npm_w <= Value.BishopValueMg) { e.factor[Color.BLACK] = (ushort) ScaleFactor.SCALE_FACTOR_ONEPAWN; } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder // for the bishop pair "extended piece", which allows us to be more flexible // in defining bishop pair bonuses. int[][] PieceCount = { new[] { pos.count(PieceType.BISHOP, Color.WHITE) > 1 ? 1 : 0, pos.count(PieceType.PAWN, Color.WHITE), pos.count(PieceType.KNIGHT, Color.WHITE), pos.count(PieceType.BISHOP, Color.WHITE), pos.count(PieceType.ROOK, Color.WHITE), pos.count(PieceType.QUEEN, Color.WHITE) }, new[] { pos.count(PieceType.BISHOP, Color.BLACK) > 1 ? 1 : 0, pos.count(PieceType.PAWN, Color.BLACK), pos.count(PieceType.KNIGHT, Color.BLACK), pos.count(PieceType.BISHOP, Color.BLACK), pos.count(PieceType.ROOK, Color.BLACK), pos.count(PieceType.QUEEN, Color.BLACK) } }; e.value = (short) ((imbalance(Color.WHITE, PieceCount) - imbalance(Color.BLACK, PieceCount))/16); return e; }
/// 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_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); }