def __init__(self, world: b2World, seed=get_boxcar_constant('gaussian_floor_seed'), num_tiles=get_boxcar_constant('max_floor_tiles')): self.world = world self.seed = seed # @TODO: Add this to the setting self.num_tiles = num_tiles self.floor_tiles: List[b2Body] = [] self.rand = np.random.RandomState( self.seed ) # @NOTE: the floor has it's own random that it references. self.floor_creation_type = get_boxcar_constant( 'floor_creation_type').lower() if self.floor_creation_type == 'gaussian': self._generate_gaussian_random_floor() elif self.floor_creation_type == 'ramp': self._generate_ramp() elif self.floor_creation_type == 'jagged': self._create_jagged_floor() self.lowest_y = 10 for floor_tile in self.floor_tiles: world_coords = [ floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[i]) for i in range(4) ] for coord in world_coords: if coord.y < self.lowest_y: self.lowest_y = coord.y
def draw_circle(painter: QPainter, body: b2Body, local=False) -> None: """ Draws a circle with the given painter. """ for fixture in body.fixtures: if isinstance(fixture.shape, b2CircleShape): # Set the color of the circle to be based off wheel density adjust = get_boxcar_constant( 'max_wheel_density') - get_boxcar_constant('min_wheel_density') # If the min/max are the same you will get 0 adjust. This is to prevent divide by zero. if adjust == 0.0: hue_ratio = 0.0 else: hue_ratio = (fixture.density - get_boxcar_constant('min_wheel_density')) / adjust hue_ratio = min(max(hue_ratio, 0.0), 1.0) # Just in case you leave the GA unbounded... color = QColor.fromHsvF(hue_ratio, 1., .8) painter.setBrush(QBrush(color, Qt.SolidPattern)) radius = fixture.shape.radius if local: center = fixture.shape.pos else: center = body.GetWorldPoint(fixture.shape.pos) # Fill circle painter.drawEllipse(QPointF(center.x, center.y), radius, radius) # Draw line (helps for visualization of how fast and direction wheel is moving) _set_painter_solid(painter, Qt.black) p0 = QPointF(center.x, center.y) p1 = QPointF(center.x + radius * math.cos(body.angle), center.y + radius * math.sin(body.angle)) painter.drawLine(p0, p1)
def _generate_gaussian_random_floor(self): """ Helper method for generating a gaussian random floor """ threshold = get_boxcar_constant('tile_gaussian_threshold') denominator = get_boxcar_constant('tile_gaussian_denominator') mu = get_boxcar_constant('tile_angle_mu') std = get_boxcar_constant('tile_angle_std') tile_position = b2Vec2(-5, 0) #@NOTE: Look in README.md for explanation of the below equation for i in range(self.num_tiles): numerator = min(i, threshold) scale = min(float(numerator) / denominator, 1.0) angle = self.rand.normal(mu, std) * scale floor_tile = create_floor_tile(self.world, tile_position, angle) self.floor_tiles.append(floor_tile) t = 1 if angle < 0: t = 0 # @TODO: Fix this. For whatever reason B2D rearranges the vertices. I should track a point during its creation instead world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[t]) tile_position = world_coord self._create_stopping_zone(tile_position)
def create_random_chassis(world: b2World) -> b2Body: min_chassis_axis = get_boxcar_constant('min_chassis_axis') max_chassis_axis = get_boxcar_constant('max_chassis_axis') vertices = [] vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), 0)) vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), random.uniform(min_chassis_axis, max_chassis_axis))) vertices.append( b2Vec2(0, random.uniform(min_chassis_axis, max_chassis_axis))) vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), random.uniform(min_chassis_axis, max_chassis_axis))) vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), 0)) vertices.append( b2Vec2(-random.uniform(min_chassis_axis, max_chassis_axis), -random.uniform(min_chassis_axis, max_chassis_axis))) vertices.append( b2Vec2(0, -random.uniform(min_chassis_axis, max_chassis_axis))) vertices.append( b2Vec2(random.uniform(min_chassis_axis, max_chassis_axis), -random.uniform(min_chassis_axis, max_chassis_axis))) densities = [] for i in range(8): densities.append( random.uniform(get_boxcar_constant('min_chassis_density'), get_boxcar_constant('max_chassis_density'))) return create_chassis(world, vertices, densities)
def create_floor_tile(world: b2World, position: b2Vec2, angle: float) -> b2Body: """ Create a floor tile at some angle """ width = get_boxcar_constant('floor_tile_width') height = get_boxcar_constant('floor_tile_height') body_def = b2BodyDef() body_def.position = position body = world.CreateBody(body_def) # Create Fixture fixture_def = b2FixtureDef() fixture_def.shape = b2PolygonShape() fixture_def.friction = 0.5 # Coordinates of tile # p3---------p2 # | | # p0---------p1 coords: List[b2Vec2] = [] coords.append(b2Vec2(0, 0)) # p0 coords.append(b2Vec2(width, 0)) # p1 coords.append(b2Vec2(width, -height)) # p2 coords.append(b2Vec2(0, -height)) # p3 # Rotate @NOTE: This rotates in reference to p0 coords = rotate_floor_tile(coords, b2Vec2(0, 0), angle) # Set vertices of fixture fixture_def.shape.vertices = coords body.CreateFixture(fixture_def) return body
def draw_polygon(painter: QPainter, body: b2Body, poly_type: str = '', adjust_painter: bool = True, local=False) -> None: """ Draws a polygon with the given painter. Uses poly_type for determining the fill of the polygon. """ if adjust_painter: _set_painter_clear(painter, Qt.black) for fixture in body.fixtures: if isinstance(fixture.shape, b2PolygonShape): poly = [] # If we are drawing a chassis, determine fill color if poly_type == 'chassis': adjust = get_boxcar_constant( 'max_chassis_density') - get_boxcar_constant( 'min_chassis_density') # If the min/max are the same you will get 0 adjust. This is to prevent divide by zero. if adjust == 0.0: hue_ratio = 0.0 else: hue_ratio = ( fixture.density - get_boxcar_constant('min_chassis_density')) / adjust hue_ratio = min( max(hue_ratio, 0.0), 1.0) # Just in case you leave the GA unbounded... color = QColor.fromHsvF(hue_ratio, 1., .8) painter.setBrush(QBrush(color, Qt.SolidPattern)) polygon: b2PolygonShape = fixture.shape local_points: List[b2Vec2] = polygon.vertices if local: world_coords = local_points else: world_coords = [ body.GetWorldPoint(point) for point in local_points ] for i in range(len(world_coords)): p0 = world_coords[i] if i == len(world_coords) - 1: p1 = world_coords[0] else: p1 = world_coords[i + 1] qp0 = QPointF(*p0) qp1 = QPointF(*p1) poly.append(qp0) poly.append(qp1) if poly: painter.drawPolygon(QPolygonF(poly))
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 init_window(self): self.centralWidget = QWidget(self) self.setCentralWidget(self.centralWidget) self.setWindowTitle(self.title) self.setGeometry(self.top, self.left, self.width, self.height) # Create stats_window self.stats_window = StatsWindow(self.centralWidget, (800, 200)) self.stats_window.setGeometry(QRect(0, 500, 800, 200)) self.stats_window.setObjectName('stats_window') # Create game_window - where the game is played self.game_window = GameWindow(self.centralWidget, (800, 500), self.world, self.floor, self.cars, self.leader) self.game_window.setGeometry(QRect(0, 0, 800, 500)) self.game_window.setObjectName('game_window') # Create settings_window - just a bunch of settings of the game and how they were defined, etc. self.settings_window = SettingsWindow(self.centralWidget, (300, 700)) self.settings_window.setGeometry(QRect(800, 0, 300, 700)) self.settings_window.setObjectName('settings_window') # Add main window self.main_window = QWidget(self) self.main_window.setGeometry(QRect(0, 0, 800, 500)) self.main_window.setObjectName('main_window') if get_boxcar_constant('show'): self.show()
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 _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 _create_jagged_floor(self): """ Helper method for creating a jagged floor. """ tile_position = b2Vec2(-5, 0) increasing_angle = get_boxcar_constant('jagged_increasing_angle') decreasing_angle = -get_boxcar_constant('jagged_decreasing_angle') for i in range(get_boxcar_constant('max_floor_tiles')): angle = increasing_angle if i % 2 == 1 else decreasing_angle floor_tile = create_floor_tile(self.world, tile_position, angle) self.floor_tiles.append(floor_tile) # You can blame this part of B2D. For Python it rearranges the vertices that I reference... t = 1 if angle < 0: t = 0 world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[t]) tile_position = world_coord self._create_stopping_zone(tile_position)
def _create_stopping_zone(self, tile_position: b2Vec2) -> None: """ Creates a stopping zone so that the cars have a flat surface at the end of whatever track they were on. """ max_car_size = (get_boxcar_constant('max_chassis_axis') * 2.0) + (2.0 * get_boxcar_constant('max_wheel_radius')) tile_width = get_boxcar_constant('floor_tile_width') tiles_needed_before_wall = math.ceil(max_car_size / tile_width) additional_landing_zone = 0.0 additional_tiles_needed = additional_landing_zone / tile_width total_tiles_needed = math.ceil(tiles_needed_before_wall + additional_tiles_needed + 1) # Create a landing zone for i in range(total_tiles_needed): floor_tile = create_floor_tile(self.world, tile_position, 0) self.floor_tiles.append(floor_tile) world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[1]) tile_position = world_coord if i == tiles_needed_before_wall: self.winning_tile = self.floor_tiles[-1]
def __init__( self, world: b2World, wheel_radii: List[float], wheel_densities: List[float], # wheel_motor_speeds: List[float], chassis_vertices: List[b2Vec2], chassis_densities: List[float], winning_tile: b2Vec2, lowest_y_pos: float, lifespan: Union[int, float], from_chromosome: bool = False) -> None: self.world = world self.wheel_radii = wheel_radii self.wheel_densities = wheel_densities self.chassis_vertices = chassis_vertices self.chassis_densities = chassis_densities self.winning_tile = winning_tile self.lowest_y_pos = lowest_y_pos self.lifespan = lifespan self.is_winner = False # These are set in _init_car self.chassis = None self.is_alive = True self.frames = 0 self.max_tries = get_boxcar_constant('car_max_tries') self.num_failures = 0 self.max_position = -100 self._destroyed = False # GA stuff self._chromosome = None self._fitness = 0.01 # If the car is being initialized and is NOT from a chromosome, then you need to initialize the GA settins # and encode the chromosome. Otherwise it will be taken car of during the deconding of the chromosome if not from_chromosome: self._init_ga_settings() self._init_car()
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 _generate_ramp(self): """ Helper method for generating a ramp """ const_angle = get_boxcar_constant('ramp_constant_angle') approach_tiles_needed = get_boxcar_constant( 'ramp_approach_distance') / get_boxcar_constant('floor_tile_width') approach_tiles_needed = math.ceil(approach_tiles_needed) # Create the approach tile_position = b2Vec2(-5, 0) for i in range(approach_tiles_needed): floor_tile = create_floor_tile(self.world, tile_position, 0) self.floor_tiles.append(floor_tile) world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[1]) tile_position = world_coord last_approach_tile = tile_position # Are we using a constant angle for the ramp? if const_angle: num_ramp_tiles = get_boxcar_constant( 'ramp_constant_distance') / get_boxcar_constant( 'floor_tile_width') num_ramp_tiles = math.ceil(num_ramp_tiles) # Create ramp for i in range(num_ramp_tiles): floor_tile = create_floor_tile(self.world, tile_position, const_angle) self.floor_tiles.append(floor_tile) world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[1]) tile_position = world_coord # If not, create the increasing ramp else: start_angle = get_boxcar_constant('ramp_start_angle') increasing_angle = get_boxcar_constant('ramp_increasing_angle') max_angle = get_boxcar_constant('ramp_max_angle') increasing_type = get_boxcar_constant( 'ramp_increasing_type').lower() current_angle = start_angle # Create ramp while True: if increasing_type == 'multiply': next_angle = current_angle * increasing_angle elif increasing_type == 'add': next_angle = current_angle + increasing_angle else: raise Exception( "Unknown 'ramp_increasing_type', '{}'".format( increasing_type)) # If the next requested angle exceeds our maximum, break if next_angle > max_angle: break floor_tile = create_floor_tile(self.world, tile_position, current_angle) self.floor_tiles.append(floor_tile) world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[1]) tile_position = world_coord current_angle = next_angle # Create the landing zone distance_to_fly = get_boxcar_constant('ramp_distance_needed_to_jump') tile_position = b2Vec2(tile_position.x + distance_to_fly, last_approach_tile.y) for i in range(10): floor_tile = create_floor_tile(self.world, tile_position, 0) self.floor_tiles.append(floor_tile) world_coord = floor_tile.GetWorldPoint( floor_tile.fixtures[0].shape.vertices[1]) tile_position = world_coord self._create_stopping_zone(tile_position)
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'))
dest='save_pop_on_close', type=str, help='destination to save the population when program exits') # Replay @NOTE: Only supports replaying the best individual. Not a list of populations. parser.add_argument('--replay-from-folder', dest='replay_from_folder', type=str, help='destination to replay individuals from') args = parser.parse_args() return args if __name__ == "__main__": global args args = parse_args() replay = False if args.replay_from_folder: if 'settings.pkl' not in os.listdir(args.replay_from_folder): raise Exception('settings.pkl not found within {}'.format( args.replay_from_folder)) settings_path = os.path.join(args.replay_from_folder, 'settings.pkl') with open(settings_path, 'rb') as f: settings.settings = pickle.load(f) replay = True world = b2World(get_boxcar_constant('gravity')) App = QApplication(sys.argv) window = MainWindow(world, replay) sys.exit(App.exec_())
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'))
def __init__(self, parent, size): super().__init__(parent) self.size = size self.resize(size[0], size[1]) self._gradient_widget = QWidget() self._gradient_widget.resize(size[0] / 2, size[1]) self._create_linear_gradient() self.boxcar_form = QFormLayout() self.layout = QVBoxLayout() column_layout = QHBoxLayout() # For the densities and gradient self.layout.setContentsMargins(0, 0, 0, 0) # Add the headers for the two columns we will have headers = QHBoxLayout() headers.setContentsMargins(0, 0, 0, 0) # Add header for chassis density label_chassis_densities = QLabel() label_chassis_densities.setText('Chassis Density') label_chassis_densities.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) headers.addWidget(label_chassis_densities) # Add spacer headers.addStretch(1) # Add header for wheel density label_wheel_density = QLabel() label_wheel_density.setText('Wheel Density') label_wheel_density.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) headers.addWidget(label_wheel_density) # Add headers self.layout.addLayout(headers) # Add Chassis Density stuff chassis_density_vbox = QVBoxLayout() chassis_density_vbox.setContentsMargins(0, 0, 0, 0) min_chassis_density = get_boxcar_constant('min_chassis_density') max_chassis_density = get_boxcar_constant('max_chassis_density') chassis_range = max_chassis_density - min_chassis_density num_slices = 10 # Number of sections to add chassis_slices = [ (chassis_range) / (num_slices - 1) * i + min_chassis_density for i in range(num_slices) ] for chassis_slice in chassis_slices: label = QLabel() text = '{:.2f} -'.format(chassis_slice) label.setAlignment(Qt.AlignRight | Qt.AlignTop) label.setText(text) chassis_density_vbox.addWidget(label) # Add the VBox to the layout column_layout.addLayout(chassis_density_vbox) # Add the actual gradient but add it to a VBox to be cheeky and set stretch at the bottom gradient_vbox = QVBoxLayout() gradient_vbox.addWidget(self._gradient_widget, 15) column_layout.addLayout(gradient_vbox, 3) # Add Wheel Density stufff wheel_density_vbox = QVBoxLayout() wheel_density_vbox.setContentsMargins(0, 0, 0, 0) min_wheel_density = get_boxcar_constant('min_wheel_density') max_wheel_density = get_boxcar_constant('max_wheel_density') wheel_range = max_wheel_density - min_wheel_density num_slices = 10 # Number of sections to add (I'm keeping it the same as the chassis density for now) wheel_slices = [ (wheel_range) / (num_slices - 1) * i + min_wheel_density for i in range(num_slices) ] for i, wheel_slice in enumerate(wheel_slices): label = QLabel() text = '- {:.2f}'.format(wheel_slice) label.setAlignment(Qt.AlignLeft | Qt.AlignTop) label.setText(text) wheel_density_vbox.addWidget(label) # Add the VBox to the layout column_layout.addLayout(wheel_density_vbox) # Add column_layout to the layout self.layout.addLayout(column_layout, 5) self.layout.addStretch(1) # Add the boxcar settings self._add_boxcar_settings() # Set overall layout self.setLayout(self.layout)
def _add_boxcar_settings(self) -> None: label_boxcar_settings = QLabel() label_boxcar_settings.setFont(font_bold) label_boxcar_settings.setText('Boxcar Settings') label_boxcar_settings.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.layout.addWidget(label_boxcar_settings) # Make small note small_note = QLabel() small_note.setFont(QtGui.QFont('Times', 8, QtGui.QFont.Normal)) small_note.setText('*indicates it is part of the Genetic Algorithm') small_note.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.layout.addWidget(small_note) self._add_bc_row_entry('floor_tile_height', 'Tile Height:', font_bold, normal_font) self._add_bc_row_entry('floor_tile_width', 'Tile Width:', font_bold, normal_font) self._add_bc_row_entry('max_floor_tiles', 'Num Floor Tiles:', font_bold, normal_font) self._add_bc_row_entry('floor_creation_type', 'Floor Type:', font_bold, normal_font) # Ramp specific stuff if get_boxcar_constant('floor_creation_type') == 'ramp': # Is the ramp constant? if get_boxcar_constant('ramp_constant_angle'): self._add_bc_row_entry('ramp_constant_angle', 'Ramp Constant Angle:', font_bold, normal_font) self._add_bc_row_entry('ramp_constant_distance', 'Ramp Distance:', font_bold, normal_font) # Otherwise it's increasing angle else: self._add_bc_row_entry('ramp_increasing_angle', 'Ramp Increasing Angle:', font_bold, normal_font) self._add_bc_row_entry('ramp_start_angle', 'Start Angle:', font_bold, normal_font) self._add_bc_row_entry('ramp_increasing_type', 'Increase By:', font_bold, normal_font) self._add_bc_row_entry('ramp_max_angle', 'Max Ramp Angle:', font_bold, normal_font) self._add_bc_row_entry('ramp_approach_distance', 'Approach Distance:', font_bold, normal_font) self._add_bc_row_entry('ramp_distance_needed_to_jump', 'Jump Distance:', font_bold, normal_font) # Gaussian specific stuff elif get_boxcar_constant('floor_creation_type') == 'gaussian': self._add_bc_row_entry('gaussian_floor_seed', 'Seed:', font_bold, normal_font) self._add_bc_row_entry('tile_angle_mu', 'Tile Angle (mu):', font_bold, normal_font) self._add_bc_row_entry('tile_angle_std', 'Tile Andle (std):', font_bold, normal_font) self._add_bc_row_entry('tile_gaussian_denominator', 'Angle Normalizer:', font_bold, normal_font) self._add_bc_row_entry('tile_gaussian_threshold', 'Max Numerator:', font_bold, normal_font) # Jagged specific stuff elif get_boxcar_constant('floor_creation_type') == 'jagged': angle_range = '-{:.2f}, {:.2f}'.format( get_boxcar_constant('jagged_increasing_angle'), get_boxcar_constant('jagged_decreasing_angle')) self._add_bc_row_entry(None, 'Jagged Angle:', font_bold, normal_font, force_value=angle_range) else: raise Exception( 'Unable to determine floor_creation_type "{}"'.format( get_boxcar_constant('floor_creation_type'))) self._add_bc_row_entry('car_max_tries', 'Car Max Tries:', font_bold, normal_font) # Chassis Axis chassis_axis_range = '[{:.2f}, {:.2f})'.format( get_boxcar_constant('min_chassis_axis'), get_boxcar_constant('max_chassis_axis')) self._add_bc_row_entry(None, '*Chassis Axis:', font_bold, normal_font, force_value=chassis_axis_range) # Chassis Density chassis_density_range = '[{:.2f}, {:.2f})'.format( get_boxcar_constant('min_chassis_density'), get_boxcar_constant('max_chassis_density')) self._add_bc_row_entry(None, '*Chassis Density:', font_bold, normal_font, force_value=chassis_density_range) # Wheel Density wheel_density_range = '[{:.2f}, {:.2f})'.format( get_boxcar_constant('min_wheel_density'), get_boxcar_constant('max_wheel_density')) self._add_bc_row_entry(None, '*Wheel Density:', font_bold, normal_font, force_value=wheel_density_range) # Num Wheels num_wheels_range = '[{:.2f}, {:.2f}]'.format( get_boxcar_constant('min_num_wheels'), get_boxcar_constant('max_num_wheels')) self._add_bc_row_entry(None, '*Num. Wheels:', font_bold, normal_font, force_value=num_wheels_range) # Wheel Radius wheel_radius_range = '[{:.2f}, {:.2f})'.format( get_boxcar_constant('min_wheel_radius'), get_boxcar_constant('max_wheel_radius')) self._add_bc_row_entry(None, '*Wheel Radius:', font_bold, normal_font, force_value=wheel_radius_range) # Wheel motor wpeed # wheel_motor_speed_range = '[{:.2f}, {:.2f})'.format( # get_boxcar_constant('min_wheel_motor_speed'), # get_boxcar_constant('max_wheel_motor_speed') # ) # self._add_bc_row_entry(None, '*Wheel Speed:', font_bold, normal_font, force_value=wheel_motor_speed_range) self._add_bc_row_entry('gravity', 'Gravity (x, y):', font_bold, normal_font) self._add_bc_row_entry('fps', 'FPS:', font_bold, normal_font) widget = QWidget() widget.setLayout(self.boxcar_form) self.scroll_area = QScrollArea() self.scroll_area.setWidget(widget) self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll_area.setMaximumHeight(250) self.scroll_area.setWidgetResizable(False) vbox = QVBoxLayout() vbox.addWidget(self.scroll_area, 0) self.layout.addLayout(vbox, 0) # @TODO: Adjust this