Optimizing Negamax Function with 5x5 Hexapawn

58 views Asked by At

I need to improve the speed of this program, because at the moment it is pretty slow. I know that representing game states in binary can be very effective, however, I don't know how to do that. I have also tried using numba, however that seems to make it slower. I have attached the code below. Thank you to anyone who can help!

    import pygame, sys, time, hashlib
    from copy import deepcopy

    pygame.init()
    red = pygame.Color(255,0,0)
    white = pygame.Color(255,255,255)
    black = pygame.Color(0,0,0)
    pygame.display.set_caption('Hexapawn AI')
    width, height = 700,700
    game_window = pygame.display.set_mode((width, height))

    def set_pawns():
        global game_window, board
        for y in range(5):
            for x in range(5):
                if board[y][x] == 1:
                    game_window.blit( blue_pawn, ( (width/5)*x, (height/5)*(4-y) ))
                if board[y][x] == -1:
                    game_window.blit( red_pawn, ( (width/5)*x , (height/5)*(4-y) ))

    def build_lines():
        global game_window
        for x in range(1,5):
            pygame.draw.line(game_window, black, (width/5 * x, 0), (width/5 * x, height), 7)
            pygame.draw.line(game_window, black, (0, height/5 * x), (width, height/5 * x), 7)

    def get_possible_moves(board, player):
        possible_moves = []
        forward = 1 if player == 1 else -1
        opponent = -1 if player == 1 else 1

        for y in range(5):
            for x in range(5):
                if board[y][x] != player:
                    continue
                if x-1 >= 0 and y+forward < 5 and board[y+forward][x-1] == opponent:
                    possible_moves.append([x,y,x-1,y+forward])
                if x+1 < 5 and y+forward < 5 and board[y+forward][x+1] == opponent:
                    possible_moves.append([x,y,x+1,y+forward])
                if (y+1 < 5 and player == 1) or (y+1 > -1 and player == -1):
                    if board[y+forward][x] == " ":
                        possible_moves.append([x,y,x,y+forward])
        return possible_moves

    def make_move(board,move,player):
        global game_window, width, height
        game_window.fill(white)
        build_lines()
        board[move[1]][move[0]] = " "
        board[move[3]][move[2]] = player
        set_pawns()

    def neg_make_move(board, move, player):
        x1, y1, x2, y2 = move
        board = deepcopy(board)
        board[y1][x1] = " "
        board[y2][x2] = player
        return board

    def check_for_win(board,player):
        if player == -1:
            if -1 in board[0]:
                return True
            if get_possible_moves(board,1) == []:
                return True
        elif player == 1:
            if 1 in board[4]:
                return True
            if get_possible_moves(board,-1) == []:
                return True
        return False

    TRANSPOSITION_TABLE = {}

    def state_hash(board):
        serialized = str(board).encode()
        return hashlib.sha256(serialized).hexdigest()

    def store(table, board, alpha, beta, best, depth):
        state = state_hash(board)
        if best[1] <= alpha:
            flag = 'UPPERCASE'
        elif best[1] >= beta:
            flag = 'LOWERCASE'
        else:
            flag = 'EXACT'

        table[state] = [best, flag, depth]

    def negamax(board, depth, turn, alpha, beta):
        alpha_org = alpha
        state = state_hash(board)
        if state in TRANSPOSITION_TABLE:
            tt_entry = TRANSPOSITION_TABLE[state]
            if tt_entry[2] >= depth:
                if tt_entry[1] == 'EXACT':
                    return tt_entry[0]
                elif tt_entry[1] == 'LOWERCASE':
                    alpha = max(alpha, tt_entry[0][1])
                elif tt_entry[1] == 'UPPERCASE':
                    beta = min(beta, tt_entry[0][1])

                if alpha >= beta:
                    return tt_entry[0]

        if check_for_win(board, -turn): 
            return None, -(25+depth)
        if depth == 0: 
            return get_possible_moves(board,turn)[0], (depth)

        best_score = -200

        for move in get_possible_moves(board,turn):
            new_board = neg_make_move(board, move, turn)
            score = -negamax(new_board, depth - 1, -turn, -beta, -alpha)[1]
            alpha = max(alpha,score)
            if score > best_score:
                best_score, best_move = score, move
            if alpha >= beta:
                break

        store(TRANSPOSITION_TABLE, board, alpha_org, beta, [best_move,best_score], depth)

        return best_move, best_score

    # Build board
    board = [[1 for x in range(5)]]
    for x in range(3):
        board.append([" " for x in range(5)])
    board.append([-1 for x in range(5)])

    game_window.fill(white)

    # Draw game board lines
    build_lines()

    # Load sprites with correct sizes
    tile_size = (width/5,height/5)
    blue_pawn = pygame.transform.scale(pygame.image.load("blue_pawn.png"), tile_size)
    red_pawn = pygame.transform.scale(pygame.image.load("red_pawn.png"), tile_size)

    # Draw the pawns to the board
    set_pawns()
    pygame.display.update()

    while True:
        for event in pygame.event.get():

          # if user clicks the X or they type esc then the screen will close
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()

        start = time.time()
        move = negamax(board,12,1,-10000,10000)[0]
        print(f"Blue move took {time.time()-start} seconds to calculate.")
        make_move(board,move,1)
        pygame.display.update()

        if check_for_win(board,1):
            print("Blue Wins!")
            pygame.quit()
            sys.exit()

        time.sleep(1)

        start = time.time()
        move = negamax(board,12,-1,-10000,10000)[0]
        print(f"Red move took {time.time()-start} seconds to calculate.")
        make_move(board,move,-1)
        pygame.display.update()

        if check_for_win(board,-1):
            print("Red Wins!")
            pygame.quit()
            sys.exit()

        pygame.display.update()
        time.sleep(1)
0

There are 0 answers