Skip to content
Snippets Groups Projects
chess_ai.py 17.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • import chess
    import io
    
    # run command "pip install stockfish"
    from stockfish import Stockfish
    
    
    skip_camera = None
    def init_stockfish(bool1):
        global skip_camera
        skip_camera = bool1
    
    
    #     with open(r"%s" %file_name, 'r') as File_object: # 'with' autocloses file
    #         file_data = File_object.read()
    
    #     return file_data
    
    # def write_to(file_name, file_data):
    
    #     with open(r"%s" %file_name, 'w') as File_object:
    #         File_object.write(file_data)
    
    #     return 0
    
    def FEN_check(FEN_string):
        '''
        DESCRIPTION:    Checks if the FEN_string is valid.
        INPUT:          FEN_string: String in FEN format.
        RETURN:         is_valid_FEN: Bool that says if the FEN_string was valid.
        '''
        try:
            board = chess.Board(FEN_string)
            return True
        except ValueError:
            return False
    
    
    def cheat_check(FEN_string, FEN_board_state_before):
    
        # # set board and FEN from state before
        # FEN_board_state_before = read_from("saved_FEN")
    
        # Edge case: AI is first to move on start
        if (FEN_board_state_before == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" or FEN_board_state_before == "" and FEN_string == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"):
            is_cheating = False
            return is_cheating
    
        if FEN_string == "":
            return is_cheating
        
    
        board_before = chess.Board(FEN_board_state_before)
    
    
        # start of weird/custom game, ignore on the first time
        if board_before.ply() < 2:
            is_cheating = False
            return is_cheating
    
    
        # possible move set from board state before
        move_list = board_before.legal_moves
    
        # iterate all moves to find a move that makes curr board state
        for move in move_list:
            # make copy of board state before and push moves
            board_copy = board_before.copy()
            board_copy.push(move)
    
            # if there's a match, it's legal
            if (board_copy.fen() == FEN_string):
                is_cheating = False
                break
    
        return is_cheating
    
    
    def special_move_check(FEN_string, best_move):
        '''
        DESCRIPTION:    Check if the move is capture, castling, promotion, or en passant.
        INPUT:          FEN_string: String that holds FEN.
                        best_move: String that holds move
        RETURN:         special_move_return: List holding flags of special moves and location of affected piece:
                            [0~4]: Hold 0/1 (False/ True) signifying what type of move.
                                0: Not a special move
                                1: En Passant
                                2: Castling
                                3: Capture
                                4: Promotion
                            [5]: Holds best move.
    
        '''
        special_move_return = [0, 0, 0, 0, 0, best_move]
        # print("This is best move:", best_move)
        board = chess.Board(FEN_string)
        move_processed = chess.Move.from_uci(best_move)
        
        # check en passant
        if board.is_en_passant(move_processed):
            special_move_return[1] = 1
    
        # check castling
        if board.is_castling(move_processed):
            special_move_return[2] = 1
    
        # check capture
        if board.is_capture(move_processed):
            special_move_return[3] = 1
    
        # check promotion
        is_promotion = chess.square_rank(chess.Move.from_uci(best_move).to_square) in [0, 7] and board.piece_type_at(chess.Move.from_uci(best_move).from_square) == chess.PAWN
        if is_promotion:
            special_move_return[4] = 1
    
        return special_move_return
    
    
    def Stockfish_eval(FEN_string):
        '''
        DESCRIPTION:    Calculates a best move based off Stockfish engine.
        INPUT:          FEN_string: String in FEN notation.          
        RETURN:         best_move: String of best move (i.e. "e7e6").
        '''
    
        # setting up stockfish
    
        if skip_camera:
            stockfish = Stockfish(  path = "C:\ece445\stockfish\stockfish-windows-x86-64-avx2.exe", # install Stockfish and input path to .exe here
                                    depth = 15,                          # 20 is good but slower, 15 is fine
                                    parameters={"Skill Level":19})    # range: 0-19? 20 might exist or rounds to 19, https://github.com/vondele/Stockfish/blob/master/src/search.cpp
        else:
            stockfish = None # FIX FOR RASPI
            
    
        # set state of board based off FEN
        stockfish.set_fen_position(FEN_string)
    
        # eval best move
        best_move = stockfish.get_best_move()
    
        # === TEST: Visualize chess board state ===
        # print(stockfish.get_board_visual(True))
    
        return best_move
    
    def Minimax_eval(FEN_string):
        return 0
    
    def Personal_eval(FEN_string):
        return 0
    
    def eval_best_move(FEN_string, which_algorithm):
        '''
        DESCRIPTION:    Takes in current game state as FEN and then evaluates a best move.
        INPUT:          FEN_string: A FEN string.
                        which_algorithm: Flag that determines best move algorithm:
                            0: Stockfish
                            1: Minimax
                            2: Personal
        RETURN:         best_move: Tuple that holds:
                            [0]: From move info.
                            [1]: To move info.
                            [2]: FEN string of board.
        '''    
        # calc best move based off flag
        best_move = [Stockfish_eval(FEN_string), Minimax_eval(FEN_string), Personal_eval(FEN_string)][which_algorithm]
    
        # print("Best move is: ", best_move)
    
        return best_move
    
    def prep_info_to_ESP(FEN_string, best_move_processed):
    
        DESCRIPTION:    Preps the info to be sent to the ESP32.
        INPUTS:         FEN_string: String that holds FEN.
                        best_move_processed: List that holds flags and best move.
        RETURN:         ESP_input: List containing info for the ESP32.
                            [0]: Contains move to go to.
                            [1]: Contains various strings of secondary piece, if applicable.
                                "": If reg move or promotion.
                                "<move>": If En Passant, Castling, or Capture.
                            [2]: Contains special move flags.
                                [0]: Reg move
                                [1]: En Passant
                                [2]: Castling
                                [3]: Capture
                                [4]: Promotion
                            [3]: Contains FEN before move.
        
    
        # print(best_move_processed)
        special_piece_location = ""
    
        # Promotion needs no case since:
            # if there's no piece in its way, it's a simple move
            # if there's a piece, it will go under Capture
    
        # Capture comes before En Passant since En Passant captures a piece in the process
        # Capture case
        if best_move_processed[3]:
            special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][3]]
    
        # En Passant case
        if best_move_processed[1]:
            # left to right
            special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][1]]
        
        # Castling case
        if best_move_processed[2]:
            # king side castle (e -> g)
            # if best_move_processed[5][2] == 'g':
            special_piece_location = [('h' if best_move_processed[5][2] == 'g' else 'a') + ' ' + best_move_processed[5][1], ('f' if best_move_processed[5][2] == 'g' else 'd') + ' ' + best_move_processed[5][1]] 
    
            # queen side castle (e -> c)
            # if best_move_processed[5][2] == 'c':
            
        ESP_input = [[best_move_processed[5][0] + ' ' + best_move_processed[5][1], best_move_processed[5][2] + ' ' + best_move_processed[5][3]], 
                     special_piece_location, 
                     best_move_processed[0:5], 
                     FEN_string]
    
        # print(ESP_input)
    
        # get move's dest and src (separated with space)
        # move_from = best_move[0:1] + ' ' + best_move[1:2]
        # move_to = best_move[2:3] + ' ' + best_move[3:4]
    
    
        return ESP_input
    
    # def update_FEN_string(FEN_string, best_move, file_name):
    #     '''
    #     DESCRIPTION:    Updates the saved FEN with the best_move added to it.
    #     INPUT:          FEN_string: String that hold FEN notation of board.
    #                     best_move: String that holds best move from eval.
    #                     file_name: String that holds the file name.
    #     RETURN:         n/a
    #     '''
    #     board = chess.Board(FEN_string)   
    #     board.push(chess.Move.from_uci(best_move))
    #     write_to(file_name, board.fen())
    
    def chess_AI(FEN_string, FEN_board_state_before, which_alg):
    
        # check if valid FEN
        is_valid_FEN = FEN_check(FEN_string)
    
        if not is_valid_FEN:
            print("FEN format is wrong, please fix")
            return -1
       
        else:
    
            # # check if there's cheating
            # is_cheating = cheat_check(FEN_string, FEN_board_state_before)
            
            # if is_cheating:
            #     print("Illegal move detected, please fix")
            #     return -1
            # else:
            
            # get best move
            best_move = eval_best_move(FEN_string, which_alg)
    
            # check move for special characteristics
            best_move_processed = special_move_check(FEN_string, best_move)
    
            # prepare info for ESP32
            ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
    
            # # update saved FEN 
            # file_name = "saved_FEN"
            # update_FEN_string(FEN_string, best_move, file_name)
    
            return ESP_input, best_move
    
    # def tests(FEN_string, which_func, which_test):
    #     '''
    #     DESCRIPTION:    Contains and executes different tests for various functions.    
    #     INPUT:          FEN_string: String containing FEN.
    #                     which_func: Int that dictates which functionality to test for.
    #                         0: FEN
    #                         1: Eval
    #                         2: Cheating
    #                     which_test: Int that dictates which test of specific functionality to test for.
    #     RETURN:
    #     '''
    #     message = "FEN Parsing" if which_func == 0 else "Eval" if which_func == 1 else "Cheating" if  which_func == 2 else "Special Move" if which_func == 3 else "Processing ESP32 Info" if which_func == 4 else "Chess AI"
    #     print("You're currently testing: " + message + "'s Test #" + str(which_test))
    
    #     # === TESTS: FEN Parsing ===
    #     if which_func == 0:
    #         is_valid_FEN = False
    
    #         # 0: Valid FEN (Success)
    #         if which_test == 0:
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
    #             is_valid_FEN = FEN_check(FEN_string)
            
    #         # 1: Invalid FEN (Fail)
    #         if which_test == 1:
    #             FEN_string = "This should totally fail"
    #             is_valid_FEN = FEN_check(FEN_string)
    
    #         # 2: Invalid FEN by 1 extra char (Fail)
    #         if which_test == 2:
    #             FEN_string = "rnbqkbnrr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
    #             is_valid_FEN = FEN_check(FEN_string)
    
    #         message = "Valid" if is_valid_FEN else "Invalid"
    #         print("This inputted FEN is:", message)
            
    #     # === TESTS: Eval ===
    #     if which_func == 1:
            
    #         # TEST: Inputting different moves ===
    #         if which_test == 0:
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    #             board = chess.Board(FEN_string)
    #             move_list = ["g1f3", "c7c5"          # "b8a6"
    #                         ]
    #             for move in move_list:
    #                 board.push(chess.Move.from_uci(move))
    
    #     # === TESTS: Cheating ===
    #     if which_func == 2:
    #         # save old FEN
    #         file_name = "saved_FEN"
    #         saved_FEN = read_from(file_name)
    #         is_cheating = True
    
    #         # 0: Illegal ply/state of board (Fail)
    #         if which_test == 0:
    #             write_to(file_name, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 1 6")  # random board state
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    #             is_cheating = cheat_check(FEN_string)
    
    #         # 1: Valid ply by human (Success)
    #         if which_test == 1:
    #             write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1" # black's turn, one ply by white since base
    #             is_cheating = cheat_check(FEN_string)
    
    #         # 2: AI is first to move on start & saved_FEN is filled (Success)
    #         if which_test == 2:
    #             write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    #             is_cheating = cheat_check(FEN_string)
    
    #         # 3: AI is first to move on start & saved_FEN is empty (Success)
    #         if which_test == 3:
    #             write_to(file_name, "") # base: white starts
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    #             is_cheating = cheat_check(FEN_string)
    
    #         message = "Yes" if is_cheating else "No"
    #         print("Was there cheating involved?:", message)
    
    #         # restore old FEN
    #         write_to(file_name, saved_FEN)
    
    #     # === TESTS: Special Moves ===
    #     if which_func == 3:
            
    #         # 0: Castle
    #         if which_test == 0:
    #             is_special_move = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")
    
    #         # 1: Capture
    #         if which_test == 1:
    #             is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")
    
    #         # 2: Promotion
    #         if which_test == 2:
    #             is_special_move = special_move_check("8/PPPPP1PP/8/8/8/8/8/8 w - - 0 1", "a7a8q")
            
    #         # 3: En Passant & Capture
    #         if which_test == 3:
    #             is_special_move = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
            
    #         # print messages based off what it was
    #         if is_special_move[0]:
    #                 print("The move results in reg move")
    #         if is_special_move[1]:
    #                 print("The move results in En Passant at location:", is_special_move[5][2:4])
    #         if is_special_move[2]:
    #                 print("The move results in Castling at location:", is_special_move[5][2:4])
    #         if is_special_move[3]:
    #                 print("The move results in Capture at location:", is_special_move[5][2:4])
    #         if is_special_move[4]:
    #                 print("The move results in Promotion at location:", is_special_move[5][2:4])
    
    #     # === TESTS: Process ESP32 ===
    #     if which_func == 4:
    
    #         # 0: En Passant
    #         if which_test == 0:
    #             FEN_string = "8/8/8/4pP2/4P3/8/8/8 w - e6 0 1"
    #             best_move_processed = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
    #             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
    
    #         # 1: Capture
    #         if which_test == 1:
    #             FEN_string = "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3"
    #             best_move_processed = is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")
    #             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
    
    #         # 2: Castling (king side)
    #         if which_test == 2:
    #             FEN_string = "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1"
    #             best_move_processed = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")
    #             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
            
    #         # 3: Castling (queen side)
    #         if which_test == 3:
    #             FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
    #             best_move_processed = special_move_check("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "e1c1")
    #             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
    
    #         print(ESP_input)
    
    #     # === TESTS: Entire Alg ===
    #     if which_func == 5:
    #         file_name = "saved_FEN"
    #         saved_FEN = read_from(file_name)
    
    #         # 0: Base game
    #         if which_test == 0:
    #             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    
    #         if which_test == 1:
    #             FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"  # grabbed from ESP32, Test 3
            
    #         board = chess.Board(FEN_string)
    #         print(board)
    
    #         which_alg = 0
    #         ret_val = chess_AI(FEN_string, which_alg)
    #         print("Return value is:",ret_val)
    #         string_test = ret_val[0][0] + ret_val[0][1]
    #         string_test = string_test.replace(" ", "")
    #         board.push(chess.Move.from_uci(string_test))
    #         print(board)
    
    #         # restore old FEN
    #         write_to(file_name, saved_FEN)
    
    # if __name__ == '__main__':  
    #     # comment out these when calling with Raspberry PI 4 (make sure when you call chess_AI, you set the parameters)
    #     FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
    #     # FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
    #     which_alg = 0
    
    #     # Chess AI program
    #     # ret_val = chess_AI(FEN_string, which_alg)
    #     # print(ret_val)
    
    #     # Tests
    #     which_func = 5
    #     which_test = 1
    #     tests(FEN_string, which_func, which_test)