""" 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)] @dataclass 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