-
Notifications
You must be signed in to change notification settings - Fork 0
/
ChessEngine.py
284 lines (229 loc) · 10.5 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
"""This class is responsible for storing all the information about the current state of a chess game. It will also be responsible
for determining the valid moves at the current state. It will also keep a move log.
"""
class GameState():
def __init__(self):
#8x8 2D list, each element of the list has 2 characters
#the first character represents the color 'b' or 'w'
#the second character represents the piece 'K','Q','B','N','R','p'
# "--" represents a blank space
self.board = [
["bR","bN","bB","bQ","bK","bB","bN","bR"],
["bp","bp","bp","bp","bp","bp","bp","bp",],
["--","--","--","--","--","--","--","--",],
["--","--","--","--","--","--","--","--",],
["--","--","--","--","--","--","--","--",],
["--","--","--","--","--","--","--","--",],
["wp","wp","wp","wp","wp","wp","wp","wp",],
["wR","wN","wB","wQ","wK","wB","wN","wR"],
]#subsitute to numpy arrays to gain efficiency, list of lists from white perspective
self.whiteToMove = True
self.movelog = []
self.moveFunctions={'p':self.getPawnMoves, 'R':self.getRookMoves, 'N':self.getKnightMoves, 'B':self.getBishopMoves,'Q':self.getQueenMoves, 'K':self.getKingMoves}
#track the king for pinning
self.whiteKingLocation = (7,4)
self.blackKingLocation = (0,4)
self.checkMate = False
self.staleMate = False
#Takes a move as a parameter and executes it (separate function for castling, pawn promotion or en peasant)
def makeMove(self, move):
self.board[move.startRow][move.startCol] = "--"
self.board[move.endRow][move.endCol] = move.pieceMoved
self.movelog.append(move)#log the move so we can undo it later
self.whiteToMove = not self.whiteToMove #swapp players
#update king location
if move.pieceMoved == 'wK':
self.whiteKingLocation = (move.endRow, move.endCol)
elif move.pieceMoved == 'bK':
self.blackKingLocation = (move.endRow, move.endCol)
"""Undo the last move made"""
def undoMove(self):
if len(self.movelog) != 0: #make sure that there is a move to undo
move = self.movelog.pop()
self.board[move.startRow][move.startCol] = move.pieceMoved
self.board[move.endRow][move.endCol] = move.pieceCaptured#place new piece into the board
self.whiteToMove = not self.whiteToMove #switch turns back
#update king position
if move.pieceMoved == 'wK':
self.whiteKingLocation = (move.startRow, move.startCol)
elif move.pieceMoved == 'bK':
self.blackKingLocation = (move.startRow, move.startCol)
""""def getValidMoves(self):
return self.getAllPossibleMoves()"""
"""All moves considering checks"""
"""We will make the distinction between all possible moves and all valid moves. So the basic algorithm for our getValidMoves() method will be this:
-get all possible moves
- for each possible moves, check to see if it is a valid move by doing the following:
- make the move
- generate all possible moves for the opposing Player
- see if any of the moves attack your king
- if your king is safe, it is a valid move and add it to a list
- retorn the list of valid moves only
"""
def getValidMoves(self):
#1) generate all possible moves
moves = self.getAllPossibleMoves()
#2) for each move, make the move
for i in range(len(moves)-1, -1,-1):#when removing from a list go backwards through that list
self.makeMove(moves[i])#this function switchs the turns
#3) generate all oponent's move
#4) for each of your opponent's moves, see if they attack your king
self.whiteToMove = not self.whiteToMove
if self.inCheck():#if after the move our king is still in check, that is not a valid move
moves.remove(moves[i])#5) if they do attack your king, not a valid move
self.whiteToMove = not self.whiteToMove
self.undoMove()
if len(moves)==0: #either checkmate or stalemate
if self.inCheck():
self.checkMate = True
else:
self.staleMate = True
else:
self.checkmate = False
self.staleMate = False
return moves
"""Determine if the current player is in check"""
def inCheck(self):
if self.whiteToMove:
return self.squareUnderAttack(self.whiteKingLocation[0], self.whiteKingLocation[1])
else:
return self.squareUnderAttack(self.blackKingLocation[0],self.blackKingLocation[1])
"""Determine if the enemy can attack the square row, col"""
def squareUnderAttack(self,row,col):
self.whiteToMove = not self.whiteToMove #switch to opponent's turn
oppMoves = self.getAllPossibleMoves()
self.whiteToMove = not self.whiteToMove#switch turn's back
for move in oppMoves:#check if any move is attacking my location
if move.endRow == row and move.endCol == col: #square under attack
self.whiteToMove = not self.whiteToMove #switch turn back
return True
return False
"""All moves without considering checks, we will consider all possible moves
despite the king being or not being in check, after we have generated
all valid moves, we will classify them with getValidMoves to see which ones the player
can actually do"""
def getAllPossibleMoves(self):
moves = []
for row in range(len(self.board)):#number of rows
for col in range(len(self.board[row])):#number of cols in given row
turn = self.board[row][col][0]
if (turn == 'w' and self.whiteToMove) or (turn=='b' and not self.whiteToMove):
piece = self.board[row][col][1]
self.moveFunctions[piece](row,col,moves)#call the function depending on the piece
return moves
"""Get all the pawn moves for the pawn located at row, col and add these moves to the list"""
"""White Pawns starts at row 6, Black pawn stars at row 2"""
def getPawnMoves(self,row,col,moves):
if self.whiteToMove: #white pawn move
if self.board[row-1][col]=='--':#square pawn advance
moves.append(Move((row,col),(row-1,col),self.board))
if row==6 and self.board[row-2][col] == '--': #2 square pawn advance
moves.append(Move((row,col),(row-2,col),self.board))
if col-1 >= 0:#capture to the left
if self.board[row-1][col-1][0] == 'b': #enemy piece to capture
moves.append(Move((row,col),(row-1,col-1),self.board))
if col+1 <= 7:#capture to the right
if self.board[row-1][col+1][0] == 'b': #enemy piece to capture
moves.append(Move((row,col),(row-1,col+1),self.board))
else: #black pawn move
if self.board[row+1][col]=='--':#square pawn advance
moves.append(Move((row,col),(row+1,col),self.board))
if row==1 and self.board[row+2][col] == '--': #2 square pawn advance
moves.append(Move((row,col),(row+2,col),self.board))
if col-1 >= 0:#capture to the right
if self.board[row+1][col-1][0] == 'w': #enemy piece to capture
moves.append(Move((row,col),(row+1,col-1),self.board))
if col+1 <= 7:#capture to the left
if self.board[row+1][col+1][0] == 'w': #enemy piece to capture
moves.append(Move((row,col),(row+1,col+1),self.board))
"""Get all the pawn moves for the rook located at row, col and add these moves to the list"""
def getRookMoves(self,row,col,moves):
#pass
directions = ((-1,0),(0,-1),(1,0),(0,1)) #up,left,down,right
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1,8):
endRow = row + d[0]*i
endCol = col + d[1]*i
if 0<= endRow<8 and 0<=endCol <8: #on board
endPiece = self.board[endRow][endCol]
if endPiece == '--': #empty space
moves.append(Move((row,col),(endRow,endCol),self.board))
elif endPiece[0] == enemyColor: #enemy piece valid
moves.append(Move((row,col),(endRow,endCol),self.board))
break#otherwhise i could jump pieces
else: #friendly piece invalid
break
else:#off board
break
def getKnightMoves(self,row,col,moves):
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 = row + m[0]
endCol = col + m[1]
if 0 <= endRow <8 and 0<=endCol <8:
endPiece = self.board[endRow][endCol]
if endPiece[0] != allyColor: #not an ally piece (empty or enemy piece)
moves.append(Move((row,col),(endRow,endCol),self.board))
def getBishopMoves(self,row,col,moves):
directions = ((-1,-1),(-1,1),(1,-1),(1,1))#4 diagonals
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1,8):
endRow = row + d[0]*i
endCol = col + d[1]*i
if 0<= endRow<8 and 0<=endCol <8: #on board
endPiece = self.board[endRow][endCol]
if endPiece == '--': #empty space
moves.append(Move((row,col),(endRow,endCol),self.board))
elif endPiece[0] == enemyColor: #enemy piece valid
moves.append(Move((row,col),(endRow,endCol),self.board))
break#otherwhise i could jump pieces
else: #friendly piece invalid
break
else:#off board
break
def getQueenMoves(self,row,col,moves):
self.getRookMoves(row,col,moves)
self.getBishopMoves(row,col,moves)
def getKingMoves(self,row,col,moves):
kingMoves = ((-1,-1),(-1,0),(-1,1),(0,1),(0,-1),(1,-1),(1,0),(1,1))
allyColor = "w" if self.whiteToMove else "b"
for i in range(8):
endRow = row + kingMoves[i][0]
endCol = col + kingMoves[i][1]
if 0<= endRow <8 and 0<= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece[0] != allyColor: #not an ally piece (empty or enemy piece)
moves.append(Move((row,col),(endRow,endCol),self.board))
class Move():
#maps key to values
# key: value
rankstoRows = {"1":7,"2":6, "3":5, "4":4, "5":3, "6":2, "7":1, "8":0}
rowstoRanks = {v: k for k, v in rankstoRows.items()}#reverse original dictionary
filestoCols = {"a":0,"b":1, "c":2,"d":3,"e":4,"f":5,"g":6,"h":7}
colstoFiles = {v: k for k, v in filestoCols.items()}
def __init__(self,startSq, endSq, board):
self.startRow = startSq[0]
self.startCol = startSq[1]
self.endRow = endSq[0]
self.endCol = endSq[1]
self.pieceMoved = board[self.startRow][self.startCol]
self.pieceCaptured = board[self.endRow][self.endCol]
self.moveID = self.startRow*1000 + self.startCol*100 + self.endRow*10 + self.endCol #unique move ID
#print(self.moveID)
"""
Override the equals method
"""
def __eq__(self,other):
if isinstance(other,Move):
return self.moveID == other.moveID
return False
def getChessNotation(self):
"""
you can add to make this like real chess notation
"""
return self.getRankFile(self.startRow, self.startCol) + self.getRankFile(self.endRow, self.endCol)
def getRankFile(self, row,col):
return self.colstoFiles[col] + self.rowstoRanks[row]