import copy import random import configparser import math import pygame from domain.entities.entity import Entity config = configparser.ConfigParser() config.read("config.ini") from domain.world import World from AI_brain.rotate_and_go_aStar import RotateAndGoAStar, State from random import randint hits = 0 misses = 0 class Cashed_sub_paths(dict): def __init__(self): super().__init__() def __missing__(self, key): self[key] = Cashed_sub_paths() return self[key] class Cashed_sub_path: def __init__(self, sub_path: list[str] = [], distance: int = 0): self.sub_path = sub_path self.distance = distance steps_distance_cashed: dict[tuple[int, int], Cashed_sub_path] = Cashed_sub_paths() class Path: def __init__(self): self.walk = [] self.permutation = [] self.real_path = [] self.distance = 0 def random_walk(self, dusts: list[Entity]): permutation = generate_random_permutation(len(dusts)) self.permutation = permutation self.walk = addStartAndStation(permutation) def calculate_distance(self, world: World): distance = 0 for i in range(len(self.walk) - 1): next_distance, next_real_path = self.step_distance( self.walk[i], self.walk[i + 1], world ) distance += next_distance # BUG this part is not working and is not used, B.1 must be resolved self.real_path = self.real_path + ["DEFAULT_ROTATION"] + next_real_path self.distance = distance def step_distance( self, from_id: int, to_id: int, world: World ) -> tuple[int, list[str]]: global hits, misses if (from_id, to_id) in steps_distance_cashed: hits += 1 distance = steps_distance_cashed[(from_id, to_id)].distance sub_path = steps_distance_cashed[(from_id, to_id)].sub_path return distance, sub_path misses += 1 path_searcher = RotateAndGoAStar( world, self.getPosition(from_id, world.dustList), self.getPosition(to_id, world.dustList), ) path_searcher.search() steps_distance_cashed[(from_id, to_id)] = Cashed_sub_path( path_searcher.actions, path_searcher.cost ) # BUG B.1 inverse path inverse_sub_path = path_searcher.actions.copy() steps_distance_cashed[(to_id, from_id)] = Cashed_sub_path( inverse_sub_path, path_searcher.cost ) return path_searcher.cost, path_searcher.actions def inverse_sub_path(sub_path: list[str]) -> list[str]: sub_path.reverse() for command in sub_path: command.replace("RL", "RR") command.replace("RR", "RR") def getPosition( self, number: int, dustList: list[Entity], ) -> State: if number == -1: dock_start_x, dock_start_y = config.get( "CONSTANT", "DockStationStartPosition" ).split(",") dock_start_x, dock_start_y = int(dock_start_x), int(dock_start_y) return State(dock_start_x, dock_start_y) if number == -2: vacuum_start_x, vacuum_start_y = config.get( "CONSTANT", "RobotStartPosition" ).split(",") vacuum_start_x, vacuum_start_y = int(vacuum_start_x), int(vacuum_start_y) return State(vacuum_start_x, vacuum_start_y) return State(dustList[number].x, dustList[number].y) def get_real_path(self, world: World): full_path = [] for index_place in range(len(self.walk) - 1): path_searcher = RotateAndGoAStar( world, self.getPosition(self.walk[index_place], world.dustList), self.getPosition(self.walk[index_place + 1], world.dustList), ) path_searcher.search() full_path = full_path + ["DEFAULT_ROTATION"] + path_searcher.actions self.real_path = full_path def generate_random_permutation(n): # Create a list of numbers from 1 to n numbers = list(range(0, n)) # Shuffle the list using the random.shuffle function random.shuffle(numbers) return numbers # BUG solution: inverse direction at the last step def addStartAndStation(permutation: list[int]): frequency = math.ceil(100 / config.getint("CONSTANT", "BananaFilling")) numer_of_stops = math.ceil(len(permutation) / frequency) walk = permutation.copy() for i in range(1, numer_of_stops): walk.insert((frequency + 1) * i - 1, -1) walk.insert(len(walk), -1) walk.insert(0, -2) return walk class GeneticAlgorytm: def __init__(self, world: World): self.world = world self.population_size = config.getint("GENETIC_ALGORITHM", "PopulationSize") self.mutation_probability = config.getfloat( "GENETIC_ALGORITHM", "MutationProbability" ) self.iteration_number = config.getint("GENETIC_ALGORITHM", "IterationNumber") self.descendants_number = config.getint( "GENETIC_ALGORITHM", "DescendantsNumber" ) self.dusts = world.dustList self.doc_station = world.doc_station self.paths: list[Path] = [] self.checked_permutations = {} self.best_path = None self.best_distance = math.inf self.best_real_path = [] def generate_population(self): for i in range(self.population_size): path = Path() path.random_walk(self.dusts) self.checked_permutations[tuple(path.permutation)] = True path.calculate_distance(self.world) self.paths.append(path) def print_top(self): print( "Best path: ", self.best_path.walk, "Distance: ", self.best_path.distance, ) for path in self.paths[1:]: print(path.walk, path.distance) def evaluate_population(self): self.paths.sort(key=lambda x: x.distance, reverse=False) self.best_distance = self.paths[0].distance self.best_path = self.paths[0] for path in self.paths[self.population_size :]: del self.checked_permutations[tuple(path.permutation)] self.paths = self.paths[: self.population_size] def create_child(self, parent1: Path, parent2: Path) -> Path: child = Path() child.permutation = parent1.permutation[: len(parent1.permutation) // 2] # Add missing items from parent2 in the order they appear for item in parent2.permutation: if item not in child.permutation: child.permutation.append(item) child.walk = addStartAndStation(child.permutation) child.calculate_distance(self.world) return child def run(self): self.generate_population() for i in range(self.iteration_number): self.crossover() self.evaluate_population() self.best_real_path = self.paths[0].get_real_path(self.world) print(hits, (misses + hits)) print(hits / (misses + hits)) def mutate(self, mutant: Path) -> Path: random_number = randint(0, len(mutant.permutation) - 1) random_number2 = random_number while random_number == random_number2: random_number2 = randint(0, len(mutant.permutation) - 1) mutant.permutation[random_number], mutant.permutation[random_number2] = ( mutant.permutation[random_number2], mutant.permutation[random_number], ) if tuple(mutant.permutation) in self.checked_permutations: return self.mutate(mutant) mutant.walk = addStartAndStation(mutant.permutation) mutant.calculate_distance(self.world) return mutant def crossover(self): for i in range(self.descendants_number): parent1 = self.paths[random.randint(0, self.population_size - 1)] parent2 = self.paths[random.randint(0, self.population_size - 1)] child = self.create_child(parent1, parent2) while tuple(child.permutation) in self.checked_permutations: parent1 = self.paths[random.randint(0, self.population_size - 1)] parent2 = self.paths[random.randint(0, self.population_size - 1)] child = self.create_child(parent1, parent2) self.checked_permutations[tuple(child.permutation)] = True self.paths.append(child) mutant = Path() mutant.permutation = child.permutation.copy() mutant = self.mutate(mutant) self.checked_permutations[tuple(mutant.permutation)] = True self.paths.append(mutant) self.evaluate_population()