pychessai 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pychessai-0.0.0/PKG-INFO +16 -0
- pychessai-0.0.0/README.md +1 -0
- pychessai-0.0.0/chessai/__init__.py +0 -0
- pychessai-0.0.0/chessai/core.py +344 -0
- pychessai-0.0.0/pychessai.egg-info/PKG-INFO +16 -0
- pychessai-0.0.0/pychessai.egg-info/SOURCES.txt +9 -0
- pychessai-0.0.0/pychessai.egg-info/dependency_links.txt +1 -0
- pychessai-0.0.0/pychessai.egg-info/requires.txt +1 -0
- pychessai-0.0.0/pychessai.egg-info/top_level.txt +1 -0
- pychessai-0.0.0/setup.cfg +4 -0
- pychessai-0.0.0/setup.py +15 -0
pychessai-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pychessai
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: chess ai which plays like a human
|
|
5
|
+
Home-page: https://github.com/infinity390/mathai4
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: lark-parser
|
|
9
|
+
Dynamic: description
|
|
10
|
+
Dynamic: description-content-type
|
|
11
|
+
Dynamic: home-page
|
|
12
|
+
Dynamic: requires-dist
|
|
13
|
+
Dynamic: requires-python
|
|
14
|
+
Dynamic: summary
|
|
15
|
+
|
|
16
|
+
dummy package
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dummy package
|
|
File without changes
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import sys
|
|
3
|
+
import chess
|
|
4
|
+
import copy
|
|
5
|
+
def evaluate_material(board):
|
|
6
|
+
if board.is_checkmate():
|
|
7
|
+
# If it's checkmate, return ±infinity based on who won
|
|
8
|
+
return float('-inf') if board.turn else float('inf')
|
|
9
|
+
|
|
10
|
+
piece_values = {
|
|
11
|
+
chess.PAWN: 100,
|
|
12
|
+
chess.KNIGHT: 300,
|
|
13
|
+
chess.BISHOP: 300,
|
|
14
|
+
chess.ROOK: 500,
|
|
15
|
+
chess.QUEEN: 900
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
white_score = 0
|
|
19
|
+
black_score = 0
|
|
20
|
+
white_bishops = 0
|
|
21
|
+
black_bishops = 0
|
|
22
|
+
|
|
23
|
+
for square, piece in board.piece_map().items():
|
|
24
|
+
value = piece_values.get(piece.piece_type, 0)
|
|
25
|
+
if piece.color == chess.WHITE:
|
|
26
|
+
white_score += value
|
|
27
|
+
if piece.piece_type == chess.BISHOP:
|
|
28
|
+
white_bishops += 1
|
|
29
|
+
else:
|
|
30
|
+
black_score += value
|
|
31
|
+
if piece.piece_type == chess.BISHOP:
|
|
32
|
+
black_bishops += 1
|
|
33
|
+
|
|
34
|
+
# Bishop pair bonus
|
|
35
|
+
if white_bishops >= 2:
|
|
36
|
+
white_score += 50
|
|
37
|
+
if black_bishops >= 2:
|
|
38
|
+
black_score += 50
|
|
39
|
+
|
|
40
|
+
return white_score - black_score
|
|
41
|
+
def normalize_fen(fen):
|
|
42
|
+
parts = fen.split(" ")
|
|
43
|
+
return parts[0]
|
|
44
|
+
|
|
45
|
+
position_history = []
|
|
46
|
+
|
|
47
|
+
def legal_moves_without_threefold_repetition(board):
|
|
48
|
+
global position_history
|
|
49
|
+
legal_moves = []
|
|
50
|
+
|
|
51
|
+
for move in board.legal_moves:
|
|
52
|
+
board.push(move)
|
|
53
|
+
normalized_fen = normalize_fen(board.fen())
|
|
54
|
+
if position_history.count(normalized_fen) > 1 or board.halfmove_clock > 10 or board.is_stalemate():
|
|
55
|
+
board.pop() # Undo the move
|
|
56
|
+
continue # Skip this move
|
|
57
|
+
|
|
58
|
+
legal_moves.append(move)
|
|
59
|
+
|
|
60
|
+
board.pop() # Undo the move
|
|
61
|
+
|
|
62
|
+
return legal_moves
|
|
63
|
+
|
|
64
|
+
def negamax_capture_pv(board, depth, alpha=float("-inf"), beta=float("inf"), loc=None):
|
|
65
|
+
if depth == 0 or board.is_game_over():
|
|
66
|
+
return colorsign(board) * evaluate_material(board), []
|
|
67
|
+
|
|
68
|
+
max_eval = float("-inf")
|
|
69
|
+
best_line = []
|
|
70
|
+
|
|
71
|
+
# Capture moves
|
|
72
|
+
for move in legal_moves_without_threefold_repetition(board):
|
|
73
|
+
if board.is_capture(move) and (loc is None or loc == move.to_square):
|
|
74
|
+
board.push(move)
|
|
75
|
+
score, line = negamax_capture_pv(board, depth - 1, -beta, -alpha, move.to_square)
|
|
76
|
+
board.pop()
|
|
77
|
+
|
|
78
|
+
score = -score
|
|
79
|
+
if score > max_eval:
|
|
80
|
+
max_eval = score
|
|
81
|
+
best_line = [move] + line
|
|
82
|
+
alpha = max(alpha, score)
|
|
83
|
+
if alpha >= beta:
|
|
84
|
+
break # Beta cutoff
|
|
85
|
+
|
|
86
|
+
# Quiet moves at depth 0
|
|
87
|
+
for move in legal_moves_without_threefold_repetition(board):
|
|
88
|
+
if not board.is_capture(move):
|
|
89
|
+
board.push(move)
|
|
90
|
+
score, line = negamax_capture_pv(board, 0, -beta, -alpha)
|
|
91
|
+
board.pop()
|
|
92
|
+
|
|
93
|
+
score = -score
|
|
94
|
+
if score > max_eval:
|
|
95
|
+
max_eval = score
|
|
96
|
+
best_line = [move] + line
|
|
97
|
+
alpha = max(alpha, score)
|
|
98
|
+
if alpha >= beta:
|
|
99
|
+
break # Beta cutoff
|
|
100
|
+
|
|
101
|
+
if max_eval == float("-inf"):
|
|
102
|
+
return colorsign(board) * evaluate_material(board), []
|
|
103
|
+
|
|
104
|
+
return max_eval, best_line
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def colorsign(board):
|
|
109
|
+
return 1 if board.turn == chess.WHITE else -1
|
|
110
|
+
|
|
111
|
+
PST = {
|
|
112
|
+
chess.PAWN: [
|
|
113
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
114
|
+
5, 5, 5, 5, 5, 5, 5, 5,
|
|
115
|
+
1, 1, 2, 3, 3, 2, 1, 1,
|
|
116
|
+
0, 0, 0, 2, 2, 0, 0, 0,
|
|
117
|
+
0, 0, 0, -2, -2, 0, 0, 0,
|
|
118
|
+
1, -1, -2, 0, 0, -2, -1, 1,
|
|
119
|
+
1, 2, 2, -2, -2, 2, 2, 1,
|
|
120
|
+
0, 0, 0, 0, 0, 0, 0, 0
|
|
121
|
+
],
|
|
122
|
+
|
|
123
|
+
chess.KNIGHT: [
|
|
124
|
+
-5, -4, -3, -3, -3, -3, -4, -5,
|
|
125
|
+
-4, -2, 0, 0, 0, 0, -2, -4,
|
|
126
|
+
-3, 0, 1, 1.5, 1.5, 1, 0, -3,
|
|
127
|
+
-3, 0.5, 1.5, 2, 2, 1.5, 0.5, -3,
|
|
128
|
+
-3, 0, 1.5, 2, 2, 1.5, 0, -3,
|
|
129
|
+
-3, 0.5, 1, 1.5, 1.5, 1, 0.5, -3,
|
|
130
|
+
-4, -2, 0, 0.5, 0.5, 0, -2, -4,
|
|
131
|
+
-5, -4, -3, -3, -3, -3, -4, -5
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
chess.BISHOP: [
|
|
135
|
+
-2, -1, -1, -1, -1, -1, -1, -2,
|
|
136
|
+
-1, 0, 0, 0, 0, 0, 0, -1,
|
|
137
|
+
-1, 0, 0.5, 1, 1, 0.5, 0, -1,
|
|
138
|
+
-1, 0.5, 0.5, 1, 1, 0.5, 0.5, -1,
|
|
139
|
+
-1, 0, 1, 1, 1, 1, 0, -1,
|
|
140
|
+
-1, 1, 1, 1, 1, 1, 1, -1,
|
|
141
|
+
-1, 0.5, 0, 0, 0, 0, 0.5, -1,
|
|
142
|
+
-2, -1, -1, -1, -1, -1, -1, -2
|
|
143
|
+
],
|
|
144
|
+
|
|
145
|
+
chess.ROOK: [
|
|
146
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
147
|
+
0.5, 1, 1, 1, 1, 1, 1, 0.5,
|
|
148
|
+
-0.5, 0, 0, 0, 0, 0, 0, -0.5,
|
|
149
|
+
-0.5, 0, 0, 0, 0, 0, 0, -0.5,
|
|
150
|
+
-0.5, 0, 0, 0, 0, 0, 0, -0.5,
|
|
151
|
+
-0.5, 0, 0, 0, 0, 0, 0, -0.5,
|
|
152
|
+
-0.5, 0, 0, 0, 0, 0, 0, -0.5,
|
|
153
|
+
0, 0, 0, 0.5, 0.5, 0, 0, 0
|
|
154
|
+
],
|
|
155
|
+
|
|
156
|
+
chess.QUEEN: [
|
|
157
|
+
-2, -1, -1, -0.5, -0.5, -1, -1, -2,
|
|
158
|
+
-1, 0, 0, 0, 0, 0, 0, -1,
|
|
159
|
+
-1, 0, 0.5, 0.5, 0.5, 0.5, 0, -1,
|
|
160
|
+
-0.5, 0, 0.5, 0.5, 0.5, 0.5, 0, -0.5,
|
|
161
|
+
0, 0, 0.5, 0.5, 0.5, 0.5, 0, -0.5,
|
|
162
|
+
-1, 0.5, 0.5, 0.5, 0.5, 0.5, 0, -1,
|
|
163
|
+
-1, 0, 0.5, 0, 0, 0, 0, -1,
|
|
164
|
+
-2, -1, -1, -0.5, -0.5, -1, -1, -2
|
|
165
|
+
],
|
|
166
|
+
|
|
167
|
+
chess.KING: [
|
|
168
|
+
-3, -4, -4, -5, -5, -4, -4, -3,
|
|
169
|
+
-3, -4, -4, -5, -5, -4, -4, -3,
|
|
170
|
+
-3, -4, -4, -5, -5, -4, -4, -3,
|
|
171
|
+
-3, -4, -4, -5, -5, -4, -4, -3,
|
|
172
|
+
-2, -3, -3, -4, -4, -3, -3, -2,
|
|
173
|
+
-1, -2, -2, -2, -2, -2, -2, -1,
|
|
174
|
+
2, 2, 0, 0, 0, 0, 2, 2,
|
|
175
|
+
2, 3, 1, 0, 0, 1, 3, 2
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Get PST value depending on piece color
|
|
181
|
+
def pst_value(piece_type, square, color):
|
|
182
|
+
if piece_type not in PST:
|
|
183
|
+
return 0
|
|
184
|
+
index = square if color == chess.WHITE else chess.square_mirror(square)
|
|
185
|
+
return PST[piece_type][index]
|
|
186
|
+
|
|
187
|
+
# Sort legal moves by PST score
|
|
188
|
+
def sort_moves_by_pst(board):
|
|
189
|
+
legal_moves = list(legal_moves_without_threefold_repetition(board))
|
|
190
|
+
|
|
191
|
+
def move_score(move):
|
|
192
|
+
piece = board.piece_at(move.from_square)
|
|
193
|
+
if piece is None:
|
|
194
|
+
return 0
|
|
195
|
+
return pst_value(piece.piece_type, move.to_square, piece.color)
|
|
196
|
+
|
|
197
|
+
return sorted(legal_moves, key=move_score, reverse=True)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def try_center_push(board):
|
|
201
|
+
sorted_moves = sort_moves_by_pst(board)
|
|
202
|
+
lst = []
|
|
203
|
+
for move in sorted_moves:
|
|
204
|
+
board2 = copy.deepcopy(board)
|
|
205
|
+
board2.push(move)
|
|
206
|
+
tmp, line = negamax_capture_pv(board2, 100)
|
|
207
|
+
lst.append([move, tmp])
|
|
208
|
+
print(line)
|
|
209
|
+
print(tmp)
|
|
210
|
+
print(evaluate_material(board))
|
|
211
|
+
if board.turn == chess.BLACK and -tmp - evaluate_material(board) >= 0:
|
|
212
|
+
return move
|
|
213
|
+
if board.turn == chess.WHITE and -tmp - evaluate_material(board) >= 0:
|
|
214
|
+
return move
|
|
215
|
+
if lst == []:
|
|
216
|
+
return None
|
|
217
|
+
return sorted(lst, key=lambda x: x[1])[0][0]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def king_search(board, depth=2, alpha=float("-inf"), beta=float("inf")):
|
|
221
|
+
|
|
222
|
+
if depth == 0 or board.is_game_over():
|
|
223
|
+
return colorsign(board) * evaluate_material(board), []
|
|
224
|
+
|
|
225
|
+
max_eval = float("-inf")
|
|
226
|
+
best_line = []
|
|
227
|
+
|
|
228
|
+
if depth == 2:
|
|
229
|
+
legal = [move for move in legal_moves_without_threefold_repetition(board) if board.gives_check(move)]
|
|
230
|
+
else:
|
|
231
|
+
legal = list(legal_moves_without_threefold_repetition(board))
|
|
232
|
+
|
|
233
|
+
for move in legal:
|
|
234
|
+
board.push(move)
|
|
235
|
+
score, line = None, None
|
|
236
|
+
if depth == 1:
|
|
237
|
+
score, line = negamax_capture_pv(board, 100, -beta, -alpha)
|
|
238
|
+
else:
|
|
239
|
+
score, line = king_search(board, depth - 1, -beta, -alpha)
|
|
240
|
+
board.pop()
|
|
241
|
+
|
|
242
|
+
score = -score
|
|
243
|
+
if score > max_eval:
|
|
244
|
+
max_eval = score
|
|
245
|
+
best_line = [move] + line
|
|
246
|
+
|
|
247
|
+
alpha = max(alpha, score)
|
|
248
|
+
if alpha >= beta:
|
|
249
|
+
break # Beta cutoff
|
|
250
|
+
|
|
251
|
+
if max_eval == float("-inf"):
|
|
252
|
+
return colorsign(board) * evaluate_material(board), []
|
|
253
|
+
|
|
254
|
+
return max_eval, best_line
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
'''
|
|
258
|
+
board = chess.Board()
|
|
259
|
+
|
|
260
|
+
fen = "r1b1kbnr/1pp1pppp/n7/p2q4/3P4/5N2/PPP2PPP/RNBQKB1R w KQkq - 0 1"
|
|
261
|
+
fen = "r1bk1bnr/1pNp1ppp/2n2q2/p3p3/4P3/5N2/PPPP1PPP/R1BQKB1R w KQkq - 0 1"
|
|
262
|
+
board.set_fen(fen)
|
|
263
|
+
|
|
264
|
+
print(board)
|
|
265
|
+
print(evaluate_material(board))
|
|
266
|
+
print()
|
|
267
|
+
print()
|
|
268
|
+
|
|
269
|
+
position startpos moves e2e3 g8f6 d2d3 f6e4 d3e4 b8c6
|
|
270
|
+
position startpos moves e2e3 g8f6 d2d3 f6e4 d3e4 b8c6 f2f3 d7d5
|
|
271
|
+
position startpos moves e2e3 g8f6 d2d3 f6e4 d3e4 b8c6 f2f3 d7d5 d1d5 d8d5 e4d5 c8d7 d5c6 d7c6 e3e4 e8c8 c2c3 e7e5
|
|
272
|
+
'''
|
|
273
|
+
|
|
274
|
+
def playmove2(board):
|
|
275
|
+
|
|
276
|
+
score, line = king_search(board)
|
|
277
|
+
if board.turn == chess.WHITE and score - evaluate_material(board) > 0:
|
|
278
|
+
|
|
279
|
+
return line[0]
|
|
280
|
+
elif board.turn == chess.BLACK and score - evaluate_material(board) > 0:
|
|
281
|
+
|
|
282
|
+
return line[0]
|
|
283
|
+
|
|
284
|
+
score, line = negamax_capture_pv(board, 100)
|
|
285
|
+
if board.turn == chess.WHITE and score - evaluate_material(board) > 0:
|
|
286
|
+
return line[0]
|
|
287
|
+
elif board.turn == chess.BLACK and score - evaluate_material(board) > 0:
|
|
288
|
+
|
|
289
|
+
return line[0]
|
|
290
|
+
|
|
291
|
+
tmp = try_center_push(board)
|
|
292
|
+
|
|
293
|
+
if tmp is not None:
|
|
294
|
+
return tmp
|
|
295
|
+
return random.choice(list(board.legal_moves))
|
|
296
|
+
|
|
297
|
+
def playmove(board):
|
|
298
|
+
tmp = playmove2(board)
|
|
299
|
+
if tmp is None:
|
|
300
|
+
return None
|
|
301
|
+
# Return the new board after applying the move from playmove2
|
|
302
|
+
board.push(tmp)
|
|
303
|
+
return board
|
|
304
|
+
|
|
305
|
+
def uci_loop():
|
|
306
|
+
global position_history
|
|
307
|
+
board = chess.Board()
|
|
308
|
+
|
|
309
|
+
while True:
|
|
310
|
+
try:
|
|
311
|
+
command = input().strip()
|
|
312
|
+
except EOFError:
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
if command == "uci":
|
|
316
|
+
print("id name MyEngine")
|
|
317
|
+
print("id author You")
|
|
318
|
+
print("uciok")
|
|
319
|
+
elif command == "isready":
|
|
320
|
+
print("readyok")
|
|
321
|
+
elif command.startswith("position"):
|
|
322
|
+
tokens = command.split()
|
|
323
|
+
board = chess.Board()
|
|
324
|
+
position_history = []
|
|
325
|
+
if "startpos" in tokens:
|
|
326
|
+
moves_index = tokens.index("moves") if "moves" in tokens else None
|
|
327
|
+
if moves_index:
|
|
328
|
+
for move in tokens[moves_index + 1:]:
|
|
329
|
+
board.push_uci(move)
|
|
330
|
+
position_history.append(normalize_fen(board.fen()))
|
|
331
|
+
# You can also add support for "fen" if needed to initialize the board
|
|
332
|
+
elif command.startswith("go"):
|
|
333
|
+
new_board = playmove(board)
|
|
334
|
+
if new_board:
|
|
335
|
+
move = new_board.peek() # Get the most recent move after playing the move
|
|
336
|
+
print(f"bestmove {move}")
|
|
337
|
+
else:
|
|
338
|
+
print("bestmove 0000")
|
|
339
|
+
elif command == "quit":
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
if __name__ == "__main__":
|
|
343
|
+
uci_loop()
|
|
344
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pychessai
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: chess ai which plays like a human
|
|
5
|
+
Home-page: https://github.com/infinity390/mathai4
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: lark-parser
|
|
9
|
+
Dynamic: description
|
|
10
|
+
Dynamic: description-content-type
|
|
11
|
+
Dynamic: home-page
|
|
12
|
+
Dynamic: requires-dist
|
|
13
|
+
Dynamic: requires-python
|
|
14
|
+
Dynamic: summary
|
|
15
|
+
|
|
16
|
+
dummy package
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lark-parser
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chessai
|
pychessai-0.0.0/setup.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="pychessai",
|
|
5
|
+
version="0.0.0",
|
|
6
|
+
description="chess ai which plays like a human",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
url="https://github.com/infinity390/mathai4",
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
install_requires=[
|
|
12
|
+
"lark-parser"
|
|
13
|
+
],
|
|
14
|
+
python_requires=">=3.7",
|
|
15
|
+
)
|