Spaces:
Sleeping
Sleeping
| """Per-component classical evaluation breakdown.""" | |
| from __future__ import annotations | |
| from dataclasses import asdict, dataclass | |
| from typing import Any | |
| from . import bitboards as bb | |
| from . import constants as C | |
| from .board import Board | |
| from .evaluate import ( | |
| EvalInfo, | |
| PSQT, | |
| evaluate_bishops, | |
| evaluate_closedness, | |
| evaluate_complexity, | |
| evaluate_kings, | |
| evaluate_kings_pawns, | |
| evaluate_knights, | |
| evaluate_passed, | |
| evaluate_pawns, | |
| evaluate_queens, | |
| evaluate_rooks, | |
| evaluate_scale_factor, | |
| evaluate_space, | |
| evaluate_threats, | |
| init_eval_info, | |
| score_eg, | |
| score_mg, | |
| ) | |
| from .types import BLACK, WHITE, make_score | |
| class ComponentScore: | |
| """Middlegame / endgame contribution in centipawns (White − Black).""" | |
| mg: int = 0 | |
| eg: int = 0 | |
| def from_packed(cls, packed: int) -> ComponentScore: | |
| return cls(score_mg(packed), score_eg(packed)) | |
| def packed(self) -> int: | |
| return make_score(self.mg, self.eg) | |
| def __add__(self, other: ComponentScore) -> ComponentScore: | |
| return ComponentScore(self.mg + other.mg, self.eg + other.eg) | |
| def sum_components(*parts: ComponentScore) -> ComponentScore: | |
| """Sum MG/EG separately (correct way to combine tapered terms).""" | |
| mg = sum(p.mg for p in parts) | |
| eg = sum(p.eg for p in parts) | |
| return ComponentScore(mg, eg) | |
| class EvaluationBreakdown: | |
| """Full classical eval with intermediate terms (before phase taper).""" | |
| fen: str | |
| side_to_move: str | |
| pawn_king_structure: ComponentScore | |
| psqt_material: ComponentScore | |
| knights: ComponentScore | |
| bishops: ComponentScore | |
| rooks: ComponentScore | |
| queens: ComponentScore | |
| kings: ComponentScore | |
| passed_pawns: ComponentScore | |
| threats: ComponentScore | |
| space: ComponentScore | |
| closedness: ComponentScore | |
| complexity: ComponentScore | |
| subtotal: ComponentScore | |
| phase: int | |
| scale_factor: int | |
| tapered: int | |
| tempo: int | |
| final: int | |
| def to_dict(self) -> dict[str, Any]: | |
| d = asdict(self) | |
| d["side_to_move"] = self.side_to_move | |
| return d | |
| def _diff(white_packed: int, black_packed: int) -> ComponentScore: | |
| return ComponentScore.from_packed(white_packed - black_packed) | |
| def evaluate_board_detailed( | |
| board: Board, | |
| *, | |
| fen: str = "", | |
| ) -> EvaluationBreakdown: | |
| """Evaluate with per-component MG/EG breakdown and final tapered score.""" | |
| ei = EvalInfo() | |
| init_eval_info(board, ei) | |
| evaluate_pawns(ei, board, WHITE) | |
| evaluate_pawns(ei, board, BLACK) | |
| evaluate_kings_pawns(ei, board, WHITE) | |
| evaluate_kings_pawns(ei, board, BLACK) | |
| pawn_king = ComponentScore.from_packed(ei.pkeval[WHITE] - ei.pkeval[BLACK]) | |
| psqt = ComponentScore.from_packed(board.psqtmat) | |
| knights = _diff(evaluate_knights(ei, board, WHITE), evaluate_knights(ei, board, BLACK)) | |
| bishops = _diff(evaluate_bishops(ei, board, WHITE), evaluate_bishops(ei, board, BLACK)) | |
| rooks = _diff(evaluate_rooks(ei, board, WHITE), evaluate_rooks(ei, board, BLACK)) | |
| queens = _diff(evaluate_queens(ei, board, WHITE), evaluate_queens(ei, board, BLACK)) | |
| kings = _diff(evaluate_kings(ei, board, WHITE), evaluate_kings(ei, board, BLACK)) | |
| passed = _diff(evaluate_passed(ei, board, WHITE), evaluate_passed(ei, board, BLACK)) | |
| threats = _diff(evaluate_threats(ei, board, WHITE), evaluate_threats(ei, board, BLACK)) | |
| space = _diff(evaluate_space(ei, board, WHITE), evaluate_space(ei, board, BLACK)) | |
| pieces = ( | |
| knights | |
| + bishops | |
| + rooks | |
| + queens | |
| + kings | |
| + passed | |
| + threats | |
| + space | |
| ) | |
| closedness = ComponentScore.from_packed(evaluate_closedness(ei, board)) | |
| pre_complexity = sum_components(pawn_king, psqt, pieces, closedness) | |
| complexity = ComponentScore.from_packed( | |
| evaluate_complexity(board, pre_complexity.packed()) | |
| ) | |
| subtotal = pre_complexity + complexity | |
| from .types import BISHOP, KNIGHT, QUEEN, ROOK | |
| factor = evaluate_scale_factor(board, subtotal.packed()) | |
| phase = ( | |
| 4 * bb.popcount(board.pieces[QUEEN]) | |
| + 2 * bb.popcount(board.pieces[ROOK]) | |
| + bb.popcount(board.pieces[KNIGHT] | board.pieces[BISHOP]) | |
| ) | |
| tapered = ( | |
| subtotal.mg * phase | |
| + subtotal.eg * (24 - phase) * factor // C.SCALE_NORMAL | |
| ) // 24 | |
| tempo = C.TEMPO | |
| final = tempo + (tapered if board.turn == WHITE else -tapered) | |
| return EvaluationBreakdown( | |
| fen=fen, | |
| side_to_move="white" if board.turn == WHITE else "black", | |
| pawn_king_structure=pawn_king, | |
| psqt_material=psqt, | |
| knights=knights, | |
| bishops=bishops, | |
| rooks=rooks, | |
| queens=queens, | |
| kings=kings, | |
| passed_pawns=passed, | |
| threats=threats, | |
| space=space, | |
| closedness=closedness, | |
| complexity=complexity, | |
| subtotal=subtotal, | |
| phase=phase, | |
| scale_factor=factor, | |
| tapered=tapered, | |
| tempo=tempo, | |
| final=final, | |
| ) | |
| def format_breakdown(bd: EvaluationBreakdown) -> str: | |
| """Human-readable report for tests or CLI.""" | |
| def line(name: str, c: ComponentScore) -> str: | |
| return f" {name:22} MG {c.mg:+6d} EG {c.eg:+6d}" | |
| lines = [ | |
| f"FEN: {bd.fen}", | |
| f"Side to move: {bd.side_to_move}", | |
| "", | |
| "Components (White - Black, centipawns):", | |
| line("pawn/king structure", bd.pawn_king_structure), | |
| line("psqt + material", bd.psqt_material), | |
| line("knights", bd.knights), | |
| line("bishops", bd.bishops), | |
| line("rooks", bd.rooks), | |
| line("queens", bd.queens), | |
| line("kings", bd.kings), | |
| line("passed pawns", bd.passed_pawns), | |
| line("threats", bd.threats), | |
| line("space", bd.space), | |
| line("closedness", bd.closedness), | |
| line("complexity", bd.complexity), | |
| "", | |
| line("subtotal (pre-taper)", bd.subtotal), | |
| f" {'phase':22} {bd.phase:6d} (0=EG-heavy, 24=MG-heavy)", | |
| f" {'scale_factor':22} {bd.scale_factor:6d} (/128 applied to EG taper)", | |
| f" {'tapered':22} {bd.tapered:+6d}", | |
| f" {'tempo':22} {bd.tempo:+6d}", | |
| f" {'FINAL (White POV)':22} {bd.final:+6d}", | |
| ] | |
| return "\n".join(lines) | |