Chess_python

About

This project was created for my AP Computer Science Principles class and became my project that I submitted for the APCSP Create Task. It is an almost faithful recreation of Chess that is playable in the terminal. It contains moves such as castling, en passant, and pawns moving 2 spaces on their first move. I was also able to implement checkmate detection, stalemate detection, and an “AI” bot (it just selects a random move from a list of legal moves). Unfortunately I was unable to incorporate three-fold repetition and not allowing the player to castle out of check.

My Role

I was responsible for all the code. I thought about getting help from ChatGPT or other online chess programs but I was adamant on figuring it out myself.

What I Learned

Oh boy I learned a LOT about programming from this project. Firstly, I better learned how to utilize StackOverflow as a resource since I incorporated colored text output which I had no clue on how to do. This project was also a huge lesson in code organization and readability. When I first started coding the project, I ran into an error that I could not resolve and I found it increasingly harder to read my code. A few days before the due date, I decided to rewrite the code, maintaining mostly the same logic but creating more functions and using (somewhat) clearer variable names. It also taught me more about classes in Python, though I definitely could have used it more effectively. Despite being a pretty messy project held together by hopes and dreams, it inspired me to work on a modified version with a GUI through Pygame, a smarter AI bot using a minimax algorithm, and cleaner code with type annotations, separate files, and more concise functions.

import sys
import random
import time
from colorama import init, Back, Style
from termcolor import colored
from copy import deepcopy

init() # initializes colorama

# defines the class for the pieces, name is the name displayed on the board like 'P' for Pawn, color determines which player owns that piece
class Piece:
    def __init__(self, name, color):
        self.name = name
        self.color = color

class Pawn(Piece):
    pass
class Rook(Piece):
    pass
class Knight(Piece):
    pass
class Bishop(Piece):
    pass
class Queen(Piece):
    pass
class King(Piece):
    pass
class Empty(Piece):
    pass

# declared outside function so its value remains even if players play again, holds the number of wins of each player
whiteWin = 0
blackWin = 0


