├── README.md
├── chess
├── __init__.py
├── chessAi.py
├── engine.py
└── main.py
├── images
├── bB.png
├── bK.png
├── bN.png
├── bQ.png
├── bR.png
├── bp.png
├── wB.png
├── wK.png
├── wN.png
├── wQ.png
├── wR.png
└── wp.png
├── images1
├── bB.png
├── bK.png
├── bN.png
├── bQ.png
├── bR.png
├── bp.png
├── wB.png
├── wK.png
├── wN.png
├── wQ.png
├── wR.png
└── wp.png
├── requirements.txt
└── sounds
├── capture.mp3
├── move-sound.mp3
└── promote.mp3
/README.md:
--------------------------------------------------------------------------------
1 | # Chess
2 |
3 | A simple chess game implemented in Python using the Pygame library.
4 | It features a basic AI that calculates the optimal move by peeking DEPTH moves ahead. The AI assesses positions and scores only.
5 |
6 | 
7 |
8 | ## Introduction
9 |
10 | This is a basic implementation of a chess game with a graphical user interface. The game allows two players to make moves on a standard chessboard. Additionally, there is an AI opponent that uses negamax algorithm, alpha beta pruining for move selection.
11 |
12 | ## Features
13 |
14 | - **Graphical User Interface:**
15 | - The game features a user-friendly graphical interface developed using the Pygame library.
16 |
17 | - **Two-player Mode:**
18 | - Play against a friend in human vs. human gameplay. Enjoy the classic chess experience with two human players.
19 |
20 | - **AI Opponent:**
21 | - Challenge yourself against an AI opponent equipped with the negamax algorithm and alpha-beta pruning. The AI has options to make valid moves, providing a single-player chess experience.
22 |
23 | - **Checkmate, Stalemate, and Legal Moves:**
24 | - The game checks for conditions such as checkmate, stalemate, and legal moves, ensuring a fair and rule-compliant gameplay experience.
25 |
26 | - **Advanced Chess Mechanics:**
27 | - Supports advanced chess mechanics, including pawn promotion, en passant, and castling for a more strategic and engaging experience.
28 |
29 | - **Undo and Reset Board:**
30 | - Press Z for undo, R for reset
31 |
32 | - **Variety of Chess Boards:**
33 | - Enjoy playing on different chess board colors, adding a personalized touch to your gaming experience.
34 |
35 | - **Immersive Sounds and Images:**
36 | - Enhance your gaming experience with multiple piece move or capture sounds.
37 |
38 | ## En Pasant
39 |
40 | In chess, the en passant rule allows a pawn to capture an opponent's pawn that has moved two squares forward from its starting position. The capturing pawn moves to the square immediately beyond the captured pawn. Here's how it works:
41 |
42 | 1. **Initial Position:**:
43 |
44 |
45 |
46 | 2. **Opponent's Move:**
47 |
48 |
49 |
50 | 3. **En Passant Capture:**
51 |
52 |
53 |
54 | ## Pawn Promotion:
55 |
56 | In chess, pawn promotion occurs when a pawn reaches the eighth rank. The pawn can be promoted to any other chess piece (except a king). Here's how it works:
57 |
58 | 1. **Reach the Eighth Rank**:
59 |
60 |
61 |
62 | 2. **Select the Promotion Piece**:
63 |
64 |
65 |
66 | ## How to Play
67 |
68 | 1. Clone the repository to your local machine.
69 |
70 | 2. Install the required dependencies.
71 |
72 | ```bash
73 | pip install pygame
74 |
75 | 3. Run the `main.py` script to start the game.
76 |
77 | ```bash
78 | python main.py
79 |
80 | **Using Visual Studio Code:**
81 |
82 | - Open Visual Studio Code.
83 |
84 | - Click on "File" -> "Open Folder" and select the cloned directory.
85 |
86 | - Open the integrated terminal in Visual Studio Code.
87 |
88 | - Run the following command to start the game:
89 |
90 | ```python
91 | python main.py
92 |
93 | ## Controls
94 |
95 | The controls for the chess game are designed to be intuitive and user-friendly.
96 |
97 | - **Selecting a Piece:**
98 | - Click on the chess piece you want to move. The selected piece will be highlighted to indicate that it's ready for a move.
99 | - If AI is thinking you can click on AI piece to see all possible moves.
100 |
101 | - **Moving a Piece:**
102 | - After selecting a piece, the legal moves for that piece will be highlighted on the chessboard.
103 | - Click on one of the highlighted squares to move the selected piece to that position.
104 |
105 | The game will automatically handle the rules of chess, including legal moves, capturing opponent pieces, pawn promotion, en passant, and castling.
106 |
107 | ## Installation
108 |
109 | 1. Clone the repository:
110 |
111 | ```bash
112 | git clone https://github.com/anuragjain-git/chess-bot.git
113 |
114 | 2. Navigate to the project directory:
115 |
116 | ```bash
117 | cd chess
118 |
119 | 3. Install dependencies:
120 |
121 | ```bash
122 | pip install -r requirements.txt
123 |
124 | 4. Run the game:
125 |
126 | ```bash
127 | python main.py
128 |
129 | ## Settings for AI
130 |
131 | ### Human vs Human Gameplay:
132 |
133 | If you want to play a game where both players are human:
134 |
135 | - Open the `main.py` file and modify the following lines:
136 |
137 | ```python
138 | SET_WHITE_AS_BOT = False
139 | SET_BLACK_AS_BOT = False
140 |
141 | - To flip the board, go to `engine.py` and set:
142 |
143 | ```python
144 | self.playerWantsToPlayAsBlack = False
145 |
146 | ### AI Gameplay:
147 |
148 | If you want to play a game with the AI:
149 |
150 | - Open the `main.py` file and modify the following lines:
151 |
152 | ```python
153 | SET_WHITE_AS_BOT = True
154 | SET_BLACK_AS_BOT = False
155 |
156 | - To flip the board, go to `engine.py` and set:
157 |
158 | ```python
159 | self.playerWantsToPlayAsBlack = False
160 |
161 | ### DEPTH:
162 |
163 | The `DEPTH` setting determines how many moves ahead the AI will consider during its search for the best move. A higher depth generally results in a stronger AI, but it also requires more computational resources.
164 |
165 | This emphasizes the recommendation to keep the `DEPTH` value at 4 or lower for a balance between AI strength and computational efficiency.
166 |
167 | If you want to modify DEPTH:
168 |
169 | - Open the `chessAi.py` file and modify the following lines:
170 |
171 | ```python
172 | DEPTH = 3
173 |
174 | ### Acknowledgments:
175 |
176 | Special thanks to the Pygame library for providing a straightforward and effective means to develop graphical applications in Python. The ease of use and versatility of Pygame greatly contributed to the creation of this chess engine.
177 |
178 | **Contributions and Feedback:**
179 | This project is open to contributions from the community. If you have ideas, want to report issues, or suggest improvements, feel free to contribute on GitHub. Your input is valuable in enhancing the overall quality and functionality of this chess engine.
180 |
181 |
--------------------------------------------------------------------------------
/chess/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/chess/__init__.py
--------------------------------------------------------------------------------
/chess/chessAi.py:
--------------------------------------------------------------------------------
1 | '''
2 | Keep DEPTH <= 4 for AI to run smoothly.
3 |
4 | DEPTH means the fot will looks depth moves ahead and calculate the best possible move based on PIECE-CAPTURE-SCORE AND PIECE-POSITION SCORE :
5 | DEPTH = 4
6 | '''
7 |
8 |
9 | '''
10 |
11 | WAYS TO IMPROVE AI AND MAKE AI FASTER
12 |
13 | 1) Create a database for initial ai moves/ book openings
14 | 2) AI find possible moves for all the piece after each move, if one piece is moved possible moves for other piece would be same no need to find again
15 | In this case new possible move would be :
16 | i) if any piece could move to the starting location of piece moved
17 | ii) if the piece moved to (x, y) position check if it blocked any piece to move to that location
18 | 3) no need to evaluate all the position again and again use zobrus hashing to save good position and depth
19 | 4) if [ black moved x, white move a, black moved y, white move b ] is sometime same as:
20 | [ black moved y, white move a, black moved x, white move b ]
21 | [ black moved x, white move b, black moved y, white move a ]
22 | [ black moved y, white move b, black moved y, white move a ]
23 | 5) Teach theories to AI, like some time it is better to capture threat than to move a pawn or take back our piece to previous position rather than attacking
24 |
25 |
26 | '''
27 |
28 |
29 | import random
30 | pieceScore = {"K": 0, "Q": 9, "R": 5, "B": 3, "N": 3, "p": 1}
31 |
32 | knightScores = [[1, 1, 1, 1, 1, 1, 1, 1],
33 | [1, 2, 2, 2, 2, 2, 2, 1],
34 | [1, 2, 3, 3, 3, 3, 2, 1],
35 | [1, 2, 3, 4, 4, 3, 2, 1],
36 | [1, 2, 3, 4, 4, 3, 2, 1],
37 | [1, 2, 3, 3, 3, 3, 2, 1],
38 | [1, 2, 2, 2, 2, 2, 2, 1],
39 | [1, 1, 1, 1, 1, 1, 1, 1]]
40 |
41 | bishopScores = [[4, 3, 2, 1, 1, 2, 3, 4],
42 | [3, 4, 3, 2, 2, 3, 4, 3],
43 | [2, 3, 4, 3, 3, 4, 3, 2],
44 | [1, 2, 3, 4, 4, 3, 2, 1],
45 | [1, 2, 3, 4, 4, 3, 2, 1],
46 | [2, 3, 4, 3, 3, 4, 3, 2],
47 | [3, 4, 3, 2, 2, 3, 4, 3],
48 | [4, 3, 2, 1, 1, 2, 3, 4]]
49 |
50 | queenScores = [[1, 1, 1, 3, 1, 1, 1, 1],
51 | [1, 2, 3, 3, 3, 1, 1, 1],
52 | [1, 4, 3, 3, 3, 4, 2, 1],
53 | [1, 2, 3, 3, 3, 2, 2, 1],
54 | [1, 2, 3, 3, 3, 2, 2, 1],
55 | [1, 4, 3, 3, 3, 4, 2, 1],
56 | [1, 1, 2, 3, 3, 1, 1, 1],
57 | [1, 1, 1, 3, 1, 1, 1, 1]]
58 |
59 | rookScores = [[4, 3, 4, 4, 4, 4, 3, 4],
60 | [4, 4, 4, 4, 4, 4, 4, 4],
61 | [1, 1, 2, 3, 3, 2, 1, 1],
62 | [1, 2, 3, 4, 4, 3, 2, 1],
63 | [1, 2, 3, 4, 4, 3, 2, 1],
64 | [1, 1, 2, 2, 2, 2, 1, 1],
65 | [4, 4, 4, 4, 4, 4, 4, 4],
66 | [4, 3, 2, 1, 1, 2, 3, 4]]
67 |
68 | whitePawnScores = [[8, 8, 8, 8, 8, 8, 8, 8],
69 | [8, 8, 8, 8, 8, 8, 8, 8],
70 | [5, 6, 6, 7, 7, 6, 6, 5],
71 | [2, 3, 3, 5, 5, 3, 3, 2],
72 | [1, 2, 3, 4, 4, 3, 2, 1],
73 | [1, 1, 2, 3, 3, 2, 1, 1],
74 | [1, 1, 1, 0, 0, 1, 1, 1],
75 | [0, 0, 0, 0, 0, 0, 0, 0]]
76 |
77 | blackPawnScores = [[0, 0, 0, 0, 0, 0, 0, 0],
78 | [1, 1, 1, 0, 0, 1, 1, 1],
79 | [1, 1, 2, 3, 3, 2, 1, 1],
80 | [1, 2, 3, 4, 4, 3, 2, 1],
81 | [2, 3, 3, 5, 5, 3, 3, 2],
82 | [5, 6, 6, 7, 7, 6, 6, 5],
83 | [8, 8, 8, 8, 8, 8, 8, 8],
84 | [8, 8, 8, 8, 8, 8, 8, 8]]
85 |
86 |
87 | piecePositionScores = {"N": knightScores, "B": bishopScores, "Q": queenScores,
88 | "R": rookScores, "wp": whitePawnScores, "bp": blackPawnScores}
89 |
90 |
91 | CHECKMATE = 1000
92 | STALEMATE = 0
93 | DEPTH = 4
94 | SET_WHITE_AS_BOT = -1
95 |
96 |
97 | def findRandomMoves(validMoves):
98 | return validMoves[random.randint(0, len(validMoves) - 1)]
99 |
100 |
101 | def findBestMove(gs, validMoves, returnQueue):
102 | global nextMove, whitePawnScores, blackPawnScores
103 | nextMove = None
104 | random.shuffle(validMoves)
105 |
106 | if gs.playerWantsToPlayAsBlack:
107 | # Swap the variables
108 | whitePawnScores, blackPawnScores = blackPawnScores, whitePawnScores
109 |
110 | SET_WHITE_AS_BOT = 1 if gs.whiteToMove else -1
111 |
112 | findMoveNegaMaxAlphaBeta(gs, validMoves, DEPTH, -
113 | CHECKMATE, CHECKMATE, SET_WHITE_AS_BOT)
114 |
115 | returnQueue.put(nextMove)
116 |
117 |
118 | # with alpha beta pruning
119 | '''
120 | alpha is keeping track of maximum so far
121 | beta is keeping track of minimum so far
122 | '''
123 |
124 |
125 | def findMoveNegaMaxAlphaBeta(gs, validMoves, depth, alpha, beta, turnMultiplier):
126 | global nextMove
127 | if depth == 0:
128 | return turnMultiplier * scoreBoard(gs)
129 |
130 | # (will add later) move ordering - like evaluate all the move first that results in check or evaluate all the move first that results in capturing opponent's queen
131 |
132 | maxScore = -CHECKMATE
133 | for move in validMoves:
134 | gs.makeMove(move)
135 | nextMoves = gs.getValidMoves() # opponent validmoves
136 | '''
137 | negative sign because what ever opponents best score is, is worst score for us
138 | negative turnMultiplier because it changes turns after moves made
139 | -beta, -alpha (new max, new min) our max become opponents new min and our min become opponents new max
140 | '''
141 | score = - \
142 | findMoveNegaMaxAlphaBeta(
143 | gs, nextMoves, depth-1, -beta, -alpha, -turnMultiplier)
144 | if score > maxScore:
145 | maxScore = score
146 | if depth == DEPTH:
147 | nextMove = move
148 | print(move, score)
149 | gs.undoMove()
150 | if maxScore > alpha:
151 | alpha = maxScore # alpha is the new max
152 | if alpha >= beta: # if we find new max is greater than minimum so far in a branch then we stop iterating in that branch as we found a worse move in that branch
153 | break
154 | return maxScore
155 |
156 |
157 | '''
158 | Positive score is good for white
159 | Negative score is good for black
160 | '''
161 |
162 |
163 | def scoreBoard(gs):
164 | if gs.checkmate:
165 | if gs.whiteToMove:
166 | gs.checkmate = False
167 | return -CHECKMATE # black wins
168 | else:
169 | gs.checkmate = False
170 | return CHECKMATE # white wins
171 | elif gs.stalemate:
172 | return STALEMATE
173 |
174 | score = 0
175 | for row in range(len(gs.board)):
176 | for col in range(len(gs.board[row])):
177 | square = gs.board[row][col]
178 | if square != "--":
179 | piecePositionScore = 0
180 | # score positionally based on piece type
181 | if square[1] != "K":
182 | # return score of the piece at that position
183 | if square[1] == "p":
184 | piecePositionScore = piecePositionScores[square][row][col]
185 | else:
186 | piecePositionScore = piecePositionScores[square[1]][row][col]
187 | if SET_WHITE_AS_BOT:
188 | if square[0] == 'w':
189 | score += pieceScore[square[1]] + \
190 | piecePositionScore * .1
191 | elif square[0] == 'b':
192 | score -= pieceScore[square[1]] + \
193 | piecePositionScore * .1
194 | else:
195 | if square[0] == 'w':
196 | score -= pieceScore[square[1]] + \
197 | piecePositionScore * .1
198 | elif square[0] == 'b':
199 | score += pieceScore[square[1]] + \
200 | piecePositionScore * .1
201 |
202 | return score
203 |
204 |
205 | '''def findBestMove(gs, validMoves):
206 | turnMultiplier = 1 if gs.whiteToMove else -1
207 | opponentMinMaxScore = CHECKMATE # for bot worst score
208 | bestMoveForPlayer = None # for black
209 | random.shuffle(validMoves)
210 | for playerMove in validMoves:
211 | gs.makeMove(playerMove) # bot (black) makes a move
212 | opponentsMoves = gs.getValidMoves() # player (white) get all valid moves
213 | opponentMaxScore = -CHECKMATE # player(opponent/white) worst possibility
214 | for opponentsMove in opponentsMoves:
215 | # the more positive the score the better the score for player(opponent)
216 | # player (opponent/white) makes a move for bot (black)
217 | gs.makeMove(opponentsMove) # player makes a move
218 | if gs.checkmate:
219 | score = -turnMultiplier * CHECKMATE # if player (white) makes a move and it results in checkmate than its the max score for player but worst for bot
220 | elif gs.stalemate:
221 | score = STALEMATE
222 | else:
223 | score = -turnMultiplier * scoreMaterial(gs.board)
224 | if score > opponentMaxScore:
225 | opponentMaxScore = score
226 | gs.undoMove()
227 | if opponentMaxScore < opponentMinMaxScore: # if player (opponent/white) moves does not result in checkmate(worst score for bot)
228 | ''''''
229 | opponentMaxScore = max score for the opponent if bot played playerMove
230 |
231 | it is calculating all possibles moves for player after bot makes move and store the minimum score of player after making player move in opponentMinMaxScore
232 | then again it check what if bot whould have played different move
233 | ''''''
234 | opponentMinMaxScore = opponentMaxScore
235 | bestMoveForPlayer = playerMove
236 | gs.undoMove()
237 | return bestMoveForPlayer '''
238 |
239 | '''def findMoveMinMax(gs, validMoves, depth, whiteToMove): #depth represent how many moves ahead we want to look to find current best move
240 | global nextMove
241 | if depth == 0:
242 | return scoreMaterial(gs.board)
243 |
244 | if whiteToMove:
245 | maxScore = -CHECKMATE # worst score for white
246 | for move in validMoves:
247 | gs.makeMove(move)
248 | nextMoves = gs.getValidMoves()
249 | score = findMoveMinMax(gs, nextMoves, depth - 1, False)
250 | if score > maxScore:
251 | maxScore = score
252 | if depth == DEPTH:
253 | nextMove = move
254 | gs.undoMove()
255 | return maxScore
256 |
257 | else:
258 | minScore = CHECKMATE # worst score for black
259 | for move in validMoves:
260 | gs.makeMove(move)
261 | nextMoves = gs.getValidMoves()
262 | score = findMoveMinMax(gs, nextMoves, depth - 1, True)
263 | if score < minScore:
264 | minScore = score
265 | if depth == DEPTH:
266 | nextMove = move
267 | gs.undoMove()
268 | return minScore'''
269 | # without alpha beta pruning
270 | '''def findMoveNegaMax(gs, validMoves, depth, turnMultiplier):
271 | global nextMove
272 | if depth == 0:
273 | return turnMultiplier * scoreBoard(gs)
274 |
275 | maxScore = -CHECKMATE
276 | for move in validMoves:
277 | gs.makeMove(move)
278 | nextMoves = gs.getValidMoves() # opponent validmoves
279 | ''''''
280 | - sign because what ever opponents best score is, is worst score for us
281 | negative turnMultiplier because it changes turns after moves made
282 | ''''''
283 | score = -findMoveNegaMax(gs, nextMoves, depth - 1, -turnMultiplier)
284 | if score > maxScore:
285 | maxScore = score
286 | if depth == DEPTH:
287 | nextMove = move
288 | gs.undoMove()
289 | return maxScore'''
290 |
291 | # calculate score of the board based on position
292 | '''
293 | def scoreMaterial(board):
294 | score = 0
295 | for row in board:
296 | for square in row:
297 | if square[0] == 'w':
298 | score += pieceScore[square[1]]
299 | elif square[0] == 'b':
300 | score -= pieceScore[square[1]]
301 |
302 | return score
303 | '''
304 |
--------------------------------------------------------------------------------
/chess/engine.py:
--------------------------------------------------------------------------------
1 | '''
2 | If you want to play as black or wants to flip the board set :
3 | self.playerWantsToPlayAsBlack = True
4 | '''
5 |
6 | # Responsible for storing all information about current state of chess game, determining valid move, able to undo moves ...
7 |
8 |
9 | class GameState():
10 | def __init__(self):
11 | self.board = [
12 | ['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
13 | ['bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp'],
14 | ['--', '--', '--', '--', '--', '--', '--', '--'],
15 | ['--', '--', '--', '--', '--', '--', '--', '--'],
16 | ['--', '--', '--', '--', '--', '--', '--', '--'],
17 | ['--', '--', '--', '--', '--', '--', '--', '--'],
18 | ['wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp'],
19 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']]
20 |
21 | self.board1 = [
22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
23 | ['wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp', 'wp'],
24 | ['--', '--', '--', '--', '--', '--', '--', '--'],
25 | ['--', '--', '--', '--', '--', '--', '--', '--'],
26 | ['--', '--', '--', '--', '--', '--', '--', '--'],
27 | ['--', '--', '--', '--', '--', '--', '--', '--'],
28 | ['bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp', 'bp'],
29 | ['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR']]
30 |
31 | self.moveFunctions = {'p': self.getPawnMoves, 'R': self.getRookMoves, 'N': self.getKnightMoves,
32 | 'B': self.getBishopMoves, 'Q': self.getQueenMoves, 'K': self.getKingMoves}
33 | self.whiteToMove = True
34 | # set playerWantsToPlayAsBlack = True if you want to flip board and play as black
35 | self.playerWantsToPlayAsBlack = False
36 | self.moveLog = []
37 | # keeping track of king positions to prevent from checks and also it makes castling easier
38 | if (self.playerWantsToPlayAsBlack):
39 | self.whiteKinglocation = (0, 4)
40 | self.blackKinglocation = (7, 4)
41 | else:
42 | self.whiteKinglocation = (7, 4)
43 | self.blackKinglocation = (0, 4)
44 | self.checkmate = False
45 | self.stalemate = False
46 | self.inCheck = False
47 | self.score = 0
48 | self.pins = []
49 | self.checks = []
50 | # co-ordinates for square where enpassant is possible
51 | self.enpasantPossible = ()
52 | self.enpasantPossibleLog = [self.enpasantPossible]
53 | # castling rights
54 | self.whiteCastleKingside = True
55 | self.whiteCastleQueenside = True
56 | self.blackCastleKingside = True
57 | self.blackCastleQueenside = True
58 | self.castleRightsLog = [castleRights(
59 | self.whiteCastleKingside, self.whiteCastleQueenside, self.blackCastleKingside, self.blackCastleQueenside)]
60 |
61 | def makeMove(self, move):
62 | self.board[move.startRow][move.startCol] = "--"
63 | self.board[move.endRow][move.endCol] = move.pieceMoved
64 | # save history of the moved played
65 | self.moveLog.append(move)
66 | # swap player
67 | self.whiteToMove = not self.whiteToMove
68 |
69 | # update king's location if moved
70 | if move.pieceMoved == 'wK':
71 | self.whiteKinglocation = (move.endRow, move.endCol)
72 | self.whiteCastleKingside = False
73 | self.whiteCastleQueenside = False
74 | elif move.pieceMoved == 'bK':
75 | self.blackKinglocation = (move.endRow, move.endCol)
76 | self.blackCastleKingside = False
77 | self.blackCastleQueenside = False
78 |
79 | # pawn promotion
80 | # if move.isPawnPromotion:
81 | # self.board[move.endRow][move.endCol] = move.pieceMoved[0] + 'Q'
82 |
83 | # enpassant move
84 | if move.isEnpassantMove:
85 | # capture piece, (same row , end col ) is the location of the opponent pawn from our pawn
86 | self.board[move.startRow][move.endCol] = '--'
87 |
88 | # update enpassant variable everytime piece is moved
89 | # only on 2 square pawn advances
90 | if move.pieceMoved[1] == 'p' and abs(move.startRow - move.endRow) == 2:
91 | # valid square will be between (startRow and endRow, endcol or startCol(because opponent's pawn 2 square move is on same col))
92 | # if we do the average of startRow and endRow it will be valid for both black and white
93 | self.enpasantPossible = (
94 | (move.startRow + move.endRow)//2, move.startCol)
95 | else:
96 | # if after opponent move its pawn to second square instead of capturing it with enpassant we played different move then enpassant move will not be possible
97 | self.enpasantPossible = ()
98 |
99 | # update Log which side castle is possible
100 | self.updateCastleRights(move)
101 | self.castleRightsLog.append(castleRights(
102 | self.whiteCastleKingside, self.whiteCastleQueenside, self.blackCastleKingside, self.blackCastleQueenside))
103 |
104 | # update enpasantPossibleLog
105 | self.enpasantPossibleLog.append(self.enpasantPossible)
106 |
107 | # castle moves
108 | if move.castle:
109 | # King Side
110 | if move.endCol - move.startCol == 2:
111 | # Rook move
112 | self.board[move.endRow][move.endCol -
113 | 1] = self.board[move.endRow][move.endCol + 1]
114 | self.board[move.endRow][move.endCol + 1] = "--"
115 | # Queen Side
116 | else:
117 | # Rook move
118 | self.board[move.endRow][move.endCol +
119 | 1] = self.board[move.endRow][move.endCol - 2]
120 | self.board[move.endRow][move.endCol - 2] = "--"
121 |
122 | def undoMove(self):
123 | if len(self.moveLog) != 0: # there is atleast one move to undo
124 | move = self.moveLog.pop()
125 | self.board[move.startRow][move.startCol] = move.pieceMoved
126 | self.board[move.endRow][move.endCol] = move.pieceCaptured
127 | self.whiteToMove = not self.whiteToMove # swap player
128 |
129 | # undo updated king's location
130 | if move.pieceMoved == 'wK':
131 | self.whiteKinglocation = (move.startRow, move.startCol)
132 | elif move.pieceMoved == 'bK':
133 | self.blackKinglocation = (move.startRow, move.startCol)
134 |
135 | # enpassant move
136 | if move.isEnpassantMove:
137 | self.board[move.endRow][move.endCol] = "--"
138 | self.board[move.startRow][move.endCol] = move.pieceCaptured
139 |
140 | self.enpasantPossibleLog.pop()
141 | self.enpasantPossible = self.enpasantPossibleLog[-1]
142 |
143 | # give pack castle rights after undo
144 | self.castleRightsLog.pop()
145 | castleRights = self.castleRightsLog[-1]
146 | self.whiteCastleKingside = castleRights.wks
147 | self.whiteCastleQueenside = castleRights.wqs
148 | self.blackCastleKingside = castleRights.bks
149 | self.blackCastleQueenside = castleRights.bqs
150 |
151 | # undo castle
152 | if move.castle:
153 | if move.endCol - move.startCol == 2: # KingSide
154 | self.board[move.endRow][move.endCol +
155 | 1] = self.board[move.endRow][move.endCol - 1] # rook move
156 | self.board[move.endRow][move.endCol - 1] = "--"
157 | else: # queenSide
158 | self.board[move.endRow][move.endCol -
159 | 2] = self.board[move.endRow][move.endCol + 1] # rook move
160 | self.board[move.endRow][move.endCol + 1] = "--"
161 |
162 | self.checkmate = False
163 | self.stalemate = False
164 |
165 | # move is valid if your king is in check and you move the piece which stops you from check
166 | def getValidMoves(self):
167 | # 1) first generate all possible moves for the piece of player in check
168 | # 2) for each move, make a move for the player in check
169 | # 3) generate all opponent moves after you moved-your-piece(when you called makeMove) to prevent check
170 | # 4) for each of opponents moves, see if opponents still attack your king
171 | # 5) if they still attack your king, its not a valid move
172 | moves = []
173 | self.inCheck, self.pins, self.checks = self.checkForPinsAndChecks()
174 | if self.whiteToMove:
175 | kingRow = self.whiteKinglocation[0]
176 | kingCol = self.whiteKinglocation[1]
177 | else:
178 | kingRow = self.blackKinglocation[0]
179 | kingCol = self.blackKinglocation[1]
180 | if self.inCheck:
181 | # only one check to the king, move the king or block the check with a piece
182 | if len(self.checks) == 1:
183 | moves = self.getAllPossibleMoves()
184 | # (row, col) of the piece which is causing the check
185 | check = self.checks[0]
186 | checkRow = check[0]
187 | checkCol = check[1]
188 | # position of the piece which is causing the check
189 | pieceChecking = self.board[checkRow][checkCol]
190 | validSquares = [] # sqaures that pieces can move to
191 | # if check is from knight than either move the king or take the knight
192 | if pieceChecking[1] == 'N':
193 | validSquares = [(checkRow, checkCol)]
194 | else:
195 | for i in range(1, 8):
196 | # check[2], check[3] are the check directions
197 | validSq = (kingRow + check[2]
198 | * i, kingCol + check[3] * i)
199 | validSquares.append(validSq)
200 | # upto the piece applying check
201 | if validSq[0] == checkRow and validSq[1] == checkCol:
202 | break
203 | # remove the move that dosen't prevent from check
204 | # going backward in all possible moves
205 | for i in range(len(moves) - 1, -1, -1):
206 | # as the king is not moved it should block the check if not then remove the move from moves
207 | if moves[i].pieceMoved[1] != 'K':
208 | # if not in validSquares then it do not block check or capture the piece making check
209 | if not (moves[i].endRow, moves[i].endCol) in validSquares:
210 | moves.remove(moves[i]) # remove the moves
211 | '''
212 | till know we will be able to find check and can move piece to block check but we are doing nothing about the pin so it will allow us to moved the pin pieced
213 | what if we move the king and is in the position of pinned we would still be able to move the pinned piece and let king be in check
214 | '''
215 | else: # if double check then king has to move
216 | self.getKingMoves(kingRow, kingCol, moves)
217 | else: # not in check all checks in moves are fine
218 | moves = self.getAllPossibleMoves()
219 |
220 | if len(moves) == 0:
221 | if self.inCheck:
222 | self.checkmate = True
223 | # print("checkmate")
224 | else:
225 | self.stalemate = True
226 | # print("stalemate")
227 | else:
228 | self.checkmate = False
229 | self.stalemate = False
230 |
231 | return moves
232 |
233 | '''
234 | # check if the current player is in check
235 | def inCheck(self):
236 | # after we moved a piece to prevent from check its still out turn to play,
237 | # as we changed turn back to us before calling inCheck, to get the king's position to see if king is still under attack
238 | if self.whiteToMove:
239 | return self.squareUnderAttack(self.whiteKinglocation[0], self.whiteKinglocation[1])
240 | else:
241 | return self.squareUnderAttack(self.blackKinglocation[0], self.blackKinglocation[1])
242 | '''
243 |
244 | # check if enemy can attack your king after you make a move while you were in check
245 | # row, col is the position of the king in attack
246 | def squareUnderAttack(self, row, col, allyColor):
247 | enemyColor = 'w' if allyColor == 'b' else 'b'
248 | directions = ((-1, 0), (0, -1), (1, 0), (0, 1),
249 | (-1, -1), (-1, 1), (1, -1), (1, 1))
250 | for j in range(len(directions)):
251 | d = directions[j]
252 | for i in range(1, 8):
253 | endRow = row + d[0] * i
254 | endCol = col + d[1] * i
255 | if 0 <= endRow < 8 and 0 <= endCol < 8:
256 | endPiece = self.board[endRow][endCol]
257 | if endPiece[0] == allyColor: # no attack from that direction
258 | break
259 | elif endPiece[0] == enemyColor:
260 | type = endPiece[1]
261 | # Possibilities
262 | # 1) Rook in any orthogonal directions
263 | # 2) Bishop in any diagonal
264 | # 3) Queen in orthogonal or diagonal directions
265 | # 4) Pawn if onw square away in any diagonal
266 | # 5) King in any direction to 1 square (to prevent king move controlled by another king)
267 | '''
268 | For Rook we will check only if directions and up, down, left, right which is in range 0 <= j <= 3 in directions.
269 | Similarty for bishop, in directions we have added the bishop direction in directions (4 to 7).
270 | For pawn if one forward diagonal square in front of king has opponent's pawn
271 | '''
272 | if (0 <= j <= 3 and type == 'R') or (4 <= j <= 7 and type == 'B') or \
273 | (i == 1 and type == 'p' and ((enemyColor == 'w' and 6 <= j <= 7) or (enemyColor == 'b' and 4 <= j <= 5))) or \
274 | (type == 'Q') or (i == 1 and type == 'K'):
275 | return True
276 | else: # enemy piece not applying check
277 | break
278 | else: # off board
279 | break
280 |
281 | def getAllPossibleMoves(self):
282 | moves = []
283 | for row in range(len(self.board)):
284 | # traverse every position to find validmove for each piece
285 | for col in range(len(self.board[0])):
286 | # check if the piece on the board[row][col] is black or white or empty
287 | turn = self.board[row][col][0]
288 | # check if the piece is white and white to move or piece is black and black to move
289 | if (turn == 'w' and self.whiteToMove) or (turn == 'b' and not self.whiteToMove):
290 | # if the piece is bR(black rook) or wp(white pawn) it returns the second character (R for Rook, Q for Queen, p for Pawn)
291 | piece = self.board[row][col][1]
292 | # same as (if piece == p (pawn)) -> self.getPawnMoves(row,col,moves)
293 | self.moveFunctions[piece](row, col, moves)
294 | return moves
295 |
296 | # Get all the Pawn moves for the Pawn located at row, col and add it to the moves
297 | def getPawnMoves(self, row, col, moves):
298 | '''
299 | to move the pawn we will first check if its in check or not
300 | '''
301 | piecePinned = False
302 | pinDirection = ()
303 | for i in range(len(self.pins)-1, -1, -1):
304 | if self.pins[i][0] == row and self.pins[i][1] == col:
305 | piecePinned = True
306 | pinDirection = (self.pins[i][2], self.pins[i][3])
307 | self.pins.remove(self.pins[i])
308 | break
309 |
310 | if (self.playerWantsToPlayAsBlack == True):
311 | if self.whiteToMove:
312 | moveAmount = 1
313 | startRow = 1
314 | enemyColor = 'b'
315 | kingRow, kingCol = self.whiteKinglocation
316 | else:
317 | moveAmount = -1
318 | startRow = 6
319 | enemyColor = 'w'
320 | kingRow, kingCol = self.blackKinglocation
321 | else:
322 | if self.whiteToMove:
323 | moveAmount = -1
324 | startRow = 6
325 | enemyColor = 'b'
326 | kingRow, kingCol = self.whiteKinglocation
327 | else:
328 | moveAmount = 1
329 | startRow = 1
330 | enemyColor = 'w'
331 | kingRow, kingCol = self.blackKinglocation
332 |
333 | if self.board[row + moveAmount][col] == "--": # first square move
334 | # if piece is not pinned then its fine or if it is pinned but from forward direction then we can still move
335 | if not piecePinned or pinDirection == (moveAmount, 0):
336 | moves.append(
337 | Move((row, col), (row+moveAmount, col), self.board))
338 | # Check if pawn can directly advance to second square
339 | if row == startRow and self.board[row+2*moveAmount][col] == "--":
340 | moves.append(
341 | Move((row, col), (row+2*moveAmount, col), self.board))
342 | # capture
343 | if col-1 >= 0: # there is a col to the left for white
344 | # check if there is a black piece to the left of your pawn that you can capture
345 | # if piece is not pinned then its fine or if it is pinned but from left direction then we can capture left piece
346 | if not piecePinned or pinDirection == (moveAmount, -1):
347 | if self.board[row+moveAmount][col-1][0] == enemyColor:
348 | moves.append(
349 | Move((row, col), (row+moveAmount, col-1), self.board))
350 | if (row+moveAmount, col-1) == self.enpasantPossible:
351 | attackingPiece = blockingPiece = False
352 | if kingRow == row:
353 | if kingCol < col: # king is left of the pawn
354 | # between king and pawn
355 | insideRange = range(kingCol + 1, col - 1)
356 | # between pawn and boarder
357 | outsideRange = range(col + 1, 8)
358 | else: # king is right of the pawn
359 | insideRange = range(kingCol - 1, col, - 1)
360 | outsideRange = range(col - 2, -1, -1)
361 | for i in insideRange:
362 | # other piece blocking check
363 | if self.board[row][i] != "--":
364 | blockingPiece = True
365 | for i in outsideRange:
366 | square = self.board[row][i]
367 | if square[0] == enemyColor and (square[1] == "R" or square[1] == "Q"):
368 | attackingPiece = True
369 | elif square != "--":
370 | blockingPiece = True
371 | if not attackingPiece or blockingPiece:
372 | moves.append(Move((row, col), (row+moveAmount, col-1),
373 | self.board, isEnpassantMove=True))
374 | if col+1 <= 7: # there is a col to the right for white
375 | # check if there is a black piece to the right of your pawn that you can capture
376 | # if piece is not pinned then its fine or if it is pinned but from left direction then we can capture right piece
377 | if not piecePinned or pinDirection == (moveAmount, 1):
378 | if self.board[row+moveAmount][col+1][0] == enemyColor:
379 | moves.append(
380 | Move((row, col), (row+moveAmount, col+1), self.board))
381 | if (row+moveAmount, col+1) == self.enpasantPossible:
382 | attackingPiece = blockingPiece = False
383 | if kingRow == row:
384 | if kingCol < col: # king is left of the pawn
385 | # between king and pawn
386 | insideRange = range(kingCol + 1, col)
387 | # between pawn and boarder
388 | outsideRange = range(col + 2, 8)
389 | else: # king is right of the pawn
390 | insideRange = range(kingCol - 1, col + 1, - 1)
391 | outsideRange = range(col - 1, -1, -1)
392 | for i in insideRange:
393 | # other piece blocking check
394 | if self.board[row][i] != "--":
395 | blockingPiece = True
396 | for i in outsideRange:
397 | square = self.board[row][i]
398 | if square[0] == enemyColor and (square[1] == "R" or square[1] == "Q"):
399 | attackingPiece = True
400 | elif square != "--":
401 | blockingPiece = True
402 | if not attackingPiece or blockingPiece:
403 | moves.append(Move((row, col), (row+moveAmount, col+1),
404 | self.board, isEnpassantMove=True))
405 |
406 | # Get all the Rook moves for the Rook located at row, col and add it to the moves
407 | def getRookMoves(self, row, col, moves):
408 | piecePinned = False
409 | pinDirection = ()
410 | for i in range(len(self.pins)-1, -1, -1):
411 | if self.pins[i][0] == row and self.pins[i][1] == col:
412 | piecePinned = True
413 | pinDirection = (self.pins[i][2], self.pins[i][3])
414 | if self.board[row][col][1] != 'Q':
415 | '''
416 | Don't remove queen from pin on rook moves, we can remove the pin in bishop move
417 |
418 | let we removed pin on rook moves, asssume pinned in forward direction from the king after this while we will call
419 | getRookMoves again while genetaring queen moves it will lis queen as not pinned (as we had removed the pin from the list) so
420 | we will be able the queen in any direction leaving king in check
421 |
422 | we can remove the pin in bishop move because while generating queen moves it will generate possible bishop moves where queen can go(go in same direction as pinned if pinned)
423 | and while generating rook moves for queen it will not generate an possible rook move for queen as for condition self.board[row][col][1] != 'Q':
424 |
425 | '''
426 | self.pins.remove(self.pins[i])
427 | break
428 |
429 | # down #up #right #left (row, col)
430 | directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
431 | # enemy color is b if whiteToMove or vice versa
432 | enemy_color = 'b' if self.whiteToMove else 'w'
433 |
434 | for direction in directions:
435 | for i in range(1, 8): # from one position rook can go upto max 7th square
436 | endRow = row + direction[0] * i # destination row
437 | endCol = col + direction[1] * i # destination col
438 |
439 | if 0 <= endRow < 8 and 0 <= endCol < 8: # on the board
440 | # if piece is not pinned then its fine or if it is pinned but from forward direction then we can still move in both forward and backward direction
441 | if not piecePinned or pinDirection == direction or pinDirection == (-direction[0], -direction[1]):
442 | # check if next square is empty
443 | if self.board[endRow][endCol] == '--':
444 | # if empty then add moves
445 | moves.append(
446 | Move((row, col), (endRow, endCol), self.board))
447 | # check if piece on next square is opponent
448 | elif self.board[endRow][endCol][0] == enemy_color:
449 | # if piece is not pinned then its fine or if it is pinned but from forward direction then we can still move
450 | if not piecePinned or pinDirection == direction:
451 | # then you can at it to the move as you can capture it
452 | moves.append(
453 | Move((row, col), (endRow, endCol), self.board))
454 | break
455 | else: # if neither then break
456 | break
457 | else: # out of the board
458 | break
459 |
460 | # Get all the Bishop moves for the Bishop located at row, col and add it to the moves
461 | def getBishopMoves(self, row, col, moves):
462 | piecePinned = False
463 | pinDirection = ()
464 | for i in range(len(self.pins)-1, -1, -1):
465 | if self.pins[i][0] == row and self.pins[i][1] == col:
466 | piecePinned = True
467 | pinDirection = (self.pins[i][2], self.pins[i][3])
468 | self.pins.remove(self.pins[i])
469 | break
470 |
471 | directions = [(1, 1), (-1, -1), (-1, 1), (1, -1)] # diagonals
472 | # enemy color is b if whiteToMove or vice versa
473 | enemy_color = 'b' if self.whiteToMove else 'w'
474 |
475 | for direction in directions:
476 | for i in range(1, 8): # from one position bishop can go upto max 7th square
477 | endRow = row + direction[0] * i # destination row
478 | endCol = col + direction[1] * i # destination col
479 | if 0 <= endRow < 8 and 0 <= endCol < 8: # on the board
480 | if not piecePinned or pinDirection == direction or pinDirection == (-direction[0], -direction[1]):
481 | # check if next square is empty
482 | if self.board[endRow][endCol] == '--':
483 | # if empty then add moves
484 | moves.append(
485 | Move((row, col), (endRow, endCol), self.board))
486 | # check if piece on next square is opponent
487 | elif self.board[endRow][endCol][0] == enemy_color:
488 | # then you can at it to the move as you can capture it
489 | moves.append(
490 | Move((row, col), (endRow, endCol), self.board))
491 | break
492 | else: # if neither then break
493 | break
494 | else: # out of the board
495 | break
496 |
497 | # Get all the Knight moves for the Knight located at row, col and add it to the moves
498 | def getKnightMoves(self, row, col, moves):
499 |
500 | piecePinned = False
501 | for i in range(len(self.pins)-1, -1, -1):
502 | if self.pins[i][0] == row and self.pins[i][1] == col:
503 | piecePinned = True
504 | self.pins.remove(self.pins[i])
505 | break
506 |
507 | # List of all possible knight moves
508 | AllPossibleKnightMoves = [
509 | (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)]
510 |
511 | for m in AllPossibleKnightMoves:
512 | endRow = row + m[0] # destination row
513 | endCol = col + m[1] # destination col
514 |
515 | if 0 <= endRow <= 7 and 0 <= endCol <= 7:
516 | if not piecePinned:
517 | # if white to move and destination either have no piece or have a black piece
518 | if self.whiteToMove and (self.board[endRow][endCol] == '--' or self.board[endRow][endCol][0] == 'b'):
519 | moves.append(
520 | Move((row, col), (endRow, endCol), self.board))
521 | # if black to move and destination either have no piece or have a black piece
522 | elif not self.whiteToMove and (self.board[endRow][endCol] == '--' or self.board[endRow][endCol][0] == 'w'):
523 | moves.append(
524 | Move((row, col), (endRow, endCol), self.board))
525 |
526 | # Get all the Queen moves for the Queen located at row, col and add it to the moves
527 | def getQueenMoves(self, row, col, moves):
528 | self.getBishopMoves(row, col, moves)
529 | self.getRookMoves(row, col, moves)
530 |
531 | # Get all the King moves for the King located at row, col and add it to the moves
532 | def getKingMoves(self, row, col, moves):
533 | # these for loops denote all possible moves for the king
534 | for i in range(-1, 2):
535 | for j in range(-1, 2):
536 | allyColor = 'w' if self.whiteToMove else 'b'
537 | if i == 0 and j == 0: # same square
538 | continue
539 | if 0 <= row + i <= 7 and 0 <= col + j <= 7:
540 | endPiece = self.board[row + i][col + j]
541 | if endPiece[0] != allyColor: # the square is empty or has an enemy piece
542 | # temporarily move the king to check if it returns in check
543 | if allyColor == 'w':
544 | self.whiteKinglocation = (row + i, col + j)
545 | else:
546 | self.blackKinglocation = (row + i, col + j)
547 |
548 | inCheck, pins, checks = self.checkForPinsAndChecks()
549 | # if king's move doesn't return in check, append to moves
550 | if not inCheck:
551 | moves.append(
552 | Move((row, col), (row + i, col + j), self.board))
553 | # move the king back to its original location
554 | if allyColor == 'w':
555 | self.whiteKinglocation = (row, col)
556 | else:
557 | self.blackKinglocation = (row, col)
558 |
559 | self.getcastleMoves(row, col, moves, allyColor)
560 |
561 | def getcastleMoves(self, row, col, moves, allyColor):
562 | inCheck = self.squareUnderAttack(row, col, allyColor)
563 | if inCheck:
564 | return
565 | if (self.whiteToMove and self.whiteCastleKingside) or (not self.whiteToMove and self.blackCastleKingside):
566 | self.getKingsidecastleMoves(row, col, moves, allyColor)
567 | if (self.whiteToMove and self.whiteCastleQueenside) or (not self.whiteToMove and self.blackCastleQueenside):
568 | self.getQueensidecastleMoves(row, col, moves, allyColor)
569 |
570 | def getKingsidecastleMoves(self, row, col, moves, allyColor):
571 | if self.board[row][col+1] == "--" and self.board[row][col+2] == "--" and not self.squareUnderAttack(row, col + 1, allyColor) and not self.squareUnderAttack(row, col + 2, allyColor):
572 | moves.append(Move((row, col), (row, col + 2),
573 | self.board, castle=True))
574 |
575 | def getQueensidecastleMoves(self, row, col, moves, allyColor):
576 | if self.board[row][col-1] == "--" and self.board[row][col-2] == "--" and self.board[row][col-3] == "--" and not self.squareUnderAttack(row, col - 1, allyColor) and not self.squareUnderAttack(row, col - 2, allyColor):
577 | moves.append(Move((row, col), (row, col - 2),
578 | self.board, castle=True))
579 |
580 | def checkForPinsAndChecks(self):
581 | pins = []
582 | checks = []
583 | inCheck = False
584 | if self.whiteToMove:
585 | enemyColor = "b"
586 | allyColor = "w"
587 | startRow = self.whiteKinglocation[0]
588 | startCol = self.whiteKinglocation[1]
589 | else:
590 | enemyColor = "w"
591 | allyColor = "b"
592 | startRow = self.blackKinglocation[0]
593 | startCol = self.blackKinglocation[1]
594 | # from king position in all directions, look for pins and checks, keep track of pins
595 | directions = ((-1, 0), (0, -1), (1, 0), (0, 1),
596 | (-1, -1), (-1, 1), (1, -1), (1, 1))
597 | for j in range(len(directions)):
598 | d = directions[j]
599 | possiblePin = () # reset
600 | for i in range(1, 8):
601 | endRow = startRow + d[0] * i
602 | endCol = startCol + d[1] * i
603 | if 0 <= endRow < 8 and 0 <= endCol < 8:
604 | # find if there is a piece
605 | endPiece = self.board[endRow][endCol]
606 | # if it's your piece it could be pinned by enemy
607 | if endPiece[0] == allyColor and endPiece[1] != 'K':
608 | if possiblePin == (): # so add it to the possiblePin
609 | possiblePin = (endRow, endCol, d[0], d[1])
610 | else: # after that square if there is another of allied piece, no pins or check is possible
611 | break
612 | elif endPiece[0] == enemyColor: # if an enemy piece is found
613 | type = endPiece[1]
614 | # Possibilities
615 | # 1) Rook in any orthogonal directions
616 | # 2) Bishop in any diagonal
617 | # 3) Queen in orthogonal or diagonal directions
618 | # 4) Pawn if onw square away in any diagonal
619 | # 5) King in any direction to 1 square (to prevent king move controlled by another king)
620 | '''
621 | For Rook we will check only if directions and up, down, left, right which is in range 0 <= j <= 3 in directions.
622 | Similarty for bishop, in directions we have added the bishop direction in directions (4 to 7).
623 | For pawn if one forward diagonal square in front of king has opponent's pawn
624 | '''
625 | if (0 <= j <= 3 and type == 'R') or (4 <= j <= 7 and type == 'B') or \
626 | (i == 1 and type == 'p' and ((enemyColor == 'w' and 6 <= j <= 7) or (enemyColor == 'b' and 4 <= j <= 5))) or \
627 | (type == 'Q') or (i == 1 and type == 'K'):
628 | '''
629 | now check if king is pinned or in check
630 | '''
631 | if possiblePin == (): # no ally piece infront of king, so check
632 | inCheck = True
633 | checks.append((endRow, endCol, d[0], d[1]))
634 | break
635 | else: # piece blocking so pin
636 | pins.append(possiblePin)
637 | break
638 | else: # enemy piece infront of king but not applying any check
639 | break
640 | else:
641 | break # off board
642 | # check for knight checks
643 | knightMoves = ((-2, -1), (-2, 1), (-1, -2), (-1, 2),
644 | (1, -2), (1, 2), (2, -1), (2, 1))
645 | for m in knightMoves:
646 | endRow = startRow + m[0]
647 | endCol = startCol + m[1]
648 | if 0 <= endRow < 8 and 0 <= endCol < 8:
649 | endPiece = self.board[endRow][endCol]
650 | if endPiece[0] == enemyColor and endPiece[1] == 'N':
651 | inCheck = True
652 | checks.append((endRow, endCol, m[0], m[1]))
653 | return inCheck, pins, checks
654 |
655 | def updateCastleRights(self, move):
656 |
657 | if move.pieceMoved == 'wK':
658 | self.whiteCastleKingside = False
659 | self.whiteCastleQueenside = False
660 | elif move.pieceMoved == 'bK':
661 | self.blackCastleKingside = False
662 | self.blackCastleQueenside = False
663 |
664 | # rook captured
665 | if move.pieceCaptured == 'wR' and move.endRow == 7 and move.endCol == 0:
666 | self.whiteCastleQueenside = False
667 | if move.pieceCaptured == 'wR' and move.endRow == 7 and move.endCol == 7:
668 | self.whiteCastleKingside = False
669 | if move.pieceCaptured == 'bR' and move.endRow == 0 and move.endCol == 0:
670 | self.blackCastleQueenside = False
671 | if move.pieceCaptured == 'bR' and move.endRow == 0 and move.endCol == 7:
672 | self.blackCastleKingside = False
673 |
674 | def getBoardString(self):
675 | # Convert the board state to a string
676 | boardString = ""
677 | for row in self.board:
678 | for square in row:
679 | boardString += square
680 | return boardString
681 |
682 |
683 | class castleRights():
684 | def __init__(self, wks, wqs, bks, bqs):
685 | self.wks = wks
686 | self.wqs = wqs
687 | self.bks = bks
688 | self.bqs = bqs
689 |
690 |
691 | class Move():
692 | # mapping keys to values
693 | # board[0][0] position in chess board is denoted as a7 (position of Black Rook)
694 |
695 | ranksToRows = {"1": 7, "2": 6, "3": 5,
696 | "4": 4, "5": 3, "6": 2, "7": 1, "8": 0}
697 | rowsToRanks = {value: key for key, value in ranksToRows.items()}
698 |
699 | filesToCols = {"a": 0, "b": 1, "c": 2,
700 | "d": 3, "e": 4, "f": 5, "g": 6, "h": 7}
701 | colsToFiles = {value: key for key, value in filesToCols.items()}
702 |
703 | pieceNotation = {
704 | "P": "",
705 | "R": "R",
706 | "N": "N",
707 | "B": "B",
708 | "Q": "Q",
709 | "K": "K"
710 | }
711 |
712 | # add an optional parameter to identify, the square for enpassant
713 | def __init__(self, startSquare, endSquare, board, isEnpassantMove=False, castle=False):
714 | self.startRow = startSquare[0]
715 | self.startCol = startSquare[1]
716 | self.endRow = endSquare[0]
717 | self.endCol = endSquare[1]
718 | self.pieceMoved = board[self.startRow][self.startCol]
719 | self.castle = castle
720 | if isEnpassantMove == True:
721 | self.pieceCaptured = board[self.startRow][self.endCol]
722 | else:
723 | self.pieceCaptured = board[self.endRow][self.endCol]
724 | self.isCapture = self.pieceCaptured != '--'
725 | self.moveID = self.startRow * 1000 + self.startCol * \
726 | 100 + self.endRow * 10 + self.endCol
727 | # pawn promotion
728 | gs = GameState()
729 | if (gs.playerWantsToPlayAsBlack):
730 | self.isPawnPromotion = (self.pieceMoved == "wp" and self.endRow == 7) or (
731 | self.pieceMoved == "bp" and self.endRow == 0)
732 | else:
733 | self.isPawnPromotion = (self.pieceMoved == "wp" and self.endRow == 0) or (
734 | self.pieceMoved == "bp" and self.endRow == 7)
735 | '''
736 | this is same as
737 |
738 | self.isPawnPromotion = False
739 | if (self.pieceMoved == "wp" and self.endRow == 0) or (self.pieceMoved == "bp" and self.endRow == 7):
740 | self.isPawnPromotion = True
741 |
742 | '''
743 | # en passant
744 | self.isEnpassantMove = isEnpassantMove
745 |
746 | # overriding the equals method (tell python if two moves are equal)
747 | # no two pieces should be on the same square
748 | # start pos and end pos can't be same
749 |
750 | def __eq__(self, other): # comparing object with another object saved in other
751 | if isinstance(other, Move):
752 | return self.moveID == other.moveID
753 | return False
754 |
755 | def getChessNotation(self):
756 | return self.getPieceNotation(self.pieceMoved, self.startCol) + self.getRankFile(self.endRow, self.endCol)
757 |
758 | def getRankFile(self, row, col):
759 | return self.colsToFiles[col] + self.rowsToRanks[row]
760 |
761 | def getPieceNotation(self, piece, col):
762 | if piece[1] == 'p':
763 | return self.getRankFile(self.startRow, self.startCol) + self.getRankFile(self.endRow, self.endCol)
764 | return self.pieceNotation[piece[1]] + self.colsToFiles[col]
765 |
766 | # overriding the str() function
767 | def __str__(self):
768 | # castle move:
769 | if self.castle:
770 | return "O-O" if self.endCol == 6 else "O-O-O"
771 |
772 | startSquare = self.getRankFile(self.startRow, self.startCol)
773 | endSquare = self.getRankFile(self.endRow, self.endCol)
774 |
775 | # pawn moves
776 | if self.pieceMoved[1] == 'p':
777 | if self.isCapture:
778 | return startSquare + "x" + endSquare
779 | else:
780 | return startSquare+endSquare
781 |
782 | # pawn promotion (add later)
783 | # add + for check # for checkmate
784 |
785 | # piece moves
786 | moveString = self.pieceMoved[1]
787 | if self.isCapture:
788 | return moveString + self.colsToFiles[self.startCol] + "x" + endSquare
789 | return moveString + self.colsToFiles[self.startCol] + endSquare
790 |
--------------------------------------------------------------------------------
/chess/main.py:
--------------------------------------------------------------------------------
1 | '''
2 | Add or remove bots :
3 |
4 | SET_WHITE_AS_BOT = False
5 | SET_BLACK_AS_BOT = True
6 | '''
7 |
8 | # Responsible for handling user input and displaying the current Gamestate object
9 |
10 | import sys
11 | import pygame as p
12 | from engine import GameState, Move
13 | from chessAi import findRandomMoves, findBestMove
14 | from multiprocessing import Process, Queue
15 |
16 | # Initialize the mixer
17 | p.mixer.init()
18 | # Load sound files
19 | move_sound = p.mixer.Sound("sounds/move-sound.mp3")
20 | capture_sound = p.mixer.Sound("sounds/capture.mp3")
21 | promote_sound = p.mixer.Sound("sounds/promote.mp3")
22 |
23 | BOARD_WIDTH = BOARD_HEIGHT = 512
24 | MOVE_LOG_PANEL_WIDTH = 250
25 | MOVE_LOG_PANEL_HEIGHT = BOARD_HEIGHT
26 | DIMENSION = 8
27 | SQ_SIZE = BOARD_HEIGHT // DIMENSION
28 | MAX_FPS = 15
29 | IMAGES = {}
30 |
31 | '''
32 |
33 | ADD BOTS
34 | IF IN GameState() ,
35 |
36 | playerWantsToPlayAsBlack = True
37 | SET_BLACK_AS_BOT SHOULD BE = FALSE
38 |
39 | '''
40 |
41 | SET_WHITE_AS_BOT = False
42 | SET_BLACK_AS_BOT = True
43 |
44 | # Define colors
45 |
46 | # 1 Green
47 |
48 | LIGHT_SQUARE_COLOR = (237, 238, 209)
49 | DARK_SQUARE_COLOR = (119, 153, 82)
50 | MOVE_HIGHLIGHT_COLOR = (84, 115, 161)
51 | POSSIBLE_MOVE_COLOR = (255, 255, 51)
52 |
53 | # 2 Brown
54 |
55 | '''
56 | LIGHT_SQUARE_COLOR = (240, 217, 181)
57 | DARK_SQUARE_COLOR = (181, 136, 99)
58 | MOVE_HIGHLIGHT_COLOR = (84, 115, 161)
59 | POSSIBLE_MOVE_COLOR = (255, 255, 51)
60 | '''
61 |
62 | # 3 Gray
63 |
64 | '''
65 | LIGHT_SQUARE_COLOR = (220,220,220)
66 | DARK_SQUARE_COLOR = (170,170,170)
67 | MOVE_HIGHLIGHT_COLOR = (84, 115, 161)
68 | POSSIBLE_MOVE_COLOR = (164,184,196)
69 | '''
70 |
71 |
72 | def loadImages():
73 | pieces = ['bR', 'bN', 'bB', 'bQ', 'bK',
74 | 'bp', 'wR', 'wN', 'wB', 'wQ', 'wK', 'wp']
75 | for piece in pieces:
76 | image_path = "images1/" + piece + ".png"
77 | original_image = p.image.load(image_path)
78 | # p.transform.smoothscale is bit slower than p.transform.scale, using this to reduce pixelation and better visual quality for scaling images to larger sizes
79 | IMAGES[piece] = p.transform.smoothscale(
80 | original_image, (SQ_SIZE, SQ_SIZE))
81 |
82 |
83 | def pawnPromotionPopup(screen, gs):
84 | font = p.font.SysFont("Times New Roman", 30, False, False)
85 | text = font.render("Choose promotion:", True, p.Color("black"))
86 |
87 | # Create buttons for promotion choices with images
88 | button_width, button_height = 100, 100
89 | buttons = [
90 | p.Rect(100, 200, button_width, button_height),
91 | p.Rect(200, 200, button_width, button_height),
92 | p.Rect(300, 200, button_width, button_height),
93 | p.Rect(400, 200, button_width, button_height)
94 | ]
95 |
96 | if gs.whiteToMove:
97 | button_images = [
98 | p.transform.smoothscale(p.image.load(
99 | "images1/bQ.png"), (100, 100)),
100 | p.transform.smoothscale(p.image.load(
101 | "images1/bR.png"), (100, 100)),
102 | p.transform.smoothscale(p.image.load(
103 | "images1/bB.png"), (100, 100)),
104 | p.transform.smoothscale(p.image.load("images1/bN.png"), (100, 100))
105 | ]
106 | else:
107 | button_images = [
108 | p.transform.smoothscale(p.image.load(
109 | "images1/wQ.png"), (100, 100)),
110 | p.transform.smoothscale(p.image.load(
111 | "images1/wR.png"), (100, 100)),
112 | p.transform.smoothscale(p.image.load(
113 | "images1/wB.png"), (100, 100)),
114 | p.transform.smoothscale(p.image.load("images1/wN.png"), (100, 100))
115 | ]
116 |
117 | while True:
118 | for e in p.event.get():
119 | if e.type == p.QUIT:
120 | p.quit()
121 | sys.exit()
122 | elif e.type == p.MOUSEBUTTONDOWN:
123 | mouse_pos = e.pos
124 | for i, button in enumerate(buttons):
125 | if button.collidepoint(mouse_pos):
126 | if i == 0:
127 | return "Q" # Return the index of the selected piece
128 | elif i == 1:
129 | return "R"
130 | elif i == 2:
131 | return "B"
132 | else:
133 | return "N"
134 |
135 | screen.fill(p.Color(LIGHT_SQUARE_COLOR))
136 | screen.blit(text, (110, 150))
137 |
138 | for i, button in enumerate(buttons):
139 | p.draw.rect(screen, p.Color("white"), button)
140 | screen.blit(button_images[i], button.topleft)
141 |
142 | p.display.flip()
143 |
144 |
145 | '''
146 | moveLocationWhite = ()
147 | movedPieceWhite = ""
148 | moveLocationBlack = ()
149 | movedPieceBlack = ""
150 |
151 | moveWhiteLog = []
152 | moveBlackLog = []
153 | '''
154 |
155 |
156 | def main():
157 | # initialize py game
158 | p.init()
159 | screen = p.display.set_mode(
160 | (BOARD_WIDTH + MOVE_LOG_PANEL_WIDTH, BOARD_HEIGHT))
161 | clock = p.time.Clock()
162 | screen.fill(p.Color(LIGHT_SQUARE_COLOR))
163 | moveLogFont = p.font.SysFont("Times New Roman", 12, False, False)
164 | # Creating gamestate object calling our constructor
165 | gs = GameState()
166 | if (gs.playerWantsToPlayAsBlack):
167 | gs.board = gs.board1
168 | # if a user makes a move we can ckeck if its in the list of valid moves
169 | validMoves = gs.getValidMoves()
170 | moveMade = False # if user makes a valid moves and the gamestate changes then we should generate new set of valid move
171 | animate = False # flag var for when we should animate a move
172 | loadImages()
173 | running = True
174 | squareSelected = () # keep tracks of last click
175 | # clicking to own piece and location where to move[(6,6),(4,4)]
176 | playerClicks = []
177 | gameOver = False # gameover if checkmate or stalemate
178 | playerWhiteHuman = not SET_WHITE_AS_BOT
179 | playerBlackHuman = not SET_BLACK_AS_BOT
180 | AIThinking = False # True if ai is thinking
181 | moveFinderProcess = None
182 | moveUndone = False
183 | pieceCaptured = False
184 | positionHistory = ""
185 | previousPos = ""
186 | countMovesForDraw = 0
187 | COUNT_DRAW = 0
188 | while running:
189 | humanTurn = (gs.whiteToMove and playerWhiteHuman) or (
190 | not gs.whiteToMove and playerBlackHuman)
191 | for e in p.event.get():
192 | if e.type == p.QUIT:
193 | running = False
194 | # Mouse Handler
195 | elif e.type == p.MOUSEBUTTONDOWN:
196 | if not gameOver: # allow mouse handling only if its not game over
197 | location = p.mouse.get_pos()
198 | col = location[0]//SQ_SIZE
199 | row = location[1]//SQ_SIZE
200 | # if user clicked on same square twice or user click outside board
201 | if squareSelected == (row, col) or col >= 8:
202 | squareSelected = () # deselect
203 | playerClicks = [] # clear player clicks
204 | else:
205 | squareSelected = (row, col)
206 | # append player both clicks (place and destination)
207 | playerClicks.append(squareSelected)
208 | # after second click (at destination)
209 | if len(playerClicks) == 2 and humanTurn:
210 | # user generated a move
211 | move = Move(playerClicks[0], playerClicks[1], gs.board)
212 | for i in range(len(validMoves)):
213 | # check if the move is in the validMoves
214 | if move == validMoves[i]:
215 | # Check if a piece is captured at the destination square
216 | # print(gs.board[validMoves[i].endRow][validMoves[i].endCol])
217 | if gs.board[validMoves[i].endRow][validMoves[i].endCol] != '--':
218 | pieceCaptured = True
219 | gs.makeMove(validMoves[i])
220 | if (move.isPawnPromotion):
221 | # Show pawn promotion popup and get the selected piece
222 | promotion_choice = pawnPromotionPopup(
223 | screen, gs)
224 | # Set the promoted piece on the board
225 | gs.board[move.endRow][move.endCol] = move.pieceMoved[0] + \
226 | promotion_choice
227 | promote_sound.play()
228 | pieceCaptured = False
229 | # add sound for human move
230 | if (pieceCaptured or move.isEnpassantMove):
231 | # Play capture sound
232 | capture_sound.play()
233 | # print("capture sound")
234 | elif not move.isPawnPromotion:
235 | # Play move sound
236 | move_sound.play()
237 | # print("move sound")
238 | pieceCaptured = False
239 | moveMade = True
240 | animate = True
241 | squareSelected = ()
242 | playerClicks = []
243 | if not moveMade:
244 | playerClicks = [squareSelected]
245 |
246 | # Key Handler
247 | elif e.type == p.KEYDOWN:
248 | if e.key == p.K_z: # undo when z is pressed
249 | gs.undoMove()
250 | # when user undo move valid move change, here we could use [ validMoves = gs.getValidMoves() ] which would update the current validMoves after undo
251 | moveMade = True
252 | animate = False
253 | gameOver = False
254 | if AIThinking:
255 | moveFinderProcess.terminate() # terminate the ai thinking if we undo
256 | AIThinking = False
257 | moveUndone = True
258 | if e.key == p.K_r: # reset board when 'r' is pressed
259 | gs = GameState()
260 | validMoves = gs.getValidMoves()
261 | squareSelected = ()
262 | playerClicks = []
263 | moveMade = False
264 | animate = False
265 | gameOver = False
266 | if AIThinking:
267 | moveFinderProcess.terminate() # terminate the ai thinking if we undo
268 | AIThinking = False
269 | moveUndone = True
270 |
271 | # AI move finder
272 | if not gameOver and not humanTurn and not moveUndone:
273 | if not AIThinking:
274 | AIThinking = True
275 | returnQueue = Queue() # keep track of data, to pass data between threads
276 | moveFinderProcess = Process(target=findBestMove, args=(
277 | gs, validMoves, returnQueue)) # when processing start we call these process
278 | # call findBestMove(gs, validMoves, returnQueue) #rest of the code could still work even if the ai is thinking
279 | moveFinderProcess.start()
280 | # AIMove = findBestMove(gs, validMoves)
281 | # gs.makeMove(AIMove)
282 | if not moveFinderProcess.is_alive():
283 | AIMove = returnQueue.get() # return from returnQueue
284 | if AIMove is None:
285 | AIMove = findRandomMoves(validMoves)
286 |
287 | if gs.board[AIMove.endRow][AIMove.endCol] != '--':
288 | pieceCaptured = True
289 |
290 | gs.makeMove(AIMove)
291 |
292 | if AIMove.isPawnPromotion:
293 | # Show pawn promotion popup and get the selected piece
294 | promotion_choice = pawnPromotionPopup(screen, gs)
295 | # Set the promoted piece on the board
296 | gs.board[AIMove.endRow][AIMove.endCol] = AIMove.pieceMoved[0] + \
297 | promotion_choice
298 | promote_sound.play()
299 | pieceCaptured = False
300 |
301 | # add sound for human move
302 | if (pieceCaptured or AIMove.isEnpassantMove):
303 | # Play capture sound
304 | capture_sound.play()
305 | # print("capture sound")
306 | elif not AIMove.isPawnPromotion:
307 | # Play move sound
308 | move_sound.play()
309 | # print("move sound")
310 | pieceCaptured = False
311 | AIThinking = False
312 | moveMade = True
313 | animate = True
314 | squareSelected = ()
315 | playerClicks = []
316 |
317 | if moveMade:
318 | if countMovesForDraw == 0 or countMovesForDraw == 1 or countMovesForDraw == 2 or countMovesForDraw == 3:
319 | countMovesForDraw += 1
320 | if countMovesForDraw == 4:
321 | positionHistory += gs.getBoardString()
322 | if previousPos == positionHistory:
323 | COUNT_DRAW += 1
324 | positionHistory = ""
325 | countMovesForDraw = 0
326 | else:
327 | previousPos = positionHistory
328 | positionHistory = ""
329 | countMovesForDraw = 0
330 | COUNT_DRAW = 0
331 | # Call animateMove to animate the move
332 | if animate:
333 | animateMove(gs.moveLog[-1], screen, gs.board, clock)
334 | # genetare new set of valid move if valid move is made
335 | validMoves = gs.getValidMoves()
336 | moveMade = False
337 | animate = False
338 | moveUndone = False
339 |
340 | drawGameState(screen, gs, validMoves, squareSelected, moveLogFont)
341 |
342 | if COUNT_DRAW == 1:
343 | gameOver = True
344 | text = 'Draw due to repetition'
345 | drawEndGameText(screen, text)
346 | if gs.stalemate:
347 | gameOver = True
348 | text = 'Stalemate'
349 | drawEndGameText(screen, text)
350 | elif gs.checkmate:
351 | gameOver = True
352 | text = 'Black wins by checkmate' if gs.whiteToMove else 'White wins by checkmate'
353 | drawEndGameText(screen, text)
354 |
355 | clock.tick(MAX_FPS)
356 | p.display.flip()
357 |
358 |
359 | def drawGameState(screen, gs, validMoves, squareSelected, moveLogFont):
360 | drawSquare(screen) # draw square on board
361 | highlightSquares(screen, gs, validMoves, squareSelected)
362 | drawPieces(screen, gs.board)
363 | drawMoveLog(screen, gs, moveLogFont)
364 |
365 |
366 | def drawSquare(screen):
367 | global colors
368 | colors = [p.Color(LIGHT_SQUARE_COLOR), p.Color(DARK_SQUARE_COLOR)]
369 | for row in range(DIMENSION):
370 | for col in range(DIMENSION):
371 | color = colors[((row + col) % 2)]
372 | p.draw.rect(screen, color, p.Rect(
373 | col * SQ_SIZE, row * SQ_SIZE, SQ_SIZE, SQ_SIZE))
374 |
375 |
376 | def highlightSquares(screen, gs, validMoves, squareSelected):
377 | if squareSelected != (): # make sure there is a square to select
378 | row, col = squareSelected
379 | # make sure they click there own piece
380 | if gs.board[row][col][0] == ('w' if gs.whiteToMove else 'b'):
381 | # highlight selected piece square
382 | # Surface in pygame used to add images or transperency feature
383 | s = p.Surface((SQ_SIZE, SQ_SIZE))
384 | # set_alpha --> transperancy value (0 transparent)
385 | s.set_alpha(100)
386 | s.fill(p.Color(MOVE_HIGHLIGHT_COLOR))
387 | screen.blit(s, (col*SQ_SIZE, row*SQ_SIZE))
388 | # highlighting valid square
389 | s.fill(p.Color(POSSIBLE_MOVE_COLOR))
390 | for move in validMoves:
391 | if move.startRow == row and move.startCol == col:
392 | screen.blit(s, (move.endCol*SQ_SIZE, move.endRow*SQ_SIZE))
393 |
394 |
395 | def drawPieces(screen, board):
396 | for row in range(DIMENSION):
397 | for col in range(DIMENSION):
398 | piece = board[row][col]
399 | if piece != "--":
400 | screen.blit(IMAGES[piece], p.Rect(
401 | col * SQ_SIZE, row * SQ_SIZE, SQ_SIZE, SQ_SIZE))
402 |
403 |
404 | def drawMoveLog(screen, gs, font):
405 | # rectangle
406 | moveLogRect = p.Rect(
407 | BOARD_WIDTH, 0, MOVE_LOG_PANEL_WIDTH, MOVE_LOG_PANEL_HEIGHT)
408 | p.draw.rect(screen, p.Color(LIGHT_SQUARE_COLOR), moveLogRect)
409 | moveLog = gs.moveLog
410 | moveTexts = []
411 |
412 | for i in range(0, len(moveLog), 2):
413 | moveString = " " + str(i//2 + 1) + ". " + str(moveLog[i]) + " "
414 | if i+1 < len(moveLog):
415 | moveString += str(moveLog[i+1])
416 | moveTexts.append(moveString)
417 |
418 | movesPerRow = 3
419 | padding = 10 # Increase padding for better readability
420 | lineSpacing = 5 # Increase line spacing for better separation
421 | textY = padding
422 |
423 | for i in range(0, len(moveTexts), movesPerRow):
424 | text = ""
425 | for j in range(movesPerRow):
426 | if i + j < len(moveTexts):
427 | text += moveTexts[i+j]
428 |
429 | textObject = font.render(text, True, p.Color('black'))
430 |
431 | # Adjust text location based on padding and line spacing
432 | textLocation = moveLogRect.move(padding, textY)
433 | screen.blit(textObject, textLocation)
434 |
435 | # Update Y coordinate for the next line with increased line spacing
436 | textY += textObject.get_height() + lineSpacing
437 |
438 |
439 | # animating a move
440 | def animateMove(move, screen, board, clock):
441 | global colors
442 | # change in row, col
443 | deltaRow = move.endRow - move.startRow
444 | deltaCol = move.endCol - move.startCol
445 | framesPerSquare = 5 # frames move one square
446 | # how many frame the animation will take
447 | frameCount = (abs(deltaRow) + abs(deltaCol)) * framesPerSquare
448 | # generate all the coordinates
449 | for frame in range(frameCount + 1):
450 | # how much does the row and col move by
451 | row, col = ((move.startRow + deltaRow*frame/frameCount, move.startCol +
452 | deltaCol*frame/frameCount)) # how far through the animation
453 | # for each frame draw the moved piece
454 | drawSquare(screen)
455 | drawPieces(screen, board)
456 |
457 | # erase the piece moved from its ending squares
458 | color = colors[(move.endRow + move.endCol) %
459 | 2] # get color of the square
460 | endSquare = p.Rect(move.endCol*SQ_SIZE, move.endRow *
461 | SQ_SIZE, SQ_SIZE, SQ_SIZE) # pygame rectangle
462 | p.draw.rect(screen, color, endSquare)
463 |
464 | # draw the captured piece back
465 | if move.pieceCaptured != '--':
466 | if move.isEnpassantMove:
467 | enPassantRow = move.endRow + \
468 | 1 if move.pieceCaptured[0] == 'b' else move.endRow - 1
469 | endSquare = p.Rect(move.endCol*SQ_SIZE, enPassantRow *
470 | SQ_SIZE, SQ_SIZE, SQ_SIZE) # pygame rectangle
471 | screen.blit(IMAGES[move.pieceCaptured], endSquare)
472 |
473 | # draw moving piece
474 | screen.blit(IMAGES[move.pieceMoved], p.Rect(
475 | col*SQ_SIZE, row*SQ_SIZE, SQ_SIZE, SQ_SIZE))
476 |
477 | p.display.flip()
478 | clock.tick(240)
479 |
480 |
481 | def drawEndGameText(screen, text):
482 | # create font object with type and size of font you want
483 | font = p.font.SysFont("Times New Roman", 30, False, False)
484 | # use the above font and render text (0 ? antialias)
485 | textObject = font.render(text, True, p.Color('black'))
486 |
487 | # Get the width and height of the textObject
488 | text_width = textObject.get_width()
489 | text_height = textObject.get_height()
490 |
491 | # Calculate the position to center the text on the screen
492 | textLocation = p.Rect(0, 0, BOARD_WIDTH, BOARD_HEIGHT).move(
493 | BOARD_WIDTH/2 - text_width/2, BOARD_HEIGHT/2 - text_height/2)
494 |
495 | # Blit the textObject onto the screen at the calculated position
496 | screen.blit(textObject, textLocation)
497 |
498 | # Create a second rendering of the text with a slight offset for a shadow effect
499 | textObject = font.render(text, 0, p.Color('Black'))
500 | screen.blit(textObject, textLocation.move(1, 1))
501 |
502 |
503 | # if we import main then main function wont run it will run only while running this file
504 | if __name__ == "__main__":
505 | main()
506 |
--------------------------------------------------------------------------------
/images/bB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bB.png
--------------------------------------------------------------------------------
/images/bK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bK.png
--------------------------------------------------------------------------------
/images/bN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bN.png
--------------------------------------------------------------------------------
/images/bQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bQ.png
--------------------------------------------------------------------------------
/images/bR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bR.png
--------------------------------------------------------------------------------
/images/bp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/bp.png
--------------------------------------------------------------------------------
/images/wB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wB.png
--------------------------------------------------------------------------------
/images/wK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wK.png
--------------------------------------------------------------------------------
/images/wN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wN.png
--------------------------------------------------------------------------------
/images/wQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wQ.png
--------------------------------------------------------------------------------
/images/wR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wR.png
--------------------------------------------------------------------------------
/images/wp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images/wp.png
--------------------------------------------------------------------------------
/images1/bB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bB.png
--------------------------------------------------------------------------------
/images1/bK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bK.png
--------------------------------------------------------------------------------
/images1/bN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bN.png
--------------------------------------------------------------------------------
/images1/bQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bQ.png
--------------------------------------------------------------------------------
/images1/bR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bR.png
--------------------------------------------------------------------------------
/images1/bp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/bp.png
--------------------------------------------------------------------------------
/images1/wB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wB.png
--------------------------------------------------------------------------------
/images1/wK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wK.png
--------------------------------------------------------------------------------
/images1/wN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wN.png
--------------------------------------------------------------------------------
/images1/wQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wQ.png
--------------------------------------------------------------------------------
/images1/wR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wR.png
--------------------------------------------------------------------------------
/images1/wp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/images1/wp.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pygame==2.1.2
2 |
--------------------------------------------------------------------------------
/sounds/capture.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/sounds/capture.mp3
--------------------------------------------------------------------------------
/sounds/move-sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/sounds/move-sound.mp3
--------------------------------------------------------------------------------
/sounds/promote.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anuragjain-git/chess-ai/8f14234b8f4beb61c4103f0ddd180e5739df3bf6/sounds/promote.mp3
--------------------------------------------------------------------------------