Forkei's picture
Upload folder using huggingface_hub
52a4f3c verified
"""
Chess position analysis and evaluation.
Provides:
- Material balance calculation
- Piece activity assessment
- Threat detection
- Position description for agent
"""
import logging
import chess as python_chess
from typing import Dict, Any, Optional, List
logger = logging.getLogger(__name__)
class PositionAnalysis:
"""Analyze chess positions and provide context for the agent."""
# Piece values in centipawns
PIECE_VALUES = {
python_chess.PAWN: 100,
python_chess.KNIGHT: 320,
python_chess.BISHOP: 330,
python_chess.ROOK: 500,
python_chess.QUEEN: 900,
python_chess.KING: 0,
}
@staticmethod
def material_balance(board: python_chess.Board) -> Dict[str, Any]:
"""
Calculate material balance.
Returns:
Dict with white_material, black_material, balance
"""
white_material = 0
black_material = 0
for piece_type, value in PositionAnalysis.PIECE_VALUES.items():
white_count = len(board.pieces(piece_type, python_chess.WHITE))
black_count = len(board.pieces(piece_type, python_chess.BLACK))
white_material += white_count * value
black_material += black_count * value
balance = white_material - black_material
return {
"white_material": white_material,
"black_material": black_material,
"balance": balance,
"description": (
"White is winning" if balance > 300
else "White is better" if balance > 100
else "Equal" if abs(balance) <= 100
else "Black is better" if balance < -100
else "Black is winning"
),
}
@staticmethod
def piece_activity(board: python_chess.Board) -> Dict[str, Any]:
"""
Assess piece activity (how mobile and threatening pieces are).
Returns:
Dict with white_activity, black_activity scores
"""
white_moves = 0
black_moves = 0
white_copy = board.copy()
white_copy.turn = python_chess.WHITE
white_moves = len(list(white_copy.legal_moves))
black_copy = board.copy()
black_copy.turn = python_chess.BLACK
black_moves = len(list(black_copy.legal_moves))
return {
"white_moves": white_moves,
"black_moves": black_moves,
"white_activity": "high" if white_moves > 30 else "normal" if white_moves > 20 else "restricted",
"black_activity": "high" if black_moves > 30 else "normal" if black_moves > 20 else "restricted",
}
@staticmethod
def king_safety(board: python_chess.Board) -> Dict[str, str]:
"""
Assess king safety for both sides.
Returns:
Dict with white_safety, black_safety descriptions
"""
def assess_king(color: python_chess.Color) -> str:
king_square = board.king(color)
if king_square is None:
return "unknown"
# Check if king is in check
if board.is_check() and board.turn == color:
return "in_check"
# Count defending pieces around king
king_file = python_chess.square_file(king_square)
king_rank = python_chess.square_rank(king_square)
has_castling = (
board.has_kingside_castling_rights(color)
or board.has_queenside_castling_rights(color)
)
if not has_castling and king_file > 2 and king_file < 6:
return "exposed"
return "safe"
return {
"white_safety": assess_king(python_chess.WHITE),
"black_safety": assess_king(python_chess.BLACK),
}
@staticmethod
def pawn_structure(board: python_chess.Board) -> Dict[str, Any]:
"""
Analyze pawn structure (weak squares, passed pawns, etc.).
Returns:
Dict with pawn structure analysis
"""
white_pawns = board.pieces(python_chess.PAWN, python_chess.WHITE)
black_pawns = board.pieces(python_chess.PAWN, python_chess.BLACK)
# Check for passed pawns (simplified)
white_passed = []
black_passed = []
for pawn_square in white_pawns:
pawn_file = python_chess.square_file(pawn_square)
pawn_rank = python_chess.square_rank(pawn_square)
# Simplified: pawn is passed if no enemy pawns ahead on adjacent files
is_passed = True
for enemy_pawn in black_pawns:
enemy_file = python_chess.square_file(enemy_pawn)
if abs(enemy_file - pawn_file) <= 1 and python_chess.square_rank(enemy_pawn) > pawn_rank:
is_passed = False
break
if is_passed:
white_passed.append(python_chess.square_name(pawn_square))
# Same for black
for pawn_square in black_pawns:
pawn_file = python_chess.square_file(pawn_square)
pawn_rank = python_chess.square_rank(pawn_square)
is_passed = True
for enemy_pawn in white_pawns:
enemy_file = python_chess.square_file(enemy_pawn)
if abs(enemy_file - pawn_file) <= 1 and python_chess.square_rank(enemy_pawn) < pawn_rank:
is_passed = False
break
if is_passed:
black_passed.append(python_chess.square_name(pawn_square))
return {
"white_pawns": len(white_pawns),
"black_pawns": len(black_pawns),
"white_passed_pawns": white_passed,
"black_passed_pawns": black_passed,
"pawn_balance": "White ahead" if len(white_pawns) > len(black_pawns) else (
"Black ahead" if len(black_pawns) > len(white_pawns) else "Equal"
),
}
@staticmethod
def threats(board: python_chess.Board) -> Dict[str, List[str]]:
"""
Detect immediate threats (checks, attacks on pieces).
Returns:
Dict with white_threats, black_threats
"""
threats = {
"white_threats": [],
"black_threats": [],
}
# Check for checks
if board.is_check():
if board.turn == python_chess.WHITE:
threats["white_threats"].append("In check")
else:
threats["black_threats"].append("In check")
# Check for hanging pieces (could be improved)
for square in python_chess.SQUARES:
piece = board.piece_at(square)
if piece is None:
continue
# Check if piece is attacked
if board.is_attacked_by(not piece.color, square):
piece_name = python_chess.piece_name(piece.piece_type)
threat_str = f"{piece_name.capitalize()} hanging on {python_chess.square_name(square)}"
if piece.color == python_chess.WHITE:
threats["black_threats"].append(threat_str)
else:
threats["white_threats"].append(threat_str)
return threats
@staticmethod
def get_position_summary(board: python_chess.Board, player_color: Optional[bool] = None) -> Dict[str, Any]:
"""
Get a comprehensive position summary.
Args:
board: Chess board
player_color: WHITE, BLACK, or None to describe from both perspectives
Returns:
Dict with complete position analysis
"""
material = PositionAnalysis.material_balance(board)
activity = PositionAnalysis.piece_activity(board)
safety = PositionAnalysis.king_safety(board)
pawns = PositionAnalysis.pawn_structure(board)
threat_list = PositionAnalysis.threats(board)
summary = {
"material": material,
"activity": activity,
"king_safety": safety,
"pawn_structure": pawns,
"threats": threat_list,
"summary_text": (
f"Material: {material['description']}. "
f"White has {activity['white_moves']} legal moves, "
f"Black has {activity['black_moves']} legal moves."
),
}
return summary
@staticmethod
def describe_position_for_agent(board: python_chess.Board) -> str:
"""
Create a natural language description of the position for the agent.
Args:
board: Chess board
Returns:
Human-readable position description
"""
analysis = PositionAnalysis.get_position_summary(board)
material = analysis["material"]
activity = analysis["activity"]
safety = analysis["king_safety"]
threats = analysis["threats"]
lines = [
f"Position Status: {material['description']}",
f"White King: {safety['white_safety']}, Black King: {safety['black_safety']}",
f"White Mobility: {activity['white_activity']}, Black Mobility: {activity['black_activity']}",
]
if threats["white_threats"]:
lines.append(f"White threats: {', '.join(threats['white_threats'])}")
if threats["black_threats"]:
lines.append(f"Black threats: {', '.join(threats['black_threats'])}")
return "\n".join(lines)
__all__ = ["PositionAnalysis"]