-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
388 lines (335 loc) · 13.2 KB
/
Copy pathutils.py
File metadata and controls
388 lines (335 loc) · 13.2 KB
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
from copy import deepcopy
import random
PIECE_VALUES = {'p': 100, 'n': 300, 'b': 330, 'r': 500, 'q': 900, 'k': 0}
# Taş-kare tabloları (beyaz perspektifinden; siyah için dikey çevrilir)
_PST = {
'p': [
0, 0, 0, 0, 0, 0, 0, 0,
50, 50, 50, 50, 50, 50, 50, 50,
10, 10, 20, 30, 30, 20, 10, 10,
5, 5, 10, 25, 25, 10, 5, 5,
0, 0, 0, 20, 20, 0, 0, 0,
5, -5,-10, 0, 0,-10, -5, 5,
5, 10, 10,-20,-20, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0,
],
'n': [
-50,-40,-30,-30,-30,-30,-40,-50,
-40,-20, 0, 0, 0, 0,-20,-40,
-30, 0, 10, 15, 15, 10, 0,-30,
-30, 5, 15, 20, 20, 15, 5,-30,
-30, 0, 15, 20, 20, 15, 0,-30,
-30, 5, 10, 15, 15, 10, 5,-30,
-40,-20, 0, 5, 5, 0,-20,-40,
-50,-40,-30,-30,-30,-30,-40,-50,
],
'b': [
-20,-10,-10,-10,-10,-10,-10,-20,
-10, 0, 0, 0, 0, 0, 0,-10,
-10, 0, 5, 10, 10, 5, 0,-10,
-10, 5, 5, 10, 10, 5, 5,-10,
-10, 0, 10, 10, 10, 10, 0,-10,
-10, 10, 10, 10, 10, 10, 10,-10,
-10, 5, 0, 0, 0, 0, 5,-10,
-20,-10,-10,-10,-10,-10,-10,-20,
],
'r': [
0, 0, 0, 0, 0, 0, 0, 0,
5, 10, 10, 10, 10, 10, 10, 5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
0, 0, 0, 5, 5, 0, 0, 0,
],
'q': [
-20,-10,-10, -5, -5,-10,-10,-20,
-10, 0, 0, 0, 0, 0, 0,-10,
-10, 0, 5, 5, 5, 5, 0,-10,
-5, 0, 5, 5, 5, 5, 0, -5,
0, 0, 5, 5, 5, 5, 0, -5,
-10, 5, 5, 5, 5, 5, 0,-10,
-10, 0, 5, 0, 0, 0, 0,-10,
-20,-10,-10, -5, -5,-10,-10,-20,
],
'k': [ # Orta oyun kral güvenliği
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-20,-30,-30,-40,-40,-30,-30,-20,
-10,-20,-20,-20,-20,-20,-20,-10,
20, 20, 0, 0, 0, 0, 20, 20,
20, 30, 10, 0, 0, 10, 30, 20,
],
}
# ── Temel fonksiyonlar ────────────────────────────────────────────────────────
def find_king(board, color):
for r in range(8):
for c in range(8):
if board[r][c] == color + "k":
return (r, c)
return None
def get_raw_moves(board, pos):
"""Şah filtresi olmadan ham hamleleri üretir (saldırı tespiti için)."""
moves = []
r, c = pos
piece = board[r][c]
if piece == "--":
return moves
color = piece[0]
ptype = piece[1]
if ptype == "p":
direction = -1 if color == "w" else 1
start_row = 6 if color == "w" else 1
if 0 <= r + direction < 8 and board[r + direction][c] == "--":
moves.append((r + direction, c))
if r == start_row and board[r + 2 * direction][c] == "--":
moves.append((r + 2 * direction, c))
for dc in [-1, 1]:
nr, nc = r + direction, c + dc
if 0 <= nr < 8 and 0 <= nc < 8:
target = board[nr][nc]
if target != "--" and target[0] != color:
moves.append((nr, nc))
elif ptype in ("r", "b", "q"):
if ptype == "r":
dirs = [(-1,0),(1,0),(0,-1),(0,1)]
elif ptype == "b":
dirs = [(-1,-1),(-1,1),(1,-1),(1,1)]
else:
dirs = [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(-1,1),(1,-1),(1,1)]
for dr, dc in dirs:
nr, nc = r + dr, c + dc
while 0 <= nr < 8 and 0 <= nc < 8:
target = board[nr][nc]
if target == "--":
moves.append((nr, nc))
else:
if target[0] != color:
moves.append((nr, nc))
break
nr += dr; nc += dc
elif ptype == "n":
for dr, dc in [(-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < 8 and 0 <= nc < 8:
target = board[nr][nc]
if target == "--" or target[0] != color:
moves.append((nr, nc))
elif ptype == "k":
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(-1,1),(1,-1),(1,1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < 8 and 0 <= nc < 8:
target = board[nr][nc]
if target == "--" or target[0] != color:
moves.append((nr, nc))
return moves
def is_square_attacked(board, square, by_color):
for row in range(8):
for col in range(8):
if board[row][col].startswith(by_color):
if square in get_raw_moves(board, (row, col)):
return True
return False
def is_in_check(board, color):
king_pos = find_king(board, color)
if king_pos is None:
return True
opponent = "b" if color == "w" else "w"
return is_square_attacked(board, king_pos, opponent)
def get_valid_moves(board, pos, game_state=None):
if game_state is None:
game_state = _default_gs()
r, c = pos
piece = board[r][c]
if piece == "--":
return []
color = piece[0]
ptype = piece[1]
opponent = "b" if color == "w" else "w"
moves = get_raw_moves(board, pos)
# Geçerken al
if ptype == "p":
ep = game_state.get('en_passant')
if ep:
direction = -1 if color == "w" else 1
if ep[0] == r + direction and abs(ep[1] - c) == 1:
moves.append(ep)
# Rok
if ptype == "k" and not is_in_check(board, color):
castle_row = 7 if color == "w" else 0
cr = game_state.get('castling', {}).get(color, {})
if cr.get('k', False):
if board[castle_row][5] == "--" and board[castle_row][6] == "--":
if (not is_square_attacked(board, (castle_row, 5), opponent) and
not is_square_attacked(board, (castle_row, 6), opponent)):
moves.append((castle_row, 6))
if cr.get('q', False):
if (board[castle_row][1] == "--" and board[castle_row][2] == "--"
and board[castle_row][3] == "--"):
if (not is_square_attacked(board, (castle_row, 3), opponent) and
not is_square_attacked(board, (castle_row, 2), opponent)):
moves.append((castle_row, 2))
# Şah tehdit eden hamleleri filtrele (make/unmake ile, deepcopy olmadan)
legal = []
for move in moves:
mr, mc = move
saved_dest = board[mr][mc]
ep_save = None
if ptype == "p" and game_state.get('en_passant') == move:
ep_save = board[r][mc]
board[r][mc] = "--"
board[mr][mc] = piece
board[r][c] = "--"
if not is_in_check(board, color):
legal.append(move)
board[r][c] = piece
board[mr][mc] = saved_dest
if ep_save is not None:
board[r][mc] = ep_save
return legal
def get_all_valid_moves(board, color, game_state=None):
all_moves = []
for row in range(8):
for col in range(8):
if board[row][col].startswith(color):
for move in get_valid_moves(board, (row, col), game_state):
all_moves.append(((row, col), move))
return all_moves
def is_checkmate(board, color, game_state=None):
return is_in_check(board, color) and len(get_all_valid_moves(board, color, game_state)) == 0
def is_stalemate(board, color, game_state=None):
return not is_in_check(board, color) and len(get_all_valid_moves(board, color, game_state)) == 0
def apply_move(board, from_pos, to_pos, game_state):
"""Hamleyi uygular, yeni board ve game_state döndürür."""
new_board = deepcopy(board)
new_gs = deepcopy(game_state)
r, c = from_pos
dr, dc = to_pos
piece = new_board[r][c]
color = piece[0]
ptype = piece[1]
opponent = "b" if color == "w" else "w"
captured = new_board[dr][dc]
new_gs['en_passant'] = None
# Geçerken al
if ptype == "p" and game_state.get('en_passant') == to_pos:
new_board[r][dc] = "--"
# Çift piyonu ilerleme → en passant hedefi ata
if ptype == "p" and abs(dr - r) == 2:
new_gs['en_passant'] = ((r + dr) // 2, c)
# Rok: kaleyi de taşı
if ptype == "k":
new_gs['castling'][color] = {'k': False, 'q': False}
if abs(dc - c) == 2:
castle_row = 7 if color == "w" else 0
if dc == 6:
new_board[castle_row][5] = new_board[castle_row][7]
new_board[castle_row][7] = "--"
elif dc == 2:
new_board[castle_row][3] = new_board[castle_row][0]
new_board[castle_row][0] = "--"
if ptype == "r":
home_row = 7 if color == "w" else 0
if r == home_row:
if c == 7: new_gs['castling'][color]['k'] = False
elif c == 0: new_gs['castling'][color]['q'] = False
if captured.endswith("r"):
opp_home = 0 if opponent == "w" else 7
if dr == opp_home:
if dc == 7: new_gs['castling'][opponent]['k'] = False
elif dc == 0: new_gs['castling'][opponent]['q'] = False
new_board[dr][dc] = piece
new_board[r][c] = "--"
return new_board, new_gs
# ── Değerlendirme ─────────────────────────────────────────────────────────────
def evaluate_board(board):
"""Materyal + taş-kare tablosu değerlendirmesi (santipyon cinsinden)."""
score = 0
for row in range(8):
for col in range(8):
p = board[row][col]
if p == "--":
continue
color, ptype = p[0], p[1]
val = PIECE_VALUES.get(ptype, 0)
pst = _PST.get(ptype, [0] * 64)
idx = (7 - row) * 8 + col if color == "w" else row * 8 + col
total = val + pst[idx]
score += total if color == "w" else -total
return score
# ── Minimax AI ────────────────────────────────────────────────────────────────
_AI_DEPTH = 3 # Arama derinliği
def _order_moves(board, moves):
"""Hamleleri sırala: MVV-LVA (değerli kurban – düşük değerli avcı önce)."""
captures, others = [], []
for fp, tp in moves:
victim = board[tp[0]][tp[1]]
if victim != "--":
attacker = board[fp[0]][fp[1]]
score = PIECE_VALUES.get(victim[1], 0) * 10 - PIECE_VALUES.get(attacker[1], 0)
captures.append((score, fp, tp))
else:
others.append((fp, tp))
captures.sort(key=lambda x: x[0], reverse=True)
return [(fp, tp) for _, fp, tp in captures] + others
def _minimax(board, gs, depth, alpha, beta, maximizing):
"""Alpha-beta budamalı minimax."""
if depth == 0:
return evaluate_board(board)
color = "w" if maximizing else "b"
all_moves = _order_moves(board, get_all_valid_moves(board, color, gs))
if not all_moves:
if is_in_check(board, color):
return (-99000 - depth) if maximizing else (99000 + depth)
return 0 # Pat
if maximizing:
best = -999999
for fp, tp in all_moves:
nb, ngs = apply_move(board, fp, tp, gs)
if nb[tp[0]][tp[1]][1] == "p" and tp[0] == 0:
nb[tp[0]][tp[1]] = "wq"
val = _minimax(nb, ngs, depth - 1, alpha, beta, False)
best = max(best, val)
alpha = max(alpha, val)
if beta <= alpha:
break
return best
else:
best = 999999
for fp, tp in all_moves:
nb, ngs = apply_move(board, fp, tp, gs)
if nb[tp[0]][tp[1]][1] == "p" and tp[0] == 7:
nb[tp[0]][tp[1]] = "bq"
val = _minimax(nb, ngs, depth - 1, alpha, beta, True)
best = min(best, val)
beta = min(beta, val)
if beta <= alpha:
break
return best
def get_ai_move(board, game_state):
"""Alpha-beta budamalı derinlik-3 minimax ile en iyi siyah hamlesini döndürür."""
all_moves = get_all_valid_moves(board, "b", game_state)
if not all_moves:
return None
ordered = _order_moves(board, all_moves)
best_val = 999999
best_moves = []
for fp, tp in ordered:
nb, ngs = apply_move(board, fp, tp, game_state)
if nb[tp[0]][tp[1]][1] == "p" and tp[0] == 7:
nb[tp[0]][tp[1]] = "bq"
val = _minimax(nb, ngs, _AI_DEPTH - 1, -999999, 999999, True)
if val < best_val - 1:
best_val = val
best_moves = [(fp, tp)]
elif val <= best_val + 1:
best_moves.append((fp, tp))
return random.choice(best_moves)
def _default_gs():
return {
'en_passant': None,
'castling': {'w': {'k': True, 'q': True}, 'b': {'k': True, 'q': True}},
}