def crossover_fitness_driven_one_point(p1, p2):
    point = random.randint(1, len(p1.gene_list) - 1)
    c1, c2 = copy.deepcopy(p1.gene_list), copy.deepcopy(p2.gene_list)
    c1[point:], c2[point:] = p2.gene_list[point:], p1.gene_list[point:]
    child1 = Individual(c1)
    child2 = Individual(c2)
    candidates = [child1, child2, p1, p2]

    best = sorted(candidates, key=lambda ind: ind.fitness, reverse=True)

    return best[0:2]
def mutation_fitness_driven_bit_flip(ind, max_tries=3):
    for t in range(0, max_tries):
        mut = copy.deepcopy(ind.gene_list)
        pos = random.randint(0, len(ind.gene_list) - 1)
        g1 = mut[pos]
        mut[pos] = (g1 + 1) % 2
        mutated = Individual(mut)
        if mutated.fitness > ind.fitness:
            return mutated
    return ind
def mutate(ind):
    if random.random() < .5:
        mut = mutation_bit_flip_ones(ind.gene_list)
    else:
        mut = mutation_shift_one(ind)
    return Individual(mut)
def crossover(parent1, parent2):
    child1_genes, child2_genes = crossover_n_point(parent1.gene_list, parent2.gene_list, 3)
    return Individual(child1_genes), Individual(child2_genes)
    Square(SquareType.city, needs_coverage = True, tower_cost = 200):   1
}

landscape = generate_random_landscape(list(square_grid.keys()), square_grid, rows, cols)


def fitness_function(coords):
    global landscape, radar_radius
    test_landscape = copy.deepcopy(landscape)
    test_landscape.add_radars(coords, radar_radius)
    return - test_landscape.uncovered_count() * 500 - test_landscape.radar_cost()


Individual.rows = rows
Individual.cols = cols
Individual.set_fitness_function(fitness_function)

POPULATION_SIZE = 60
CROSSOVER_PROBABILITY = .5
MUTATION_PROBABILITY = .5
MAX_GENERATIONS = 400

first_population = [Individual.generate_random(.005) for _ in range(POPULATION_SIZE)]
best_ind = random.choice(first_population)
fit_avg = []
fit_best = []
generation_num = 0
population = first_population.copy()
generation_number = 0

while generation_num < MAX_GENERATIONS:
def mutate(ind):
    mut = mutation_bit_flip(ind.gene_list)
    return Individual(mut)
    square_grid = {
        Square(SquareType.water, needs_coverage = False, tower_cost = 500): 20,
        Square(SquareType.land, needs_coverage = False, tower_cost = 30):   100,
        Square(SquareType.hill, needs_coverage = False, tower_cost = 100):  8,
        Square(SquareType.city, needs_coverage = True, tower_cost = 200):   1
    }

    landscape = generate_random_landscape(list(square_grid.keys()), square_grid, rows, cols)
    plot_landscape(landscape)
    plot_costs(landscape)
    plot_coverage(landscape)


    def fintess_function(coords):
        return 0


    Individual.set_fitness_function(fintess_function)
    Individual.rows = rows
    Individual.cols = cols
    ind = Individual.generate_random(.0005)
    test_landscape = copy.deepcopy(landscape)
    test_landscape.add_radars(ind.get_coordinates(), 7)
    plot_coverage(test_landscape)

    radars = ind.count_radars()
    uncovered = test_landscape.uncovered_count()

    print(f'Radars: {radars}')
    print(f'Uncovered Squares: {uncovered}')