Spaces:
Sleeping
Sleeping
| """ | |
| Classical position evaluation. | |
| Returns centipawns from White's perspective after phase tapering and tempo. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from . import attacks | |
| from . import bitboards as bb | |
| from . import constants as C | |
| from . import masks | |
| from .types import ( | |
| BISHOP, | |
| BLACK, | |
| KING, | |
| KNIGHT, | |
| PAWN, | |
| QUEEN, | |
| ROOK, | |
| WHITE, | |
| make_piece, | |
| make_score, | |
| score_eg, | |
| score_mg, | |
| ) | |
| PSQT: list[list[int]] = [[0] * 64 for _ in range(32)] | |
| class EvalInfo: | |
| pawn_attacks: list[int] = field(default_factory=lambda: [0, 0]) | |
| pawn_attacks_by2: list[int] = field(default_factory=lambda: [0, 0]) | |
| rammed_pawns: list[int] = field(default_factory=lambda: [0, 0]) | |
| blocked_pawns: list[int] = field(default_factory=lambda: [0, 0]) | |
| king_areas: list[int] = field(default_factory=lambda: [0, 0]) | |
| mobility_areas: list[int] = field(default_factory=lambda: [0, 0]) | |
| attacked: list[int] = field(default_factory=lambda: [0, 0]) | |
| attacked_by2: list[int] = field(default_factory=lambda: [0, 0]) | |
| attacked_by: list[list[int]] = field(default_factory=lambda: [[0] * 6 for _ in range(2)]) | |
| occupied_minus_bishops: list[int] = field(default_factory=lambda: [0, 0]) | |
| occupied_minus_rooks: list[int] = field(default_factory=lambda: [0, 0]) | |
| passed_pawns: int = 0 | |
| king_square: list[int] = field(default_factory=lambda: [0, 0]) | |
| king_attacks_count: list[int] = field(default_factory=lambda: [0, 0]) | |
| king_attackers_count: list[int] = field(default_factory=lambda: [0, 0]) | |
| king_attackers_weight: list[int] = field(default_factory=lambda: [0, 0]) | |
| pkeval: list[int] = field(default_factory=lambda: [0, 0]) | |
| pksafety: list[int] = field(default_factory=lambda: [0, 0]) | |
| def init_psqt() -> None: | |
| from .types import ( | |
| BLACK_BISHOP, | |
| BLACK_KING, | |
| BLACK_KNIGHT, | |
| BLACK_PAWN, | |
| BLACK_QUEEN, | |
| BLACK_ROOK, | |
| WHITE_BISHOP, | |
| WHITE_KING, | |
| WHITE_KNIGHT, | |
| WHITE_PAWN, | |
| WHITE_QUEEN, | |
| WHITE_ROOK, | |
| ) | |
| for sq in range(64): | |
| sq1 = bb.relative_square(WHITE, sq) | |
| sq2 = bb.relative_square(BLACK, sq) | |
| PSQT[WHITE_PAWN][sq] = C.PawnValue + C.PawnPSQT[sq1] | |
| PSQT[WHITE_KNIGHT][sq] = C.KnightValue + C.KnightPSQT[sq1] | |
| PSQT[WHITE_BISHOP][sq] = C.BishopValue + C.BishopPSQT[sq1] | |
| PSQT[WHITE_ROOK][sq] = C.RookValue + C.RookPSQT[sq1] | |
| PSQT[WHITE_QUEEN][sq] = C.QueenValue + C.QueenPSQT[sq1] | |
| PSQT[WHITE_KING][sq] = C.KingValue + C.KingPSQT[sq1] | |
| PSQT[BLACK_PAWN][sq] = -C.PawnValue - C.PawnPSQT[sq2] | |
| PSQT[BLACK_KNIGHT][sq] = -C.KnightValue - C.KnightPSQT[sq2] | |
| PSQT[BLACK_BISHOP][sq] = -C.BishopValue - C.BishopPSQT[sq2] | |
| PSQT[BLACK_ROOK][sq] = -C.RookValue - C.RookPSQT[sq2] | |
| PSQT[BLACK_QUEEN][sq] = -C.QueenValue - C.QueenPSQT[sq2] | |
| PSQT[BLACK_KING][sq] = -C.KingValue - C.KingPSQT[sq2] | |
| init_psqt() | |
| def init_eval_info(board, ei: EvalInfo) -> None: | |
| white = board.colours[WHITE] | |
| black = board.colours[BLACK] | |
| pawns = board.pieces[PAWN] | |
| bishops = board.pieces[BISHOP] | board.pieces[QUEEN] | |
| rooks = board.pieces[ROOK] | board.pieces[QUEEN] | |
| kings = board.pieces[KING] | |
| ei.pawn_attacks[WHITE] = attacks.pawn_attack_span(white & pawns, (1 << 64) - 1, WHITE) | |
| ei.pawn_attacks[BLACK] = attacks.pawn_attack_span(black & pawns, (1 << 64) - 1, BLACK) | |
| ei.pawn_attacks_by2[WHITE] = attacks.pawn_attack_double(white & pawns, (1 << 64) - 1, WHITE) | |
| ei.pawn_attacks_by2[BLACK] = attacks.pawn_attack_double(black & pawns, (1 << 64) - 1, BLACK) | |
| ei.rammed_pawns[WHITE] = attacks.pawn_advance(black & pawns, ~(white & pawns), BLACK) | |
| ei.rammed_pawns[BLACK] = attacks.pawn_advance(white & pawns, ~(black & pawns), WHITE) | |
| ei.blocked_pawns[WHITE] = attacks.pawn_advance(white | black, ~(white & pawns), BLACK) | |
| ei.blocked_pawns[BLACK] = attacks.pawn_advance(white | black, ~(black & pawns), WHITE) | |
| ei.king_square[WHITE] = bb.getlsb(white & kings) | |
| ei.king_square[BLACK] = bb.getlsb(black & kings) | |
| ei.king_areas[WHITE] = masks.king_area_masks(WHITE, ei.king_square[WHITE]) | |
| ei.king_areas[BLACK] = masks.king_area_masks(BLACK, ei.king_square[BLACK]) | |
| ei.mobility_areas[WHITE] = ~( | |
| ei.pawn_attacks[BLACK] | (white & kings) | ei.blocked_pawns[WHITE] | |
| ) & ((1 << 64) - 1) | |
| ei.mobility_areas[BLACK] = ~( | |
| ei.pawn_attacks[WHITE] | (black & kings) | ei.blocked_pawns[BLACK] | |
| ) & ((1 << 64) - 1) | |
| ei.attacked[WHITE] = ei.attacked_by[WHITE][KING] = attacks.king_attacks(ei.king_square[WHITE]) | |
| ei.attacked[BLACK] = ei.attacked_by[BLACK][KING] = attacks.king_attacks(ei.king_square[BLACK]) | |
| ei.occupied_minus_bishops[WHITE] = (white | black) ^ (white & bishops) | |
| ei.occupied_minus_bishops[BLACK] = (white | black) ^ (black & bishops) | |
| ei.occupied_minus_rooks[WHITE] = (white | black) ^ (white & rooks) | |
| ei.occupied_minus_rooks[BLACK] = (white | black) ^ (black & rooks) | |
| ei.king_attacks_count[:] = [0, 0] | |
| ei.king_attackers_count[:] = [0, 0] | |
| ei.king_attackers_weight[:] = [0, 0] | |
| ei.passed_pawns = 0 | |
| ei.pkeval[:] = [0, 0] | |
| ei.pksafety[:] = [0, 0] | |
| def evaluate_pawns(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| forward = 8 if colour == WHITE else -8 | |
| eval_score = 0 | |
| ei.attacked_by2[us] = ei.pawn_attacks[us] & ei.attacked[us] | |
| ei.attacked[us] |= ei.pawn_attacks[us] | |
| ei.attacked_by[us][PAWN] = ei.pawn_attacks[us] | |
| atk = ei.pawn_attacks[us] & ei.king_areas[them] | |
| ei.king_attacks_count[them] += bb.popcount(atk) | |
| pawns = board.pieces[PAWN] | |
| my_pawns = temp = pawns & board.colours[us] | |
| enemy_pawns = pawns & board.colours[them] | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| f = bb.file_of(sq) | |
| neighbors = my_pawns & masks.adjacent_files_masks(f) | |
| backup = my_pawns & masks.passed_pawn_masks(them, sq) | |
| stoppers = enemy_pawns & masks.passed_pawn_masks(us, sq) | |
| threats = enemy_pawns & attacks.pawn_attacks(us, sq) | |
| support = my_pawns & attacks.pawn_attacks(them, sq) | |
| push_threats = enemy_pawns & attacks.pawn_attacks(us, sq + forward) | |
| push_support = my_pawns & attacks.pawn_attacks(them, sq + forward) | |
| leftovers = stoppers ^ threats ^ push_threats | |
| if not stoppers: | |
| ei.passed_pawns |= 1 << sq | |
| elif not leftovers and bb.popcount(push_support) >= bb.popcount(push_threats): | |
| flag = 1 if bb.popcount(support) >= bb.popcount(threats) else 0 | |
| ei.pkeval[us] += C.PawnCandidatePasser[flag][bb.relative_rank_of(us, sq)] | |
| if not threats and not neighbors: | |
| ei.pkeval[us] += C.PawnIsolated[f] | |
| if bb.several(bb.FILES[f] & my_pawns): | |
| flag = int( | |
| (bool(stoppers) and (bool(threats) or bool(neighbors))) | |
| or bool(stoppers & ~masks.forward_file_masks(us, sq)) | |
| ) | |
| ei.pkeval[us] += C.PawnStacked[flag][f] | |
| if neighbors and push_threats and not backup: | |
| flag = 1 if not (bb.FILES[f] & enemy_pawns) else 0 | |
| ei.pkeval[us] += C.PawnBackwards[flag][bb.relative_rank_of(us, sq)] | |
| elif masks.pawn_connected_masks(us, sq) & my_pawns: | |
| ei.pkeval[us] += C.PawnConnected32[bb.relative_square32(us, sq)] | |
| return eval_score | |
| def evaluate_kings_pawns(ei: EvalInfo, board, colour: int) -> int: | |
| us = colour | |
| my_pawns = board.pieces[PAWN] & board.colours[us] | |
| enemy_pawns = board.pieces[PAWN] & board.colours[1 - us] | |
| king_sq = ei.king_square[us] | |
| dist = masks.king_pawn_file_distance(board.pieces[PAWN], king_sq) | |
| ei.pkeval[us] += C.KingPawnFileProximity[dist] | |
| kf = bb.file_of(king_sq) | |
| for file in range(max(0, kf - 1), min(8, kf + 2)): | |
| ours = my_pawns & bb.FILES[file] & masks.forward_ranks_masks(us, bb.rank_of(king_sq)) | |
| our_dist = 7 if not ours else abs(bb.rank_of(king_sq) - bb.rank_of(bb.backmost(us, ours))) | |
| theirs = ( | |
| enemy_pawns & bb.FILES[file] & masks.forward_ranks_masks(us, bb.rank_of(king_sq)) | |
| ) | |
| their_dist = 7 if not theirs else abs( | |
| bb.rank_of(king_sq) - bb.rank_of(bb.backmost(us, theirs)) | |
| ) | |
| on_king_file = file == kf | |
| ei.pkeval[us] += C.KingShelter[on_king_file][file][our_dist] | |
| ei.pksafety[us] += C.SafetyShelter[on_king_file][our_dist] | |
| blocked = our_dist != 7 and our_dist == their_dist - 1 | |
| ei.pkeval[us] += C.KingStorm[blocked][bb.MIRROR_FILE[file]][their_dist] | |
| ei.pksafety[us] += C.SafetyStorm[blocked][their_dist] | |
| return 0 | |
| def evaluate_knights(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| enemy_pawns = board.pieces[PAWN] & board.colours[them] | |
| temp = board.pieces[KNIGHT] & board.colours[us] | |
| ei.attacked_by[us][KNIGHT] = 0 | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| att = attacks.knight_attacks(sq) | |
| ei.attacked_by2[us] |= att & ei.attacked[us] | |
| ei.attacked[us] |= att | |
| ei.attacked_by[us][KNIGHT] |= att | |
| if bb.test_bit(masks.outpost_ranks_masks(us), sq) and not ( | |
| masks.outpost_square_masks(us, sq) & enemy_pawns | |
| ): | |
| outside = bb.test_bit(bb.FILE_A | bb.FILE_H, sq) | |
| defended = bb.test_bit(ei.pawn_attacks[us], sq) | |
| eval_score += C.KnightOutpost[outside][defended] | |
| if bb.test_bit(attacks.pawn_advance(board.pieces[PAWN], 0, them), sq): | |
| eval_score += C.KnightBehindPawn | |
| kd = min( | |
| masks.distance_between(sq, ei.king_square[them]), | |
| masks.distance_between(sq, ei.king_square[us]), | |
| ) | |
| if kd >= 4: | |
| eval_score += C.KnightInSiberia[kd - 4] | |
| count = bb.popcount(ei.mobility_areas[us] & att) | |
| eval_score += C.KnightMobility[count] | |
| att &= ei.king_areas[them] & ~ei.pawn_attacks_by2[them] | |
| if att: | |
| ei.king_attacks_count[them] += bb.popcount(att) | |
| ei.king_attackers_count[them] += 1 | |
| ei.king_attackers_weight[them] += C.SafetyKnightWeight | |
| return eval_score | |
| def evaluate_bishops(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| enemy_pawns = board.pieces[PAWN] & board.colours[them] | |
| temp = board.pieces[BISHOP] & board.colours[us] | |
| ei.attacked_by[us][BISHOP] = 0 | |
| if (temp & bb.WHITE_SQUARES) and (temp & bb.BLACK_SQUARES): | |
| eval_score += C.BishopPair | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| att = attacks.bishop_attacks(sq, ei.occupied_minus_bishops[us]) | |
| ei.attacked_by2[us] |= att & ei.attacked[us] | |
| ei.attacked[us] |= att | |
| ei.attacked_by[us][BISHOP] |= att | |
| count = bb.popcount(ei.rammed_pawns[us] & bb.squares_of_matching_colour(sq)) | |
| eval_score += count * C.BishopRammedPawns | |
| if bb.test_bit(masks.outpost_ranks_masks(us), sq) and not ( | |
| masks.outpost_square_masks(us, sq) & enemy_pawns | |
| ): | |
| outside = bb.test_bit(bb.FILE_A | bb.FILE_H, sq) | |
| defended = bb.test_bit(ei.pawn_attacks[us], sq) | |
| eval_score += C.BishopOutpost[outside][defended] | |
| if bb.test_bit(attacks.pawn_advance(board.pieces[PAWN], 0, them), sq): | |
| eval_score += C.BishopBehindPawn | |
| if bb.test_bit(bb.LONG_DIAGONALS & ~bb.CENTER_SQUARES, sq) and bb.several( | |
| attacks.bishop_attacks(sq, board.pieces[PAWN]) & bb.CENTER_SQUARES | |
| ): | |
| eval_score += C.BishopLongDiagonal | |
| count = bb.popcount(ei.mobility_areas[us] & att) | |
| eval_score += C.BishopMobility[count] | |
| att &= ei.king_areas[them] & ~ei.pawn_attacks_by2[them] | |
| if att: | |
| ei.king_attacks_count[them] += bb.popcount(att) | |
| ei.king_attackers_count[them] += 1 | |
| ei.king_attackers_weight[them] += C.SafetyBishopWeight | |
| return eval_score | |
| def evaluate_rooks(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| my_pawns = board.pieces[PAWN] & board.colours[us] | |
| enemy_pawns = board.pieces[PAWN] & board.colours[them] | |
| temp = board.pieces[ROOK] & board.colours[us] | |
| ei.attacked_by[us][ROOK] = 0 | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| att = attacks.rook_attacks(sq, ei.occupied_minus_rooks[us]) | |
| ei.attacked_by2[us] |= att & ei.attacked[us] | |
| ei.attacked[us] |= att | |
| ei.attacked_by[us][ROOK] |= att | |
| if not (my_pawns & bb.FILES[bb.file_of(sq)]): | |
| open_f = not (enemy_pawns & bb.FILES[bb.file_of(sq)]) | |
| eval_score += C.RookFile[open_f] | |
| if bb.relative_rank_of(us, sq) == 6 and bb.relative_rank_of( | |
| us, ei.king_square[them] | |
| ) >= 6: | |
| eval_score += C.RookOnSeventh | |
| count = bb.popcount(ei.mobility_areas[us] & att) | |
| eval_score += C.RookMobility[count] | |
| att &= ei.king_areas[them] & ~ei.pawn_attacks_by2[them] | |
| if att: | |
| ei.king_attacks_count[them] += bb.popcount(att) | |
| ei.king_attackers_count[them] += 1 | |
| ei.king_attackers_weight[them] += C.SafetyRookWeight | |
| return eval_score | |
| def evaluate_queens(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| temp = board.pieces[QUEEN] & board.colours[us] | |
| occupied = board.colours[WHITE] | board.colours[BLACK] | |
| ei.attacked_by[us][QUEEN] = 0 | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| att = attacks.queen_attacks(sq, occupied) | |
| ei.attacked_by2[us] |= att & ei.attacked[us] | |
| ei.attacked[us] |= att | |
| ei.attacked_by[us][QUEEN] |= att | |
| if attacks.discovered_attacks(board, sq, us): | |
| eval_score += C.QueenRelativePin | |
| count = bb.popcount(ei.mobility_areas[us] & att) | |
| eval_score += C.QueenMobility[count] | |
| att &= ei.king_areas[them] & ~ei.pawn_attacks_by2[them] | |
| if att: | |
| ei.king_attacks_count[them] += bb.popcount(att) | |
| ei.king_attackers_count[them] += 1 | |
| ei.king_attackers_weight[them] += C.SafetyQueenWeight | |
| return eval_score | |
| def evaluate_kings(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| enemy_queens = board.pieces[QUEEN] & board.colours[them] | |
| defenders = ( | |
| (board.pieces[PAWN] & board.colours[us]) | |
| | (board.pieces[KNIGHT] & board.colours[us]) | |
| | (board.pieces[BISHOP] & board.colours[us]) | |
| ) | |
| king_sq = ei.king_square[us] | |
| count = bb.popcount(defenders & ei.king_areas[us]) | |
| eval_score += C.KingDefenders[count] | |
| if ei.king_attackers_count[us] > 1 - bb.popcount(enemy_queens): | |
| weak = ( | |
| ei.attacked[them] | |
| & ~ei.attacked_by2[us] | |
| & (~ei.attacked[us] | ei.attacked_by[us][QUEEN] | ei.attacked_by[us][KING]) | |
| ) | |
| scaled = int( | |
| 9.0 | |
| * ei.king_attacks_count[us] | |
| / max(1, bb.popcount(ei.king_areas[us])) | |
| ) | |
| safe = (~board.colours[them]) & ( | |
| ~ei.attacked[us] | (weak & ei.attacked_by2[them]) | |
| ) | |
| occupied = board.colours[WHITE] | board.colours[BLACK] | |
| ksq = ei.king_square[us] | |
| knight_checks = attacks.knight_attacks(ksq) & safe & ei.attacked_by[them][KNIGHT] | |
| bishop_checks = attacks.bishop_attacks(ksq, occupied) & safe & ei.attacked_by[them][BISHOP] | |
| rook_checks = attacks.rook_attacks(ksq, occupied) & safe & ei.attacked_by[them][ROOK] | |
| queen_checks = ( | |
| attacks.bishop_attacks(ksq, occupied) | attacks.rook_attacks(ksq, occupied) | |
| ) & safe & ei.attacked_by[them][QUEEN] | |
| safety = ei.king_attackers_weight[us] | |
| safety += C.SafetyAttackValue * scaled | |
| safety += C.SafetyWeakSquares * bb.popcount(weak & ei.king_areas[us]) | |
| safety += C.SafetyNoEnemyQueens * (not enemy_queens) | |
| safety += C.SafetySafeQueenCheck * bb.popcount(queen_checks) | |
| safety += C.SafetySafeRookCheck * bb.popcount(rook_checks) | |
| safety += C.SafetySafeBishopCheck * bb.popcount(bishop_checks) | |
| safety += C.SafetySafeKnightCheck * bb.popcount(knight_checks) | |
| safety += ei.pksafety[us] + C.SafetyAdjustment | |
| mg = score_mg(safety) | |
| eg = score_eg(safety) | |
| eval_score += make_score(-mg * max(0, mg) // 720, -max(0, eg) // 20) | |
| return eval_score | |
| def evaluate_passed(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| my_passers = board.colours[us] & ei.passed_pawns | |
| occupied = board.colours[WHITE] | board.colours[BLACK] | |
| temp = my_passers | |
| while temp: | |
| sq, temp = bb.poplsb(temp) | |
| rank = bb.relative_rank_of(us, sq) | |
| bitboard = attacks.pawn_advance(1 << sq, 0, us) | |
| can_advance = not (bitboard & occupied) | |
| safe_advance = not (bitboard & ei.attacked[them]) | |
| eval_score += C.PassedPawn[can_advance][safe_advance][rank] | |
| if bb.several(masks.forward_file_masks(us, sq) & my_passers): | |
| continue | |
| dist = masks.distance_between(sq, ei.king_square[us]) | |
| eval_score += dist * C.PassedFriendlyDistance[rank] | |
| dist = masks.distance_between(sq, ei.king_square[them]) | |
| eval_score += dist * C.PassedEnemyDistance[rank] | |
| promo = masks.forward_ranks_masks(us, bb.rank_of(sq)) & bb.FILES[bb.file_of(sq)] | |
| if not (promo & (board.colours[them] | ei.attacked[them])): | |
| eval_score += C.PassedSafePromotionPath | |
| return eval_score | |
| def evaluate_threats(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| rank3 = bb.RANK_3 if us == WHITE else bb.RANK_6 | |
| eval_score = 0 | |
| friendly = board.colours[us] | |
| enemy = board.colours[them] | |
| occupied = friendly | enemy | |
| pawns = friendly & board.pieces[PAWN] | |
| knights = friendly & board.pieces[KNIGHT] | |
| bishops = friendly & board.pieces[BISHOP] | |
| rooks = friendly & board.pieces[ROOK] | |
| queens = friendly & board.pieces[QUEEN] | |
| by_pawns = ei.attacked_by[them][PAWN] | |
| by_minors = ei.attacked_by[them][KNIGHT] | ei.attacked_by[them][BISHOP] | |
| by_majors = ei.attacked_by[them][ROOK] | ei.attacked_by[them][QUEEN] | |
| poorly = (ei.attacked[them] & ~ei.attacked[us]) | ( | |
| ei.attacked_by2[them] & ~ei.attacked_by2[us] & ~ei.attacked_by[us][PAWN] | |
| ) | |
| weak_minors = (knights | bishops) & poorly | |
| overloaded = ( | |
| (knights | bishops | rooks | queens) | |
| & ei.attacked[us] | |
| & ~ei.attacked_by2[us] | |
| & ei.attacked[them] | |
| & ~ei.attacked_by2[them] | |
| ) | |
| push = attacks.pawn_advance(pawns, occupied, us) | |
| push |= attacks.pawn_advance(push & ~by_pawns & rank3, occupied, us) | |
| push &= ~by_pawns & (ei.attacked[us] | ~ei.attacked[them]) | |
| push = attacks.pawn_attack_span(push, enemy & ~ei.attacked_by[us][PAWN], us) | |
| eval_score += bb.popcount(pawns & ~by_pawns & poorly) * C.ThreatWeakPawn | |
| eval_score += bb.popcount((knights | bishops) & by_pawns) * C.ThreatMinorAttackedByPawn | |
| eval_score += bb.popcount((knights | bishops) & by_minors) * C.ThreatMinorAttackedByMinor | |
| eval_score += bb.popcount(weak_minors & by_majors) * C.ThreatMinorAttackedByMajor | |
| eval_score += bb.popcount(rooks & (by_pawns | by_minors)) * C.ThreatRookAttackedByLesser | |
| eval_score += bb.popcount(weak_minors & ei.attacked_by[them][KING]) * C.ThreatMinorAttackedByKing | |
| eval_score += bb.popcount(rooks & poorly & ei.attacked_by[them][KING]) * C.ThreatRookAttackedByKing | |
| eval_score += bb.popcount(queens & ei.attacked[them]) * C.ThreatQueenAttackedByOne | |
| eval_score += bb.popcount(overloaded) * C.ThreatOverloadedPieces | |
| eval_score += bb.popcount(push) * C.ThreatByPawnPush | |
| return eval_score | |
| def evaluate_space(ei: EvalInfo, board, colour: int) -> int: | |
| us, them = colour, 1 - colour | |
| eval_score = 0 | |
| friendly = board.colours[us] | |
| enemy = board.colours[them] | |
| uncontrolled = ( | |
| ei.attacked_by2[them] | |
| & ei.attacked[us] | |
| & ~ei.attacked_by2[us] | |
| & ~ei.attacked_by[us][PAWN] | |
| ) | |
| eval_score += bb.popcount(uncontrolled & (friendly | enemy)) * C.SpaceRestrictPiece | |
| eval_score += bb.popcount(uncontrolled & ~friendly & ~enemy) * C.SpaceRestrictEmpty | |
| minors_majors = board.pieces[KNIGHT] | board.pieces[BISHOP] | |
| if ( | |
| bb.popcount(minors_majors) | |
| + 2 * bb.popcount(board.pieces[ROOK] | board.pieces[QUEEN]) | |
| > 12 | |
| ): | |
| center = ~ei.attacked[them] & (ei.attacked[us] | friendly) & bb.CENTER_BIG | |
| eval_score += bb.popcount(center) * C.SpaceCenterControl | |
| return eval_score | |
| def evaluate_closedness(ei: EvalInfo, board) -> int: | |
| white = board.colours[WHITE] | |
| black = board.colours[BLACK] | |
| knights = board.pieces[KNIGHT] | |
| rooks = board.pieces[ROOK] | |
| closedness = ( | |
| bb.popcount(board.pieces[PAWN]) | |
| + 3 * bb.popcount(ei.rammed_pawns[WHITE]) | |
| - 4 * masks.open_file_count(board.pieces[PAWN]) | |
| ) | |
| closedness = max(0, min(8, closedness // 3)) | |
| count = bb.popcount(white & knights) - bb.popcount(black & knights) | |
| eval_score = count * C.ClosednessKnightAdjustment[closedness] | |
| count = bb.popcount(white & rooks) - bb.popcount(black & rooks) | |
| eval_score += count * C.ClosednessRookAdjustment[closedness] | |
| return eval_score | |
| def evaluate_complexity(board, eval_packed: int) -> int: | |
| eg = score_eg(eval_packed) | |
| sign = (eg > 0) - (eg < 0) | |
| both_flanks = (board.pieces[PAWN] & bb.LEFT_FLANK) and ( | |
| board.pieces[PAWN] & bb.RIGHT_FLANK | |
| ) | |
| minors_majors = ( | |
| board.pieces[KNIGHT] | |
| | board.pieces[BISHOP] | |
| | board.pieces[ROOK] | |
| | board.pieces[QUEEN] | |
| ) | |
| complexity = ( | |
| C.ComplexityTotalPawns * bb.popcount(board.pieces[PAWN]) | |
| + C.ComplexityPawnFlanks * both_flanks | |
| + C.ComplexityPawnEndgame * (not minors_majors) | |
| + C.ComplexityAdjustment | |
| ) | |
| v = sign * max(score_eg(complexity), -abs(eg)) | |
| return make_score(0, v) | |
| def evaluate_scale_factor(board, eval_packed: int) -> int: | |
| pawns = board.pieces[PAWN] | |
| knights = board.pieces[KNIGHT] | |
| bishops = board.pieces[BISHOP] | |
| rooks = board.pieces[ROOK] | |
| queens = board.pieces[QUEEN] | |
| minors = knights | bishops | |
| pieces = knights | bishops | rooks | |
| white = board.colours[WHITE] | |
| black = board.colours[BLACK] | |
| if score_eg(eval_packed) < 0: | |
| weak, strong = white, black | |
| else: | |
| weak, strong = black, white | |
| if ( | |
| bb.only_one(white & bishops) | |
| and bb.only_one(black & bishops) | |
| and bb.only_one(bishops & bb.WHITE_SQUARES) | |
| ): | |
| if ( | |
| not (rooks | queens) | |
| and bb.only_one(white & knights) | |
| and bb.only_one(black & knights) | |
| ): | |
| return C.SCALE_OCB_ONE_KNIGHT | |
| if ( | |
| not (knights | queens) | |
| and bb.only_one(white & rooks) | |
| and bb.only_one(black & rooks) | |
| ): | |
| return C.SCALE_OCB_ONE_ROOK | |
| if not (knights | rooks | queens): | |
| return C.SCALE_OCB_BISHOPS_ONLY | |
| if bb.only_one(queens) and bb.several(pieces) and pieces == (weak & pieces): | |
| return C.SCALE_LONE_QUEEN | |
| if (strong & minors) and bb.popcount(strong) == 2: | |
| return C.SCALE_DRAW | |
| if ( | |
| not queens | |
| and not bb.several(pieces & white) | |
| and not bb.several(pieces & black) | |
| and bb.popcount(strong & pawns) - bb.popcount(weak & pawns) > 2 | |
| ): | |
| return C.SCALE_LARGE_PAWN_ADV | |
| return min(C.SCALE_NORMAL, 96 + bb.popcount(pawns & strong) * 8) | |
| def evaluate_pieces(ei: EvalInfo, board) -> int: | |
| ev = evaluate_pawns(ei, board, WHITE) - evaluate_pawns(ei, board, BLACK) | |
| evaluate_kings_pawns(ei, board, WHITE) | |
| evaluate_kings_pawns(ei, board, BLACK) | |
| ev += evaluate_knights(ei, board, WHITE) - evaluate_knights(ei, board, BLACK) | |
| ev += evaluate_bishops(ei, board, WHITE) - evaluate_bishops(ei, board, BLACK) | |
| ev += evaluate_rooks(ei, board, WHITE) - evaluate_rooks(ei, board, BLACK) | |
| ev += evaluate_queens(ei, board, WHITE) - evaluate_queens(ei, board, BLACK) | |
| ev += evaluate_kings(ei, board, WHITE) - evaluate_kings(ei, board, BLACK) | |
| ev += evaluate_passed(ei, board, WHITE) - evaluate_passed(ei, board, BLACK) | |
| ev += evaluate_threats(ei, board, WHITE) - evaluate_threats(ei, board, BLACK) | |
| ev += evaluate_space(ei, board, WHITE) - evaluate_space(ei, board, BLACK) | |
| return ev | |
| def evaluate_board_classical(board) -> int: | |
| """ | |
| Score is from White's perspective, including tempo, in centipawns. | |
| """ | |
| from .breakdown import evaluate_board_detailed | |
| return evaluate_board_detailed(board).final | |