-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathChessEngine.py
913 lines (773 loc) · 33.2 KB
/
ChessEngine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 19 08:41:31 2021
@author: Alexander Leszczynski
"""
# import numpy as np
import copy
import random
import time
class GameState:
"""
This class is responsible for storing all the information about the current state of a chess game.
It is also responsible for determining the valid moves at the current state and also keeps a move log.
"""
def __init__(self):
"""
This is the Constructor of the Gamestate class
Returns
-------
None.
"""
# board is an 6x6 1d list, each piece on the board is represented by 2 letters in the list
# The first character represents the color of the piece, "b" or "w"
# The second character represents the type of the piece "K", "Q", "R", "B", "N" or "p"
# "--" represents an empty space with no piece
self.board = ['bR', 'bB', 'bN', 'bK', 'bB', 'bR',
'bp', 'bp', 'bp', 'bp', 'bp', 'bp',
'--', '--', '--', '--', '--', '--',
'--', '--', '--', '--', '--', '--',
'wp', 'wp', 'wp', 'wp', 'wp', 'wp',
'wR', 'wB', 'wN', 'wK', 'wB', 'wR']
# for testing interesting positions
"""
self.board = ['bR', '--', '--', '--', '--', '--',
'--', '--', 'bK', '--', '--', '--',
'--', '--', '--', 'bp', '--', '--',
'--', '--', 'bB', 'wp', 'bp', '--',
'bp', '--', 'wK', '--', 'wp', '--',
'bR', '--', '--', '--', '--', '--']
"""
# to flip the board:
"""
for j in range(len(self.board)):
print('b' in self.board[j])
if 'b' in self.board[j]:
self.board[j] = self.board[j].replace('b', 'w')
elif 'w' in self.board[j]:
self.board[j] = self.board[j].replace('w', 'b')
self.board.reverse()
for j in range(len(self.board) // 6):
print(self.board[j * 6: (j*6)+5])
"""
self.moveFunctions = {'p': self.getPawnMoves, 'R': self.getRookMoves, 'N': self.getKnightMoves,
'B': self.getBishopMoves, 'K': self.getKingMoves, 'Q': self.getQueenMoves}
self.whiteToMove = True
self.dimension = 6
self.above = - self.dimension
self.under = self.dimension
self.left = -1
self.right = 1
self.moveLog = []
# self.whiteKingLocation = (5, 3)
# self.blackKingLocation = (0, 3) # to not have to scan for the king
# in case one has an experimental board:
try:
wKL = self.board.index('wK')
self.whiteKingLocation = (wKL // 6, wKL % 6)
bKL = self.board.index('bK')
self.blackKingLocation = (bKL // 6, bKL % 6)
except ValueError as e:
print('Both kings have to be present on the board!')
raise e
self.inCheck = False
self.pins = []
self.checks = []
# self.currentCastlingRight = CastleRights(False, False, False, False) # this has to be in the code when testing positions where castling is not allowed
# self.currentCastlingRight = CastleRights(True, True, True,
# True)
# again, if one has an experimental board: (one can also enter True or False if one wants to try stuff)
# the order of the arguments is: white kingside, black kingside, white queenside, black queenside
self.currentCastlingRight = CastleRights(self.whiteKingLocation == (5, 3) and self.board[5 * 6 + 5] == 'wR',
self.blackKingLocation == (0, 3) and self.board[0 * 6 + 5] == 'bR',
self.whiteKingLocation == (5, 3) and self.board[5 * 6 + 0] == 'wR',
self.blackKingLocation == (0, 3) and self.board[0 * 6 + 0] == 'bR')
self.castleRightsLog = [CastleRights(self.currentCastlingRight.wks, self.currentCastlingRight.bks,
self.currentCastlingRight.wqs, self.currentCastlingRight.bqs)]
self.checkMate = False
self.staleMate = False
self.draw = False
self.threefold = False
self.illegal_move_done = False
self.game_log = {}
def __str__(self):
s = copy.deepcopy(self.board)
r = ''
for j, ss in enumerate(s):
if j % 6 == 5:
r += ss + '\n'
else:
r += ss + ' '
return r
def makeMove(self, move):
"""
Makes the move on the board.
Parameters
----------
move : Move
Returns
-------
None.
"""
self.board[move.startRC] = "--" # Square left behind will be empty
self.board[move.endRC] = move.pieceMoved
self.moveLog.append(move) # log the move so we can undo it later
self.whiteToMove = not self.whiteToMove # swap players turn
# update kings location
if move.pieceMoved == "wK":
self.whiteKingLocation = (move.endRow, move.endCol)
if move.pieceMoved == "bK":
self.blackKingLocation = (move.endRow, move.endCol)
# pawn promotion
if move.isPawnPromotion: # auto promotion to queen
# promotedPiece = input("Promote to Q, R, B or N:")
self.board[move.endRC] = move.pieceMoved[0] + "R" # promotedPiece
# make castle move
if move.pieceMoved[1] == "K" and abs(move.endCol - move.startCol) == 2:
if move.endCol - move.startCol == 2: # kingside castle move
self.board[move.endRC - 1] = move.pieceMoved[0] + "R" # moves the rook
self.board[move.endRC] = move.pieceMoved # deletes old rook
else: # queenside castle move
self.board[move.endRC + 1] = self.board[move.endRC - 1] # moves the rook
self.board[move.endRC - 1] = "--"
# update castling rights - whenever a rook or a king moves
self.updateCastleRights(move)
self.castleRightsLog.append(CastleRights(self.currentCastlingRight.wks, self.currentCastlingRight.bks,
self.currentCastlingRight.wqs, self.currentCastlingRight.bqs))
# threefold logic
if tuple(self.board) in self.game_log:
self.game_log[tuple(self.board)] += 1
if self.game_log[tuple(self.board)] == 3:
self.threefold = True
else:
self.game_log.update({tuple(self.board): 1})
# check for draw by insufficient material
self.draw = not ('bR' in self.board or 'bB' in self.board or 'bN' in self.board or
'bp' in self.board or 'bB' in self.board or 'bR' in self.board or
'wR' in self.board or 'wB' in self.board or 'wN' in self.board or
'wp' in self.board or 'wB' in self.board or 'wR' in self.board)
def undoMove(self):
"""
Takes the last move made from the moveLog and undoes it
Returns
-------
None.
"""
if len(self.moveLog) != 0: # make sure at least one move has been made to undo
move = self.moveLog.pop()
# undo move from GameLog to avoid threefold stacking
self.game_log[tuple(self.board)] -= 1
self.board[move.startRC] = move.pieceMoved
self.board[move.endRC] = move.pieceCaptured
self.whiteToMove = not self.whiteToMove # swap players turn
# update kings location if needed
if move.pieceMoved == "wK":
self.whiteKingLocation = (move.startRow, move.startCol)
if move.pieceMoved == "bK":
self.blackKingLocation = (move.startRow, move.startCol)
# undo castling rights
self.castleRightsLog.pop() # get rid of the castle rights from the move we are undoing
castleRights = copy.deepcopy(self.castleRightsLog[-1])
self.currentCastlingRight = castleRights
# undo the castle move
if move.pieceMoved[1] == "K" and abs(move.endCol - move.startCol) == 2:
if move.endCol - move.startCol == 2: # kingside
self.board[move.endRC] = self.board[move.endRC - 1] # moves the rook
self.board[move.endRC - 1] = "--" # deletes old rook
else: # queenside
self.board[move.endRC - 1] = self.board[move.endRC + 1] # moves the rook
self.board[move.endRC + 1] = "--" # deletes old rook
# undo checkmate and Stalemate flags
self.checkMate = False
self.staleMate = False
def updateCastleRights(self, move):
"""
Update the castle rights given the move
Parameters
----------
move : Move
Is an instance of Move class
Returns
-------
None.
"""
if move.pieceMoved == "wK":
self.currentCastlingRight.wks = False
self.currentCastlingRight.wqs = False
elif move.pieceMoved == "bK":
self.currentCastlingRight.bks = False
self.currentCastlingRight.bqs = False
elif move.pieceMoved == "wR":
if move.startRow == 5:
if move.startCol == 0: # left Rook
self.currentCastlingRight.wqs = False
elif move.startCol == 5: # right Rook
self.currentCastlingRight.wks = False
elif move.pieceMoved == "bR":
if move.startRow == 0:
if move.startCol == 0: # left Rook
self.currentCastlingRight.bqs = False
elif move.startCol == 5: # right Rook
self.currentCastlingRight.bks = False
# if a rook is captured
if move.pieceCaptured == 'wR':
if move.endRow == 5:
if move.endCol == 0:
self.currentCastlingRight.wqs = False
elif move.endCol == 5:
self.currentCastlingRight.wks = False
elif move.pieceCaptured == 'bR':
if move.endRow == 0:
if move.endCol == 0:
self.currentCastlingRight.bqs = False
elif move.endCol == 5:
self.currentCastlingRight.bks = False
def getValidMoves(self):
"""
All moves considering checks
Returns
-------
list of moves
"""
moves = []
self.inCheck, self.pins, self.checks = self.checkForPinsAndChecks()
if self.whiteToMove:
kingRow = self.whiteKingLocation[0]
kingCol = self.whiteKingLocation[1]
else:
kingRow = self.blackKingLocation[0]
kingCol = self.blackKingLocation[1]
if self.inCheck:
if len(self.checks) == 1: # only 1 check, block check or move king
moves = self.getAllPossibleMoves()
# to block a check you must move a piece into one of the sqaures between the enemy piece and king
check = self.checks[0] # check info
checkRow = check[0]
checkCol = check[1]
checkRC = checkRow * 6 + checkCol
pieceChecking = self.board[checkRC] # enemy piece causing the check
validSquares = [] # squares that pieces can move to
# if knight, must be captured or move king, other pieces can be blocked
if pieceChecking[1] == "N":
validSquares = [(checkRow, checkCol)]
else:
for i in range(1, 6):
validSquare = (kingRow + check[2] * i,
kingCol + check[3] * i) # check[2] and check[3] are the check directions
validSquares.append(validSquare)
if validSquare[0] == checkRow and validSquare[
1] == checkCol: # you arrived at the checking piece
break
# get rid of any moves that don't block check or move king
for i in range(len(moves) - 1, -1, -1): # going backwards through the moves
if moves[i].pieceMoved[1] != "K": # move doesn't move king so it must block or capture
if not (moves[i].endRow, moves[i].endCol) in validSquares: # move doesnt block or capture piece
moves.remove(moves[i])
else: # double check, king has to move
self.getKingMoves(kingRow, kingCol, moves)
if len(moves) == 0:
self.checkMate = True
else: # not in check therefore all moves are fine
moves = self.getAllPossibleMoves()
self.getCastleMoves(kingRow, kingCol, moves)
if len(moves) == 0 and not self.checkMate:
self.staleMate = True
random.shuffle(moves)
return moves
def squareUnderAttack(self, r, c): # nötig für castle moves
"""
determines if enemy can attack the square (r, c)
Parameters
----------
r : int
Row
c : int
Column
Returns
-------
Bool
True if square is under attack
"""
self.whiteToMove = not self.whiteToMove # switch opponents turn
oppMoves = self.getAllPossibleMoves()
self.whiteToMove = not self.whiteToMove # switch turns back
if (self.whiteToMove and r > 0) or (not self.whiteToMove and r < self.dimension - 1):
r_offset = -1 if self.whiteToMove else 1
enemy_color = 'b' if self.whiteToMove else 'w'
column_offsets = []
if c < self.dimension - 1: column_offsets.append(1)
if c > 0: column_offsets.append(-1)
if any([self.board[self.dimension * (r + r_offset) + (c + c_offset)] == enemy_color + 'p' for c_offset in column_offsets]):
return True
for move in oppMoves:
if move.endRow == r and move.endCol == c: # square is under attack
return True
return False
def checkForPinsAndChecks(self):
# TODO: dimension dependent.
"""
checks for Pins and checks and returns all pins and checks
Returns
-------
Bool,List,List
if the Player is in check, list of pins and list of checks
"""
pins = [] # squares where the allied pinned piece is and the direction it is pinned from
checks = [] # squares where enemy is applying check
inCheck = False
if self.whiteToMove:
enemyColor = "b"
allyColor = "w"
startRow = self.whiteKingLocation[0]
startCol = self.whiteKingLocation[1]
else:
enemyColor = "w"
allyColor = "b"
startRow = self.blackKingLocation[0]
startCol = self.blackKingLocation[1]
# check outward from king for pins and checks, keep track of pins
directions = ((-1, 0), (0, -1), (1, 0), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1))
for j in range(len(directions)):
d = directions[j]
possiblePin = () # reset possible pins
for i in range(1, self.dimension):
endRow = startRow + d[0] * i
endCol = startCol + d[1] * i
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension:
endPiece = self.board[endRC]
if endPiece[0] == allyColor and endPiece[1] != "K":
if possiblePin == (): # 1st allied piece could be pinned
possiblePin = (endRow, endCol, d[0], d[1])
else: # 2nd allied piece, so no pin or check possible for this direction
break
elif endPiece[0] == enemyColor:
typus = endPiece[1]
# 4 possibilities in this condition
# 1.) orthogonally away from king and piece is a rook
# 2.) diagonally away from king and piece is a bishop
# 3.) 1 sqare away diagonally from king and piece is a pawn
# 4.) any direction 1 square away and piece is a king (to prevent Kings checking each other)
if (0 <= j <= 3 and typus == "R") or \
(4 <= j <= 7 and typus == "B") or \
(i == 1 and typus == "p" and (
(enemyColor == "w" and 6 <= j <= 7) or (enemyColor == "b" and 4 <= j <= 5))) or \
(typus == "Q") or (i == 1 and typus == "K"):
if possiblePin == (): # no piece blocking, so check
inCheck = True
checks.append((endRow, endCol, d[0], d[1]))
break
else: # piece blocking so pin
pins.append(possiblePin)
break
else: # enemy piece not applying check
break
else: # off board
break
# check for knight checks
knightMoves = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1))
for m in knightMoves:
endRow = startRow + m[0]
endCol = startCol + m[1]
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension:
endPiece = self.board[endRC]
if endPiece[0] == enemyColor and endPiece[1] == "N": # enemy knight attacking king
inCheck = True
checks.append((endRow, endCol, m[0], m[1]))
return inCheck, pins, checks
def getAllPossibleMoves(self):
"""
All moves without considering checks
Returns
-------
list of moves
"""
# moves = [Move((4,3),(3,3), self.board) ] #just to test if it works so far Many weird bugs because of this was left in
moves = []
for r in range(6): # number of rows on the board
for c in range(6): # number of columns in the row r
rc = r * 6 + c
turn = self.board[rc][0]
if (turn == "w" and self.whiteToMove) or (
turn == "b" and not self.whiteToMove): # here the "or" used to be an "and" which i think was wrong
piece = self.board[rc][1]
self.moveFunctions[piece](r, c, moves) # calls the appropriate move function based on piece type
return moves
# TODO: this method is dimension dependent.
def getPawnMoves(self, r, c, moves):
"""
get all the pawn moves of the pawn located at row r column c and add them to the moves list
Parameters
----------
r : int
Row of the pawn
c : int
Column of the pawn
moves : list
list of possible moves
Returns
-------
None.
"""
rc = r * 6 + c
piecePinned = False
pinDirection = ()
for i in range(len(self.pins) - 1, -1, -1):
if self.pins[i][0] == r and self.pins[i][1] == c:
piecePinned = True
pinDirection = (self.pins[i][2], self.pins[i][3])
self.pins.remove(self.pins[i])
break
if self.whiteToMove:
moveAmount = -1
moveAmountl = -6
enemyColor = "b"
kingRow, kingCol = self.whiteKingLocation
else:
moveAmount = 1
moveAmountl = 6
enemyColor = "w"
kingRow, kingCol = self.blackKingLocation
if self.board[rc + moveAmountl] == "--": # 1square move
if not piecePinned or pinDirection == (moveAmount, 0):
moves.append(Move((r, c), (r + moveAmount, c), self.board))
if c - 1 >= 0: # capture to the left
if not piecePinned or pinDirection == (moveAmount, -1):
if self.board[rc + moveAmountl - 1][0] == enemyColor:
moves.append(Move((r, c), (r + moveAmount, c - 1), self.board))
if c + 1 <= 5: # capture to the right
if not piecePinned or pinDirection == (moveAmount, 1):
if self.board[rc + moveAmountl + 1][0] == enemyColor:
moves.append(Move((r, c), (r + moveAmount, c + 1), self.board))
def getRookMoves(self, r, c, moves):
"""
get all the Rook moves of the Rook located at row r column c and add them to the moves list
Parameters
----------
r : int
Row of the Rook
c : int
Column of the Rook
moves : list
list of possible moves
Returns
-------
None.
"""
piecePinned = False
pinDirection = ()
for i in range(len(self.pins) - 1, -1, -1):
if self.pins[i][0] == r and self.pins[i][1] == c:
piecePinned = True
pinDirection = (self.pins[i][2], self.pins[i][3])
self.pins.remove(self.pins[i])
break
directions = ((-1, 0), (0, -1), (1, 0), (0, 1))
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1, 6):
endRow = r + d[0] * i
endCol = c + d[1] * i
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension: # in the board dimensions
if not piecePinned or pinDirection == d or pinDirection == (-d[0], -d[1]):
endPiece = self.board[endRC]
if endPiece == "--": # empty target square
moves.append(Move((r, c), (endRow, endCol), self.board))
elif endPiece[0] == enemyColor: # enemy piece on targetsquare
moves.append(Move((r, c), (endRow, endCol), self.board))
break # ends inner for loop
else:
break
else: # off board
break
def getKnightMoves(self, r, c, moves):
"""
get all the Knight moves of the Knight located at row r column c and add them to the moves list
A Horsie has 8 possible Jumps at best, all 8 are getting checked in this function
Parameters
----------
r : int
Row of the Knight
c : int
Column of the Knight
moves : list
list of possible moves
Returns
-------
None.
"""
piecePinned = False
for i in range(len(self.pins) - 1, -1, -1):
if self.pins[i][0] == r and self.pins[i][1] == c:
piecePinned = True
self.pins.remove(self.pins[i])
break
knightMoves = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1))
allyColor = "w" if self.whiteToMove else "b"
for m in knightMoves:
endRow = r + m[0]
endCol = c + m[1]
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension: # in the board dimensions
if not piecePinned:
endPiece = self.board[endRC]
if endPiece[0] != allyColor: # not a piece of own color
moves.append(Move((r, c), (endRow, endCol), self.board))
def getBishopMoves(self, r, c, moves):
"""
get all the Bishop moves of the Bishop located at row r column c and add them to the moves list
Parameters
----------
r : int
Row of the Bishop
c : int
Column of the Bishop
moves : list
list of possible moves
Returns
-------
None.
"""
piecePinned = False
pinDirection = ()
for i in range(len(self.pins) - 1, -1, -1):
if self.pins[i][0] == r and self.pins[i][1] == c:
piecePinned = True
pinDirection = (self.pins[i][2], self.pins[i][3])
self.pins.remove(self.pins[i])
break
directions = ((-1, -1), (-1, 1), (1, -1), (1, 1))
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1, 8): # max distance a bishop can move is 7 squares
endRow = r + d[0] * i
endCol = c + d[1] * i
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension: # in the board dimensions
if not piecePinned or pinDirection == d or pinDirection == (-d[0], -d[1]):
endPiece = self.board[endRC]
if endPiece == "--": # empty target square
moves.append(Move((r, c), (endRow, endCol), self.board))
elif endPiece[0] == enemyColor: # enemy piece on targetsquare
moves.append(Move((r, c), (endRow, endCol), self.board))
break # ends inner for loop
else:
break
else: # off board
break
def getQueenMoves(self, r, c, moves):
"""
get all the Queen moves of the Queen located at row r column c and add them to the moves list
Parameters
----------
r : int
Row of the King
c : int
Column of the King
moves : list
list of possible moves
Returns
-------
None.
"""
self.getRookMoves(r, c, moves)
self.getBishopMoves(r, c, moves)
def getKingMoves(self, r, c, moves):
"""
get all the King moves of the King located at row r column c and add them to the moves list
Parameters
----------
r : int
Row of the King
c : int
Column of the King
moves : list
list of possible moves
Returns
-------
None.
"""
rowMoves = (-1, -1, -1, 0, 0, 1, 1, 1)
colMoves = (-1, 0, 1, -1, 1, -1, 0, 1)
allyColor = "w" if self.whiteToMove else "b"
for i in range(8):
endRow = r + rowMoves[i]
endCol = c + colMoves[i]
endRC = endRow * 6 + endCol
if 0 <= endRow < self.dimension and 0 <= endCol < self.dimension: # in the board dimensions
endPiece = self.board[endRC]
if endPiece[0] != allyColor: # not a piece of own color
# place the king on end square and check for checks
if allyColor == "w":
self.whiteKingLocation = (endRow, endCol)
else:
self.blackKingLocation = (endRow, endCol)
inCheck, pins, checks = self.checkForPinsAndChecks()
if not inCheck:
moves.append(Move((r, c), (endRow, endCol), self.board))
# place king back on original location
if allyColor == "w":
self.whiteKingLocation = (r, c)
else:
self.blackKingLocation = (r, c)
def getCastleMoves(self, r, c, moves):
"""
Generate all valid castle moves for the king at (r,c) and add them to the list of moves
Returns
-------
None.
"""
if (self.whiteToMove and self.currentCastlingRight.wks and self.whiteKingLocation[1] == 3) or \
(not self.whiteToMove and self.currentCastlingRight.bks and self.blackKingLocation[1] == 3):
self.getKingsideCastleMoves(r, c, moves)
if (self.whiteToMove and self.currentCastlingRight.wqs) or (
not self.whiteToMove and self.currentCastlingRight.bqs):
self.getQueensideCastleMoves(r, c, moves)
def getKingsideCastleMoves(self, r, c, moves):
"""
adds valid kingside castling moves if there are any
Parameters
----------
r : int
Row
c : int
Column
moves : list
list of valid moves
Returns
-------
None.
"""
rc = r * 6 + c
if self.board[rc + 1] == "--":
if not self.squareUnderAttack(r, c + 1):
moves.append(Move((r, c), (r, c + 2), self.board))
def getQueensideCastleMoves(self, r, c, moves):
"""
adds valid queenside castling moves if there are any
Parameters
----------
r : int
Row
c : int
Column
moves : list
list of valid moves
Returns
-------
None.
"""
rc = r * 6 + c
if self.board[rc - 1] == "--" and self.board[rc - 2] == "--":
if not self.squareUnderAttack(r, c - 1) and not self.squareUnderAttack(r, c - 2):
moves.append(Move((r, c), (r, c - 2), self.board))
class CastleRights:
"""
This class creates an object that holds the castle Rights
"""
def __init__(self, wks, bks, wqs, bqs):
self.wks = wks
self.bks = bks
self.wqs = wqs
self.bqs = bqs
class Move():
"""
This class creates a move object with all the information about a move
"""
# maps keys to values
# key : value
ranksToRows = {"1": 5, "2": 4, "3": 3, "4": 2,
"5": 1, "6": 0}
rowsToRanks = {v: k for k, v in ranksToRows.items()}
filesToCols = {"a": 0, "b": 1, "c": 2, "d": 3,
"e": 4, "f": 5}
colsToFiles = {v: k for k, v in filesToCols.items()}
def __init__(self, startSq, endSq, board):
self.startRow = startSq[0]
self.startCol = startSq[1]
self.startRC = self.startRow * 6 + self.startCol
self.endRow = endSq[0]
self.endCol = endSq[1]
self.endRC = self.endRow * 6 + self.endCol
self.pieceMoved = board[self.startRC]
self.pieceCaptured = board[self.endRC]
# check if pieceMoved is a valid figure
# if self.pieceMoved == "--":
# raise ValueError('Tried moving a piece that is not on the board.')
self.moveID = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol
# pawn promotion
self.isPawnPromotion = (self.pieceMoved == "wp" and self.endRow == 0) or (
self.pieceMoved == "bp" and self.endRow == 5)
# castle moves
self.isCastleMove = self.pieceMoved[1] == "K" and abs(self.endCol - self.startCol) == 2
# captures
self.isCapture = self.pieceCaptured != "--"
# self.created_timestamp = ' ' + str(time.time())
self.created_timestamp = ''
def __eq__(self, other):
"""
Overriding the equals method
Parameters
----------
other : Move
another Move
Returns
-------
Boolean
True when its the same move
"""
return self.moveID == other.moveID
def getChessNotation(self):
"""
Gets the chess notation of a move
Returns
-------
String
Chess notation
"""
# To make this like real chess notations
return self.getRankFile(self.startRow, self.startCol) + self.getRankFile(self.endRow, self.endCol)
def getRankFile(self, r, c):
"""
Converts rows and columns to ranks and files
Parameters
----------
r : int
row
c : int
column
Returns
-------
Sring
Chess notation
"""
return self.colsToFiles[c] + self.rowsToRanks[r]
# overriding the str() function
def __str__(self):
"""
Overriding string method to make it look more like real chess notation
Returns
-------
String
Chess notation
"""
# castle move
if self.isCastleMove:
return "O-O" + self.created_timestamp if self.endCol == 5 else "O-O-O"
endSquare = self.getRankFile(self.endRow, self.endCol)
# pawn moves
if self.pieceMoved[1] == "p":
if self.isCapture:
return self.colsToFiles[self.startCol] + "x" + endSquare + self.created_timestamp
else:
return endSquare + self.created_timestamp
# pawn promotions
# two of the same type of piece moving to a square, Nbd2 if both Knights can move to d2
# also adding + for check move and # for checkmate move
# piece moves
moveString = self.pieceMoved[1]
if self.isCapture:
moveString += "x"
return moveString + endSquare + self.created_timestamp