# functions controls the main game loop
def main():
    # creating board to keep track of moves used for calculations
    board = [
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
    ]

    # creating board that will actually be displayed to the player
    display = [
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
        [" ", " ", " ", " ", " ", " ", " ", " "],
    ]

    # adds pieces into the board in the stndard chess position
    for col in range(8):
        board[1][col] = Pawn(" P ", "black")
        board[6][col] = Pawn(" P ", "white")
    for row in range(2, 6):
        for col in range(8):
            board[row][col] = Empty("   ", "empty")

    board[7][4] = King(" K ", "white")
    board[0][4] = King(" K ", "black")

    board[7][2] = Bishop(" B ", "white")
    board[7][5] = Bishop(" B ", "white")
    board[0][2] = Bishop(" B ", "black")
    board[0][5] = Bishop(" B ", "black")

    board[7][1] = Knight(" N ", "white")
    board[7][6] = Knight(" N ", "white")
    board[0][1] = Knight(" N ", "black")
    board[0][6] = Knight(" N ", "black")

    board[7][3] = Queen(" Q ", "white")
    board[0][3] = Queen(" Q ", "black")

    board[7][0] = Rook(" R ", "white")
    board[7][7] = Rook(" R ", "white")
    board[0][0] = Rook(" R ", "black")
    board[0][7] = Rook(" R ", "black")

    againstBot = False
    colorChoice = ""
    stalemate = False
    checkmate = False
    playerTurn = "white"
    turnCount = 1
    global enPassant
    enPassant = False
    global enPassantPosition
    enPassantPosition = ""
    global wShortCastle
    global wLongCastle
    global bShortCastle
    global bLongCastle
    wShortCastle = True
    wLongCastle = True
    bShortCastle = True
    bLongCastle = True
    global whiteWin
    global blackWin

    # prints out the rules, then waits 1 second before starting the game
    print("\n---Chess---")
    print("This is a recreation of Chess with all the major features. Put the enemy king in checkmate to win!")
    print("Format your input as '[coordinate of the piece you want to move] [coordinate of the square you want to move to]' ")
    print("Input with the letters first, then numbers. Eg: 'e2e4' moves the piece on e2 to the e4 square. Capitalization does not matter")
    print("If you need extra help with moves, type 'moves' to get a list of all possible moves in your current position")
    print("If you have never played Chess before, input 'help' and 'rules' to learn about the game, rules, and different pieces")
    time.sleep(1)

    # asks if the user wants to play against the bot, loops until the user inputs 1 or 2, 1 means playing against player, 2 means playing against the bot
    while againstBot != "1" or againstBot != "2":
        againstBot = input("\nDo you want to play against another player (find a real life partner) or an AI bot (bot plays randomly)? Type 1 or 2. \n1 - Player \n2 - Bot \n")
        againstBot = againstBot.replace(" ", "")
        if againstBot == "1":
            againstBot = False
            break
        elif againstBot == "2":
            againstBot = True
            # if the player chooses bot, loop until the player decides what color they want to play as
            while colorChoice != "white" or colorChoice != "black":
                colorChoice = input("\nDo you want to play as white or black? White always moves first. The bot will play as the other color. \nType white or black: ")
                colorChoice = colorChoice.lower().replace(" ", "")
                if colorChoice == "white":
                    print("\nYou will play as white.")
                    break
                elif colorChoice == "black":
                    print("\nYou will play as " + colored("black", "red") + ".")
                    break
            break

    while stalemate == False or checkmate == False:
        # updates the display board according to the positions of the pieces on the board
        update_display_board(board, display)
        print("\n   | a | b | c | d | e | f | g | h | Wins: " + str(blackWin))
        print("---+---+---+---+---+---+---+---+---+---")
        for row in range(8):
            # prints the number coordinates then prints a row of the display board
            print(" " + str(abs(8 - row)) + " |", end="")
            print("|".join(display[row]), end="")
            print("| " + str(abs(8 - row)))
            print(Style.RESET_ALL + "---+---+---+---+---+---+---+---+---+---")
        print("   | a | b | c | d | e | f | g | h | Wins: " + str(whiteWin) + "\n")

        # takes the current position and finds all the possible moves that the current player can make
        possibleMovesList = find_possible_moves(playerTurn, board)
        if len(possibleMovesList) == 0:
            # if the player has no legal moves, check if its checkmate or stalemate
            if check_king_attacked(playerTurn, board) == True:
                checkmate = True
                break
            else:
                stalemate = True
                break

        legalMove = False
        # loops until the player gives a legal move
        while legalMove == False:
            if againstBot == True:
                # prompts player for move on their turn, then checks if its one of the legal moves
                if colorChoice == playerTurn:
                    if playerTurn == "black":
                        playerMove = input("* Player " + colored(playerTurn, "red") + "'s move: ")
                    else:
                        playerMove = input("* Player " + playerTurn + "'s move: ")
                    playerMove = playerMove.replace(" ", "")
                    for possibleMove in possibleMovesList:
                        if playerMove == possibleMove:
                            legalMove = True
                            break
                    else:
                        print(help_menu(playerMove, playerTurn, display, againstBot, possibleMovesList))
                # if not player turn, have the bot play a move
                else:
                    time.sleep(0.25)
                    # prints black as a different text color when its blacks turn
                    if playerTurn == "black":
                        print("* AI Bot " + colored(playerTurn, "red") + " is thinking of a move: ")
                    else:
                        print("* AI Bot " + playerTurn + " is thinking of a move: ")
                    time.sleep(0.75)
                    # the bot chooses a random value from the list of possible moves and uses it as its move
                    randomMove = random.randint(0, len(possibleMovesList) - 1)
                    playerMove = possibleMovesList[randomMove]
                    print("AI Bot played " + playerMove)
                    time.sleep(1)
                    legalMove = True
                    break

            # prompts players for their move when playing against player, then checks if its one of the legal moves
            else:
                if playerTurn == "black":
                    playerMove = input("* Player " + colored(playerTurn, "red") + "'s move: ")
                else:
                    playerMove = input("* Player " + playerTurn + "'s move: ")
                playerMove = playerMove.replace(" ", "")
                for possibleMove in possibleMovesList:
                    if playerMove == possibleMove:
                        legalMove = True
                        break
                else:
                    print(help_menu(playerMove, playerTurn, display, againstBot, possibleMovesList))

        # converts the player move into list indexs then updates the board with the moves
        playerInitial, playerFinal = convert_to_usable(playerMove)
        # updates the board with the players move
        board = deepcopy(update_game_board(playerInitial, playerFinal, playerTurn, board))
        # check if the player moved the king/rook or if the opposite color captured it, then turns the corresponding castling side illegal
        if (playerTurn == "white" and playerInitial == "70") or (playerTurn == "black" and playerFinal == "70"):
            wLongCastle = False
        if (playerTurn == "white" and playerInitial == "77") or (playerTurn == "black" and playerFinal == "77"):
            wShortCastle = False
        if playerTurn == "white" and playerInitial == "74":
            wLongCastle = False
            wShortCastle = False
        if (playerTurn == "black" and playerInitial == "00") or (playerTurn == "white" and playerFinal == "00"):
            bLongCastle = False
        if (playerTurn == "black" and playerInitial == "07") or (playerTurn == "white" and playerFinal == "07"):
            bShortCastle = False
        if playerTurn == "black" and playerInitial == "04":
            wLongCastle = False
            wShortCastle = False

        # checks if the players initial move was a pawn at starting position and they moved 2 spaces and saves the position of the final move
        # then turns en passant true until the next turn, which turns it false unless player also moved 2 spaces
        if playerTurn == "white":
            for wPawnCol in range(8):
                if playerInitial == "6" + str(wPawnCol) and playerFinal == "4" + str(wPawnCol) and isinstance(board[int(playerFinal[0])][int(playerFinal[1])], Pawn):
                    enPassant = True
                    enPassantPosition = str(playerFinal) + str(wPawnCol)
                    break
            else:
                enPassant = False
                enPassantPosition = ""
        elif playerTurn == "black":
            for bPawnCol in range(8):
                if playerInitial == "1" + str(bPawnCol) and playerFinal == "3" + str(bPawnCol) and isinstance(board[int(playerFinal[0])][int(playerFinal[1])], Pawn):
                    enPassant = True
                    enPassantPosition = str(playerFinal) + str(bPawnCol)
                    break
            else:
                enPassant = False
                enPassantPosition = ""

        check_pawn_promotion(board)
        # adds to the turn count and changes the players turn
        turnCount += 1
        if turnCount % 2 == 0:
            playerTurn = "black"
        else:
            playerTurn = "white"
        if check_king_attacked(playerTurn, board) == True:
            print("Check! Your King is currently under attack!")

    # if the game ended, check whether it was by checkmate or stalemate, and adds a win if checkmate
    if checkmate == True:
        print("---CHECKMATE---")
        if playerTurn == "white":
            print("White is currently in check and any move white makes will result in a check.")
            print("Player " + colored("black", "red") + " is the winner!")
            blackWin += 1
        else:
            print(colored("Black", "red") + " is currently in check and any move " + colored("black", "red") + " makes will result in a check.")
            print("Player white is the winner!")
            whiteWin += 1
        print("---Game Ended---")
    if stalemate == True:
        print("---STALEMATE---")
        if playerTurn == "white":
            print("White is not currently in check and has no legal moves, as they all result in check. ")
        else:
            print(colored("Black", "red") + " is not in check and has no legal moves, as they all result in check.")
        print("---Game Ended---")

    prompt_rematch()


