def _add_top_down_entry(layout: QVBoxLayout, controller: str, constant: str, label_text: str, label_font, value_font, alignment: Qt.AlignmentFlag = Qt.AlignLeft | Qt.AlignVCenter, force_value=None) -> None: hbox = QHBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) # Create label label = QLabel() label.setFont(label_font) label.setText(label_text) label.setAlignment(alignment) # Create value value_label = QLabel() value_label.setFont(value_font) value = None if controller == 'boxcar' and constant: value = get_boxcar_constant(constant) elif controller == 'ga' and constant: value = get_ga_constant(constant) elif force_value: value = force_value value_label.setText(str(value)) # Add the labels to the hbox hbox.addWidget(label, 1) hbox.addWidget(value_label, 1) # Finally add hbox to the top-down layout layout.addLayout(hbox)
def _add_row_entry(form: QFormLayout, controller: str, constant: str, label_text: str, label_font, value_font, alignment: Qt.AlignmentFlag = Qt.AlignLeft | Qt.AlignVCenter, force_value=None) -> None: # Create label label = QLabel() label.setFont(label_font) label.setText(label_text) label.setAlignment(alignment) # Create value value_label = QLabel() value_label.setFont(value_font) value = None if controller == 'boxcar' and constant: value = get_boxcar_constant(constant) elif controller == 'ga' and constant: value = get_ga_constant(constant) elif force_value: value = force_value value_label.setText(str(value)) form.addRow(label, value_label)
def calculate_fitness(self) -> None: """ Calculate the fitness of an individual at the end of a generation. """ func = get_ga_constant('fitness_function') fitness = func(max(self.max_position, 0.0), self.num_wheels, self.chassis_volume, self.wheels_volume, self.frames) self._fitness = max(fitness, 0.0001)
def _set_first_gen(self) -> None: """ Sets the first generation, i.e. random cars """ # Create the floor if FIRST_GEN, but not if it's in progress if self.state == States.FIRST_GEN: self.floor = Floor(self.world) # We are now in progress of creating the first gen self.state = States.FIRST_GEN_IN_PROGRESS # Initialize cars randomly self.cars = [] # Determine how many cars to make num_to_create = None if get_ga_constant( 'num_parents' ) - self._total_individuals_ran >= get_boxcar_constant( 'run_at_a_time'): num_to_create = get_boxcar_constant('run_at_a_time') else: num_to_create = get_ga_constant( 'num_parents') - self._total_individuals_ran # @NOTE that I create the subset of cars for i in range(num_to_create): car = create_random_car(self.world, self.floor.winning_tile, self.floor.lowest_y) self.cars.append(car) self._next_pop.extend( self.cars ) # Add the cars to the next_pop which is used by population leader = self.find_new_leader() self.leader = leader # Time to go to new state? if self._total_individuals_ran == get_ga_constant('num_parents'): self._creating_random_cars = False self.state = States.NEXT_GEN
def _set_number_of_cars_alive(self) -> None: """ Set the number of cars alive on the screen label """ total_for_gen = get_ga_constant('num_parents') if self.current_generation > 0: total_for_gen = self._next_gen_size num_batches = math.ceil(total_for_gen / get_boxcar_constant('run_at_a_time')) text = '{}/{} (batch {}/{})'.format(self.num_cars_alive, self.batch_size, self.current_batch, num_batches) self.stats_window.current_num_alive.setText(text)
def _mutation(self, chromosome: np.ndarray) -> None: """ Randomly decide if we should perform mutation on a gene within the chromosome. This is done in place """ rand_mutation = random.random() mutation_bucket = np.digitize(rand_mutation, self._mutation_bins) # Gaussian if mutation_bucket == 0: mutation_rate = get_ga_constant('mutation_rate') if get_ga_constant('mutation_rate_type').lower() == 'dynamic': mutation_rate = mutation_rate / math.sqrt( self.current_generation + 1) gaussian_mutation(chromosome, mutation_rate, scale=get_ga_constant('gaussian_mutation_scale')) # Random uniform elif mutation_bucket == 1: #@TODO: add to this pass else: raise Exception( 'Unable to determine valid mutation based off probabilities')
def _crossover(self, p1_chromosome: np.ndarray, p2_chromosome: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ Perform crossover between two parent chromosomes and return TWO child chromosomes """ rand_crossover = random.random() crossover_bucket = np.digitize(rand_crossover, self._crossover_bins) # SBX if crossover_bucket == 0: c1_chromosome, c2_chromosome = SBX(p1_chromosome, p2_chromosome, get_ga_constant('SBX_eta')) else: raise Exception( 'Unable to determine valid crossover based off probabilities') return c1_chromosome, c2_chromosome
def _update(self) -> None: """ Called once every 1/FPS to update everything """ for car in self.cars: if not car.is_alive: continue # Did the car die/win? if not car.update(): # Another individual has finished self._total_individuals_ran += 1 # Decrement the number of cars alive self.num_cars_alive -= 1 self._set_number_of_cars_alive() # If the car that just died/won was the leader, we need to find a new one if car == self.leader: leader = self.find_new_leader() self.leader = leader self.game_window.leader = leader else: if not self.leader: self.leader = leader self.game_window.leader = leader else: car_pos = car.position.x if car_pos > self.leader.position.x: self.leader = car self.game_window.leader = car # If the leader is valid, then just pan to the leader if not self.manual_control and self.leader: self.game_window.pan_camera_to_leader() # If there is not a leader then the generation is over OR the next group of N need to run if not self.leader: # Replay state if self.state == States.REPLAY: name = 'car_{}.npy'.format(self.current_generation) car = load_car(self.world, self.floor.winning_tile, self.floor.lowest_y, np.inf, args.replay_from_folder, name) self.cars = [car] self.game_window.cars = self.cars self.leader = self.find_new_leader() self.game_window.leader = self.leader self.current_generation += 1 txt = 'Replay {}/{}'.format(self.current_generation, self.num_replay_inds) self.stats_window.generation.setText( "<font color='red'>Replay</font>") self.stats_window.pop_size.setText( "<font color='red'>Replay</font>") self.stats_window.current_num_alive.setText( "<font color='red'>" + txt + '</font>') return # Are we still in the process of just random creation? if self.state in (States.FIRST_GEN, States.FIRST_GEN_IN_PROGRESS): self._set_first_gen() self.game_window.leader = self.leader self.game_window.cars = self.cars self.num_cars_alive = len(self.cars) self.batch_size = self.num_cars_alive self.current_batch += 1 self._set_number_of_cars_alive() return # Next N individuals need to run # We already have a population defined and we need to create N cars to run elif self.state == States.NEXT_GEN_CREATE_OFFSPRING: num_create = min( self._next_gen_size - self._total_individuals_ran, get_boxcar_constant('run_at_a_time')) self.cars = self._create_num_offspring(num_create) self.batch_size = len(self.cars) self.num_cars_alive = len(self.cars) self._next_pop.extend( self.cars) # These cars will now be part of the next pop self.game_window.cars = self.cars leader = self.find_new_leader() self.leader = leader self.game_window.leader = leader # should we go to the next state? if (self.current_generation == 0 and (self._total_individuals_ran >= get_ga_constant('num_parents'))) or\ (self.current_generation > 0 and (self._total_individuals_ran >= self._next_gen_size)): self.state = States.NEXT_GEN else: self.current_batch += 1 self._set_number_of_cars_alive() return elif self.state in (States.NEXT_GEN, States.NEXT_GEN_COPY_PARENTS_OVER, States.NEXT_GEN_CREATE_OFFSPRING): self.next_generation() return else: raise Exception( 'You should not be able to get here, but if you did, awesome! Report this to me if you actually get here.' ) self.world.ClearForces() # Update windows self.game_window._update() # Step self.world.Step(1. / FPS, 10, 6)
def _create_num_offspring(self, number_of_offspring) -> List[Individual]: """ This is a helper function to decide whether to grab from current pop or create new offspring. Creates a number of offspring from the current population. This assumes that the current population are all able to reproduce. This is broken up from the main next_generation function so that we can create N individuals at a time if needed without going to the next generation. Mainly used if `run_at_a_time` is < the number of individuals that are in the next generation. """ next_pop: List[Individual] = [] #@TODO: comment this to new state # If the selection type is plus, then it means certain individuals survive to the next generation, so we need # to grab those first before we create new ones # if get_ga_constant('selection_type').lower() == 'plus' and len(self._next_pop) < get_ga_constant('num_parents'): if self.state == States.NEXT_GEN_COPY_PARENTS_OVER: # Select the subset of the individuals to bring to the next gen increment = 0 # How much did the offset increment by for idx in range(self._offset_into_population, len(self.population.individuals)): # for individual in self.population.individuals[self._offset_into_population: self._offset_into_population + number_of_offspring]: individual = self.population.individuals[idx] increment += 1 # For offset world = self.world wheel_radii = individual.wheel_radii wheel_densities = individual.wheel_densities #wheel_motor_speeds = individual.wheel_motor_speeds chassis_vertices = individual.chassis_vertices chassis_densities = individual.chassis_densities winning_tile = individual.winning_tile lowest_y_pos = individual.lowest_y_pos lifespan = individual.lifespan # If the individual is still alive, they survive if lifespan > 0: car = Car( world, wheel_radii, wheel_densities, # wheel_motor_speeds, # Wheel chassis_vertices, chassis_densities, # Chassis winning_tile, lowest_y_pos, lifespan) next_pop.append(car) # Check to see if we've added enough parents. The reason we check here is if you requet 5 parents but # 2/5 are dead, then you need to keep going until you get 3 good ones. if len(next_pop) == number_of_offspring: break else: print("Oh dear, you're dead") # Increment offset for the next time self._offset_into_population += increment # If there weren't enough parents that made it to the new generation, we just accept it and move on. # Since the lifespan could have reached 0, you are not guaranteed to always have the same number of parents copied over. if self._offset_into_population >= len( self.population.individuals): self.state = States.NEXT_GEN_CREATE_OFFSPRING # Otherwise just perform crossover with the current population and produce num_of_offspring # @NOTE: The state, even if we got here through State.NEXT_GEN or State.NEXT_GEN_COPY_PARENTS_OVER is now # going to switch to State.NEXT_GEN_CREATE_OFFSPRING based off this else condition. It's not set here, but # rather at the end of new_generation else: # Keep adding children until we reach the size we need while len(next_pop) < number_of_offspring: # Tournament crossover if get_ga_constant( 'crossover_selection').lower() == 'tournament': p1, p2 = tournament_selection( self.population, 2, get_ga_constant('tournament_size')) # Roulette elif get_ga_constant( 'crossover_selection').lower() == 'roulette': p1, p2 = roulette_wheel_selection(self.population, 2) else: raise Exception( 'crossover_selection "{}" is not supported'.format( get_ga_constant('crossover_selection').lower())) # Crossover c1_chromosome, c2_chromosome = self._crossover( p1.chromosome, p2.chromosome) # Mutation self._mutation(c1_chromosome) self._mutation(c2_chromosome) # Don't let the chassis density become <=0. It is bad smart_clip(c1_chromosome) smart_clip(c2_chromosome) # Create children from the new chromosomes c1 = Car.create_car_from_chromosome( p1.world, p1.winning_tile, p1.lowest_y_pos, get_ga_constant('lifespan'), c1_chromosome) c2 = Car.create_car_from_chromosome( p2.world, p2.winning_tile, p2.lowest_y_pos, get_ga_constant('lifespan'), c2_chromosome) # Add children to the next generation next_pop.extend([c1, c2]) # Return the next population that will play. Remember, this can be a subset of the overall population since # those parents still exist. return next_pop
def next_generation(self) -> None: if self.state == States.NEXT_GEN: self.stats_window.pop_size.setText(str(self._next_gen_size)) self.current_batch = 0 # Set next state to copy parents if its plus, otherwise comma is just going to create offspring if get_ga_constant('selection_type').lower() == 'plus': self.state = States.NEXT_GEN_COPY_PARENTS_OVER elif get_ga_constant('selection_type').lower() == 'comma': self.state = States.NEXT_GEN_CREATE_OFFSPRING else: raise Exception('Invalid selection_type: "{}"'.format( get_ga_constant('selection_type'))) self._offset_into_population = 0 self._total_individuals_ran = 0 # Reset back to the first individual self.population.individuals = self._next_pop self._next_pop = [] # Reset the next pop # Calculate fit for individual in self.population.individuals: individual.calculate_fitness() # Should we save the pop if args.save_pop: path = os.path.join( args.save_pop, 'pop_gen{}'.format(self.current_generation)) if os.path.exists(path): raise Exception( '{} already exists. This would overwrite everything, choose a different folder or delete it and try again' .format(path)) os.makedirs(path) save_population(path, self.population, settings.settings) # Save best? if args.save_best: save_car(args.save_best, 'car_{}'.format(self.current_generation), self.population.fittest_individual, settings.settings) self._set_previous_gen_avg_fitness() self._set_previous_gen_num_winners() self._increment_generation() # Grab the best individual and compare to best fitness best_ind = self.population.fittest_individual if best_ind.fitness > self.max_fitness: self.max_fitness = best_ind.fitness self._set_max_fitness() self.gen_without_improvement = 0 else: self.gen_without_improvement += 1 # Set text for gen improvement self.stats_window.gens_without_improvement.setText( str(self.gen_without_improvement)) # Set the population to be just the parents allowed for reproduction. Only really matters if `plus` method is used. # If `plus` method is used, there can be more individuals in the next generation, so this limits the number of parents. self.population.individuals = elitism_selection( self.population, get_ga_constant('num_parents')) random.shuffle(self.population.individuals) # Parents + offspring selection type ('plus') if get_ga_constant('selection_type').lower() == 'plus': # Decrement lifespan for individual in self.population.individuals: individual.lifespan -= 1 num_offspring = min(self._next_gen_size - len(self._next_pop), get_boxcar_constant('run_at_a_time')) self.cars = self._create_num_offspring(num_offspring) # Set number of cars alive self.num_cars_alive = len(self.cars) self.batch_size = self.num_cars_alive self.current_batch += 1 self._set_number_of_cars_alive() self._next_pop.extend(self.cars) # Add to next_pop self.game_window.cars = self.cars leader = self.find_new_leader() self.leader = leader self.game_window.leader = leader if get_ga_constant('selection_type').lower() == 'comma': self.state = States.NEXT_GEN_CREATE_OFFSPRING elif get_ga_constant('selection_type').lower( ) == 'plus' and self._offset_into_population >= len( self.population.individuals): self.state = States.NEXT_GEN_CREATE_OFFSPRING
def __init__(self, world, replay=False): super().__init__() self.world = world self.title = 'Genetic Algorithm - Cars' self.top = 150 self.left = 150 self.width = 1100 self.height = 700 self.max_fitness = 0.0 self.cars = [] self.population = Population([]) self.state = States.FIRST_GEN self._next_pop = [ ] # Used when you are in state 1, i.e. creating new cars from the old population self.current_batch = 1 self.batch_size = get_boxcar_constant('run_at_a_time') self.gen_without_improvement = 0 self.replay = replay self.manual_control = False self.current_generation = 0 self.leader = None # What car is leading self.num_cars_alive = get_boxcar_constant('run_at_a_time') self.batch_size = self.num_cars_alive self._total_individuals_ran = 0 self._offset_into_population = 0 # Used if we display only a certain number at a # Determine whether or not we are in the process of creating random cars. # This is used for when we only run so many at a time. For instance if `run_at_a_time` is 20 and # `num_parents` is 1500, then we can't just create 1500 cars. Instead we create batches of 20 to # run at a time. This flag is for deciding when we are done with that so we can move on to crossover # and mutation. self._creating_random_cars = True # Determine how large the next generation is if get_ga_constant('selection_type').lower() == 'plus': self._next_gen_size = get_ga_constant( 'num_parents') + get_ga_constant('num_offspring') elif get_ga_constant('selection_type').lower() == 'comma': self._next_gen_size = get_ga_constant('num_parents') else: raise Exception('Selection type "{}" is invalid'.format( get_ga_constant('selection_type'))) if self.replay: global args self.floor = Floor(self.world) self.state = States.REPLAY self.num_replay_inds = len([ x for x in os.listdir(args.replay_from_folder) if x.startswith('car_') ]) else: self._set_first_gen() # self.population = Population(self.cars) # For now this is all I'm supporting, may change in the future. There really isn't a reason to use # uniform or single point here because all the values have different ranges, and if you clip them, it # can make those crossovers useless. Instead just use simulated binary crossover to ensure better crossover. self._crossover_bins = np.cumsum([get_ga_constant('probability_SBX')]) self._mutation_bins = np.cumsum([ get_ga_constant('probability_gaussian'), get_ga_constant('probability_random_uniform') ]) self.init_window() self.stats_window.pop_size.setText(str(get_ga_constant('num_parents'))) self._set_number_of_cars_alive() self.game_window.cars = self.cars self._timer = QTimer(self) self._timer.timeout.connect(self._update) self._timer.start(1000 // get_boxcar_constant('fps'))
def _add_ga_settings_window(self) -> None: self.ga_settings_window = QVBoxLayout() # GA settings title label_ga_settings = QLabel() label_ga_settings.setFont(font_bold) label_ga_settings.setText('Genetic Algorithm Settings') label_ga_settings.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.ga_settings_window.addWidget(label_ga_settings) self._add_ga_entry('selection_type', 'Selection Type:', font_bold, normal_font) # Prob mutation mutation_prob = '{:.1f}% Gaussian\n{:.1f}% Uniform'.format( get_ga_constant('probability_gaussian') * 100.0, get_ga_constant('probability_random_uniform') * 100.0) self._add_ga_entry(None, 'Prob. Mutation:', font_bold, normal_font, force_value=mutation_prob) # Prob crossover crossover_prob = '{:.1f}% SBX'.format( get_ga_constant('probability_SBX') * 100.0) self._add_ga_entry(None, 'Prob. Crossover:', font_bold, normal_font, force_value=crossover_prob) # Crossover selection # Tournament crossover selection if get_ga_constant('crossover_selection').lower() == 'tournament': if not get_ga_constant('tournament_size'): raise Exception('You must provide a tournament size if you choose "tournament" for crossover_selection.\n'+\ '"tournament_size" can be anywhere from [1, len(population))') parent_crossover = 'tournament of {}'.format( get_ga_constant('tournament_size')) # roulette elif get_ga_constant('crossover_selection').lower() == 'roulette': parent_crossover = 'roulette' else: raise Exception( 'Unable to determine crossover_selection "{}"'.format( get_ga_constant('crossover_selection'))) self._add_ga_entry(None, 'Crossover Selection:', font_bold, normal_font, force_value=parent_crossover) # Mutation rate mutation_rate = '{:.1f}%'.format( get_ga_constant('mutation_rate') * 100) # static if get_ga_constant('mutation_rate_type').lower() == 'static': mutation_rate += ' Static' # decaying elif get_ga_constant('mutation_rate_type').lower() == 'decaying': mutation_rate += ' Decaying' else: raise Exception( 'Unable to determine mutation_rate_type "{}"'.format( get_ga_constant('mutation_rate_type'))) self._add_ga_entry(None, 'Mutation Rate:', font_bold, normal_font, force_value=mutation_rate) # Lifespan lifespan = get_ga_constant('lifespan') lifespan = str(int(lifespan)) if lifespan != np.inf else 'infinite' self._add_ga_entry(None, 'Lifespan:', font_bold, normal_font, force_value=lifespan) self.grid.addLayout(self.ga_settings_window, 0, 3)
def _init_window(self) -> None: ROW = 0 COL = 0 stats_vbox = QVBoxLayout() stats_vbox.setContentsMargins(0, 0, 0, 0) # Create the current generation generation_label = QLabel() generation_label.setFont(font_bold) generation_label.setText('Generation:') generation_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.generation = QLabel() self.generation.setFont(normal_font) self.generation.setText("<font color='red'>" + '1' + '</font>') self.generation.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_generation = QHBoxLayout() hbox_generation.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_generation.addWidget(generation_label, 1) hbox_generation.addWidget(self.generation, 1) stats_vbox.addLayout(hbox_generation) # Current number alive current_num_alive_label = QLabel() current_num_alive_label.setFont(font_bold) current_num_alive_label.setText('Current Alive:') current_num_alive_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.current_num_alive = QLabel() self.current_num_alive.setFont(normal_font) self.current_num_alive.setText(str(get_ga_constant('num_parents'))) self.current_num_alive.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_num_alive = QHBoxLayout() hbox_num_alive.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_num_alive.addWidget(current_num_alive_label, 1) hbox_num_alive.addWidget(self.current_num_alive, 1) stats_vbox.addLayout(hbox_num_alive) # population size pop_size_label = QLabel() pop_size_label.setFont(font_bold) pop_size_label.setText('Population Size:') pop_size_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.pop_size = QLabel() self.pop_size.setFont(normal_font) self.pop_size.setText(str(get_ga_constant('num_parents'))) self.pop_size.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_pop_size = QHBoxLayout() hbox_pop_size.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_pop_size.addWidget(pop_size_label, 1) hbox_pop_size.addWidget(self.pop_size, 1) stats_vbox.addLayout(hbox_pop_size) # Create best fitness best_fitness_label = QLabel() best_fitness_label.setFont(font_bold) best_fitness_label.setText('Best Fitness Ever:') generation_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.best_fitness = QLabel() self.best_fitness.setFont(normal_font) self.best_fitness.setText('0.0') self.best_fitness.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_fitness = QHBoxLayout() hbox_fitness.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_fitness.addWidget(best_fitness_label, 1) hbox_fitness.addWidget(self.best_fitness, 1) stats_vbox.addLayout(hbox_fitness) # Average fitness last gen average_fitness_label = QLabel() average_fitness_label.setFont(font_bold) average_fitness_label.setText('Mean Fitness Last Gen:') average_fitness_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.average_fitness_last_gen = QLabel() self.average_fitness_last_gen.setFont(normal_font) self.average_fitness_last_gen.setText('0.0') self.average_fitness_last_gen.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_avg_fitness = QHBoxLayout() hbox_avg_fitness.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_avg_fitness.addWidget(average_fitness_label, 1) hbox_avg_fitness.addWidget(self.average_fitness_last_gen, 1) stats_vbox.addLayout(hbox_avg_fitness) # num solved last gen num_solved_last_gen_label = QLabel() num_solved_last_gen_label.setFont(font_bold) num_solved_last_gen_label.setText('# Solved Last Gen:') num_solved_last_gen_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.num_solved_last_gen = QLabel() self.num_solved_last_gen.setFont(normal_font) self.num_solved_last_gen.setText('0') self.num_solved_last_gen.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_num_solved = QHBoxLayout() hbox_num_solved.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_num_solved.addWidget(num_solved_last_gen_label, 1) hbox_num_solved.addWidget(self.num_solved_last_gen, 1) stats_vbox.addLayout(hbox_num_solved) # generations without improvement gens_without_improvement_label = QLabel() gens_without_improvement_label.setFont(font_bold) gens_without_improvement_label.setText('Stale Generations:') gens_without_improvement_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.gens_without_improvement = QLabel() self.gens_without_improvement.setFont(normal_font) self.gens_without_improvement.setText('0') self.gens_without_improvement.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) hbox_gens_without_improvement = QHBoxLayout() hbox_gens_without_improvement.setContentsMargins(5, 0, 0, 0) # Give equal weight hbox_gens_without_improvement.addWidget(gens_without_improvement_label, 1) hbox_gens_without_improvement.addWidget(self.gens_without_improvement, 1) stats_vbox.addLayout(hbox_gens_without_improvement) self.grid.addLayout(stats_vbox, 0, 0) # self.grid.addWidget(QLabel(),0,1) # self.grid.setColumnStretch(1,10) self._add_ga_settings_window() # Finally add the GA Settings Window
def create_random_car(world: b2World, winning_tile: b2Vec2, lowest_y_pos: float): """ Creates a random car based off the values found in settings.py under the settings dictionary """ # Create a number of random wheels. # Each wheel will have a random radius and density num_wheels = random.randint(get_boxcar_constant('min_num_wheels'), get_boxcar_constant('max_num_wheels') + 1) wheel_verts = list(range(num_wheels)) # What vertices should we attach to? rand.shuffle(wheel_verts) wheel_verts = wheel_verts[:num_wheels] wheel_radii = [0.0 for _ in range(8)] wheel_densities = [0.0 for _ in range(8)] # wheel_motor_speeds = [random.uniform(get_boxcar_constant('min_wheel_motor_speed'), get_boxcar_constant('max_wheel_motor_speed')) # for _ in range(8)] # Doesn't matter if this is set. There won't be a wheel if the density OR radius is 0 # Assign a random radius/density to vertices found in wheel_verts for vert_idx in wheel_verts: radius = random.uniform(get_boxcar_constant('min_wheel_radius'), get_boxcar_constant('max_wheel_radius')) density = random.uniform(get_boxcar_constant('min_wheel_density'), get_boxcar_constant('max_wheel_density')) # Override the intiial 0.0 wheel_radii[vert_idx] = radius wheel_densities[vert_idx] = density min_chassis_axis = get_boxcar_constant('min_chassis_axis') max_chassis_axis = get_boxcar_constant('max_chassis_axis') #### # The chassis vertices are on a grid and defined by v0-v7 like so: # # v2 # | # v3 | v1 # v4 -------------- v0 # v5 | v7 # | # v6 # # V0, V2, V4 and V6 are on an axis, while the V1 is defined somewhere between V0 and V2, V3 is defined somewhere between V2 and V4, etc. chassis_vertices = [] chassis_vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), 0)) chassis_vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), random.uniform(min_chassis_axis, max_chassis_axis))) chassis_vertices.append( b2Vec2(0, random.uniform(min_chassis_axis, max_chassis_axis))) chassis_vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), random.uniform(min_chassis_axis, max_chassis_axis))) chassis_vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), 0)) chassis_vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), -random.uniform(min_chassis_axis, max_chassis_axis))) chassis_vertices.append( b2Vec2(0, -random.uniform(min_chassis_axis, max_chassis_axis))) chassis_vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), -random.uniform(min_chassis_axis, max_chassis_axis))) # Now t hat we have our chassis vertices, we need to get a random density for them as well densities = [] for i in range(8): densities.append( random.uniform(get_boxcar_constant('min_chassis_density'), get_boxcar_constant('max_chassis_density'))) return Car( world, wheel_radii, wheel_densities, # wheel_motor_speeds, chassis_vertices, densities, winning_tile, lowest_y_pos, lifespan=get_ga_constant('lifespan'))