def bfs_move(inp, rowI, colI, hero, enemy):
    '\n    Perform a breadth first search for each adjacent tile\n    Although not the most efficient, the approach is still fast and makes it\n    easy to sort in such a way that satisfies all the conditions\n    '
    if adjacent_enemy(inp, rowI, colI, enemy):
        return None
    first_moves = [((rowI + 1), colI), ((rowI - 1), colI), (rowI, (colI - 1)), (rowI, (colI + 1))]
    first_moves = [x for x in first_moves if (inp[x[0]][x[1]] == '.')]
    best_moves = []
    for move in first_moves:
        (r, c) = move
        if adjacent_enemy(inp, r, c, enemy):
            best_moves.append((move, 1, move))
            continue
        seen_coordinates = {(rowI, colI), (r, c)}
        stack = [((r + 1), c), ((r - 1), c), (r, (c - 1)), (r, (c + 1))]
        stack = [x for x in stack if ((inp[x[0]][x[1]] == '.') and ((x[0], x[1]) not in seen_coordinates))]
        i = 1
        run = True
        while run:
            i += 1
            new_stack = []
            for tile in stack:
                if (tile in seen_coordinates):
                    continue
                seen_coordinates.add(tile)
                (r, c) = tile
                if adjacent_enemy(inp, r, c, enemy):
                    best_moves.append((move, i, (r, c)))
                    run = False
                    continue
                new_tiles = [((r + 1), c), ((r - 1), c), (r, (c - 1)), (r, (c + 1))]
                new_stack += [x for x in new_tiles if ((inp[x[0]][x[1]] == '.') and ((x[0], x[1]) not in seen_coordinates))]
            stack = list(set(new_stack))
            if (not stack):
                run = False
    return get_best_move(best_moves)
Perform a breadth first search for each adjacent tile
Although not the most efficient, the approach is still fast and makes it
easy to sort in such a way that satisfies all the conditions
def bfs_move(inp, rowI, colI, hero, enemy):
    '\n    Perform a breadth first search for each adjacent tile\n    Although not the most efficient, the approach is still fast and makes it\n    easy to sort in such a way that satisfies all the conditions\n    '
    if adjacent_enemy(inp, rowI, colI, enemy):
        return None
    first_moves = [((rowI + 1), colI), ((rowI - 1), colI), (rowI, (colI - 1)), (rowI, (colI + 1))]
    first_moves = [x for x in first_moves if (inp[x[0]][x[1]] == '.')]
    best_moves = []
    for move in first_moves:
        (r, c) = move
        if adjacent_enemy(inp, r, c, enemy):
            best_moves.append((move, 1, move))
            continue
        seen_coordinates = {(rowI, colI), (r, c)}
        stack = [((r + 1), c), ((r - 1), c), (r, (c - 1)), (r, (c + 1))]
        stack = [x for x in stack if ((inp[x[0]][x[1]] == '.') and ((x[0], x[1]) not in seen_coordinates))]
        i = 1
        run = True
        while run:
            i += 1
            new_stack = []
            for tile in stack:
                if (tile in seen_coordinates):
                    continue
                seen_coordinates.add(tile)
                (r, c) = tile
                if adjacent_enemy(inp, r, c, enemy):
                    best_moves.append((move, i, (r, c)))
                    run = False
                    continue
                new_tiles = [((r + 1), c), ((r - 1), c), (r, (c - 1)), (r, (c + 1))]
                new_stack += [x for x in new_tiles if ((inp[x[0]][x[1]] == '.') and ((x[0], x[1]) not in seen_coordinates))]
            stack = list(set(new_stack))
            if (not stack):
                run = False
    return get_best_move(best_moves)