# function takes the move and converts it into list indexes to be used for calculations
def convert_to_usable(move):
    # convert to a list because strings are immutable
    move = list(move)
    # changes the move format from (col, row) into (row, col), inverting the row since player views it bottom to top while list index goes top to bottom
    initialRow = abs(int(move[1]) - 8)
    initialCol = ord(move[0].lower()) - 97
    finalRow = abs(int(move[3]) - 8)
    finalCol = ord(move[2].lower()) - 97
    # combines the converted moves into a string
    initial = str(initialRow) + str(initialCol)
    final = str(finalRow) + str(finalCol)
    return initial, final


# function takes the players moves and applies it to the game board and updates it
def update_game_board(initialMove, finalMove, playerColor, gameBoard):
    global enPassant
    global wShortCastle
    global wLongCastle
    global bShortCastle
    global bLongCastle
    # copies the board, then updates the final position with the piece of the initial position
    newGameBoard = deepcopy(gameBoard)
    newGameBoard[int(finalMove[0])][int(finalMove[1])] = newGameBoard[int(initialMove[0])][int(initialMove[1])]
    # if the player moved en passant, also turn the captured pawn spot empty
    if enPassant == True:
        if playerColor == "white" and int(initialMove[0]) == 3 and abs(int(initialMove[0]) - 1 == int(finalMove[0])) and abs(int(finalMove[1]) - int(initialMove[1])) == 1 and int(finalMove[1]) == int(enPassantPosition[1]):
            newGameBoard[int(finalMove[0]) + 1][int(finalMove[1])] = Empty("   ", "empty")
        if playerColor == "black" and int(initialMove[0]) == 5 and abs(int(initialMove[0]) + 1 == int(finalMove[0])) and abs(int(finalMove[1]) - int(initialMove[1])) == 1 and int(finalMove[1]) == int(enPassantPosition[1]):
            newGameBoard[int(finalMove[0]) - 1][int(finalMove[1])] = Empty("   ", "empty")
    # if the player castled, move the rook to the other side of the king
    if wShortCastle == True and initialMove == "74" and finalMove == "76":
        newGameBoard[7][5] = Rook(" R ", "white")
        newGameBoard[7][7] = Empty("   ", "empty")
    if wLongCastle == True and initialMove == "74" and finalMove == "72":
        newGameBoard[7][3] = Rook(" R ", "white")
        newGameBoard[7][0] = Empty("   ", "empty")
    if bShortCastle == True and initialMove == "04" and finalMove == "06":
        newGameBoard[0][5] = Rook(" R ", "black")
        newGameBoard[0][7] = Empty("   ", "empty")
    if bLongCastle == True and initialMove == "04" and finalMove == "02":
        newGameBoard[0][3] = Rook(" R ", "black")
        newGameBoard[0][0] = Empty("   ", "empty")
    # turns the initial positon empty since the piece just moved from there
    newGameBoard[int(initialMove[0])][int(initialMove[1])] = Empty("   ", "empty")
    return newGameBoard


# function takes the game board and translates it into another board that displays the pieces with color
def update_display_board(gameBoard, displayBoard):
    # loop through the board
    for row in range(8):
        for col in range(8):
            # calculates every other square of the board in order to color the lighter squares of the checkered pattern
            if (row + col) % 2 == 0:
                # check which piece is in each position of the board, then copies the name of the piece into the display board
                if isinstance(gameBoard[row][col], Pawn):
                    displayBoard[row][col] = Back.WHITE + Style.BRIGHT + colored(" P ", "white", attrs=["bold"])
                    # if the piece is black, it will change the color on the display to reflect it
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" P ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Bishop):
                    displayBoard[row][col] = Back.WHITE + colored(" B ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" B ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Knight):
                    displayBoard[row][col] = Back.WHITE + colored(" N ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" N ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Rook):
                    displayBoard[row][col] = Back.WHITE + colored(" R ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" R ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], King):
                    displayBoard[row][col] = Back.WHITE + colored(" K ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" K ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Queen):
                    displayBoard[row][col] = Back.WHITE + colored(" Q ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.WHITE + colored(" Q ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Empty):
                    displayBoard[row][col] = Back.WHITE + colored("   ", "white")
            # color the darker squares of the board
            else:
                # check which piece is in each position of the board, then copies the name of the piece into the display board
                if isinstance(gameBoard[row][col], Pawn):
                    displayBoard[row][col] = Back.GREEN + Style.BRIGHT + colored(" P ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" P ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Bishop):
                    displayBoard[row][col] = Back.GREEN + colored(" B ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" B ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Knight):
                    displayBoard[row][col] = Back.GREEN + colored(" N ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" N ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Rook):
                    displayBoard[row][col] = Back.GREEN + colored(" R ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" R ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], King):
                    displayBoard[row][col] = Back.GREEN + colored(" K ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" K ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Queen):
                    displayBoard[row][col] = Back.GREEN + colored(" Q ", "white", attrs=["bold"])
                    if gameBoard[row][col].color == "black":
                        displayBoard[row][col] = Back.GREEN + colored(" Q ", "red", attrs=["bold"])
                elif isinstance(gameBoard[row][col], Empty):
                    displayBoard[row][col] = Back.GREEN + colored("   ", "white")
    return displayBoard


