// init_eval_info() initializes king bitboards for given color adding // pawn attacks. To be done at the beginning of the evaluation. public static void init_eval_info(Position pos, EvalInfo ei, Color Us) { Color Them = (Us == ColorS.WHITE ? ColorS.BLACK : ColorS.WHITE); Square Down = (Us == ColorS.WHITE ? SquareS.DELTA_S : SquareS.DELTA_N); ei.pinnedPieces[Us] = pos.pinned_pieces(Us); Bitboard b = ei.attackedBy[Them][PieceTypeS.KING] = pos.attacks_from_square_piecetype(pos.king_square(Them), PieceTypeS.KING); ei.attackedBy[Us][PieceTypeS.ALL_PIECES] = ei.attackedBy[Us][PieceTypeS.PAWN] = ei.pi.pawn_attacks(Us); // Init king safety tables only if we are going to use them if (pos.count(Us, PieceTypeS.QUEEN) != 0 && pos.non_pawn_material(Us) > ValueS.QueenValueMg + ValueS.PawnValueMg) { ei.kingRing[Them] = b | BitBoard.shift_bb(b, Down); b &= ei.attackedBy[Us][PieceTypeS.PAWN]; ei.kingAttackersCount[Us] = (b != 0) ? Bitcount.popcount_Max15(b) : 0; ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; } else { ei.kingRing[Them] = 0; ei.kingAttackersCount[Us] = 0; } }
public static Score evaluate(Position pos, Pawns.Entry e, Color Us) { Color Them = (Us == ColorS.WHITE ? ColorS.BLACK : ColorS.WHITE); Square Up = (Us == ColorS.WHITE ? SquareS.DELTA_N : SquareS.DELTA_S); Square Right = (Us == ColorS.WHITE ? SquareS.DELTA_NE : SquareS.DELTA_SW); Square Left = (Us == ColorS.WHITE ? SquareS.DELTA_NW : SquareS.DELTA_SE); Bitboard b, p, doubled; Square s; File f; Rank r; bool passed, isolated, opposed, connected, backward, candidate, unsupported; Score value = ScoreS.SCORE_ZERO; Square[] pl = pos.list(Us, PieceTypeS.PAWN); int plPos = 0; Bitboard ourPawns = pos.pieces_color_piecetype(Us, PieceTypeS.PAWN); Bitboard theirPawns = pos.pieces_color_piecetype(Them, PieceTypeS.PAWN); e.passedPawns[Us] = e.candidatePawns[Us] = 0; e.kingSquares[Us] = SquareS.SQ_NONE; e.semiopenFiles[Us] = 0xFF; e.pawnAttacks[Us] = BitBoard.shift_bb(ourPawns, Right) | BitBoard.shift_bb(ourPawns, Left); e.pawnsOnSquares[Us][ColorS.BLACK] = Bitcount.popcount_Max15(ourPawns & BitBoard.DarkSquares); e.pawnsOnSquares[Us][ColorS.WHITE] = pos.count(Us, PieceTypeS.PAWN) - e.pawnsOnSquares[Us][ColorS.BLACK]; // Loop through all pawns of the current color and score each pawn while ((s = pl[plPos++]) != SquareS.SQ_NONE) { Debug.Assert(pos.piece_on(s) == Types.make_piece(Us, PieceTypeS.PAWN)); f = Types.file_of(s); // This file cannot be semi-open e.semiopenFiles[Us] &= ~(1 << f); // Previous rank p = BitBoard.rank_bb_square(s - Types.pawn_push(Us)); // Our rank plus previous one b = BitBoard.rank_bb_square(s) | p; // Flag the pawn as passed, isolated, doubled, // unsupported or connected (but not the backward one). connected = (ourPawns & BitBoard.adjacent_files_bb(f) & b) != 0; unsupported = (0 == (ourPawns & BitBoard.adjacent_files_bb(f) & p)); isolated = (0 == (ourPawns & BitBoard.adjacent_files_bb(f))); doubled = ourPawns & BitBoard.forward_bb(Us, s); opposed = (theirPawns & BitBoard.forward_bb(Us, s)) != 0; passed = (0 == (theirPawns & BitBoard.passed_pawn_mask(Us, s))); // Test for backward pawn. // If the pawn is passed, isolated, or connected it cannot be // backward. If there are friendly pawns behind on adjacent files // or if it can capture an enemy pawn it cannot be backward either. if ((passed | isolated | connected) || (ourPawns & BitBoard.pawn_attack_span(Them, s)) != 0 || (pos.attacks_from_pawn(s, Us) & theirPawns) != 0) { backward = false; } else { // We now know that 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 = BitBoard.pawn_attack_span(Us, s) & (ourPawns | theirPawns); b = BitBoard.pawn_attack_span(Us, s) & BitBoard.rank_bb_square(BitBoard.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(b, Up)) & theirPawns) != 0; } Debug.Assert(opposed | passed | (BitBoard.pawn_attack_span(Us, s) & theirPawns) != 0); // A not-passed pawn is a candidate to become passed, if it is free to // advance and if the number of friendly pawns beside or behind this // pawn on adjacent files is higher than or equal to the number of // enemy pawns in the forward direction on the adjacent files. candidate = !(opposed | passed | backward | isolated) && (b = BitBoard.pawn_attack_span(Them, s + Types.pawn_push(Us)) & ourPawns) != 0 && Bitcount.popcount_Max15(b) >= Bitcount.popcount_Max15(BitBoard.pawn_attack_span(Us, s) & theirPawns); // 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 && 0 == doubled) { e.passedPawns[Us] |= BitBoard.SquareBB[s]; } // Score this pawn if (isolated) { value -= Isolated[opposed ? 1 : 0][f]; } if (unsupported && !isolated) { value -= UnsupportedPawnPenalty; } if (doubled != 0) { value -= Types.divScore(Doubled[f], BitBoard.rank_distance(s, BitBoard.lsb(doubled))); } if (backward) { value -= Backward[opposed ? 1 : 0][f]; } if (connected) { value += Connected[f][Types.relative_rank_square(Us, s)]; } if (candidate) { value += CandidatePassed[Types.relative_rank_square(Us, s)]; if (0 == doubled) { e.candidatePawns[Us] |= BitBoard.SquareBB[s]; } } } // In endgame it's better to have pawns on both wings. So give a bonus according // to file distance between left and right outermost pawns. if (pos.count(Us, PieceTypeS.PAWN) > 1) { b = (Bitboard)(e.semiopenFiles[Us] ^ 0xFF); value += PawnsFileSpan * (BitBoard.msb(b) - BitBoard.lsb(b)); } return(value); }
// init_magics() computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we // use the so called "fancy" approach. public static void init_magics(PieceType pt, Bitboard[][] attacks, Bitboard[] magics, Bitboard[] masks, uint[] shifts, Square[] deltas, Fn index) { int[][] MagicBoosters = new int[2][] { new int[] { 969, 1976, 2850, 542, 2069, 2852, 1708, 164 }, new int[] { 3101, 552, 3555, 926, 834, 26, 2131, 1117 } }; RKISS rk = new RKISS(); Bitboard[] occupancy = new UInt64[4096], reference = new UInt64[4096]; Bitboard edges, b; int i, size, booster; for (Square s = SquareS.SQ_A1; s <= SquareS.SQ_H8; s++) { // Board edges are not considered in the relevant occupancies edges = ((BitBoard.Rank1BB | BitBoard.Rank8BB) & ~BitBoard.rank_bb_square(s)) | ((BitBoard.FileABB | BitBoard.FileHBB) & ~BitBoard.file_bb_square(s)); // Given a square 's', the mask is the bitboard of sliding attacks from // 's' computed on an empty board. The index must be big enough to contain // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. masks[s] = sliding_attack(deltas, s, 0) & ~edges; shifts[s] = 32 - (uint)Bitcount.popcount_Max15(masks[s]); // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = 0; size = 0; do { occupancy[size] = b; reference[size] = sliding_attack(deltas, s, b); size++; b = (b - masks[s]) & masks[s]; } while (b != 0); // Set the offset for the table of the next square. We have individual // table sizes for each square with "Fancy Magic Bitboards". attacks[s] = new Bitboard[size]; booster = MagicBoosters[0][Types.rank_of(s)]; // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. do { do { magics[s] = rk.magic_rand(booster); }while (Bitcount.popcount_Max15((magics[s] * masks[s]) >> 56) < 6); Array.Clear(attacks[s], 0, size); // A good magic must map every possible occupancy to an index that // looks up the correct sliding attack in the attacks[s] database. // Note that we build up the database for square 's' as a side // effect of verifying the magic. for (i = 0; i < size; i++) { Bitboard attack = attacks[s][index(s, occupancy[i], pt)]; if (attack != 0 && attack != reference[i]) { break; } Debug.Assert(reference[i] != 0); //attack = reference[i]; attacks[s][index(s, occupancy[i], pt)] = reference[i]; } } while (i != size); } }
// evaluate_threats() assigns bonuses according to the type of attacking piece // and the type of attacked one. public static Score evaluate_threats(Position pos, EvalInfo ei, Color Us, bool Trace) { Color Them = (Us == ColorS.WHITE ? ColorS.BLACK : ColorS.WHITE); Bitboard b, weakEnemies; Score score = ScoreS.SCORE_ZERO; // Enemies not defended by a pawn and under our attack weakEnemies = pos.pieces_color(Them) & ~ei.attackedBy[Them][PieceTypeS.PAWN] & ei.attackedBy[Us][PieceTypeS.ALL_PIECES]; // Add a bonus according if the attacking pieces are minor or major if (weakEnemies != 0) { b = weakEnemies & (ei.attackedBy[Us][PieceTypeS.PAWN] | ei.attackedBy[Us][PieceTypeS.KNIGHT] | ei.attackedBy[Us][PieceTypeS.BISHOP]); if (b != 0) { score += Threat[0][Types.type_of_piece(pos.piece_on(BitBoard.lsb(b)))]; } b = weakEnemies & (ei.attackedBy[Us][PieceTypeS.ROOK] | ei.attackedBy[Us][PieceTypeS.QUEEN]); if (b != 0) { score += Threat[1][Types.type_of_piece(pos.piece_on(BitBoard.lsb(b)))]; } b = weakEnemies & ~ei.attackedBy[Them][PieceTypeS.ALL_PIECES]; if (b != 0) { score += BitBoard.more_than_one(b) ? Hanging[Us != pos.side_to_move()?1:0] * Bitcount.popcount_Max15(b) : Hanging[Us == pos.side_to_move()?1:0]; } } if (Trace) { Tracing.terms[Us][TermsS.THREAT] = score; } return(score); }
// evaluate_king() assigns bonuses and penalties to a king of a given color public static Score evaluate_king(Position pos, EvalInfo ei, Color Us, bool Trace) { Color Them = (Us == ColorS.WHITE ? ColorS.BLACK : ColorS.WHITE); Bitboard undefended, b, b1, b2, safe; int attackUnits; Square ksq = pos.king_square(Us); // King shelter and enemy pawns storm Score score = ei.pi.king_safety(pos, ksq, Us); // 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 undefended = ei.attackedBy[Them][PieceTypeS.ALL_PIECES] & ei.attackedBy[Us][PieceTypeS.KING] & ~(ei.attackedBy[Us][PieceTypeS.PAWN] | ei.attackedBy[Us][PieceTypeS.KNIGHT] | ei.attackedBy[Us][PieceTypeS.BISHOP] | ei.attackedBy[Us][PieceTypeS.ROOK] | ei.attackedBy[Us][PieceTypeS.QUEEN]); // Initialize the 'attackUnits' variable, which is used later on as an // index to 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). attackUnits = Math.Min(20, (ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) / 2) + 3 * (ei.kingAdjacentZoneAttacksCount[Them] + Bitcount.popcount_Max15(undefended)) + 2 * (ei.pinnedPieces[Us] != 0 ? 1 : 0) - Types.mg_value(score) / 32; // Analyse the enemy's safe queen contact checks. Firstly, find the // undefended squares around the king that are attacked by the enemy's // queen... b = undefended & ei.attackedBy[Them][PieceTypeS.QUEEN] & ~pos.pieces_color(Them); if (b != 0) { // ...and then remove squares not supported by another enemy piece b &= (ei.attackedBy[Them][PieceTypeS.PAWN] | ei.attackedBy[Them][PieceTypeS.KNIGHT] | ei.attackedBy[Them][PieceTypeS.BISHOP] | ei.attackedBy[Them][PieceTypeS.ROOK]); if (b != 0) { attackUnits += QueenContactCheck * Bitcount.popcount_Max15(b) * (Them == pos.side_to_move() ? 2 : 1); } } // Analyse the enemy's safe rook contact checks. Firstly, find the // undefended squares around the king that are attacked by the enemy's // rooks... b = undefended & ei.attackedBy[Them][PieceTypeS.ROOK] & ~pos.pieces_color(Them); // Consider only squares where the enemy rook gives check b &= BitBoard.PseudoAttacks[PieceTypeS.ROOK][ksq]; if (b != 0) { // ...and then remove squares not supported by another enemy piece b &= (ei.attackedBy[Them][PieceTypeS.PAWN] | ei.attackedBy[Them][PieceTypeS.KNIGHT] | ei.attackedBy[Them][PieceTypeS.BISHOP] | ei.attackedBy[Them][PieceTypeS.QUEEN]); if (b != 0) { attackUnits += RookContactCheck * Bitcount.popcount_Max15(b) * (Them == pos.side_to_move() ? 2 : 1); } } // Analyse enemy's safe distance checks for sliders and knights safe = ~(pos.pieces_color(Them) | ei.attackedBy[Us][PieceTypeS.ALL_PIECES]); b1 = pos.attacks_from_square_piecetype(ksq, PieceTypeS.ROOK) & safe; b2 = pos.attacks_from_square_piecetype(ksq, PieceTypeS.BISHOP) & safe; // Enemy queen safe checks b = (b1 | b2) & ei.attackedBy[Them][PieceTypeS.QUEEN]; if (b != 0) { attackUnits += QueenCheck * Bitcount.popcount_Max15(b); } // Enemy rooks safe checks b = b1 & ei.attackedBy[Them][PieceTypeS.ROOK]; if (b != 0) { attackUnits += RookCheck * Bitcount.popcount_Max15(b); } // Enemy bishops safe checks b = b2 & ei.attackedBy[Them][PieceTypeS.BISHOP]; if (b != 0) { attackUnits += BishopCheck * Bitcount.popcount_Max15(b); } // Enemy knights safe checks b = pos.attacks_from_square_piecetype(ksq, PieceTypeS.KNIGHT) & ei.attackedBy[Them][PieceTypeS.KNIGHT] & safe; if (b != 0) { attackUnits += KnightCheck * Bitcount.popcount_Max15(b); } // To index KingDanger[] attackUnits must be in [0, 99] range attackUnits = Math.Min(99, Math.Max(0, attackUnits)); // Finally, extract the king danger score from the KingDanger[] // array and subtract the score from evaluation. score -= KingDanger[Us == Search.RootColor ? 1 : 0][attackUnits]; } if (Trace) { Tracing.terms[Us][PieceTypeS.KING] = score; } return(score); }
// evaluate_pieces() assigns bonuses and penalties to the pieces of a given color public static Score evaluate_pieces(Position pos, EvalInfo ei, Score[] mobility, Bitboard[] mobilityArea, PieceType Pt, Color Us, bool Trace) { if (Us == ColorS.WHITE && Pt == PieceTypeS.KING) { return(ScoreS.SCORE_ZERO); } Bitboard b; Square s; Score score = ScoreS.SCORE_ZERO; PieceType NextPt = (Us == ColorS.WHITE ? Pt : (Pt + 1)); Color Them = (Us == ColorS.WHITE ? ColorS.BLACK : ColorS.WHITE); Square[] pl = pos.list(Us, Pt); int plPos = 0; ei.attackedBy[Us][Pt] = 0; while ((s = pl[plPos++]) != SquareS.SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == PieceTypeS.BISHOP ? BitBoard.attacks_bb_SBBPT(s, pos.pieces() ^ pos.pieces_color_piecetype(Us, PieceTypeS.QUEEN), PieceTypeS.BISHOP) : Pt == PieceTypeS.ROOK ? BitBoard.attacks_bb_SBBPT(s, pos.pieces() ^ pos.pieces_color_piecetype(Us, PieceTypeS.ROOK, PieceTypeS.QUEEN), PieceTypeS.ROOK) : pos.attacks_from_square_piecetype(s, Pt); if ((ei.pinnedPieces[Us] & BitBoard.SquareBB[s]) != 0) { b &= BitBoard.LineBB[pos.king_square(Us)][s]; } ei.attackedBy[Us][PieceTypeS.ALL_PIECES] |= ei.attackedBy[Us][Pt] |= b; if ((b & ei.kingRing[Them]) != 0) { ei.kingAttackersCount[Us]++; ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; Bitboard bb = (b & ei.attackedBy[Them][PieceTypeS.KING]); if (bb != 0) { ei.kingAdjacentZoneAttacksCount[Us] += Bitcount.popcount_Max15(bb); } } if (Pt == PieceTypeS.QUEEN) { b &= ~(ei.attackedBy[Them][PieceTypeS.KNIGHT] | ei.attackedBy[Them][PieceTypeS.BISHOP] | ei.attackedBy[Them][PieceTypeS.ROOK]); } int mob = (Pt != PieceTypeS.QUEEN ? Bitcount.popcount_Max15(b & mobilityArea[Us]) : Bitcount.popcount(b & mobilityArea[Us])); mobility[Us] += MobilityBonus[Pt][mob]; // Decrease score if we are attacked by an enemy pawn. The remaining part // of threat evaluation must be done later when we have full attack info. if ((ei.attackedBy[Them][PieceTypeS.PAWN] & BitBoard.SquareBB[s]) != 0) { score -= ThreatenedByPawn[Pt]; } if (Pt == PieceTypeS.BISHOP || Pt == PieceTypeS.KNIGHT) { // Penalty for bishop with same coloured pawns if (Pt == PieceTypeS.BISHOP) { score -= BishopPawns * ei.pi.pawns_on_same_color_squares(Us, s); } // Bishop and knight outposts squares if (0 == (pos.pieces_color_piecetype(Them, PieceTypeS.PAWN) & BitBoard.pawn_attack_span(Us, s))) { score += evaluate_outposts(pos, ei, s, Pt, Us); } // Bishop or knight behind a pawn if (Types.relative_rank_square(Us, s) < RankS.RANK_5 && (pos.pieces_piecetype(PieceTypeS.PAWN) & BitBoard.SquareBB[(s + Types.pawn_push(Us))]) != 0) { score += MinorBehindPawn; } } if (Pt == PieceTypeS.ROOK) { // Rook piece attacking enemy pawns on the same rank/file if (Types.relative_rank_square(Us, s) >= RankS.RANK_5) { Bitboard pawns = pos.pieces_color_piecetype(Them, PieceTypeS.PAWN) & BitBoard.PseudoAttacks[PieceTypeS.ROOK][s]; if (pawns != 0) { score += Bitcount.popcount_Max15(pawns) * RookOnPawn; } } // Give a bonus for a rook on a open or semi-open file if (ei.pi.semiopen_file(Us, Types.file_of(s)) != 0) { score += ei.pi.semiopen_file(Them, Types.file_of(s)) != 0 ? RookOpenFile : RookSemiopenFile; } if (mob > 3 || ei.pi.semiopen_file(Us, Types.file_of(s)) != 0) { continue; } Square ksq = pos.king_square(Us); // Penalize rooks which are trapped by a king. Penalize more if the // king has lost its castling capability. if (((Types.file_of(ksq) < FileS.FILE_E) == (Types.file_of(s) < Types.file_of(ksq))) && (Types.rank_of(ksq) == Types.rank_of(s) || Types.relative_rank_square(Us, ksq) == RankS.RANK_1) && 0 == ei.pi.semiopen_side(Us, Types.file_of(ksq), Types.file_of(s) < Types.file_of(ksq))) { score -= (TrappedRook - Types.make_score(mob * 8, 0)) * (1 + (pos.can_castle_color(Us) == 0 ? 1 : 0)); } } // 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 == PieceTypeS.BISHOP && pos.is_chess960() != 0 && (s == Types.relative_square(Us, SquareS.SQ_A1) || s == Types.relative_square(Us, SquareS.SQ_H1))) { Square d = Types.pawn_push(Us) + (Types.file_of(s) == FileS.FILE_A ? SquareS.DELTA_E : SquareS.DELTA_W); if (pos.piece_on(s + d) == Types.make_piece(Us, PieceTypeS.PAWN)) { score -= !pos.empty(s + d + Types.pawn_push(Us)) ? TrappedBishopA1H1 * 4 : pos.piece_on(s + d + d) == Types.make_piece(Us, PieceTypeS.PAWN) ? TrappedBishopA1H1 * 2 : TrappedBishopA1H1; } } } if (Trace) { Tracing.terms[Us][Pt] = score; } return(score - evaluate_pieces(pos, ei, mobility, mobilityArea, NextPt, Them, Trace)); }