Spaces:
Running
Running
File size: 3,030 Bytes
a1b0768 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """Map raw features to 0–100 per-side analysis scores."""
from __future__ import annotations
from .features import RawPositionFeatures, SideRawFeatures, _STARTING_MATERIAL_INDEX
def _clamp100(x: float) -> float:
return max(0.0, min(100.0, x))
def _linear(value: float, max_value: float) -> float:
if max_value <= 0:
return 0.0
return _clamp100(100.0 * value / max_value)
def _quality_from_penalty(penalty: float, max_penalty: float) -> float:
if max_penalty <= 0:
return 100.0
return _clamp100(100.0 - 100.0 * penalty / max_penalty)
def normalize_side(raw: SideRawFeatures) -> dict[str, float]:
"""Convert one side's raw features to named 0–100 scores (higher = better where noted)."""
closedness = _linear(raw.closedness_index, 8.0)
openness = _linear(raw.open_files, 8.0)
pawn_penalty = (
raw.isolated_pawns * 14.0
+ raw.backward_pawns * 12.0
+ raw.stacked_pawns * 8.0
)
pawn_bonus = (
raw.connected_pawns * 4.0
+ raw.candidate_passers * 10.0
+ raw.passed_pawns * 18.0
)
pawn_structure = _clamp100(50.0 + pawn_bonus - pawn_penalty)
king_penalty = (
raw.king_attacks * 4.0
+ raw.king_attackers * 12.0
+ raw.king_weak_squares * 10.0
)
king_bonus = raw.king_defenders * 6.0 + raw.shelter_mg / 500_000.0
king_safety = _quality_from_penalty(max(0.0, king_penalty - king_bonus), 80.0)
mobility = (
raw.knight_mobility
+ raw.bishop_mobility
+ raw.rook_mobility
+ raw.queen_mobility
)
piece_activity = _linear(mobility + raw.outposts * 3.0, 55.0)
passed_pawn_pressure = _linear(raw.passed_pressure, 40.0)
space = _linear(raw.center_control * 4.0 + raw.space_restricted, 24.0)
tactical_tension = _linear(raw.threat_units + raw.pawn_push_threats, 18.0)
tactical_complexity = _linear(raw.complexity_units, 20.0)
material = _linear(raw.material_index, float(_STARTING_MATERIAL_INDEX))
return {
"closedness": closedness,
"openness": openness,
"pawn_structure": pawn_structure,
"king_safety": king_safety,
"piece_activity": piece_activity,
"passed_pawn_pressure": passed_pawn_pressure,
"space": space,
"tactical_tension": tactical_tension,
"tactical_complexity": tactical_complexity,
"material": material,
}
def normalize_position(raw: RawPositionFeatures) -> dict:
"""Full position analysis dict with per-side 0–100 components."""
white = normalize_side(raw.white)
black = normalize_side(raw.black)
phase_middlegame = _linear(raw.phase_index, 24.0)
return {
"phase_middlegame": phase_middlegame,
"white": white,
"black": black,
}
COMPONENT_KEYS = (
"closedness",
"openness",
"pawn_structure",
"king_safety",
"piece_activity",
"passed_pawn_pressure",
"space",
"tactical_tension",
"tactical_complexity",
"material",
)
|