# function generates all possible player inputs and checks which inputs can turn into legal moves
def find_possible_moves(playerColor, gameBoard):
    allPossibleMoves = []
    # generate 4 numbers and convert 2 of them into letters, then combines them in a variable to simulate a player inputted move and checks legality
    for letterInitial in range(0, 8):
        for numInitial in range(1, 9):
            for letterFinal in range(0, 8):
                for numFinal in range(1, 9):
                    testLetterInitial = chr(letterInitial + 97)
                    testLetterFinal = chr(letterFinal + 97)
                    testMove = str(testLetterInitial) + str(numInitial) + str(testLetterFinal) + str(numFinal)
                    testInitial, testFinal = convert_to_usable(testMove)
                    if check_legal_move(testInitial, testFinal, playerColor, gameBoard) == True:
                        # creates a new updated board using the test moves, then checks if the move will put the player in check
                        testBoard = deepcopy(update_game_board(testInitial, testFinal, playerColor, gameBoard))
                        if check_king_attacked(playerColor, testBoard) == False:
                            allPossibleMoves.append(testMove)
    return allPossibleMoves


# functions finds the current players king position, then scans the area around to see if an opposing piece is attacking it
def check_king_attacked(playerColor, gameBoard):
    if playerColor == "white":
        otherColor = "black"
    else:
        otherColor = "white"
    # tries to find the row and col of the current players king
    for kingRow in range(8):
        for kingCol in range(8):
            # find the king that is the same as the player color
            if isinstance(gameBoard[kingRow][kingCol], King) and gameBoard[kingRow][kingCol].color == playerColor:
                # loop through the board positions again to find the enemy king
                for otherKingRow in range(8):
                    for otherKingCol in range(8):
                        # gets the position of the other king
                        if isinstance(gameBoard[otherKingRow][otherKingCol], King) and gameBoard[otherKingRow][otherKingCol].color == otherColor:
                            # checks if the other king is 1 space away from player's king
                            if abs(otherKingRow - kingRow) == 1 and abs(otherKingCol - kingCol) == 0:
                                return True
                            elif abs(otherKingRow - kingRow) == 0 and abs(otherKingCol - kingCol) == 1:
                                return True
                            elif abs(otherKingRow - kingRow) == 1 and abs(otherKingCol - kingCol) == 1:
                                return True
                        else:
                            break

                rowIncrement = 1
                colIncrement = 1
                # scanning vertical axis up
                while kingRow - rowIncrement >= 0:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow - rowIncrement][kingCol].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow - rowIncrement][kingCol].color == otherColor:
                        # if its a rook or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow - rowIncrement][kingCol], Rook) or isinstance(gameBoard[kingRow - rowIncrement][kingCol], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                # scanning vertical axis down
                while kingRow + rowIncrement <= 7:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow + rowIncrement][kingCol].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow + rowIncrement][kingCol].color == otherColor:
                        # if its a rook or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow + rowIncrement][kingCol], Rook) or isinstance(gameBoard[kingRow + rowIncrement][kingCol], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                colIncrement = 1
                # scanning horizontal axis left
                while kingCol - colIncrement >= 0:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow][kingCol - colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow][kingCol - colIncrement].color == otherColor:
                        # if its a rook or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow][kingCol - colIncrement], Rook) or isinstance(gameBoard[kingRow][kingCol - colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    colIncrement += 1
                    continue

                # reset the increments used for calculation
                colIncrement = 1
                # scanning horizontal axis right
                while kingCol + colIncrement <= 7:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow][kingCol + colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow][kingCol + colIncrement].color == otherColor:
                        # if its a rook or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow][kingCol + colIncrement], Rook) or isinstance(gameBoard[kingRow][kingCol + colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    colIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                colIncrement = 1
                # scanning top left diagonal
                while kingRow - rowIncrement >= 0 and kingCol - colIncrement >= 0:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow - rowIncrement][kingCol - colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow - rowIncrement][kingCol - colIncrement].color == otherColor:
                        # if its a bishop or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow - rowIncrement][kingCol - colIncrement], Bishop) or isinstance(gameBoard[kingRow - rowIncrement][kingCol - colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    colIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                colIncrement = 1
                # scanning top right diagonal
                while kingRow - rowIncrement >= 0 and kingCol + colIncrement <= 7:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow - rowIncrement][kingCol + colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow - rowIncrement][kingCol + colIncrement].color == otherColor:
                        # if its a bishop or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow - rowIncrement][kingCol + colIncrement], Bishop) or isinstance(gameBoard[kingRow - rowIncrement][kingCol + colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    colIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                colIncrement = 1
                # scanning bottom left diagonal
                while kingRow + rowIncrement <= 7 and kingCol - colIncrement >= 0:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow + rowIncrement][kingCol - colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow + rowIncrement][kingCol - colIncrement].color == otherColor:
                        # if its a bishop or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow + rowIncrement][kingCol - colIncrement], Bishop) or isinstance(gameBoard[kingRow + rowIncrement][kingCol - colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    colIncrement += 1
                    continue

                # reset the increments used for calculation
                rowIncrement = 1
                colIncrement = 1
                # scanning bottom right diagonal
                while (kingRow + rowIncrement) <= 7 and (kingCol + colIncrement) <= 7:
                    # if it meets a same color piece, the King is not in check in that direction so break from the loop
                    if gameBoard[kingRow + rowIncrement][kingCol + colIncrement].color == playerColor:
                        break
                    # check if it meets a opposite color piece
                    elif gameBoard[kingRow + rowIncrement][kingCol + colIncrement].color == otherColor:
                        # if its a bishop or queen, the King is in check so return True
                        if isinstance(gameBoard[kingRow + rowIncrement][kingCol + colIncrement], Bishop) or isinstance(gameBoard[kingRow + rowIncrement][kingCol + colIncrement], Queen):
                            return True
                        # if not, the King is not in check in that direction
                        else:
                            break
                    # if neither, its an empty square so move to check the next square
                    rowIncrement += 1
                    colIncrement += 1
                    continue

                # loop through the game board coordinates
                for knightRow in range(8):
                    for knightCol in range(8):
                        # checks for an opposing knight
                        if isinstance(gameBoard[knightRow][knightCol], Knight) and gameBoard[knightRow][knightCol].color == otherColor:
                            # checks if the knight is attacking the king using pythagorean thereom, 2 squares and 1 square away
                            if pow(abs(knightRow - kingRow), 2) + pow(abs(knightCol - kingCol), 2) == 5:
                                return True

                # scanning for black pawn if player is white
                if playerColor == "white" and kingRow > 1:
                    # ensures no index error
                    if kingCol < 7:
                        # check for black pawn on right side, a king on the last col cant be attacked by a right side pawn
                        if (gameBoard[kingRow - 1][kingCol + 1], Pawn) and gameBoard[kingRow - 1][kingCol + 1].color == "black":
                            return True
                    if kingCol > 0:
                        # check for black pawn on left side, a king on the first col cant be attacked by a left side pawn
                        if (gameBoard[kingRow - 1][kingCol - 1], Pawn) and gameBoard[kingRow - 1][kingCol - 1].color == "black":
                            return True
                # scanning for white pawn if player is black
                elif playerColor == "black" and kingRow < 7:
                    # ensures no index error
                    if kingCol < 7:
                        # check for white pawn on right side, a king on the last col cant be attacked by a right side pawn
                        if (gameBoard[kingRow + 1][kingCol + 1], Pawn) and gameBoard[kingRow + 1][kingCol + 1].color == "white":
                            return True
                    if kingCol > 0:
                        # check for white pawn on left side, a king on the first col cant be attacked by a left side pawn
                        if (gameBoard[kingRow + 1][kingCol - 1], Pawn) and gameBoard[kingRow + 1][kingCol - 1].color == "white":
                            return True
                # returns false is nothing is attacking the king
                return False
    return False


