diff --git a/Chess/Images/Highlight.jpg b/Chess/Images/Highlight.jpg new file mode 100644 index 0000000..6b920cf Binary files /dev/null and b/Chess/Images/Highlight.jpg differ diff --git a/Chess/Images/PromotionMenu.jpg b/Chess/Images/PromotionMenu.jpg new file mode 100644 index 0000000..f021a05 Binary files /dev/null and b/Chess/Images/PromotionMenu.jpg differ diff --git a/Chess/Images/bB.png b/Chess/Images/bB.png new file mode 100644 index 0000000..453cb32 Binary files /dev/null and b/Chess/Images/bB.png differ diff --git a/Chess/Images/bK.png b/Chess/Images/bK.png new file mode 100644 index 0000000..225f869 Binary files /dev/null and b/Chess/Images/bK.png differ diff --git a/Chess/Images/bN.png b/Chess/Images/bN.png new file mode 100644 index 0000000..8e3d04e Binary files /dev/null and b/Chess/Images/bN.png differ diff --git a/Chess/Images/bQ.png b/Chess/Images/bQ.png new file mode 100644 index 0000000..0d94a1c Binary files /dev/null and b/Chess/Images/bQ.png differ diff --git a/Chess/Images/bR.png b/Chess/Images/bR.png new file mode 100644 index 0000000..b9748e8 Binary files /dev/null and b/Chess/Images/bR.png differ diff --git a/Chess/Images/bp.png b/Chess/Images/bp.png new file mode 100644 index 0000000..c432d38 Binary files /dev/null and b/Chess/Images/bp.png differ diff --git a/Chess/Images/chess.jpg b/Chess/Images/chess.jpg new file mode 100644 index 0000000..026f345 Binary files /dev/null and b/Chess/Images/chess.jpg differ diff --git a/Chess/Images/chess1.jpg b/Chess/Images/chess1.jpg new file mode 100644 index 0000000..2c55305 Binary files /dev/null and b/Chess/Images/chess1.jpg differ diff --git a/Chess/Images/dtb.jpg b/Chess/Images/dtb.jpg new file mode 100644 index 0000000..28cf524 Binary files /dev/null and b/Chess/Images/dtb.jpg differ diff --git a/Chess/Images/icon.png b/Chess/Images/icon.png new file mode 100644 index 0000000..49a1748 Binary files /dev/null and b/Chess/Images/icon.png differ diff --git a/Chess/Images/logo1.jpg b/Chess/Images/logo1.jpg new file mode 100644 index 0000000..dc8e890 Binary files /dev/null and b/Chess/Images/logo1.jpg differ diff --git a/Chess/Images/ltb.jpg b/Chess/Images/ltb.jpg new file mode 100644 index 0000000..843c3b9 Binary files /dev/null and b/Chess/Images/ltb.jpg differ diff --git a/Chess/Images/royal.jpg b/Chess/Images/royal.jpg new file mode 100644 index 0000000..164245b Binary files /dev/null and b/Chess/Images/royal.jpg differ diff --git a/Chess/Images/wB.png b/Chess/Images/wB.png new file mode 100644 index 0000000..26dae01 Binary files /dev/null and b/Chess/Images/wB.png differ diff --git a/Chess/Images/wK.png b/Chess/Images/wK.png new file mode 100644 index 0000000..d734164 Binary files /dev/null and b/Chess/Images/wK.png differ diff --git a/Chess/Images/wN.png b/Chess/Images/wN.png new file mode 100644 index 0000000..2d716b1 Binary files /dev/null and b/Chess/Images/wN.png differ diff --git a/Chess/Images/wQ.png b/Chess/Images/wQ.png new file mode 100644 index 0000000..a4fe68c Binary files /dev/null and b/Chess/Images/wQ.png differ diff --git a/Chess/Images/wR.png b/Chess/Images/wR.png new file mode 100644 index 0000000..a805de4 Binary files /dev/null and b/Chess/Images/wR.png differ diff --git a/Chess/Images/wp.png b/Chess/Images/wp.png new file mode 100644 index 0000000..e98fae2 Binary files /dev/null and b/Chess/Images/wp.png differ diff --git a/Chess/README.md b/Chess/README.md new file mode 100644 index 0000000..5913249 --- /dev/null +++ b/Chess/README.md @@ -0,0 +1,45 @@ +# Two Player Chess + +This is a simple two-player chess game built in Python. It uses the standard rules of chess and allows two players to play against each other on the same computer. + +## Installation + +To install and run the game, follow these steps: + +1. Clone the repository to your local machine. +2. Navigate to the project directory in your terminal. +3. Install pygame , a Python library for creating games, by running the following command: + + ``` + pip install pygame + ``` + +4. Start the game by running the following command: + + ``` + python main.py + ``` + +## How to Play + +The game is played using the standard rules of chess. Each player takes turns moving their pieces on the board until one player is in checkmate or a draw is declared. + +To move a piece, select it with your mouse and drag it to the desired square. If the move is legal, the piece will be placed on the new square. If the move is not legal, the piece will return to its original position. + +## Features + +- `En Passant`: Special pawn capture move inclusion +- `Castling`: Ability to perform the castling maneuver +- `Checkmate and Stalemate Detection`: Logic for detecting game-ending states +- `User Interface`: Graphical representation of the board with mouse controls +- `Standard Chess Rules`: Adherence to traditional chess rules +- `Two-Player Mode`: Enable two human players to compete on the same device + +## Code Overview + +The game is built using two Python files: + +- `engine.py`: This file contains the logic for the chess game, including the rules for moving pieces and checking for checkmate and stalemate. +- `main.py`: This file contains the user interface for the game, including the graphical representation of the board and the mouse controls for moving pieces. + +Build with :heart: by [Purna Shrestha](https://github.com/purnasth) \ No newline at end of file diff --git a/Chess/engine.py b/Chess/engine.py new file mode 100644 index 0000000..fba0bdf --- /dev/null +++ b/Chess/engine.py @@ -0,0 +1,599 @@ + + +class gamestate(): + def __init__(self): + # 2D 8x8 list, each element has 2 characters. + # The first character represents the color of the piece + # The second character represents the type of the piece + # "--" represents an empty space with no piece + # "wp" represents a white pawn + # "bR" represents a black rook + # "bK" represents a black king + # "wQ" represents a white queen + # and so on + self.board = [ + ["bR","bN","bB","bQ","bK","bB","bN","bR"], + ["bp","bp","bp","bp","bp","bp","bp","bp"], + ["--","--","--","--","--","--","--","--"], + ["--","--","--","--","--","--","--","--"], + ["--","--","--","--","--","--","--","--"], + ["--","--","--","--","--","--","--","--"], + ["wp","wp","wp","wp","wp","wp","wp","wp"], + ["wR","wN","wB","wQ","wK","wB","wN","wR"]] + + # dictionary to map pieces to their respective move functions + # so that proper function is called for each piece + + self.moveFunctions = {'p':self.getPawnMoves,'R':self.getRookMoves,'N':self.getKnightMoves, + 'B':self.getBishopMoves,'Q':self.getQueenMoves,'K':self.getKingMoves} + + + # we dont have to write 6 different functions for each piece + + + + + self.whitemove=True + self.moveLog = [] + self.whiteKingLocation = (7,4) + self.blackKingLocation = (0,4) + self.checkmate = False + self.stalemate = False + self.enpassantPossible = () # coordinates for the square where en passant capture is possible + self.currentCastlingRights = castleRights(True,True,True,True) + self.castleRightsLog = [castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs)] + + # pawn promotion is if white pawn reaches row 0 + # or if a black pawn reaches row 7 + + def makePawnPromotion(self,move,user_choice): + if move.pawn_promotion: + # place queen of same color at pawn's place + self.board[move.endRow][move.endCol] = move.pieceMoved[0] + user_choice + + + + + def makeMove(self,move): + # make the move and update the board + self.board[move.startRow][move.startCol] = "--" + self.board[move.endRow][move.endCol] = move.pieceMoved + self.moveLog.append(move) + # swap turns after move + self.whitemove = not self.whitemove + if move.pieceMoved == 'wK': + self.whiteKingLocation = (move.endRow,move.endCol) + if move.pieceMoved == 'bK': + self.blackKingLocation = (move.endRow,move.endCol) + + if move.enpassantPossible: + self.board[move.startRow][move.endCol] = "--" + # capturing the pawn + #update enpassantPossible variable + + # only if the pawn moves two squares ahead + # used abs so that it works for both white and black pawns + # both up the board and down the board + if move.pieceMoved[1] == 'p' and abs(move.startRow - move.endRow) == 2: + self.enpassantPossible = ((move.startRow + move.endRow)//2,move.startCol) + else: + # reset enpassantPossible + self.enpassantPossible = () + + # updateCastleRights(move) + + + if move.isCastleMove: + if move.endCol - move.startCol == 2: + # king side castle move + self.board[move.endRow][move.endCol-1] = self.board[move.endRow][move.endCol+1] + self.board[move.endRow][move.endCol+1] = "--" + else: + # queen side castle move + self.board[move.endRow][move.endCol+1] = self.board[move.endRow][move.endCol-2] + self.board[move.endRow][move.endCol-2] = "--" + + + # castling + # if king moves two squares to the right + # then rook moves one square to the left + # and vice versa + self.updateCastleRights(move) + self.castleRightsLog.append(castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs)) + + # update casting rights whenever it is a rook or a king move + # if a rook or a king moves from its starting position + # then we have to update the castling rights + + + + + + + + + # pawn promotion + + + + + def undoMove(self): + # to make sure that there is a move to undo + if len(self.moveLog) != 0: + # pop returns and removes the last element from the list + move = self.moveLog.pop() + + self.board[move.startRow][move.startCol] = move.pieceMoved + # undoing the move + self.board[move.endRow][move.endCol] = move.pieceCaptured + # to make sure the piece captured is not empty + # switch turns back + self.whitemove = not self.whitemove + if move.pieceMoved == 'wK': + self.whiteKingLocation = (move.startRow,move.startCol) + if move.pieceMoved == 'bK': + self.blackKingLocation = (move.startRow,move.startCol) + + # undo enpassantPossible + if move.enpassantPossible: + self.board[move.endRow][move.endCol] = "--" + # leave the landing square blank + self.board[move.startRow][move.endCol] = move.pieceCaptured + # redo the enpassant capture + # if i undo the move, i have to set the enpassantPossible to the square where the enpassant capture was possible + self.enpassantPossible = (move.endRow,move.endCol) + # undo 2 square pawn advance + if move.pieceMoved[1] == 'p' and abs(move.startRow - move.endRow) == 2: + self.enpassantPossible = () + self.castleRightsLog.pop() + self.currentCastlingRights = self.castleRightsLog[-1] + self.currentCastlingRights = castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs) + # undo castling rights + # if a rook or a king moves from its starting position + # then we have to update the castling rights + # if a rook or a king moves from its starting position + # then we have to update the castling rights + if move.isCastleMove: + if move.endCol - move.startCol == 2: + # king side castle move + self.board[move.endRow][move.endCol+1] = self.board[move.endRow][move.endCol-1] + self.board[move.endRow][move.endCol-1] = "--" + else: + # queen side castle move + self.board[move.endRow][move.endCol-2] = self.board[move.endRow][move.endCol+1] + self.board[move.endRow][move.endCol+1] = "--" + self.checkmate = False + self.stalemate = False + + def updateCastleRights(self,move): + if move.pieceMoved == 'wK': + self.currentCastlingRights.wks = False + self.currentCastlingRights.wqs = False + elif move.pieceMoved == 'bK': + self.currentCastlingRights.bks = False + self.currentCastlingRights.bqs = False + elif move.pieceMoved == 'wR': + if move.startRow == 7: + if move.startCol == 0: + self.currentCastlingRights.wqs = False + elif move.startCol == 7: + self.currentCastlingRights.wks = False + elif move.pieceMoved == 'bR': + if move.startRow == 0: + if move.startCol == 0: + self.currentCastlingRights.bqs = False + elif move.startCol == 7: + self.currentCastlingRights.bks = False + def getvalidmoves(self): + for log in self.moveLog: + print(log.getChessNotation()) + # to store a copy of the enpassantPossible variable + temp_enpassantPossible = self.enpassantPossible + tempCastleRights = castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs) + # 1. generate all possible moves + moves = self.getAllPossibleMoves() + + # 2. for each move, make the move + + if self.whitemove: + self.getCastleMoves(self.whiteKingLocation[0],self.whiteKingLocation[1],moves) + else: + self.getCastleMoves(self.blackKingLocation[0],self.blackKingLocation[1],moves) + + + # while removing an element from a list, we have to traverse the list backwards + # because the indexes change after removing the element + + + + for i in range(len(moves)-1,-1,-1): + + # make move + self.makeMove(moves[i]) + + # 3. generate all possible moves for the opponent + # 4. for each of your opponent's move, see if they attack your king + self.whitemove = not self.whitemove + if self.inCheck(): + moves.remove(moves[i]) + self.whitemove = not self.whitemove + self.undoMove() + + + # 3. generate all possible moves for the opponent + # 4. for each of your opponent's move, see if they attack your king + if len(moves) == 0: + if self.inCheck(): + self.checkmate = True + else: + self.stalemate = True + # if we undo the move, we have to set the checkmate and stalemate to false + else: + self.checkmate = False + self.stalemate = False + + # because we are not making any move + # we have to reset the enpassantPossible variable + # to its original value + + self.enpassantPossible = temp_enpassantPossible + + + # reset + self.currentCastlingRights = tempCastleRights + + + return moves + + def inCheck(self): + if self.whitemove: + return self.squareUnderAttack(self.whiteKingLocation[0],self.whiteKingLocation[1]) + else: + return self.squareUnderAttack(self.blackKingLocation[0],self.blackKingLocation[1]) + + def squareUnderAttack(self,r,c): + self.whitemove = not self.whitemove + opp_moves = self.getAllPossibleMoves() + self.whitemove = not self.whitemove + for move in opp_moves: + if move.endRow == r and move.endCol == c: + return True + return False + + + + + + + # all moves without considering checks + def getAllPossibleMoves(self): + + # + + + + # empty list for storing all possible moves + poss_moves = [] + # loop through all the squares in the board using nested for loop + for rows in range(len(self.board)): + for columns in range(len(self.board[rows])): + + # checking the first char of pieces + # assigning moves to pieces according to their color + turn = self.board[rows][columns][0] + if (turn == 'w' and self.whitemove) or (turn == 'b' and not self.whitemove): + # if the piece is a pawn + # because each piece has its own set of rules + piece = self.board[rows][columns][1] + self.moveFunctions[piece](rows,columns,poss_moves) + # calls the proper function for each piece + + + + return poss_moves + + def getPawnMoves(self,rows,columns,poss_moves): + # if white pawn + # + if self.whitemove: + # if the square in front of the pawn is empty + if self.board[rows-1][columns] == "--": + # going a row ahead not diagonal so column reamins the same + poss_moves.append(Move((rows,columns),(rows-1,columns),self.board)) + + # if the pawn is in its starting position + # for pawns first move + # we can move two squares ahead + # so append move rows-2 + + # check two conditions for this + # 1. the square two squares ahead is empty + # pawn is at row 6 (its starting position) + if rows == 6 and self.board[rows-2][columns] == "--": + poss_moves.append(Move((rows,columns),(rows-2,columns),self.board)) + # mark this square by photo + + + + if columns-1 >= 0: + if self.board[rows-1][columns-1][0] == 'b': + poss_moves.append(Move((rows,columns),(rows-1,columns-1),self.board)) + elif (rows-1,columns-1) == self.enpassantPossible: + # if the square is the enpassant square + # then we can capture the pawn + # so we have to add the move + poss_moves.append(Move((rows,columns),(rows-1,columns-1),self.board,enpassantPossible = True)) + if columns+1 <= 7: + if self.board[rows-1][columns+1][0] == 'b': + poss_moves.append(Move((rows,columns),(rows-1,columns+1),self.board)) + elif (rows-1,columns+1) == self.enpassantPossible: + # if the square is the enpassant square + # then we can capture the pawn + # so we have to add the move + poss_moves.append(Move((rows,columns),(rows-1,columns+1),self.board,enpassantPossible = True)) + else: + if self.board[rows+1][columns] == "--": + poss_moves.append(Move((rows,columns),(rows+1,columns),self.board)) + if rows == 1 and self.board[rows+2][columns] == "--": + poss_moves.append(Move((rows,columns),(rows+2,columns),self.board)) + if columns-1 >= 0: + if self.board[rows+1][columns-1][0] == 'w': + poss_moves.append(Move((rows,columns),(rows+1,columns-1),self.board)) + elif (rows+1,columns-1) == self.enpassantPossible: + # if the square is the enpassant square + # then we can capture the pawn + # so we have to add the move + poss_moves.append(Move((rows,columns),(rows+1,columns-1),self.board,enpassantPossible = True)) + if columns+1 <= 7: + if self.board[rows+1][columns+1][0] == 'w': + poss_moves.append(Move((rows,columns),(rows+1,columns+1),self.board)) + elif (rows+1,columns+1) == self.enpassantPossible: + # if the square is the enpassant square + # then we can capture the pawn + # so we have to add the move + poss_moves.append(Move((rows,columns),(rows+1,columns+1),self.board,enpassantPossible = True)) + def getRookMoves(self,rows,columns,poss_moves): + + # to get the direction like vector without changing the value of the original tuple + # back row,back column,forward row,forward column + directions = ((-1,0),(0,-1),(1,0),(0,1)) + # white rook --> wR + # black rook --> bR + # so we can use the first character to check the color of the piece + # and the second character to check the type of the piece + # so we can use the second character to check the type of the piece + # so we can use the second character to check the type of the piece + enemy_color = "b" if self.whitemove else "w" + for d in directions: + for i in range(1,8): + + # direction into magnitude + endRow = rows + d[0]*i + + # kaha into kitne se + endCol = columns + d[1]*i + # if the square is on the board + + if 0 <= endRow < 8 and 0 <= endCol < 8: + # square is on the board + endPiece = self.board[endRow][endCol] + # if the square is empty + + # append the moves till the squares are empty + if endPiece == "--": + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + elif endPiece[0] == enemy_color: + # move to capture the piece + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + break + # cant go over the enemy piece + else: + # cant capture alsi + break + # cant go over your own piece + else: + # all squares are out of board + break + + + def getKnightMoves(self,rows,columns,poss_moves): + knight_moves = ((-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)) + ally_color = "w" if self.whitemove else "b" + for m in knight_moves: + endRow = rows + m[0] + endCol = columns + m[1] + if 0 <= endRow < 8 and 0 <= endCol < 8: + + endPiece = self.board[endRow][endCol] + if endPiece[0] != ally_color: + # enemy hai kya + # nahi na + # toh move + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + + + def getBishopMoves(self,rows,columns,poss_moves): + # bishop can move diagonally + # left diagonal down, left diagonal up, right diagonal down, right diagonal up + directions = ((-1,-1),(-1,1),(1,-1),(1,1)) + enemy_color = "b" if self.whitemove else "w" + for d in directions: + # maximum square limit + for i in range(1,8): + endRow = rows + d[0]*i + endCol = columns + d[1]*i + if 0 <= endRow < 8 and 0 <= endCol < 8: + endPiece = self.board[endRow][endCol] + if endPiece == "--": + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + elif endPiece[0] == enemy_color: + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + break + else: + break + else: + break + + + def getQueenMoves(self,rows,columns,poss_moves): + + self.getBishopMoves(rows,columns,poss_moves) + self.getRookMoves(rows,columns,poss_moves) + + def getKingMoves(self,rows,columns,poss_moves): + # all squares surrounding the king + directions = ((-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)) + + # but just one move + + ally_color = "w" if self.whitemove else "b" + + for d in directions: + # traversing for all possible moves + + endRow = rows + d[0] + endCol = columns + d[1] + + # filtering out the moves that are out of board + if 0 <= endRow < 8 and 0 <= endCol < 8: + + endPiece = self.board[endRow][endCol] + + # not running on a ally piece + if endPiece[0] != ally_color: + poss_moves.append(Move((rows,columns),(endRow,endCol),self.board)) + + # self.getCastleMoves(rows,columns,poss_moves) + + + def getCastleMoves(self,rows,columns,poss_moves): + if self.squareUnderAttack(rows,columns): + return + if (self.whitemove and self.currentCastlingRights.wks) or (not self.whitemove and self.currentCastlingRights.bks): + self.getKingSideCastleMoves(rows,columns,poss_moves) + if (self.whitemove and self.currentCastlingRights.wqs) or (not self.whitemove and self.currentCastlingRights.bqs): + self.getQueenSideCastleMoves(rows,columns,poss_moves) + + def getKingSideCastleMoves(self,rows,columns,poss_moves): + if self.board[rows][columns+1] == '--' and self.board[rows][columns+2] == '--': + if not self.squareUnderAttack(rows,columns+1) and not self.squareUnderAttack(rows,columns+2): + poss_moves.append(Move((rows,columns),(rows,columns+2),self.board,isCastleMove = True)) + + + def getQueenSideCastleMoves(self,rows,columns,poss_moves): + if self.board[rows][columns-1] == '--' and self.board[rows][columns-2] == '--' and self.board[rows][columns-3] == '--': + if not self.squareUnderAttack(rows,columns-1) and not self.squareUnderAttack(rows,columns-2): + poss_moves.append(Move((rows,columns),(rows,columns-2),self.board,isCastleMove = True)) + + +class castleRights(): + def __init__(self,wks,bks,wqs,bqs): + self.wks = wks + self.bks = bks + self.wqs = wqs + self.bqs = bqs + # white king side, black king side, white queen side, black queen side + + + + + + + + + + +class Move(): + # map position from rows and columns to ranks and files in chess + + + # so using dictionaries to map + ranksToRows = {"1":7,"2":6,"3":5,"4":4, + "5":3,"6":2,"7":1,"8":0} + + # reversing the above dictionary + rowsToRanks = {v:k for k,v in ranksToRows.items()} + + + # using for converting columns to files + filesToCols = {"a":0,"b":1,"c":2,"d":3, + "e":4,"f":5,"g":6,"h":7} + colsToFiles = {v:k for k,v in filesToCols.items()} + + + # fn with optional parameter + def __init__(self,start_sq,end_sq,board,enpassantPossible = False,isCastleMove = False): + # start_sq + # source + + # end_sq + # destination + + # board state passed to validate the move and store information about the move + # what piece was captured? --> information + + # for first tuple that is sq_selected in player_clicks + self.startRow = start_sq[0] + self.startCol = start_sq[1] + + # for second tuple that is sq_selected in player_clicks + self.endRow = end_sq[0] + self.endCol = end_sq[1] + + # refers to pos in board + self.pieceMoved = board[self.startRow][self.startCol] # piece moved + + # refers to pos in board + self.pieceCaptured = board[self.endRow][self.endCol] # piece captured + self.moveID = self.startRow*1000 + self.startCol*100 + self.endRow*10 + self.endCol + + # default value for flag + self.pawn_promotion = False + # conditions of location and piece for pawn promotion + if (self.pieceMoved == "wp" and self.endRow == 0) or (self.pieceMoved == "bp" and self.endRow == 7): + self.pawn_promotion = True + + # flag for en passant move + self.enpassantPossible = enpassantPossible + if self.enpassantPossible: + self.pieceCaptured = 'wp' if self.pieceMoved == 'bp' else 'bp' + + self.isCastleMove = isCastleMove + # if pawn is moving two squares ahead + # if self.pieceMoved[1] == 'p' and abs(self.startRow - self.endRow) == 2: + # self.isEnpassantMove = True + # if pawn is moving two squares ahead + # if the pawn moves two squares ahead + + + + + + # self.promotionChoice = "Q" + + # we could write pawn promotion flags in the getpawnmoves itself but we chose this because of less new code to be written here + print(self.moveID) + + + def __eq__(self, other): + # comparing this object to another object + + # to ensure that we are comparing two move objects and not some other class object + if isinstance(other,Move): + return self.moveID == other.moveID + return False + + def getChessNotation(self): + # you can add to make this like real chess notation + return self.pieceMoved + "to" + self.getRankFile(self.startRow,self.startCol) + self.getRankFile(self.endRow,self.endCol) + + def getRankFile(self,rows,columns): + return self.colsToFiles[columns] + self.rowsToRanks[rows] + + # castling + # king cannot move to a square that is under attack + # sqs clear + # sqs cannot be under attack + # king didnt move + # king not in check + # first move of king and rook \ No newline at end of file diff --git a/Chess/main.py b/Chess/main.py new file mode 100644 index 0000000..bc10559 --- /dev/null +++ b/Chess/main.py @@ -0,0 +1,326 @@ +import pygame as p +import engine + + +# square window for our game. +# can change screen size from here +screen_width = screen_height = 550 +screen_caption = "Two Player Chess by Purna" +icon = p.image.load(r"Images\icon.png") + +# rows and columns + +dimensions = 8 + +# making sqaures in the screen to display chess board boxes +sq_size = screen_height // dimensions + +fps = 30 +# to pass as an argument in clock.tick +# adjust if game become laggy + +images = {} + +def load_images(): + + # load all images once as it is cpu heavy task + pieces = ["wp", "wR", "wN", "wB", "wQ", "wK", "bp", "bR", "bN", "bB", "bQ", "bK"] + for piece in pieces: + image_path = r"Images" + "\\" + piece + ".png" + + images[piece] = p.transform.scale(p.image.load(image_path).convert_alpha(), (sq_size, sq_size)) + + # pygame.transform.scale to adjust the image + +def main(): + p.init() + + + + # os.system("welcome.mp3") + + + # setting screen with sizes + + # closing our face detection window + + + screen = p.display.set_mode((screen_width,screen_height), p.HWSURFACE | p.DOUBLEBUF) + + + + p.display.set_caption(screen_caption) + p.display.set_icon(icon) + p.display.update() + # clock object + clock = p.time.Clock() + # fps change karega to limit CPU in clock.tick(15) + + screen.fill(p.Color("white")) + # aise hi + + # creating a gamestate object joh ki constructor ko call karega apne + # dot operator to call gamestate() in engine + + gs = engine.gamestate() + + + # to store valid moves + valid_moves = gs.getvalidmoves() + + + # print(gs.board) + move_made = False + # to update valid moves only when a move is made + + # flag variable for when a move is made + # loading the images "once" + load_images() + + # running variable to check start and quit + running = True + # tuple to keep the last square selected + sq_selected = () + # no square is selected at start + # tuple: (row,col) + # playerClicks = [] + + # list to keep two inputs + player_clicks = [] + # keep track of player clicks (two tuples: [(6, 4), (4, 4)]) + + + done = True + + chess = p.transform.scale_by(p.image.load(r"Images\chess.jpg"),0.25) + screen.fill(p.Color("black")) + while done: + + screen.blit(chess,p.Rect(200-5*sq_size + 180,200-5*sq_size + 200,10,10)) + screen.blit(p.transform.scale_by(icon,0.5),p.Rect(470-5*sq_size+15,screen_height/2-270,10,10)) + showtext(screen, "Welcome to Chess", (screen_height/2 - 230,screen_height/2 - 10), 40) + showtext(screen, "Press any key to start the game", (screen_height/2 - 220,screen_height/2+50),25) + + + p.display.flip() + for event in p.event.get(): + if event.type == p.QUIT: + p.quit() + if event.type == p.KEYDOWN: + done = False + # showtext(screen, predicted_name + " is playing") + + + + + + # start of my gameloop + while running: + + # lets keep a for loop to get events + for event in p.event.get(): + # print(p.display.Info()) + # if the type of event is this + + if event.type == p.QUIT: + # to exit the whileloop + running = False + elif event.type == p.MOUSEBUTTONDOWN: + # mouse kaha h? + mouse_location = p.mouse.get_pos() # (x,y) location of mouse + # get x and y from list + column = mouse_location[0]//sq_size + row = mouse_location[1]//sq_size + + + # first click is select, second click is undo + if sq_selected == (row,column): + # user clicks same sqaure again + sq_selected = () # undo + player_clicks = [] + else: + # store the square selected by the user now + sq_selected = (row,column) + player_clicks.append(sq_selected) + # first time it will append to empty list then it appends to list[0] + + + # hume pata karna hai user ka first click hai ya second + if len(player_clicks)==2: + + # do clicks hogye toh bolenge make move + # so call the move class constructor + move = engine.Move(player_clicks[0],player_clicks[1],gs.board) + print(move.getChessNotation()) + + # player_clicks[0] is our source + # player_clicks[1] is our piece's destination + for i in range(len(valid_moves)): + + # only get valid move object + # so check for it + + if move == valid_moves[i]: + + gs.makeMove(valid_moves[i]) + user_choice = "Q" + while move.pawn_promotion: + p.display.set_caption("Choose a piece to promote to") + screen.fill(p.Color("black")) + screen.blit(p.transform.scale_by(p.image.load(r"Images\PromotionMenu.jpg"),0.2),p.Rect(200-sq_size,200-sq_size,10,10)) + showtext(screen, "Enter the corresponding character of the piece you want to promote to :", (200,200),12) + p.display.flip() + user_choice = "" + for event in p.event.get(): + if event.type == p.KEYDOWN: + if event.key == p.K_q: + gs.makePawnPromotion(move,"Q") + move.pawn_promotion=False + + elif event.key == p.K_r: + gs.makePawnPromotion(move,"R") + move.pawn_promotion=False + + elif event.key == p.K_b: + gs.makePawnPromotion(move,"B") + move.pawn_promotion=False + elif event.key == p.K_n: + gs.makePawnPromotion(move,"N") + move.pawn_promotion=False + else: + gs.makePawnPromotion(move,"Q") + move.pawn_promotion=False + p.display.set_caption("ChessAI") + + + + + # argument to makemove is generated by the engine + + + + move_made = True + + sq_selected = () # reset user clicks + player_clicks = [] + + + + # reset the user clicks after making the move each time + if not move_made: + player_clicks = [sq_selected] + + + #gs.makeMove(move) + # to make the move + + elif event.type == p.KEYDOWN: + if event.key == p.K_z: + gs.undoMove() + + move_made = True + # when the user undoes a move the valid moves change + # so change the flag variable to true + + # to update the valid moves + if move_made: + valid_moves = gs.getvalidmoves() + move_made = False + + + + # calling the draw boardand pieces fn + draw_game_state(screen,gs) + clock.tick(fps) + p.display.flip() + # to update the display + +# method to draw sqs on board and graphics of a current gamestate +def draw_game_state(screen,gs): + + # to draw squares on the board + drawboard(screen) + + #board-->pieces order ofc matter karega nhi toh pieces piche chip jayenge + + # to draw pieces + drawpieces(screen,gs.board) # board from engine gamestate ka object gs , isliye dot + +def drawboard(screen): + # lets draw squares + # white and grey alternate + # make list to store white and grey switch karna easy hoga + # colors = [p.Color("white"), p.Color("dark gray")] + images = [p.image.load(r"images\ltb.jpg").convert_alpha(),p.image.load(r"images\dtb.jpg").convert_alpha()] + + for rows in range(dimensions): + for columns in range(dimensions): + # [00,10,20,30,40,50,60,70] + # [01,11,21,31,41,51,61,71] + # [02,12,22,32,42,52,62,72] + # [03,13,23,33,43,53,63,73] + # [04,14,24,34,44,54,64,74] + # [05,15,25,35,45,55,65,75] + # [06,16,26,36,46,56,66,76] + # [07,17,27,37,47,57,67,77] + + # trend we see here is that if we add rows and columns + # dark sqaures are odd + # light sqaures are even + + # color = colors[(rows+columns)%2] + image = images[(rows+columns)%2] + # even --> colors[0] --> white + # odd --> colors[1] --> black + + # smpart + + # just draw rectangle (surface,color,) + custom_img = p.Surface((sq_size,sq_size)) + + screen.blit(image,p.Rect(columns*sq_size,rows*sq_size,sq_size,sq_size)) + + # p.draw.rect(screen, color, p.Rect(columns*sq_size,rows*sq_size, sq_size, sq_size)) + +def drawpieces(screen,board): + for rows in range(dimensions): + for columns in range(dimensions): + pieces = board[rows][columns] + if pieces != "--": + screen.blit(images[pieces],p.Rect(columns*sq_size,rows*sq_size,sq_size,sq_size)) + # accessing our gs.board multi dim list by using [][] + # to assign each square a piece + +# function to show a menu an ask the user the piece to promote to in pawn promotion + + + + + + + + +def showtext(screen,text,location,fontsize): + font = p.font.SysFont("Copperplate gothic", fontsize, True, False) + textObject = font.render(text, 0, p.Color('White')) + location1 = p.Rect(location, location) + # textLocation = p.Rect(0, 0, screen_width, screen_height).move(screen_width / 2 - textObject.get_width() / 2, screen_height / 2 - textObject.get_height() / 2) + # white = p.Color("black") + # screen.blit(white,p.rect(textLocation,textLocation,200,200)) + screen.blit(textObject, location1) + + + + + + + + +# if we import something in the main code we need to do this cause it wont run otherwise +# THIS CODE WE HAVE TO RUN AS THIS IS OUR MAIN CODE AND WE IMPORT OTHER MODULES IN THIS CODE +# SO WE WRITE THIS +# The if __name__ == "__main__": construct is used to +# ensure that a specific block of code only runs when the Python script is executed directly, +# not when it's imported as a module in another script. +if __name__=="__main__": + main() \ No newline at end of file