Rafs-an09002 commited on
Commit
b8f4b95
·
verified ·
1 Parent(s): 5950ee5

Create engine/endgame.py

Browse files
Files changed (1) hide show
  1. engine/endgame.py +208 -0
engine/endgame.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Endgame Detection and Special Handling
3
+ Research: Nalimov/Syzygy Tablebases, Stockfish endgame evaluation
4
+ """
5
+
6
+ import chess
7
+ from typing import Optional
8
+
9
+
10
+ class EndgameDetector:
11
+ """
12
+ Detect endgame phase and apply special handling
13
+ """
14
+
15
+ # Material thresholds for endgame detection
16
+ ENDGAME_MATERIAL = {
17
+ 'pawn_endgame': 0, # Only pawns + kings
18
+ 'minor_endgame': 660, # 2 minor pieces or less
19
+ 'major_endgame': 1320, # Rooks/Queens but limited material
20
+ }
21
+
22
+ def __init__(self):
23
+ self.phase = 'middlegame'
24
+
25
+ def detect_phase(self, board: chess.Board) -> str:
26
+ """
27
+ Detect game phase based on material
28
+
29
+ Returns:
30
+ 'opening', 'middlegame', or 'endgame'
31
+ """
32
+ # Count material (excluding kings)
33
+ total_material = 0
34
+ piece_values = {
35
+ chess.PAWN: 1,
36
+ chess.KNIGHT: 3,
37
+ chess.BISHOP: 3,
38
+ chess.ROOK: 5,
39
+ chess.QUEEN: 9
40
+ }
41
+
42
+ for piece_type in piece_values:
43
+ count_white = len(board.pieces(piece_type, chess.WHITE))
44
+ count_black = len(board.pieces(piece_type, chess.BLACK))
45
+ total_material += (count_white + count_black) * piece_values[piece_type]
46
+
47
+ # Phase detection
48
+ if board.fullmove_number < 10:
49
+ self.phase = 'opening'
50
+ elif total_material <= 16: # Rough endgame threshold
51
+ self.phase = 'endgame'
52
+ else:
53
+ self.phase = 'middlegame'
54
+
55
+ return self.phase
56
+
57
+ def is_known_draw(self, board: chess.Board) -> bool:
58
+ """
59
+ Check for known theoretical draws
60
+
61
+ Returns:
62
+ True if position is known draw
63
+ """
64
+ # Insufficient material
65
+ if board.is_insufficient_material():
66
+ return True
67
+
68
+ # Fifty-move rule
69
+ if board.halfmove_clock >= 100:
70
+ return True
71
+
72
+ # Specific endgame draws
73
+ if self._is_kxk(board):
74
+ return True
75
+
76
+ return False
77
+
78
+ def _is_kxk(self, board: chess.Board) -> bool:
79
+ """Check for King vs King (or with insufficient material)"""
80
+ pieces = board.piece_map()
81
+
82
+ # Count non-king pieces
83
+ non_king_pieces = sum(1 for p in pieces.values() if p.piece_type != chess.KING)
84
+
85
+ # K vs K
86
+ if non_king_pieces == 0:
87
+ return True
88
+
89
+ # K+B vs K or K+N vs K (insufficient)
90
+ if non_king_pieces == 1:
91
+ for piece in pieces.values():
92
+ if piece.piece_type in [chess.BISHOP, chess.KNIGHT]:
93
+ return True
94
+
95
+ return False
96
+
97
+ def adjust_evaluation(self, board: chess.Board, eval_score: float) -> float:
98
+ """
99
+ Adjust evaluation based on endgame knowledge
100
+
101
+ Args:
102
+ board: Current position
103
+ eval_score: Raw evaluation score
104
+
105
+ Returns:
106
+ Adjusted evaluation
107
+ """
108
+ phase = self.detect_phase(board)
109
+
110
+ # Known draws
111
+ if self.is_known_draw(board):
112
+ return 0.0
113
+
114
+ # Endgame adjustments
115
+ if phase == 'endgame':
116
+ # King activity bonus in endgame
117
+ king_activity_bonus = self._king_activity_bonus(board)
118
+ eval_score += king_activity_bonus
119
+
120
+ # Pawn endgame evaluation
121
+ if self._is_pawn_endgame(board):
122
+ pawn_eval = self._evaluate_pawn_endgame(board)
123
+ eval_score = eval_score * 0.7 + pawn_eval * 0.3
124
+
125
+ return eval_score
126
+
127
+ def _king_activity_bonus(self, board: chess.Board) -> float:
128
+ """
129
+ Calculate king activity bonus in endgame
130
+ Active king is crucial in endgame
131
+ """
132
+ bonus = 0.0
133
+
134
+ for color in [chess.WHITE, chess.BLACK]:
135
+ king_sq = board.king(color)
136
+ if king_sq is None:
137
+ continue
138
+
139
+ # Center proximity (Manhattan distance from center)
140
+ rank, file = divmod(king_sq, 8)
141
+ center_distance = abs(rank - 3.5) + abs(file - 3.5)
142
+
143
+ # Closer to center = better
144
+ activity = (7 - center_distance) * 5
145
+
146
+ if color == chess.WHITE:
147
+ bonus += activity
148
+ else:
149
+ bonus -= activity
150
+
151
+ return bonus
152
+
153
+ def _is_pawn_endgame(self, board: chess.Board) -> bool:
154
+ """Check if position is pure pawn endgame"""
155
+ for piece_type in [chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN]:
156
+ if len(board.pieces(piece_type, chess.WHITE)) > 0:
157
+ return False
158
+ if len(board.pieces(piece_type, chess.BLACK)) > 0:
159
+ return False
160
+ return True
161
+
162
+ def _evaluate_pawn_endgame(self, board: chess.Board) -> float:
163
+ """
164
+ Special evaluation for pawn endgames
165
+ Focus on: passed pawns, king proximity, pawn races
166
+ """
167
+ eval = 0.0
168
+
169
+ # Passed pawn evaluation
170
+ for color in [chess.WHITE, chess.BLACK]:
171
+ for pawn_sq in board.pieces(chess.PAWN, color):
172
+ if self._is_passed_pawn(board, pawn_sq, color):
173
+ # Passed pawn bonus (increases closer to promotion)
174
+ rank = pawn_sq // 8
175
+ if color == chess.WHITE:
176
+ distance_to_promotion = 7 - rank
177
+ eval += (7 - distance_to_promotion) * 20
178
+ else:
179
+ distance_to_promotion = rank
180
+ eval -= (7 - distance_to_promotion) * 20
181
+
182
+ return eval
183
+
184
+ def _is_passed_pawn(self, board: chess.Board, pawn_sq: int, color: chess.Color) -> bool:
185
+ """Check if pawn is passed (no opposing pawns ahead)"""
186
+ rank, file = divmod(pawn_sq, 8)
187
+
188
+ # Check files: current, left, right
189
+ files_to_check = [file]
190
+ if file > 0:
191
+ files_to_check.append(file - 1)
192
+ if file < 7:
193
+ files_to_check.append(file + 1)
194
+
195
+ # Check if any enemy pawns block path
196
+ if color == chess.WHITE:
197
+ ranks_ahead = range(rank + 1, 8)
198
+ else:
199
+ ranks_ahead = range(0, rank)
200
+
201
+ for check_rank in ranks_ahead:
202
+ for check_file in files_to_check:
203
+ check_sq = check_rank * 8 + check_file
204
+ piece = board.piece_at(check_sq)
205
+ if piece and piece.piece_type == chess.PAWN and piece.color != color:
206
+ return False
207
+
208
+ return True