github-actions[bot]
deploy prod from 06dbd16a01ddcfe02b2d936c681e3e4eaa9b141f
8e756fd
Raw
History Blame Contribute Delete
24.8 kB
"""
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