hash-map commited on
Commit
3dbc34f
·
verified ·
1 Parent(s): 5c8b0c5

Update engine.py

Browse files
Files changed (1) hide show
  1. engine.py +752 -751
engine.py CHANGED
@@ -1,752 +1,753 @@
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
 
 
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 r<=6:
499
+ if not peice_pinned or pin_direction == (1,0):
500
+ if self.board[r+1][c]=='--':
501
+ moves.append(Move((r,c),(r+1,c),self.board))
502
+ if r == 1:
503
+ if self.board[3][c]=='--' and self.board[2][c]=='--':
504
+ moves.append(Move((1,c),(3,c),self.board))
505
+ if not peice_pinned or pin_direction == (1,-1):
506
+ if c>=1:
507
+ if self.board[r+1][c-1][0]=='w':
508
+ moves.append(Move((r,c),(r+1,c-1),self.board))
509
+ elif (r+1,c-1)==self.empassant_moves:
510
+ moves.append(Move((r,c),(r+1,c-1),self.board,is_empassant_move=True))
511
+ if not peice_pinned or pin_direction == (1,1):
512
+ if c<=6 :
513
+ if self.board[r+1][c+1][0]=='w':
514
+ moves.append(Move((r,c),(r+1,c+1),self.board))
515
+ elif (r+1,c+1)==self.empassant_moves:
516
+ moves.append(Move((r,c),(r+1,c+1),self.board,is_empassant_move=True))
517
+ return moves
518
+ '''
519
+ this func return the rook moves for particular rook
520
+ '''
521
+ def get_rook_moves(self,r,c,moves):
522
+ peice_pinned = False
523
+ pin_direction = ()
524
+ for i in range(len(self.pins)-1,-1,-1):
525
+ if self.pins[i][0] == r and self.pins[i][1]==c:
526
+ peice_pinned=True
527
+ pin_direction = (self.pins[i][2],self.pins[i][3])
528
+ if self.board[r][c][1]!='Q': #cannot remove queen from pin on rook moves ,onl remove it from bishop moves
529
+ self.pins.remove(self.pins[i])
530
+ break
531
+ if self.whiteToMove:
532
+ ur_symbol= 'w'
533
+ opp = 'b'
534
+ else:
535
+ ur_symbol= 'b'
536
+ opp = 'w'
537
+ for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
538
+ for i in range(1,8):
539
+ new_x,new_y = r + x*i ,c + y*i
540
+ if not self.is_valid_square(new_x,new_y):
541
+ break
542
+ else:
543
+ if not peice_pinned or pin_direction == (x,y) or pin_direction == (-x,-y):
544
+ if self.board[new_x][new_y][0]=='-':
545
+ moves.append(Move((r,c),(new_x,new_y),self.board))
546
+ elif self.board[new_x][new_y][0]==opp:
547
+ moves.append(Move((r,c),(new_x,new_y),self.board))
548
+ break
549
+ else :
550
+ break
551
+ return moves
552
+
553
+ '''
554
+ this func return the knight moves for particular rook
555
+ '''
556
+
557
+
558
+ def get_knight_moves(self,r,c,moves):
559
+ peice_pinned = False
560
+
561
+ for i in range(len(self.pins)-1,-1,-1):
562
+ if self.pins[i][0] == r and self.pins[i][1]==c:
563
+ peice_pinned=True
564
+ self.pins.remove(self.pins[i])
565
+ break
566
+
567
+ if self.whiteToMove:
568
+ ur_symbol= 'w'
569
+ opp = 'b'
570
+ else:
571
+ ur_symbol= 'b'
572
+ opp = 'w'
573
+ for x,y in self.knight_directions:
574
+ new_x,new_y = r+x,c+y
575
+ if (self.is_valid_square(new_x,new_y)):
576
+ if not peice_pinned:
577
+ if self.board[new_x][new_y][0]!=ur_symbol:
578
+ moves.append(Move((r,c),(new_x,new_y),self.board))
579
+ return moves
580
+ '''
581
+ this func return the bishop moves for particular rook
582
+ '''
583
+ def get_bishop_moves(self,r,c,moves):
584
+ peice_pinned = False
585
+ pin_direction = ()
586
+ for i in range(len(self.pins)-1,-1,-1):
587
+ if self.pins[i][0] == r and self.pins[i][1]==c:
588
+ peice_pinned=True
589
+ pin_direction = (self.pins[i][2],self.pins[i][3])
590
+ self.pins.remove(self.pins[i])
591
+ break
592
+ if self.whiteToMove:
593
+ ur_symbol= 'w'
594
+ opp = 'b'
595
+ else:
596
+ ur_symbol= 'b'
597
+ opp = 'w'
598
+ for x,y in self.bishop_directions:
599
+ for i in range(1,8):
600
+ new_x,new_y = r + x*i ,c + y*i
601
+ if not self.is_valid_square(new_x,new_y):
602
+ break
603
+ else:
604
+ if not peice_pinned or pin_direction == (x,y) or pin_direction == (-x,-y):
605
+ if self.board[new_x][new_y][0]=='-':
606
+ moves.append(Move((r,c),(new_x,new_y),self.board))
607
+ elif self.board[new_x][new_y][0]==opp:
608
+ moves.append(Move((r,c),(new_x,new_y),self.board))
609
+ break
610
+ else :
611
+ break
612
+ return moves
613
+
614
+ '''
615
+ this func return the king moves for particular king
616
+ '''
617
+ def get_king_moves(self,r,c,moves):
618
+
619
+ if self.whiteToMove:
620
+ ur_symbol= 'w'
621
+ opp = 'b'
622
+ else:
623
+ ur_symbol= 'b'
624
+ opp = 'w'
625
+ for x,y in self.king_directions:
626
+ new_x,new_y = r+x,c+y
627
+ if (self.is_valid_square(new_x,new_y)):
628
+ if self.board[new_x][new_y][0]!=ur_symbol:
629
+ if ur_symbol == 'w':
630
+ self.white_king_location = (new_x,new_y)
631
+ else:
632
+ self.black_king_location = (new_x,new_y)
633
+ incheck,pins,checks = self.check_for_pins_and_checks() #check for pins and checks and if not add the move
634
+ if not incheck:
635
+ moves.append(Move((r,c),(new_x,new_y),self.board))
636
+ if ur_symbol == 'w':
637
+ self.white_king_location = (r,c)
638
+ else:
639
+ self.black_king_location = (r,c) # place king in original position
640
+ return moves
641
+ '''
642
+ this func return the queen moves for particular rook
643
+ '''
644
+ def get_queen_moves(self,r,c,moves):
645
+ return self.get_bishop_moves(r,c,moves) + self.get_rook_moves(r,c,moves)
646
+
647
+
648
+ '''
649
+ generate all castle moves
650
+
651
+ '''
652
+ def get_castle_moves(self,r,c,moves,my_color):
653
+ if self.square_under_attack(r,c):
654
+ return # cannot castle if king is in check
655
+ if (self.whiteToMove and self.current_castling_rights.wks) or (not self.whiteToMove and self.current_castling_rights.bks):
656
+ self.king_side_castle_moves(r,c,moves,my_color)
657
+ if (self.whiteToMove and self.current_castling_rights.wqs) or (not self.whiteToMove and self.current_castling_rights.bqs):
658
+ self.queen_side_castle_moves(r,c,moves,my_color)
659
+
660
+ def king_side_castle_moves(self,r,c,moves,my_color):
661
+ if c + 2 <= 7:
662
+ if self.board[r][c+1]== '--' and self.board[r][c+2]== '--':
663
+ if not self.square_under_attack(r,c+1) and not self.square_under_attack(r,c+2):
664
+ moves.append ( Move((r,c),(r,c+2),self.board,castle=True))
665
+
666
+
667
+
668
+
669
+
670
+ def queen_side_castle_moves(self,r,c,moves,my_color):
671
+ if c-3 >=0:
672
+ if self.board[r][c-1]== '--' and self.board[r][c-2]== '--' and self.board[r][c-3]== '--':
673
+ if not self.square_under_attack(r,c-1) and not self.square_under_attack(r,c-2) :
674
+ moves.append ( Move((r,c),(r,c-2),self.board,castle=True))
675
+
676
+ '''
677
+ make castling right class other wise difficult to include it in main code
678
+ '''
679
+ class Castling_Rights():
680
+ def __init__(self,wks,wqs,bks,bqs):
681
+ self.bks=bks
682
+ self.bqs=bqs
683
+ self.wks=wks
684
+ self.wqs=wqs
685
+
686
+ class Move():
687
+ ranks_to_rows = {
688
+ '1':7,'2':6,'3':5,'4':4,'5':3,'6':2,'7':1,'8':0}
689
+ rows_to_ranks = {v:k for k,v in ranks_to_rows.items()}
690
+ files_to_cols = {chr(97+i):i for i in range(8)}
691
+ cols_to_files={v:k for k,v in files_to_cols.items()}
692
+ 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
693
+ self.start_row = startsq[0]
694
+ self.start_col = startsq[1]
695
+ self.end_row = endsq[0]
696
+ self.end_col = endsq[1]
697
+
698
+ self.peice_moved = board[self.start_row][self.start_col]
699
+ self.peice_captured = board[self.end_row][self.end_col]
700
+ self.is_pawn_promotion = False
701
+ if (self.peice_moved == 'wp' and self.end_row==0) or (self.peice_moved == 'bp' and self.end_row==7):
702
+ self.is_pawn_promotion=True
703
+ self.promotion_choice =choice
704
+ self.is_empassant_move=False
705
+ if is_empassant_move:
706
+ self.is_empassant_move=True
707
+ self.peice_captured = 'wp' if self.peice_moved =='bp' else 'bp'
708
+ self.castle=castle
709
+ self.is_capture = self.peice_captured!='--'
710
+
711
+ 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
712
+ #have to tell python if two moves are equal
713
+ '''
714
+ over writing a method
715
+ other wise python check and they are two different objects
716
+ '''
717
+ def __eq__(self, value):
718
+ if isinstance(value,Move):
719
+ return value.move_id == self.move_id
720
+ return False
721
+
722
+ def get_chess_notation(self):
723
+ #make it to look move in chess notation
724
+ return self.get_rank_file(self.start_row,self.start_col) + self.get_rank_file(self.end_row,self.end_col)
725
+
726
+ def get_rank_file(self,r,c):
727
+ return self.cols_to_files[c]+ self.rows_to_ranks[r] #first column than row
728
+ def __str__(self):
729
+ #castle move
730
+ if self.castle:
731
+ return "o-o" if self.end_col==6 else 'o-o-o'
732
+ end_square = self.get_rank_file(self.end_row,self.end_col)
733
+ #pawn move
734
+ if self.peice_moved[1] == 'p':
735
+ if self.is_capture:
736
+ return self.cols_to_files[self.start_col] + 'x'+ end_square
737
+ else:
738
+ return end_square
739
+ # pawn promotion
740
+ #Nbd2 both knights can move to d2
741
+
742
+ # for check and checkmate
743
+ # peice moves
744
+ move_string = self.peice_moved[1]
745
+ if self.is_capture:
746
+ move_string+='x'
747
+ 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)}"""
748
+
749
+
750
+
751
+
752
+
753