BlueMoonBot commited on
Commit
edfef7e
·
verified ·
1 Parent(s): ded1e9a

Create stockfish.py

Browse files
Files changed (1) hide show
  1. fairy/stockfish.py +266 -0
fairy/stockfish.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chess
2
+ import time
3
+ import math
4
+
5
+ class ComplexMaiaEngine:
6
+ def __init__(self, skill_level=3):
7
+ # Default skill_level = 3 (~1300 Elo). Max = 20 (2000+ Elo conceptually, throttled by Python speed)
8
+ self.skill_level = max(1, min(20, skill_level))
9
+ self.nodes_visited = 0
10
+
11
+ # Piece values for Evaluation
12
+ self.PIECE_VALUES = {
13
+ chess.PAWN: 100,
14
+ chess.KNIGHT: 320,
15
+ chess.BISHOP: 330,
16
+ chess.ROOK: 500,
17
+ chess.QUEEN: 900,
18
+ chess.KING: 20000
19
+ }
20
+
21
+ # Piece-Square Tables (Heuristics for positional strength)
22
+ self.PAWN_TABLE = [
23
+ 0, 0, 0, 0, 0, 0, 0, 0,
24
+ 50, 50, 50, 50, 50, 50, 50, 50,
25
+ 10, 10, 20, 30, 30, 20, 10, 10,
26
+ 5, 5, 10, 25, 25, 10, 5, 5,
27
+ 0, 0, 0, 20, 20, 0, 0, 0,
28
+ 5, -5,-10, 0, 0,-10, -5, 5,
29
+ 5, 10, 10,-20,-20, 10, 10, 5,
30
+ 0, 0, 0, 0, 0, 0, 0, 0
31
+ ]
32
+
33
+ self.KNIGHT_TABLE = [
34
+ -50,-40,-30,-30,-30,-30,-40,-50,
35
+ -40,-20, 0, 0, 0, 0,-20,-40,
36
+ -30, 0, 10, 15, 15, 10, 0,-30,
37
+ -30, 5, 15, 20, 20, 15, 5,-30,
38
+ -30, 0, 15, 20, 20, 15, 0,-30,
39
+ -30, 5, 10, 15, 15, 10, 5,-30,
40
+ -40,-20, 0, 5, 5, 0,-20,-40,
41
+ -50,-40,-30,-30,-30,-30,-40,-50
42
+ ]
43
+
44
+ self.BISHOP_TABLE = [
45
+ -20,-10,-10,-10,-10,-10,-10,-20,
46
+ -10, 0, 0, 0, 0, 0, 0,-10,
47
+ -10, 0, 5, 10, 10, 5, 0,-10,
48
+ -10, 5, 5, 10, 10, 5, 5,-10,
49
+ -10, 0, 10, 10, 10, 10, 0,-10,
50
+ -10, 10, 10, 10, 10, 10, 10,-10,
51
+ -10, 5, 0, 0, 0, 0, 5,-10,
52
+ -20,-10,-10,-10,-10,-10,-10,-20
53
+ ]
54
+
55
+ self.ROOK_TABLE = [
56
+ 0, 0, 0, 0, 0, 0, 0, 0,
57
+ 5, 10, 10, 10, 10, 10, 10, 5,
58
+ -5, 0, 0, 0, 0, 0, 0, -5,
59
+ -5, 0, 0, 0, 0, 0, 0, -5,
60
+ -5, 0, 0, 0, 0, 0, 0, -5,
61
+ -5, 0, 0, 0, 0, 0, 0, -5,
62
+ -5, 0, 0, 0, 0, 0, 0, -5,
63
+ 0, 0, 0, 5, 5, 0, 0, 0
64
+ ]
65
+
66
+ self.QUEEN_TABLE = [
67
+ -20,-10,-10, -5, -5,-10,-10,-20,
68
+ -10, 0, 0, 0, 0, 0, 0,-10,
69
+ -10, 0, 5, 5, 5, 5, 0,-10,
70
+ -5, 0, 5, 5, 5, 5, 0, -5,
71
+ 0, 0, 5, 5, 5, 5, 0, -5,
72
+ -10, 5, 5, 5, 5, 5, 0,-10,
73
+ -10, 0, 5, 0, 0, 0, 0,-10,
74
+ -20,-10,-10, -5, -5,-10,-10,-20
75
+ ]
76
+
77
+ # King Middle-game position table
78
+ self.KING_TABLE = [
79
+ -30,-40,-40,-50,-50,-40,-40,-30,
80
+ -30,-40,-40,-50,-50,-40,-40,-30,
81
+ -30,-40,-40,-50,-50,-40,-40,-30,
82
+ -30,-40,-40,-50,-50,-40,-40,-30,
83
+ -20,-30,-30,-40,-40,-30,-30,-20,
84
+ -10,-20,-20,-20,-20,-20,-20,-10,
85
+ 20, 20, 0, 0, 0, 0, 20, 20,
86
+ 20, 30, 10, 0, 0, 10, 30, 20
87
+ ]
88
+
89
+ def set_skill_level(self, level):
90
+ self.skill_level = max(1, min(20, level))
91
+
92
+ def evaluate_board(self, board):
93
+ """ Statically evaluates the board layout. Positive = White advantage """
94
+ if board.is_checkmate():
95
+ if board.turn == chess.WHITE:
96
+ return -99999 # Black wins
97
+ else:
98
+ return 99999 # White wins
99
+ if board.is_stalemate() or board.is_insufficient_material():
100
+ return 0
101
+
102
+ score = 0
103
+
104
+ # Material and Positional Scanning
105
+ for square in chess.SQUARES:
106
+ piece = board.piece_at(square)
107
+ if piece:
108
+ piece_type = piece.piece_type
109
+ value = self.PIECE_VALUES[piece_type]
110
+
111
+ # Dynamic adjustment based on skill level
112
+ # Lower skill levels simulate poor board evaluation
113
+ if self.skill_level < 5 and piece_type in [chess.KNIGHT, chess.BISHOP]:
114
+ value -= 15 # Tweak values down to skew strategic choice
115
+
116
+ # Positional Table Additions
117
+ table_score = 0
118
+ square_idx = square if piece.color == chess.WHITE else chess.square_mirror(square)
119
+
120
+ if piece_type == chess.PAWN: table_score = self.PAWN_TABLE[square_idx]
121
+ elif piece_type == chess.KNIGHT: table_score = self.KNIGHT_TABLE[square_idx]
122
+ elif piece_type == chess.BISHOP: table_score = self.BISHOP_TABLE[square_idx]
123
+ elif piece_type == chess.ROOK: table_score = self.ROOK_TABLE[square_idx]
124
+ elif piece_type == chess.QUEEN: table_score = self.QUEEN_TABLE[square_idx]
125
+ elif piece_type == chess.KING: table_score = self.KING_TABLE[square_idx]
126
+
127
+ # Blunder emulation for low ELO
128
+ if self.skill_level <= 3 and time.time() % 7 == 0:
129
+ table_score = -table_score # Intentionally value positional squares wrong occasionally
130
+
131
+ if piece.color == chess.WHITE:
132
+ score += (value + table_score)
133
+ else:
134
+ score -= (value + table_score)
135
+
136
+ return score
137
+
138
+ def order_moves(self, board, moves):
139
+ """ Evaluates moves early to maximize Alpha-Beta Pruning Efficiency """
140
+ scored_moves = []
141
+ for move in moves:
142
+ score = 0
143
+ # Prioritize captures
144
+ if board.is_capture(move):
145
+ captured = board.piece_at(move.to_square)
146
+ attacker = board.piece_at(move.from_square)
147
+ if captured and attacker:
148
+ score += 10 * self.PIECE_VALUES[captured.piece_type] - self.PIECE_VALUES[attacker.piece_type]
149
+
150
+ # Prioritize promotions
151
+ if move.promotion:
152
+ score += 900
153
+
154
+ # Avoid putting pieces in danger
155
+ if board.is_attacked_by(not board.turn, move.to_square):
156
+ score -= self.PIECE_VALUES[board.piece_at(move.from_square).piece_type] if board.piece_at(move.from_square) else 50
157
+
158
+ scored_moves.append((score, move))
159
+
160
+ # High skill level gets perfect move ordering, low skill level gets chaotic ordering
161
+ if self.skill_level > 8:
162
+ scored_moves.sort(key=lambda x: x[0], reverse=True)
163
+
164
+ return [move for score, move in scored_moves]
165
+
166
+ def alpha_beta(self, board, depth, alpha, beta, maximizing_player):
167
+ """ Core Decision Engine Process """
168
+ self.nodes_visited += 1
169
+
170
+ if depth == 0 or board.is_game_over():
171
+ return self.evaluate_board(board)
172
+
173
+ legal_moves = self.order_moves(board, board.legal_moves)
174
+
175
+ if maximizing_player:
176
+ max_eval = -math.inf
177
+ for move in legal_moves:
178
+ board.push(move)
179
+ evaluation = self.alpha_beta(board, depth - 1, alpha, beta, False)
180
+ board.pop()
181
+ max_eval = max(max_eval, evaluation)
182
+ alpha = max(alpha, evaluation)
183
+ if beta <= alpha:
184
+ break # Beta Pruning
185
+ return max_eval
186
+ else:
187
+ min_eval = math.inf
188
+ for move in legal_moves:
189
+ board.push(move)
190
+ evaluation = self.alpha_beta(board, depth - 1, alpha, beta, True)
191
+ board.pop()
192
+ min_eval = min(min_eval, evaluation)
193
+ beta = min(beta, evaluation)
194
+ if beta <= alpha:
195
+ break # Alpha Pruning
196
+ return min_eval
197
+
198
+ def select_best_move(self, board):
199
+ """ Calculates optimal choice utilizing an Iterative Deepening technique """
200
+ self.nodes_visited = 0
201
+ best_move = None
202
+
203
+ # Determine strict search depths based on Skill Level
204
+ # Level 3 targets Depth 3 (Solid ~1300 ELO play)
205
+ if self.skill_level <= 3:
206
+ target_depth = 3
207
+ elif self.skill_level <= 8:
208
+ target_depth = 4
209
+ else:
210
+ target_depth = 5 # Maximum depth for pure Python before lag starts hitting hard
211
+
212
+ legal_moves = self.order_moves(board, board.legal_moves)
213
+
214
+ if not legal_moves:
215
+ return None
216
+
217
+ # Fallback default
218
+ best_move = legal_moves[0]
219
+
220
+ # Iterative Deepening wrapper loop
221
+ for current_depth in range(1, target_depth + 1):
222
+ if board.turn == chess.WHITE:
223
+ best_value = -math.inf
224
+ for move in legal_moves:
225
+ board.push(move)
226
+ board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, False)
227
+ board.pop()
228
+ if board_value > best_value:
229
+ best_value = board_value
230
+ best_move = move
231
+ else:
232
+ best_value = math.inf
233
+ for move in legal_moves:
234
+ board.push(move)
235
+ board_value = self.alpha_beta(board, current_depth - 1, -math.inf, math.inf, True)
236
+ board.pop()
237
+ if board_value < best_value:
238
+ best_value = board_value
239
+ best_move = move
240
+
241
+ print(f"Skill Level: {self.skill_level} | Evaluated {self.nodes_visited} Positions | Best Move: {best_move}")
242
+ return best_move
243
+
244
+ # ==============================================================================
245
+ # LOCAL EXECUTION DEMONSTRATION
246
+ # ==============================================================================
247
+ if __name__ == "__main__":
248
+ # Initialize the engine at default skill level 3 (approx. 1300 ELO)
249
+ engine = ComplexMaiaEngine(skill_level=3)
250
+ game_board = chess.Board()
251
+
252
+ print("--- Local Engine Game Simulation (First 3 Turns) ---")
253
+ print(game_board)
254
+
255
+ # Simulate a couple of turns to show the code functions correctly
256
+ for turn in range(3):
257
+ # White Move (Engine)
258
+ print("\nEngine Calculating...")
259
+ chosen_move = engine.select_best_move(game_board)
260
+ game_board.push(chosen_move)
261
+ print(game_board)
262
+
263
+ # Adjust skill slider on the fly to simulate switching behavior
264
+ if turn == 1:
265
+ print("\n!!! Level Scaled Up to MAX (20) !!!")
266
+ engine.set_skill_level(20)