hash-map commited on
Commit
98ab355
·
verified ·
1 Parent(s): 5e4ebda

Upload 40 files

Browse files
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+ move_width =200
10
+ move_height=0
11
+ width,height=512,512 #can be 768,768
12
+ dimensions = 8 #chess board is 64 squares
13
+ sq_size = int(height/dimensions)
14
+ max_fps=15
15
+ images ={}
16
+ #load images
17
+ #loading image sis very expensive so load only once per game
18
+ # board = chess.Board('rnbqkbnr/8/8/8/8/8/8/8')
19
+ # svg = chess.svg.board(board)
20
+
21
+ # make engine that recognize legal chess move or not
22
+ #hopefully 2 player game
23
+ # with open('b.svg', 'w', encoding="utf-8") as f:
24
+ # f.write(svg)
25
+
26
+
27
+ '''
28
+ load images in global dictionary .
29
+ called exactly on the main
30
+ '''
31
+ def load_images():
32
+ peices=['bQ','bK','bB','bN','bR','wQ','wK','wB','wN','wR','bp','wp']
33
+ for peice in peices:
34
+ images[peice] = pygame.transform.scale(pygame.image.load("./images/"+peice+".png"),(sq_size,sq_size)) #cenetr peice nicely
35
+
36
+ # we can access an peice by calling image['wp] we added them in the dictionary
37
+
38
+ '''
39
+ draw squares on board
40
+ always top left square is white
41
+ '''
42
+ def draw_board(screen):
43
+ global colors #so that we can use them globally
44
+ colors = [pygame.Color('white'),pygame.Color(194, 194, 194)]
45
+ for r in range(dimensions):
46
+ for c in range(dimensions):
47
+ parity = (r+c) & 1
48
+ color = colors[parity]
49
+ pygame.draw.rect(screen,color,pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
50
+
51
+
52
+ '''
53
+ draw peices using current game state (board)
54
+ '''
55
+ def draw_peices(screen,board):
56
+ for r in range(dimensions):
57
+ for c in range(dimensions):
58
+ peice = board[r][c]
59
+ if peice !='--':
60
+ screen.blit(images[peice],pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
61
+
62
+ import pygame
63
+
64
+ scroll_offset = 0 # global scroll variable
65
+
66
+ def draw_move_log(screen, gs, width, move_width, height):
67
+ global scroll_offset
68
+ font = pygame.font.SysFont('Arial', 16, False, False)
69
+ move_log_rect = pygame.Rect(width, 0, move_width, height)
70
+
71
+ # Draw background
72
+ pygame.draw.rect(screen, pygame.Color('black'), move_log_rect)
73
+
74
+ moves = gs.moveLog
75
+ text_y = 5 - scroll_offset # apply scroll offset here
76
+
77
+ for j, i in enumerate(moves):
78
+ text = f"{j+1}. {str(i)}"
79
+ text_object = font.render(text, True, pygame.Color('white'))
80
+ text_location = move_log_rect.move(5, text_y)
81
+ screen.blit(text_object, text_location)
82
+ text_y += text_object.get_height() + 5
83
+
84
+
85
+ def handle_scroll(event):
86
+ """Handles mouse wheel scrolling"""
87
+ global scroll_offset
88
+ if event.type == pygame.MOUSEBUTTONDOWN:
89
+ if event.button == 4: # scroll up
90
+ scroll_offset = max(0, scroll_offset - 20)
91
+ elif event.button == 5: # scroll down
92
+ scroll_offset += 20
93
+
94
+
95
+ '''
96
+ rensonsible for graphics in current game state
97
+ '''
98
+ def draw_game_state(screen,gs,valid_moves,sq_selected):
99
+ draw_board(screen) #draw squares on board
100
+ high_light_squares(screen,gs,valid_moves,sq_selected)
101
+ draw_peices(screen,gs.board)
102
+ draw_move_log(screen,gs,512,200,512)
103
+ '''
104
+ hgihlight the square selected and moves for peices selected
105
+ '''
106
+
107
+ def high_light_squares(screen,gs,valid_moves,sqselected):
108
+ if sqselected != ():
109
+ r,c = sqselected
110
+ if gs.board[r][c][0] == ('w' if gs.whiteToMove else 'b'): #sq selected is a peice
111
+ # highlight selected square
112
+ # use surface
113
+ s = pygame.Surface((sq_size,sq_size))
114
+
115
+ s.set_alpha(100) # transparent
116
+ s.fill(pygame.Color('blue'))
117
+ screen.blit(s,(c*sq_size,r*sq_size))
118
+ # highlist moves from that square
119
+ s.fill(pygame.Color('red'))
120
+
121
+ for move in valid_moves:
122
+ if move.start_row == r and move.start_col==c:
123
+ 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)
124
+ if gs.board[move.end_row][move.end_col][0]== ('b' if gs.whiteToMove else 'w'):
125
+ screen.blit(s,(sq_size*move.end_col,sq_size*move.end_row))
126
+ if len(gs.moveLog)>=1:
127
+ prev_move= gs.moveLog[-1]
128
+ s.set_alpha(100) # transparent
129
+ s.fill(pygame.Color('dark green'))
130
+ r,c = prev_move.end_row,prev_move.end_col
131
+ screen.blit(s,(c*sq_size,r*sq_size))
132
+
133
+
134
+
135
+ #what the board does is redraw images when u move
136
+ #animation is simply slow the change such that u see every frame
137
+
138
+ def main():
139
+ pygame.init()
140
+ screen = pygame.display.set_mode((width+move_width,height+move_height))
141
+ clock = pygame.time.Clock() #clock
142
+ screen.fill(pygame.Color('white'))
143
+ gs = engine.GameState() #create a game state and craete variables
144
+ load_images() # load only once before whilw
145
+ running = True
146
+ sqselected = ()
147
+ player_clicks=[] #two squares of player clicks
148
+ valid_moves = gs.get_valid_moves()
149
+ game_over=False
150
+ player_one = True # white true , machine is playing false
151
+ player_two = False # similarly but for player two
152
+ ai_thinking = False
153
+ move_finder_procee = None
154
+ move_undone = False
155
+ if len(valid_moves)<=5:
156
+ for move in valid_moves:
157
+ print(move.peice_captured ,move.peice_moved, move.id)
158
+ move_made = False #until the valid move we need not generate valid moves
159
+ # make ui changes
160
+ animate=False
161
+ while running:
162
+ human_Turn = (gs.whiteToMove and player_one) or (not gs.whiteToMove and player_two)
163
+ for e in pygame.event.get():
164
+ #mouse handler
165
+ if e.type == pygame.QUIT:
166
+ running=False
167
+ elif e.type == pygame.MOUSEBUTTONDOWN:
168
+ if not game_over and human_Turn:
169
+ location =pygame.mouse.get_pos() #location of mouse
170
+ col = int(location[0]//sq_size)
171
+ row = int(location[1]//sq_size)
172
+ if sqselected == (row,col) or col>=8: #user click same square then unmove
173
+ sqselected=()
174
+ player_clicks=[]
175
+ else:
176
+ sqselected = (row,col)
177
+ player_clicks.append(sqselected) # append for both first and second cicks
178
+ if len(player_clicks)==2: #after the second click
179
+ move = engine.Move(player_clicks[0],player_clicks[1],gs.board)
180
+ for i in range(len(valid_moves)):
181
+ if move==valid_moves[i]:#move is pretty cheap
182
+ print("move taken",move.get_chess_notation(),"peice_moved:",gs.board[move.start_row][move.start_col])
183
+ gs.make_move(valid_moves[i])
184
+ move_made=True
185
+ animate=True
186
+ sqselected=()
187
+ player_clicks=[]
188
+ if not move_made:
189
+ print("invalid_move",move.get_chess_notation(),move.peice_captured,move.peice_moved)
190
+ player_clicks=[sqselected] #after move is doen reset squares
191
+ if gs.check_mate or gs.steale_mate:
192
+ running=False
193
+
194
+ #keyboard handlers
195
+ elif e.type == pygame.KEYDOWN:
196
+ if e.key == pygame.K_z:
197
+ gs.undo_move()
198
+ move_made=True
199
+ game_over=False
200
+ if ai_thinking:
201
+ move_find_process.terminate()
202
+ ai_thinking=False
203
+ move_undone=True
204
+
205
+ elif e.key == pygame.K_r:
206
+ gs = engine.GameState()
207
+ valid_moves=gs.get_valid_moves()
208
+ sqselected=()
209
+ player_clicks=[]
210
+ move_made=False
211
+ animate=True
212
+ game_over=False
213
+ if ai_thinking:
214
+ move_find_process.terminate()
215
+ ai_thinking=False
216
+ move_undone=True
217
+ #reset the board
218
+ # best moves
219
+ if not game_over and not human_Turn and not move_undone:
220
+ if not ai_thinking:
221
+ ai_thinking = True # threads wont share data
222
+ returnQueue = Queue() # used to pass data between threads
223
+ move_find_process = Process(target=move_finder.find_best_move,args=(gs,valid_moves,returnQueue)) # passing function as parameter
224
+
225
+ move_find_process.start() #creates new thread without waiting this code tun
226
+ if not move_find_process.is_alive():
227
+ print('done thinking')
228
+ move = returnQueue.get()
229
+ if move is None:
230
+ move = move_finder.random_move(valid_moves)
231
+ gs.make_move(move)
232
+ move_made = True
233
+ animate = True
234
+ ai_thinking = False
235
+ if move_made:
236
+ valid_moves = gs.get_valid_moves()
237
+ if animate:
238
+ animateMove(gs.moveLog[-1],screen,gs.board,clock)
239
+ animate=False
240
+ print('valid_moves:',len(valid_moves))
241
+ if len(valid_moves)<=5:
242
+ for move in valid_moves:
243
+ print(move.peice_captured ,move.peice_moved, move.move_id)
244
+
245
+ move_made=False
246
+ move_undone = False
247
+
248
+ draw_game_state(screen,gs,valid_moves,sqselected) #add mouse hnadlers
249
+ if gs.check_mate:
250
+ game_over=True
251
+ if gs.whiteToMove:
252
+ draw_end_game_text(screen,'black wins by checkmate')
253
+ else:
254
+ draw_end_game_text(screen,"white wins by checkmate")
255
+ elif gs.steale_mate:
256
+ game_over=True
257
+ draw_end_game_text(screen,'stealmate no moves for king and no check')
258
+ clock.tick(max_fps)
259
+ pygame.display.flip()
260
+ from pygame.math import Vector2
261
+
262
+ def animateMove(move, screen, board, clock):
263
+ start = Vector2(move.start_col, move.start_row)
264
+ end = Vector2(move.end_col, move.end_row)
265
+ distance = end.distance_to(start)
266
+ frames_per_sq = 10
267
+ frame_count = int(distance * frames_per_sq)
268
+
269
+ for frame in range(frame_count + 1):
270
+ t = frame / frame_count # in [0, 1]
271
+ current = start.lerp(end, t) # linear interpolation
272
+ c, r = current.x, current.y
273
+
274
+ draw_board(screen)
275
+ draw_peices(screen, board)
276
+
277
+ # erase ending square
278
+ colour = colors[(move.end_row + move.end_col) & 1]
279
+ end_sq = pygame.Rect(move.end_col * sq_size, move.end_row * sq_size, sq_size, sq_size)
280
+ pygame.draw.rect(screen, colour, end_sq)
281
+
282
+ # draw captured piece if any
283
+ if move.peice_captured != '--':
284
+ if move.is_empassant_move:
285
+ screen.blit(images[move.peice_captured], end_sq)
286
+ else:
287
+ screen.blit(images[move.peice_captured], end_sq)
288
+
289
+ # draw moving piece at interpolated position
290
+ screen.blit(images[move.peice_moved],
291
+ pygame.Rect(c * sq_size, r * sq_size, sq_size, sq_size))
292
+
293
+ pygame.display.flip()
294
+ clock.tick(120)
295
+
296
+ def draw_end_game_text(screen,text):
297
+ font = pygame.font.SysFont('Helvitca',32,True,False)
298
+ text_object = font.render(text,0,pygame.Color('Gray'))
299
+ text_location = pygame.Rect(0,0,width,height).move(width/2,height/2)
300
+ screen.blit(text_object,text_location)
301
+ text_object = font.render(text,0,pygame.Color('Black'))
302
+ screen.blit(text_object,text_location.move(2,2))
303
+
304
+ # def get_moves(image_path):
305
+ # board,fen = get_board(image_path)
306
+ # gs= engine.GameState(board)
307
+ # moves = move_finder.get_best_n_moves(gs)
308
+ # gs = engine.GameState(board)
309
+ # gs.whiteToMove = not gs.whiteToMove
310
+ # moves2 = move_finder.get_best_n_moves(gs)
311
+ # colour = ('white_moves','black_moves') if not gs.whiteToMove else ('black_moves','white_moves')
312
+ # return fen, {colour[0]:moves,colour[1]:moves2}
313
+
314
+ if __name__=='__main__':
315
+ #handle and update graphics and input
316
+ #whenever u import this module else where this wont run this fucntion
317
+ main()
318
+ # get_moves('./image.png')
319
+
320
+
321
+
322
+
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/__init__.py ADDED
File without changes
final/__pycache__/agent.cpython-312.pyc ADDED
Binary file (19.9 kB). View file
 
final/__pycache__/engine.cpython-312.pyc ADDED
Binary file (33.7 kB). View file
 
final/__pycache__/final.cpython-312.pyc ADDED
Binary file (8.85 kB). View file
 
final/__pycache__/infer_nnue.cpython-312.pyc ADDED
Binary file (2.93 kB). View file
 
final/__pycache__/move_finder.cpython-312.pyc ADDED
Binary file (16.1 kB). View file
 
final/__pycache__/network.cpython-312.pyc ADDED
Binary file (1.98 kB). View file
 
final/__pycache__/nnue_model.cpython-312.pyc ADDED
Binary file (2.42 kB). View file
 
final/__pycache__/rl_agent.cpython-312.pyc ADDED
Binary file (1.69 kB). View file
 
final/__pycache__/train_agent.cpython-312.pyc ADDED
Binary file (20 kB). View file
 
final/__pycache__/train_nnue.cpython-312.pyc ADDED
Binary file (7.44 kB). View file
 
final/app.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+ move_width =200
10
+ move_height=0
11
+ width,height=512,512 #can be 768,768
12
+ dimensions = 8 #chess board is 64 squares
13
+ sq_size = int(height/dimensions)
14
+ max_fps=15
15
+ images ={}
16
+ #load images
17
+ #loading image sis very expensive so load only once per game
18
+ # board = chess.Board('rnbqkbnr/8/8/8/8/8/8/8')
19
+ # svg = chess.svg.board(board)
20
+
21
+ # make engine that recognize legal chess move or not
22
+ #hopefully 2 player game
23
+ # with open('b.svg', 'w', encoding="utf-8") as f:
24
+ # f.write(svg)
25
+
26
+
27
+ '''
28
+ load images in global dictionary .
29
+ called exactly on the main
30
+ '''
31
+ def load_images():
32
+ peices=['bQ','bK','bB','bN','bR','wQ','wK','wB','wN','wR','bp','wp']
33
+ for peice in peices:
34
+ images[peice] = pygame.transform.scale(pygame.image.load("./images/"+peice+".png"),(sq_size,sq_size)) #cenetr peice nicely
35
+
36
+ # we can access an peice by calling image['wp] we added them in the dictionary
37
+
38
+ '''
39
+ draw squares on board
40
+ always top left square is white
41
+ '''
42
+ def draw_board(screen):
43
+ global colors #so that we can use them globally
44
+ colors = [pygame.Color('white'),pygame.Color(194, 194, 194)]
45
+ for r in range(dimensions):
46
+ for c in range(dimensions):
47
+ parity = (r+c) & 1
48
+ color = colors[parity]
49
+ pygame.draw.rect(screen,color,pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
50
+
51
+
52
+ '''
53
+ draw peices using current game state (board)
54
+ '''
55
+ def draw_peices(screen,board):
56
+ for r in range(dimensions):
57
+ for c in range(dimensions):
58
+ peice = board[r][c]
59
+ if peice !='--':
60
+ screen.blit(images[peice],pygame.Rect(c*sq_size,r*sq_size,sq_size,sq_size))
61
+
62
+ import pygame
63
+
64
+ scroll_offset = 0 # global scroll variable
65
+
66
+ def draw_move_log(screen, gs, width, move_width, height):
67
+ global scroll_offset
68
+ font = pygame.font.SysFont('Arial', 16, False, False)
69
+ move_log_rect = pygame.Rect(width, 0, move_width, height)
70
+
71
+ # Draw background
72
+ pygame.draw.rect(screen, pygame.Color('black'), move_log_rect)
73
+
74
+ moves = gs.moveLog
75
+ text_y = 5 - scroll_offset # apply scroll offset here
76
+
77
+ for j, i in enumerate(moves):
78
+ text = f"{j+1}. {str(i)}"
79
+ text_object = font.render(text, True, pygame.Color('white'))
80
+ text_location = move_log_rect.move(5, text_y)
81
+ screen.blit(text_object, text_location)
82
+ text_y += text_object.get_height() + 5
83
+
84
+
85
+ def handle_scroll(event):
86
+ """Handles mouse wheel scrolling"""
87
+ global scroll_offset
88
+ if event.type == pygame.MOUSEBUTTONDOWN:
89
+ if event.button == 4: # scroll up
90
+ scroll_offset = max(0, scroll_offset - 20)
91
+ elif event.button == 5: # scroll down
92
+ scroll_offset += 20
93
+
94
+
95
+ '''
96
+ rensonsible for graphics in current game state
97
+ '''
98
+ def draw_game_state(screen,gs,valid_moves,sq_selected):
99
+ draw_board(screen) #draw squares on board
100
+ high_light_squares(screen,gs,valid_moves,sq_selected)
101
+ draw_peices(screen,gs.board)
102
+ draw_move_log(screen,gs,512,200,512)
103
+ '''
104
+ hgihlight the square selected and moves for peices selected
105
+ '''
106
+
107
+ def high_light_squares(screen,gs,valid_moves,sqselected):
108
+ if sqselected != ():
109
+ r,c = sqselected
110
+ if gs.board[r][c][0] == ('w' if gs.whiteToMove else 'b'): #sq selected is a peice
111
+ # highlight selected square
112
+ # use surface
113
+ s = pygame.Surface((sq_size,sq_size))
114
+
115
+ s.set_alpha(100) # transparent
116
+ s.fill(pygame.Color('blue'))
117
+ screen.blit(s,(c*sq_size,r*sq_size))
118
+ # highlist moves from that square
119
+ s.fill(pygame.Color('red'))
120
+
121
+ for move in valid_moves:
122
+ if move.start_row == r and move.start_col==c:
123
+ 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)
124
+ if gs.board[move.end_row][move.end_col][0]== ('b' if gs.whiteToMove else 'w'):
125
+ screen.blit(s,(sq_size*move.end_col,sq_size*move.end_row))
126
+ if len(gs.moveLog)>=1:
127
+ prev_move= gs.moveLog[-1]
128
+ s.set_alpha(100) # transparent
129
+ s.fill(pygame.Color('dark green'))
130
+ r,c = prev_move.end_row,prev_move.end_col
131
+ screen.blit(s,(c*sq_size,r*sq_size))
132
+
133
+
134
+
135
+ #what the board does is redraw images when u move
136
+ #animation is simply slow the change such that u see every frame
137
+
138
+ def main():
139
+ pygame.init()
140
+ screen = pygame.display.set_mode((width+move_width,height+move_height))
141
+ clock = pygame.time.Clock() #clock
142
+ screen.fill(pygame.Color('white'))
143
+ gs = engine.GameState() #create a game state and craete variables
144
+ load_images() # load only once before whilw
145
+ running = True
146
+ sqselected = ()
147
+ player_clicks=[] #two squares of player clicks
148
+ valid_moves = gs.get_valid_moves()
149
+ game_over=False
150
+ player_one = True # white true , machine is playing false
151
+ player_two = False # similarly but for player two
152
+ ai_thinking = False
153
+ move_finder_procee = None
154
+ move_undone = False
155
+ if len(valid_moves)<=5:
156
+ for move in valid_moves:
157
+ print(move.peice_captured ,move.peice_moved, move.id)
158
+ move_made = False #until the valid move we need not generate valid moves
159
+ # make ui changes
160
+ animate=False
161
+ while running:
162
+ human_Turn = (gs.whiteToMove and player_one) or (not gs.whiteToMove and player_two)
163
+ for e in pygame.event.get():
164
+ #mouse handler
165
+ if e.type == pygame.QUIT:
166
+ running=False
167
+ elif e.type == pygame.MOUSEBUTTONDOWN:
168
+ if not game_over and human_Turn:
169
+ location =pygame.mouse.get_pos() #location of mouse
170
+ col = int(location[0]//sq_size)
171
+ row = int(location[1]//sq_size)
172
+ if sqselected == (row,col) or col>=8: #user click same square then unmove
173
+ sqselected=()
174
+ player_clicks=[]
175
+ else:
176
+ sqselected = (row,col)
177
+ player_clicks.append(sqselected) # append for both first and second cicks
178
+ if len(player_clicks)==2: #after the second click
179
+ move = engine.Move(player_clicks[0],player_clicks[1],gs.board)
180
+ for i in range(len(valid_moves)):
181
+ if move==valid_moves[i]:#move is pretty cheap
182
+ print("move taken",move.get_chess_notation(),"peice_moved:",gs.board[move.start_row][move.start_col])
183
+ gs.make_move(valid_moves[i])
184
+ move_made=True
185
+ animate=True
186
+ sqselected=()
187
+ player_clicks=[]
188
+ if not move_made:
189
+ print("invalid_move",move.get_chess_notation(),move.peice_captured,move.peice_moved)
190
+ player_clicks=[sqselected] #after move is doen reset squares
191
+ if gs.check_mate or gs.steale_mate:
192
+ running=False
193
+
194
+ #keyboard handlers
195
+ elif e.type == pygame.KEYDOWN:
196
+ if e.key == pygame.K_z:
197
+ gs.undo_move()
198
+ move_made=True
199
+ game_over=False
200
+ if ai_thinking:
201
+ move_find_process.terminate()
202
+ ai_thinking=False
203
+ move_undone=True
204
+
205
+ elif e.key == pygame.K_r:
206
+ gs = engine.GameState()
207
+ valid_moves=gs.get_valid_moves()
208
+ sqselected=()
209
+ player_clicks=[]
210
+ move_made=False
211
+ animate=True
212
+ game_over=False
213
+ if ai_thinking:
214
+ move_find_process.terminate()
215
+ ai_thinking=False
216
+ move_undone=True
217
+ #reset the board
218
+ # best moves
219
+ if not game_over and not human_Turn and not move_undone:
220
+ if not ai_thinking:
221
+ ai_thinking = True # threads wont share data
222
+ returnQueue = Queue() # used to pass data between threads
223
+ move_find_process = Process(target=move_finder.find_best_move,args=(gs,valid_moves,returnQueue)) # passing function as parameter
224
+
225
+ move_find_process.start() #creates new thread without waiting this code tun
226
+ if not move_find_process.is_alive():
227
+ print('done thinking')
228
+ move = returnQueue.get()
229
+ if move is None:
230
+ move = move_finder.random_move(valid_moves)
231
+ gs.make_move(move)
232
+ move_made = True
233
+ animate = True
234
+ ai_thinking = False
235
+ if move_made:
236
+ valid_moves = gs.get_valid_moves()
237
+ if animate:
238
+ animateMove(gs.moveLog[-1],screen,gs.board,clock)
239
+ animate=False
240
+ print('valid_moves:',len(valid_moves))
241
+ if len(valid_moves)<=5:
242
+ for move in valid_moves:
243
+ print(move.peice_captured ,move.peice_moved, move.move_id)
244
+
245
+ move_made=False
246
+ move_undone = False
247
+
248
+ draw_game_state(screen,gs,valid_moves,sqselected) #add mouse hnadlers
249
+ if gs.check_mate:
250
+ game_over=True
251
+ if gs.whiteToMove:
252
+ draw_end_game_text(screen,'black wins by checkmate')
253
+ else:
254
+ draw_end_game_text(screen,"white wins by checkmate")
255
+ elif gs.steale_mate:
256
+ game_over=True
257
+ draw_end_game_text(screen,'stealmate no moves for king and no check')
258
+ clock.tick(max_fps)
259
+ pygame.display.flip()
260
+ from pygame.math import Vector2
261
+
262
+ def animateMove(move, screen, board, clock):
263
+ start = Vector2(move.start_col, move.start_row)
264
+ end = Vector2(move.end_col, move.end_row)
265
+ distance = end.distance_to(start)
266
+ frames_per_sq = 10
267
+ frame_count = int(distance * frames_per_sq)
268
+
269
+ for frame in range(frame_count + 1):
270
+ t = frame / frame_count # in [0, 1]
271
+ current = start.lerp(end, t) # linear interpolation
272
+ c, r = current.x, current.y
273
+
274
+ draw_board(screen)
275
+ draw_peices(screen, board)
276
+
277
+ # erase ending square
278
+ colour = colors[(move.end_row + move.end_col) & 1]
279
+ end_sq = pygame.Rect(move.end_col * sq_size, move.end_row * sq_size, sq_size, sq_size)
280
+ pygame.draw.rect(screen, colour, end_sq)
281
+
282
+ # draw captured piece if any
283
+ if move.peice_captured != '--':
284
+ if move.is_empassant_move:
285
+ screen.blit(images[move.peice_captured], end_sq)
286
+ else:
287
+ screen.blit(images[move.peice_captured], end_sq)
288
+
289
+ # draw moving piece at interpolated position
290
+ screen.blit(images[move.peice_moved],
291
+ pygame.Rect(c * sq_size, r * sq_size, sq_size, sq_size))
292
+
293
+ pygame.display.flip()
294
+ clock.tick(120)
295
+
296
+ def draw_end_game_text(screen,text):
297
+ font = pygame.font.SysFont('Helvitca',32,True,False)
298
+ text_object = font.render(text,0,pygame.Color('Gray'))
299
+ text_location = pygame.Rect(0,0,width,height).move(width/2,height/2)
300
+ screen.blit(text_object,text_location)
301
+ text_object = font.render(text,0,pygame.Color('Black'))
302
+ screen.blit(text_object,text_location.move(2,2))
303
+
304
+ # def get_moves(image_path):
305
+ # board,fen = get_board(image_path)
306
+ # gs= engine.GameState(board)
307
+ # moves = move_finder.get_best_n_moves(gs)
308
+ # gs = engine.GameState(board)
309
+ # gs.whiteToMove = not gs.whiteToMove
310
+ # moves2 = move_finder.get_best_n_moves(gs)
311
+ # colour = ('white_moves','black_moves') if not gs.whiteToMove else ('black_moves','white_moves')
312
+ # return fen, {colour[0]:moves,colour[1]:moves2}
313
+
314
+ if __name__=='__main__':
315
+ #handle and update graphics and input
316
+ #whenever u import this module else where this wont run this fucntion
317
+ main()
318
+ # get_moves('./image.png')
319
+
320
+
321
+
322
+
final/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/games_play.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import deque
2
+ import random
3
+ import torch
4
+ import torch
5
+ from engine import GameState
6
+ from move_finder import find_best_move_shallow
7
+ from infer_nnue import gs_to_nnue_features
8
+ from nnue_model import NNUE
9
+ from tqdm import tqdm
10
+ from infer_nnue import NNUEInfer
11
+ NNUE_FEATURES = 32
12
+ def pad_features(feats):
13
+ if len(feats) < NNUE_FEATURES:
14
+ return feats + [0] * (NNUE_FEATURES - len(feats))
15
+ return feats[:NNUE_FEATURES]
16
+
17
+ import pickle
18
+
19
+ def load_pgn_dataset(path):
20
+ trajectories = []
21
+ current_traj = []
22
+
23
+ with open(path, "rb") as f:
24
+ while True:
25
+ try:
26
+ chunk = pickle.load(f)
27
+ for item in chunk:
28
+ current_traj.append(item)
29
+
30
+ # heuristic: end trajectory on side-to-move flip
31
+ if len(current_traj) > 1 and \
32
+ current_traj[-1]["stm"] != current_traj[-2]["stm"]:
33
+ trajectories.append(current_traj)
34
+ current_traj = []
35
+
36
+ except EOFError:
37
+ break
38
+
39
+ if current_traj:
40
+ trajectories.append(current_traj)
41
+
42
+ return trajectories
43
+
44
+
45
+ @torch.no_grad()
46
+ @torch.no_grad()
47
+ def td_targets_from_traj(model, traj, gamma=0.99):
48
+ if len(traj) == 1:
49
+ return [0.0]
50
+
51
+ feats = [pad_features(x["features"]) for x in traj]
52
+ stm = [x["stm"] for x in traj]
53
+
54
+ feats = torch.tensor(feats, dtype=torch.long, device="cuda")
55
+ stm = torch.tensor(stm, dtype=torch.long, device="cuda")
56
+
57
+ values = model(feats, stm).view(-1)
58
+
59
+ targets = torch.empty_like(values)
60
+
61
+ # TD(0) with turn flip
62
+ targets[:-1] = gamma * (-values[1:])
63
+ targets[-1] = values[-1].detach()
64
+
65
+ # value clipping (STOCKFISH STYLE)
66
+ targets = torch.clamp(targets, -1.0, 1.0)
67
+
68
+ return targets.cpu().tolist()
69
+
70
+
71
+
72
+ from collections import deque
73
+ import random
74
+
75
+ class ReplayBuffer:
76
+ def __init__(self, capacity=300_000):
77
+ self.buf = deque(maxlen=capacity)
78
+
79
+ def add(self, f, stm, t):
80
+ self.buf.append((f, stm, t))
81
+
82
+ def sample(self, n):
83
+ return random.sample(self.buf, n)
84
+
85
+ def __len__(self):
86
+ return len(self.buf)
87
+
88
+
89
+ def train_from_replay(model, optimizer, replay, batch_size):
90
+ if len(replay) < batch_size:
91
+ return
92
+
93
+ batch = replay.sample(batch_size)
94
+ feats, stm, targets = zip(*batch)
95
+
96
+ feats = torch.tensor(feats, dtype=torch.long, device="cuda")
97
+ stm = torch.tensor(stm, dtype=torch.long, device="cuda")
98
+ targ = torch.tensor(targets, dtype=torch.float, device="cuda")
99
+
100
+ preds = model(feats, stm).view(-1)
101
+
102
+ loss = torch.nn.functional.smooth_l1_loss(preds, targ)
103
+
104
+ optimizer.zero_grad(set_to_none=True)
105
+ loss.backward()
106
+ optimizer.step()
107
+
108
+
109
+ from tqdm import tqdm
110
+ device = "cuda"
111
+ model = NNUE().to(device)
112
+ model.load_state_dict(torch.load("nnue_model.pt", weights_only=True))
113
+ optimizer = torch.optim.Adam(model.parameters(), lr=5e-5)
114
+
115
+ replay = ReplayBuffer()
116
+ trajectories = load_pgn_dataset("nnue_dataset.pkl")
117
+
118
+ for epoch in range(3):
119
+ print(f"Epoch {epoch}")
120
+
121
+ for traj in tqdm(trajectories):
122
+ if len(traj) < 2:
123
+ continue
124
+
125
+ targets = td_targets_from_traj(model, traj)
126
+
127
+ for x, t in zip(traj, targets):
128
+ replay.add(
129
+ pad_features(x["features"]),
130
+ x["stm"],
131
+ t
132
+ )
133
+
134
+ for _ in range(3):
135
+ train_from_replay(model, optimizer, replay, batch_size=512)
136
+
137
+ torch.save(model.state_dict(), "nnue_model_td.pt")
final/images/bB.png ADDED
final/images/bK.png ADDED
final/images/bN.png ADDED
final/images/bQ.png ADDED
final/images/bR.png ADDED
final/images/bp.png ADDED
final/images/wB.png ADDED
final/images/wK.png ADDED
final/images/wN.png ADDED
final/images/wQ.png ADDED
final/images/wR.png ADDED
final/images/wp.png ADDED
final/infer_nnue.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ PIECE_TO_INDEX = {
3
+ 'wp': 0, 'wN': 1, 'wB': 2, 'wR': 3, 'wQ': 4,
4
+ 'bp': 5, 'bN': 6, 'bB': 7, 'bR': 8, 'bQ': 9
5
+ }
6
+
7
+
8
+ NUM_PIECES = 10
9
+ NUM_SQUARES = 64
10
+ NUM_FEATURES = NUM_PIECES * NUM_SQUARES * NUM_SQUARES
11
+
12
+ PAD_IDX = NUM_FEATURES
13
+ def find_king_squares(board):
14
+ wk = bk = None
15
+ for r in range(8):
16
+ for c in range(8):
17
+ if board[r][c] == "wK":
18
+ wk = r * 8 + c
19
+ elif board[r][c] == "bK":
20
+ bk = r * 8 + c
21
+ return wk, bk
22
+
23
+ def gs_to_nnue_features(gs):
24
+ board = gs.board
25
+ wk, bk = find_king_squares(board)
26
+
27
+ features = []
28
+
29
+ for r in range(8):
30
+ for c in range(8):
31
+ piece = board[r][c]
32
+ if piece == "--" or piece[1] == "K":
33
+ continue
34
+
35
+ p_idx = PIECE_TO_INDEX[piece]
36
+ sq = r * 8 + c
37
+
38
+ if piece[0] == 'w':
39
+ king_sq = wk
40
+ else:
41
+ king_sq = bk
42
+
43
+ if king_sq is None:
44
+ continue
45
+
46
+ # index = p * 64 * 64 + king * 64 + piece_square
47
+ idx = (
48
+ p_idx * 64 * 64 +
49
+ king_sq * 64 +
50
+ sq
51
+ )
52
+ features.append(idx)
53
+
54
+ return features
55
+
56
+ import torch
57
+
58
+ class NNUEInfer:
59
+ def __init__(self, model, device="cpu"):
60
+ self.device = device
61
+ self.model = model.to(device)
62
+ self.model.eval()
63
+
64
+ @torch.no_grad()
65
+ def __call__(self, features, stm):
66
+ """
67
+ features : List[int]
68
+ stm : 1 if white to move, 0 if black
69
+ returns : float score
70
+ """
71
+ if not features:
72
+ features = [PAD_IDX]
73
+
74
+ feats = torch.tensor(
75
+ features,
76
+ dtype=torch.long,
77
+ device=self.device
78
+ ).unsqueeze(0)
79
+
80
+ stm = torch.tensor(
81
+ [stm],
82
+ dtype=torch.long,
83
+ device=self.device
84
+ )
85
+
86
+ return self.model(feats, stm).item()
final/move_finder.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import torch
3
+ from nnue_model import NNUE
4
+ from infer_nnue import NNUEInfer
5
+ import infer_nnue
6
+
7
+ piece_score = {'K': 1000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'p': 100,'--':0,'-':0}
8
+ CHECKMATE = 100000
9
+ STALEMATE = 0
10
+ DEPTH = 3
11
+
12
+
13
+ # Pawn (p = 100)
14
+ pawn_table = [
15
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
16
+ [ 5, 10, 10,-20,-20, 10, 10, 5],
17
+ [ 5, -5,-10, 0, 0,-10, -5, 5],
18
+ [ 0, 0, 0, 20, 20, 0, 0, 0],
19
+ [ 5, 5, 10, 25, 25, 10, 5, 5],
20
+ [ 10, 10, 20, 30, 30, 20, 10, 10],
21
+ [ 50, 50, 50, 50, 50, 50, 50, 50],
22
+ [ 0, 0, 0, 0, 0, 0, 0, 0]
23
+ ]
24
+
25
+ # Knight (N = 320)
26
+ knight_table = [
27
+ [-50,-40,-30,-30,-30,-30,-40,-50],
28
+ [-40,-20, 0, 0, 0, 0,-20,-40],
29
+ [-30, 0, 10, 15, 15, 10, 0,-30],
30
+ [-30, 5, 15, 20, 20, 15, 5,-30],
31
+ [-30, 0, 15, 20, 20, 15, 0,-30],
32
+ [-30, 5, 10, 15, 15, 10, 5,-30],
33
+ [-40,-20, 0, 5, 5, 0,-20,-40],
34
+ [-50,-40,-30,-30,-30,-30,-40,-50]
35
+ ]
36
+
37
+ # Bishop (B = 330)
38
+ bishop_table = [
39
+ [-20,-10,-10,-10,-10,-10,-10,-20],
40
+ [-10, 5, 0, 0, 0, 0, 5,-10],
41
+ [-10, 10, 10, 10, 10, 10, 10,-10],
42
+ [-10, 0, 10, 10, 10, 10, 0,-10],
43
+ [-10, 5, 5, 10, 10, 5, 5,-10],
44
+ [-10, 0, 5, 10, 10, 5, 0,-10],
45
+ [-10, 0, 0, 0, 0, 0, 0,-10],
46
+ [-20,-10,-10,-10,-10,-10,-10,-20]
47
+ ]
48
+
49
+ # Rook (R = 500)
50
+ rook_table = [
51
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
52
+ [ 5, 10, 10, 10, 10, 10, 10, 5],
53
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
54
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
55
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
56
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
57
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
58
+ [ 0, 0, 0, 5, 5, 0, 0, 0]
59
+ ]
60
+
61
+ # Queen (Q = 900)
62
+ queen_table = [
63
+ [-20,-10,-10, -5, -5,-10,-10,-20],
64
+ [-10, 0, 0, 0, 0, 5, 0,-10],
65
+ [-10, 0, 5, 5, 5, 5, 5,-10],
66
+ [ -5, 0, 5, 5, 5, 5, 0, -5],
67
+ [ 0, 0, 5, 5, 5, 5, 0, -5],
68
+ [-10, 5, 5, 5, 5, 5, 0,-10],
69
+ [-10, 0, 5, 0, 0, 0, 0,-10],
70
+ [-20,-10,-10, -5, -5,-10,-10,-20]
71
+ ]
72
+
73
+ # King (K = 1000) – Middlegame
74
+ king_table_mid = [
75
+ [-30,-40,-40,-50,-50,-40,-40,-30],
76
+ [-30,-40,-40,-50,-50,-40,-40,-30],
77
+ [-30,-40,-40,-50,-50,-40,-40,-30],
78
+ [-30,-40,-40,-50,-50,-40,-40,-30],
79
+ [-20,-30,-30,-40,-40,-30,-30,-20],
80
+ [-10,-20,-20,-20,-20,-20,-20,-10],
81
+ [ 20, 20, 0, 0, 0, 0, 20, 20],
82
+ [ 20, 30, 10, 0, 0, 10, 30, 20]
83
+ ]
84
+
85
+ # King (K = 1000) – Endgame
86
+ king_table_end = [
87
+ [-50,-40,-30,-20,-20,-30,-40,-50],
88
+ [-30,-20,-10, 0, 0,-10,-20,-30],
89
+ [-30,-10, 20, 30, 30, 20,-10,-30],
90
+ [-30,-10, 30, 40, 40, 30,-10,-30],
91
+ [-30,-10, 30, 40, 40, 30,-10,-30],
92
+ [-30,-10, 20, 30, 30, 20,-10,-30],
93
+ [-30,-30, 0, 0, 0, 0,-30,-30],
94
+ [-50,-30,-30,-30,-30,-30,-30,-50]
95
+ ]
96
+
97
+ king_scores = [[0]*8 for _ in range(8)]
98
+ for r in range(8):
99
+ for c in range(8):
100
+ king_scores[r][c]= king_table_end[r][c]+ king_table_end[r][c]
101
+ for r in range(4):
102
+ for c in range(8):
103
+ pawn_table[r][c],pawn_table[7-r][c]=pawn_table[r][c],pawn_table[7-r][c]
104
+
105
+
106
+
107
+
108
+ peice_position_scores = {'N':knight_table,'K':king_scores,'N':knight_table,'B':bishop_table,'Q':queen_table,'p':pawn_table,'R':rook_table}
109
+
110
+ '''
111
+ use openings
112
+ use numpy and better board representation
113
+ better use p.q or something like that
114
+ transposition tables
115
+ save the evaluation zobra hash
116
+ add which moves it is stoping
117
+ add attacking and defensive
118
+ we can teach end game theory
119
+ if apeice is attacked try to move that first
120
+ storing the data of moves not to recalculate
121
+ '''
122
+
123
+
124
+
125
+
126
+
127
+ def random_move(valid_moves):
128
+ ind=random.randint(0,len(valid_moves)-1)
129
+ return valid_moves[ind]
130
+
131
+
132
+ ## checking for greedy
133
+ # for all moves check where i can have more peices/value
134
+ #but we also need to score_material the next move so that the best possible comes out
135
+
136
+
137
+ #greedy algorithim and try to get better position
138
+ #score material on board
139
+ #assume black playing ai and check mate is worst
140
+ # go for level 2
141
+ # we want to minize the maximum of opponent score
142
+
143
+
144
+ def find_best_move_non_recursion(gs,valid_moves):
145
+ turn = 1 if gs.whiteToMove else -1
146
+ opponent_min_max_score= CHECKMATE #smallest of their maximums
147
+ best_player_move = None
148
+ random.shuffle(valid_moves)
149
+ for player_move in valid_moves:
150
+ gs.make_move(player_move)
151
+ opponent_moves = gs.get_valid_moves()
152
+ if gs.check_mate:
153
+ opponent_max_score = -CHECKMATE
154
+ elif gs.steale_mate:
155
+ opponent_max_score=STALEMATE
156
+ else:
157
+ opponent_max_score = -CHECKMATE
158
+ random.shuffle(opponent_moves)
159
+ for opponent_move in opponent_moves:
160
+ gs.make_move(opponent_move)
161
+ gs.get_valid_moves()
162
+ if gs.check_mate:
163
+ score = CHECKMATE
164
+ elif gs.steale_mate:
165
+ score=STALEMATE
166
+ else:
167
+ score = -turn * score_material(gs.board)
168
+
169
+ if (score>opponent_max_score):
170
+ opponent_max_score=score # try to find best move for opponent
171
+
172
+ gs.undo_move()
173
+ if opponent_min_max_score> opponent_max_score:
174
+ opponent_min_max_score = opponent_max_score # try to find best move for u which is worst(best) move
175
+ best_move = player_move # my new best is least of all opponent bests
176
+
177
+ gs.undo_move()
178
+
179
+ return best_move
180
+ # solve this recursively
181
+ # prune the branches we do not need
182
+
183
+
184
+ '''
185
+ helper method for best method
186
+ '''
187
+ def find_best_move(gs, valid_moves, return_queue):
188
+ result = find_move_nega_max_alpha_beta(
189
+ gs, valid_moves, DEPTH, -2*CHECKMATE, 2*CHECKMATE, 1
190
+ )
191
+
192
+ score, best_moves = result
193
+
194
+ print("Top moves:")
195
+ for sc, mv in best_moves:
196
+ print(mv.get_chess_notation(), "score:", sc)
197
+
198
+ chosen_move = random.choice(best_moves)[1]
199
+ return_queue.put(chosen_move)
200
+
201
+
202
+
203
+
204
+ '''
205
+ find min max move
206
+ '''
207
+ def find_move_min_max(gs,valid_moves,depth,whiteToMove):
208
+ global next_move
209
+ if depth == 0 :
210
+ return score_material(gs)
211
+ if whiteToMove: #maximize score
212
+ max_score = - CHECKMATE
213
+ for move in valid_moves:
214
+ gs.make_move(move)
215
+ next_moves = gs.get_valid_moves()
216
+ score = find_move_min_max(gs,next_moves,depth-1,False)
217
+ if score>max_score:
218
+ max_score=score
219
+ if depth == DEPTH :
220
+ next_move = move
221
+ gs.undo_move()
222
+ return max_score
223
+ else:
224
+ min_score = CHECKMATE
225
+ for move in valid_moves:
226
+ gs.make_move(move)
227
+ next_moves = gs.get_valid_moves()
228
+ score = find_move_min_max(gs,next_moves,depth-1,True)
229
+ if score<min_score:
230
+ min_score=score
231
+ if depth == DEPTH :
232
+ next_move = move
233
+ gs.undo_move()
234
+ return min_score
235
+
236
+
237
+ '''
238
+ combine if else to one
239
+ '''
240
+
241
+ def find_move_nega_max(gs,valid_moves,depth,turn):
242
+ #always try to maximize but with multilier
243
+ global next_move,count
244
+ count +=1
245
+ if depth == 0 :
246
+ return turn * score_material(gs)
247
+
248
+ max_score = CHECKMATE
249
+ for move in valid_moves:
250
+ gs.make_move(move)
251
+ next_moves = gs.get_valid_moves()
252
+ score = -find_move_nega_max(gs,next_moves,depth-1,-1 * turn) #this is very important
253
+ if score>max_score:
254
+ max_score=score
255
+ if depth == DEPTH :
256
+ next_move = move
257
+ gs.undo_move()
258
+ return max_score
259
+
260
+ '''
261
+ the alpha beta pruning
262
+ remove branches that wont make any good
263
+ also depends on scoring algorithim
264
+ also add positional scores
265
+ need to control more squares and attack more squares
266
+ alpha beta these are the maximum and minimum u can acheive values overall
267
+
268
+ if max_score>alpha then max_score is alpha
269
+ if alpha>beta then prune that branch
270
+ ugot best else where no need for it
271
+ '''
272
+
273
+ def order_moves(gs, moves):
274
+ return moves
275
+
276
+ # def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
277
+ # global count, next_move
278
+ # count += 1 # counts all nodes visited
279
+
280
+ # if depth == 0:
281
+ # return turn * score_material(gs)
282
+
283
+ # max_score = -CHECKMATE
284
+ # valid_moves=order_moves(gs,valid_moves)
285
+ # for move in valid_moves:
286
+ # gs.make_move(move)
287
+ # next_moves = gs.get_valid_moves()
288
+ # score = -find_move_nega_max_alpha_beta(
289
+ # gs, next_moves, depth - 1, -beta, -alpha, -turn
290
+ # )
291
+ # gs.undo_move()
292
+
293
+ # if score > max_score:
294
+ # max_score = score
295
+ # if depth == DEPTH:
296
+ # next_move = move
297
+
298
+ # alpha = max(alpha, max_score)
299
+ # if alpha >= beta:
300
+ # break
301
+
302
+ # return max_score
303
+
304
+
305
+ TOP_N = 1 # number of best moves you want
306
+
307
+ # def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
308
+ # if depth == 0:
309
+ # return turn * score_material(gs)
310
+
311
+ # max_score = -CHECKMATE
312
+ # scored_moves = []
313
+
314
+ # # move ordering to improve pruning
315
+ # valid_moves = order_moves(gs, valid_moves)
316
+
317
+ # for move in valid_moves:
318
+ # gs.make_move(move)
319
+ # next_moves = gs.get_valid_moves()
320
+ # score = -find_move_nega_max_alpha_beta(
321
+ # gs, next_moves, depth - 1, -beta, -alpha, -turn
322
+ # )
323
+ # gs.undo_move()
324
+
325
+ # scored_moves.append((score, move))
326
+
327
+ # max_score = max(max_score, score)
328
+ # alpha = max(alpha, max_score)
329
+ # if alpha >= beta:
330
+ # break # alpha-beta cutoff
331
+
332
+ # # Only save best moves at root depth
333
+ # if depth == DEPTH:
334
+ # scored_moves.sort(key=lambda x: x[0], reverse=True)
335
+ # best_moves = [(score, move) for score, move in scored_moves[:TOP_N]]
336
+
337
+ # return max_score
338
+
339
+ def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
340
+ if depth == 0:
341
+ return turn * score_material(gs)
342
+
343
+ max_score = -CHECKMATE
344
+ best_local_moves = []
345
+
346
+ valid_moves = order_moves(gs, valid_moves)
347
+
348
+ for move in valid_moves:
349
+ gs.make_move(move)
350
+ score = -find_move_nega_max_alpha_beta(
351
+ gs, gs.get_valid_moves(), depth - 1, -beta, -alpha, -turn
352
+ )
353
+ gs.undo_move()
354
+
355
+ if score > max_score:
356
+ max_score = score
357
+ best_local_moves = [(score, move)]
358
+ elif score == max_score:
359
+ best_local_moves.append((score, move))
360
+
361
+ alpha = max(alpha, max_score)
362
+ if alpha >= beta:
363
+ break
364
+
365
+ # ONLY at root
366
+ if depth == DEPTH:
367
+ best_local_moves.sort(key=lambda x: x[0], reverse=True)
368
+ return max_score, best_local_moves[:TOP_N]
369
+
370
+ return max_score
371
+
372
+ '''
373
+ score the board
374
+ positive score good for white
375
+ a negative score good for black
376
+ increase the scoring function
377
+ counting attacking and defending moves
378
+ '''
379
+
380
+
381
+
382
+ def score_material_hand(gs):
383
+ """Full evaluation of the board with material, positional, mobility, defense, etc."""
384
+ self=gs
385
+ if self.check_mate:
386
+ if self.whiteToMove:
387
+ return -CHECKMATE
388
+ else:
389
+ return CHECKMATE
390
+ elif self.steale_mate:
391
+ return STALEMATE
392
+
393
+ board = self.board
394
+ score = 0
395
+
396
+ white_squares_controlled = set()
397
+ black_squares_controlled = set()
398
+
399
+ # Material, piece-square, and piece defense evaluation
400
+ for r in range(8):
401
+ for c in range(8):
402
+ square = (r, c)
403
+ piece_info = board[r][c]
404
+
405
+ if piece_info == "--":
406
+ continue
407
+
408
+ color, piece = piece_info[0], piece_info[1]
409
+
410
+ base_value = piece_score[piece]
411
+
412
+ if color == 'w':
413
+ # Material value
414
+ score += base_value
415
+
416
+
417
+ score += peice_position_scores[piece][r][c]
418
+
419
+ #
420
+ moves = self.move_functions[piece](r,c,[])
421
+ for move in moves:
422
+ white_squares_controlled.add((move.end_row, move.end_col))
423
+
424
+ # Bonus for defending own piece
425
+ if board[move.end_row][move.end_col][0] == 'w':
426
+ defended_piece = board[move.end_row][move.end_col][1]
427
+ score += piece_score[defended_piece]
428
+
429
+ # Bonus for killing enemy valuable piece
430
+ if board[move.end_row][move.end_col][0] == 'b':
431
+ victim = board[move.end_row][move.end_col][1]
432
+ score += piece_score[victim] *1
433
+ elif color == 'b':
434
+ score -= base_value
435
+ score -= peice_position_scores[piece][7 - r][c]
436
+
437
+ moves = self.move_functions[piece](r,c,[])
438
+ for move in moves:
439
+ black_squares_controlled.add((move.end_row, move.end_col))
440
+
441
+ # Defense bonus
442
+ if board[move.end_row][move.end_col][0] == 'b':
443
+ defended_piece = board[move.end_row][move.end_col][1]
444
+ score -= piece_score[defended_piece]
445
+
446
+ # Killing enemy valuable piece
447
+ if board[move.end_row][move.end_col][0] == 'w':
448
+ victim = board[move.end_row][move.end_col][1]
449
+ score -= piece_score[victim] *1
450
+
451
+
452
+ # Bishop pair bonus
453
+ white_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'wB')
454
+ black_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'bB')
455
+ if white_bishops >= 2:
456
+ score += 50
457
+ if black_bishops >= 2:
458
+ score -= 50
459
+
460
+ # King safety (penalize exposed kings)
461
+ score += self.king_safety( "w") - self.king_safety("b")
462
+ score += (len(white_squares_controlled) - len(black_squares_controlled))*5
463
+
464
+
465
+ return score
466
+
467
+
468
+ device='cuda'
469
+ model = NNUE().to(device)
470
+ model.load_state_dict(torch.load("nnue_phase4_final.pt", map_location=device,weights_only=True))
471
+ nnue_eval = NNUEInfer(model, device)
472
+ CHECKMATE = 5000
473
+ STALEMATE = 0
474
+ MAX_RAW_EVAL = 2500 # hard clamp
475
+ TANH_SCALE = 1000.0 # controls steepness
476
+ import math
477
+
478
+ def clamp_eval(x):
479
+ return max(-MAX_RAW_EVAL, min(MAX_RAW_EVAL, x))
480
+
481
+ def tanh_scale_eval(x):
482
+ # maps to (-1, 1)
483
+ return math.tanh(x / TANH_SCALE)
484
+ def score_material(gs):
485
+ # Terminal positions
486
+ if gs.check_mate:
487
+ return -1 if gs.whiteToMove else 1
488
+ if gs.steale_mate:
489
+ return STALEMATE
490
+
491
+ # 1️⃣ Extract NNUE features
492
+ features = infer_nnue.gs_to_nnue_features(gs)
493
+
494
+ # 2️⃣ Side to move
495
+ stm = 1 if gs.whiteToMove else 0
496
+
497
+ # 3️⃣ NNUE inference
498
+ score = nnue_eval(features, stm)
499
+ return float(score)
500
+
501
+
502
+ def score_nnue(gs, nnue_eval):
503
+ if gs.check_mate:
504
+ return -1.0 if gs.whiteToMove else 1.0
505
+ if gs.steale_mate:
506
+ return 0.0
507
+
508
+ feats = infer_nnue.gs_to_nnue_features(gs)
509
+ stm = 1 if gs.whiteToMove else 0
510
+ return float(nnue_eval(feats, stm))
511
+
512
+ def get_best_n_moves(gs, n=1):
513
+ """
514
+ Returns best n moves for both White and Black.
515
+ """
516
+ best_white, best_black = [], []
517
+
518
+ # White to move
519
+ if gs.whiteToMove:
520
+ moves = gs.get_valid_moves()
521
+ scored = []
522
+ for move in moves:
523
+ gs.make_move(move)
524
+ score = -find_move_nega_max_alpha_beta(
525
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, -1
526
+ )
527
+ gs.undo_move()
528
+ scored.append((score, str(move)))
529
+ scored.sort(key=lambda x: x[0], reverse=True)
530
+ best_white = scored[:n]
531
+
532
+ # Black to move
533
+ else:
534
+ moves = gs.get_valid_moves()
535
+ scored = []
536
+ for move in moves:
537
+ gs.make_move(move)
538
+ score = -find_move_nega_max_alpha_beta(
539
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, 1
540
+ )
541
+ gs.undo_move()
542
+ scored.append((score, str(move)))
543
+ scored.sort(key=lambda x: x[0], reverse=True)
544
+ best_black = scored[:n]
545
+ return best_white if best_white else best_black
546
+
547
+
548
+ def find_best_move_shallow(gs, depth=2):
549
+ valid_moves = gs.get_valid_moves()
550
+ best_score = -1e9
551
+ best_move = None
552
+
553
+ for move in valid_moves:
554
+ gs.make_move(move)
555
+ score = -find_move_nega_max_alpha_beta(
556
+ gs, gs.get_valid_moves(), depth - 1,
557
+ -CHECKMATE, CHECKMATE,
558
+ -1 if gs.whiteToMove else 1
559
+ )
560
+ gs.undo_move()
561
+
562
+ if score > best_score:
563
+ best_score = score
564
+ best_move = move
565
+
566
+ return best_move, best_score
567
+
568
+
569
+
570
+
571
+ # def score_nnue(gs, nnue_eval):
572
+ # if gs.check_mate:
573
+ # return -1.0 if gs.whiteToMove else 1.0
574
+ # if gs.steale_mate:
575
+ # return 0.0
576
+
577
+ # feats = infer_nnue.gs_to_nnue_features(gs)
578
+ # stm = 1 if gs.whiteToMove else 0
579
+ # return float(nnue_eval(feats, stm))
final/nnue_model.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import onnx
4
+ import sys
5
+ sys.stdout.reconfigure(encoding='utf-8')
6
+
7
+ NUM_FEATURES = 10 * 64 * 64 # NNUE feature space
8
+ PAD_IDX = NUM_FEATURES
9
+
10
+ class NNUE(nn.Module):
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.embed = nn.Embedding(NUM_FEATURES + 1, 256, padding_idx=PAD_IDX)
14
+ self.fc1 = nn.Linear(256 + 1, 256)
15
+ self.fc2 = nn.Linear(256, 64)
16
+ self.fc3 = nn.Linear(64, 1)
17
+
18
+ def forward(self, feats, stm):
19
+ # feats: [B, N] long
20
+ x = self.embed(feats).sum(dim=1)
21
+
22
+ # stm: [B] -> [-1, +1]
23
+ stm = stm.float().unsqueeze(1) * 2 - 1
24
+ x = torch.cat([x, stm], dim=1)
25
+
26
+ x = torch.relu(self.fc1(x))
27
+ x = torch.relu(self.fc2(x))
28
+ return self.fc3(x).squeeze(1)
29
+
30
+
31
+ # ------------------------------
32
+ # Create model
33
+ # ------------------------------
34
+ torch_model = NNUE()
35
+ torch_model.eval()
36
+
37
+ # ------------------------------
38
+ # Correct example inputs
39
+ # ------------------------------
40
+ BATCH = 1
41
+ ACTIVE_FEATURES = 32 # typical NNUE sparse feature count
42
+
43
+ # Feature indices (LONG!)
44
+ feats = torch.randint(
45
+ low=0,
46
+ high=NUM_FEATURES,
47
+ size=(BATCH, ACTIVE_FEATURES),
48
+ dtype=torch.long
49
+ )
50
+
51
+ # Side to move: 0 = black, 1 = white
52
+ stm = torch.randint(0, 2, (BATCH,), dtype=torch.long)
53
+
54
+ example_inputs = (feats, stm)
final/nnue_phase4_final.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a59b06835534ebdccc3bb3ca0ff047e504918081e76027acc2cea81ccfab043b
3
+ size 42277715
final/requirements.txt ADDED
Binary file (200 Bytes). View file
 
games_play.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import deque
2
+ import random
3
+ import torch
4
+ import torch
5
+ from engine import GameState
6
+ from move_finder import find_best_move_shallow
7
+ from infer_nnue import gs_to_nnue_features
8
+ from nnue_model import NNUE
9
+ from tqdm import tqdm
10
+ from infer_nnue import NNUEInfer
11
+ NNUE_FEATURES = 32
12
+ def pad_features(feats):
13
+ if len(feats) < NNUE_FEATURES:
14
+ return feats + [0] * (NNUE_FEATURES - len(feats))
15
+ return feats[:NNUE_FEATURES]
16
+
17
+ import pickle
18
+
19
+ def load_pgn_dataset(path):
20
+ trajectories = []
21
+ current_traj = []
22
+
23
+ with open(path, "rb") as f:
24
+ while True:
25
+ try:
26
+ chunk = pickle.load(f)
27
+ for item in chunk:
28
+ current_traj.append(item)
29
+
30
+ # heuristic: end trajectory on side-to-move flip
31
+ if len(current_traj) > 1 and \
32
+ current_traj[-1]["stm"] != current_traj[-2]["stm"]:
33
+ trajectories.append(current_traj)
34
+ current_traj = []
35
+
36
+ except EOFError:
37
+ break
38
+
39
+ if current_traj:
40
+ trajectories.append(current_traj)
41
+
42
+ return trajectories
43
+
44
+
45
+ @torch.no_grad()
46
+ @torch.no_grad()
47
+ def td_targets_from_traj(model, traj, gamma=0.99):
48
+ if len(traj) == 1:
49
+ return [0.0]
50
+
51
+ feats = [pad_features(x["features"]) for x in traj]
52
+ stm = [x["stm"] for x in traj]
53
+
54
+ feats = torch.tensor(feats, dtype=torch.long, device="cuda")
55
+ stm = torch.tensor(stm, dtype=torch.long, device="cuda")
56
+
57
+ values = model(feats, stm).view(-1)
58
+
59
+ targets = torch.empty_like(values)
60
+
61
+ # TD(0) with turn flip
62
+ targets[:-1] = gamma * (-values[1:])
63
+ targets[-1] = values[-1].detach()
64
+
65
+ # value clipping (STOCKFISH STYLE)
66
+ targets = torch.clamp(targets, -1.0, 1.0)
67
+
68
+ return targets.cpu().tolist()
69
+
70
+
71
+
72
+ from collections import deque
73
+ import random
74
+
75
+ class ReplayBuffer:
76
+ def __init__(self, capacity=300_000):
77
+ self.buf = deque(maxlen=capacity)
78
+
79
+ def add(self, f, stm, t):
80
+ self.buf.append((f, stm, t))
81
+
82
+ def sample(self, n):
83
+ return random.sample(self.buf, n)
84
+
85
+ def __len__(self):
86
+ return len(self.buf)
87
+
88
+
89
+ def train_from_replay(model, optimizer, replay, batch_size):
90
+ if len(replay) < batch_size:
91
+ return
92
+
93
+ batch = replay.sample(batch_size)
94
+ feats, stm, targets = zip(*batch)
95
+
96
+ feats = torch.tensor(feats, dtype=torch.long, device="cuda")
97
+ stm = torch.tensor(stm, dtype=torch.long, device="cuda")
98
+ targ = torch.tensor(targets, dtype=torch.float, device="cuda")
99
+
100
+ preds = model(feats, stm).view(-1)
101
+
102
+ loss = torch.nn.functional.smooth_l1_loss(preds, targ)
103
+
104
+ optimizer.zero_grad(set_to_none=True)
105
+ loss.backward()
106
+ optimizer.step()
107
+
108
+
109
+ from tqdm import tqdm
110
+ device = "cuda"
111
+ model = NNUE().to(device)
112
+ model.load_state_dict(torch.load("nnue_model.pt", weights_only=True))
113
+ optimizer = torch.optim.Adam(model.parameters(), lr=5e-5)
114
+
115
+ replay = ReplayBuffer()
116
+ trajectories = load_pgn_dataset("nnue_dataset.pkl")
117
+
118
+ for epoch in range(3):
119
+ print(f"Epoch {epoch}")
120
+
121
+ for traj in tqdm(trajectories):
122
+ if len(traj) < 2:
123
+ continue
124
+
125
+ targets = td_targets_from_traj(model, traj)
126
+
127
+ for x, t in zip(traj, targets):
128
+ replay.add(
129
+ pad_features(x["features"]),
130
+ x["stm"],
131
+ t
132
+ )
133
+
134
+ for _ in range(3):
135
+ train_from_replay(model, optimizer, replay, batch_size=512)
136
+
137
+ torch.save(model.state_dict(), "nnue_model_td.pt")
infer_nnue.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ PIECE_TO_INDEX = {
3
+ 'wp': 0, 'wN': 1, 'wB': 2, 'wR': 3, 'wQ': 4,
4
+ 'bp': 5, 'bN': 6, 'bB': 7, 'bR': 8, 'bQ': 9
5
+ }
6
+
7
+
8
+ NUM_PIECES = 10
9
+ NUM_SQUARES = 64
10
+ NUM_FEATURES = NUM_PIECES * NUM_SQUARES * NUM_SQUARES
11
+
12
+ PAD_IDX = NUM_FEATURES
13
+ def find_king_squares(board):
14
+ wk = bk = None
15
+ for r in range(8):
16
+ for c in range(8):
17
+ if board[r][c] == "wK":
18
+ wk = r * 8 + c
19
+ elif board[r][c] == "bK":
20
+ bk = r * 8 + c
21
+ return wk, bk
22
+
23
+ def gs_to_nnue_features(gs):
24
+ board = gs.board
25
+ wk, bk = find_king_squares(board)
26
+
27
+ features = []
28
+
29
+ for r in range(8):
30
+ for c in range(8):
31
+ piece = board[r][c]
32
+ if piece == "--" or piece[1] == "K":
33
+ continue
34
+
35
+ p_idx = PIECE_TO_INDEX[piece]
36
+ sq = r * 8 + c
37
+
38
+ if piece[0] == 'w':
39
+ king_sq = wk
40
+ else:
41
+ king_sq = bk
42
+
43
+ if king_sq is None:
44
+ continue
45
+
46
+ # index = p * 64 * 64 + king * 64 + piece_square
47
+ idx = (
48
+ p_idx * 64 * 64 +
49
+ king_sq * 64 +
50
+ sq
51
+ )
52
+ features.append(idx)
53
+
54
+ return features
55
+
56
+ import torch
57
+
58
+ class NNUEInfer:
59
+ def __init__(self, model, device="cpu"):
60
+ self.device = device
61
+ self.model = model.to(device)
62
+ self.model.eval()
63
+
64
+ @torch.no_grad()
65
+ def __call__(self, features, stm):
66
+ """
67
+ features : List[int]
68
+ stm : 1 if white to move, 0 if black
69
+ returns : float score
70
+ """
71
+ if not features:
72
+ features = [PAD_IDX]
73
+
74
+ feats = torch.tensor(
75
+ features,
76
+ dtype=torch.long,
77
+ device=self.device
78
+ ).unsqueeze(0)
79
+
80
+ stm = torch.tensor(
81
+ [stm],
82
+ dtype=torch.long,
83
+ device=self.device
84
+ )
85
+
86
+ return self.model(feats, stm).item()
move_finder.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import torch
3
+ from nnue_model import NNUE
4
+ from infer_nnue import NNUEInfer
5
+ import infer_nnue
6
+
7
+ piece_score = {'K': 1000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'p': 100,'--':0,'-':0}
8
+ CHECKMATE = 100000
9
+ STALEMATE = 0
10
+ DEPTH = 3
11
+
12
+
13
+ # Pawn (p = 100)
14
+ pawn_table = [
15
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
16
+ [ 5, 10, 10,-20,-20, 10, 10, 5],
17
+ [ 5, -5,-10, 0, 0,-10, -5, 5],
18
+ [ 0, 0, 0, 20, 20, 0, 0, 0],
19
+ [ 5, 5, 10, 25, 25, 10, 5, 5],
20
+ [ 10, 10, 20, 30, 30, 20, 10, 10],
21
+ [ 50, 50, 50, 50, 50, 50, 50, 50],
22
+ [ 0, 0, 0, 0, 0, 0, 0, 0]
23
+ ]
24
+
25
+ # Knight (N = 320)
26
+ knight_table = [
27
+ [-50,-40,-30,-30,-30,-30,-40,-50],
28
+ [-40,-20, 0, 0, 0, 0,-20,-40],
29
+ [-30, 0, 10, 15, 15, 10, 0,-30],
30
+ [-30, 5, 15, 20, 20, 15, 5,-30],
31
+ [-30, 0, 15, 20, 20, 15, 0,-30],
32
+ [-30, 5, 10, 15, 15, 10, 5,-30],
33
+ [-40,-20, 0, 5, 5, 0,-20,-40],
34
+ [-50,-40,-30,-30,-30,-30,-40,-50]
35
+ ]
36
+
37
+ # Bishop (B = 330)
38
+ bishop_table = [
39
+ [-20,-10,-10,-10,-10,-10,-10,-20],
40
+ [-10, 5, 0, 0, 0, 0, 5,-10],
41
+ [-10, 10, 10, 10, 10, 10, 10,-10],
42
+ [-10, 0, 10, 10, 10, 10, 0,-10],
43
+ [-10, 5, 5, 10, 10, 5, 5,-10],
44
+ [-10, 0, 5, 10, 10, 5, 0,-10],
45
+ [-10, 0, 0, 0, 0, 0, 0,-10],
46
+ [-20,-10,-10,-10,-10,-10,-10,-20]
47
+ ]
48
+
49
+ # Rook (R = 500)
50
+ rook_table = [
51
+ [ 0, 0, 0, 0, 0, 0, 0, 0],
52
+ [ 5, 10, 10, 10, 10, 10, 10, 5],
53
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
54
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
55
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
56
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
57
+ [ -5, 0, 0, 0, 0, 0, 0, -5],
58
+ [ 0, 0, 0, 5, 5, 0, 0, 0]
59
+ ]
60
+
61
+ # Queen (Q = 900)
62
+ queen_table = [
63
+ [-20,-10,-10, -5, -5,-10,-10,-20],
64
+ [-10, 0, 0, 0, 0, 5, 0,-10],
65
+ [-10, 0, 5, 5, 5, 5, 5,-10],
66
+ [ -5, 0, 5, 5, 5, 5, 0, -5],
67
+ [ 0, 0, 5, 5, 5, 5, 0, -5],
68
+ [-10, 5, 5, 5, 5, 5, 0,-10],
69
+ [-10, 0, 5, 0, 0, 0, 0,-10],
70
+ [-20,-10,-10, -5, -5,-10,-10,-20]
71
+ ]
72
+
73
+ # King (K = 1000) – Middlegame
74
+ king_table_mid = [
75
+ [-30,-40,-40,-50,-50,-40,-40,-30],
76
+ [-30,-40,-40,-50,-50,-40,-40,-30],
77
+ [-30,-40,-40,-50,-50,-40,-40,-30],
78
+ [-30,-40,-40,-50,-50,-40,-40,-30],
79
+ [-20,-30,-30,-40,-40,-30,-30,-20],
80
+ [-10,-20,-20,-20,-20,-20,-20,-10],
81
+ [ 20, 20, 0, 0, 0, 0, 20, 20],
82
+ [ 20, 30, 10, 0, 0, 10, 30, 20]
83
+ ]
84
+
85
+ # King (K = 1000) – Endgame
86
+ king_table_end = [
87
+ [-50,-40,-30,-20,-20,-30,-40,-50],
88
+ [-30,-20,-10, 0, 0,-10,-20,-30],
89
+ [-30,-10, 20, 30, 30, 20,-10,-30],
90
+ [-30,-10, 30, 40, 40, 30,-10,-30],
91
+ [-30,-10, 30, 40, 40, 30,-10,-30],
92
+ [-30,-10, 20, 30, 30, 20,-10,-30],
93
+ [-30,-30, 0, 0, 0, 0,-30,-30],
94
+ [-50,-30,-30,-30,-30,-30,-30,-50]
95
+ ]
96
+
97
+ king_scores = [[0]*8 for _ in range(8)]
98
+ for r in range(8):
99
+ for c in range(8):
100
+ king_scores[r][c]= king_table_end[r][c]+ king_table_end[r][c]
101
+ for r in range(4):
102
+ for c in range(8):
103
+ pawn_table[r][c],pawn_table[7-r][c]=pawn_table[r][c],pawn_table[7-r][c]
104
+
105
+
106
+
107
+
108
+ peice_position_scores = {'N':knight_table,'K':king_scores,'N':knight_table,'B':bishop_table,'Q':queen_table,'p':pawn_table,'R':rook_table}
109
+
110
+ '''
111
+ use openings
112
+ use numpy and better board representation
113
+ better use p.q or something like that
114
+ transposition tables
115
+ save the evaluation zobra hash
116
+ add which moves it is stoping
117
+ add attacking and defensive
118
+ we can teach end game theory
119
+ if apeice is attacked try to move that first
120
+ storing the data of moves not to recalculate
121
+ '''
122
+
123
+
124
+
125
+
126
+
127
+ def random_move(valid_moves):
128
+ ind=random.randint(0,len(valid_moves)-1)
129
+ return valid_moves[ind]
130
+
131
+
132
+ ## checking for greedy
133
+ # for all moves check where i can have more peices/value
134
+ #but we also need to score_material the next move so that the best possible comes out
135
+
136
+
137
+ #greedy algorithim and try to get better position
138
+ #score material on board
139
+ #assume black playing ai and check mate is worst
140
+ # go for level 2
141
+ # we want to minize the maximum of opponent score
142
+
143
+
144
+ def find_best_move_non_recursion(gs,valid_moves):
145
+ turn = 1 if gs.whiteToMove else -1
146
+ opponent_min_max_score= CHECKMATE #smallest of their maximums
147
+ best_player_move = None
148
+ random.shuffle(valid_moves)
149
+ for player_move in valid_moves:
150
+ gs.make_move(player_move)
151
+ opponent_moves = gs.get_valid_moves()
152
+ if gs.check_mate:
153
+ opponent_max_score = -CHECKMATE
154
+ elif gs.steale_mate:
155
+ opponent_max_score=STALEMATE
156
+ else:
157
+ opponent_max_score = -CHECKMATE
158
+ random.shuffle(opponent_moves)
159
+ for opponent_move in opponent_moves:
160
+ gs.make_move(opponent_move)
161
+ gs.get_valid_moves()
162
+ if gs.check_mate:
163
+ score = CHECKMATE
164
+ elif gs.steale_mate:
165
+ score=STALEMATE
166
+ else:
167
+ score = -turn * score_material(gs.board)
168
+
169
+ if (score>opponent_max_score):
170
+ opponent_max_score=score # try to find best move for opponent
171
+
172
+ gs.undo_move()
173
+ if opponent_min_max_score> opponent_max_score:
174
+ opponent_min_max_score = opponent_max_score # try to find best move for u which is worst(best) move
175
+ best_move = player_move # my new best is least of all opponent bests
176
+
177
+ gs.undo_move()
178
+
179
+ return best_move
180
+ # solve this recursively
181
+ # prune the branches we do not need
182
+
183
+
184
+ '''
185
+ helper method for best method
186
+ '''
187
+ def find_best_move(gs, valid_moves, return_queue):
188
+ result = find_move_nega_max_alpha_beta(
189
+ gs, valid_moves, DEPTH, -2*CHECKMATE, 2*CHECKMATE, 1
190
+ )
191
+
192
+ score, best_moves = result
193
+
194
+ print("Top moves:")
195
+ for sc, mv in best_moves:
196
+ print(mv.get_chess_notation(), "score:", sc)
197
+
198
+ chosen_move = random.choice(best_moves)[1]
199
+ return_queue.put(chosen_move)
200
+
201
+
202
+
203
+
204
+ '''
205
+ find min max move
206
+ '''
207
+ def find_move_min_max(gs,valid_moves,depth,whiteToMove):
208
+ global next_move
209
+ if depth == 0 :
210
+ return score_material(gs)
211
+ if whiteToMove: #maximize score
212
+ max_score = - CHECKMATE
213
+ for move in valid_moves:
214
+ gs.make_move(move)
215
+ next_moves = gs.get_valid_moves()
216
+ score = find_move_min_max(gs,next_moves,depth-1,False)
217
+ if score>max_score:
218
+ max_score=score
219
+ if depth == DEPTH :
220
+ next_move = move
221
+ gs.undo_move()
222
+ return max_score
223
+ else:
224
+ min_score = CHECKMATE
225
+ for move in valid_moves:
226
+ gs.make_move(move)
227
+ next_moves = gs.get_valid_moves()
228
+ score = find_move_min_max(gs,next_moves,depth-1,True)
229
+ if score<min_score:
230
+ min_score=score
231
+ if depth == DEPTH :
232
+ next_move = move
233
+ gs.undo_move()
234
+ return min_score
235
+
236
+
237
+ '''
238
+ combine if else to one
239
+ '''
240
+
241
+ def find_move_nega_max(gs,valid_moves,depth,turn):
242
+ #always try to maximize but with multilier
243
+ global next_move,count
244
+ count +=1
245
+ if depth == 0 :
246
+ return turn * score_material(gs)
247
+
248
+ max_score = CHECKMATE
249
+ for move in valid_moves:
250
+ gs.make_move(move)
251
+ next_moves = gs.get_valid_moves()
252
+ score = -find_move_nega_max(gs,next_moves,depth-1,-1 * turn) #this is very important
253
+ if score>max_score:
254
+ max_score=score
255
+ if depth == DEPTH :
256
+ next_move = move
257
+ gs.undo_move()
258
+ return max_score
259
+
260
+ '''
261
+ the alpha beta pruning
262
+ remove branches that wont make any good
263
+ also depends on scoring algorithim
264
+ also add positional scores
265
+ need to control more squares and attack more squares
266
+ alpha beta these are the maximum and minimum u can acheive values overall
267
+
268
+ if max_score>alpha then max_score is alpha
269
+ if alpha>beta then prune that branch
270
+ ugot best else where no need for it
271
+ '''
272
+
273
+ def order_moves(gs, moves):
274
+ return moves
275
+
276
+ # def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
277
+ # global count, next_move
278
+ # count += 1 # counts all nodes visited
279
+
280
+ # if depth == 0:
281
+ # return turn * score_material(gs)
282
+
283
+ # max_score = -CHECKMATE
284
+ # valid_moves=order_moves(gs,valid_moves)
285
+ # for move in valid_moves:
286
+ # gs.make_move(move)
287
+ # next_moves = gs.get_valid_moves()
288
+ # score = -find_move_nega_max_alpha_beta(
289
+ # gs, next_moves, depth - 1, -beta, -alpha, -turn
290
+ # )
291
+ # gs.undo_move()
292
+
293
+ # if score > max_score:
294
+ # max_score = score
295
+ # if depth == DEPTH:
296
+ # next_move = move
297
+
298
+ # alpha = max(alpha, max_score)
299
+ # if alpha >= beta:
300
+ # break
301
+
302
+ # return max_score
303
+
304
+
305
+ TOP_N = 1 # number of best moves you want
306
+
307
+ # def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
308
+ # if depth == 0:
309
+ # return turn * score_material(gs)
310
+
311
+ # max_score = -CHECKMATE
312
+ # scored_moves = []
313
+
314
+ # # move ordering to improve pruning
315
+ # valid_moves = order_moves(gs, valid_moves)
316
+
317
+ # for move in valid_moves:
318
+ # gs.make_move(move)
319
+ # next_moves = gs.get_valid_moves()
320
+ # score = -find_move_nega_max_alpha_beta(
321
+ # gs, next_moves, depth - 1, -beta, -alpha, -turn
322
+ # )
323
+ # gs.undo_move()
324
+
325
+ # scored_moves.append((score, move))
326
+
327
+ # max_score = max(max_score, score)
328
+ # alpha = max(alpha, max_score)
329
+ # if alpha >= beta:
330
+ # break # alpha-beta cutoff
331
+
332
+ # # Only save best moves at root depth
333
+ # if depth == DEPTH:
334
+ # scored_moves.sort(key=lambda x: x[0], reverse=True)
335
+ # best_moves = [(score, move) for score, move in scored_moves[:TOP_N]]
336
+
337
+ # return max_score
338
+
339
+ def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
340
+ if depth == 0:
341
+ return turn * score_material(gs)
342
+
343
+ max_score = -CHECKMATE
344
+ best_local_moves = []
345
+
346
+ valid_moves = order_moves(gs, valid_moves)
347
+
348
+ for move in valid_moves:
349
+ gs.make_move(move)
350
+ score = -find_move_nega_max_alpha_beta(
351
+ gs, gs.get_valid_moves(), depth - 1, -beta, -alpha, -turn
352
+ )
353
+ gs.undo_move()
354
+
355
+ if score > max_score:
356
+ max_score = score
357
+ best_local_moves = [(score, move)]
358
+ elif score == max_score:
359
+ best_local_moves.append((score, move))
360
+
361
+ alpha = max(alpha, max_score)
362
+ if alpha >= beta:
363
+ break
364
+
365
+ # ONLY at root
366
+ if depth == DEPTH:
367
+ best_local_moves.sort(key=lambda x: x[0], reverse=True)
368
+ return max_score, best_local_moves[:TOP_N]
369
+
370
+ return max_score
371
+
372
+ '''
373
+ score the board
374
+ positive score good for white
375
+ a negative score good for black
376
+ increase the scoring function
377
+ counting attacking and defending moves
378
+ '''
379
+
380
+
381
+
382
+ def score_material_hand(gs):
383
+ """Full evaluation of the board with material, positional, mobility, defense, etc."""
384
+ self=gs
385
+ if self.check_mate:
386
+ if self.whiteToMove:
387
+ return -CHECKMATE
388
+ else:
389
+ return CHECKMATE
390
+ elif self.steale_mate:
391
+ return STALEMATE
392
+
393
+ board = self.board
394
+ score = 0
395
+
396
+ white_squares_controlled = set()
397
+ black_squares_controlled = set()
398
+
399
+ # Material, piece-square, and piece defense evaluation
400
+ for r in range(8):
401
+ for c in range(8):
402
+ square = (r, c)
403
+ piece_info = board[r][c]
404
+
405
+ if piece_info == "--":
406
+ continue
407
+
408
+ color, piece = piece_info[0], piece_info[1]
409
+
410
+ base_value = piece_score[piece]
411
+
412
+ if color == 'w':
413
+ # Material value
414
+ score += base_value
415
+
416
+
417
+ score += peice_position_scores[piece][r][c]
418
+
419
+ #
420
+ moves = self.move_functions[piece](r,c,[])
421
+ for move in moves:
422
+ white_squares_controlled.add((move.end_row, move.end_col))
423
+
424
+ # Bonus for defending own piece
425
+ if board[move.end_row][move.end_col][0] == 'w':
426
+ defended_piece = board[move.end_row][move.end_col][1]
427
+ score += piece_score[defended_piece]
428
+
429
+ # Bonus for killing enemy valuable piece
430
+ if board[move.end_row][move.end_col][0] == 'b':
431
+ victim = board[move.end_row][move.end_col][1]
432
+ score += piece_score[victim] *1
433
+ elif color == 'b':
434
+ score -= base_value
435
+ score -= peice_position_scores[piece][7 - r][c]
436
+
437
+ moves = self.move_functions[piece](r,c,[])
438
+ for move in moves:
439
+ black_squares_controlled.add((move.end_row, move.end_col))
440
+
441
+ # Defense bonus
442
+ if board[move.end_row][move.end_col][0] == 'b':
443
+ defended_piece = board[move.end_row][move.end_col][1]
444
+ score -= piece_score[defended_piece]
445
+
446
+ # Killing enemy valuable piece
447
+ if board[move.end_row][move.end_col][0] == 'w':
448
+ victim = board[move.end_row][move.end_col][1]
449
+ score -= piece_score[victim] *1
450
+
451
+
452
+ # Bishop pair bonus
453
+ white_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'wB')
454
+ black_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'bB')
455
+ if white_bishops >= 2:
456
+ score += 50
457
+ if black_bishops >= 2:
458
+ score -= 50
459
+
460
+ # King safety (penalize exposed kings)
461
+ score += self.king_safety( "w") - self.king_safety("b")
462
+ score += (len(white_squares_controlled) - len(black_squares_controlled))*5
463
+
464
+
465
+ return score
466
+
467
+
468
+ device='cuda'
469
+ model = NNUE().to(device)
470
+ model.load_state_dict(torch.load("nnue_phase4_final.pt", map_location=device,weights_only=True))
471
+ nnue_eval = NNUEInfer(model, device)
472
+ CHECKMATE = 5000
473
+ STALEMATE = 0
474
+ MAX_RAW_EVAL = 2500 # hard clamp
475
+ TANH_SCALE = 1000.0 # controls steepness
476
+ import math
477
+
478
+ def clamp_eval(x):
479
+ return max(-MAX_RAW_EVAL, min(MAX_RAW_EVAL, x))
480
+
481
+ def tanh_scale_eval(x):
482
+ # maps to (-1, 1)
483
+ return math.tanh(x / TANH_SCALE)
484
+ def score_material(gs):
485
+ # Terminal positions
486
+ if gs.check_mate:
487
+ return -1 if gs.whiteToMove else 1
488
+ if gs.steale_mate:
489
+ return STALEMATE
490
+
491
+ # 1️⃣ Extract NNUE features
492
+ features = infer_nnue.gs_to_nnue_features(gs)
493
+
494
+ # 2️⃣ Side to move
495
+ stm = 1 if gs.whiteToMove else 0
496
+
497
+ # 3️⃣ NNUE inference
498
+ score = nnue_eval(features, stm)
499
+ return float(score)
500
+
501
+
502
+ def score_nnue(gs, nnue_eval):
503
+ if gs.check_mate:
504
+ return -1.0 if gs.whiteToMove else 1.0
505
+ if gs.steale_mate:
506
+ return 0.0
507
+
508
+ feats = infer_nnue.gs_to_nnue_features(gs)
509
+ stm = 1 if gs.whiteToMove else 0
510
+ return float(nnue_eval(feats, stm))
511
+
512
+ def get_best_n_moves(gs, n=1):
513
+ """
514
+ Returns best n moves for both White and Black.
515
+ """
516
+ best_white, best_black = [], []
517
+
518
+ # White to move
519
+ if gs.whiteToMove:
520
+ moves = gs.get_valid_moves()
521
+ scored = []
522
+ for move in moves:
523
+ gs.make_move(move)
524
+ score = -find_move_nega_max_alpha_beta(
525
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, -1
526
+ )
527
+ gs.undo_move()
528
+ scored.append((score, str(move)))
529
+ scored.sort(key=lambda x: x[0], reverse=True)
530
+ best_white = scored[:n]
531
+
532
+ # Black to move
533
+ else:
534
+ moves = gs.get_valid_moves()
535
+ scored = []
536
+ for move in moves:
537
+ gs.make_move(move)
538
+ score = -find_move_nega_max_alpha_beta(
539
+ gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, 1
540
+ )
541
+ gs.undo_move()
542
+ scored.append((score, str(move)))
543
+ scored.sort(key=lambda x: x[0], reverse=True)
544
+ best_black = scored[:n]
545
+ return best_white if best_white else best_black
546
+
547
+
548
+ def find_best_move_shallow(gs, depth=2):
549
+ valid_moves = gs.get_valid_moves()
550
+ best_score = -1e9
551
+ best_move = None
552
+
553
+ for move in valid_moves:
554
+ gs.make_move(move)
555
+ score = -find_move_nega_max_alpha_beta(
556
+ gs, gs.get_valid_moves(), depth - 1,
557
+ -CHECKMATE, CHECKMATE,
558
+ -1 if gs.whiteToMove else 1
559
+ )
560
+ gs.undo_move()
561
+
562
+ if score > best_score:
563
+ best_score = score
564
+ best_move = move
565
+
566
+ return best_move, best_score
567
+
568
+
569
+
570
+
571
+ # def score_nnue(gs, nnue_eval):
572
+ # if gs.check_mate:
573
+ # return -1.0 if gs.whiteToMove else 1.0
574
+ # if gs.steale_mate:
575
+ # return 0.0
576
+
577
+ # feats = infer_nnue.gs_to_nnue_features(gs)
578
+ # stm = 1 if gs.whiteToMove else 0
579
+ # return float(nnue_eval(feats, stm))
nnue_model.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import onnx
4
+ import sys
5
+ sys.stdout.reconfigure(encoding='utf-8')
6
+
7
+ NUM_FEATURES = 10 * 64 * 64 # NNUE feature space
8
+ PAD_IDX = NUM_FEATURES
9
+
10
+ class NNUE(nn.Module):
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.embed = nn.Embedding(NUM_FEATURES + 1, 256, padding_idx=PAD_IDX)
14
+ self.fc1 = nn.Linear(256 + 1, 256)
15
+ self.fc2 = nn.Linear(256, 64)
16
+ self.fc3 = nn.Linear(64, 1)
17
+
18
+ def forward(self, feats, stm):
19
+ # feats: [B, N] long
20
+ x = self.embed(feats).sum(dim=1)
21
+
22
+ # stm: [B] -> [-1, +1]
23
+ stm = stm.float().unsqueeze(1) * 2 - 1
24
+ x = torch.cat([x, stm], dim=1)
25
+
26
+ x = torch.relu(self.fc1(x))
27
+ x = torch.relu(self.fc2(x))
28
+ return self.fc3(x).squeeze(1)
29
+
30
+
31
+ # ------------------------------
32
+ # Create model
33
+ # ------------------------------
34
+ torch_model = NNUE()
35
+ torch_model.eval()
36
+
37
+ # ------------------------------
38
+ # Correct example inputs
39
+ # ------------------------------
40
+ BATCH = 1
41
+ ACTIVE_FEATURES = 32 # typical NNUE sparse feature count
42
+
43
+ # Feature indices (LONG!)
44
+ feats = torch.randint(
45
+ low=0,
46
+ high=NUM_FEATURES,
47
+ size=(BATCH, ACTIVE_FEATURES),
48
+ dtype=torch.long
49
+ )
50
+
51
+ # Side to move: 0 = black, 1 = white
52
+ stm = torch.randint(0, 2, (BATCH,), dtype=torch.long)
53
+
54
+ example_inputs = (feats, stm)
nnue_phase4_final.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a59b06835534ebdccc3bb3ca0ff047e504918081e76027acc2cea81ccfab043b
3
+ size 42277715
requirements.txt ADDED
Binary file (200 Bytes). View file