Example #1
0
    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
Example #2
0
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)
Example #3
0
    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)
Example #4
0
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)
Example #5
0
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
Example #6
0
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))
Example #7
0
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)
Example #8
0
    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()
Example #9
0
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)
Example #10
0
    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
Example #11
0
 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)
Example #12
0
    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)
Example #13
0
    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]
Example #14
0
    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()
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
    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
Example #18
0
    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'))
Example #19
0
        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_())
Example #20
0
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'))
Example #21
0
    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)
Example #22
0
    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