hash-map commited on
Commit
026baa3
·
verified ·
1 Parent(s): c871a64

Upload 10 files

Browse files
Files changed (10) hide show
  1. __init__.py +0 -0
  2. app.py +48 -0
  3. board.svg +8 -0
  4. chess_model.h5 +3 -0
  5. engine.py +752 -0
  6. final.py +182 -0
  7. image.png +0 -0
  8. main.py +324 -0
  9. move_finder.py +510 -0
  10. requirements.txt +0 -0
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import chess
3
+ import chess.svg
4
+ from PIL import Image
5
+ import io
6
+ import cairosvg
7
+
8
+ from main import get_moves # your engine wrapper
9
+
10
+
11
+ def predict_from_image(img):
12
+ # Save uploaded image
13
+ img = img.convert("RGB")
14
+ img.save("board.png", format="PNG")
15
+
16
+ # Your function should return (fen, moves)
17
+ fen, moves = get_moves("board.png")
18
+
19
+ # Convert FEN -> SVG -> PNG for Gradio display
20
+ board = chess.Board(fen)
21
+ svg_data = chess.svg.board(board=board, size=350)
22
+
23
+ # convert svg to png (since Gradio Image widget expects raster)
24
+ png_bytes = cairosvg.svg2png(bytestring=svg_data)
25
+ board_img = Image.open(io.BytesIO(png_bytes))
26
+
27
+ return fen, str(moves), board_img
28
+
29
+
30
+ with gr.Blocks() as demo:
31
+ gr.Markdown("# ♟️ Chess AI from Image")
32
+
33
+ with gr.Row():
34
+ image = gr.Image(type="pil", label="Upload Chessboard Image")
35
+ board_display = gr.Image(type="pil", label="Detected Board")
36
+
37
+ fen_out = gr.Textbox(label="Detected FEN")
38
+ moves_out = gr.Textbox(label="Predicted Best Moves")
39
+
40
+ btn = gr.Button("Analyze Board")
41
+
42
+ btn.click(
43
+ fn=predict_from_image,
44
+ inputs=image,
45
+ outputs=[fen_out, moves_out, board_display]
46
+ )
47
+
48
+ demo.launch()
board.svg ADDED
chess_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63c90c04c52375c9ed51bf5e36c1c1214e8c7bf3116c633fa3660e2059d261d7
3
+ size 261952
engine.py ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ this file contains all details of game state and other parametrs
3
+ """
4
+ class GameState():
5
+ def __init__(self,board=[[]]):
6
+ self.board=[['bR','bN','bB','bQ','bK','bB','bN','bR'],
7
+ ['bp','bp','bp','bp','bp','bp','bp','bp'],
8
+ ['--','--','--','--','--','--','--','--'],
9
+ ['--','--','--','--','--','--','--','--'],
10
+ ['--','--','--','--','--','--','--','--'],
11
+ ['--','--','--','--','--','--','--','--'],
12
+ ['wp','wp','wp','wp','wp','wp','wp','wp'],
13
+ ['wR','wN','wB','wQ','wK','wB','wN','wR']
14
+ ]
15
+
16
+ if self.is_valid_board(board):
17
+ self.board=board
18
+
19
+ self.whiteToMove=True
20
+ self.moveLog=[]
21
+ self.knight_directions=[(-2, -1), (-1, -2), (-2, 1), (-1, 2), (2, -1), (1, -2), (2, 1), (1, 2)]
22
+ self.bishop_directions= [(-1,-1),(-1,1),(1,-1),(1,1)]
23
+ self.king_directions=[(-1,0),(0,-1),(1,0),(0,1),(-1,-1),(-1,1),(1,-1),(1,1)]
24
+ self.check_mate = False
25
+ self.steale_mate = False
26
+ self.inheck = False # if king is in check this will be True
27
+ self.pins=[] # if any peice stopping the check and if u move them u gona get check
28
+ self.checks=[] # possible checks
29
+ # we need to keep track of squares where u can eliminate if u took double move in the first place
30
+ #that move name is empassant move
31
+ # we can have dictionary to store functions
32
+
33
+ self.protects=[[]]
34
+ self.threatens =[[]]
35
+ self.peices_can_move_to = [[]]
36
+
37
+
38
+ self.move_functions={'p':self.get_pawn_moves,
39
+ 'R':self.get_rook_moves,
40
+ 'N':self.get_knight_moves,
41
+ 'B':self.get_bishop_moves,
42
+ 'K':self.get_king_moves,
43
+ 'Q':self.get_queen_moves
44
+ }
45
+ #solution 1 to checks is keep track of kings location
46
+ self.black_king_location=(0,4)
47
+ self.white_king_location=(7,4)
48
+ # we need to keep track of squares where u can eliminate if u took double move in the first place
49
+ #that move name is empassant move
50
+ # we can have dictionary to store functions
51
+ self.empassant_moves=() #square for which empassant move is possible
52
+ self.current_castling_rights = Castling_Rights(True,True,True,True)
53
+ self.castle_rights_log=[Castling_Rights(self.current_castling_rights.wks,self.current_castling_rights.wqs,self.current_castling_rights.bks,self.current_castling_rights.bqs)]
54
+ self.empassant_possible_log=[self.empassant_moves]
55
+ # when current castling rights modified it creates new object and pt it in log
56
+
57
+
58
+
59
+
60
+ '''
61
+ To castle, your king and the chosen rook must not have moved,
62
+ there must be no pieces between them,
63
+ the king cannot be in or pass through check,
64
+ and the king must not end up in check.
65
+ castle must be first move to both king and rook
66
+ this is the only move where two peice move
67
+
68
+ '''
69
+ def make_move(self,move): #this is not for castling and pawn promotion just to add it for squares
70
+ self.board[move.start_row][move.start_col]= '--'
71
+ self.board[move.end_row][move.end_col]= move.peice_moved
72
+ if move.peice_moved=='bK':
73
+ self.black_king_location= (move.end_row,move.end_col)
74
+ if move.peice_moved=="wK":
75
+ self.white_king_location= (move.end_row,move.end_col)
76
+
77
+ if move.is_pawn_promotion:
78
+ self.board[move.end_row][move.end_col] = move.peice_moved[0]+ move.promotion_choice
79
+
80
+ #castle move
81
+ if move.castle:
82
+ if move.end_col - move.start_col ==2: #king side col
83
+ self.board [move.end_row][move.end_col-1]= self.board[move.end_row][move.end_col+1]
84
+ self.board[move.end_row][move.end_col+1]='--'
85
+ else:
86
+ self.board [move.end_row][move.end_col+1]= self.board[move.end_row][move.end_col-2] #2 squares aqay from it starts
87
+ self.board[move.end_row][move.end_col-2]='--'
88
+
89
+
90
+ #empassant move
91
+ if move.is_empassant_move: # remove square that is not captured but on the road
92
+ self.board[move.start_row][move.end_col] = '--' # capturing the pawn
93
+
94
+ #update empassant possible
95
+ #only in the case
96
+ if move.peice_moved[1] == 'p' and abs(move.start_row-move.end_row)==2:
97
+ self.empassant_moves=( (move.start_row + move.end_row)//2 ,move.end_col )
98
+
99
+ else:
100
+ self.empassant_moves = ()
101
+
102
+ #update castling rights whenever is is king or rook moves
103
+ self.update_castle_rights(move)
104
+ self.castle_rights_log.append(Castling_Rights(self.current_castling_rights.wks,self.current_castling_rights.wqs,self.current_castling_rights.bks,self.current_castling_rights.bqs))
105
+ self.empassant_possible_log.append(self.empassant_moves)
106
+ self.moveLog.append(move)
107
+ self.whiteToMove = not self.whiteToMove #switch turns
108
+ '''
109
+ undo the previous move made
110
+ '''
111
+ def undo_move(self):
112
+ if len(self.moveLog):
113
+ l_move = self.moveLog.pop()
114
+ self.whiteToMove = not self.whiteToMove
115
+ self.board[l_move.end_row][l_move.end_col]=l_move.peice_captured
116
+ self.board[l_move.start_row][l_move.start_col]=l_move.peice_moved
117
+ move=l_move
118
+ if move.peice_moved=='bK':
119
+ self.black_king_location= (move.start_row,move.start_col)
120
+ if move.peice_moved=="wK":
121
+ self.white_king_location= (move.start_row,move.start_col)
122
+ if move.is_empassant_move:
123
+ self.board[l_move.end_row][l_move.end_col] = '--' #leave end row and column as it is
124
+ self.board[l_move.start_row][l_move.end_col]= move.peice_captured
125
+
126
+ self.empassant_possible_log.pop()
127
+ self.empassant_moves = self.empassant_possible_log[-1]
128
+
129
+ if move.castle:
130
+
131
+ if move.end_col - move.start_col ==2: #king side col
132
+ self.board [move.end_row][move.end_col+1]= self.board[move.end_row][move.end_col-1]
133
+ self.board[move.end_row][move.end_col-1]='--'
134
+ else:
135
+ self.board [move.end_row][move.end_col-2]= self.board[move.end_row][move.end_col+1] #2 squares aqay from it starts
136
+ self.board[move.end_row][move.end_col+1]='--'
137
+
138
+
139
+ ## undo the castling rights
140
+ self.castle_rights_log.pop() #get rid of new castle rights
141
+ self.current_castling_rights = self.castle_rights_log[-1]
142
+
143
+ #undo checkmate move
144
+ self.check_mate = False
145
+ self.steale_mate = False
146
+
147
+
148
+
149
+ else:
150
+ print("this is our starting move ")
151
+ #if u move then it might be check to u so need to check these possiblities
152
+ #so we need to generate possible moves in next turn abd based on that we need to move
153
+
154
+ def is_valid_board(self,board):
155
+ # must be list of 8 rows
156
+ if len(board) != 8:
157
+ return False
158
+ for row in board:
159
+ # each row must have 8 columns
160
+ if len(row) != 8:
161
+ return False
162
+ # check no element is empty
163
+ if any(cell in [None, "", "-"] for cell in row):
164
+ return False
165
+ return True
166
+
167
+ '''
168
+ all moves including checks
169
+ '''
170
+ def update_castle_rights(self,move):
171
+ if move.peice_moved=='wK':
172
+ self.current_castling_rights.wks=False
173
+ self.current_castling_rights.wqs=False
174
+ elif move.peice_moved=='bK':
175
+ self.current_castling_rights.bks=False
176
+ self.current_castling_rights.bqs=False
177
+ elif move.peice_moved=='wR' and move.start_row==0:
178
+ if move.start_col==7:
179
+ self.current_castling_rights.wks=False
180
+ elif move.start_col==0:
181
+ self.current_castling_rights.wqs=False
182
+ elif move.peice_moved=='bR' and move.start_row==7:
183
+ if move.start_col==7:
184
+ self.current_castling_rights.bks=False
185
+ elif move.start_col==0:
186
+ self.current_castling_rights.bqs=False
187
+
188
+ # if rook is captured
189
+ if move.peice_captured == 'wR':
190
+ if move.end_row == 7:
191
+ if move.end_col == 0:
192
+ self.current_castling_rights.wqs=False
193
+ elif move.end_col == 7:
194
+ self.current_castling_rights.wks = False
195
+ elif move.peice_captured == 'bR':
196
+ if move.end_row == 0:
197
+ if move.end_col == 0:
198
+ self.current_castling_rights.bqs=False
199
+ elif move.end_col == 7:
200
+ self.current_castling_rights.bks = False
201
+
202
+
203
+
204
+
205
+
206
+ def is_valid_square(self,r,c):
207
+ if r>=0 and r<=7 and c>=0 and c<=7:
208
+ return True
209
+ else:
210
+ return False
211
+
212
+ def king_safety(self, color):
213
+ board = self.board
214
+ score = 0
215
+
216
+ # Find king position
217
+ king_pos = None
218
+ for r in range(8):
219
+ for c in range(8):
220
+ if board[r][c] == color + 'K':
221
+ king_pos = (r, c)
222
+ break
223
+ if king_pos:
224
+ break
225
+
226
+ if not king_pos:
227
+ return 0 # King missing? shouldn't happen.
228
+
229
+ r, c = king_pos
230
+
231
+ # Pawn shield (pawns in front of king)
232
+ if color == 'w':
233
+ pawn_row = r - 1
234
+ if pawn_row >= 0:
235
+ for dc in [-1, 0, 1]:
236
+ cc = c + dc
237
+ if 0 <= cc < 8:
238
+ if board[pawn_row][cc] == 'wp':
239
+ score += 30 # strong pawn shield
240
+ elif board[pawn_row][cc] == '--':
241
+ score -= 15 # weak if missing
242
+ else: # black
243
+ pawn_row = r + 1
244
+ if pawn_row < 8:
245
+ for dc in [-1, 0, 1]:
246
+ cc = c + dc
247
+ if 0 <= cc < 8:
248
+ if board[pawn_row][cc] == 'bp':
249
+ score += 30
250
+ elif board[pawn_row][cc] == '--':
251
+ score -= 15
252
+
253
+ # Open file penalty (if no pawn in king’s file)
254
+ file_has_pawn = False
255
+ for rr in range(8):
256
+ if board[rr][c] == color + 'p':
257
+ file_has_pawn = True
258
+ break
259
+ if not file_has_pawn:
260
+ score -= 40 # open file in front of king is dangerous
261
+
262
+ # Enemy attacks around the king (adjacent squares)
263
+ king_zone = [(r + dr, c + dc) for dr in [-1, 0, 1] for dc in [-1, 0, 1] if not (dr == 0 and dc == 0)]
264
+ enemy_color = 'w' if color == 'b' else 'b'
265
+ for (rr, cc) in king_zone:
266
+ if 0 <= rr < 8 and 0 <= cc < 8:
267
+ self.whiteToMove = not self.whiteToMove
268
+ moves = self.get_all_possible_moves()
269
+ for move in moves:
270
+ if (move.end_row, move.end_col) == (rr, cc):
271
+ score -= 20 # enemy attacks near king
272
+ self.whiteToMove = not self.whiteToMove
273
+
274
+ return score
275
+
276
+
277
+ def get_valid_moves(self):
278
+
279
+ #naive solution
280
+ #this is very inefficient and generate all moves in two levels for check
281
+ #generate all moves
282
+ # for all moves try to generate next possible moves
283
+ #for each opponent move check if he can attack your king
284
+ #if my king is attacked then it is invalid
285
+
286
+
287
+ # # if u are removing then it is better to traverse list backwards
288
+ # #indexes wont shift
289
+ # for i in range(len(moves)-1,-1,-1):
290
+ # self.make_move(moves[i])
291
+ # #swap turns so this will check my check moves
292
+ # self.whiteToMove = not self.whiteToMove
293
+ # if self.has_check():
294
+ # moves.remove(moves[i])
295
+ # self.whiteToMove = not self.whiteToMove
296
+ # self.undo_move()
297
+
298
+ # decide algo2
299
+ #check for all verticals,horizantals,diagnols and which peices can attack king
300
+ #check for kinght attacks
301
+ #check for direct checks
302
+ #check for if i move this peice can i got any check
303
+ #ckeck for check where u have to move
304
+ self.incheck,self.pins,self.checks = self.check_for_pins_and_checks()
305
+ if self.whiteToMove:
306
+ king_row,king_col = self.white_king_location
307
+ else:
308
+ king_row,king_col = self.black_king_location
309
+ if self.incheck:
310
+ if len(self.checks)==1:
311
+
312
+ moves = self.get_all_possible_moves()
313
+ check_row,check_col,x_dist,y_dist = self.checks[0]
314
+
315
+ peice_checking = self.board[check_row][check_col]
316
+ valid_squares=[]
317
+ if peice_checking[1]=='N':
318
+ valid_squares=[(check_row,check_col)]
319
+ else:
320
+ for i in range(1,8):
321
+ valid_square = (king_row + i*x_dist , king_col + i*y_dist)
322
+ valid_squares.append(valid_square)
323
+ if valid_square[0] == check_row and valid_square[1]==check_col: #once u get to peice and checks
324
+ break
325
+ for i in range(len(moves)-1,-1,-1):
326
+ if moves[i].peice_moved[1] != 'K':
327
+ if not ( moves[i].end_row,moves[i].end_col) in valid_squares: #these moves not blobk check so no need
328
+ moves.remove(moves[i])
329
+ else: # double check king has to move
330
+ moves=[]
331
+ moves=self.get_king_moves(king_row,king_col,moves)
332
+ else:
333
+
334
+ moves = self.get_all_possible_moves() # no check so all moves are fine
335
+ if self.whiteToMove:
336
+ self.get_castle_moves(self.white_king_location[0],self.white_king_location[1],moves,'w')
337
+ else:
338
+ self.get_castle_moves(self.black_king_location[0],self.black_king_location[1],moves,'b')
339
+
340
+
341
+
342
+
343
+
344
+ if len(moves)==0: #either check mate or stealmate
345
+ if self.has_check():
346
+ self.check_mate=True
347
+ else:
348
+ self.steale_mate=True
349
+ else:
350
+ self.check_mate=False
351
+ self.steale_mate=False
352
+
353
+ return moves
354
+ '''
355
+ determine if current player in check
356
+ if player in check need to remove check otherwise game over
357
+ '''
358
+
359
+ def check_for_pins_and_checks(self):
360
+ pins=[]
361
+ checks=[]
362
+ incheck=False
363
+ if self.whiteToMove:
364
+ my_color='w'
365
+ enemy_color='b'
366
+ start_row,start_col = self.white_king_location
367
+ else:
368
+ my_color='b'
369
+ enemy_color='w'
370
+ start_row,start_col = self.black_king_location
371
+
372
+ for j,(x,y) in enumerate(self.king_directions):
373
+ possible_pins = ()
374
+ for i in range(1,8):
375
+ new_x,new_y = start_row+ x*i , start_col + y*i
376
+ if self.is_valid_square(new_x,new_y):
377
+ end_peice = self.board[new_x][new_y]
378
+ if end_peice[0]==my_color and end_peice[1]!='K':
379
+ if possible_pins == (): #first pin could be found
380
+ possible_pins = (new_x,new_y,x,y) #
381
+ else: # 2nd allied peice or no pins break
382
+ break
383
+ elif end_peice[0] == enemy_color :
384
+ type = end_peice[1]
385
+ #5 possibilities here in this complex situation
386
+ # orthogonnaly rook
387
+ # diagonally king
388
+ #anywhere king
389
+ # pawn or king at one square distance
390
+ #any direction 1 square away and peice is a king (necessary to not to go in other king's controlled square)
391
+
392
+
393
+ if (0<=j<=3 and type=='R') or \
394
+ (4<=j<=7 and type=='B') or \
395
+ (type=='Q') or \
396
+ (i==1 and type=='K') or \
397
+ (i==1 and type=='p' and (
398
+ (enemy_color=='w' and j in [6,7]) or
399
+ (enemy_color=='b' and j in [4,5])
400
+ )):
401
+ if possible_pins == ():
402
+ incheck = True
403
+ checks.append((new_x,new_y,x,y))
404
+ else:
405
+ pins.append(possible_pins)
406
+ break
407
+ else:
408
+ break
409
+
410
+ else:
411
+ break
412
+
413
+
414
+
415
+ for x,y in self.knight_directions:
416
+ new_x,new_y = start_row + x,start_col + y
417
+ if self.is_valid_square(new_x,new_y):
418
+ end_peice = self.board[new_x][new_y]
419
+ if end_peice[1]== 'N' and end_peice[0]==enemy_color: #kinght attack king
420
+ incheck=True
421
+
422
+ checks.append((new_x,new_y,x,y))
423
+
424
+ return incheck,pins,checks
425
+
426
+ def has_check(self):
427
+ if self.whiteToMove:
428
+ return self.square_under_attack(self.white_king_location[0],self.white_king_location[1])
429
+ else:
430
+ return self.square_under_attack(self.black_king_location[0],self.black_king_location[1])
431
+ pass
432
+
433
+ '''
434
+ this determines if enemy can attack this square
435
+ '''
436
+ def square_under_attack(self,r,c):
437
+ self.whiteToMove = not self.whiteToMove #change to my opponent
438
+ opp_moves = self.get_all_possible_moves()
439
+ for move in opp_moves:
440
+ if move.end_row == r and move.end_col == c:
441
+ self.whiteToMove = not self.whiteToMove
442
+ return True
443
+ self.whiteToMove = not self.whiteToMove
444
+ return False
445
+
446
+
447
+
448
+ '''
449
+ all moves without checks
450
+ for each possible move check to see if it is a valid move by doing the following
451
+ make a move
452
+ generate moves for opposite player
453
+ see if any of ur moves ur king is attacked
454
+ king is move add valid move to the list
455
+ '''
456
+ def get_all_possible_moves(self):
457
+ moves=[]
458
+ for r in range(len(self.board)):
459
+ for c in range(len(self.board[r])):
460
+ turn = self.board[r][c][0]
461
+ if (turn == 'w' and self.whiteToMove) or (turn=='b' and not self.whiteToMove):
462
+ peice = self.board[r][c][1]
463
+ self.move_functions[peice](r,c,moves) #calls the appropriate move functions
464
+ return moves
465
+ '''
466
+ this func return the pawn moves for particular pawn
467
+ '''
468
+ def get_pawn_moves(self,r,c,moves: list):
469
+ peice_pinned = False
470
+ pin_direction = ()
471
+ for i in range(len(self.pins)-1,-1,-1):
472
+ if self.pins[i][0] == r and self.pins[i][1]==c:
473
+ peice_pinned=True
474
+ pin_direction = (self.pins[i][2],self.pins[i][3])
475
+ self.pins.remove(self.pins[i])
476
+ break
477
+ if self.whiteToMove:
478
+ if r == 6 :
479
+ if not peice_pinned or pin_direction == (-1,0):
480
+ if self.board[4][c]=='--' and self.board[5][c]=='--':
481
+ moves.append(Move((6,c),(4,c),self.board))
482
+ if self.board[r-1][c]=='--':
483
+ if not peice_pinned or pin_direction == (-1,0):
484
+ moves.append(Move((r,c),(r-1,c),self.board))
485
+ if c>=1:
486
+ if not peice_pinned or pin_direction == (-1,-1):
487
+ if self.board[r-1][c-1][0]=='b':
488
+ moves.append(Move((r,c),(r-1,c-1),self.board))
489
+ elif (r-1,c-1)==self.empassant_moves:
490
+ moves.append(Move((r,c),(r-1,c-1),self.board,is_empassant_move=True))
491
+ if c<=6 :
492
+ if not peice_pinned or pin_direction == (-1,+1):
493
+ if self.board[r-1][c+1][0]=='b':
494
+ moves.append(Move((r,c),(r-1,c+1),self.board))
495
+ elif (r-1,c+1)==self.empassant_moves:
496
+ moves.append(Move((r,c),(r-1,c+1),self.board,is_empassant_move=True))
497
+ else :
498
+ if not peice_pinned or pin_direction == (1,0):
499
+ if self.board[r+1][c]=='--':
500
+ moves.append(Move((r,c),(r+1,c),self.board))
501
+ if r == 1:
502
+ if self.board[3][c]=='--' and self.board[2][c]=='--':
503
+ moves.append(Move((1,c),(3,c),self.board))
504
+ if not peice_pinned or pin_direction == (1,-1):
505
+ if c>=1:
506
+ if self.board[r+1][c-1][0]=='w':
507
+ moves.append(Move((r,c),(r+1,c-1),self.board))
508
+ elif (r+1,c-1)==self.empassant_moves:
509
+ moves.append(Move((r,c),(r+1,c-1),self.board,is_empassant_move=True))
510
+ if not peice_pinned or pin_direction == (1,1):
511
+ if c<=6 :
512
+ if self.board[r+1][c+1][0]=='w':
513
+ moves.append(Move((r,c),(r+1,c+1),self.board))
514
+ elif (r+1,c+1)==self.empassant_moves:
515
+ moves.append(Move((r,c),(r+1,c+1),self.board,is_empassant_move=True))
516
+ return moves
517
+ '''
518
+ this func return the rook moves for particular rook
519
+ '''
520
+ def get_rook_moves(self,r,c,moves):
521
+ peice_pinned = False
522
+ pin_direction = ()
523
+ for i in range(len(self.pins)-1,-1,-1):
524
+ if self.pins[i][0] == r and self.pins[i][1]==c:
525
+ peice_pinned=True
526
+ pin_direction = (self.pins[i][2],self.pins[i][3])
527
+ if self.board[r][c][1]!='Q': #cannot remove queen from pin on rook moves ,onl remove it from bishop moves
528
+ self.pins.remove(self.pins[i])
529
+ break
530
+ if self.whiteToMove:
531
+ ur_symbol= 'w'
532
+ opp = 'b'
533
+ else:
534
+ ur_symbol= 'b'
535
+ opp = 'w'
536
+ for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
537
+ for i in range(1,8):
538
+ new_x,new_y = r + x*i ,c + y*i
539
+ if not self.is_valid_square(new_x,new_y):
540
+ break
541
+ else:
542
+ if not peice_pinned or pin_direction == (x,y) or pin_direction == (-x,-y):
543
+ if self.board[new_x][new_y][0]=='-':
544
+ moves.append(Move((r,c),(new_x,new_y),self.board))
545
+ elif self.board[new_x][new_y][0]==opp:
546
+ moves.append(Move((r,c),(new_x,new_y),self.board))
547
+ break
548
+ else :
549
+ break
550
+ return moves
551
+
552
+ '''
553
+ this func return the knight moves for particular rook
554
+ '''
555
+
556
+
557
+ def get_knight_moves(self,r,c,moves):
558
+ peice_pinned = False
559
+
560
+ for i in range(len(self.pins)-1,-1,-1):
561
+ if self.pins[i][0] == r and self.pins[i][1]==c:
562
+ peice_pinned=True
563
+ self.pins.remove(self.pins[i])
564
+ break
565
+
566
+ if self.whiteToMove:
567
+ ur_symbol= 'w'
568
+ opp = 'b'
569
+ else:
570
+ ur_symbol= 'b'
571
+ opp = 'w'
572
+ for x,y in self.knight_directions:
573
+ new_x,new_y = r+x,c+y
574
+ if (self.is_valid_square(new_x,new_y)):
575
+ if not peice_pinned:
576
+ if self.board[new_x][new_y][0]!=ur_symbol:
577
+ moves.append(Move((r,c),(new_x,new_y),self.board))
578
+ return moves
579
+ '''
580
+ this func return the bishop moves for particular rook
581
+ '''
582
+ def get_bishop_moves(self,r,c,moves):
583
+ peice_pinned = False
584
+ pin_direction = ()
585
+ for i in range(len(self.pins)-1,-1,-1):
586
+ if self.pins[i][0] == r and self.pins[i][1]==c:
587
+ peice_pinned=True
588
+ pin_direction = (self.pins[i][2],self.pins[i][3])
589
+ self.pins.remove(self.pins[i])
590
+ break
591
+ if self.whiteToMove:
592
+ ur_symbol= 'w'
593
+ opp = 'b'
594
+ else:
595
+ ur_symbol= 'b'
596
+ opp = 'w'
597
+ for x,y in self.bishop_directions:
598
+ for i in range(1,8):
599
+ new_x,new_y = r + x*i ,c + y*i
600
+ if not self.is_valid_square(new_x,new_y):
601
+ break
602
+ else:
603
+ if not peice_pinned or pin_direction == (x,y) or pin_direction == (-x,-y):
604
+ if self.board[new_x][new_y][0]=='-':
605
+ moves.append(Move((r,c),(new_x,new_y),self.board))
606
+ elif self.board[new_x][new_y][0]==opp:
607
+ moves.append(Move((r,c),(new_x,new_y),self.board))
608
+ break
609
+ else :
610
+ break
611
+ return moves
612
+
613
+ '''
614
+ this func return the king moves for particular king
615
+ '''
616
+ def get_king_moves(self,r,c,moves):
617
+
618
+ if self.whiteToMove:
619
+ ur_symbol= 'w'
620
+ opp = 'b'
621
+ else:
622
+ ur_symbol= 'b'
623
+ opp = 'w'
624
+ for x,y in self.king_directions:
625
+ new_x,new_y = r+x,c+y
626
+ if (self.is_valid_square(new_x,new_y)):
627
+ if self.board[new_x][new_y][0]!=ur_symbol:
628
+ if ur_symbol == 'w':
629
+ self.white_king_location = (new_x,new_y)
630
+ else:
631
+ self.black_king_location = (new_x,new_y)
632
+ incheck,pins,checks = self.check_for_pins_and_checks() #check for pins and checks and if not add the move
633
+ if not incheck:
634
+ moves.append(Move((r,c),(new_x,new_y),self.board))
635
+ if ur_symbol == 'w':
636
+ self.white_king_location = (r,c)
637
+ else:
638
+ self.black_king_location = (r,c) # place king in original position
639
+ return moves
640
+ '''
641
+ this func return the queen moves for particular rook
642
+ '''
643
+ def get_queen_moves(self,r,c,moves):
644
+ return self.get_bishop_moves(r,c,moves) + self.get_rook_moves(r,c,moves)
645
+
646
+
647
+ '''
648
+ generate all castle moves
649
+
650
+ '''
651
+ def get_castle_moves(self,r,c,moves,my_color):
652
+ if self.square_under_attack(r,c):
653
+ return # cannot castle if king is in check
654
+ if (self.whiteToMove and self.current_castling_rights.wks) or (not self.whiteToMove and self.current_castling_rights.bks):
655
+ self.king_side_castle_moves(r,c,moves,my_color)
656
+ if (self.whiteToMove and self.current_castling_rights.wqs) or (not self.whiteToMove and self.current_castling_rights.bqs):
657
+ self.queen_side_castle_moves(r,c,moves,my_color)
658
+
659
+ def king_side_castle_moves(self,r,c,moves,my_color):
660
+ if c + 2 <= 7:
661
+ if self.board[r][c+1]== '--' and self.board[r][c+2]== '--':
662
+ if not self.square_under_attack(r,c+1) and not self.square_under_attack(r,c+2):
663
+ moves.append ( Move((r,c),(r,c+2),self.board,castle=True))
664
+
665
+
666
+
667
+
668
+
669
+ def queen_side_castle_moves(self,r,c,moves,my_color):
670
+ if c-3 >=0:
671
+ if self.board[r][c-1]== '--' and self.board[r][c-2]== '--' and self.board[r][c-3]== '--':
672
+ if not self.square_under_attack(r,c-1) and not self.square_under_attack(r,c-2) :
673
+ moves.append ( Move((r,c),(r,c-2),self.board,castle=True))
674
+
675
+ '''
676
+ make castling right class other wise difficult to include it in main code
677
+ '''
678
+ class Castling_Rights():
679
+ def __init__(self,wks,wqs,bks,bqs):
680
+ self.bks=bks
681
+ self.bqs=bqs
682
+ self.wks=wks
683
+ self.wqs=wqs
684
+
685
+ class Move():
686
+ ranks_to_rows = {
687
+ '1':7,'2':6,'3':5,'4':4,'5':3,'6':2,'7':1,'8':0}
688
+ rows_to_ranks = {v:k for k,v in ranks_to_rows.items()}
689
+ files_to_cols = {chr(97+i):i for i in range(8)}
690
+ cols_to_files={v:k for k,v in files_to_cols.items()}
691
+ def __init__(self,startsq,endsq,board,choice='Q',is_empassant_move=False,castle=False): #for undowing the move its better to store the board information
692
+ self.start_row = startsq[0]
693
+ self.start_col = startsq[1]
694
+ self.end_row = endsq[0]
695
+ self.end_col = endsq[1]
696
+
697
+ self.peice_moved = board[self.start_row][self.start_col]
698
+ self.peice_captured = board[self.end_row][self.end_col]
699
+ self.is_pawn_promotion = False
700
+ if (self.peice_moved == 'wp' and self.end_row==0) or (self.peice_moved == 'bp' and self.end_row==7):
701
+ self.is_pawn_promotion=True
702
+ self.promotion_choice =choice
703
+ self.is_empassant_move=False
704
+ if is_empassant_move:
705
+ self.is_empassant_move=True
706
+ self.peice_captured = 'wp' if self.peice_moved =='bp' else 'bp'
707
+ self.castle=castle
708
+ self.is_capture = self.peice_captured!='--'
709
+
710
+ self.move_id = self.start_row*1000 + self.start_col * 100 + self.end_row * 10 + self.end_col # generate unique id and since all below 10 we can do this
711
+ #have to tell python if two moves are equal
712
+ '''
713
+ over writing a method
714
+ other wise python check and they are two different objects
715
+ '''
716
+ def __eq__(self, value):
717
+ if isinstance(value,Move):
718
+ return value.move_id == self.move_id
719
+ return False
720
+
721
+ def get_chess_notation(self):
722
+ #make it to look move in chess notation
723
+ return self.get_rank_file(self.start_row,self.start_col) + self.get_rank_file(self.end_row,self.end_col)
724
+
725
+ def get_rank_file(self,r,c):
726
+ return self.cols_to_files[c]+ self.rows_to_ranks[r] #first column than row
727
+ def __str__(self):
728
+ #castle move
729
+ if self.castle:
730
+ return "o-o" if self.end_col==6 else 'o-o-o'
731
+ end_square = self.get_rank_file(self.end_row,self.end_col)
732
+ #pawn move
733
+ if self.peice_moved[1] == 'p':
734
+ if self.is_capture:
735
+ return self.cols_to_files[self.start_col] + 'x'+ end_square
736
+ else:
737
+ return end_square
738
+ # pawn promotion
739
+ #Nbd2 both knights can move to d2
740
+
741
+ # for check and checkmate
742
+ # peice moves
743
+ move_string = self.peice_moved[1]
744
+ if self.is_capture:
745
+ move_string+='x'
746
+ return move_string + end_square + f"""{self.peice_moved} to {self.get_rank_file(self.end_row, self.end_col)}:{self.get_rank_file(self.start_row, self.start_col)}"""
747
+
748
+
749
+
750
+
751
+
752
+
final.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chess.svg
2
+ import chess
3
+ import re
4
+ import cv2
5
+ import random as rd
6
+ import numpy as np
7
+ import matplotlib.pyplot as plt
8
+ from tensorflow.keras.models import load_model
9
+ import chess, chess.svg
10
+ from pathlib import Path
11
+ import webbrowser
12
+ piece_symbols = 'prbnkqPRBNKQ-'
13
+ try:
14
+ model = load_model('./chess_model.h5')
15
+ print("Model loaded successfully")
16
+ except Exception as e:
17
+ print(f"Error loading model: {e}")
18
+
19
+ def onehot_from_fen(fen):
20
+ eye = np.eye(13)
21
+ output = np.empty((0, 13))
22
+ fen = re.sub('[-]', '', fen)
23
+
24
+ for char in fen:
25
+ if(char in '12345678'):
26
+ output = np.append(output, np.tile(eye[12], (int(char), 1)), axis=0)
27
+ else:
28
+ idx = piece_symbols.index(char)
29
+ output = np.append(output, eye[idx].reshape((1, 13)), axis=0)
30
+ return output
31
+ import numpy as np
32
+ import re
33
+
34
+
35
+
36
+ def board_from_fen(fen):
37
+ board = []
38
+ fen = fen.split()[0] # just the board part (ignore turn, castling, etc.)
39
+
40
+ for row in fen.split('/'):
41
+ row_out = []
42
+ for char in row:
43
+ if char.isdigit():
44
+ # expand empty squares
45
+ row_out.extend([0] * int(char))
46
+ else:
47
+ idx = piece_symbols.index(char) + 1 # +1 so 0 = empty
48
+ row_out.append(idx)
49
+ board.append(row_out)
50
+
51
+ return np.array(board, dtype=np.int8) # shape (8,8)
52
+
53
+ def fen_from_onehot(one_hot):
54
+ output = ''
55
+ for j in range(8):
56
+ for i in range(8):
57
+ if(one_hot[j][i] == 12):
58
+ output += ' '
59
+ else:
60
+ output += piece_symbols[one_hot[j][i]]
61
+ if(j != 7):
62
+ output += '-'
63
+
64
+ for i in range(8, 0, -1):
65
+ output = output.replace(' ' * i, str(i))
66
+ return output
67
+
68
+
69
+ def order_points(pts):
70
+ # Order 4 points: top-left, top-right, bottom-right, bottom-left
71
+ rect = np.zeros((4, 2), dtype="float32")
72
+ s = pts.sum(axis=1)
73
+ rect[0] = pts[np.argmin(s)]
74
+ rect[2] = pts[np.argmax(s)]
75
+ diff = np.diff(pts, axis=1)
76
+ rect[1] = pts[np.argmin(diff)]
77
+ rect[3] = pts[np.argmax(diff)]
78
+ return rect
79
+
80
+ def preprocess_chessboard(image_path, target_size=(30, 30)):
81
+ img = cv2.imread(image_path)
82
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
83
+ blur = cv2.GaussianBlur(gray, (5,5), 0)
84
+ edges = cv2.Canny(blur, 50, 150)
85
+
86
+ # Find contours
87
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
88
+
89
+ cropped_board = None
90
+ warped = None
91
+ squares = []
92
+
93
+ if contours:
94
+ cnt = max(contours, key=cv2.contourArea)
95
+ epsilon = 0.02 * cv2.arcLength(cnt, True)
96
+ approx = cv2.approxPolyDP(cnt, epsilon, True)
97
+
98
+ # If we found 4 corners, warp
99
+ if len(approx) == 4:
100
+ pts = approx.reshape(4,2)
101
+ rect = order_points(pts)
102
+ board_size = 256 # fixed square board
103
+ dst = np.array([
104
+ [0, 0],
105
+ [board_size-1, 0],
106
+ [board_size-1, board_size-1],
107
+ [0, board_size-1]
108
+ ], dtype="float32")
109
+
110
+ M = cv2.getPerspectiveTransform(rect, dst)
111
+ warped = cv2.warpPerspective(img, M, (board_size, board_size))
112
+ cropped_board = warped
113
+ else:
114
+ # fallback: just resize whole image
115
+ warped = cv2.resize(img, (256, 256))
116
+ cropped_board = warped
117
+ else:
118
+ # no contours, fallback
119
+ warped = cv2.resize(img, (256, 256))
120
+ cropped_board = warped
121
+
122
+ # Split into 64 squares
123
+ board_size = warped.shape[0]
124
+ sq_size = board_size // 8
125
+ for row in range(8):
126
+ for col in range(8):
127
+ square = warped[row*sq_size:(row+1)*sq_size,
128
+ col*sq_size:(col+1)*sq_size]
129
+ square = cv2.resize(square, target_size)
130
+ square = square / 255.0
131
+ squares.append(square)
132
+
133
+ return np.array(squares), cropped_board
134
+
135
+ def display_with_predicted_fen(image):
136
+ squares,_ = preprocess_chessboard(image)
137
+ pred = model.predict(squares).argmax(axis=1).reshape(8, 8)
138
+ fen = fen_from_onehot(pred)
139
+ return fen
140
+
141
+ def get_board_from_image(img_path):
142
+ image = cv2.imread(img_path)
143
+ if image is None:
144
+ print('sorry image is null')
145
+ return
146
+ ext = img_path.split('.')[-1]
147
+ if not (ext=='jpg' or ext == 'jpeg' or ext == 'png'):
148
+ print('sorry image needs to be in jpg/jpeg/png format')
149
+ return
150
+ predicted_fen = display_with_predicted_fen(img_path)
151
+ # Create board from predicted FEN
152
+ board = chess.Board(predicted_fen.replace('-','/'))
153
+ return predicted_fen
154
+ def get_board_from_fen(fen:str):
155
+ print(type(fen))
156
+ fen=fen.split('-')
157
+ if len(fen)!=8:
158
+ return [[]]
159
+ answers = [[] for _ in range(8)]
160
+ map={'p':'bp','r':'bR','n':'bN','k':'bK','q':'bQ','b':'bB','P':'wp','R':'wR','N':'wN','K':'wK','Q':'wQ','B':'wB'}
161
+ for i,f in enumerate(fen):
162
+ for char in f:
163
+
164
+ if char in '12345678':
165
+ answers[i].extend(['--']*(int(char)))
166
+ elif char in piece_symbols:
167
+ answers[i].append(map[char])
168
+ return answers
169
+
170
+
171
+ def get_board(image_path):
172
+ fen_notation = get_board_from_image(image_path)
173
+ if fen_notation:
174
+ board = get_board_from_fen(fen_notation)
175
+
176
+ else:
177
+ return [[]],""
178
+ return board,fen_notation.replace('-','/')
179
+
180
+ if __name__ == "__main__":
181
+ get_board()
182
+
image.png ADDED
main.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chess
2
+ import chess.svg
3
+ import pygame
4
+ import engine
5
+ from pygame import Vector2
6
+ import move_finder
7
+ from multiprocessing import Process,Queue
8
+ from final import get_board
9
+
10
+ move_width =200
11
+ move_height=0
12
+ width,height=512,512 #can be 768,768
13
+ dimensions = 8 #chess board is 64 squares
14
+ sq_size = int(height/dimensions)
15
+ max_fps=15
16
+ images ={}
17
+ #load images
18
+ #loading image sis very expensive so load only once per game
19
+ # board = chess.Board('rnbqkbnr/8/8/8/8/8/8/8')
20
+ # svg = chess.svg.board(board)
21
+
22
+ # make engine that recognize legal chess move or not
23
+ #hopefully 2 player game
24
+ # with open('b.svg', 'w', encoding="utf-8") as f:
25
+ # f.write(svg)
26
+
27
+
28
+ '''
29
+ load images in global dictionary .
30
+ called exactly on the main
31
+ '''
32
+ def load_images():
33
+ peices=['bQ','bK','bB','bN','bR','wQ','wK','wB','wN','wR','bp','wp']
34
+ for peice in peices:
35
+ images[peice] = pygame.transform.scale(pygame.image.load("./images/"+peice+".png"),(sq_size,sq_size)) #cenetr peice nicely
36
+
37
+ # we can access an peice by calling image['wp] we added them in the dictionary
38
+
39
+ '''
40
+ draw squares on board
41
+ always top left square is white
42
+ '''
43
+ def draw_board(screen):
44
+ global colors #so that we can use them globally
45
+ colors = [pygame.Color('white'),pygame.Color(194, 194, 194)]
46
+ for r in range(dimensions):
47
+ for c in range(dimensions):
48
+ parity = (r+c) & 1
49
+ color = colors[parity]
50
+ pygame.draw.rect(screen,color,pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
51
+
52
+
53
+ '''
54
+ draw peices using current game state (board)
55
+ '''
56
+ def draw_peices(screen,board):
57
+ for r in range(dimensions):
58
+ for c in range(dimensions):
59
+ peice = board[r][c]
60
+ if peice !='--':
61
+ screen.blit(images[peice],pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
62
+
63
+ import pygame
64
+
65
+ scroll_offset = 0 # global scroll variable
66
+
67
+ def draw_move_log(screen, gs, width, move_width, height):
68
+ global scroll_offset
69
+ font = pygame.font.SysFont('Arial', 16, False, False)
70
+ move_log_rect = pygame.Rect(width, 0, move_width, height)
71
+
72
+ # Draw background
73
+ pygame.draw.rect(screen, pygame.Color('black'), move_log_rect)
74
+
75
+ moves = gs.moveLog
76
+ text_y = 5 - scroll_offset # apply scroll offset here
77
+
78
+ for j, i in enumerate(moves):
79
+ text = f"{j+1}. {str(i)}"
80
+ text_object = font.render(text, True, pygame.Color('white'))
81
+ text_location = move_log_rect.move(5, text_y)
82
+ screen.blit(text_object, text_location)
83
+ text_y += text_object.get_height() + 5
84
+
85
+
86
+ def handle_scroll(event):
87
+ """Handles mouse wheel scrolling"""
88
+ global scroll_offset
89
+ if event.type == pygame.MOUSEBUTTONDOWN:
90
+ if event.button == 4: # scroll up
91
+ scroll_offset = max(0, scroll_offset - 20)
92
+ elif event.button == 5: # scroll down
93
+ scroll_offset += 20
94
+
95
+
96
+ '''
97
+ rensonsible for graphics in current game state
98
+ '''
99
+ def draw_game_state(screen,gs,valid_moves,sq_selected):
100
+ draw_board(screen) #draw squares on board
101
+ high_light_squares(screen,gs,valid_moves,sq_selected)
102
+ draw_peices(screen,gs.board)
103
+ draw_move_log(screen,gs,512,200,512)
104
+ '''
105
+ hgihlight the square selected and moves for peices selected
106
+ '''
107
+
108
+ def high_light_squares(screen,gs,valid_moves,sqselected):
109
+ if sqselected != ():
110
+ r,c = sqselected
111
+ if gs.board[r][c][0] == ('w' if gs.whiteToMove else 'b'): #sq selected is a peice
112
+ # highlight selected square
113
+ # use surface
114
+ s = pygame.Surface((sq_size,sq_size))
115
+
116
+ s.set_alpha(100) # transparent
117
+ s.fill(pygame.Color('blue'))
118
+ screen.blit(s,(c*sq_size,r*sq_size))
119
+ # highlist moves from that square
120
+ s.fill(pygame.Color('red'))
121
+
122
+ for move in valid_moves:
123
+ if move.start_row == r and move.start_col==c:
124
+ pygame.draw.circle(screen,pygame.Color(0,255,0),( int(sq_size*(move.end_col + 0.5)),int(sq_size*(move.end_row + 0.5))),7.5)
125
+ if gs.board[move.end_row][move.end_col][0]== ('b' if gs.whiteToMove else 'w'):
126
+ screen.blit(s,(sq_size*move.end_col,sq_size*move.end_row))
127
+ if len(gs.moveLog)>=1:
128
+ prev_move= gs.moveLog[-1]
129
+ s.set_alpha(100) # transparent
130
+ s.fill(pygame.Color('dark green'))
131
+ r,c = prev_move.end_row,prev_move.end_col
132
+ screen.blit(s,(c*sq_size,r*sq_size))
133
+
134
+
135
+
136
+ #what the board does is redraw images when u move
137
+ #animation is simply slow the change such that u see every frame
138
+
139
+ def main():
140
+ pygame.init()
141
+ screen = pygame.display.set_mode((width+move_width,height+move_height))
142
+ clock = pygame.time.Clock() #clock
143
+ screen.fill(pygame.Color('white'))
144
+ gs = engine.GameState() #create a game state and craete variables
145
+ load_images() # load only once before whilw
146
+ running = True
147
+ sqselected = ()
148
+ player_clicks=[] #two squares of player clicks
149
+ valid_moves = gs.get_valid_moves()
150
+ game_over=False
151
+ player_one = True # white true , machine is playing false
152
+ player_two = False # similarly but for player two
153
+ ai_thinking = False
154
+ move_finder_procee = None
155
+ move_undone = False
156
+ if len(valid_moves)<=5:
157
+ for move in valid_moves:
158
+ print(move.peice_captured ,move.peice_moved, move.id)
159
+ move_made = False #until the valid move we need not generate valid moves
160
+ # make ui changes
161
+ animate=False
162
+ while running:
163
+ human_Turn = (gs.whiteToMove and player_one) or (not gs.whiteToMove and player_two)
164
+ for e in pygame.event.get():
165
+ #mouse handler
166
+ if e.type == pygame.QUIT:
167
+ running=False
168
+ elif e.type == pygame.MOUSEBUTTONDOWN:
169
+ if not game_over and human_Turn:
170
+ location =pygame.mouse.get_pos() #location of mouse
171
+ col = int(location[0]//sq_size)
172
+ row = int(location[1]//sq_size)
173
+ if sqselected == (row,col) or col>=8: #user click same square then unmove
174
+ sqselected=()
175
+ player_clicks=[]
176
+ else:
177
+ sqselected = (row,col)
178
+ player_clicks.append(sqselected) # append for both first and second cicks
179
+ if len(player_clicks)==2: #after the second click
180
+ move = engine.Move(player_clicks[0],player_clicks[1],gs.board)
181
+ for i in range(len(valid_moves)):
182
+ if move==valid_moves[i]:#move is pretty cheap
183
+ print("move taken",move.get_chess_notation(),"peice_moved:",gs.board[move.start_row][move.start_col])
184
+ gs.make_move(valid_moves[i])
185
+ move_made=True
186
+ animate=True
187
+ sqselected=()
188
+ player_clicks=[]
189
+ if not move_made:
190
+ print("invalid_move",move.get_chess_notation(),move.peice_captured,move.peice_moved)
191
+ player_clicks=[sqselected] #after move is doen reset squares
192
+ if gs.check_mate or gs.steale_mate:
193
+ running=False
194
+
195
+ #keyboard handlers
196
+ elif e.type == pygame.KEYDOWN:
197
+ if e.key == pygame.K_z:
198
+ gs.undo_move()
199
+ move_made=True
200
+ game_over=False
201
+ if ai_thinking:
202
+ move_find_process.terminate()
203
+ ai_thinking=False
204
+ move_undone=True
205
+
206
+ elif e.key == pygame.K_r:
207
+ gs = engine.GameState()
208
+ valid_moves=gs.get_valid_moves()
209
+ sqselected=()
210
+ player_clicks=[]
211
+ move_made=False
212
+ animate=True
213
+ game_over=False
214
+ if ai_thinking:
215
+ move_find_process.terminate()
216
+ ai_thinking=False
217
+ move_undone=True
218
+ #reset the board
219
+ # best moves
220
+ if not game_over and not human_Turn and not move_undone:
221
+ if not ai_thinking:
222
+ ai_thinking = True # threads wont share data
223
+ returnQueue = Queue() # used to pass data between threads
224
+ move_find_process = Process(target=move_finder.find_best_move,args=(gs,valid_moves,returnQueue)) # passing function as parameter
225
+
226
+ move_find_process.start() #creates new thread without waiting this code tun
227
+ if not move_find_process.is_alive():
228
+ print('done thinking')
229
+ move = returnQueue.get()
230
+ if move is None:
231
+ move = move_finder.random_move(valid_moves)
232
+ gs.make_move(move)
233
+ move_made = True
234
+ animate = True
235
+ ai_thinking = False
236
+ if move_made:
237
+ valid_moves = gs.get_valid_moves()
238
+ if animate:
239
+ animateMove(gs.moveLog[-1],screen,gs.board,clock)
240
+ animate=False
241
+ print('valid_moves:',len(valid_moves))
242
+ if len(valid_moves)<=5:
243
+ for move in valid_moves:
244
+ print(move.peice_captured ,move.peice_moved, move.move_id)
245
+
246
+ move_made=False
247
+ move_undone = False
248
+
249
+ draw_game_state(screen,gs,valid_moves,sqselected) #add mouse hnadlers
250
+ if gs.check_mate:
251
+ game_over=True
252
+ if gs.whiteToMove:
253
+ draw_end_game_text(screen,'black wins by checkmate')
254
+ else:
255
+ draw_end_game_text(screen,"white wins by checkmate")
256
+ elif gs.steale_mate:
257
+ game_over=True
258
+ draw_end_game_text(screen,'stealmate no moves for king and no check')
259
+ clock.tick(max_fps)
260
+ pygame.display.flip()
261
+ from pygame.math import Vector2
262
+
263
+ def animateMove(move, screen, board, clock):
264
+ start = Vector2(move.start_col, move.start_row)
265
+ end = Vector2(move.end_col, move.end_row)
266
+ distance = end.distance_to(start)
267
+ frames_per_sq = 10
268
+ frame_count = int(distance * frames_per_sq)
269
+
270
+ for frame in range(frame_count + 1):
271
+ t = frame / frame_count # in [0, 1]
272
+ current = start.lerp(end, t) # linear interpolation
273
+ c, r = current.x, current.y
274
+
275
+ draw_board(screen)
276
+ draw_peices(screen, board)
277
+
278
+ # erase ending square
279
+ colour = colors[(move.end_row + move.end_col) & 1]
280
+ end_sq = pygame.Rect(move.end_col * sq_size, move.end_row * sq_size, sq_size, sq_size)
281
+ pygame.draw.rect(screen, colour, end_sq)
282
+
283
+ # draw captured piece if any
284
+ if move.peice_captured != '--':
285
+ if move.is_empassant_move:
286
+ screen.blit(images[move.peice_captured], end_sq)
287
+ else:
288
+ screen.blit(images[move.peice_captured], end_sq)
289
+
290
+ # draw moving piece at interpolated position
291
+ screen.blit(images[move.peice_moved],
292
+ pygame.Rect(c * sq_size, r * sq_size, sq_size, sq_size))
293
+
294
+ pygame.display.flip()
295
+ clock.tick(120)
296
+
297
+ def draw_end_game_text(screen,text):
298
+ font = pygame.font.SysFont('Helvitca',32,True,False)
299
+ text_object = font.render(text,0,pygame.Color('Gray'))
300
+ text_location = pygame.Rect(0,0,width,height).move(width/2,height/2)
301
+ screen.blit(text_object,text_location)
302
+ text_object = font.render(text,0,pygame.Color('Black'))
303
+ screen.blit(text_object,text_location.move(2,2))
304
+
305
+ def get_moves(image_path):
306
+ board,fen = get_board(image_path)
307
+ gs= engine.GameState(board)
308
+ moves = move_finder.get_best_n_moves(gs)
309
+
310
+ gs.whiteToMove = not gs.whiteToMove
311
+ moves2 = move_finder.get_best_n_moves(gs)
312
+ colour = ('white_moves','black_moves') if not gs.whiteToMove else ('black_moves','white_moves')
313
+ return fen, {colour[0]:moves,colour[1]:moves2}
314
+
315
+ if __name__=='__main__':
316
+ #handle and update graphics and input
317
+ #whenever u import this module else where this wont run this fucntion
318
+ #main()
319
+ get_moves('./image.png')
320
+
321
+
322
+
323
+
324
+
move_finder.py ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+
3
+
4
+ piece_score = {'K': 1000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'p': 100,'--':0,'-':0}
5
+ CHECKMATE = 100000
6
+ STALEMATE = 0
7
+ DEPTH = 3
8
+
9
+
10
+ # Pawn (p = 100)
11
+ pawn_table = [
12
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
13
+ [ 5, 10, 10,-20,-20, 10, 10, 5],
14
+ [ 5, -5,-10, 0, 0,-10, -5, 5],
15
+ [ 0, 0, 0, 20, 20, 0, 0, 0],
16
+ [ 5, 5, 10, 25, 25, 10, 5, 5],
17
+ [ 10, 10, 20, 30, 30, 20, 10, 10],
18
+ [ 50, 50, 50, 50, 50, 50, 50, 50],
19
+ [ 0, 0, 0, 0, 0, 0, 0, 0]
20
+ ]
21
+
22
+ # Knight (N = 320)
23
+ knight_table = [
24
+ [-50,-40,-30,-30,-30,-30,-40,-50],
25
+ [-40,-20, 0, 0, 0, 0,-20,-40],
26
+ [-30, 0, 10, 15, 15, 10, 0,-30],
27
+ [-30, 5, 15, 20, 20, 15, 5,-30],
28
+ [-30, 0, 15, 20, 20, 15, 0,-30],
29
+ [-30, 5, 10, 15, 15, 10, 5,-30],
30
+ [-40,-20, 0, 5, 5, 0,-20,-40],
31
+ [-50,-40,-30,-30,-30,-30,-40,-50]
32
+ ]
33
+
34
+ # Bishop (B = 330)
35
+ bishop_table = [
36
+ [-20,-10,-10,-10,-10,-10,-10,-20],
37
+ [-10, 5, 0, 0, 0, 0, 5,-10],
38
+ [-10, 10, 10, 10, 10, 10, 10,-10],
39
+ [-10, 0, 10, 10, 10, 10, 0,-10],
40
+ [-10, 5, 5, 10, 10, 5, 5,-10],
41
+ [-10, 0, 5, 10, 10, 5, 0,-10],
42
+ [-10, 0, 0, 0, 0, 0, 0,-10],
43
+ [-20,-10,-10,-10,-10,-10,-10,-20]
44
+ ]
45
+
46
+ # Rook (R = 500)
47
+ rook_table = [
48
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
49
+ [ 5, 10, 10, 10, 10, 10, 10, 5],
50
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
51
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
52
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
53
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
54
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
55
+ [ 0, 0, 0, 5, 5, 0, 0, 0]
56
+ ]
57
+
58
+ # Queen (Q = 900)
59
+ queen_table = [
60
+ [-20,-10,-10, -5, -5,-10,-10,-20],
61
+ [-10, 0, 0, 0, 0, 5, 0,-10],
62
+ [-10, 0, 5, 5, 5, 5, 5,-10],
63
+ [ -5, 0, 5, 5, 5, 5, 0, -5],
64
+ [ 0, 0, 5, 5, 5, 5, 0, -5],
65
+ [-10, 5, 5, 5, 5, 5, 0,-10],
66
+ [-10, 0, 5, 0, 0, 0, 0,-10],
67
+ [-20,-10,-10, -5, -5,-10,-10,-20]
68
+ ]
69
+
70
+ # King (K = 1000) – Middlegame
71
+ king_table_mid = [
72
+ [-30,-40,-40,-50,-50,-40,-40,-30],
73
+ [-30,-40,-40,-50,-50,-40,-40,-30],
74
+ [-30,-40,-40,-50,-50,-40,-40,-30],
75
+ [-30,-40,-40,-50,-50,-40,-40,-30],
76
+ [-20,-30,-30,-40,-40,-30,-30,-20],
77
+ [-10,-20,-20,-20,-20,-20,-20,-10],
78
+ [ 20, 20, 0, 0, 0, 0, 20, 20],
79
+ [ 20, 30, 10, 0, 0, 10, 30, 20]
80
+ ]
81
+
82
+ # King (K = 1000) – Endgame
83
+ king_table_end = [
84
+ [-50,-40,-30,-20,-20,-30,-40,-50],
85
+ [-30,-20,-10, 0, 0,-10,-20,-30],
86
+ [-30,-10, 20, 30, 30, 20,-10,-30],
87
+ [-30,-10, 30, 40, 40, 30,-10,-30],
88
+ [-30,-10, 30, 40, 40, 30,-10,-30],
89
+ [-30,-10, 20, 30, 30, 20,-10,-30],
90
+ [-30,-30, 0, 0, 0, 0,-30,-30],
91
+ [-50,-30,-30,-30,-30,-30,-30,-50]
92
+ ]
93
+
94
+ king_scores = [[0]*8 for _ in range(8)]
95
+ for r in range(8):
96
+ for c in range(8):
97
+ king_scores[r][c]= king_table_end[r][c]+ king_table_end[r][c]
98
+ for r in range(4):
99
+ for c in range(8):
100
+ pawn_table[r][c],pawn_table[7-r][c]=pawn_table[r][c],pawn_table[7-r][c]
101
+
102
+
103
+
104
+
105
+ peice_position_scores = {'N':knight_table,'K':king_scores,'N':knight_table,'B':bishop_table,'Q':queen_table,'p':pawn_table,'R':rook_table}
106
+
107
+ '''
108
+ use openings
109
+ use numpy and better board representation
110
+ better use p.q or something like that
111
+ transposition tables
112
+ save the evaluation zobra hash
113
+ add which moves it is stoping
114
+ add attacking and defensive
115
+ we can teach end game theory
116
+ if apeice is attacked try to move that first
117
+ storing the data of moves not to recalculate
118
+ '''
119
+
120
+
121
+
122
+
123
+
124
+ def random_move(valid_moves):
125
+ ind=random.randint(0,len(valid_moves)-1)
126
+ return valid_moves[ind]
127
+
128
+
129
+ ## checking for greedy
130
+ # for all moves check where i can have more peices/value
131
+ #but we also need to score_material the next move so that the best possible comes out
132
+
133
+
134
+ #greedy algorithim and try to get better position
135
+ #score material on board
136
+ #assume black playing ai and check mate is worst
137
+ # go for level 2
138
+ # we want to minize the maximum of opponent score
139
+
140
+
141
+ def find_best_move_non_recursion(gs,valid_moves):
142
+ turn = 1 if gs.whiteToMove else -1
143
+ opponent_min_max_score= CHECKMATE #smallest of their maximums
144
+ best_player_move = None
145
+ random.shuffle(valid_moves)
146
+ for player_move in valid_moves:
147
+ gs.make_move(player_move)
148
+ opponent_moves = gs.get_valid_moves()
149
+ if gs.check_mate:
150
+ opponent_max_score = -CHECKMATE
151
+ elif gs.steale_mate:
152
+ opponent_max_score=STALEMATE
153
+ else:
154
+ opponent_max_score = -CHECKMATE
155
+ random.shuffle(opponent_moves)
156
+ for opponent_move in opponent_moves:
157
+ gs.make_move(opponent_move)
158
+ gs.get_valid_move()
159
+ if gs.check_mate:
160
+ score = CHECKMATE
161
+ elif gs.steale_mate:
162
+ score=STALEMATE
163
+ else:
164
+ score = -turn * score_material(gs.board)
165
+
166
+ if (score>opponent_max_score):
167
+ opponent_max_score=score # try to find best move for opponent
168
+
169
+ gs.undo_move()
170
+ if opponent_min_max_score> opponent_max_score:
171
+ opponent_min_max_score = opponent_max_score # try to find best move for u which is worst(best) move
172
+ best_move = player_move # my new best is least of all opponent bests
173
+
174
+ gs.undo_move()
175
+
176
+ return best_move
177
+ # solve this recursively
178
+ # prune the branches we do not need
179
+
180
+
181
+ '''
182
+ helper method for best method
183
+ '''
184
+ def find_best_move(gs, valid_moves, return_queue):
185
+ global count, best_moves
186
+ count = 0
187
+ score = find_move_nega_max_alpha_beta(
188
+ gs, gs.get_valid_moves(), DEPTH, -2*CHECKMATE, 2*CHECKMATE, 1
189
+ )
190
+
191
+ print("Top moves:")
192
+ for score, mv in best_moves:
193
+ print(mv.get_chess_notation(), "score:", score)
194
+
195
+ # pick a random move among top N
196
+ chosen_move = random.choice(best_moves)[1]
197
+ return_queue.put(chosen_move)
198
+
199
+
200
+
201
+ '''
202
+ find min max move
203
+ '''
204
+ def find_move_min_max(gs,valid_moves,depth,whiteToMove):
205
+ global next_move
206
+ if depth == 0 :
207
+ return score_material(gs)
208
+ if whiteToMove: #maximize score
209
+ max_score = - CHECKMATE
210
+ for move in valid_moves:
211
+ gs.make_move(move)
212
+ next_moves = gs.get_valid_moves()
213
+ score = find_move_min_max(gs,next_moves,depth-1,False)
214
+ if score>max_score:
215
+ max_score=score
216
+ if depth == DEPTH :
217
+ next_move = move
218
+ gs.undo_move()
219
+ return max_score
220
+ else:
221
+ min_score = CHECKMATE
222
+ for move in valid_moves:
223
+ gs.make_move(move)
224
+ next_moves = gs.get_valid_moves()
225
+ score = find_move_min_max(gs,next_moves,depth-1,True)
226
+ if score<min_score:
227
+ min_score=score
228
+ if depth == DEPTH :
229
+ next_move = move
230
+ gs.undo_move()
231
+ return min_score
232
+
233
+
234
+ '''
235
+ combine if else to one
236
+ '''
237
+
238
+ def find_move_nega_max(gs,valid_moves,depth,turn):
239
+ #always try to maximize but with multilier
240
+ global next_move,count
241
+ count +=1
242
+ if depth == 0 :
243
+ return turn * score_material(gs)
244
+
245
+ max_score = CHECKMATE
246
+ for move in valid_moves:
247
+ gs.make_move(move)
248
+ next_moves = gs.get_valid_moves()
249
+ score = -find_move_nega_max(gs,next_moves,depth-1,-1 * turn) #this is very important
250
+ if score>max_score:
251
+ max_score=score
252
+ if depth == DEPTH :
253
+ next_move = move
254
+ gs.undo_move()
255
+ return max_score
256
+
257
+ '''
258
+ the alpha beta pruning
259
+ remove branches that wont make any good
260
+ also depends on scoring algorithim
261
+ also add positional scores
262
+ need to control more squares and attack more squares
263
+ alpha beta these are the maximum and minimum u can acheive values overall
264
+
265
+ if max_score>alpha then max_score is alpha
266
+ if alpha>beta then prune that branch
267
+ ugot best else where no need for it
268
+ '''
269
+ # Killer moves: 2 per depth (ply)
270
+ killer_moves = {}
271
+
272
+ # History heuristic: success count
273
+ history_heuristic = {
274
+ 'w': [[0 for _ in range(8)] for _ in range(8)],
275
+ 'b': [[0 for _ in range(8)] for _ in range(8)]
276
+ }
277
+ def order_moves(gs, moves):
278
+ scored_moves = []
279
+
280
+ for move in moves:
281
+ score=0
282
+ score -= score_material(gs)
283
+
284
+ # 1. Captures (MVV-LVA style)
285
+ if move.is_capture:
286
+ attacker = move.peice_moved[1]
287
+ victim = move.peice_captured[1] if move.peice_captured[1]!='-' else '--'
288
+ score += ( piece_score.get(victim, 0))*10 - piece_score.get(attacker, 0)
289
+
290
+ # 2. Checks (simulate move and test)
291
+ gs.make_move(move)
292
+ if gs.incheck:
293
+ score += 80
294
+ gs.undo_move()
295
+
296
+ # 3. Promotions
297
+ if move.is_pawn_promotion:
298
+ score += 150 + piece_score['Q']*10
299
+
300
+ # 4. Castling (good for king safety)
301
+ if move.castle:
302
+ score += 50
303
+
304
+ # 5.
305
+ for dr, dc in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,1),(-1,1),(1,-1)]:
306
+ rr, cc = move.end_row + dr, move.end_col + dc
307
+ if 0 <= rr < 8 and 0 <= cc < 8:
308
+ piece = gs.board[rr][cc]
309
+ if piece != "--" and piece[0] == move.peice_moved[0]: # same color
310
+ score += 1
311
+ score += score_material(gs)
312
+ scored_moves.append((score, move))
313
+
314
+ # Sort by score descending
315
+ scored_moves.sort(key=lambda x: x[0], reverse=True)
316
+ return [m for _, m in scored_moves]
317
+
318
+ # def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
319
+ # global count, next_move
320
+ # count += 1 # counts all nodes visited
321
+
322
+ # if depth == 0:
323
+ # return turn * score_material(gs)
324
+
325
+ # max_score = -CHECKMATE
326
+ # valid_moves=order_moves(gs,valid_moves)
327
+ # for move in valid_moves:
328
+ # gs.make_move(move)
329
+ # next_moves = gs.get_valid_moves()
330
+ # score = -find_move_nega_max_alpha_beta(
331
+ # gs, next_moves, depth - 1, -beta, -alpha, -turn
332
+ # )
333
+ # gs.undo_move()
334
+
335
+ # if score > max_score:
336
+ # max_score = score
337
+ # if depth == DEPTH:
338
+ # next_move = move
339
+
340
+ # alpha = max(alpha, max_score)
341
+ # if alpha >= beta:
342
+ # break
343
+
344
+ # return max_score
345
+
346
+
347
+ TOP_N = 5 # number of best moves you want
348
+
349
+ def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
350
+ if depth == 0:
351
+ return turn * score_material(gs)
352
+
353
+ max_score = -CHECKMATE
354
+ scored_moves = []
355
+
356
+ # move ordering to improve pruning
357
+ valid_moves = order_moves(gs, valid_moves)
358
+
359
+ for move in valid_moves:
360
+ gs.make_move(move)
361
+ next_moves = gs.get_valid_moves()
362
+ score = -find_move_nega_max_alpha_beta(
363
+ gs, next_moves, depth - 1, -beta, -alpha, -turn
364
+ )
365
+ gs.undo_move()
366
+
367
+ scored_moves.append((score, move))
368
+
369
+ max_score = max(max_score, score)
370
+ alpha = max(alpha, max_score)
371
+ if alpha >= beta:
372
+ break # alpha-beta cutoff
373
+
374
+ # Only save best moves at root depth
375
+ if depth == DEPTH:
376
+ scored_moves.sort(key=lambda x: x[0], reverse=True)
377
+ best_moves = [(score, move) for score, move in scored_moves[:TOP_N]]
378
+
379
+ return max_score
380
+
381
+
382
+ '''
383
+ score the board
384
+ positive score good for white
385
+ a negative score good for black
386
+ increase the scoring function
387
+ counting attacking and defending moves
388
+ '''
389
+
390
+
391
+
392
+ def score_material(self):
393
+ """Full evaluation of the board with material, positional, mobility, defense, etc."""
394
+
395
+ if self.check_mate:
396
+ if self.whiteToMove:
397
+ return -CHECKMATE
398
+ else:
399
+ return CHECKMATE
400
+ elif self.steale_mate:
401
+ return STALEMATE
402
+
403
+ board = self.board
404
+ score = 0
405
+
406
+ white_squares_controlled = set()
407
+ black_squares_controlled = set()
408
+
409
+ # Material, piece-square, and piece defense evaluation
410
+ for r in range(8):
411
+ for c in range(8):
412
+ square = (r, c)
413
+ piece_info = board[r][c]
414
+
415
+ if piece_info == "--":
416
+ continue
417
+
418
+ color, piece = piece_info[0], piece_info[1]
419
+
420
+ base_value = piece_score[piece]
421
+
422
+ if color == 'w':
423
+ # Material value
424
+ score += base_value
425
+
426
+
427
+ score += peice_position_scores[piece][r][c]
428
+
429
+ #
430
+ moves = self.move_functions[piece](r,c,[])
431
+ for move in moves:
432
+ white_squares_controlled.add((move.end_row, move.end_col))
433
+
434
+ # Bonus for defending own piece
435
+ if board[move.end_row][move.end_col][0] == 'w':
436
+ defended_piece = board[move.end_row][move.end_col][1]
437
+ score += piece_score[defended_piece]
438
+
439
+ # Bonus for killing enemy valuable piece
440
+ if board[move.end_row][move.end_col][0] == 'b':
441
+ victim = board[move.end_row][move.end_col][1]
442
+ score += piece_score[victim] *10
443
+ elif color == 'b':
444
+ score -= base_value
445
+ score -= peice_position_scores[piece][7 - r][c]
446
+
447
+ moves = self.move_functions[piece](r,c,[])
448
+ for move in moves:
449
+ black_squares_controlled.add((move.end_row, move.end_col))
450
+
451
+ # Defense bonus
452
+ if board[move.end_row][move.end_col][0] == 'b':
453
+ defended_piece = board[move.end_row][move.end_col][1]
454
+ score -= piece_score[defended_piece]
455
+
456
+ # Killing enemy valuable piece
457
+ if board[move.end_row][move.end_col][0] == 'w':
458
+ victim = board[move.end_row][move.end_col][1]
459
+ score -= piece_score[victim] *10
460
+
461
+
462
+ # Bishop pair bonus
463
+ white_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'wB')
464
+ black_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'bB')
465
+ if white_bishops >= 2:
466
+ score += 50
467
+ if black_bishops >= 2:
468
+ score -= 50
469
+
470
+ # King safety (penalize exposed kings)
471
+ score += self.king_safety( "w") - self.king_safety("b")
472
+ score += (len(white_squares_controlled) - len(black_squares_controlled))*5
473
+
474
+
475
+ return score
476
+
477
+ def get_best_n_moves(gs, n=5):
478
+ """
479
+ Returns best n moves for both White and Black.
480
+ """
481
+ best_white, best_black = [], []
482
+
483
+ # White to move
484
+ if gs.whiteToMove:
485
+ moves = gs.get_valid_moves()
486
+ scored = []
487
+ for move in moves:
488
+ gs.make_move(move)
489
+ score = -find_move_nega_max_alpha_beta(
490
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, -1
491
+ )
492
+ gs.undo_move()
493
+ scored.append((score, str(move)))
494
+ scored.sort(key=lambda x: x[0], reverse=True)
495
+ best_white = scored[:n]
496
+
497
+ # Black to move
498
+ else:
499
+ moves = gs.get_valid_moves()
500
+ scored = []
501
+ for move in moves:
502
+ gs.make_move(move)
503
+ score = -find_move_nega_max_alpha_beta(
504
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, 1
505
+ )
506
+ gs.undo_move()
507
+ scored.append((score, str(move)))
508
+ scored.sort(key=lambda x: x[0], reverse=True)
509
+ best_black = scored[:n]
510
+ return best_white if best_white else best_black
requirements.txt ADDED
Binary file (178 Bytes). View file