# function scans the board for any pawns on the end rows and prompts for promotion if there is
def check_pawn_promotion(gameBoard):
    for col in range(8):
        # checks for pawns on the top row
        if gameBoard[0][col].name == " P ":
            # continue prompting until the player correctly promotes
            while gameBoard[0][col].name == " P ":
                promotion = input("Type the piece you want to promote the pawn into. (Type 'B' 'N' 'R' or 'Q') \n")
                promotion = promotion.lower().replace(" ", "")
                # checks which piece the player inputted, then replaces the pawn with the new piece
                if promotion == "b":
                    gameBoard[0][col] = Bishop(" B ", "white")
                elif promotion == "n":
                    gameBoard[0][col] = Knight(" N ", "white")
                elif promotion == "r":
                    gameBoard[0][col] = Rook(" R ", "white")
                elif promotion == "q":
                    gameBoard[0][col] = Queen(" Q ", "white")
                else:
                    print("Invalid piece")

        # checks for pawns on the bottom row
        elif gameBoard[7][col].name == " P ":
            # continue prompting until the player correctly promotes
            while gameBoard[7][col].name == " P ":
                promotion = input("Type the piece you want to promote the pawn into. (Type 'B' 'N' 'R' or 'Q') \n")
                promotion = promotion.lower().replace(" ", "")
                # checks which piece the player inputted, then replaces the pawn with the new piece
                if promotion == "b":
                    gameBoard[7][col] = Bishop(" B ", "black")
                elif promotion == "n":
                    gameBoard[7][col] = Knight(" N ", "black")
                elif promotion == "r":
                    gameBoard[7][col] = Rook(" R ", "black")
                elif promotion == "q":
                    gameBoard[7][col] = Queen(" Q ", "black")
                else:
                    print("Invalid piece")
    return gameBoard


# function checks whether the input was correct, then calls the corresponding move function to ensure move was legal
def check_legal_move(initialMove, finalMove, playerColor, gameBoard):
    # ensures initial and final moves are different
    if initialMove != finalMove:
        # ensures the player is moving only their color pieces
        if gameBoard[int(initialMove[0])][int(initialMove[1])].color == playerColor:
            # checks which piece is being moved, then calls the corresponding function to check if it is legal
            if isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], Pawn):
                return check_pawn_move(initialMove, finalMove, playerColor, gameBoard)
            elif isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], Knight):
                return check_knight_move(initialMove, finalMove, playerColor, gameBoard)
            elif isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], Bishop):
                return check_bishop_move(initialMove, finalMove, playerColor, gameBoard)
            elif isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], Rook):
                return check_rook_move(initialMove, finalMove, playerColor, gameBoard)
            elif isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], Queen):
                return check_queen_move(initialMove, finalMove, playerColor, gameBoard)
            elif isinstance(gameBoard[int(initialMove[0])][int(initialMove[1])], King):
                return check_king_move(initialMove, finalMove, playerColor, gameBoard)
            else:
                return False
        else:
            return False
    else:
        return False


