├── 4. Valid Moves Advanced ├── README.md ├── ChessMain.py ├── ChessEngine.py └── ChessEngineAd.py ├── 5. Pawn Promotion and En - Passant ├── README.md ├── ChessMain.py └── ChessEngine.py ├── 6. EnPassant and Pawn Promotion in Advanced Algo ├── README.md ├── ChessMain.py └── ChessEngine.py ├── 12. Move Log ├── README.md ├── ChessBot.py └── ChessMain.py ├── images ├── bB.png ├── bK.png ├── bN.png ├── bP.png ├── bQ.png ├── bR.png ├── wB.png ├── wK.png ├── wN.png ├── wP.png ├── wQ.png ├── wR.png └── README.md ├── 10. Recursive Min Max ├── README.md ├── ChessBot.py └── ChessMain.py ├── 11. NegaMax Alpha Beta Pruning ├── README.md ├── ChessBot.py └── ChessMain.py ├── 7. Castling ├── README.md └── ChessMain.py ├── 9. Random Move BOT ├── README.md ├── ChessBot.py └── ChessMain.py ├── 8. Castling Advanced ├── README.md └── ChessMain.py ├── 3. Valid Moves ├── README.md ├── ChessMain.py └── ChessEngine.py ├── 2 step Min Max BOT ├── README.md ├── ChessBot.py └── ChessMain.py ├── 1. Basic Board and Piece Movement ├── README.md ├── ChessEngine.py └── ChessMain.py ├── 2. All Possible Moves ├── README.md ├── ChessMain.py └── ChessEngine.py ├── config.py ├── README.md ├── .github └── workflows │ └── azure-webapps-python.yml ├── ChessBot.py └── ChessMain.py /4. Valid Moves Advanced/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /5. Pawn Promotion and En - Passant/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /6. EnPassant and Pawn Promotion in Advanced Algo/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /12. Move Log/README.md: -------------------------------------------------------------------------------- 1 | #### This folder contains files for Move Log display -------------------------------------------------------------------------------- /images/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bB.png -------------------------------------------------------------------------------- /images/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bK.png -------------------------------------------------------------------------------- /images/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bN.png -------------------------------------------------------------------------------- /images/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bP.png -------------------------------------------------------------------------------- /images/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bQ.png -------------------------------------------------------------------------------- /images/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/bR.png -------------------------------------------------------------------------------- /images/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wB.png -------------------------------------------------------------------------------- /images/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wK.png -------------------------------------------------------------------------------- /images/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wN.png -------------------------------------------------------------------------------- /images/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wP.png -------------------------------------------------------------------------------- /images/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wQ.png -------------------------------------------------------------------------------- /images/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monil-bansal/Chess/HEAD/images/wR.png -------------------------------------------------------------------------------- /10. Recursive Min Max/README.md: -------------------------------------------------------------------------------- 1 | ### This folder contains code for recursive Min Max with Variable Depth -------------------------------------------------------------------------------- /11. NegaMax Alpha Beta Pruning/README.md: -------------------------------------------------------------------------------- 1 | ### This folder contains code for NegaMax Algorithm with Alpha Beta Pruning 2 | -------------------------------------------------------------------------------- /7. Castling/README.md: -------------------------------------------------------------------------------- 1 | #### This Folder contains the code till castling. 2 | 3 | ###### Highlightinh the valid moves and making the bot is still left. -------------------------------------------------------------------------------- /9. Random Move BOT/README.md: -------------------------------------------------------------------------------- 1 | #### These files contains the code of a chess game against a BOT which chooses a move at random from a list of valid moves. -------------------------------------------------------------------------------- /9. Random Move BOT/ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def findRandomMove(validMoves): 5 | return validMoves[random.randint(0, len(validMoves) - 1)] 6 | -------------------------------------------------------------------------------- /8. Castling Advanced/README.md: -------------------------------------------------------------------------------- 1 | #### This Folder contains the code till castling with optimised algorithm. 2 | 3 | ###### Highlightinh the valid moves and making the bot is still left. -------------------------------------------------------------------------------- /3. Valid Moves/README.md: -------------------------------------------------------------------------------- 1 | ## Generating a list of valid moves: 2 | 1) only allowing valid moves 3 | 2) Considering Checks, Checkmates and Stalemates 4 | 5 | 6 | NOTE: CASTLING, PAWN PROMOTION AND EN-PASSANT NOT CODED TILL NOW. 7 | -------------------------------------------------------------------------------- /2 step Min Max BOT/README.md: -------------------------------------------------------------------------------- 1 | ### This folder contains files for an iterative solution of MinMax algorithm implemented for the BOT. 2 | 3 | The BOT looks 2 steps in the future and decides the best move according to the pieces on the board. -------------------------------------------------------------------------------- /1. Basic Board and Piece Movement/README.md: -------------------------------------------------------------------------------- 1 | # This folder contains the files for the basic board and moves 2 | 3 | Note: There is no validation on the moves what so ever -> a same color can move any number of times, a King can be captured, etc. 4 | -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | # A folder for all the chess pieces 2 | 3 | Naming : 4 | the name consists of 2 characters : 5 | - 1) (w or b) : denoting the color of piece -> white or black piece respectively 6 | - 2) (P, R, N, B, Q or K) : denoting the type of piecee -> Pawn, Rook, Knight, Bishop, Queen or King respectively 7 | -------------------------------------------------------------------------------- /2. All Possible Moves/README.md: -------------------------------------------------------------------------------- 1 | ## This folder contains all the code for generating ALL POSSIBLE MOVES 2 | 1) This is without thinking about invalid moves, checks and checkmates. 3 | 2) Castling is not included till now 4 | 3) En - passant is not considered till now 5 | 6 | ----------------------- 7 | #### BUT a piece can't capture a same color piece, also pieces moves according to rule -> Knight moves 2.5 squares. 8 | #### AND white and black pieces take turns 9 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This Files contains all the values that you can play with to modify the board. 3 | 4 | 5 | Other BOARD_COLOR_DARK value that you may try : (110, 110, 110) -> grey color 6 | 7 | Default Chess.com Colors : (235, 235, 208) -> Light || (119, 148, 85) -> Dark 8 | (178,135,108) 9 | (241,223,197) 10 | ''' 11 | 12 | BOARD_WIDTH = BOARD_HEIGHT = 480 # Width and Height for the board (only the playing area) 13 | MOVE_LOG_PANNEL_WIDTH = 250 # Width of the 12. Move Log 14 | MOVE_LOG_PANNEL_HEIGHT = BOARD_HEIGHT # Height of the 12. Move Log 15 | DIMENTION = 8 # 8*8 CHESS BOARD # Dimention of the Chess Board 16 | MAX_FPS = 15 # Max Frames Per Second for the animation 17 | MOVE_LOG_FONT_NAME = 'Arial' # Font for the move log 18 | MOVE_LOG_FONT_SIZE = 12 # Size for the move log 19 | PLAYER_ONE_HUMAN = True # Is player1 -> WHITE played by human || False -> for BOT 20 | PLAYER_TWO_HUMAN = False # Is player2 -> BLACK played by human || False -> for BOT 21 | IMAGE_FOLDER = './images/' # Folder Where images are stored for the pieces 22 | BOARD_COLOR_LIGHT = (241, 223, 197) # Color (in R, G, B) for the Light squares of the board 23 | BOARD_COLOR_DARK = (178, 135, 108) # Color (in R, G, B) for the Dark Squares of the board 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Chess 4 | 5 | 6 | ![ezgif com-gif-maker](https://user-images.githubusercontent.com/31930832/121518639-589d7880-ca0e-11eb-9dd8-2b06dc1378ee.gif) 7 | 8 | 9 | ### Files : 10 | 1) ChessMain.py: Contains the code to handle user input and draw the game visuals 11 | 2) ChessEngine.py : Contains the logic of the Chess game (Using Brute Force Algorithm for calculating valid moves) 12 | 3) ChessEngineAd.py : Contains the logic of the Chess game (Using Optimised Algorithm for calculating valid moves) 13 | 4) ChessBot.py : Contains the logic for the BOT to play smartly considering captures, defences and positional advantages to some extent. 14 | 15 | ###Library to be installed for this Chess engine to work : 16 | - pyGame: `pip3 install pygame` 17 | 18 | ### To play the game using optimised algorithm run the following command in the terminal : 19 | `python3 ChessMain.py adv` or `python ChessMain.py adv` 20 | 21 | ### To play the game using brute force algorithm run the following command in the terminal : 22 | `python3 ChessMain.py` or `python ChessMain.py` 23 | 24 | -------- 25 | 26 | #### To choose whether to play a 2 player game or vs Computer or just see Computer Playing against itself make the following changes in the Config File: 27 | 28 | - For Playing as White (Default)                        : Set PLAYER_ONE_HUMAN = True and PLAYER_TWO_HUMAN = False 29 | 30 | - For Playing as Black                                        : Set PLAYER_TWO_HUMAN = True and PLAYER_ONE_HUMAN = False 31 | - For 2 Player Game                                          : Set both PLAYER_ONE_HUMAN and PLAYER_TWO_HUMAN = True 32 | - For seeing Computer play from both sides   : Set both PLAYER_ONE_HUMAN and PLAYER_TWO_HUMAN = False 33 | 34 | -------------------------------------------------------------------------------- /1. Basic Board and Piece Movement/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [ 16 | ['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 17 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['--', '--', '--', '--', '--', '--', '--', '--'], 22 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 23 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 24 | self.whiteToMove = True 25 | self.moveLog = [] 26 | 27 | def makeMove(self, move): 28 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 29 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 30 | self.moveLog.append(move) # record the move 31 | self.whiteToMove = not self.whiteToMove # swap the turn 32 | 33 | class Move(): 34 | 35 | #maps keys to values 36 | #For converting (row, col) to Chess Notations => (0,0) -> a8 37 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 38 | "5": 3, "6": 2, "7": 1, "8": 0 } 39 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 40 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 41 | "e":4, "f":5, "g":6, "h":7} 42 | colsToFiles = {v:k for k,v in filesToCols.items()} 43 | 44 | def __init__(self, startSq, endSq, board): 45 | self.startRow = startSq[0] 46 | self.startCol = startSq[1] 47 | self.endRow = endSq[0] 48 | self.endCol = endSq[1] 49 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 50 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 51 | 52 | def getChessNotation(self): 53 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 54 | 55 | def getFileRank (self, r, c): 56 | return self.colsToFiles[c] + self.rowsToRanks[r] 57 | 58 | 59 | -------------------------------------------------------------------------------- /2 step Min Max BOT/ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # A map of piece to score value -> Standard chess scores 4 | pieceScore = {'K': 0, "P": 1, "N": 3, "B": 3, "R": 5, "Q": 9} #making King = 0, as no one can actually take the king 5 | CHECKMATE = 1000 # if you lead to checkmate you win -> hence max attainable score 6 | STALEMATE = 0 # If you can win(capture opponent's piece) avoid it but if you loosing(opponent can give you Checkmate) try it hence 0 and not -1000 7 | 8 | ''' 9 | Function to calculate RANDOM move from the list of valid moves. 10 | ''' 11 | def findRandomMove(validMoves): 12 | if len(validMoves) > 0: 13 | return validMoves[random.randint(0, len(validMoves) - 1)] 14 | 15 | 16 | ''' 17 | Function to find the BEST move from the list of valid moves 18 | ''' 19 | def findBestMove(gs, validMoves): 20 | turnMultiplier = 1 if gs.whiteToMove else -1 # for allowing AI to play as any color 21 | playerMaxScore = -CHECKMATE # as AI is playing Black this is the worst possible score -> AI will start from worst and try to improve 22 | bestMove = None 23 | random.shuffle(validMoves) 24 | for playerMove in validMoves: # not assigning colors so AI can play as both: playerMove -> move of the current player || opponentMove -> opponent's move 25 | gs.makeMove(playerMove) 26 | opponentMinScore = CHECKMATE 27 | opponentMoves = gs.getValidMoves() 28 | if gs.checkMate: 29 | gs.undoMove() 30 | return playerMove 31 | elif gs.staleMate: 32 | opponentMinScore = STALEMATE 33 | else: 34 | for opponentMove in opponentMoves: 35 | gs.makeMove(opponentMove) 36 | gs.getValidMoves() 37 | if gs.checkMate: 38 | score = -CHECKMATE 39 | elif gs.staleMate: 40 | score = STALEMATE 41 | else: 42 | score = turnMultiplier * boardScore(gs.board) 43 | if score < opponentMinScore: 44 | opponentMinScore = score 45 | gs.checkMate = False 46 | gs.staleMate = False 47 | gs.undoMove() 48 | if playerMaxScore < opponentMinScore: 49 | playerMaxScore = opponentMinScore 50 | bestMove = playerMove 51 | gs.undoMove() 52 | return bestMove 53 | 54 | 55 | ''' 56 | Gives the score of the board according to the material on it -> White piece positive material and Black piece negative material. 57 | Assuming that Human is playing White and BOT is playing black 58 | ''' 59 | def boardScore(board): 60 | score = 0 61 | for row in board: 62 | for square in row: 63 | if square[0] == 'w': 64 | score += pieceScore[square[1]] 65 | elif square[0] == 'b': 66 | score -= pieceScore[square[1]] 67 | return score 68 | 69 | 70 | -------------------------------------------------------------------------------- /.github/workflows/azure-webapps-python.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a Python application to an Azure Web App when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure App Service web app. 4 | # For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=bash&pivots=python-framework-flask 5 | # 6 | # To configure this workflow: 7 | # 8 | # 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal. 9 | # For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials 10 | # 11 | # 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret. 12 | # For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret 13 | # 14 | # 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the PYTHON_VERSION environment variables below. 15 | # 16 | # For more information on GitHub Actions for Azure: https://github.com/Azure/Actions 17 | # For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 18 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples 19 | 20 | name: Build and deploy Python app to Azure Web App 21 | 22 | env: 23 | AZURE_WEBAPP_NAME: your-app-name # set this to the name of your Azure Web App 24 | PYTHON_VERSION: '3.8' # set this to the Python version to use 25 | 26 | on: 27 | push: 28 | branches: [ "main" ] 29 | workflow_dispatch: 30 | 31 | permissions: 32 | contents: read 33 | 34 | jobs: 35 | build: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v3 40 | 41 | - name: Set up Python version 42 | uses: actions/setup-python@v3.0.0 43 | with: 44 | python-version: ${{ env.PYTHON_VERSION }} 45 | cache: 'pip' 46 | 47 | - name: Create and start virtual environment 48 | run: | 49 | python -m venv venv 50 | source venv/bin/activate 51 | 52 | - name: Install dependencies 53 | run: pip install -r requirements.txt 54 | 55 | # Optional: Add step to run tests here (PyTest, Django test suites, etc.) 56 | 57 | - name: Upload artifact for deployment jobs 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: python-app 61 | path: | 62 | . 63 | !venv/ 64 | 65 | deploy: 66 | permissions: 67 | contents: none 68 | runs-on: ubuntu-latest 69 | needs: build 70 | environment: 71 | name: 'Development' 72 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 73 | 74 | steps: 75 | - name: Download artifact from build job 76 | uses: actions/download-artifact@v3 77 | with: 78 | name: python-app 79 | path: . 80 | 81 | - name: 'Deploy to Azure Web App' 82 | id: deploy-to-webapp 83 | uses: azure/webapps-deploy@v2 84 | with: 85 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 86 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} 87 | -------------------------------------------------------------------------------- /1. Basic Board and Piece Movement/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | import ChessEngine 9 | 10 | p.init() 11 | 12 | WIDTH = HEIGHT = 512 13 | DIMENTION = 8 # 8*8 CHESS BOARD 14 | SQ_SIZE = HEIGHT // DIMENTION 15 | MAX_FPS = 15 16 | IMAGES = {} 17 | 18 | ''' 19 | Initialise the global dictionary of images. This will be called exactly once in the main 20 | ''' 21 | def loadImages(): 22 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 23 | for piece in pieces: 24 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 25 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 26 | 27 | ''' 28 | This will be out main driver. It will handle user input and update the graphics. 29 | ''' 30 | def main(): 31 | screen = p.display.set_mode((WIDTH, HEIGHT)) 32 | clock = p.time.Clock() 33 | screen.fill(p.Color('white')) 34 | gs = ChessEngine.GameState() 35 | loadImages() #only do this once -> before the while loop 36 | running = True 37 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 38 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 39 | while running: 40 | for e in p.event.get(): 41 | if e.type == p.QUIT : 42 | runnin = False 43 | elif e.type == p.MOUSEBUTTONDOWN: 44 | location = p.mouse.get_pos() # (x,y) position of mouse 45 | col = location[0]//SQ_SIZE 46 | row = location[1]//SQ_SIZE 47 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 48 | sqSelected = () 49 | playerClicks = [] 50 | else: 51 | sqSelected = (row,col) 52 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 53 | if len(playerClicks) == 2: # when 2nd click 54 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 55 | print(move.getChessNotation) 56 | gs.makeMove(move) 57 | playerClicks = [] # reset platerClicks 58 | sqSelected = () # reset user clicks 59 | drawGameState(screen, gs) 60 | clock.tick(MAX_FPS) 61 | p.display.flip() 62 | 63 | ''' 64 | responsible for all the graphics in the game 65 | ''' 66 | def drawGameState(screen, gs): 67 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 68 | drawPieces(screen, gs.board) #draw pieces on the board 69 | # FUTURE SCOPE : add in piece highlighting or move suggestions 70 | 71 | 72 | ''' 73 | draw the squares on the board 74 | ''' 75 | def drawBoard(screen): 76 | colors = [p.Color('white'), p.Color('gray')] 77 | for r in range(DIMENTION): 78 | for c in range(DIMENTION): 79 | color = colors[(r+c)%2] 80 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 81 | 82 | 83 | 84 | ''' 85 | draw the pieces on the board using ChessEngine.GameState.board. 86 | ''' 87 | def drawPieces(screen, board): 88 | for r in range(DIMENTION): 89 | for c in range(DIMENTION): 90 | piece = board[r][c] 91 | if piece != '--': 92 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 93 | 94 | 95 | 96 | if __name__ == '__main__': 97 | main() 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /3. Valid Moves/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | import ChessEngine 9 | 10 | p.init() 11 | 12 | WIDTH = HEIGHT = 480 13 | DIMENTION = 8 # 8*8 CHESS BOARD 14 | SQ_SIZE = HEIGHT // DIMENTION 15 | MAX_FPS = 15 16 | IMAGES = {} 17 | 18 | ''' 19 | Initialise the global dictionary of images. This will be called exactly once in the main 20 | ''' 21 | def loadImages(): 22 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 23 | for piece in pieces: 24 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 25 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 26 | 27 | ''' 28 | This will be out main driver. It will handle user input and update the graphics. 29 | ''' 30 | def main(): 31 | screen = p.display.set_mode((WIDTH, HEIGHT)) 32 | clock = p.time.Clock() 33 | screen.fill(p.Color('white')) 34 | gs = ChessEngine.GameState() 35 | validMoves = gs.getValidMoves() # get a list of valid moves. 36 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 37 | loadImages() #only do this once -> before the while loop 38 | running = True 39 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 40 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 41 | while running: 42 | for e in p.event.get(): 43 | if e.type == p.QUIT : 44 | runnin = False 45 | #MOUSE HANDLERS 46 | elif e.type == p.MOUSEBUTTONDOWN: 47 | location = p.mouse.get_pos() # (x,y) position of mouse 48 | col = location[0]//SQ_SIZE 49 | row = location[1]//SQ_SIZE 50 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 51 | sqSelected = () 52 | playerClicks = [] 53 | else: 54 | sqSelected = (row,col) 55 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 56 | if len(playerClicks) == 2: # when 2nd click 57 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 58 | 59 | if move in validMoves: 60 | gs.makeMove(move) 61 | moveMade = True 62 | playerClicks = [] # reset platerClicks 63 | sqSelected = () # reset user clicks 64 | else: 65 | playerClicks = [sqSelected] 66 | 67 | #KEY HANDLERS 68 | elif e.type == p.KEYDOWN: 69 | if e.key == p.K_z: 70 | gs.undoMove() 71 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 72 | 73 | if moveMade: 74 | validMoves = gs.getValidMoves() 75 | moveMade = False 76 | drawGameState(screen, gs) 77 | clock.tick(MAX_FPS) 78 | p.display.flip() 79 | 80 | ''' 81 | responsible for all the graphics in the game 82 | ''' 83 | def drawGameState(screen, gs): 84 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 85 | drawPieces(screen, gs.board) #draw pieces on the board 86 | # FUTURE SCOPE : add in piece highlighting or move suggestions 87 | 88 | 89 | ''' 90 | draw the squares on the board 91 | ''' 92 | def drawBoard(screen): 93 | colors = [p.Color('white'), p.Color('gray')] 94 | for r in range(DIMENTION): 95 | for c in range(DIMENTION): 96 | color = colors[(r+c)%2] 97 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 98 | 99 | 100 | 101 | ''' 102 | draw the pieces on the board using ChessEngine.GameState.board. 103 | ''' 104 | def drawPieces(screen, board): 105 | for r in range(DIMENTION): 106 | for c in range(DIMENTION): 107 | piece = board[r][c] 108 | if piece != '--': 109 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 110 | 111 | 112 | 113 | if __name__ == '__main__': 114 | main() 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /2. All Possible Moves/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | import ChessEngine 9 | 10 | p.init() 11 | 12 | WIDTH = HEIGHT = 480 13 | DIMENTION = 8 # 8*8 CHESS BOARD 14 | SQ_SIZE = HEIGHT // DIMENTION 15 | MAX_FPS = 15 16 | IMAGES = {} 17 | 18 | ''' 19 | Initialise the global dictionary of images. This will be called exactly once in the main 20 | ''' 21 | def loadImages(): 22 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 23 | for piece in pieces: 24 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 25 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 26 | 27 | ''' 28 | This will be out main driver. It will handle user input and update the graphics. 29 | ''' 30 | def main(): 31 | screen = p.display.set_mode((WIDTH, HEIGHT)) 32 | clock = p.time.Clock() 33 | screen.fill(p.Color('white')) 34 | gs = ChessEngine.GameState() 35 | validMoves = gs.getValidMoves() # get a list of valid moves. 36 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 37 | loadImages() #only do this once -> before the while loop 38 | running = True 39 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 40 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 41 | while running: 42 | for e in p.event.get(): 43 | if e.type == p.QUIT : 44 | runnin = False 45 | #MOUSE HANDLERS 46 | elif e.type == p.MOUSEBUTTONDOWN: 47 | location = p.mouse.get_pos() # (x,y) position of mouse 48 | col = location[0]//SQ_SIZE 49 | row = location[1]//SQ_SIZE 50 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 51 | sqSelected = () 52 | playerClicks = [] 53 | else: 54 | sqSelected = (row,col) 55 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 56 | if len(playerClicks) == 2: # when 2nd click 57 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 58 | 59 | if move in validMoves: 60 | gs.makeMove(move) 61 | moveMade = True 62 | playerClicks = [] # reset platerClicks 63 | sqSelected = () # reset user clicks 64 | else: 65 | playerClicks = [sqSelected] 66 | 67 | #KEY HANDLERS 68 | elif e.type == p.KEYDOWN: 69 | if e.key == p.K_z: 70 | gs.undoMove() 71 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 72 | 73 | if moveMade: 74 | validMoves = gs.getValidMoves() 75 | moveMade = False 76 | drawGameState(screen, gs) 77 | clock.tick(MAX_FPS) 78 | p.display.flip() 79 | 80 | ''' 81 | responsible for all the graphics in the game 82 | ''' 83 | def drawGameState(screen, gs): 84 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 85 | drawPieces(screen, gs.board) #draw pieces on the board 86 | # FUTURE SCOPE : add in piece highlighting or move suggestions 87 | 88 | 89 | ''' 90 | draw the squares on the board 91 | ''' 92 | def drawBoard(screen): 93 | colors = [p.Color('white'), p.Color('gray')] 94 | for r in range(DIMENTION): 95 | for c in range(DIMENTION): 96 | color = colors[(r+c)%2] 97 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 98 | 99 | 100 | 101 | ''' 102 | draw the pieces on the board using ChessEngine.GameState.board. 103 | ''' 104 | def drawPieces(screen, board): 105 | for r in range(DIMENTION): 106 | for c in range(DIMENTION): 107 | piece = board[r][c] 108 | if piece != '--': 109 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 110 | 111 | 112 | 113 | if __name__ == '__main__': 114 | main() 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /4. Valid Moves Advanced/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | import ChessEngineAd as ChessEngine 9 | # import ChessEngine 10 | 11 | p.init() 12 | 13 | WIDTH = HEIGHT = 480 14 | DIMENTION = 8 # 8*8 CHESS BOARD 15 | SQ_SIZE = HEIGHT // DIMENTION 16 | MAX_FPS = 15 17 | IMAGES = {} 18 | 19 | ''' 20 | Initialise the global dictionary of images. This will be called exactly once in the main 21 | ''' 22 | def loadImages(): 23 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 24 | for piece in pieces: 25 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 26 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 27 | 28 | ''' 29 | This will be out main driver. It will handle user input and update the graphics. 30 | ''' 31 | def main(): 32 | screen = p.display.set_mode((WIDTH, HEIGHT)) 33 | clock = p.time.Clock() 34 | screen.fill(p.Color('white')) 35 | gs = ChessEngine.GameState() 36 | validMoves = gs.getValidMoves() # get a list of valid moves. 37 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 38 | loadImages() #only do this once -> before the while loop 39 | running = True 40 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 41 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 42 | while running: 43 | for e in p.event.get(): 44 | if e.type == p.QUIT : 45 | runnin = False 46 | #MOUSE HANDLERS 47 | elif e.type == p.MOUSEBUTTONDOWN: 48 | location = p.mouse.get_pos() # (x,y) position of mouse 49 | col = location[0]//SQ_SIZE 50 | row = location[1]//SQ_SIZE 51 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 52 | sqSelected = () 53 | playerClicks = [] 54 | else: 55 | sqSelected = (row,col) 56 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 57 | if len(playerClicks) == 2: # when 2nd click 58 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 59 | 60 | if move in validMoves: 61 | gs.makeMove(move) 62 | moveMade = True 63 | playerClicks = [] # reset platerClicks 64 | sqSelected = () # reset user clicks 65 | else: 66 | playerClicks = [sqSelected] 67 | 68 | #KEY HANDLERS 69 | elif e.type == p.KEYDOWN: 70 | if e.key == p.K_z: 71 | gs.undoMove() 72 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 73 | 74 | if moveMade: 75 | validMoves = gs.getValidMoves() 76 | moveMade = False 77 | drawGameState(screen, gs) 78 | clock.tick(MAX_FPS) 79 | p.display.flip() 80 | 81 | ''' 82 | responsible for all the graphics in the game 83 | ''' 84 | def drawGameState(screen, gs): 85 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 86 | drawPieces(screen, gs.board) #draw pieces on the board 87 | # FUTURE SCOPE : add in piece highlighting or move suggestions 88 | 89 | 90 | ''' 91 | draw the squares on the board 92 | ''' 93 | def drawBoard(screen): 94 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 95 | for r in range(DIMENTION): 96 | for c in range(DIMENTION): 97 | color = colors[(r+c)%2] 98 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 99 | 100 | 101 | 102 | ''' 103 | draw the pieces on the board using ChessEngine.GameState.board. 104 | ''' 105 | def drawPieces(screen, board): 106 | for r in range(DIMENTION): 107 | for c in range(DIMENTION): 108 | piece = board[r][c] 109 | if piece != '--': 110 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 111 | 112 | 113 | 114 | if __name__ == '__main__': 115 | main() 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /5. Pawn Promotion and En - Passant/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | # import ChessEngineAd as ChessEngine 9 | import ChessEngine 10 | 11 | p.init() 12 | 13 | WIDTH = HEIGHT = 480 14 | DIMENTION = 8 # 8*8 CHESS BOARD 15 | SQ_SIZE = HEIGHT // DIMENTION 16 | MAX_FPS = 15 17 | IMAGES = {} 18 | 19 | ''' 20 | Initialise the global dictionary of images. This will be called exactly once in the main 21 | ''' 22 | def loadImages(): 23 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 24 | for piece in pieces: 25 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 26 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 27 | 28 | ''' 29 | This will be out main driver. It will handle user input and update the graphics. 30 | ''' 31 | def main(): 32 | screen = p.display.set_mode((WIDTH, HEIGHT)) 33 | clock = p.time.Clock() 34 | screen.fill(p.Color('white')) 35 | gs = ChessEngine.GameState() 36 | validMoves = gs.getValidMoves() # get a list of valid moves. 37 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 38 | loadImages() #only do this once -> before the while loop 39 | running = True 40 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 41 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 42 | while running: 43 | for e in p.event.get(): 44 | if e.type == p.QUIT : 45 | runnin = False 46 | #MOUSE HANDLERS 47 | elif e.type == p.MOUSEBUTTONDOWN: 48 | location = p.mouse.get_pos() # (x,y) position of mouse 49 | col = location[0]//SQ_SIZE 50 | row = location[1]//SQ_SIZE 51 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 52 | sqSelected = () 53 | playerClicks = [] 54 | else: 55 | sqSelected = (row,col) 56 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 57 | if len(playerClicks) == 2: # when 2nd click 58 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 59 | for i in range(len(validMoves)): 60 | if move == validMoves[i]: 61 | gs.makeMove(validMoves[i]) 62 | moveMade = True 63 | playerClicks = [] # reset platerClicks 64 | sqSelected = () # reset user clicks 65 | if not moveMade : 66 | playerClicks = [sqSelected] 67 | 68 | #KEY HANDLERS 69 | elif e.type == p.KEYDOWN: 70 | if e.key == p.K_z: 71 | gs.undoMove() 72 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 73 | 74 | if moveMade: 75 | validMoves = gs.getValidMoves() 76 | moveMade = False 77 | drawGameState(screen, gs) 78 | clock.tick(MAX_FPS) 79 | p.display.flip() 80 | 81 | ''' 82 | responsible for all the graphics in the game 83 | ''' 84 | def drawGameState(screen, gs): 85 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 86 | drawPieces(screen, gs.board) #draw pieces on the board 87 | # FUTURE SCOPE : add in piece highlighting or move suggestions 88 | 89 | 90 | ''' 91 | draw the squares on the board 92 | ''' 93 | def drawBoard(screen): 94 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 95 | for r in range(DIMENTION): 96 | for c in range(DIMENTION): 97 | color = colors[(r+c)%2] 98 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 99 | 100 | 101 | 102 | ''' 103 | draw the pieces on the board using ChessEngine.GameState.board. 104 | ''' 105 | def drawPieces(screen, board): 106 | for r in range(DIMENTION): 107 | for c in range(DIMENTION): 108 | piece = board[r][c] 109 | if piece != '--': 110 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 111 | 112 | 113 | 114 | if __name__ == '__main__': 115 | main() 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /6. EnPassant and Pawn Promotion in Advanced Algo/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | import ChessEngineAd as ChessEngine 9 | # import ChessEngine 10 | 11 | p.init() 12 | 13 | WIDTH = HEIGHT = 480 14 | DIMENTION = 8 # 8*8 CHESS BOARD 15 | SQ_SIZE = HEIGHT // DIMENTION 16 | MAX_FPS = 15 17 | IMAGES = {} 18 | 19 | ''' 20 | Initialise the global dictionary of images. This will be called exactly once in the main 21 | ''' 22 | def loadImages(): 23 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 24 | for piece in pieces: 25 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 26 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 27 | 28 | ''' 29 | This will be out main driver. It will handle user input and update the graphics. 30 | ''' 31 | def main(): 32 | screen = p.display.set_mode((WIDTH, HEIGHT)) 33 | clock = p.time.Clock() 34 | screen.fill(p.Color('white')) 35 | gs = ChessEngine.GameState() 36 | validMoves = gs.getValidMoves() # get a list of valid moves. 37 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 38 | loadImages() #only do this once -> before the while loop 39 | running = True 40 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 41 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 42 | while running: 43 | for e in p.event.get(): 44 | if e.type == p.QUIT : 45 | runnin = False 46 | #MOUSE HANDLERS 47 | elif e.type == p.MOUSEBUTTONDOWN: 48 | location = p.mouse.get_pos() # (x,y) position of mouse 49 | col = location[0]//SQ_SIZE 50 | row = location[1]//SQ_SIZE 51 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 52 | sqSelected = () 53 | playerClicks = [] 54 | else: 55 | sqSelected = (row,col) 56 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 57 | if len(playerClicks) == 2: # when 2nd click 58 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 59 | for i in range(len(validMoves)): 60 | if move == validMoves[i]: 61 | gs.makeMove(validMoves[i]) 62 | moveMade = True 63 | playerClicks = [] # reset platerClicks 64 | sqSelected = () # reset user clicks 65 | if not moveMade : 66 | playerClicks = [sqSelected] 67 | 68 | #KEY HANDLERS 69 | elif e.type == p.KEYDOWN: 70 | if e.key == p.K_z: 71 | gs.undoMove() 72 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 73 | 74 | if moveMade: 75 | validMoves = gs.getValidMoves() 76 | moveMade = False 77 | drawGameState(screen, gs) 78 | clock.tick(MAX_FPS) 79 | p.display.flip() 80 | 81 | ''' 82 | responsible for all the graphics in the game 83 | ''' 84 | def drawGameState(screen, gs): 85 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 86 | drawPieces(screen, gs.board) #draw pieces on the board 87 | # FUTURE SCOPE : add in piece highlighting or move suggestions 88 | 89 | 90 | ''' 91 | draw the squares on the board 92 | ''' 93 | def drawBoard(screen): 94 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 95 | for r in range(DIMENTION): 96 | for c in range(DIMENTION): 97 | color = colors[(r+c)%2] 98 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 99 | 100 | 101 | 102 | ''' 103 | draw the pieces on the board using ChessEngine.GameState.board. 104 | ''' 105 | def drawPieces(screen, board): 106 | for r in range(DIMENTION): 107 | for c in range(DIMENTION): 108 | piece = board[r][c] 109 | if piece != '--': 110 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 111 | 112 | 113 | 114 | if __name__ == '__main__': 115 | main() 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /8. Castling Advanced/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Baisc, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | 14 | p.init() 15 | 16 | WIDTH = HEIGHT = 480 17 | DIMENTION = 8 # 8*8 CHESS BOARD 18 | SQ_SIZE = HEIGHT // DIMENTION 19 | MAX_FPS = 15 20 | IMAGES = {} 21 | 22 | ''' 23 | Initialise the global dictionary of images. This will be called exactly once in the main 24 | ''' 25 | def loadImages(): 26 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 27 | for piece in pieces: 28 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 29 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 30 | 31 | ''' 32 | This will be out main driver. It will handle user input and update the graphics. 33 | ''' 34 | def main(): 35 | screen = p.display.set_mode((WIDTH, HEIGHT)) 36 | clock = p.time.Clock() 37 | screen.fill(p.Color('white')) 38 | gs = ChessEngine.GameState() 39 | validMoves = gs.getValidMoves() # get a list of valid moves. 40 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 41 | loadImages() #only do this once -> before the while loop 42 | running = True 43 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 44 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 45 | while running: 46 | for e in p.event.get(): 47 | if e.type == p.QUIT : 48 | runnin = False 49 | #MOUSE HANDLERS 50 | elif e.type == p.MOUSEBUTTONDOWN: 51 | location = p.mouse.get_pos() # (x,y) position of mouse 52 | col = location[0]//SQ_SIZE 53 | row = location[1]//SQ_SIZE 54 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 55 | sqSelected = () 56 | playerClicks = [] 57 | else: 58 | sqSelected = (row,col) 59 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 60 | if len(playerClicks) == 2: # when 2nd click 61 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 62 | for i in range(len(validMoves)): 63 | if move == validMoves[i]: 64 | gs.makeMove(validMoves[i]) 65 | moveMade = True 66 | playerClicks = [] # reset platerClicks 67 | sqSelected = () # reset user clicks 68 | if not moveMade : 69 | playerClicks = [sqSelected] 70 | 71 | #KEY HANDLERS 72 | elif e.type == p.KEYDOWN: 73 | if e.key == p.K_z: 74 | gs.undoMove() 75 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 76 | 77 | if moveMade: 78 | validMoves = gs.getValidMoves() 79 | moveMade = False 80 | drawGameState(screen, gs) 81 | clock.tick(MAX_FPS) 82 | p.display.flip() 83 | 84 | ''' 85 | responsible for all the graphics in the game 86 | ''' 87 | def drawGameState(screen, gs): 88 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 89 | drawPieces(screen, gs.board) #draw pieces on the board 90 | # FUTURE SCOPE : add in piece highlighting or move suggestions 91 | 92 | 93 | ''' 94 | draw the squares on the board 95 | ''' 96 | def drawBoard(screen): 97 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 98 | for r in range(DIMENTION): 99 | for c in range(DIMENTION): 100 | color = colors[(r+c)%2] 101 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 102 | 103 | 104 | 105 | ''' 106 | draw the pieces on the board using ChessEngine.GameState.board. 107 | ''' 108 | def drawPieces(screen, board): 109 | for r in range(DIMENTION): 110 | for c in range(DIMENTION): 111 | piece = board[r][c] 112 | if piece != '--': 113 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 114 | 115 | 116 | 117 | if __name__ == '__main__': 118 | main() 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /7. Castling/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | 7 | import pygame as p 8 | # import ChessEngineAd as ChessEngine 9 | import ChessEngine 10 | 11 | p.init() 12 | 13 | WIDTH = HEIGHT = 480 14 | DIMENTION = 8 # 8*8 CHESS BOARD 15 | SQ_SIZE = HEIGHT // DIMENTION 16 | MAX_FPS = 15 17 | IMAGES = {} 18 | 19 | ''' 20 | Initialise the global dictionary of images. This will be called exactly once in the main 21 | ''' 22 | def loadImages(): 23 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 24 | for piece in pieces: 25 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE ) ) 26 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 27 | 28 | ''' 29 | This will be out main driver. It will handle user input and update the graphics. 30 | ''' 31 | def main(): 32 | screen = p.display.set_mode((WIDTH, HEIGHT)) 33 | clock = p.time.Clock() 34 | screen.fill(p.Color('white')) 35 | gs = ChessEngine.GameState() 36 | validMoves = gs.getValidMoves() # get a list of valid moves. 37 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 38 | loadImages() #only do this once -> before the while loop 39 | running = True 40 | sqSelected = () #no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 41 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 42 | while running: 43 | for e in p.event.get(): 44 | if e.type == p.QUIT : 45 | runnin = False 46 | #MOUSE HANDLERS 47 | elif e.type == p.MOUSEBUTTONDOWN: 48 | location = p.mouse.get_pos() # (x,y) position of mouse 49 | col = location[0]//SQ_SIZE 50 | row = location[1]//SQ_SIZE 51 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 52 | sqSelected = () 53 | playerClicks = [] 54 | else: 55 | sqSelected = (row,col) 56 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 57 | if len(playerClicks) == 2: # when 2nd click 58 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 59 | for i in range(len(validMoves)): 60 | if move == validMoves[i]: 61 | gs.makeMove(validMoves[i]) 62 | print(gs.currentCastlingRights.wks, end = ', ') 63 | print(gs.currentCastlingRights.wqs, end = ', ') 64 | print(gs.currentCastlingRights.bks, end = ', ') 65 | print(gs.currentCastlingRights.bqs) 66 | moveMade = True 67 | playerClicks = [] # reset platerClicks 68 | sqSelected = () # reset user clicks 69 | if not moveMade : 70 | playerClicks = [sqSelected] 71 | 72 | #KEY HANDLERS 73 | elif e.type == p.KEYDOWN: 74 | if e.key == p.K_z: 75 | gs.undoMove() 76 | moveMade = True #can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 77 | 78 | if moveMade: 79 | validMoves = gs.getValidMoves() 80 | moveMade = False 81 | drawGameState(screen, gs) 82 | clock.tick(MAX_FPS) 83 | p.display.flip() 84 | 85 | ''' 86 | responsible for all the graphics in the game 87 | ''' 88 | def drawGameState(screen, gs): 89 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 90 | drawPieces(screen, gs.board) #draw pieces on the board 91 | # FUTURE SCOPE : add in piece highlighting or move suggestions 92 | 93 | 94 | ''' 95 | draw the squares on the board 96 | ''' 97 | def drawBoard(screen): 98 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 99 | for r in range(DIMENTION): 100 | for c in range(DIMENTION): 101 | color = colors[(r+c)%2] 102 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 103 | 104 | 105 | 106 | ''' 107 | draw the pieces on the board using ChessEngine.GameState.board. 108 | ''' 109 | def drawPieces(screen, board): 110 | for r in range(DIMENTION): 111 | for c in range(DIMENTION): 112 | piece = board[r][c] 113 | if piece != '--': 114 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 115 | 116 | 117 | 118 | if __name__ == '__main__': 119 | main() 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /10. Recursive Min Max/ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # A map of piece to score value -> Standard chess scores 4 | pieceScore = {'K': 0, "P": 1, "N": 3, "B": 3, "R": 5, "Q": 9} #making King = 0, as no one can actually take the king 5 | CHECKMATE = 1000 # if you lead to checkmate you win -> hence max attainable score 6 | STALEMATE = 0 # If you can win(capture opponent's piece) avoid it but if you loosing(opponent can give you Checkmate) try it hence 0 and not -1000 7 | DEPTH = 4 # Depth for recursive calls 8 | ''' 9 | Function to calculate RANDOM move from the list of valid moves. 10 | ''' 11 | def findRandomMove(validMoves): 12 | if len(validMoves) > 0: 13 | return validMoves[random.randint(0, len(validMoves) - 1)] 14 | 15 | 16 | ''' 17 | Function to find the BEST move from the list of valid moves 18 | ''' 19 | def findBestMove(gs, validMoves): 20 | turnMultiplier = 1 if gs.whiteToMove else -1 # for allowing AI to play as any color 21 | playerMaxScore = -CHECKMATE # as AI is playing Black this is the worst possible score -> AI will start from worst and try to improve 22 | bestMove = None 23 | random.shuffle(validMoves) 24 | for playerMove in validMoves: # not assigning colors so AI can play as both: playerMove -> move of the current player || opponentMove -> opponent's move 25 | gs.makeMove(playerMove) 26 | opponentMinScore = CHECKMATE 27 | opponentMoves = gs.getValidMoves() 28 | if gs.checkMate: 29 | gs.undoMove() 30 | return playerMove 31 | elif gs.staleMate: 32 | opponentMinScore = STALEMATE 33 | else: 34 | for opponentMove in opponentMoves: 35 | gs.makeMove(opponentMove) 36 | gs.getValidMoves() 37 | if gs.checkMate: 38 | score = -CHECKMATE 39 | elif gs.staleMate: 40 | score = STALEMATE 41 | else: 42 | score = turnMultiplier * materialScore(gs.board) 43 | if score < opponentMinScore: 44 | opponentMinScore = score 45 | gs.undoMove() 46 | if playerMaxScore < opponentMinScore: 47 | playerMaxScore = opponentMinScore 48 | bestMove = playerMove 49 | gs.undoMove() 50 | return bestMove 51 | 52 | ''' 53 | Helper method to call recursion for the 1st time 54 | ''' 55 | def findBestMoveMinMax(gs, validMoves): 56 | global nextMove # to find the next move 57 | nextMove = None 58 | findMoveMinMax(gs, validMoves, DEPTH, gs.whiteToMove) 59 | return nextMove 60 | 61 | ''' 62 | Find the best move based on material itself 63 | ''' 64 | def findMoveMinMax(gs, validMoves, depth, whiteToMove): 65 | if depth == 0: # We have reached the bottom of the tree -> with fixed depth == DEPTH 66 | return boardScore(gs) #return the score 67 | 68 | global nextMove 69 | if gs.whiteToMove: # Try to maximise score 70 | maxScore = -CHECKMATE 71 | for move in validMoves: 72 | gs.makeMove(move) 73 | nextMoves = gs.getValidMoves() 74 | score = findMoveMinMax(gs, nextMoves, depth-1, False) 75 | if score > maxScore: 76 | maxScore = score 77 | if depth == DEPTH: 78 | nextMove = move 79 | gs.undoMove() 80 | return maxScore 81 | else: # Try to minimise score 82 | minScore = CHECKMATE 83 | for move in validMoves: 84 | gs.makeMove(move) 85 | nextMoves = gs.getValidMoves() 86 | score = findMoveMinMax(gs, nextMoves, depth-1, True) 87 | if score < minScore: 88 | minScore = score 89 | if depth == DEPTH: 90 | nextMove = move 91 | gs.undoMove() 92 | 93 | return minScore 94 | 95 | ''' 96 | better scoring algorithm with considering checks and stalemates. 97 | +ve score good for white 98 | -ve score good for black 99 | ''' 100 | def boardScore(gs): 101 | if gs.checkMate: 102 | if gs.whiteToMove: 103 | return -CHECKMATE # BLACK WINS 104 | else: 105 | return CHECKMATE 106 | if gs.staleMate: 107 | return STALEMATE 108 | score = 0 109 | for row in gs.board: 110 | for square in row: 111 | if square[0] == 'w': 112 | score += pieceScore[square[1]] 113 | elif square[0] == 'b': 114 | score -= pieceScore[square[1]] 115 | return score 116 | 117 | ''' 118 | Gives the score of the board according to the material on it -> White piece positive material and Black piece negative material. 119 | Assuming that Human is playing White and BOT is playing black 120 | ''' 121 | def materialScore(board): 122 | score = 0 123 | for row in board: 124 | for square in row: 125 | if square[0] == 'w': 126 | score += pieceScore[square[1]] 127 | elif square[0] == 'b': 128 | score -= pieceScore[square[1]] 129 | return score 130 | 131 | 132 | -------------------------------------------------------------------------------- /12. Move Log/ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # A map of piece to score value -> Standard chess scores 4 | pieceScore = {'K': 0, "P": 1, "N": 3, "B": 3, "R": 5, "Q": 9} #making King = 0, as no one can actually take the king 5 | CHECKMATE = 1000 # if you lead to checkmate you win -> hence max attainable score 6 | STALEMATE = 0 # If you can win(capture opponent's piece) avoid it but if you loosing(opponent can give you Checkmate) try it hence 0 and not -1000 7 | DEPTH = 3 # Depth for recursive calls 8 | ''' 9 | Function to calculate RANDOM move from the list of valid moves. 10 | ''' 11 | def findRandomMove(validMoves): 12 | if len(validMoves) > 0: 13 | return validMoves[random.randint(0, len(validMoves) - 1)] 14 | 15 | 16 | ''' 17 | Function to find the BEST move from the list of valid moves 18 | ''' 19 | def findBestMove(gs, validMoves): 20 | turnMultiplier = 1 if gs.whiteToMove else -1 # for allowing AI to play as any color 21 | playerMaxScore = -CHECKMATE # as AI is playing Black this is the worst possible score -> AI will start from worst and try to improve 22 | bestMove = None 23 | random.shuffle(validMoves) 24 | for playerMove in validMoves: # not assigning colors so AI can play as both: playerMove -> move of the current player || opponentMove -> opponent's move 25 | gs.makeMove(playerMove) 26 | opponentMinScore = CHECKMATE 27 | opponentMoves = gs.getValidMoves() 28 | if gs.checkMate: 29 | gs.undoMove() 30 | return playerMove 31 | elif gs.staleMate: 32 | opponentMinScore = STALEMATE 33 | else: 34 | for opponentMove in opponentMoves: 35 | gs.makeMove(opponentMove) 36 | gs.getValidMoves() 37 | if gs.checkMate: 38 | score = -CHECKMATE 39 | elif gs.staleMate: 40 | score = STALEMATE 41 | else: 42 | score = turnMultiplier * materialScore(gs.board) 43 | if score < opponentMinScore: 44 | opponentMinScore = score 45 | gs.undoMove() 46 | if playerMaxScore < opponentMinScore: 47 | playerMaxScore = opponentMinScore 48 | bestMove = playerMove 49 | gs.undoMove() 50 | return bestMove 51 | 52 | ''' 53 | Helper method to call recursion for the 1st time 54 | ''' 55 | def findBestMoveMinMax(gs, validMoves): 56 | global nextMove # to find the next move 57 | nextMove = None 58 | random.shuffle(validMoves) 59 | findMoveNegaMaxAlphaBetaPruning(gs, validMoves, DEPTH, -CHECKMATE, CHECKMATE, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm with Alpha Beta Pruning 60 | # findMoveNegaMax(gs, validMoves, DEPTH, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm 61 | # findMoveMinMax(gs, validMoves, DEPTH, gs.whiteToMove) # For using Min-Max Algorithm 62 | return nextMove 63 | 64 | ''' 65 | Find the best move based on material itself 66 | ''' 67 | def findMoveMinMax(gs, validMoves, depth, whiteToMove): 68 | if depth == 0: # We have reached the bottom of the tree -> with fixed depth == DEPTH 69 | return boardScore(gs) #return the score 70 | 71 | global nextMove 72 | if gs.whiteToMove: # Try to maximise score 73 | maxScore = -CHECKMATE 74 | for move in validMoves: 75 | gs.makeMove(move) 76 | nextMoves = gs.getValidMoves() 77 | score = findMoveMinMax(gs, nextMoves, depth-1, False) 78 | if score > maxScore: 79 | maxScore = score 80 | if depth == DEPTH: 81 | nextMove = move 82 | gs.undoMove() 83 | return maxScore 84 | else: # Try to minimise score 85 | minScore = CHECKMATE 86 | for move in validMoves: 87 | gs.makeMove(move) 88 | nextMoves = gs.getValidMoves() 89 | score = findMoveMinMax(gs, nextMoves, depth-1, True) 90 | if score < minScore: 91 | minScore = score 92 | if depth == DEPTH: 93 | nextMove = move 94 | gs.undoMove() 95 | return minScore 96 | 97 | ''' 98 | BEST Move calculator using NegaMax Algorithm 99 | ''' 100 | def findMoveNegaMax(gs, validMoves, depth, turnMultiplier): 101 | global nextMove 102 | if depth == 0: 103 | return turnMultiplier * boardScore(gs) 104 | 105 | maxScore = -CHECKMATE 106 | for move in validMoves: 107 | gs.makeMove(move) 108 | nextMoves = gs.getValidMoves() 109 | score = -findMoveNegaMax(gs, nextMoves, depth-1, -turnMultiplier) # negative for NEGA Max 110 | if score > maxScore: 111 | maxScore = score 112 | if depth == DEPTH: 113 | nextMove = move 114 | gs.undoMove() 115 | return score 116 | 117 | ''' 118 | BEST Move calculator using NegaMax Algorithm along with Alpha Beta Pruning 119 | ''' 120 | def findMoveNegaMaxAlphaBetaPruning(gs, validMoves, depth, alpha, beta, turnMultiplier): 121 | global nextMove 122 | if depth == 0: 123 | return turnMultiplier * boardScore(gs) 124 | 125 | # Move Ordering -> (TODO) 126 | # Traverse better moves 1st -> ones with checks and captures -> will lead to more pruning and more optimised algorithm 127 | maxScore = -CHECKMATE 128 | for move in validMoves: 129 | gs.makeMove(move) 130 | nextMoves = gs.getValidMoves() 131 | score = -findMoveNegaMaxAlphaBetaPruning(gs, nextMoves, depth-1, -beta, -alpha, -turnMultiplier) # negative for NEGA Max 132 | if score > maxScore: 133 | maxScore = score 134 | if depth == DEPTH: 135 | nextMove = move 136 | gs.undoMove() 137 | if maxScore > alpha: 138 | alpha = maxScore 139 | if alpha >= beta: 140 | break 141 | return maxScore 142 | 143 | 144 | ''' 145 | better scoring algorithm with considering checks and stalemates. 146 | +ve score good for white 147 | -ve score good for black 148 | ''' 149 | def boardScore(gs): 150 | if gs.checkMate: 151 | if gs.whiteToMove: 152 | return -CHECKMATE # BLACK WINS 153 | else: 154 | return CHECKMATE 155 | if gs.staleMate: 156 | return STALEMATE 157 | score = 0 158 | for row in gs.board: 159 | for square in row: 160 | if square[0] == 'w': 161 | score += pieceScore[square[1]] 162 | elif square[0] == 'b': 163 | score -= pieceScore[square[1]] 164 | return score 165 | 166 | ''' 167 | Gives the score of the board according to the material on it -> White piece positive material and Black piece negative material. 168 | Assuming that Human is playing White and BOT is playing black 169 | ''' 170 | def materialScore(board): 171 | score = 0 172 | for row in board: 173 | for square in row: 174 | if square[0] == 'w': 175 | score += pieceScore[square[1]] 176 | elif square[0] == 'b': 177 | score -= pieceScore[square[1]] 178 | return score 179 | 180 | 181 | -------------------------------------------------------------------------------- /11. NegaMax Alpha Beta Pruning/ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # A map of piece to score value -> Standard chess scores 4 | pieceScore = {'K': 0, "P": 1, "N": 3, "B": 3, "R": 5, "Q": 9} #making King = 0, as no one can actually take the king 5 | CHECKMATE = 1000 # if you lead to checkmate you win -> hence max attainable score 6 | STALEMATE = 0 # If you can win(capture opponent's piece) avoid it but if you loosing(opponent can give you Checkmate) try it hence 0 and not -1000 7 | DEPTH = 3 # Depth for recursive calls 8 | ''' 9 | Function to calculate RANDOM move from the list of valid moves. 10 | ''' 11 | def findRandomMove(validMoves): 12 | if len(validMoves) > 0: 13 | return validMoves[random.randint(0, len(validMoves) - 1)] 14 | 15 | 16 | ''' 17 | Function to find the BEST move from the list of valid moves 18 | ''' 19 | def findBestMove(gs, validMoves): 20 | turnMultiplier = 1 if gs.whiteToMove else -1 # for allowing AI to play as any color 21 | playerMaxScore = -CHECKMATE # as AI is playing Black this is the worst possible score -> AI will start from worst and try to improve 22 | bestMove = None 23 | random.shuffle(validMoves) 24 | for playerMove in validMoves: # not assigning colors so AI can play as both: playerMove -> move of the current player || opponentMove -> opponent's move 25 | gs.makeMove(playerMove) 26 | opponentMinScore = CHECKMATE 27 | opponentMoves = gs.getValidMoves() 28 | if gs.checkMate: 29 | gs.undoMove() 30 | return playerMove 31 | elif gs.staleMate: 32 | opponentMinScore = STALEMATE 33 | else: 34 | for opponentMove in opponentMoves: 35 | gs.makeMove(opponentMove) 36 | gs.getValidMoves() 37 | if gs.checkMate: 38 | score = -CHECKMATE 39 | elif gs.staleMate: 40 | score = STALEMATE 41 | else: 42 | score = turnMultiplier * materialScore(gs.board) 43 | if score < opponentMinScore: 44 | opponentMinScore = score 45 | gs.undoMove() 46 | if playerMaxScore < opponentMinScore: 47 | playerMaxScore = opponentMinScore 48 | bestMove = playerMove 49 | gs.undoMove() 50 | return bestMove 51 | 52 | ''' 53 | Helper method to call recursion for the 1st time 54 | ''' 55 | def findBestMoveMinMax(gs, validMoves): 56 | global nextMove # to find the next move 57 | nextMove = None 58 | random.shuffle(validMoves) 59 | findMoveNegaMaxAlphaBetaPruning(gs, validMoves, DEPTH, -CHECKMATE, CHECKMATE, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm with Alpha Beta Pruning 60 | # findMoveNegaMax(gs, validMoves, DEPTH, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm 61 | # findMoveMinMax(gs, validMoves, DEPTH, gs.whiteToMove) # For using Min-Max Algorithm 62 | return nextMove 63 | 64 | ''' 65 | Find the best move based on material itself 66 | ''' 67 | def findMoveMinMax(gs, validMoves, depth, whiteToMove): 68 | if depth == 0: # We have reached the bottom of the tree -> with fixed depth == DEPTH 69 | return boardScore(gs) #return the score 70 | 71 | global nextMove 72 | if gs.whiteToMove: # Try to maximise score 73 | maxScore = -CHECKMATE 74 | for move in validMoves: 75 | gs.makeMove(move) 76 | nextMoves = gs.getValidMoves() 77 | score = findMoveMinMax(gs, nextMoves, depth-1, False) 78 | if score > maxScore: 79 | maxScore = score 80 | if depth == DEPTH: 81 | nextMove = move 82 | gs.undoMove() 83 | return maxScore 84 | else: # Try to minimise score 85 | minScore = CHECKMATE 86 | for move in validMoves: 87 | gs.makeMove(move) 88 | nextMoves = gs.getValidMoves() 89 | score = findMoveMinMax(gs, nextMoves, depth-1, True) 90 | if score < minScore: 91 | minScore = score 92 | if depth == DEPTH: 93 | nextMove = move 94 | gs.undoMove() 95 | return minScore 96 | 97 | ''' 98 | BEST Move calculator using NegaMax Algorithm 99 | ''' 100 | def findMoveNegaMax(gs, validMoves, depth, turnMultiplier): 101 | global nextMove 102 | if depth == 0: 103 | return turnMultiplier * boardScore(gs) 104 | 105 | maxScore = -CHECKMATE 106 | for move in validMoves: 107 | gs.makeMove(move) 108 | nextMoves = gs.getValidMoves() 109 | score = -findMoveNegaMax(gs, nextMoves, depth-1, -turnMultiplier) # negative for NEGA Max 110 | if score > maxScore: 111 | maxScore = score 112 | if depth == DEPTH: 113 | nextMove = move 114 | gs.undoMove() 115 | return score 116 | 117 | ''' 118 | BEST Move calculator using NegaMax Algorithm along with Alpha Beta Pruning 119 | ''' 120 | def findMoveNegaMaxAlphaBetaPruning(gs, validMoves, depth, alpha, beta, turnMultiplier): 121 | global nextMove 122 | if depth == 0: 123 | return turnMultiplier * boardScore(gs) 124 | 125 | # Move Ordering -> (TODO) 126 | # Traverse better moves 1st -> ones with checks and captures -> will lead to more pruning and more optimised algorithm 127 | maxScore = -CHECKMATE 128 | for move in validMoves: 129 | gs.makeMove(move) 130 | nextMoves = gs.getValidMoves() 131 | score = -findMoveNegaMaxAlphaBetaPruning(gs, nextMoves, depth-1, -beta, -alpha, -turnMultiplier) # negative for NEGA Max 132 | if score > maxScore: 133 | maxScore = score 134 | if depth == DEPTH: 135 | nextMove = move 136 | gs.undoMove() 137 | if maxScore > alpha: 138 | alpha = maxScore 139 | if alpha >= beta: 140 | break 141 | return maxScore 142 | 143 | 144 | ''' 145 | better scoring algorithm with considering checks and stalemates. 146 | +ve score good for white 147 | -ve score good for black 148 | ''' 149 | def boardScore(gs): 150 | if gs.checkMate: 151 | if gs.whiteToMove: 152 | return -CHECKMATE # BLACK WINS 153 | else: 154 | return CHECKMATE 155 | if gs.staleMate: 156 | return STALEMATE 157 | score = 0 158 | for row in gs.board: 159 | for square in row: 160 | if square[0] == 'w': 161 | score += pieceScore[square[1]] 162 | elif square[0] == 'b': 163 | score -= pieceScore[square[1]] 164 | return score 165 | 166 | ''' 167 | Gives the score of the board according to the material on it -> White piece positive material and Black piece negative material. 168 | Assuming that Human is playing White and BOT is playing black 169 | ''' 170 | def materialScore(board): 171 | score = 0 172 | for row in board: 173 | for square in row: 174 | if square[0] == 'w': 175 | score += pieceScore[square[1]] 176 | elif square[0] == 'b': 177 | score -= pieceScore[square[1]] 178 | return score 179 | 180 | 181 | -------------------------------------------------------------------------------- /9. Random Move BOT/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | import ChessBot 14 | p.init() 15 | 16 | WIDTH = HEIGHT = 480 17 | DIMENTION = 8 # 8*8 CHESS BOARD 18 | SQ_SIZE = HEIGHT // DIMENTION 19 | MAX_FPS = 15 20 | IMAGES = {} 21 | 22 | ''' 23 | Initialise the global dictionary of images. This will be called exactly once in the main 24 | ''' 25 | def loadImages(): 26 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 27 | for piece in pieces: 28 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 29 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 30 | 31 | ''' 32 | This will be out main driver. It will handle user input and update the graphics. 33 | ''' 34 | def main(): 35 | screen = p.display.set_mode((WIDTH, HEIGHT)) 36 | clock = p.time.Clock() 37 | screen.fill(p.Color('white')) 38 | gs = ChessEngine.GameState() 39 | validMoves = gs.getValidMoves() # get a list of valid moves. 40 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 41 | loadImages() # only do this once -> before the while loop 42 | running = True 43 | animate = False # Flag variable to note when we should animate the piece movement 44 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 45 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 46 | playerOne = True # if Human is playing white -> this will be true 47 | playerTwo = False # if Human is playing black -> this will be true 48 | gameOver = False # True in case of Checkmate and Stalemate 49 | while running: 50 | humanTurn = not (gs.whiteToMove ^ playerOne) 51 | for e in p.event.get(): 52 | if e.type == p.QUIT: 53 | running = False 54 | #MOUSE HANDLERS 55 | elif e.type == p.MOUSEBUTTONDOWN: 56 | if not gameOver and humanTurn: 57 | location = p.mouse.get_pos() # (x,y) position of mouse 58 | col = location[0]//SQ_SIZE 59 | row = location[1]//SQ_SIZE 60 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 61 | sqSelected = () 62 | playerClicks = [] 63 | else: 64 | sqSelected = (row, col) 65 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 66 | if len(playerClicks) == 2: # when 2nd click 67 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 68 | for i in range(len(validMoves)): 69 | if move == validMoves[i]: 70 | gs.makeMove(validMoves[i]) 71 | moveMade = True 72 | animate = True 73 | playerClicks = [] # reset platerClicks 74 | sqSelected = () # reset user clicks 75 | if not moveMade: 76 | playerClicks = [sqSelected] 77 | 78 | 79 | 80 | #KEY HANDLERS 81 | elif e.type == p.KEYDOWN: 82 | if e.key == p.K_z: #undo last move id 'z' is pressed 83 | gs.undoMove() 84 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 85 | if e.key == p.K_r: #reset the game if 'r' is pressed 86 | gs = ChessEngine.GameState() 87 | sqSelected = () 88 | playerClicks = [] 89 | moveMade = False 90 | animate = False 91 | gameOver = False 92 | validMoves = gs.getValidMoves() 93 | 94 | # AI Move finder logic 95 | if not gameOver and not humanTurn: 96 | AIMove = ChessBot.findRandomMove(validMoves) 97 | gs.makeMove(AIMove) 98 | moveMade = True 99 | animate = True 100 | 101 | if moveMade: 102 | if len(gs.moveLog) > 0 and animate: 103 | animate = False 104 | animateMove(gs.moveLog[-1], screen, gs.board, clock) 105 | validMoves = gs.getValidMoves() 106 | moveMade = False 107 | drawGameState(screen, gs, sqSelected, validMoves) 108 | 109 | if gs.checkMate: 110 | gameOver = True 111 | if gs.whiteToMove: 112 | drawText(screen, "Black Won by Checkmate!"); 113 | else: 114 | drawText(screen, "White Won by Checkmate!"); 115 | 116 | if gs.staleMate: 117 | gameOver = True 118 | drawText(screen, "Draw due to Stalemate!"); 119 | clock.tick(MAX_FPS) 120 | p.display.flip() 121 | 122 | 123 | ''' 124 | For highlighting the correct sq. of selected piece and the squares it can move to 125 | ''' 126 | def highlightSquares(screen, gs, selectedSquare, validMoves): 127 | if selectedSquare != (): 128 | r, c = selectedSquare 129 | enemyColor = 'b' if gs.whiteToMove else 'w' 130 | allyColor = 'w' if gs.whiteToMove else 'b' 131 | if gs.board[r][c][0] == allyColor: 132 | #Highlighting the selected Square 133 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 134 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 135 | s.fill(p.Color('blue')) 136 | screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE)) 137 | 138 | #Highlighting the valid move squares 139 | s.fill(p.Color('yellow')) 140 | for move in validMoves: 141 | if move.startRow == r and move.startCol == c: 142 | endRow = move.endRow 143 | endCol = move.endCol 144 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 145 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 146 | 147 | 148 | ''' 149 | responsible for all the graphics in the game 150 | ''' 151 | def drawGameState(screen, gs, selectedSquare, validMoves): 152 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 153 | highlightSquares(screen, gs, selectedSquare, validMoves) 154 | drawPieces(screen, gs.board) #draw pieces on the board 155 | 156 | 157 | ''' 158 | draw the squares on the board 159 | ''' 160 | def drawBoard(screen): 161 | global colors 162 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 163 | for r in range(DIMENTION): 164 | for c in range(DIMENTION): 165 | color = colors[(r+c)%2] 166 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 167 | 168 | 169 | 170 | ''' 171 | draw the pieces on the board using ChessEngine.GameState.board. 172 | ''' 173 | def drawPieces(screen, board): 174 | for r in range(DIMENTION): 175 | for c in range(DIMENTION): 176 | piece = board[r][c] 177 | if piece != '--': 178 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 179 | 180 | 181 | ''' 182 | Animates the movement of piece 183 | ''' 184 | def animateMove(move, screen, board, clock): 185 | global colors 186 | dR = move.endRow - move.startRow 187 | dC = move.endCol - move.startCol 188 | framesPerSquare = 5 # frames to move 1 square 189 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 190 | for frame in range(frameCount + 1): 191 | r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount) 192 | drawBoard(screen) 193 | drawPieces(screen, board) 194 | #erase piece from endRow, endCol 195 | color = colors[(move.endRow + move.endCol) % 2] 196 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.endRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 197 | p.draw.rect(screen, color, endSqaure) 198 | #draw captured piece back 199 | # if move.pieceCaptured != '--': 200 | # screen.blit(IMAGES[move.pzieceCaptured], endSqaure) 201 | #draw moving piece 202 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE)) 203 | p.display.flip() 204 | clock.tick(60) 205 | 206 | ''' 207 | To wrtie some text in the middle of the screen! 208 | ''' 209 | def drawText(screen, text): 210 | # Font Name Size Bold Italics 211 | font = p.font.SysFont("Helvitica", 32, True, False); 212 | textObject = font.render(text, 0, p.Color('Blue')) 213 | textLocation = p.Rect(0, 0, WIDTH, HEIGHT).move(WIDTH/2 - textObject.get_width()/2, HEIGHT/2 - textObject.get_height()/2) 214 | screen.blit(textObject, textLocation) 215 | 216 | 217 | 218 | if __name__ == '__main__': 219 | main() 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /2 step Min Max BOT/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | import ChessBot 14 | p.init() 15 | 16 | WIDTH = HEIGHT = 480 17 | DIMENTION = 8 # 8*8 CHESS BOARD 18 | SQ_SIZE = HEIGHT // DIMENTION 19 | MAX_FPS = 15 20 | IMAGES = {} 21 | 22 | ''' 23 | Initialise the global dictionary of images. This will be called exactly once in the main 24 | ''' 25 | def loadImages(): 26 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 27 | for piece in pieces: 28 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 29 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 30 | 31 | ''' 32 | This will be out main driver. It will handle user input and update the graphics. 33 | ''' 34 | def main(): 35 | screen = p.display.set_mode((WIDTH, HEIGHT)) 36 | clock = p.time.Clock() 37 | screen.fill(p.Color('white')) 38 | gs = ChessEngine.GameState() 39 | validMoves = gs.getValidMoves() # get a list of valid moves. 40 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 41 | loadImages() # only do this once -> before the while loop 42 | running = True 43 | animate = False # Flag variable to note when we should animate the piece movement 44 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 45 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 46 | playerOne = True # if Human is playing white -> this will be true 47 | playerTwo = False # if Human is playing black -> this will be true 48 | gameOver = False # True in case of Checkmate and Stalemate 49 | while running: 50 | humanTurn = not (gs.whiteToMove ^ playerOne) 51 | for e in p.event.get(): 52 | if e.type == p.QUIT: 53 | running = False 54 | #MOUSE HANDLERS 55 | elif e.type == p.MOUSEBUTTONDOWN: 56 | if not gameOver and humanTurn: 57 | location = p.mouse.get_pos() # (x,y) position of mouse 58 | col = location[0]//SQ_SIZE 59 | row = location[1]//SQ_SIZE 60 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 61 | sqSelected = () 62 | playerClicks = [] 63 | else: 64 | sqSelected = (row, col) 65 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 66 | if len(playerClicks) == 2: # when 2nd click 67 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 68 | for i in range(len(validMoves)): 69 | if move == validMoves[i]: 70 | gs.makeMove(validMoves[i]) 71 | moveMade = True 72 | animate = True 73 | playerClicks = [] # reset platerClicks 74 | sqSelected = () # reset user clicks 75 | if not moveMade: 76 | playerClicks = [sqSelected] 77 | 78 | 79 | 80 | #KEY HANDLERS 81 | elif e.type == p.KEYDOWN: 82 | if e.key == p.K_z: #undo last move id 'z' is pressed 83 | gs.undoMove() 84 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 85 | if e.key == p.K_r: #reset the game if 'r' is pressed 86 | gs = ChessEngine.GameState() 87 | sqSelected = () 88 | playerClicks = [] 89 | moveMade = False 90 | animate = False 91 | gameOver = False 92 | validMoves = gs.getValidMoves() 93 | 94 | # AI Move finder logic 95 | if not gameOver and not humanTurn: 96 | AIMove = ChessBot.findBestMove(gs, validMoves) 97 | if AIMove is None: # If AI can't find any move -> if any move will lead to opponent giving a checkmate. 98 | AIMove = ChessBot.findRandomMove(validMoves) 99 | gs.makeMove(AIMove) 100 | moveMade = True 101 | animate = True 102 | 103 | if moveMade: 104 | if len(gs.moveLog) > 0 and animate: 105 | animate = False 106 | animateMove(gs.moveLog[-1], screen, gs.board, clock) 107 | validMoves = gs.getValidMoves() 108 | moveMade = False 109 | drawGameState(screen, gs, sqSelected, validMoves) 110 | 111 | if gs.checkMate: 112 | gameOver = True 113 | if gs.whiteToMove: 114 | drawText(screen, "Black Won by Checkmate!"); 115 | else: 116 | drawText(screen, "White Won by Checkmate!"); 117 | 118 | if gs.staleMate: 119 | gameOver = True 120 | drawText(screen, "Draw due to Stalemate!"); 121 | clock.tick(MAX_FPS) 122 | p.display.flip() 123 | 124 | 125 | ''' 126 | For highlighting the correct sq. of selected piece and the squares it can move to 127 | ''' 128 | def highlightSquares(screen, gs, selectedSquare, validMoves): 129 | if selectedSquare != (): 130 | r, c = selectedSquare 131 | enemyColor = 'b' if gs.whiteToMove else 'w' 132 | allyColor = 'w' if gs.whiteToMove else 'b' 133 | if gs.board[r][c][0] == allyColor: 134 | #Highlighting the selected Square 135 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 136 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 137 | s.fill(p.Color('blue')) 138 | screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE)) 139 | 140 | #Highlighting the valid move squares 141 | s.fill(p.Color('yellow')) 142 | for move in validMoves: 143 | if move.startRow == r and move.startCol == c: 144 | endRow = move.endRow 145 | endCol = move.endCol 146 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 147 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 148 | 149 | 150 | ''' 151 | responsible for all the graphics in the game 152 | ''' 153 | def drawGameState(screen, gs, selectedSquare, validMoves): 154 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 155 | highlightSquares(screen, gs, selectedSquare, validMoves) 156 | drawPieces(screen, gs.board) #draw pieces on the board 157 | 158 | 159 | ''' 160 | draw the squares on the board 161 | ''' 162 | def drawBoard(screen): 163 | global colors 164 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 165 | for r in range(DIMENTION): 166 | for c in range(DIMENTION): 167 | color = colors[(r+c)%2] 168 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 169 | 170 | 171 | 172 | ''' 173 | draw the pieces on the board using ChessEngine.GameState.board. 174 | ''' 175 | def drawPieces(screen, board): 176 | for r in range(DIMENTION): 177 | for c in range(DIMENTION): 178 | piece = board[r][c] 179 | if piece != '--': 180 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 181 | 182 | 183 | ''' 184 | Animates the movement of piece 185 | ''' 186 | def animateMove(move, screen, board, clock): 187 | global colors 188 | dR = move.endRow - move.startRow 189 | dC = move.endCol - move.startCol 190 | framesPerSquare = 3 # frames to move 1 square 191 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 192 | for frame in range(frameCount + 1): 193 | r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount) 194 | drawBoard(screen) 195 | drawPieces(screen, board) 196 | #erase piece from endRow, endCol 197 | color = colors[(move.endRow + move.endCol) % 2] 198 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.endRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 199 | p.draw.rect(screen, color, endSqaure) 200 | #draw captured piece back 201 | # if move.pieceCaptured != '--': 202 | # screen.blit(IMAGES[move.pzieceCaptured], endSqaure) 203 | #draw moving piece 204 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE)) 205 | p.display.flip() 206 | clock.tick(60) 207 | 208 | ''' 209 | To wrtie some text in the middle of the screen! 210 | ''' 211 | def drawText(screen, text): 212 | # Font Name Size Bold Italics 213 | font = p.font.SysFont("Helvitica", 32, True, False); 214 | textObject = font.render(text, 0, p.Color('Blue')) 215 | textLocation = p.Rect(0, 0, WIDTH, HEIGHT).move(WIDTH/2 - textObject.get_width()/2, HEIGHT/2 - textObject.get_height()/2) 216 | screen.blit(textObject, textLocation) 217 | 218 | 219 | 220 | if __name__ == '__main__': 221 | main() 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /10. Recursive Min Max/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | import ChessBot 14 | p.init() 15 | 16 | WIDTH = HEIGHT = 480 17 | DIMENTION = 8 # 8*8 CHESS BOARD 18 | SQ_SIZE = HEIGHT // DIMENTION 19 | MAX_FPS = 15 20 | IMAGES = {} 21 | 22 | ''' 23 | Initialise the global dictionary of images. This will be called exactly once in the main 24 | ''' 25 | def loadImages(): 26 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 27 | for piece in pieces: 28 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 29 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 30 | 31 | ''' 32 | This will be out main driver. It will handle user input and update the graphics. 33 | ''' 34 | def main(): 35 | screen = p.display.set_mode((WIDTH, HEIGHT)) 36 | clock = p.time.Clock() 37 | screen.fill(p.Color('white')) 38 | gs = ChessEngine.GameState() 39 | validMoves = gs.getValidMoves() # get a list of valid moves. 40 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 41 | loadImages() # only do this once -> before the while loop 42 | running = True 43 | animate = False # Flag variable to note when we should animate the piece movement 44 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 45 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 46 | playerOne = True # if Human is playing white -> this will be true 47 | playerTwo = False # if Human is playing black -> this will be true 48 | gameOver = False # True in case of Checkmate and Stalemate 49 | while running: 50 | humanTurn = not (gs.whiteToMove ^ playerOne) 51 | for e in p.event.get(): 52 | if e.type == p.QUIT: 53 | running = False 54 | #MOUSE HANDLERS 55 | elif e.type == p.MOUSEBUTTONDOWN: 56 | if not gameOver and humanTurn: 57 | location = p.mouse.get_pos() # (x,y) position of mouse 58 | col = location[0]//SQ_SIZE 59 | row = location[1]//SQ_SIZE 60 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 61 | sqSelected = () 62 | playerClicks = [] 63 | else: 64 | sqSelected = (row, col) 65 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 66 | if len(playerClicks) == 2: # when 2nd click 67 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 68 | for i in range(len(validMoves)): 69 | if move == validMoves[i]: 70 | gs.makeMove(validMoves[i]) 71 | moveMade = True 72 | animate = True 73 | playerClicks = [] # reset playerClicks 74 | sqSelected = () # reset user clicks 75 | if not moveMade: 76 | playerClicks = [sqSelected] 77 | 78 | 79 | 80 | #KEY HANDLERS 81 | elif e.type == p.KEYDOWN: 82 | if e.key == p.K_z: #undo last move id 'z' is pressed 83 | gs.undoMove() 84 | gameOver = False 85 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 86 | if e.key == p.K_r: #reset the game if 'r' is pressed 87 | gs = ChessEngine.GameState() 88 | sqSelected = () 89 | playerClicks = [] 90 | moveMade = False 91 | animate = False 92 | gameOver = False 93 | validMoves = gs.getValidMoves() 94 | 95 | # AI Move finder logic 96 | if not gameOver and not humanTurn: 97 | AIMove = ChessBot.findBestMoveMinMax(gs, validMoves) 98 | if AIMove is None: # If AI can't find any move -> if any move will lead to opponent giving a checkmate. 99 | AIMove = ChessBot.findRandomMove(validMoves) 100 | gs.makeMove(AIMove) 101 | moveMade = True 102 | animate = True 103 | 104 | if moveMade: 105 | if len(gs.moveLog) > 0 and animate: 106 | animate = False 107 | animateMove(gs.moveLog[-1], screen, gs.board, clock) 108 | validMoves = gs.getValidMoves() 109 | moveMade = False 110 | 111 | drawGameState(screen, gs, sqSelected, validMoves) 112 | 113 | if gs.checkMate: 114 | gameOver = True 115 | if gs.whiteToMove: 116 | drawText(screen, "Black Won by Checkmate!"); 117 | else: 118 | drawText(screen, "White Won by Checkmate!"); 119 | 120 | if gs.staleMate: 121 | gameOver = True 122 | drawText(screen, "Draw due to Stalemate!") 123 | 124 | clock.tick(MAX_FPS) 125 | p.display.flip() 126 | 127 | 128 | ''' 129 | For highlighting the correct sq. of selected piece and the squares it can move to 130 | ''' 131 | def highlightSquares(screen, gs, selectedSquare, validMoves): 132 | if selectedSquare != (): 133 | r, c = selectedSquare 134 | enemyColor = 'b' if gs.whiteToMove else 'w' 135 | allyColor = 'w' if gs.whiteToMove else 'b' 136 | if gs.board[r][c][0] == allyColor: 137 | #Highlighting the selected Square 138 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 139 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 140 | s.fill(p.Color('blue')) 141 | screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE)) 142 | 143 | #Highlighting the valid move squares 144 | s.fill(p.Color('yellow')) 145 | for move in validMoves: 146 | if move.startRow == r and move.startCol == c: 147 | endRow = move.endRow 148 | endCol = move.endCol 149 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 150 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 151 | 152 | 153 | ''' 154 | responsible for all the graphics in the game 155 | ''' 156 | def drawGameState(screen, gs, selectedSquare, validMoves): 157 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 158 | highlightSquares(screen, gs, selectedSquare, validMoves) 159 | drawPieces(screen, gs.board) #draw pieces on the board 160 | 161 | 162 | ''' 163 | draw the squares on the board 164 | ''' 165 | def drawBoard(screen): 166 | global colors 167 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 168 | for r in range(DIMENTION): 169 | for c in range(DIMENTION): 170 | color = colors[(r+c)%2] 171 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 172 | 173 | 174 | 175 | ''' 176 | draw the pieces on the board using ChessEngine.GameState.board. 177 | ''' 178 | def drawPieces(screen, board): 179 | for r in range(DIMENTION): 180 | for c in range(DIMENTION): 181 | piece = board[r][c] 182 | if piece != '--': 183 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 184 | 185 | 186 | ''' 187 | Animates the movement of piece 188 | ''' 189 | def animateMove(move, screen, board, clock): 190 | global colors 191 | dR = move.endRow - move.startRow 192 | dC = move.endCol - move.startCol 193 | framesPerSquare = 3 # frames to move 1 square 194 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 195 | for frame in range(frameCount + 1): 196 | r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount) 197 | drawBoard(screen) 198 | drawPieces(screen, board) 199 | #erase piece from endRow, endCol 200 | color = colors[(move.endRow + move.endCol) % 2] 201 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.endRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 202 | p.draw.rect(screen, color, endSqaure) 203 | #draw captured piece back 204 | # if move.pieceCaptured != '--': 205 | # screen.blit(IMAGES[move.pzieceCaptured], endSqaure) 206 | #draw moving piece 207 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE)) 208 | p.display.flip() 209 | clock.tick(60) 210 | 211 | ''' 212 | To wrtie some text in the middle of the screen! 213 | ''' 214 | def drawText(screen, text): 215 | # Font Name Size Bold Italics 216 | font = p.font.SysFont("Helvitica", 32, True, False); 217 | textObject = font.render(text, 0, p.Color('Blue')) 218 | textLocation = p.Rect(0, 0, WIDTH, HEIGHT).move(WIDTH/2 - textObject.get_width()/2, HEIGHT/2 - textObject.get_height()/2) 219 | screen.blit(textObject, textLocation) 220 | 221 | 222 | 223 | if __name__ == '__main__': 224 | main() 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /11. NegaMax Alpha Beta Pruning/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | import ChessBot 14 | p.init() 15 | 16 | WIDTH = HEIGHT = 480 17 | DIMENTION = 8 # 8*8 CHESS BOARD 18 | SQ_SIZE = HEIGHT // DIMENTION 19 | MAX_FPS = 15 20 | IMAGES = {} 21 | 22 | ''' 23 | Initialise the global dictionary of images. This will be called exactly once in the main 24 | ''' 25 | def loadImages(): 26 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 27 | for piece in pieces: 28 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 29 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 30 | 31 | ''' 32 | This will be out main driver. It will handle user input and update the graphics. 33 | ''' 34 | def main(): 35 | screen = p.display.set_mode((WIDTH, HEIGHT)) 36 | clock = p.time.Clock() 37 | screen.fill(p.Color('white')) 38 | gs = ChessEngine.GameState() 39 | validMoves = gs.getValidMoves() # get a list of valid moves. 40 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 41 | loadImages() # only do this once -> before the while loop 42 | running = True 43 | animate = False # Flag variable to note when we should animate the piece movement 44 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 45 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 46 | playerOne = True # if Human is playing white -> this will be true 47 | playerTwo = False # if Human is playing black -> this will be true 48 | gameOver = False # True in case of Checkmate and Stalemate 49 | while running: 50 | humanTurn = not (gs.whiteToMove ^ playerOne) 51 | for e in p.event.get(): 52 | if e.type == p.QUIT: 53 | running = False 54 | #MOUSE HANDLERS 55 | elif e.type == p.MOUSEBUTTONDOWN: 56 | if not gameOver and humanTurn: 57 | location = p.mouse.get_pos() # (x,y) position of mouse 58 | col = location[0]//SQ_SIZE 59 | row = location[1]//SQ_SIZE 60 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 61 | sqSelected = () 62 | playerClicks = [] 63 | else: 64 | sqSelected = (row, col) 65 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 66 | if len(playerClicks) == 2: # when 2nd click 67 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 68 | for i in range(len(validMoves)): 69 | if move == validMoves[i]: 70 | gs.makeMove(validMoves[i]) 71 | moveMade = True 72 | animate = True 73 | playerClicks = [] # reset playerClicks 74 | sqSelected = () # reset user clicks 75 | if not moveMade: 76 | playerClicks = [sqSelected] 77 | 78 | 79 | 80 | #KEY HANDLERS 81 | elif e.type == p.KEYDOWN: 82 | if e.key == p.K_z: #undo last move id 'z' is pressed 83 | gs.undoMove() 84 | gameOver = False 85 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 86 | if e.key == p.K_r: #reset the game if 'r' is pressed 87 | gs = ChessEngine.GameState() 88 | sqSelected = () 89 | playerClicks = [] 90 | moveMade = False 91 | animate = False 92 | gameOver = False 93 | validMoves = gs.getValidMoves() 94 | 95 | # AI Move finder logic 96 | if not gameOver and not humanTurn: 97 | AIMove = ChessBot.findBestMoveMinMax(gs, validMoves) 98 | if AIMove is None: # If AI can't find any move -> if any move will lead to opponent giving a checkmate. 99 | AIMove = ChessBot.findRandomMove(validMoves) 100 | gs.makeMove(AIMove) 101 | moveMade = True 102 | animate = True 103 | 104 | if moveMade: 105 | if len(gs.moveLog) > 0 and animate: 106 | animate = False 107 | animateMove(gs.moveLog[-1], screen, gs.board, clock) 108 | validMoves = gs.getValidMoves() 109 | moveMade = False 110 | 111 | drawGameState(screen, gs, sqSelected, validMoves) 112 | 113 | if gs.checkMate: 114 | gameOver = True 115 | if gs.whiteToMove: 116 | drawText(screen, "Black Won by Checkmate!"); 117 | else: 118 | drawText(screen, "White Won by Checkmate!"); 119 | 120 | if gs.staleMate: 121 | gameOver = True 122 | drawText(screen, "Draw due to Stalemate!") 123 | 124 | clock.tick(MAX_FPS) 125 | p.display.flip() 126 | 127 | 128 | ''' 129 | For highlighting the correct sq. of selected piece and the squares it can move to 130 | ''' 131 | def highlightSquares(screen, gs, selectedSquare, validMoves): 132 | if selectedSquare != (): 133 | r, c = selectedSquare 134 | enemyColor = 'b' if gs.whiteToMove else 'w' 135 | allyColor = 'w' if gs.whiteToMove else 'b' 136 | if gs.board[r][c][0] == allyColor: 137 | #Highlighting the selected Square 138 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 139 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 140 | s.fill(p.Color('blue')) 141 | screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE)) 142 | 143 | #Highlighting the valid move squares 144 | s.fill(p.Color('yellow')) 145 | for move in validMoves: 146 | if move.startRow == r and move.startCol == c: 147 | endRow = move.endRow 148 | endCol = move.endCol 149 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 150 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 151 | 152 | 153 | ''' 154 | responsible for all the graphics in the game 155 | ''' 156 | def drawGameState(screen, gs, selectedSquare, validMoves): 157 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 158 | highlightSquares(screen, gs, selectedSquare, validMoves) 159 | drawPieces(screen, gs.board) #draw pieces on the board 160 | 161 | 162 | ''' 163 | draw the squares on the board 164 | ''' 165 | def drawBoard(screen): 166 | global colors 167 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 168 | for r in range(DIMENTION): 169 | for c in range(DIMENTION): 170 | color = colors[(r+c)%2] 171 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 172 | 173 | 174 | 175 | ''' 176 | draw the pieces on the board using ChessEngine.GameState.board. 177 | ''' 178 | def drawPieces(screen, board): 179 | for r in range(DIMENTION): 180 | for c in range(DIMENTION): 181 | piece = board[r][c] 182 | if piece != '--': 183 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 184 | 185 | 186 | ''' 187 | Animates the movement of piece 188 | ''' 189 | def animateMove(move, screen, board, clock): 190 | global colors 191 | dR = move.endRow - move.startRow 192 | dC = move.endCol - move.startCol 193 | framesPerSquare = 3 # frames to move 1 square 194 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 195 | for frame in range(frameCount + 1): 196 | r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount) 197 | drawBoard(screen) 198 | drawPieces(screen, board) 199 | #erase piece from endRow, endCol 200 | color = colors[(move.endRow + move.endCol) % 2] 201 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.endRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 202 | p.draw.rect(screen, color, endSqaure) 203 | #draw captured piece back 204 | # if move.pieceCaptured != '--': 205 | # screen.blit(IMAGES[move.pzieceCaptured], endSqaure) 206 | #draw moving piece 207 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE)) 208 | p.display.flip() 209 | clock.tick(60) 210 | 211 | ''' 212 | To wrtie some text in the middle of the screen! 213 | ''' 214 | def drawText(screen, text): 215 | # Font Name Size Bold Italics 216 | font = p.font.SysFont("Helvitica", 32, True, False); 217 | textObject = font.render(text, 0, p.Color('Blue')) 218 | textLocation = p.Rect(0, 0, WIDTH, HEIGHT).move(WIDTH/2 - textObject.get_width()/2, HEIGHT/2 - textObject.get_height()/2) 219 | screen.blit(textObject, textLocation) 220 | 221 | 222 | 223 | if __name__ == '__main__': 224 | main() 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /ChessBot.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # A map of piece to score value -> Standard chess scores 4 | pieceScore = {'K': 0, "P": 1, "N": 3, "B": 3, "R": 5, "Q": 9} #making King = 0, as no one can actually take the king 5 | 6 | # Knight Sore according to position -> score reduces as we move towards edges 7 | knightScores = [[1, 1, 1, 1, 1, 1, 1, 1], 8 | [1, 2, 2, 2, 2, 2, 2, 1], 9 | [1, 2, 3, 3, 3, 3, 2, 1], 10 | [1, 2, 3, 4, 4, 3, 2, 1], 11 | [1, 2, 3, 4, 4, 3, 2, 1], 12 | [1, 2, 3, 3, 3, 3, 2, 1], 13 | [1, 2, 2, 2, 2, 2, 2, 1], 14 | [1, 1, 1, 1, 1, 1, 1, 1]] 15 | 16 | bishopScores = [[4, 3, 2, 1, 1, 2, 3, 4], 17 | [3, 4, 3, 2, 2, 3, 4, 3], 18 | [2, 3, 4, 3, 3, 4, 3, 2], 19 | [1, 2, 3, 4, 4, 3, 2, 1], 20 | [1, 2, 3, 4, 4, 3, 2, 1], 21 | [2, 3, 4, 3, 3, 4, 3, 2], 22 | [3, 4, 3, 2, 2, 3, 4, 3], 23 | [4, 3, 2, 1, 1, 2, 3, 4]] 24 | 25 | queenScores = [[1, 1, 1, 3, 1, 1, 1, 1], 26 | [1, 2, 3, 3, 3, 1, 1, 1], 27 | [1, 4, 3, 3, 3, 4, 2, 1], 28 | [1, 2, 3, 3, 3, 2, 2, 1], 29 | [1, 2, 3, 3, 3, 2, 2, 1], 30 | [1, 4, 3, 3, 3, 4, 2, 1], 31 | [1, 1, 2, 3, 3, 1, 1, 1], 32 | [1, 1, 1, 3, 1, 1, 1, 1]] 33 | 34 | rookScores = [[4, 3, 4, 4, 4, 4, 3, 4], 35 | [4, 4, 4, 4, 4, 4, 4, 4], 36 | [1, 1, 2, 3, 3, 2, 1, 1], 37 | [1, 2, 3, 4, 4, 3, 2, 1], 38 | [1, 2, 3, 4, 4, 3, 2, 1], 39 | [1, 1, 2, 3, 3, 2, 1, 1], 40 | [4, 4, 4, 4, 4, 4, 4, 4], 41 | [4, 3, 4, 4, 4, 4, 3, 4]] 42 | 43 | whitePawnScores = [[8, 8, 8, 8, 8, 8, 8, 8], 44 | [8, 8, 8, 8, 8, 8, 8, 8], 45 | [5, 6, 6, 7, 7, 6, 6, 5], 46 | [2, 3, 3, 5, 5, 3, 3, 2], 47 | [1, 2, 3, 3, 2, 2, 1, 1], 48 | [1, 1, 2, 3, 3, 2, 1, 1], 49 | [1, 1, 1, 0, 0, 1, 1, 1], 50 | [0, 0, 0, 0, 0, 0, 0, 0]] 51 | 52 | blackPawnScores = [[0, 0, 0, 0, 0, 0, 0, 0], 53 | [1, 1, 1, 0, 0, 1, 1, 1], 54 | [1, 1, 2, 3, 3, 2, 1, 1], 55 | [1, 2, 3, 3, 2, 2, 1, 1], 56 | [2, 3, 3, 5, 5, 3, 3, 2], 57 | [5, 6, 6, 7, 7, 6, 6, 5], 58 | [8, 8, 8, 8, 8, 8, 8, 8], 59 | [8, 8, 8, 8, 8, 8, 8, 8]] 60 | 61 | piecePositionScores = {'N': knightScores, "Q": queenScores, "B": bishopScores, "R": rookScores, 'wP': whitePawnScores, 'bP' : blackPawnScores} 62 | 63 | CHECKMATE = 1000 # if you lead to checkmate you win -> hence max attainable score 64 | STALEMATE = 0 # If you can win(capture opponent's piece) avoid it but if you loosing(opponent can give you Checkmate) try it hence 0 and not -1000 65 | DEPTH = 3 # Depth for recursive calls 66 | ''' 67 | Function to calculate RANDOM move from the list of valid moves. 68 | ''' 69 | def findRandomMove(validMoves): 70 | if len(validMoves) > 0: 71 | return validMoves[random.randint(0, len(validMoves) - 1)] 72 | 73 | 74 | ''' 75 | Function to find the BEST move from the list of valid moves 76 | ''' 77 | def findBestMove(gs, validMoves): 78 | turnMultiplier = 1 if gs.whiteToMove else -1 # for allowing AI to play as any color 79 | playerMaxScore = -CHECKMATE # as AI is playing Black this is the worst possible score -> AI will start from worst and try to improve 80 | bestMove = None 81 | random.shuffle(validMoves) 82 | for playerMove in validMoves: # not assigning colors so AI can play as both: playerMove -> move of the current player || opponentMove -> opponent's move 83 | gs.makeMove(playerMove) 84 | opponentMinScore = CHECKMATE 85 | opponentMoves = gs.getValidMoves() 86 | if gs.checkMate: 87 | gs.undoMove() 88 | return playerMove 89 | elif gs.staleMate: 90 | opponentMinScore = STALEMATE 91 | else: 92 | for opponentMove in opponentMoves: 93 | gs.makeMove(opponentMove) 94 | gs.getValidMoves() 95 | if gs.checkMate: 96 | score = -CHECKMATE 97 | elif gs.staleMate: 98 | score = STALEMATE 99 | else: 100 | score = turnMultiplier * materialScore(gs.board) 101 | if score < opponentMinScore: 102 | opponentMinScore = score 103 | gs.undoMove() 104 | if playerMaxScore < opponentMinScore: 105 | playerMaxScore = opponentMinScore 106 | bestMove = playerMove 107 | gs.undoMove() 108 | return bestMove 109 | 110 | ''' 111 | Helper method to call recursion for the 1st time 112 | ''' 113 | def findBestMoveMinMax(gs, validMoves): 114 | global nextMove # to find the next move 115 | nextMove = None 116 | random.shuffle(validMoves) 117 | findMoveNegaMaxAlphaBetaPruning(gs, validMoves, DEPTH, -CHECKMATE, CHECKMATE, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm with Alpha Beta Pruning 118 | # findMoveNegaMax(gs, validMoves, DEPTH, 1 if gs.whiteToMove else -1) # For using Nega Max Algorithm 119 | # findMoveMinMax(gs, validMoves, DEPTH, gs.whiteToMove) # For using Min-Max Algorithm 120 | return nextMove 121 | 122 | ''' 123 | Find the best move based on material itself 124 | ''' 125 | def findMoveMinMax(gs, validMoves, depth, whiteToMove): 126 | if depth == 0: # We have reached the bottom of the tree -> with fixed depth == DEPTH 127 | return boardScore(gs) #return the score 128 | 129 | global nextMove 130 | if gs.whiteToMove: # Try to maximise score 131 | maxScore = -CHECKMATE 132 | for move in validMoves: 133 | gs.makeMove(move) 134 | nextMoves = gs.getValidMoves() 135 | score = findMoveMinMax(gs, nextMoves, depth-1, False) 136 | if score > maxScore: 137 | maxScore = score 138 | if depth == DEPTH: 139 | nextMove = move 140 | gs.undoMove() 141 | return maxScore 142 | else: # Try to minimise score 143 | minScore = CHECKMATE 144 | for move in validMoves: 145 | gs.makeMove(move) 146 | nextMoves = gs.getValidMoves() 147 | score = findMoveMinMax(gs, nextMoves, depth-1, True) 148 | if score < minScore: 149 | minScore = score 150 | if depth == DEPTH: 151 | nextMove = move 152 | gs.undoMove() 153 | return minScore 154 | 155 | ''' 156 | BEST Move calculator using NegaMax Algorithm 157 | ''' 158 | def findMoveNegaMax(gs, validMoves, depth, turnMultiplier): 159 | global nextMove 160 | if depth == 0: 161 | return turnMultiplier * boardScore(gs) 162 | 163 | maxScore = -CHECKMATE 164 | for move in validMoves: 165 | gs.makeMove(move) 166 | nextMoves = gs.getValidMoves() 167 | score = -findMoveNegaMax(gs, nextMoves, depth-1, -turnMultiplier) # negative for NEGA Max 168 | if score > maxScore: 169 | maxScore = score 170 | if depth == DEPTH: 171 | nextMove = move 172 | gs.undoMove() 173 | return score 174 | 175 | ''' 176 | BEST Move calculator using NegaMax Algorithm along with Alpha Beta Pruning 177 | ''' 178 | def findMoveNegaMaxAlphaBetaPruning(gs, validMoves, depth, alpha, beta, turnMultiplier): 179 | global nextMove 180 | if depth == 0: 181 | return turnMultiplier * boardScore(gs) 182 | 183 | # Move Ordering -> (TODO) 184 | # Traverse better moves 1st -> ones with checks and captures -> will lead to more pruning and more optimised algorithm 185 | maxScore = -CHECKMATE 186 | for move in validMoves: 187 | gs.makeMove(move) 188 | nextMoves = gs.getValidMoves() 189 | score = -findMoveNegaMaxAlphaBetaPruning(gs, nextMoves, depth-1, -beta, -alpha, -turnMultiplier) # negative for NEGA Max 190 | if score > maxScore: 191 | maxScore = score 192 | if depth == DEPTH: 193 | nextMove = move 194 | gs.undoMove() 195 | if maxScore > alpha: 196 | alpha = maxScore 197 | if alpha >= beta: 198 | break 199 | return maxScore 200 | 201 | 202 | ''' 203 | better scoring algorithm with considering checks and stalemates. 204 | +ve score good for white 205 | -ve score good for black 206 | ''' 207 | def boardScore(gs): 208 | if gs.checkMate: 209 | if gs.whiteToMove: 210 | return -CHECKMATE # BLACK WINS 211 | else: 212 | return CHECKMATE 213 | if gs.staleMate: 214 | return STALEMATE 215 | score = 0 216 | for row in range(len(gs.board)): 217 | for col in range(len(gs.board[row])): 218 | square = gs.board[row][col] 219 | if square != '--': 220 | #score it positionally 221 | piecePositionScore = 0 222 | 223 | if square[1] != 'K': 224 | if square[1] == 'P': 225 | piecePositionScore = piecePositionScores[square][row][col] 226 | else: 227 | piecePositionScore = piecePositionScores[square[1]][row][col] 228 | if square[0] == 'w': 229 | score += pieceScore[square[1]] + piecePositionScore * 0.1 # 0.1 to make the game less positional 230 | elif square[0] == 'b': 231 | score -= pieceScore[square[1]] + piecePositionScore * 0.1 232 | return score 233 | 234 | ''' 235 | Gives the score of the board according to the material on it -> White piece positive material and Black piece negative material. 236 | Assuming that Human is playing White and BOT is playing black 237 | ''' 238 | def materialScore(board): 239 | score = 0 240 | for row in board: 241 | for square in row: 242 | if square[0] == 'w': 243 | score += pieceScore[square[1]] 244 | elif square[0] == 'b': 245 | score -= pieceScore[square[1]] 246 | return score 247 | 248 | 249 | -------------------------------------------------------------------------------- /2. All Possible Moves/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', 'wR', '--', '--', 'bB', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | self.whiteToMove = True 24 | self.moveLog = [] 25 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 26 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 27 | 28 | ''' 29 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 30 | ''' 31 | def makeMove(self, move): 32 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 33 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 34 | self.moveLog.append(move) # record the move 35 | self.whiteToMove = not self.whiteToMove # swap the turn 36 | 37 | ''' 38 | Undo a move. 39 | ''' 40 | def undoMove(self): 41 | if len(self.moveLog) == 0: 42 | print('No move done till now. Can\'t UNDO at the start of the game') 43 | return 44 | move = self.moveLog.pop() 45 | self.board[move.startRow][move.startCol] = move.pieceMoved 46 | self.board[move.endRow][move.endCol] = move.pieceCaptured 47 | self.whiteToMove = not self.whiteToMove 48 | 49 | ''' 50 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 51 | ''' 52 | def getValidMoves(self): 53 | return self.getAllPossibleMoves() 54 | 55 | ''' 56 | Get a list of all possible moves -> Without considering CHECKS 57 | ''' 58 | def getAllPossibleMoves(self): 59 | moves = [] 60 | for r in range(len(self.board)): 61 | for c in range(len(self.board[r])): 62 | turn = self.board[r][c][0] 63 | piece = self.board[r][c][1] 64 | if not ((self.whiteToMove) ^ (turn == 'w')): 65 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 66 | if piece != '-': 67 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 68 | return moves 69 | 70 | ''' 71 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 72 | ''' 73 | def getPawnMoves(self, r, c, moves): 74 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 75 | if self.board[r-1][c] == '--': # 1 square pawn advance 76 | moves.append(Move((r, c), (r-1, c), self.board)) 77 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 78 | moves.append(Move((r, c), (r-2, c), self.board)) 79 | if c-1 >= 0 and self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 80 | moves.append(Move((r, c), (r-1, c-1), self.board)) 81 | if c+1 < len(self.board) and self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 82 | moves.append(Move((r, c), (r-1, c+1), self.board)) 83 | 84 | 85 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 86 | if self.board[r+1][c] == '--': # 1 square pawn advance 87 | moves.append(Move((r, c), (r+1, c), self.board)) 88 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 89 | moves.append(Move((r, c), (r+2, c), self.board)) 90 | if c-1 >= 0 and self.board[r+1][c-1][0] == 'w': # enemy pice to capture to the left 91 | moves.append(Move((r, c), (r+1, c-1), self.board)) 92 | if c+1 < len(self.board) and self.board[r+1][c+1][0] == 'w': # enemy pice to capture to the right 93 | moves.append(Move((r, c), (r+1, c+1), self.board)) 94 | 95 | ''' 96 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 97 | ''' 98 | def getRookMoves(self, r, c, moves): 99 | # #UP THE FILE 100 | # for i in range(r-1,-1,-1): 101 | # #Empty Square 102 | # if self.board[i][c] == '--': 103 | # moves.append(Move((r, c), (i, c), self.board)) 104 | # #Capture opponent's piece 105 | # elif self.board[i][c][0] != self.board[r][c][0]: 106 | # moves.append(Move((r, c), (i, c), self.board)) 107 | # break 108 | # #Same Color piece 109 | # else: 110 | # break 111 | 112 | # #DOWN THE FILE 113 | # for i in range(r+1, len(self.board)): 114 | # #Empty Square 115 | # if self.board[i][c] == '--': 116 | # moves.append(Move((r, c), (i, c), self.board)) 117 | # #Capture Oponent's piece 118 | # elif self.board[i][c][0] != self.board[r][c][0]: 119 | # moves.append(Move((r, c), (i, c), self.board)) 120 | # break 121 | # # Same color piece 122 | # else: 123 | # break 124 | 125 | # #LEFT IN THE RANK 126 | # for i in range(c-1,-1,-1): 127 | # #Empty Square 128 | # if self.board[r][i] == '--': 129 | # moves.append(Move((r, c), (r, i), self.board)) 130 | # #Capture Oponent's piece 131 | # elif self.board[r][i][0] != self.board[r][c][0]: 132 | # moves.append(Move((r, c), (r, i), self.board)) 133 | # break 134 | # # Same color piece 135 | # else: 136 | # break 137 | 138 | # #RIGHT IN THE RANK 139 | # for i in range(c+1, len(self.board[r])): 140 | # #Empty Square 141 | # if self.board[r][i] == '--': 142 | # moves.append(Move((r, c), (r, i), self.board)) 143 | # #Capture Oponent's piece 144 | # elif self.board[r][i][0] != self.board[r][c][0]: 145 | # moves.append(Move((r, c), (r, i), self.board)) 146 | # break 147 | # # Same color piece 148 | # else: 149 | # break 150 | 151 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 152 | 153 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 154 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 155 | for d in directions: 156 | for i in range(1,8): 157 | endRow = r + (d[0] * i) 158 | endCol = c + (d[1] * i) 159 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 160 | if self.board[endRow][endCol] == '--': #Empty Square 161 | moves.append(Move((r, c), (endRow, endCol), self.board)) 162 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 163 | moves.append(Move((r, c), (endRow, endCol), self.board)) 164 | break 165 | else: 166 | break # same color piece 167 | else : 168 | break #off board 169 | 170 | ''' 171 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 172 | ''' 173 | def getKnightMoves(self, r, c, moves): 174 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 175 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 176 | for d in directions: 177 | endRow = r + d[0] 178 | endCol = c + d[1] 179 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 180 | endPiece = self.board[endRow][endCol] 181 | if endPiece[0] != allyColor: 182 | moves.append(Move((r, c), (endRow, endCol), self.board)) 183 | 184 | ''' 185 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 186 | ''' 187 | def getBishopMoves(self, r, c, moves): 188 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 189 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 190 | for d in directions: 191 | for i in range(1,8): 192 | endRow = r + (d[0] * i) 193 | endCol = c + (d[1] * i) 194 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 195 | if self.board[endRow][endCol] == '--': #Empty Square 196 | moves.append(Move((r, c), (endRow, endCol), self.board)) 197 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 198 | moves.append(Move((r, c), (endRow, endCol), self.board)) 199 | break 200 | else: 201 | break # same color piece 202 | else : 203 | break #off board 204 | 205 | ''' 206 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 207 | ''' 208 | def getQueenMoves(self, r, c, moves): 209 | self.getRookMoves(r, c, moves) 210 | self.getBishopMoves(r, c, moves) 211 | 212 | ''' 213 | Get all possible moves for a King located at (r,c) and add the moves to the list. 214 | ''' 215 | def getKingMoves(self, r, c, moves): 216 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 217 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 218 | for d in directions: 219 | endRow = r + d[0] 220 | endCol = c + d[1] 221 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 222 | endPiece = self.board[endRow][endCol] 223 | if endPiece[0] != allyColor: 224 | moves.append(Move((r, c), (endRow, endCol), self.board)) 225 | 226 | 227 | class Move(): 228 | 229 | #maps keys to values 230 | #For converting (row, col) to Chess Notations => (0,0) -> a8 231 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 232 | "5": 3, "6": 2, "7": 1, "8": 0 } 233 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 234 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 235 | "e":4, "f":5, "g":6, "h":7} 236 | colsToFiles = {v:k for k,v in filesToCols.items()} 237 | 238 | def __init__(self, startSq, endSq, board): 239 | self.startRow = startSq[0] 240 | self.startCol = startSq[1] 241 | self.endRow = endSq[0] 242 | self.endCol = endSq[1] 243 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 244 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 245 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 246 | 247 | def getChessNotation(self): 248 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 249 | 250 | def getFileRank (self, r, c): 251 | return self.colsToFiles[c] + self.rowsToRanks[r] 252 | 253 | ''' 254 | overriding equal to method 255 | ''' 256 | def __eq__(self,other): 257 | return isinstance(other, Move) and self.moveId == other.moveId 258 | 259 | 260 | -------------------------------------------------------------------------------- /12. Move Log/ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | #Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 10 | import ChessEngineAd as ChessEngine 11 | else: 12 | import ChessEngine 13 | import ChessBot 14 | p.init() 15 | 16 | BOARD_WIDTH = BOARD_HEIGHT = 480 17 | MOVE_LOG_PANNEL_WIDTH = 250 18 | MOVE_LOG_PANNEL_HEIGHT = BOARD_HEIGHT 19 | DIMENTION = 8 # 8*8 CHESS BOARD 20 | SQ_SIZE = BOARD_HEIGHT // DIMENTION 21 | MAX_FPS = 15 22 | IMAGES = {} 23 | MOVE_LOG_FONT = p.font.SysFont('Arial', 12, False, False) 24 | 25 | ''' 26 | Initialise the global dictionary of images. This will be called exactly once in the main 27 | ''' 28 | def loadImages(): 29 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 30 | for piece in pieces: 31 | IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 32 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 33 | 34 | ''' 35 | This will be out main driver. It will handle user input and update the graphics. 36 | ''' 37 | def main(): 38 | screen = p.display.set_mode((BOARD_WIDTH + MOVE_LOG_PANNEL_WIDTH, BOARD_HEIGHT)) 39 | clock = p.time.Clock() 40 | screen.fill(p.Color('white')) 41 | gs = ChessEngine.GameState() 42 | validMoves = gs.getValidMoves() # get a list of valid moves. 43 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 44 | loadImages() # only do this once -> before the while loop 45 | running = True 46 | animate = False # Flag variable to note when we should animate the piece movement 47 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 48 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 49 | playerOne = True # if Human is playing white -> this will be true 50 | playerTwo = False # if Human is playing black -> this will be true 51 | gameOver = False # True in case of Checkmate and Stalemate 52 | while running: 53 | humanTurn = not (gs.whiteToMove ^ playerOne) 54 | for e in p.event.get(): 55 | if e.type == p.QUIT: 56 | running = False 57 | #MOUSE HANDLERS 58 | elif e.type == p.MOUSEBUTTONDOWN: 59 | if not gameOver and humanTurn: 60 | location = p.mouse.get_pos() # (x,y) position of mouse 61 | col = location[0]//SQ_SIZE 62 | row = location[1]//SQ_SIZE 63 | if(col >= 8): # Click out of board (on move log panel) -> do nothing 64 | continue 65 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 66 | sqSelected = () 67 | playerClicks = [] 68 | else: 69 | sqSelected = (row, col) 70 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 71 | if len(playerClicks) == 2: # when 2nd click 72 | move = ChessEngine.Move(playerClicks[0],playerClicks[1], gs.board) 73 | for i in range(len(validMoves)): 74 | if move == validMoves[i]: 75 | gs.makeMove(validMoves[i]) 76 | moveMade = True 77 | animate = True 78 | playerClicks = [] # reset playerClicks 79 | sqSelected = () # reset user clicks 80 | if not moveMade: 81 | playerClicks = [sqSelected] 82 | 83 | 84 | 85 | #KEY HANDLERS 86 | elif e.type == p.KEYDOWN: 87 | if e.key == p.K_z: #undo last move id 'z' is pressed 88 | gs.undoMove() 89 | gameOver = False 90 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 91 | if e.key == p.K_r: #reset the game if 'r' is pressed 92 | gs = ChessEngine.GameState() 93 | sqSelected = () 94 | playerClicks = [] 95 | moveMade = False 96 | animate = False 97 | gameOver = False 98 | validMoves = gs.getValidMoves() 99 | 100 | # AI Move finder logic 101 | if not gameOver and not humanTurn: 102 | AIMove = ChessBot.findBestMoveMinMax(gs, validMoves) 103 | if AIMove is None: # If AI can't find any move -> if any move will lead to opponent giving a checkmate. 104 | AIMove = ChessBot.findRandomMove(validMoves) 105 | gs.makeMove(AIMove) 106 | moveMade = True 107 | animate = True 108 | 109 | if moveMade: 110 | if len(gs.moveLog) > 0 and animate: 111 | animate = False 112 | animateMove(gs.moveLog[-1], screen, gs.board, clock) 113 | validMoves = gs.getValidMoves() 114 | moveMade = False 115 | 116 | drawGameState(screen, gs, sqSelected, validMoves) 117 | 118 | #Print Checkmate 119 | if gs.checkMate: 120 | gameOver = True 121 | if gs.whiteToMove: 122 | drawEndGameText(screen, "Black Won by Checkmate!"); 123 | else: 124 | drawEndGameText(screen, "White Won by Checkmate!"); 125 | 126 | #Print Stalmate 127 | if gs.staleMate: 128 | gameOver = True 129 | drawEndGameText(screen, "Draw due to Stalemate!") 130 | 131 | clock.tick(MAX_FPS) 132 | p.display.flip() 133 | 134 | 135 | ''' 136 | responsible for all the graphics in the game 137 | ''' 138 | def drawGameState(screen, gs, selectedSquare, validMoves): 139 | drawBoard(screen) #draw squares on board (should be called before drawing anything else) 140 | highlightSquares(screen, gs, selectedSquare, validMoves) 141 | if len(gs.moveLog) > 0: 142 | highlightLastMove(screen, gs.moveLog[-1]) 143 | drawPieces(screen, gs.board) #draw pieces on the board 144 | drawMoveLog(screen, gs) 145 | ''' 146 | draw the squares on the board 147 | ''' 148 | def drawBoard(screen): 149 | global colors 150 | colors = [p.Color(235, 235, 208), p.Color(119, 148, 85)] 151 | for r in range(DIMENTION): 152 | for c in range(DIMENTION): 153 | color = colors[(r+c)%2] 154 | p.draw.rect(screen, color, p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 155 | 156 | ''' 157 | For highlighting the correct sq. of selected piece and the squares it can move to 158 | ''' 159 | def highlightSquares(screen, gs, selectedSquare, validMoves): 160 | if selectedSquare != (): 161 | r, c = selectedSquare 162 | enemyColor = 'b' if gs.whiteToMove else 'w' 163 | allyColor = 'w' if gs.whiteToMove else 'b' 164 | if gs.board[r][c][0] == allyColor: 165 | #Highlighting the selected Square 166 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 167 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 168 | s.fill(p.Color('blue')) 169 | screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE)) 170 | 171 | #Highlighting the valid move squares 172 | s.fill(p.Color('yellow')) 173 | for move in validMoves: 174 | if move.startRow == r and move.startCol == c: 175 | endRow = move.endRow 176 | endCol = move.endCol 177 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 178 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 179 | 180 | ''' 181 | Highlight the last move 182 | ''' 183 | def highlightLastMove(screen, move): 184 | startRow = move.startRow 185 | startCol = move.startCol 186 | endRow = move.endRow 187 | endCol = move.endCol 188 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 189 | s.set_alpha(100) 190 | s.fill(p.Color("pink")) 191 | screen.blit(s, (startCol * SQ_SIZE, startRow * SQ_SIZE)) 192 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 193 | 194 | ''' 195 | Draw the pieces on the board using ChessEngine.GameState.board. 196 | ''' 197 | def drawPieces(screen, board): 198 | for r in range(DIMENTION): 199 | for c in range(DIMENTION): 200 | piece = board[r][c] 201 | if piece != '--': 202 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE*c, SQ_SIZE*r , SQ_SIZE, SQ_SIZE)) 203 | 204 | ''' 205 | Draw the 12. Move Log 206 | ''' 207 | def drawMoveLog(screen, gs): 208 | font = MOVE_LOG_FONT 209 | moveLogRect = p.Rect(BOARD_WIDTH, 0, MOVE_LOG_PANNEL_WIDTH, MOVE_LOG_PANNEL_HEIGHT) 210 | p.draw.rect(screen, p.Color('black'), moveLogRect) 211 | moveLog = [] 212 | for i in range(0, len(gs.moveLog), 2): 213 | moveText = str(i//2 + 1) + ". " + gs.moveLog[i].getChessNotation() 214 | if i < len(gs.moveLog) - 1: 215 | moveText += " " + gs.moveLog[i+1].getChessNotation() 216 | moveLog.append(moveText) 217 | 218 | horizontalPadding = 5 219 | verticalPadding = 5 220 | lineSpacing = 5 221 | for i in range(len(moveLog)): 222 | textObject = font.render(moveLog[i], True, p.Color('white')) 223 | if(verticalPadding + textObject.get_height() >= (MOVE_LOG_PANNEL_HEIGHT - 1)): 224 | # if i > 1 225 | verticalPadding = 5 226 | horizontalPadding += 100 227 | textLocation = p.Rect(moveLogRect.move(horizontalPadding, verticalPadding)) 228 | verticalPadding += textObject.get_height() + lineSpacing 229 | 230 | screen.blit(textObject, textLocation) 231 | 232 | ''' 233 | Animates the movement of piece 234 | ''' 235 | def animateMove(move, screen, board, clock): 236 | global colors 237 | dR = move.endRow - move.startRow 238 | dC = move.endCol - move.startCol 239 | framesPerSquare = 3 # frames to move 1 square 240 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 241 | for frame in range(frameCount + 1): 242 | r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount) 243 | drawBoard(screen) 244 | drawPieces(screen, board) 245 | #erase piece from endRow, endCol 246 | color = colors[(move.endRow + move.endCol) % 2] 247 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.endRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 248 | p.draw.rect(screen, color, endSqaure) 249 | #draw captured piece back 250 | if move.pieceCaptured != '--': 251 | if move.enPassant: 252 | endSqaure = p.Rect(move.endCol * SQ_SIZE, move.startRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 253 | screen.blit(IMAGES[move.pieceCaptured], endSqaure) 254 | #draw moving piece 255 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE)) 256 | p.display.flip() 257 | clock.tick(60) 258 | 259 | ''' 260 | To wrtie some text in the middle of the screen! 261 | ''' 262 | def drawEndGameText(screen, text): 263 | # Font Name Size Bold Italics 264 | font = p.font.SysFont('Helvitica', 32, True, False) 265 | textObject = font.render(text, 0, p.Color('White')) 266 | textLocation = p.Rect(0, 0, BOARD_WIDTH, BOARD_HEIGHT).move(BOARD_WIDTH / 2 - textObject.get_width() / 2, BOARD_HEIGHT / 2 - textObject.get_height() / 2) 267 | screen.blit(textObject, textLocation) 268 | textObject = font.render(text, 0, p.Color('Black')) 269 | screen.blit(textObject, textLocation.move(2, 2)) 270 | textObject = font.render(text, 0, p.Color('Blue')) 271 | screen.blit(textObject, textLocation.move(4, 4)) 272 | 273 | if __name__ == '__main__': 274 | main() 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /3. Valid Moves/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | 24 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 25 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 26 | self.whiteToMove = True 27 | self.moveLog = [] 28 | #Keeping track of kings to make valid move calculation and castling easier. 29 | self.whiteKingLocation = (7,4) 30 | self.blackKingLocation = (0,4) 31 | 32 | #keep track of checkmate and stalemate 33 | self.checkMate = False 34 | self.staleMate = False 35 | 36 | ''' 37 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 38 | ''' 39 | def makeMove(self, move): 40 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 41 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 42 | self.moveLog.append(move) # record the move 43 | self.whiteToMove = not self.whiteToMove # swap the turn 44 | #UPDATE KING'S POSITION 45 | if move.pieceMoved == 'wK': 46 | self.whiteKingLocation = (move.endRow, move.endCol) 47 | if move.pieceMoved == 'bK': 48 | self.blackKingLocation = (move.endRow, move.endCol) 49 | ''' 50 | Undo a move. 51 | ''' 52 | def undoMove(self): 53 | if len(self.moveLog) == 0: 54 | print('No move done till now. Can\'t UNDO at the start of the game') 55 | return 56 | move = self.moveLog.pop() 57 | self.board[move.startRow][move.startCol] = move.pieceMoved 58 | self.board[move.endRow][move.endCol] = move.pieceCaptured 59 | self.whiteToMove = not self.whiteToMove 60 | #UPDATE KING'S POSITION 61 | if move.pieceMoved == 'wK': 62 | self.whiteKingLocation = (move.startRow, move.startCol) 63 | if move.pieceMoved == 'bK': 64 | self.blackKingLocation = (move.startRow, move.startCol) 65 | 66 | ''' 67 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 68 | ''' 69 | def getValidMoves(self): 70 | # 1) Get a List of all possible Moves 71 | moves = self.getAllPossibleMoves() 72 | # 2) Make a move from the list of possible moves 73 | for i in range(len(moves)-1, -1, -1): #travering in opposite direction cause we have to remove some elements from the middle. 74 | self.makeMove(moves[i]) 75 | self.whiteToMove = not self.whiteToMove 76 | # 3) Generate all of the opponents move after making the move in previous stel 77 | # 4) Check if any of the opponents move leads to check -> if so remove the move from our list 78 | if self.inCheck(): 79 | moves.remove(moves[i]) 80 | self.whiteToMove = not self.whiteToMove 81 | self.undoMove() 82 | # 5) Return the final list of moves 83 | if len(moves) == 0: 84 | if self.inCheck(): 85 | print("CHECK MATE! " + ('w' if not self.whiteToMove else 'b') + " wins") 86 | 87 | self.checkMate = True 88 | else: 89 | print("DRAW DUE TO STALEMATE") 90 | self.staleMate = True 91 | else: 92 | self.checkMate = False 93 | self.staleMate = False 94 | return moves 95 | 96 | ''' 97 | Checks if the current player is under check 98 | ''' 99 | def inCheck(self): 100 | if self.whiteToMove: 101 | return self.isUnderAttack(self.whiteKingLocation[0], self.whiteKingLocation[1]) 102 | else: 103 | return self.isUnderAttack(self.blackKingLocation[0], self.blackKingLocation[1]) 104 | 105 | ''' 106 | Checks if sq (r,c) is under attack or not 107 | ''' 108 | def isUnderAttack(self, r, c): 109 | self.whiteToMove = not self.whiteToMove # switch to opponent's turn 110 | opponentsMove = self.getAllPossibleMoves() #generate opponents move 111 | self.whiteToMove = not self.whiteToMove # switch back turns 112 | for move in opponentsMove: 113 | if move.endRow == r and move.endCol == c: #sq under attak 114 | return True 115 | return False 116 | 117 | ''' 118 | Get a list of all possible moves -> Without considering CHECKS 119 | ''' 120 | def getAllPossibleMoves(self): 121 | moves = [] 122 | for r in range(len(self.board)): 123 | for c in range(len(self.board[r])): 124 | turn = self.board[r][c][0] 125 | piece = self.board[r][c][1] 126 | if not ((self.whiteToMove) ^ (turn == 'w')): 127 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 128 | if piece != '-': 129 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 130 | return moves 131 | 132 | ''' 133 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 134 | ''' 135 | def getPawnMoves(self, r, c, moves): 136 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 137 | if self.board[r-1][c] == '--': # 1 square pawn advance 138 | moves.append(Move((r, c), (r-1, c), self.board)) 139 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 140 | moves.append(Move((r, c), (r-2, c), self.board)) 141 | if c-1 >= 0 and self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 142 | moves.append(Move((r, c), (r-1, c-1), self.board)) 143 | if c+1 < len(self.board) and self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 144 | moves.append(Move((r, c), (r-1, c+1), self.board)) 145 | 146 | 147 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 148 | if self.board[r+1][c] == '--': # 1 square pawn advance 149 | moves.append(Move((r, c), (r+1, c), self.board)) 150 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 151 | moves.append(Move((r, c), (r+2, c), self.board)) 152 | if c-1 >= 0 and self.board[r+1][c-1][0] == 'w': # enemy pice to capture to the left 153 | moves.append(Move((r, c), (r+1, c-1), self.board)) 154 | if c+1 < len(self.board) and self.board[r+1][c+1][0] == 'w': # enemy pice to capture to the right 155 | moves.append(Move((r, c), (r+1, c+1), self.board)) 156 | 157 | ''' 158 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 159 | ''' 160 | def getRookMoves(self, r, c, moves): 161 | # #UP THE FILE 162 | # for i in range(r-1,-1,-1): 163 | # #Empty Square 164 | # if self.board[i][c] == '--': 165 | # moves.append(Move((r, c), (i, c), self.board)) 166 | # #Capture opponent's piece 167 | # elif self.board[i][c][0] != self.board[r][c][0]: 168 | # moves.append(Move((r, c), (i, c), self.board)) 169 | # break 170 | # #Same Color piece 171 | # else: 172 | # break 173 | 174 | # #DOWN THE FILE 175 | # for i in range(r+1, len(self.board)): 176 | # #Empty Square 177 | # if self.board[i][c] == '--': 178 | # moves.append(Move((r, c), (i, c), self.board)) 179 | # #Capture Oponent's piece 180 | # elif self.board[i][c][0] != self.board[r][c][0]: 181 | # moves.append(Move((r, c), (i, c), self.board)) 182 | # break 183 | # # Same color piece 184 | # else: 185 | # break 186 | 187 | # #LEFT IN THE RANK 188 | # for i in range(c-1,-1,-1): 189 | # #Empty Square 190 | # if self.board[r][i] == '--': 191 | # moves.append(Move((r, c), (r, i), self.board)) 192 | # #Capture Oponent's piece 193 | # elif self.board[r][i][0] != self.board[r][c][0]: 194 | # moves.append(Move((r, c), (r, i), self.board)) 195 | # break 196 | # # Same color piece 197 | # else: 198 | # break 199 | 200 | # #RIGHT IN THE RANK 201 | # for i in range(c+1, len(self.board[r])): 202 | # #Empty Square 203 | # if self.board[r][i] == '--': 204 | # moves.append(Move((r, c), (r, i), self.board)) 205 | # #Capture Oponent's piece 206 | # elif self.board[r][i][0] != self.board[r][c][0]: 207 | # moves.append(Move((r, c), (r, i), self.board)) 208 | # break 209 | # # Same color piece 210 | # else: 211 | # break 212 | 213 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 214 | 215 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 216 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 217 | for d in directions: 218 | for i in range(1,8): 219 | endRow = r + (d[0] * i) 220 | endCol = c + (d[1] * i) 221 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 222 | if self.board[endRow][endCol] == '--': #Empty Square 223 | moves.append(Move((r, c), (endRow, endCol), self.board)) 224 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 225 | moves.append(Move((r, c), (endRow, endCol), self.board)) 226 | break 227 | else: 228 | break # same color piece 229 | else : 230 | break #off board 231 | 232 | ''' 233 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 234 | ''' 235 | def getKnightMoves(self, r, c, moves): 236 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 237 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 238 | for d in directions: 239 | endRow = r + d[0] 240 | endCol = c + d[1] 241 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 242 | endPiece = self.board[endRow][endCol] 243 | if endPiece[0] != allyColor: 244 | moves.append(Move((r, c), (endRow, endCol), self.board)) 245 | 246 | ''' 247 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 248 | ''' 249 | def getBishopMoves(self, r, c, moves): 250 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 251 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 252 | for d in directions: 253 | for i in range(1,8): 254 | endRow = r + (d[0] * i) 255 | endCol = c + (d[1] * i) 256 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 257 | if self.board[endRow][endCol] == '--': #Empty Square 258 | moves.append(Move((r, c), (endRow, endCol), self.board)) 259 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 260 | moves.append(Move((r, c), (endRow, endCol), self.board)) 261 | break 262 | else: 263 | break # same color piece 264 | else : 265 | break #off board 266 | 267 | ''' 268 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 269 | ''' 270 | def getQueenMoves(self, r, c, moves): 271 | self.getRookMoves(r, c, moves) 272 | self.getBishopMoves(r, c, moves) 273 | 274 | ''' 275 | Get all possible moves for a King located at (r,c) and add the moves to the list. 276 | ''' 277 | def getKingMoves(self, r, c, moves): 278 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 279 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 280 | for d in directions: 281 | endRow = r + d[0] 282 | endCol = c + d[1] 283 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 284 | endPiece = self.board[endRow][endCol] 285 | if endPiece[0] != allyColor: 286 | moves.append(Move((r, c), (endRow, endCol), self.board)) 287 | 288 | 289 | class Move(): 290 | 291 | #maps keys to values 292 | #For converting (row, col) to Chess Notations => (0,0) -> a8 293 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 294 | "5": 3, "6": 2, "7": 1, "8": 0 } 295 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 296 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 297 | "e":4, "f":5, "g":6, "h":7} 298 | colsToFiles = {v:k for k,v in filesToCols.items()} 299 | 300 | def __init__(self, startSq, endSq, board): 301 | self.startRow = startSq[0] 302 | self.startCol = startSq[1] 303 | self.endRow = endSq[0] 304 | self.endCol = endSq[1] 305 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 306 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 307 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 308 | 309 | def getChessNotation(self): 310 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 311 | 312 | def getFileRank (self, r, c): 313 | return self.colsToFiles[c] + self.rowsToRanks[r] 314 | 315 | ''' 316 | overriding equal to method 317 | ''' 318 | def __eq__(self,other): 319 | return isinstance(other, Move) and self.moveId == other.moveId 320 | 321 | 322 | -------------------------------------------------------------------------------- /4. Valid Moves Advanced/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | 24 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 25 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 26 | self.whiteToMove = True 27 | self.moveLog = [] 28 | #Keeping track of kings to make valid move calculation and castling easier. 29 | self.whiteKingLocation = (7,4) 30 | self.blackKingLocation = (0,4) 31 | 32 | #keep track of checkmate and stalemate 33 | self.checkMate = False 34 | self.staleMate = False 35 | 36 | ''' 37 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 38 | ''' 39 | def makeMove(self, move): 40 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 41 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 42 | self.moveLog.append(move) # record the move 43 | self.whiteToMove = not self.whiteToMove # swap the turn 44 | #UPDATE KING'S POSITION 45 | if move.pieceMoved == 'wK': 46 | self.whiteKingLocation = (move.endRow, move.endCol) 47 | if move.pieceMoved == 'bK': 48 | self.blackKingLocation = (move.endRow, move.endCol) 49 | ''' 50 | Undo a move. 51 | ''' 52 | def undoMove(self): 53 | if len(self.moveLog) == 0: 54 | print('No move done till now. Can\'t UNDO at the start of the game') 55 | return 56 | move = self.moveLog.pop() 57 | self.board[move.startRow][move.startCol] = move.pieceMoved 58 | self.board[move.endRow][move.endCol] = move.pieceCaptured 59 | self.whiteToMove = not self.whiteToMove 60 | #UPDATE KING'S POSITION 61 | if move.pieceMoved == 'wK': 62 | self.whiteKingLocation = (move.startRow, move.startCol) 63 | if move.pieceMoved == 'bK': 64 | self.blackKingLocation = (move.startRow, move.startCol) 65 | 66 | ''' 67 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 68 | ''' 69 | def getValidMoves(self): 70 | # 1) Get a List of all possible Moves 71 | moves = self.getAllPossibleMoves() 72 | # 2) Make a move from the list of possible moves 73 | for i in range(len(moves)-1, -1, -1): #travering in opposite direction cause we have to remove some elements from the middle. 74 | self.makeMove(moves[i]) 75 | self.whiteToMove = not self.whiteToMove 76 | # 3) Generate all of the opponents move after making the move in previous stel 77 | # 4) Check if any of the opponents move leads to check -> if so remove the move from our list 78 | if self.inCheck(): 79 | moves.remove(moves[i]) 80 | self.whiteToMove = not self.whiteToMove 81 | self.undoMove() 82 | # 5) Return the final list of moves 83 | if len(moves) == 0: 84 | if self.inCheck(): 85 | print("CHECK MATE! " + ('white' if not self.whiteToMove else 'black') + " wins") 86 | 87 | self.checkMate = True 88 | else: 89 | print("DRAW DUE TO STALEMATE") 90 | self.staleMate = True 91 | else: 92 | self.checkMate = False 93 | self.staleMate = False 94 | return moves 95 | 96 | ''' 97 | Checks if the current player is under check 98 | ''' 99 | def inCheck(self): 100 | if self.whiteToMove: 101 | return self.isUnderAttack(self.whiteKingLocation[0], self.whiteKingLocation[1]) 102 | else: 103 | return self.isUnderAttack(self.blackKingLocation[0], self.blackKingLocation[1]) 104 | 105 | ''' 106 | Checks if sq (r,c) is under attack or not 107 | ''' 108 | def isUnderAttack(self, r, c): 109 | self.whiteToMove = not self.whiteToMove # switch to opponent's turn 110 | opponentsMove = self.getAllPossibleMoves() #generate opponents move 111 | self.whiteToMove = not self.whiteToMove # switch back turns 112 | for move in opponentsMove: 113 | if move.endRow == r and move.endCol == c: #sq under attak 114 | return True 115 | return False 116 | 117 | ''' 118 | Get a list of all possible moves -> Without considering CHECKS 119 | ''' 120 | def getAllPossibleMoves(self): 121 | moves = [] 122 | for r in range(len(self.board)): 123 | for c in range(len(self.board[r])): 124 | turn = self.board[r][c][0] 125 | piece = self.board[r][c][1] 126 | if not ((self.whiteToMove) ^ (turn == 'w')): 127 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 128 | if piece != '-': 129 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 130 | return moves 131 | 132 | ''' 133 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 134 | ''' 135 | def getPawnMoves(self, r, c, moves): 136 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 137 | if self.board[r-1][c] == '--': # 1 square pawn advance 138 | moves.append(Move((r, c), (r-1, c), self.board)) 139 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 140 | moves.append(Move((r, c), (r-2, c), self.board)) 141 | if c-1 >= 0 and self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 142 | moves.append(Move((r, c), (r-1, c-1), self.board)) 143 | if c+1 < len(self.board) and self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 144 | moves.append(Move((r, c), (r-1, c+1), self.board)) 145 | 146 | 147 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 148 | if self.board[r+1][c] == '--': # 1 square pawn advance 149 | moves.append(Move((r, c), (r+1, c), self.board)) 150 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 151 | moves.append(Move((r, c), (r+2, c), self.board)) 152 | if c-1 >= 0 and self.board[r+1][c-1][0] == 'w': # enemy pice to capture to the left 153 | moves.append(Move((r, c), (r+1, c-1), self.board)) 154 | if c+1 < len(self.board) and self.board[r+1][c+1][0] == 'w': # enemy pice to capture to the right 155 | moves.append(Move((r, c), (r+1, c+1), self.board)) 156 | 157 | ''' 158 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 159 | ''' 160 | def getRookMoves(self, r, c, moves): 161 | # #UP THE FILE 162 | # for i in range(r-1,-1,-1): 163 | # #Empty Square 164 | # if self.board[i][c] == '--': 165 | # moves.append(Move((r, c), (i, c), self.board)) 166 | # #Capture opponent's piece 167 | # elif self.board[i][c][0] != self.board[r][c][0]: 168 | # moves.append(Move((r, c), (i, c), self.board)) 169 | # break 170 | # #Same Color piece 171 | # else: 172 | # break 173 | 174 | # #DOWN THE FILE 175 | # for i in range(r+1, len(self.board)): 176 | # #Empty Square 177 | # if self.board[i][c] == '--': 178 | # moves.append(Move((r, c), (i, c), self.board)) 179 | # #Capture Oponent's piece 180 | # elif self.board[i][c][0] != self.board[r][c][0]: 181 | # moves.append(Move((r, c), (i, c), self.board)) 182 | # break 183 | # # Same color piece 184 | # else: 185 | # break 186 | 187 | # #LEFT IN THE RANK 188 | # for i in range(c-1,-1,-1): 189 | # #Empty Square 190 | # if self.board[r][i] == '--': 191 | # moves.append(Move((r, c), (r, i), self.board)) 192 | # #Capture Oponent's piece 193 | # elif self.board[r][i][0] != self.board[r][c][0]: 194 | # moves.append(Move((r, c), (r, i), self.board)) 195 | # break 196 | # # Same color piece 197 | # else: 198 | # break 199 | 200 | # #RIGHT IN THE RANK 201 | # for i in range(c+1, len(self.board[r])): 202 | # #Empty Square 203 | # if self.board[r][i] == '--': 204 | # moves.append(Move((r, c), (r, i), self.board)) 205 | # #Capture Oponent's piece 206 | # elif self.board[r][i][0] != self.board[r][c][0]: 207 | # moves.append(Move((r, c), (r, i), self.board)) 208 | # break 209 | # # Same color piece 210 | # else: 211 | # break 212 | 213 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 214 | 215 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 216 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 217 | for d in directions: 218 | for i in range(1,8): 219 | endRow = r + (d[0] * i) 220 | endCol = c + (d[1] * i) 221 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 222 | if self.board[endRow][endCol] == '--': #Empty Square 223 | moves.append(Move((r, c), (endRow, endCol), self.board)) 224 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 225 | moves.append(Move((r, c), (endRow, endCol), self.board)) 226 | break 227 | else: 228 | break # same color piece 229 | else : 230 | break #off board 231 | 232 | ''' 233 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 234 | ''' 235 | def getKnightMoves(self, r, c, moves): 236 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 237 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 238 | for d in directions: 239 | endRow = r + d[0] 240 | endCol = c + d[1] 241 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 242 | endPiece = self.board[endRow][endCol] 243 | if endPiece[0] != allyColor: 244 | moves.append(Move((r, c), (endRow, endCol), self.board)) 245 | 246 | ''' 247 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 248 | ''' 249 | def getBishopMoves(self, r, c, moves): 250 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 251 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 252 | for d in directions: 253 | for i in range(1,8): 254 | endRow = r + (d[0] * i) 255 | endCol = c + (d[1] * i) 256 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 257 | if self.board[endRow][endCol] == '--': #Empty Square 258 | moves.append(Move((r, c), (endRow, endCol), self.board)) 259 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 260 | moves.append(Move((r, c), (endRow, endCol), self.board)) 261 | break 262 | else: 263 | break # same color piece 264 | else : 265 | break #off board 266 | 267 | ''' 268 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 269 | ''' 270 | def getQueenMoves(self, r, c, moves): 271 | self.getRookMoves(r, c, moves) 272 | self.getBishopMoves(r, c, moves) 273 | 274 | ''' 275 | Get all possible moves for a King located at (r,c) and add the moves to the list. 276 | ''' 277 | def getKingMoves(self, r, c, moves): 278 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 279 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 280 | for d in directions: 281 | endRow = r + d[0] 282 | endCol = c + d[1] 283 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 284 | endPiece = self.board[endRow][endCol] 285 | if endPiece[0] != allyColor: 286 | moves.append(Move((r, c), (endRow, endCol), self.board)) 287 | 288 | 289 | class Move(): 290 | 291 | #maps keys to values 292 | #For converting (row, col) to Chess Notations => (0,0) -> a8 293 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 294 | "5": 3, "6": 2, "7": 1, "8": 0 } 295 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 296 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 297 | "e":4, "f":5, "g":6, "h":7} 298 | colsToFiles = {v:k for k,v in filesToCols.items()} 299 | 300 | def __init__(self, startSq, endSq, board): 301 | self.startRow = startSq[0] 302 | self.startCol = startSq[1] 303 | self.endRow = endSq[0] 304 | self.endCol = endSq[1] 305 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 306 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 307 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 308 | 309 | def getChessNotation(self): 310 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 311 | 312 | def getFileRank (self, r, c): 313 | return self.colsToFiles[c] + self.rowsToRanks[r] 314 | 315 | ''' 316 | overriding equal to method 317 | ''' 318 | def __eq__(self,other): 319 | return isinstance(other, Move) and self.moveId == other.moveId 320 | 321 | 322 | -------------------------------------------------------------------------------- /ChessMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is our main driver file. It will be responsible for 3 | - handling user input 4 | - displaying current GameState object 5 | """ 6 | # Working with arguments to see which algorithm to use for the Chess Engine -> Basic, Advances; 7 | import sys 8 | import pygame as p 9 | 10 | if len(sys.argv) > 1 and (sys.argv[1]).lower() == 'adv': 11 | import ChessEngineAd as ChessEngine 12 | else: 13 | import ChessEngine 14 | import ChessBot 15 | import config 16 | 17 | p.init() 18 | 19 | BOARD_WIDTH = BOARD_HEIGHT = config.BOARD_WIDTH 20 | MOVE_LOG_PANNEL_WIDTH = config.MOVE_LOG_PANNEL_WIDTH 21 | MOVE_LOG_PANNEL_HEIGHT = config.MOVE_LOG_PANNEL_HEIGHT 22 | DIMENTION = config.DIMENTION # 8*8 CHESS BOARD 23 | SQ_SIZE = BOARD_HEIGHT // DIMENTION 24 | MAX_FPS = config.MAX_FPS 25 | IMAGES = {} 26 | MOVE_LOG_FONT = p.font.SysFont(config.MOVE_LOG_FONT_NAME, config.MOVE_LOG_FONT_SIZE, False, False) 27 | 28 | ''' 29 | Initialise the global dictionary of images. This will be called exactly once in the main 30 | ''' 31 | 32 | 33 | def loadImages(): 34 | pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] 35 | for piece in pieces: 36 | IMAGES[piece] = p.transform.scale(p.image.load(config.IMAGE_FOLDER + piece + ".png"), (SQ_SIZE, SQ_SIZE)) 37 | 38 | 39 | # Note: We can access a piece by saying IMAGES['wP'] -> will give white pawn; 40 | 41 | ''' 42 | This will be out main driver. It will handle user input and update the graphics. 43 | ''' 44 | 45 | 46 | def main(): 47 | screen = p.display.set_mode((BOARD_WIDTH + MOVE_LOG_PANNEL_WIDTH, BOARD_HEIGHT)) 48 | clock = p.time.Clock() 49 | screen.fill(p.Color('white')) 50 | gs = ChessEngine.GameState() 51 | validMoves = gs.getValidMoves() # get a list of valid moves. 52 | moveMade = False # to check if the user made a move. If true recalculate validMoves. 53 | loadImages() # only do this once -> before the while loop 54 | running = True 55 | animate = False # Flag variable to note when we should animate the piece movement 56 | sqSelected = () # no sq is selected initially, keep track of the last click by the user -> (tuple : (row,col)) 57 | playerClicks = [] # contains players clicks => [(6,4),(4,4)] -> pawn at (6,4) moved 2 steps up on (4,4) 58 | playerOne = config.PLAYER_ONE_HUMAN # if Human is playing white -> this will be true 59 | playerTwo = config.PLAYER_TWO_HUMAN # if Human is playing black -> this will be true 60 | gameOver = False # True in case of Checkmate and Stalemate 61 | while running: 62 | humanTurn = (gs.whiteToMove and playerOne) or (not gs.whiteToMove and playerTwo) 63 | for e in p.event.get(): 64 | if e.type == p.QUIT: 65 | running = False 66 | # MOUSE HANDLERS 67 | elif e.type == p.MOUSEBUTTONDOWN: 68 | if not gameOver and humanTurn: 69 | location = p.mouse.get_pos() # (x,y) position of mouse 70 | col = location[0] // SQ_SIZE 71 | row = location[1] // SQ_SIZE 72 | if not playerOne: 73 | row, col = blackPerspectiveRow(row, col) 74 | if (col >= 8) or col < 0: # Click out of board (on move log panel) -> do nothing 75 | continue 76 | if sqSelected == (row, col): # user selected the same sq. twice -> deselect the selecion 77 | sqSelected = () 78 | playerClicks = [] 79 | else: 80 | sqSelected = (row, col) 81 | playerClicks.append(sqSelected) # append for both 1st and 2nd click 82 | if len(playerClicks) == 2: # when 2nd click 83 | move = ChessEngine.Move(playerClicks[0], playerClicks[1], gs.board) 84 | for i in range(len(validMoves)): 85 | if move == validMoves[i]: 86 | gs.makeMove(validMoves[i]) 87 | moveMade = True 88 | animate = True 89 | playerClicks = [] # reset playerClicks 90 | sqSelected = () # reset user clicks 91 | if not moveMade: 92 | playerClicks = [sqSelected] 93 | 94 | 95 | 96 | # KEY HANDLERS 97 | elif e.type == p.KEYDOWN: 98 | if e.key == p.K_z: # undo last move id 'z' is pressed 99 | gs.undoMove() 100 | gameOver = False 101 | moveMade = True # can do `validMoves = gs.validMoves()` but then if we change function name we will have to change the call at various places. 102 | if e.key == p.K_r: # reset the game if 'r' is pressed 103 | gs = ChessEngine.GameState() 104 | sqSelected = () 105 | playerClicks = [] 106 | moveMade = False 107 | animate = False 108 | gameOver = False 109 | validMoves = gs.getValidMoves() 110 | 111 | # AI Move finder logic 112 | if not gameOver and not humanTurn: 113 | AIMove = ChessBot.findBestMoveMinMax(gs, validMoves) 114 | if AIMove is None: # If AI can't find any move -> if any move will lead to opponent giving a checkmate. 115 | AIMove = ChessBot.findRandomMove(validMoves) 116 | gs.makeMove(AIMove) 117 | moveMade = True 118 | animate = True 119 | 120 | if moveMade: 121 | if len(gs.moveLog) > 0 and animate: 122 | animate = False 123 | animateMove(gs.moveLog[-1], screen, gs.board, clock, playerOne) 124 | validMoves = gs.getValidMoves() 125 | 126 | moveMade = False 127 | 128 | drawGameState(screen, gs, sqSelected, validMoves, playerOne) 129 | 130 | # Print Checkmate 131 | if gs.checkMate: 132 | gameOver = True 133 | if gs.whiteToMove: 134 | drawEndGameText(screen, "Black Won by Checkmate!"); 135 | else: 136 | drawEndGameText(screen, "White Won by Checkmate!"); 137 | 138 | # Print Stalmate 139 | if gs.staleMate: 140 | gameOver = True 141 | drawEndGameText(screen, "Draw due to Stalemate!") 142 | 143 | clock.tick(MAX_FPS) 144 | p.display.flip() 145 | 146 | 147 | ''' 148 | Calculate Row for Black's perspective 149 | ''' 150 | 151 | 152 | def blackPerspectiveRow(r, c): 153 | return (7 - r, 7 - c) 154 | 155 | 156 | ''' 157 | responsible for all the graphics in the game 158 | ''' 159 | 160 | 161 | def drawGameState(screen, gs, selectedSquare, validMoves, whitesPerspective): 162 | drawBoard(screen) # draw squares on board (should be called before drawing anything else) 163 | highlightSquares(screen, gs, selectedSquare, validMoves, whitesPerspective) 164 | highlightCheck(screen, gs, whitesPerspective) 165 | if len(gs.moveLog) > 0: 166 | highlightLastMove(screen, gs.moveLog[-1], whitesPerspective) 167 | drawPieces(screen, gs.board, whitesPerspective) # draw pieces on the board 168 | drawMoveLog(screen, gs) 169 | 170 | 171 | ''' 172 | draw the squares on the board 173 | ''' 174 | 175 | 176 | def drawBoard(screen): 177 | global colors 178 | colors = [config.BOARD_COLOR_LIGHT, config.BOARD_COLOR_DARK] 179 | for r in range(DIMENTION): 180 | for c in range(DIMENTION): 181 | color = colors[(r + c) % 2] 182 | p.draw.rect(screen, color, p.Rect(SQ_SIZE * c, SQ_SIZE * r, SQ_SIZE, SQ_SIZE)) 183 | 184 | 185 | ''' 186 | Highlight Check 187 | ''' 188 | 189 | 190 | def highlightCheck(screen, gs, whitesPerspective): 191 | if gs.inCheck: 192 | r, c = gs.whiteKingLocation if gs.whiteToMove else gs.blackKingLocation 193 | if not whitesPerspective: 194 | r, c = blackPerspectiveRow(r, c) 195 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 196 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 197 | s.fill(p.Color('red')) 198 | screen.blit(s, (c * SQ_SIZE, r * SQ_SIZE)) 199 | 200 | 201 | ''' 202 | For highlighting the correct sq. of selected piece and the squares it can move to 203 | ''' 204 | 205 | 206 | def highlightSquares(screen, gs, selectedSquare, validMoves, whitesPerspective): 207 | if selectedSquare != (): 208 | r, c = selectedSquare 209 | r1, c1 = r, c 210 | if not whitesPerspective: 211 | r1, c1 = blackPerspectiveRow(r, c) 212 | enemyColor = 'b' if gs.whiteToMove else 'w' 213 | allyColor = 'w' if gs.whiteToMove else 'b' 214 | if gs.board[r][c][0] == allyColor: 215 | # Highlighting the selected Square 216 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 217 | s.set_alpha(100) # transparency value -> 0 : 100% transparent | 255 : 100% Opaque 218 | s.fill(p.Color('blue')) 219 | screen.blit(s, (c1 * SQ_SIZE, r1 * SQ_SIZE)) 220 | 221 | # Highlighting the valid move squares 222 | s.fill(p.Color('yellow')) 223 | 224 | for move in validMoves: 225 | if move.startRow == r and move.startCol == c: 226 | endRow = move.endRow 227 | endCol = move.endCol 228 | drawEndRow, drawEndCol = endRow, endCol 229 | if not whitesPerspective: 230 | drawEndRow, drawEndCol = blackPerspectiveRow(endRow, endCol) 231 | if gs.board[endRow][endCol] == '--' or gs.board[endRow][endCol][0] == enemyColor: 232 | screen.blit(s, (drawEndCol * SQ_SIZE, drawEndRow * SQ_SIZE)) 233 | 234 | 235 | ''' 236 | Highlight the last move 237 | ''' 238 | 239 | 240 | def highlightLastMove(screen, move, whitesPerspective): 241 | startRow = move.startRow 242 | startCol = move.startCol 243 | endRow = move.endRow 244 | endCol = move.endCol 245 | if not whitesPerspective: 246 | startRow, startCol = blackPerspectiveRow(startRow, startCol) 247 | endRow, endCol = blackPerspectiveRow(endRow, endCol) 248 | s = p.Surface((SQ_SIZE, SQ_SIZE)) 249 | s.set_alpha(100) 250 | s.fill(p.Color("pink")) 251 | screen.blit(s, (startCol * SQ_SIZE, startRow * SQ_SIZE)) 252 | screen.blit(s, (endCol * SQ_SIZE, endRow * SQ_SIZE)) 253 | 254 | 255 | ''' 256 | Draw the pieces on the board using ChessEngine.GameState.board. 257 | ''' 258 | 259 | 260 | def drawPieces(screen, board, whitesPerspective): 261 | for r in range(DIMENTION): 262 | for c in range(DIMENTION): 263 | r1, c1 = r, c 264 | if not whitesPerspective: 265 | r1, c1 = blackPerspectiveRow(r, c) 266 | piece = board[r1][c1] 267 | if piece != '--': 268 | screen.blit(IMAGES[piece], p.Rect(SQ_SIZE * c, SQ_SIZE * r, SQ_SIZE, SQ_SIZE)) 269 | 270 | 271 | ''' 272 | Draw the 12. Move Log 273 | ''' 274 | 275 | 276 | def drawMoveLog(screen, gs): 277 | font = MOVE_LOG_FONT 278 | moveLogRect = p.Rect(BOARD_WIDTH, 0, MOVE_LOG_PANNEL_WIDTH, MOVE_LOG_PANNEL_HEIGHT) 279 | p.draw.rect(screen, p.Color('black'), moveLogRect) 280 | moveLog = [] 281 | for i in range(0, len(gs.moveLog), 2): 282 | moveText = str(i // 2 + 1) + ". " + gs.moveLog[i].getChessNotation() 283 | if i < len(gs.moveLog) - 1: 284 | moveText += " " + gs.moveLog[i + 1].getChessNotation() 285 | moveLog.append(moveText) 286 | 287 | horizontalPadding = 5 288 | verticalPadding = 5 289 | lineSpacing = 5 290 | for i in range(len(moveLog)): 291 | textObject = font.render(moveLog[i], True, p.Color('white')) 292 | if (verticalPadding + textObject.get_height() >= (MOVE_LOG_PANNEL_HEIGHT - 1)): 293 | # if i > 1 294 | verticalPadding = 5 295 | horizontalPadding += 100 296 | textLocation = p.Rect(moveLogRect.move(horizontalPadding, verticalPadding)) 297 | verticalPadding += textObject.get_height() + lineSpacing 298 | 299 | screen.blit(textObject, textLocation) 300 | if gs.checkMate: 301 | textObject = font.render("#", True, p.Color('Red')) 302 | textLocation = p.Rect(moveLogRect.move(horizontalPadding, verticalPadding)) 303 | screen.blit(textObject, textLocation) 304 | if gs.staleMate: 305 | textObject = font.render("!", True, p.Color('Blue')) 306 | textLocation = p.Rect(moveLogRect.move(horizontalPadding, verticalPadding)) 307 | screen.blit(textObject, textLocation) 308 | 309 | 310 | ''' 311 | Animates the movement of piece 312 | ''' 313 | 314 | 315 | def animateMove(move, screen, board, clock, whitesPerspective): 316 | global colors 317 | dR = move.endRow - move.startRow 318 | dC = move.endCol - move.startCol 319 | framesPerSquare = 3 # frames to move 1 square 320 | frameCount = (abs(dR) + abs(dC)) * framesPerSquare 321 | drawEndRow, drawEndCol = move.endRow, move.endCol 322 | drawStartRow, drawStartCol = move.startRow, move.startCol 323 | if not whitesPerspective: 324 | drawStartRow, drawStartCol = blackPerspectiveRow(move.startRow, move.startCol) 325 | drawEndRow, drawEndCol = blackPerspectiveRow(move.endRow, move.endCol) 326 | for frame in range(frameCount + 1): 327 | r, c = (move.startRow + dR * frame / frameCount, move.startCol + dC * frame / frameCount) 328 | if not whitesPerspective: 329 | r, c = blackPerspectiveRow(r, c) 330 | drawBoard(screen) 331 | drawPieces(screen, board, whitesPerspective) 332 | # erase piece from endRow, endCol 333 | color = colors[(drawEndRow + drawEndCol) % 2] 334 | endSqaure = p.Rect(drawEndCol * SQ_SIZE, drawEndRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 335 | p.draw.rect(screen, color, endSqaure) 336 | # draw captured piece back 337 | if move.pieceCaptured != '--': 338 | if move.enPassant: 339 | endSqaure = p.Rect(drawEndCol * SQ_SIZE, drawStartRow * SQ_SIZE, SQ_SIZE, SQ_SIZE) 340 | screen.blit(IMAGES[move.pieceCaptured], endSqaure) 341 | # draw moving piece 342 | screen.blit(IMAGES[move.pieceMoved], p.Rect(c * SQ_SIZE, r * SQ_SIZE, SQ_SIZE, SQ_SIZE)) 343 | p.display.flip() 344 | clock.tick(60) 345 | 346 | 347 | ''' 348 | To wrtie some text in the middle of the screen! 349 | ''' 350 | 351 | 352 | def drawEndGameText(screen, text): 353 | # Font Name Size Bold Italics 354 | font = p.font.SysFont('Helvitica', 32, True, False) 355 | textObject = font.render(text, 0, p.Color('White')) 356 | textLocation = p.Rect(0, 0, BOARD_WIDTH, BOARD_HEIGHT).move(BOARD_WIDTH / 2 - textObject.get_width() / 2, 357 | BOARD_HEIGHT / 2 - textObject.get_height() / 2) 358 | screen.blit(textObject, textLocation) 359 | textObject = font.render(text, 0, p.Color('Black')) 360 | screen.blit(textObject, textLocation.move(2, 2)) 361 | textObject = font.render(text, 0, p.Color('Blue')) 362 | screen.blit(textObject, textLocation.move(4, 4)) 363 | 364 | 365 | if __name__ == '__main__': 366 | main() 367 | -------------------------------------------------------------------------------- /5. Pawn Promotion and En - Passant/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | 24 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 25 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 26 | self.whiteToMove = True 27 | self.moveLog = [] 28 | #Keeping track of kings to make valid move calculation and castling easier. 29 | self.whiteKingLocation = (7,4) 30 | self.blackKingLocation = (0,4) 31 | 32 | #keep track of checkmate and stalemate 33 | self.checkMate = False 34 | self.staleMate = False 35 | 36 | self.enPassantPossible = () 37 | ''' 38 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 39 | ''' 40 | def makeMove(self, move): 41 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 42 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 43 | self.moveLog.append(move) # record the move 44 | self.whiteToMove = not self.whiteToMove # swap the turn 45 | #UPDATE KING'S POSITION 46 | if move.pieceMoved == 'wK': 47 | self.whiteKingLocation = (move.endRow, move.endCol) 48 | if move.pieceMoved == 'bK': 49 | self.blackKingLocation = (move.endRow, move.endCol) 50 | 51 | #Pawn Promotion 52 | if move.isPawnPromotion: 53 | self.board[move.endRow][move.endCol] = move.pieceMoved[0] + 'Q' # hardcoding Q for now. will include options later. 54 | 55 | #En-Passant 56 | if move.isEnPassantMove: 57 | self.board[move.startRow][move.endCol] = '--' # Capturing the Piece 58 | 59 | # Update enPassantPossible Variable 60 | if move.pieceMoved[1] == 'P' and abs(move.endRow - move.startRow) == 2 : # only on 2 sq. pawn advance 61 | self.enPassantPossible = ((move.startRow +move.endRow)//2 , move.endCol) 62 | else: 63 | self.enPassantPossible = () 64 | 65 | 66 | ''' 67 | Undo a move. 68 | ''' 69 | def undoMove(self): 70 | if len(self.moveLog) == 0: 71 | print('No move done till now. Can\'t UNDO at the start of the game') 72 | return 73 | move = self.moveLog.pop() 74 | self.board[move.startRow][move.startCol] = move.pieceMoved 75 | self.board[move.endRow][move.endCol] = move.pieceCaptured 76 | self.whiteToMove = not self.whiteToMove 77 | #UPDATE KING'S POSITION 78 | if move.pieceMoved == 'wK': 79 | self.whiteKingLocation = (move.startRow, move.startCol) 80 | if move.pieceMoved == 'bK': 81 | self.blackKingLocation = (move.startRow, move.startCol) 82 | 83 | #Undo Enpassant Move 84 | if move.isEnPassantMove: 85 | self.board[move.endRow][move.endCol] = '--' 86 | self.board[move.startRow][move.endCol] = move.pieceCaptured 87 | self.board[move.startRow][move.startCol] = move.pieceMoved 88 | self.enPassantPossible = (move.endRow, move.endCol) 89 | 90 | # UNDO a 2 sq pawn advance 91 | if move.pieceMoved[1] == 'P' and abs(move.endRow - move.startRow) == 2 : 92 | self.enPassantPossible = () 93 | 94 | 95 | ''' 96 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 97 | ''' 98 | def getValidMoves(self): 99 | tempEnPassant = self.enPassantPossible 100 | # 1) Get a List of all possible Moves 101 | moves = self.getAllPossibleMoves() 102 | # 2) Make a move from the list of possible moves 103 | for i in range(len(moves)-1, -1, -1): #travering in opposite direction cause we have to remove some elements from the middle. 104 | self.makeMove(moves[i]) 105 | self.whiteToMove = not self.whiteToMove 106 | # 3) Generate all of the opponents move after making the move in previous stel 107 | # 4) Check if any of the opponents move leads to check -> if so remove the move from our list 108 | if self.inCheck(): 109 | moves.remove(moves[i]) 110 | self.whiteToMove = not self.whiteToMove 111 | self.undoMove() 112 | # 5) Return the final list of moves 113 | if len(moves) == 0: 114 | if self.inCheck(): 115 | print("CHECK MATE! " + ('white' if not self.whiteToMove else 'black') + " wins") 116 | 117 | self.checkMate = True 118 | else: 119 | print("DRAW DUE TO STALEMATE") 120 | self.staleMate = True 121 | else: 122 | self.checkMate = False 123 | self.staleMate = False 124 | self.enPassantPossible = tempEnPassant 125 | return moves 126 | 127 | ''' 128 | Checks if the current player is under check 129 | ''' 130 | def inCheck(self): 131 | if self.whiteToMove: 132 | return self.isUnderAttack(self.whiteKingLocation[0], self.whiteKingLocation[1]) 133 | else: 134 | return self.isUnderAttack(self.blackKingLocation[0], self.blackKingLocation[1]) 135 | 136 | ''' 137 | Checks if sq (r,c) is under attack or not 138 | ''' 139 | def isUnderAttack(self, r, c): 140 | self.whiteToMove = not self.whiteToMove # switch to opponent's turn 141 | opponentsMove = self.getAllPossibleMoves() #generate opponents move 142 | self.whiteToMove = not self.whiteToMove # switch back turns 143 | for move in opponentsMove: 144 | if move.endRow == r and move.endCol == c: #sq under attak 145 | return True 146 | return False 147 | 148 | ''' 149 | Get a list of all possible moves -> Without considering CHECKS 150 | ''' 151 | def getAllPossibleMoves(self): 152 | moves = [] 153 | for r in range(len(self.board)): 154 | for c in range(len(self.board[r])): 155 | turn = self.board[r][c][0] 156 | piece = self.board[r][c][1] 157 | if not ((self.whiteToMove) ^ (turn == 'w')): 158 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 159 | if piece != '-': 160 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 161 | return moves 162 | 163 | ''' 164 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 165 | ''' 166 | def getPawnMoves(self, r, c, moves): 167 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 168 | if self.board[r-1][c] == '--': # 1 square pawn advance 169 | moves.append(Move((r, c), (r-1, c), self.board)) 170 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 171 | moves.append(Move((r, c), (r-2, c), self.board)) 172 | if c-1 >= 0: 173 | if self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 174 | moves.append(Move((r, c), (r-1, c-1), self.board)) 175 | if self.enPassantPossible == (r-1, c-1): 176 | moves.append(Move((r, c), (r-1, c-1), self.board, isEnPassantMove = True)) 177 | if c+1 < len(self.board): 178 | if self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 179 | moves.append(Move((r, c), (r-1, c+1), self.board)) 180 | if self.enPassantPossible == (r-1, c+1): 181 | moves.append(Move((r, c), (r-1, c+1), self.board, isEnPassantMove = True)) 182 | 183 | 184 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 185 | if self.board[r+1][c] == '--': # 1 square pawn advance 186 | moves.append(Move((r, c), (r+1, c), self.board)) 187 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 188 | moves.append(Move((r, c), (r+2, c), self.board)) 189 | if c-1 >= 0: 190 | if self.board[r+1][c-1][0] == 'b': # enemy pice to capture to the left 191 | moves.append(Move((r, c), (r+1, c-1), self.board)) 192 | if self.enPassantPossible == (r+1, c-1): 193 | moves.append(Move((r, c), (r+1, c-1), self.board, isEnPassantMove = True)) 194 | if c+1 < len(self.board): 195 | if self.board[r+1][c+1][0] == 'b': # enemy pice to capture to the right 196 | moves.append(Move((r, c), (r+1, c+1), self.board)) 197 | if self.enPassantPossible == (r+1, c+1): 198 | moves.append(Move((r, c), (r+1, c+1), self.board, isEnPassantMove = True)) 199 | 200 | ''' 201 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 202 | ''' 203 | def getRookMoves(self, r, c, moves): 204 | # #UP THE FILE 205 | # for i in range(r-1,-1,-1): 206 | # #Empty Square 207 | # if self.board[i][c] == '--': 208 | # moves.append(Move((r, c), (i, c), self.board)) 209 | # #Capture opponent's piece 210 | # elif self.board[i][c][0] != self.board[r][c][0]: 211 | # moves.append(Move((r, c), (i, c), self.board)) 212 | # break 213 | # #Same Color piece 214 | # else: 215 | # break 216 | 217 | # #DOWN THE FILE 218 | # for i in range(r+1, len(self.board)): 219 | # #Empty Square 220 | # if self.board[i][c] == '--': 221 | # moves.append(Move((r, c), (i, c), self.board)) 222 | # #Capture Oponent's piece 223 | # elif self.board[i][c][0] != self.board[r][c][0]: 224 | # moves.append(Move((r, c), (i, c), self.board)) 225 | # break 226 | # # Same color piece 227 | # else: 228 | # break 229 | 230 | # #LEFT IN THE RANK 231 | # for i in range(c-1,-1,-1): 232 | # #Empty Square 233 | # if self.board[r][i] == '--': 234 | # moves.append(Move((r, c), (r, i), self.board)) 235 | # #Capture Oponent's piece 236 | # elif self.board[r][i][0] != self.board[r][c][0]: 237 | # moves.append(Move((r, c), (r, i), self.board)) 238 | # break 239 | # # Same color piece 240 | # else: 241 | # break 242 | 243 | # #RIGHT IN THE RANK 244 | # for i in range(c+1, len(self.board[r])): 245 | # #Empty Square 246 | # if self.board[r][i] == '--': 247 | # moves.append(Move((r, c), (r, i), self.board)) 248 | # #Capture Oponent's piece 249 | # elif self.board[r][i][0] != self.board[r][c][0]: 250 | # moves.append(Move((r, c), (r, i), self.board)) 251 | # break 252 | # # Same color piece 253 | # else: 254 | # break 255 | 256 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 257 | 258 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 259 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 260 | for d in directions: 261 | for i in range(1,8): 262 | endRow = r + (d[0] * i) 263 | endCol = c + (d[1] * i) 264 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 265 | if self.board[endRow][endCol] == '--': #Empty Square 266 | moves.append(Move((r, c), (endRow, endCol), self.board)) 267 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 268 | moves.append(Move((r, c), (endRow, endCol), self.board)) 269 | break 270 | else: 271 | break # same color piece 272 | else : 273 | break #off board 274 | 275 | ''' 276 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 277 | ''' 278 | def getKnightMoves(self, r, c, moves): 279 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 280 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 281 | for d in directions: 282 | endRow = r + d[0] 283 | endCol = c + d[1] 284 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 285 | endPiece = self.board[endRow][endCol] 286 | if endPiece[0] != allyColor: 287 | moves.append(Move((r, c), (endRow, endCol), self.board)) 288 | 289 | ''' 290 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 291 | ''' 292 | def getBishopMoves(self, r, c, moves): 293 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 294 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 295 | for d in directions: 296 | for i in range(1,8): 297 | endRow = r + (d[0] * i) 298 | endCol = c + (d[1] * i) 299 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 300 | if self.board[endRow][endCol] == '--': #Empty Square 301 | moves.append(Move((r, c), (endRow, endCol), self.board)) 302 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 303 | moves.append(Move((r, c), (endRow, endCol), self.board)) 304 | break 305 | else: 306 | break # same color piece 307 | else : 308 | break #off board 309 | 310 | ''' 311 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 312 | ''' 313 | def getQueenMoves(self, r, c, moves): 314 | self.getRookMoves(r, c, moves) 315 | self.getBishopMoves(r, c, moves) 316 | 317 | ''' 318 | Get all possible moves for a King located at (r,c) and add the moves to the list. 319 | ''' 320 | def getKingMoves(self, r, c, moves): 321 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 322 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 323 | for d in directions: 324 | endRow = r + d[0] 325 | endCol = c + d[1] 326 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 327 | endPiece = self.board[endRow][endCol] 328 | if endPiece[0] != allyColor: 329 | moves.append(Move((r, c), (endRow, endCol), self.board)) 330 | 331 | 332 | class Move(): 333 | 334 | #maps keys to values 335 | #For converting (row, col) to Chess Notations => (0,0) -> a8 336 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 337 | "5": 3, "6": 2, "7": 1, "8": 0 } 338 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 339 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 340 | "e":4, "f":5, "g":6, "h":7} 341 | colsToFiles = {v:k for k,v in filesToCols.items()} 342 | 343 | def __init__(self, startSq, endSq, board, isEnPassantMove = False): 344 | self.startRow = startSq[0] 345 | self.startCol = startSq[1] 346 | self.endRow = endSq[0] 347 | self.endCol = endSq[1] 348 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 349 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 350 | #Pawn Promotion 351 | self.isPawnPromotion = (self.pieceMoved == 'wP' and self.endRow == 0) or (self.pieceMoved == 'bP' and self.endRow == 7) 352 | 353 | #En Passant 354 | self.isEnPassantMove = isEnPassantMove 355 | if self.isEnPassantMove: 356 | self.pieceCaptured = 'wP' if self.pieceMoved == 'bP' else 'bP' 357 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 358 | 359 | 360 | def getChessNotation(self): 361 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 362 | 363 | def getFileRank (self, r, c): 364 | return self.colsToFiles[c] + self.rowsToRanks[r] 365 | 366 | ''' 367 | overriding equal to method 368 | ''' 369 | def __eq__(self,other): 370 | return isinstance(other, Move) and self.moveId == other.moveId 371 | 372 | 373 | -------------------------------------------------------------------------------- /6. EnPassant and Pawn Promotion in Advanced Algo/ChessEngine.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | 24 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 25 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 26 | self.whiteToMove = True 27 | self.moveLog = [] 28 | #Keeping track of kings to make valid move calculation and castling easier. 29 | self.whiteKingLocation = (7,4) 30 | self.blackKingLocation = (0,4) 31 | 32 | #keep track of checkmate and stalemate 33 | self.checkMate = False 34 | self.staleMate = False 35 | 36 | self.enPassantPossible = () 37 | ''' 38 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 39 | ''' 40 | def makeMove(self, move): 41 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 42 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 43 | self.moveLog.append(move) # record the move 44 | self.whiteToMove = not self.whiteToMove # swap the turn 45 | #UPDATE KING'S POSITION 46 | if move.pieceMoved == 'wK': 47 | self.whiteKingLocation = (move.endRow, move.endCol) 48 | if move.pieceMoved == 'bK': 49 | self.blackKingLocation = (move.endRow, move.endCol) 50 | 51 | #Pawn Promotion 52 | if move.isPawnPromotion: 53 | self.board[move.endRow][move.endCol] = move.pieceMoved[0] + 'Q' # hardcoding Q for now. will include options later. 54 | 55 | #En-Passant 56 | if move.isEnPassantMove: 57 | self.board[move.startRow][move.endCol] = '--' # Capturing the Piece 58 | 59 | # Update enPassantPossible Variable 60 | if move.pieceMoved[1] == 'P' and abs(move.endRow - move.startRow) == 2 : # only on 2 sq. pawn advance 61 | self.enPassantPossible = ((move.startRow + move.endRow)//2 , move.endCol) 62 | else: 63 | self.enPassantPossible = () 64 | 65 | 66 | ''' 67 | Undo a move. 68 | ''' 69 | def undoMove(self): 70 | if len(self.moveLog) == 0: 71 | print('No move done till now. Can\'t UNDO at the start of the game') 72 | return 73 | move = self.moveLog.pop() 74 | self.board[move.startRow][move.startCol] = move.pieceMoved 75 | self.board[move.endRow][move.endCol] = move.pieceCaptured 76 | self.whiteToMove = not self.whiteToMove 77 | 78 | #UPDATE KING'S POSITION 79 | if move.pieceMoved == 'wK': 80 | self.whiteKingLocation = (move.startRow, move.startCol) 81 | if move.pieceMoved == 'bK': 82 | self.blackKingLocation = (move.startRow, move.startCol) 83 | 84 | #Undo Enpassant Move 85 | if move.isEnPassantMove: 86 | self.board[move.endRow][move.endCol] = '--' 87 | self.board[move.startRow][move.endCol] = move.pieceCaptured 88 | self.enPassantPossible = (move.endRow, move.endCol) 89 | 90 | # UNDO a 2 sq pawn advance 91 | if move.pieceMoved[1] == 'P' and abs(move.endRow - move.startRow) == 2 : 92 | self.enPassantPossible = () 93 | 94 | 95 | ''' 96 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 97 | ''' 98 | def getValidMoves(self): 99 | tempEnPassant = self.enPassantPossible 100 | # 1) Get a List of all possible Moves 101 | moves = self.getAllPossibleMoves() 102 | # 2) Make a move from the list of possible moves 103 | for i in range(len(moves)-1, -1, -1): #travering in opposite direction cause we have to remove some elements from the middle. 104 | self.makeMove(moves[i]) 105 | self.whiteToMove = not self.whiteToMove # for generating opponent's move change turn 106 | # 3) Generate all of the opponents move after making the move in previous stel 107 | # 4) Check if any of the opponents move leads to check -> if so remove the move from our list 108 | if self.inCheck(): 109 | moves.remove(moves[i]) 110 | self.whiteToMove = not self.whiteToMove 111 | self.undoMove() 112 | # 5) Return the final list of moves 113 | if len(moves) == 0: 114 | if self.inCheck(): 115 | print("CHECK MATE! " + ('white' if not self.whiteToMove else 'black') + " wins") 116 | 117 | self.checkMate = True 118 | else: 119 | print("DRAW DUE TO STALEMATE") 120 | self.staleMate = True 121 | else: 122 | self.checkMate = False 123 | self.staleMate = False 124 | self.enPassantPossible = tempEnPassant 125 | return moves 126 | 127 | ''' 128 | Checks if the current player is under check 129 | ''' 130 | def inCheck(self): 131 | if self.whiteToMove: 132 | return self.isUnderAttack(self.whiteKingLocation[0], self.whiteKingLocation[1]) 133 | else: 134 | return self.isUnderAttack(self.blackKingLocation[0], self.blackKingLocation[1]) 135 | 136 | ''' 137 | Checks if sq (r,c) is under attack or not 138 | ''' 139 | def isUnderAttack(self, r, c): 140 | self.whiteToMove = not self.whiteToMove # switch to opponent's turn 141 | opponentsMove = self.getAllPossibleMoves() #generate opponents move 142 | self.whiteToMove = not self.whiteToMove # switch back turns 143 | for move in opponentsMove: 144 | if move.endRow == r and move.endCol == c: #sq under attak 145 | return True 146 | return False 147 | 148 | ''' 149 | Get a list of all possible moves -> Without considering CHECKS 150 | ''' 151 | def getAllPossibleMoves(self): 152 | moves = [] 153 | for r in range(len(self.board)): 154 | for c in range(len(self.board[r])): 155 | turn = self.board[r][c][0] 156 | piece = self.board[r][c][1] 157 | if not ((self.whiteToMove) ^ (turn == 'w')): 158 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 159 | if piece != '-': 160 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 161 | return moves 162 | 163 | ''' 164 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 165 | ''' 166 | def getPawnMoves(self, r, c, moves): 167 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 168 | if self.board[r-1][c] == '--': # 1 square pawn advance 169 | moves.append(Move((r, c), (r-1, c), self.board)) 170 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 171 | moves.append(Move((r, c), (r-2, c), self.board)) 172 | if c-1 >= 0: 173 | if self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 174 | moves.append(Move((r, c), (r-1, c-1), self.board)) 175 | if self.enPassantPossible == (r-1, c-1): 176 | moves.append(Move((r, c), (r-1, c-1), self.board, isEnPassantMove = True)) 177 | if c+1 < len(self.board): 178 | if self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 179 | moves.append(Move((r, c), (r-1, c+1), self.board)) 180 | if self.enPassantPossible == (r-1, c+1): 181 | moves.append(Move((r, c), (r-1, c+1), self.board, isEnPassantMove = True)) 182 | 183 | 184 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 185 | if self.board[r+1][c] == '--': # 1 square pawn advance 186 | moves.append(Move((r, c), (r+1, c), self.board)) 187 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 188 | moves.append(Move((r, c), (r+2, c), self.board)) 189 | if c-1 >= 0: 190 | if self.board[r+1][c-1][0] == 'b': # enemy pice to capture to the left 191 | moves.append(Move((r, c), (r+1, c-1), self.board)) 192 | if self.enPassantPossible == (r+1, c-1): 193 | moves.append(Move((r, c), (r+1, c-1), self.board, isEnPassantMove = True)) 194 | if c+1 < len(self.board): 195 | if self.board[r+1][c+1][0] == 'b': # enemy pice to capture to the right 196 | moves.append(Move((r, c), (r+1, c+1), self.board)) 197 | if self.enPassantPossible == (r+1, c+1): 198 | moves.append(Move((r, c), (r+1, c+1), self.board, isEnPassantMove = True)) 199 | 200 | ''' 201 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 202 | ''' 203 | def getRookMoves(self, r, c, moves): 204 | # #UP THE FILE 205 | # for i in range(r-1,-1,-1): 206 | # #Empty Square 207 | # if self.board[i][c] == '--': 208 | # moves.append(Move((r, c), (i, c), self.board)) 209 | # #Capture opponent's piece 210 | # elif self.board[i][c][0] != self.board[r][c][0]: 211 | # moves.append(Move((r, c), (i, c), self.board)) 212 | # break 213 | # #Same Color piece 214 | # else: 215 | # break 216 | 217 | # #DOWN THE FILE 218 | # for i in range(r+1, len(self.board)): 219 | # #Empty Square 220 | # if self.board[i][c] == '--': 221 | # moves.append(Move((r, c), (i, c), self.board)) 222 | # #Capture Oponent's piece 223 | # elif self.board[i][c][0] != self.board[r][c][0]: 224 | # moves.append(Move((r, c), (i, c), self.board)) 225 | # break 226 | # # Same color piece 227 | # else: 228 | # break 229 | 230 | # #LEFT IN THE RANK 231 | # for i in range(c-1,-1,-1): 232 | # #Empty Square 233 | # if self.board[r][i] == '--': 234 | # moves.append(Move((r, c), (r, i), self.board)) 235 | # #Capture Oponent's piece 236 | # elif self.board[r][i][0] != self.board[r][c][0]: 237 | # moves.append(Move((r, c), (r, i), self.board)) 238 | # break 239 | # # Same color piece 240 | # else: 241 | # break 242 | 243 | # #RIGHT IN THE RANK 244 | # for i in range(c+1, len(self.board[r])): 245 | # #Empty Square 246 | # if self.board[r][i] == '--': 247 | # moves.append(Move((r, c), (r, i), self.board)) 248 | # #Capture Oponent's piece 249 | # elif self.board[r][i][0] != self.board[r][c][0]: 250 | # moves.append(Move((r, c), (r, i), self.board)) 251 | # break 252 | # # Same color piece 253 | # else: 254 | # break 255 | 256 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 257 | 258 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 259 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 260 | for d in directions: 261 | for i in range(1,8): 262 | endRow = r + (d[0] * i) 263 | endCol = c + (d[1] * i) 264 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 265 | if self.board[endRow][endCol] == '--': #Empty Square 266 | moves.append(Move((r, c), (endRow, endCol), self.board)) 267 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 268 | moves.append(Move((r, c), (endRow, endCol), self.board)) 269 | break 270 | else: 271 | break # same color piece 272 | else : 273 | break #off board 274 | 275 | ''' 276 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 277 | ''' 278 | def getKnightMoves(self, r, c, moves): 279 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 280 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 281 | for d in directions: 282 | endRow = r + d[0] 283 | endCol = c + d[1] 284 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 285 | endPiece = self.board[endRow][endCol] 286 | if endPiece[0] != allyColor: 287 | moves.append(Move((r, c), (endRow, endCol), self.board)) 288 | 289 | ''' 290 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 291 | ''' 292 | def getBishopMoves(self, r, c, moves): 293 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 294 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 295 | for d in directions: 296 | for i in range(1,8): 297 | endRow = r + (d[0] * i) 298 | endCol = c + (d[1] * i) 299 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 300 | if self.board[endRow][endCol] == '--': #Empty Square 301 | moves.append(Move((r, c), (endRow, endCol), self.board)) 302 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 303 | moves.append(Move((r, c), (endRow, endCol), self.board)) 304 | break 305 | else: 306 | break # same color piece 307 | else : 308 | break #off board 309 | 310 | ''' 311 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 312 | ''' 313 | def getQueenMoves(self, r, c, moves): 314 | self.getRookMoves(r, c, moves) 315 | self.getBishopMoves(r, c, moves) 316 | 317 | ''' 318 | Get all possible moves for a King located at (r,c) and add the moves to the list. 319 | ''' 320 | def getKingMoves(self, r, c, moves): 321 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 322 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 323 | for d in directions: 324 | endRow = r + d[0] 325 | endCol = c + d[1] 326 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 327 | endPiece = self.board[endRow][endCol] 328 | if endPiece[0] != allyColor: 329 | moves.append(Move((r, c), (endRow, endCol), self.board)) 330 | 331 | 332 | class Move(): 333 | 334 | #maps keys to values 335 | #For converting (row, col) to Chess Notations => (0,0) -> a8 336 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 337 | "5": 3, "6": 2, "7": 1, "8": 0 } 338 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 339 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 340 | "e":4, "f":5, "g":6, "h":7} 341 | colsToFiles = {v:k for k,v in filesToCols.items()} 342 | 343 | def __init__(self, startSq, endSq, board, isEnPassantMove = False): 344 | self.startRow = startSq[0] 345 | self.startCol = startSq[1] 346 | self.endRow = endSq[0] 347 | self.endCol = endSq[1] 348 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 349 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 350 | #Pawn Promotion 351 | self.isPawnPromotion = (self.pieceMoved == 'wP' and self.endRow == 0) or (self.pieceMoved == 'bP' and self.endRow == 7) 352 | 353 | #En Passant 354 | self.isEnPassantMove = isEnPassantMove 355 | if self.isEnPassantMove: 356 | self.pieceCaptured = 'wP' if self.pieceMoved == 'bP' else 'bP' 357 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 358 | 359 | 360 | def getChessNotation(self): 361 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 362 | 363 | def getFileRank (self, r, c): 364 | return self.colsToFiles[c] + self.rowsToRanks[r] 365 | 366 | ''' 367 | overriding equal to method 368 | ''' 369 | def __eq__(self,other): 370 | return isinstance(other, Move) and self.moveId == other.moveId 371 | 372 | 373 | -------------------------------------------------------------------------------- /4. Valid Moves Advanced/ChessEngineAd.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is responsible for: 3 | - storing all the information about the current game state. 4 | - determining the valid moves 5 | - will keep a move log (for doing undo and look back into current game) 6 | """ 7 | 8 | class GameState(): 9 | def __init__(self): 10 | # board is a 8*8 2D list 11 | # each element is a 2 character long string consisting of 12 | # - lower case (b/w) as color 13 | # - upper case (R,N,B,Q,K or P) as piece name 14 | # in case the cell is empty then we store '--' 15 | self.board = [['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'], 16 | ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'], 17 | ['--', '--', '--', '--', '--', '--', '--', '--'], 18 | ['--', '--', '--', '--', '--', '--', '--', '--'], 19 | ['--', '--', '--', '--', '--', '--', '--', '--'], 20 | ['--', '--', '--', '--', '--', '--', '--', '--'], 21 | ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'], 22 | ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR']] 23 | 24 | self.moveFunctions = {'P' : self.getPawnMoves, 'R' : self.getRookMoves, 'N' : self.getKnightMoves, 25 | 'B' : self.getBishopMoves, 'Q' : self.getQueenMoves, 'K' : self.getKingMoves} 26 | self.whiteToMove = True 27 | self.moveLog = [] 28 | #Keeping track of kings to make valid move calculation and castling easier. 29 | self.whiteKingLocation = (7,4) 30 | self.blackKingLocation = (0,4) 31 | 32 | self.inCheck = False 33 | self.pins = [] 34 | self.checks = [] 35 | 36 | ''' 37 | A function to move pieces on the board and record them. (Won't work for castling, pawn-promotion and en-passant) 38 | ''' 39 | def makeMove(self, move): 40 | self.board[move.startRow][move.startCol] = '--' # empty the start cell 41 | self.board[move.endRow][move.endCol] = move.pieceMoved # keep the piece moved on the end cell 42 | self.moveLog.append(move) # record the move 43 | self.whiteToMove = not self.whiteToMove # swap the turn 44 | #UPDATE KING'S POSITION 45 | if move.pieceMoved == 'wK': 46 | self.whiteKingLocation = (move.endRow, move.endCol) 47 | if move.pieceMoved == 'bK': 48 | self.blackKingLocation = (move.endRow, move.endCol) 49 | ''' 50 | Undo a move. 51 | ''' 52 | def undoMove(self): 53 | if len(self.moveLog) == 0: 54 | print('No move done till now. Can\'t UNDO at the start of the game') 55 | return 56 | move = self.moveLog.pop() 57 | self.board[move.startRow][move.startCol] = move.pieceMoved 58 | self.board[move.endRow][move.endCol] = move.pieceCaptured 59 | self.whiteToMove = not self.whiteToMove 60 | #UPDATE KING'S POSITION 61 | if move.pieceMoved == 'wK': 62 | self.whiteKingLocation = (move.startRow, move.startCol) 63 | if move.pieceMoved == 'bK': 64 | self.blackKingLocation = (move.startRow, move.startCol) 65 | 66 | ''' 67 | Get a list of all the valis moves -> the moves that user can actually make. => Considering CHECKS. 68 | ''' 69 | def getValidMoves(self): 70 | moves = [] 71 | self.inCheck, self.pins, self.checks = self.checkForPinsAndChecks() 72 | print(self.inCheck) 73 | print(self.pins) 74 | print(self.checks) 75 | if self.whiteToMove: 76 | kingRow = self.whiteKingLocation[0] 77 | kingCol = self.whiteKingLocation[1] 78 | else: 79 | kingRow = self.blackKingLocation[0] 80 | kingCol = self.blackKingLocation[1] 81 | if self.inCheck : 82 | if self.inCheck: 83 | if len(self.checks) == 1 : # only 1 check -> block check or move king 84 | moves = self.getAllPossibleMoves() 85 | #to block check -> move piece btw King and enemy piece Attacking 86 | check = self.checks[0] 87 | checkRow = check[0] 88 | checkCol = check[1] 89 | pieceChecking = self.board[checkRow][checkCol] # enemy piece causing the check 90 | validSquares = [] # sq. to which we can bring our piece to block the check 91 | #if Night then it must be captured or move the king 92 | if pieceChecking[1] == 'N': 93 | validSquares.append((checkRow, checkCol)) 94 | #other pieces can be blocked 95 | else: 96 | for i in range(1,8): 97 | validSquare = (kingRow + check[2] * i, kingCol + check[3] * i) 98 | validSquares.append(validSquare) 99 | if validSquare[0] == checkRow and validSquare[1] == checkCol: # once you reach attacking piece stop. 100 | break 101 | # get rid of any move that doesn't block king or capture the piece 102 | for i in range(len(moves)-1, -1, -1): 103 | if moves[i].pieceMoved[1] != 'K': #move doesn't move King so must block or Capture 104 | if not (moves[i].endRow, moves[i].endCol) in validSquares: 105 | moves.remove(moves[i]) 106 | else: # double check -> must move king 107 | self.getKingMoves(kingRow, kingCol, moves) 108 | 109 | else: 110 | moves = self.getAllPossibleMoves() 111 | 112 | if len(moves) == 0: 113 | if self.inCheck : 114 | if self.whiteToMove : 115 | print("Black Wins") 116 | else: 117 | print("White Wins") 118 | else: 119 | print("DRAW!! -> Stalemate", end = ', ') 120 | if self.whiteToMove : 121 | print("White Does Not have Moves") 122 | else: 123 | print("Black Does Not have Moves") 124 | return moves 125 | 126 | 127 | ''' 128 | Returns if a player is in check, a list of all pins and list of all checks 129 | ''' 130 | def checkForPinsAndChecks(self): 131 | pins = [] # sq. where allied pinned piece is and the direction of pin 132 | checks = [] # sq. where enemy is attacking 133 | inCheck = False 134 | #basic info 135 | if self.whiteToMove: 136 | enemyColor = 'b' 137 | allyColor = 'w' 138 | startRow = self.whiteKingLocation[0] 139 | startCol = self.whiteKingLocation[1] 140 | else: 141 | enemyColor = 'w' 142 | allyColor = 'b' 143 | startRow = self.blackKingLocation[0] 144 | startCol = self.blackKingLocation[1] 145 | #check outward from king for pins and checks and keep their track 146 | # U L D R U L U R D L D R 147 | directions = [(-1,0), (0,-1), (1,0), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)] 148 | for j in range(len(directions)): # stands for direction => [0,3] -> orthogoal || [4,7] -> diagonal 149 | d = directions[j] 150 | possiblePins = () #reset possible pins 151 | for i in range(1,8): # stands for number of sq. away 152 | endRow = startRow + (d[0] * i) 153 | endCol = startCol + (d[1] * i) 154 | if 0 <= endRow < 8 and 0 <= endCol < 8: 155 | endPiece = self.board[endRow][endCol] 156 | if endPiece[0] == allyColor and endPiece[1] != 'K': # when we call this function from KingMoves we temp. move king -> this generates a phantom king and actual king is protecting it so we don't want that. 157 | if possiblePins == (): # 1st piece that too ally -> might be a pin 158 | possiblePins = (endRow, endCol, d[0], d[1]) 159 | else: # 2nd ally piece -> no pins or checks in this direction 160 | break 161 | elif endPiece[0] == enemyColor: 162 | pieceType = endPiece[1] 163 | # Five different possibilities here: 164 | # 1) orthogonally away Rook 165 | # 2) Diagonally away Bishop 166 | # 3) 1 sq. diagonally away Pawn 167 | # 4) Any Direction away Queen 168 | # 5) 1 sq. any direction King 169 | if (0 <= j <= 3 and pieceType == 'R') or \ 170 | (4 <= j <= 7 and pieceType == 'B') or \ 171 | (i == 1 and pieceType == 'P') and ((enemyColor == 'w' and 6<= j <= 7) or (enemyColor=='b' and 4<= j <=5)) or \ 172 | (pieceType == 'Q') or \ 173 | (i == 1 and pieceType == 'K'): 174 | if possiblePins == (): # no piece blocking, so check 175 | inCheck = True 176 | checks.append((endRow, endCol, d[0], d[1])) 177 | break 178 | else: # there exists possibility of pin 179 | pins.append(possiblePins) 180 | break 181 | else: # enemy piece not applying check 182 | break 183 | else: # OFF BOARD 184 | break 185 | # CHECK FOR KNIGHT CHECKS: 186 | knightMoves = [(-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)] 187 | for m in knightMoves: 188 | endRow = startRow + m[0] 189 | endCol = startCol + m[1] 190 | if 0 <= endRow <=7 and 0 <= endCol <= 7: 191 | endPiece = self.board[endRow][endCol] 192 | if endPiece[0] == enemyColor and endPiece[1] == 'N' : #enemy knight attacking king 193 | inCheck = True 194 | checks.append((endRow, endCol, d[0], d[1])) 195 | return inCheck, pins, checks 196 | 197 | ''' 198 | Get a list of all possible moves -> Without considering CHECKS 199 | ''' 200 | def getAllPossibleMoves(self): 201 | moves = [] 202 | for r in range(len(self.board)): 203 | for c in range(len(self.board[r])): 204 | turn = self.board[r][c][0] 205 | piece = self.board[r][c][1] 206 | if not ((self.whiteToMove) ^ (turn == 'w')): 207 | # if (self.whiteToMove and turn == 'w') or (self.whiteToMove == False and turn == 'b'): 208 | if piece != '-': 209 | self.moveFunctions[piece](r, c, moves) #call appropriate get piece move function 210 | return moves 211 | 212 | ''' 213 | Get all possible moves for a pawn located at (r,c) and add the moves to the list. 214 | ''' 215 | def getPawnMoves(self, r, c, moves): 216 | piecePinned = False 217 | pinDirection = () 218 | for i in range(len(self.pins)-1, -1, -1): 219 | if self.pins[i][0] == r and self.pins[i][1] == c: 220 | piecePinned = True 221 | pinDirection = (self.pins[i][2], self.pins[i][3]) 222 | self.pins.remove(self.pins[i]) 223 | break 224 | 225 | if self.whiteToMove and self.board[r][c][0] == 'w': # WHITE PAWN MOVES 226 | if self.board[r-1][c] == '--': # 1 square pawn advance 227 | if not piecePinned or pinDirection == (-1, 0): 228 | moves.append(Move((r, c), (r-1, c), self.board)) 229 | if r == 6 and self.board[r-2][c] == '--': # 2 square pawn advance 230 | moves.append(Move((r, c), (r-2, c), self.board)) 231 | #captures 232 | if c-1 >= 0 and self.board[r-1][c-1][0] == 'b': # enemy pice to capture to the left 233 | if not piecePinned or pinDirection == (-1, -1): 234 | moves.append(Move((r, c), (r-1, c-1), self.board)) 235 | if c+1 < len(self.board) and self.board[r-1][c+1][0] == 'b': # enemy pice to capture to the right 236 | if not piecePinned or pinDirection == (-1, 1): 237 | moves.append(Move((r, c), (r-1, c+1), self.board)) 238 | 239 | 240 | if not self.whiteToMove and self.board[r][c][0] == 'b': # BLACK PAWN MOVES 241 | if self.board[r+1][c] == '--': # 1 square pawn advance 242 | if not piecePinned or pinDirection == (1, 0): 243 | moves.append(Move((r, c), (r+1, c), self.board)) 244 | if r == 1 and self.board[r+2][c] == '--': # 2 square pawn advance 245 | moves.append(Move((r, c), (r+2, c), self.board)) 246 | if c-1 >= 0 and self.board[r+1][c-1][0] == 'w': # enemy pice to capture to the left 247 | if not piecePinned or pinDirection == (1, -1): 248 | moves.append(Move((r, c), (r+1, c-1), self.board)) 249 | if c+1 < len(self.board) and self.board[r+1][c+1][0] == 'w': # enemy pice to capture to the right 250 | if not piecePinned or pinDirection == (1, 1): 251 | moves.append(Move((r, c), (r+1, c+1), self.board)) 252 | 253 | ''' 254 | Get all possible moves for a Rook located at (r,c) and add the moves to the list. 255 | ''' 256 | def getRookMoves(self, r, c, moves): 257 | # #UP THE FILE 258 | # for i in range(r-1,-1,-1): 259 | # #Empty Square 260 | # if self.board[i][c] == '--': 261 | # moves.append(Move((r, c), (i, c), self.board)) 262 | # #Capture opponent's piece 263 | # elif self.board[i][c][0] != self.board[r][c][0]: 264 | # moves.append(Move((r, c), (i, c), self.board)) 265 | # break 266 | # #Same Color piece 267 | # else: 268 | # break 269 | 270 | # #DOWN THE FILE 271 | # for i in range(r+1, len(self.board)): 272 | # #Empty Square 273 | # if self.board[i][c] == '--': 274 | # moves.append(Move((r, c), (i, c), self.board)) 275 | # #Capture Oponent's piece 276 | # elif self.board[i][c][0] != self.board[r][c][0]: 277 | # moves.append(Move((r, c), (i, c), self.board)) 278 | # break 279 | # # Same color piece 280 | # else: 281 | # break 282 | 283 | # #LEFT IN THE RANK 284 | # for i in range(c-1,-1,-1): 285 | # #Empty Square 286 | # if self.board[r][i] == '--': 287 | # moves.append(Move((r, c), (r, i), self.board)) 288 | # #Capture Oponent's piece 289 | # elif self.board[r][i][0] != self.board[r][c][0]: 290 | # moves.append(Move((r, c), (r, i), self.board)) 291 | # break 292 | # # Same color piece 293 | # else: 294 | # break 295 | 296 | # #RIGHT IN THE RANK 297 | # for i in range(c+1, len(self.board[r])): 298 | # #Empty Square 299 | # if self.board[r][i] == '--': 300 | # moves.append(Move((r, c), (r, i), self.board)) 301 | # #Capture Oponent's piece 302 | # elif self.board[r][i][0] != self.board[r][c][0]: 303 | # moves.append(Move((r, c), (r, i), self.board)) 304 | # break 305 | # # Same color piece 306 | # else: 307 | # break 308 | 309 | # ----------- ANOTHER WAY TO IMPLEMENT THIS ---------- # 310 | piecePinned = False 311 | pinDirection = () 312 | for i in range(len(self.pins)-1, -1, -1): 313 | if self.pins[i][0] == r and self.pins[i][1] == c: 314 | piecePinned = True 315 | pinDirection = (self.pins[i][2], self.pins[i][3]) 316 | if self.board[r][c][1] != 'Q' : # if we remove pin in case of a Queen then it will allow Bishop type moves on Queen. 317 | self.pins.remove(self.pins[i]) 318 | break 319 | directions = ((-1,0) , (1,0) , (0,-1), (0,1)) # up down left right 320 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 321 | for d in directions: 322 | for i in range(1,8): 323 | endRow = r + (d[0] * i) 324 | endCol = c + (d[1] * i) 325 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 326 | if not piecePinned or pinDirection == d or pinDirection == (-d[0], -d[1]): # move towards the pic or away from it 327 | if self.board[endRow][endCol] == '--': #Empty square 328 | moves.append(Move((r, c), (endRow, endCol), self.board)) 329 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 330 | moves.append(Move((r, c), (endRow, endCol), self.board)) 331 | break 332 | else: 333 | break # same color piece 334 | else : 335 | break #off board 336 | 337 | ''' 338 | Get all possible moves for a Knight located at (r,c) and add the moves to the list. 339 | ''' 340 | def getKnightMoves(self, r, c, moves): 341 | print("Here") 342 | piecePinned = False 343 | for i in range(len(self.pins)-1, -1, -1): 344 | if self.pins[i][0] == r and self.pins[i][1] == c: 345 | piecePinned = True 346 | self.pins.remove(self.pins[i]) 347 | break 348 | 349 | if not piecePinned: # if a knight is pinned we can't move it any where 350 | directions = ((-1,-2) , (-2,-1), (1,-2), (2,-1), (1,2), (2,1), (-1,2), (-2,1)) 351 | allyColor = 'w' if self.whiteToMove else 'b' # opponenet's color according to current turn 352 | for d in directions: 353 | endRow = r + d[0] 354 | endCol = c + d[1] 355 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 356 | endPiece = self.board[endRow][endCol] 357 | if endPiece[0] != allyColor: 358 | moves.append(Move((r, c), (endRow, endCol), self.board)) 359 | 360 | ''' 361 | Get all possible moves for a Bishop located at (r,c) and add the moves to the list. 362 | ''' 363 | def getBishopMoves(self, r, c, moves): 364 | piecePinned = False 365 | pinDirection = () 366 | for i in range(len(self.pins)-1, -1, -1): 367 | if self.pins[i][0] == r and self.pins[i][1] == c: 368 | piecePinned = True 369 | pinDirection = (self.pins[i][2], self.pins[i][3]) 370 | self.pins.remove(self.pins[i]) 371 | break 372 | 373 | directions = ((-1,-1), (-1,1), (1,-1), (1,1)) # (top left) (top right) (bottom left) (bottom right) 374 | enemyColor = 'b' if self.whiteToMove else 'w' # opponenet's color according to current turn 375 | for d in directions: 376 | for i in range(1,8): 377 | endRow = r + (d[0] * i) 378 | endCol = c + (d[1] * i) 379 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 380 | if not piecePinned or pinDirection == d or pinDirection == (-d[0], -d[1]): 381 | if self.board[endRow][endCol] == '--': #Empty Square 382 | moves.append(Move((r, c), (endRow, endCol), self.board)) 383 | elif self.board[endRow][endCol][0] == enemyColor: # capture opponent's piece 384 | moves.append(Move((r, c), (endRow, endCol), self.board)) 385 | break 386 | else: 387 | break # same color piece 388 | else : 389 | break #off board 390 | 391 | ''' 392 | Get all possible moves for a Queen located at (r,c) and add the moves to the list. 393 | ''' 394 | def getQueenMoves(self, r, c, moves): 395 | self.getRookMoves(r, c, moves) 396 | self.getBishopMoves(r, c, moves) 397 | 398 | ''' 399 | Get all possible moves for a King located at (r,c) and add the moves to the list. 400 | ''' 401 | def getKingMoves(self, r, c, moves): 402 | directions = ((-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)) 403 | allyColor = 'w' if self.whiteToMove else 'b' # ally color according to current turn 404 | for d in directions: 405 | endRow = r + d[0] 406 | endCol = c + d[1] 407 | if endRow >=0 and endRow < len(self.board) and endCol >=0 and endCol < len(self.board[endRow]): 408 | endPiece = self.board[endRow][endCol] 409 | if endPiece[0] != allyColor: # not an ally color -> enemy or blank 410 | 411 | # temporarily move the king to the new location 412 | if allyColor == 'w': 413 | self.whiteKingLocation = (endRow, endCol) 414 | if allyColor == 'b': 415 | self.blackKingLocation = (endRow, endCol) 416 | 417 | # check for check 418 | inCheck, pins, checks = self.checkForPinsAndChecks() 419 | # if not check then valid move 420 | if not inCheck: 421 | moves.append(Move((r, c), (endRow, endCol), self.board)) 422 | 423 | #place king back 424 | if allyColor == 'w': 425 | self.whiteKingLocation = (r, c) 426 | if allyColor == 'b': 427 | self.blackKingLocation = (r, c) 428 | 429 | 430 | class Move(): 431 | 432 | #maps keys to values 433 | #For converting (row, col) to Chess Notations => (0,0) -> a8 434 | ranksToRows = {"1": 7 , "2": 6, "3": 5, "4": 4, 435 | "5": 3, "6": 2, "7": 1, "8": 0 } 436 | rowsToRanks = {v:k for k,v in ranksToRows.items()} 437 | filesToCols = {"a":0, "b":1, "c":2, "d":3, 438 | "e":4, "f":5, "g":6, "h":7} 439 | colsToFiles = {v:k for k,v in filesToCols.items()} 440 | 441 | def __init__(self, startSq, endSq, board): 442 | self.startRow = startSq[0] 443 | self.startCol = startSq[1] 444 | self.endRow = endSq[0] 445 | self.endCol = endSq[1] 446 | self.pieceMoved = board[self. startRow][self. startCol] # can't be '--' 447 | self.pieceCaptured = board[self. endRow][self. endCol] # can be '--' -> no piece was captured 448 | self.moveId = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol 449 | 450 | def getChessNotation(self): 451 | return self.getFileRank(self.startRow,self.startCol) + self.getFileRank(self.endRow,self.endCol) 452 | 453 | def getFileRank (self, r, c): 454 | return self.colsToFiles[c] + self.rowsToRanks[r] 455 | 456 | ''' 457 | overriding equal to method 458 | ''' 459 | def __eq__(self,other): 460 | return isinstance(other, Move) and self.moveId == other.moveId 461 | 462 | 463 | --------------------------------------------------------------------------------