├── 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 | ![Untitled video - Made with Clipchamp](https://github.com/anuragjain-git/chess/assets/98457054/bfdea7e5-502b-4852-a2c9-115ea32e45da) 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 | Chessboard 45 | 46 | 2. **Opponent's Move:** 47 | 48 | Chessboard 49 | 50 | 3. **En Passant Capture:** 51 | 52 | Chessboard 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 | Chessboard 61 | 62 | 2. **Select the Promotion Piece**: 63 | 64 | Chessboard 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 --------------------------------------------------------------------------------