# function checks whether the king move is legal
def check_king_move(initialMove, finalMove, playerColor, gameBoard):
    global wShortCastle
    global wLongCastle
    global bShortCastle
    global bLongCastle
    # since a king can move up to 1 space around it, check if the change in position is either 1 or 0 for both directions
    if (abs(int(finalMove[0]) - int(initialMove[0])) == 1 or abs(int(finalMove[0]) - int(initialMove[0])) == 0) and (abs(int(finalMove[1]) - int(initialMove[1])) == 1 or abs(int(finalMove[1]) - int(initialMove[1])) == 0):
        if playerColor == "white" and (isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty) or gameBoard[int(finalMove[0])][int(finalMove[1])].color == "black"):
            return True
        elif playerColor == "black" and (isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty) or gameBoard[int(finalMove[0])][int(finalMove[1])].color == "white"):
            return True
        else:
            return False
    # checks for long castle if king moves 2 spaces left
    elif int(finalMove[0]) == int(initialMove[0]) and int(initialMove[1]) - 2 == int(finalMove[1]):
        # if white long castles, check that its legal and that all the spaces in between are empty
        if playerColor == "white" and wLongCastle == True and isinstance(gameBoard[7][1], Empty) and isinstance(gameBoard[7][2], Empty) and isinstance(gameBoard[7][3], Empty):
            return True
        # if black long castles, check that its legal and that all the spaces in between are empty
        elif playerColor == "black" and bLongCastle == True and isinstance(gameBoard[0][1], Empty) and isinstance(gameBoard[0][2], Empty) and isinstance(gameBoard[0][3], Empty):
            return True
        else:
            return False
    # checks for short castle if king moves 2 spaces right
    elif int(finalMove[0]) == int(initialMove[0]) and int(initialMove[1]) + 2 == int(finalMove[1]):
        # if white short castles, check that its legal and that all the spaces in between are empty
        if playerColor == "white" and wShortCastle == True and isinstance(gameBoard[7][5], Empty) and isinstance(gameBoard[7][6], Empty):
            return True
        # if black short castles, check that its legal and that all the spaces in between are empty
        elif playerColor == "black" and bShortCastle == True and isinstance(gameBoard[0][5], Empty) and isinstance(gameBoard[0][6], Empty):
            return True
        else:
            return False
    else:
        return False


# function checks whether the pawn move is legal
def check_pawn_move(initialMove, finalMove, playerColor, gameBoard):
    global enPassant
    global enPassantPosition
    if playerColor == "white":
        # check if the pawn moved 2 spaces, which is only possible if the pawn is still on its starting square
        if int(initialMove[0]) == 6 and int(finalMove[0]) == 4 and int(initialMove[1]) == int(finalMove[1]):
            # checks that the two squares in front are empty
            if isinstance(gameBoard[int(initialMove[0]) - 1][int(initialMove[1])], Empty) and isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
        # checks if the pawn moved 1 space forward in the same row and that the square its moving to is empty
        elif int(initialMove[0]) - 1 == int(finalMove[0]) and int(initialMove[1]) == int(finalMove[1]) and isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
            return True
        # checks if the pawn is capturing diagonally, which would change its col by 1 and move it forward 1
        elif gameBoard[int(finalMove[0])][int(finalMove[1])].color == "black":
            if int(initialMove[0]) - 1 == int(finalMove[0]) and abs(int(finalMove[1]) - int(initialMove[1])) == 1:
                return True
            else:
                return False
        # checks if the white pawn moved diagonally while en passant is true and that the white piece moves into the same row as the black piece that just moved
        elif enPassant == True and int(initialMove[0]) == 3 and abs(int(initialMove[0]) - 1 == int(finalMove[0])) and abs(int(finalMove[1]) - int(initialMove[1])) == 1 and int(finalMove[1]) == int(enPassantPosition[1]):
            return True
        else:
            return False

    # check legal movement for black
    else:
        # check if the pawn moved 2 spaces, which is only possible if the pawn is still on its starting square
        if int(initialMove[0]) == 1 and int(finalMove[0]) == 3 and int(initialMove[1]) == int(finalMove[1]):
            if isinstance(gameBoard[int(initialMove[0]) + 1][int(initialMove[1])], Empty) and isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
        # checks if the pawn moved 1 space forward in the same row and that the square its moving to is empty
        elif int(initialMove[0]) + 1 == int(finalMove[0]) and int(initialMove[1]) == int(finalMove[1]) and isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
            return True
        # checks if the pawn is capturing diagonally, which would change both axis by 1 space
        elif gameBoard[int(finalMove[0])][int(finalMove[1])].color == "white":
            if int(initialMove[0]) + 1 == int(finalMove[0]) and abs(int(finalMove[1]) - int(initialMove[1])) == 1:
                return True
            else:
                return False
        # checks if the black pawn moved diagonally while en passant is true and that the white piece moves into the same row as the white piece that just moved
        elif enPassant == True and int(initialMove[0]) == 5 and abs(int(initialMove[0]) + 1 == int(finalMove[0])) and abs(int(finalMove[1]) - int(initialMove[1])) == 1 and int(finalMove[1]) == int(enPassantPosition[1]):
            return True
        else:
            return False


# function checks whether the knight move is legal
def check_knight_move(initialMove, finalMove, playerColor, gameBoard):
    # since the movement is always 2 squares + 1 square, I used the Pythagorean theorem to ensure it equals 5 to be legal
    if pow(abs(int(finalMove[0]) - int(initialMove[0])), 2) + pow(abs(int(finalMove[1]) - int(initialMove[1])), 2) == 5:
        # ensures the final square is either empty or a piece of the opposite color
        if isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
            return True
        elif playerColor == "white" and gameBoard[int(finalMove[0])][int(finalMove[1])].color == "black":
            return True
        elif playerColor == "black" and gameBoard[int(finalMove[0])][int(finalMove[1])].color == "white":
            return True
        else:
            return False
    else:
        return False


# function checks whether the bishop move was legal
def check_bishop_move(initialMove, finalMove, playerColor, gameBoard):
    if playerColor == "white":
        otherColor = "black"
    else:
        otherColor = "white"
    squareIncrement = 1
    # ensures that both axis move by the same number of squares
    if abs(int(finalMove[0]) - int(initialMove[0])) == abs(int(finalMove[1]) - int(initialMove[1])):
        bishopSpaces = abs(int(finalMove[0]) - int(initialMove[0]))
        # top left diagonal
        if int(finalMove[0]) < int(initialMove[0]) and int(finalMove[1]) < int(initialMove[1]):
            while squareIncrement < bishopSpaces:
                # checks that all the squares diagonally between the final position is empty
                if isinstance(gameBoard[int(initialMove[0]) - squareIncrement][int(initialMove[1]) - squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # check that the final position is empty or capturing an opposite color
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False

        # top right diagonal
        elif int(finalMove[0]) < int(initialMove[0]) and int(finalMove[1]) > int(initialMove[1]):
            while squareIncrement < bishopSpaces:
                # checks that all the squares diagonally between the final position is empty
                if isinstance(gameBoard[int(initialMove[0]) - squareIncrement][int(initialMove[1]) + squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # check that the final position is empty or capturing an opposite color
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False

        # bottom left diagonal
        elif int(finalMove[0]) > int(initialMove[0]) and int(finalMove[1]) < int(initialMove[1]):
            while squareIncrement < bishopSpaces:
                # checks that all the squares diagonally between the final position is empty
                if isinstance(gameBoard[int(initialMove[0]) + squareIncrement][int(initialMove[1]) - squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # check that the final position is empty or capturing an opposite color
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False

        # bottom right diagonal
        elif int(finalMove[0]) > int(initialMove[0]) and int(finalMove[1]) > int(initialMove[1]):
            while squareIncrement < bishopSpaces:
                # checks that all the squares diagonally between the final position is empty
                if isinstance(gameBoard[int(initialMove[0]) + squareIncrement][int(initialMove[1]) + squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # check that the final position is empty or capturing an opposite color
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
    else:
        return False


# function checks whether the rook move was legal
def check_rook_move(initialMove, finalMove, playerColor, gameBoard):
    if playerColor == "white":
        otherColor = "black"
    else:
        otherColor = "white"
    squareIncrement = 1
    # check if horizontal movement
    if (initialMove[0] == finalMove[0] and initialMove[1] != finalMove[1]):
        rookHorizontal = abs(int(finalMove[1]) - int(initialMove[1]))
        # calculates right side
        if int(finalMove[1]) > int(initialMove[1]):
            # loops to check if every square in between is empty
            while squareIncrement < rookHorizontal:
                if isinstance(gameBoard[int(initialMove[0])][int(initialMove[1]) + squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # ensures the ending position is empty square or capturing an opposite color piece
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
        # calculates left side
        elif int(finalMove[1]) < int(initialMove[1]):
            # loops to check if every square in between is empty
            while squareIncrement < rookHorizontal:
                if isinstance(gameBoard[int(initialMove[0])][int(initialMove[1]) - squareIncrement], Empty):
                    squareIncrement += 1
                else:
                    return False
            # ensures the ending position is empty square or capturing an opposite color piece
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
    # check if vertical movement
    elif (initialMove[0] != finalMove[0] and initialMove[1] == finalMove[1]):
        rookVertical = abs(int(finalMove[0]) - int(initialMove[0]))
        # calculates down side
        if int(finalMove[0]) > int(initialMove[0]):
            # loops to check if every square in between is empty
            while squareIncrement < rookVertical:
                if isinstance(gameBoard[int(initialMove[0]) + squareIncrement][int(initialMove[1])], Empty):
                    squareIncrement += 1
                else:
                    return False
            # ensures the ending position is empty square or capturing an opposite color piece
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
        # calculates up side
        elif int(finalMove[0]) < int(initialMove[0]):
            # loops to check if every square in between is empty
            while squareIncrement < rookVertical:
                if isinstance(gameBoard[int(initialMove[0]) - squareIncrement][int(initialMove[1])], Empty):
                    squareIncrement += 1
                else:
                    return False
            # ensures the ending position is empty square or capturing an opposite color piece
            if gameBoard[int(finalMove[0])][int(finalMove[1])].color == otherColor or isinstance(gameBoard[int(finalMove[0])][int(finalMove[1])], Empty):
                return True
            else:
                return False
    else:
        return False


# function checks whether the queen move was legal
def check_queen_move(initialMove, finalMove, playerColor, gameBoard):
    # the queens movement is the same as bishop + rook combined
    if check_bishop_move(initialMove, finalMove, playerColor, gameBoard) == True or check_rook_move(initialMove, finalMove, playerColor, gameBoard) == True:
        return True
    else:
        return False


# function handles errors and print different messages depending on user input
def help_menu(action, playerColor, displayBoard, againstBot, possibleMovesList):
    # turns it lowercase and removes any spaces
    action = action.lower().replace(" ", "")
    if action == "help":
        message = "---HELP--- \n"
        message += "Format your moves as '[initialSquare] [finalSquare]' with letter coordinate first, then number. Spaces and capitalization do not matter. \n"
        message += "EXAMPLE: 'd3d4' moves the piece from position D3 to position D4, as long as the movement is legal for the piece you move. \n"
        message += "--LIST OF PIECES--\n"
        message += "* Pawn or 'P' can move 1 space forward. It can only capture top diagonally 1 space, which means it can be blocked if a piece is directly in front. \n"
        message += "If the Pawn has not moved yet, it can move 2 spaces forward instead of 1. If it reaches the end, it can promote to any piece except for the King. \n"
        message += "En Passant - If an enemy pawn moves 2 spaces and your pawn is directly left/right, you can move behind the pawn and capture it ONLY the turn after. \n"
        message += "* Knight or 'N' can only move/capture in an L shape. 2 spaces in a direction, turn, 1 space. It is the only piece that can jump over other pieces. \n"
        message += "* Bishop or 'B' can only move/capture in a diagonal. Basically, the Bishop can only stay on other squares of the same color as its initial square. \n"
        message += "* Rook or 'R' can only move/capture in a straight horizontal/vertical line. \n"
        message += "* Queen or 'Q' can move/capture in a straight horizontal/vertical line OR diagonally. Its like a rook and bishop combined, making it a strong piece. \n"
        message += "* King or 'K' can only move/capture 1 space in any direction. The King should always be protected as you will lose if your side's King is captured. \n"
        message += "Castling - ONLY IF your King and one of your Rooks has not moved yet and the squares in between are empty, your King can move 2 spaces left or right. \n"
        message += "Castling left side is called 'long castle' while castling right side is called 'short castle.' This moves the Rook to the other side of the King. \n"
        message += "---------- \n"
    elif action == "rules":
        message = "---RULES--- \n"
        message += "To win, you must put the enemy King in Checkmate, where any move results in check. If your King is in sight of an opposing piece, you are in 'Check'. \n"
        message += "You must put your king out of danger by either capturing the attacking piece, moving out of the attack, or blocking the attack with another piece. \n"
        message += "It is Stalemate if the player has no legal moves and is not currently in Check. There is info on special moves like En Passant by inputting 'help'. \n"
        message += "----------- \n"
    elif action == "board":
        # prints the board again for the player if they inputted board
        print("\n   | a | b | c | d | e | f | g | h | Wins: " + str(blackWin))
        print("---+---+---+---+---+---+---+---+---+---")
        for row in range(8):
            print(" " + str(abs(8 - row)) + " |", end="")
            print("|".join(displayBoard[row]), end="")
            print("| " + str(abs(8 - row)))
            print(Style.RESET_ALL + "---+---+---+---+---+---+---+---+---+---")
        print("   | a | b | c | d | e | f | g | h | Wins: " + str(whiteWin))
        return ""
    elif action == "move" or action == "moves":
        print("Possible Moves: " + ", ".join(possibleMovesList))
        return ""
    elif action == "end":
        print("---PROGRAM TERMINATED--- \n")
        sys.exit(1)
    elif action == "draw":
        prompt_draw(playerColor, againstBot)
        return ""
    elif action == "resign":
        prompt_resign(playerColor, againstBot)
        return ""
    # if nothing, print a generic error message
    else:
        message = "---ERROR--- \n"
        message += "There was a mistake with your input. Please try again. Input 'help' or 'rules' if you need help formatting your input or learning how each piece moves. \n"
        message += "If you are struggling to type moves, input 'moves' to get a list of legal moves in your position, then choose one and input it as your move. \n"
        message += "You may input 'draw' to offer the other player a draw (AI Bot will decline draw offers) or 'resign' to immediately forfeit the game and have the other player win. \n"
        message += "If the terminal gets filled with too much text, you can input 'board' to print the board again. \n"
        message += "In case of error, input 'end' to immediately end and terminate the program. \n"
        message += "----------- \n"
    return message


# function checks if the players want to draw
def prompt_draw(playerColor, againstBot):
    global whiteWin
    global blackWin
    if againstBot == True:
        print("AI Bot has declined the draw offer. You can not offer a draw to the AI Bot!")
        return 1
    else:
        if playerColor == "white":
            otherColor = colored("black", "red")
        else:
            playerColor = colored("black", "red")
            otherColor = "white"
        print("Player " + playerColor + " is offering a draw.")
        # continuously prompts the player input until their either accept or decline
        while True:
            decision = input("Player " + otherColor + " do you 'accept' or 'decline'? ")
            decision = decision.lower().replace(" ", "")
            # check if the player wants to accept the draw
            if decision == "accept":
                print("Player " + otherColor + " has accepted the draw offer. Neither player wins. ")
                print("---Game ended. DRAW!--- \n")
                whiteWin += 0.5
                blackWin += 0.5
                prompt_rematch()
            elif decision == "decline":
                # returns 1 to continue the game
                print("Player " + otherColor + " has declined the draw offer. The game will continue. ")
                return 1


# function checks if the player wants to resign
def prompt_resign(playerColor, againstBot):
    global whiteWin
    global blackWin
    if playerColor == "white":
        otherColor = colored("black", "red")
    else:
        playerColor = colored("black", "red")
        otherColor = "white"
    print("Player " + playerColor + " has resigned.")
    # prints a different text depending if player was playing against ai bot
    if againstBot == True:
        print("---AI Bot " + otherColor + " is the winner!--- \n")
    else:
        print("---Player " + otherColor + " is the winner!--- \n")
    if playerColor == "white":
        blackWin += 1
    else:
        whiteWin += 1
    prompt_rematch()


# function prompts the players if they want to rematch
def prompt_rematch():
    print("CURRENT SCORE: " + str(whiteWin) + "-" + str(blackWin) + "\n")
    rematch = input("Would you like to play again? Type 'yes' or 'no': ")
    rematch = rematch.lower().replace(" ", "")
    if rematch == "yes":
        # if the players want to play again, call the main function again which also resets the board
        print("\033c")
        print("Restarting game \n")
        time.sleep(0.5)
        main()
    else:
        print("Ending Program")
        sys.exit(0)


# calls the main function to start the game loop
if __name__ == "__main__":
    main()