예제 #1
0
 def __init__(self, agent: BeepBoop, arrival_delay: float = 0):
     super().__init__(agent)
     self.arrival_delay: float = arrival_delay
     self.controller: SimpleControllerState = SimpleControllerState()
     self.bot_vel: Vector3 = Vector3(0, 0, 0)
     self.bot_yaw: float = 0
     self.bot_pos: Vector3 = Vector3(0, 0, 0)
예제 #2
0
def line_line_intersection(a1: Vector3, a2: Vector3, b1: Vector3,
                           b2: Vector3) -> Vector3:
    """
    Determines the intersection point of line a1a2 and line b1b2.
    Both lines are assumed to be infinitely long.

    :param a1: Starting point of line 1
    :param a2: Ending point of line 1
    :param b1: Starting point of line 2
    :param b2: Ending point of line 2
    :return: The intersection point of line 1 and line 2
    """

    # From https://stackoverflow.com/a/20677983/7245441

    def det(a: Vector3, b: Vector3) -> float:
        return a.x * b.y - a.y * b.x

    y_diff = Vector3(a1.y - a2.y, b1.y - b2.y, 0)
    x_diff = Vector3(a1.x - a2.x, b1.x - b2.x, 0)

    div = det(x_diff, y_diff)
    if div == 0:
        raise Exception("Lines do not intersect")

    d = Vector3(det(a1, a2), det(b1, b2), 0)
    x = det(d, x_diff) / div
    y = det(d, y_diff) / div

    return Vector3(x, y, 0)
예제 #3
0
    def get_output(
            self,
            packet: GameTickPacket) -> Union[SimpleControllerState, None]:
        position: Vector3 = Vector3(
            packet.game_cars[self.agent.index].physics.location)
        yaw: float = packet.game_cars[self.agent.index].physics.rotation.yaw

        while True:
            if len(self.path.path) == 0:
                return None

            if Vector3.distance(position, self.path.path[0]) < 300:
                self.path.remove_points_to_index(0)
            else:
                break

        next_point: Vector3 = self.path.path[0]

        self.path.draw_path()

        steer: float = gosling_steering(position, yaw, next_point)
        handbrake: bool = calculations.angle_to_target(position, yaw,
                                                       next_point) > 1.75
        boost: bool = calculations.angle_to_target(position, yaw,
                                                   next_point) < 0.35
        return SimpleControllerState(steer,
                                     1,
                                     boost=boost,
                                     handbrake=handbrake)
예제 #4
0
def simple_aim(position: Vector3, yaw: float, target: Vector3) -> float:
    """Bang bang controller for steering."""
    pos_to_target: Vector3 = target - position
    facing: Vector3 = Vector3(math.cos(yaw), math.sin(yaw), 0)
    self_right: Vector3 = Vector3.cross_product(facing, Vector3(0, 0, 1))

    if Vector3.dot_product(self_right, pos_to_target) < 0:
        return 1.0
    else:
        return -1.0
예제 #5
0
    def closest_point(points: List[Slice],
                      position: Vector3) -> Optional[Slice]:
        closest: Optional[Slice] = None
        closest_dist: float = float("inf")
        for point in points:
            dist = Vector3.distance(position, Vector3(point.physics.location))
            if dist < closest_dist:
                closest = point
                closest_dist = dist

        return closest
예제 #6
0
 def closest_boost(player_pos: Vector3, boost_pads: BoostList,
                   boost_pad_states: BoostStateList) -> Optional[Vector3]:
     closest: Optional[Vector3] = None
     closest_dist: float = float("inf")
     for i in range(len(boost_pads)):
         if boost_pads[i].is_full_boost and boost_pad_states[i].is_active:
             pad: Vector3 = Vector3(boost_pads[i].location)
             current_dist: float = Vector3.distance(player_pos, pad)
             if current_dist < closest_dist:
                 closest_dist = current_dist
                 closest = pad
     return closest
예제 #7
0
    def get_closest_small_pad(self, bot_pos: Vector3) -> BoostPad:
        closest_pad: BoostPad = None
        dist_to_closest_pad: float = float("inf")

        for boost_pad in self.agent.get_field_info().boost_pads:
            current_pad: BoostPad = boost_pad
            current_pos: Vector3 = Vector3(current_pad.location)
            dist_to_current_pad = Vector3.distance(bot_pos, current_pos)

            if not current_pad.is_full_boost and dist_to_current_pad < dist_to_closest_pad:
                dist_to_closest_pad = dist_to_current_pad
                closest_pad = current_pad

        return closest_pad
예제 #8
0
    def choose_step(self, packet: GameTickPacket) -> BaseStep:
        ball = Vector3(packet.game_ball.physics.location)
        bot = Vector3(packet.game_cars[self.agent.index].physics.location)
        own_goal: Vector3 = Vector3(self.agent.get_field_info().goals[self.agent.team].location)

        if ball.x == 0 and ball.y == 0:
            return KickoffStep(self.agent)
        elif abs(bot.y) > 5300 and abs(bot.x) < 8000:
            return EscapeGoalStep(self.agent)
        elif ball_prediction.get_ball_in_net(self.agent.get_ball_prediction_struct(), own_goal.y) is not None:
            return SaveGoalStep(self.agent)
        elif (bot.y + 200 < ball.y) if self.agent.team else (bot.y - 200 > ball.y):
            # Go to bot's own goal if the ball is in between the bot and the bot's own goal
            return SimpleMoveStep(self.agent, own_goal)
        else:
            return ShotStep(self.agent)
예제 #9
0
    def get_output(self,
                   packet: GameTickPacket) -> Optional[SimpleControllerState]:
        goal: Vector3 = Vector3(
            self.agent.get_field_info().goals[self.agent.team].location)
        ball_prediction: BallPrediction = self.agent.get_ball_prediction_struct(
        )
        ball_in_net: Optional[Slice] = get_ball_in_net(ball_prediction, goal.y)
        if ball_in_net is None:
            self.cancellable = True
            return None

        self.agent.game_info.read_packet(packet)

        if self.current_action is None:
            # Figure out what action to take (aerial, dribble, or kicking the ball away)
            ground_bounces: List[Slice] = get_ground_bounces(
                self.agent.get_ball_prediction_struct())
            ground_bounces_filtered: List[Slice] = self.bounces_filter(
                ground_bounces, ball_in_net.game_seconds, MIN_Z_VEL)

            if len(ground_bounces_filtered) > 0:
                self.current_action = SimpleDribbleStep(self.agent)
            else:
                self.current_action = HitAwayFromGoalStep(self.agent)

        return self.current_action.get_output(packet)
예제 #10
0
    def _generate_path_with_ball_position(self, packet: GameTickPacket,
                                          ball: Vector3) -> List[Vector3]:
        car = Vector3(packet.game_cars[self.agent.index].physics.location)

        if pathing.in_shooting_cone(car, ball,
                                    -1 * calculations.sign(self.agent.team)):
            # Car is in the shooting cone, so make a straight path
            self.path = pathing.linear_bezier(car, ball)
        else:
            # If the direction of the car and the desired ball direction are pointing to different sides of the line
            # between the car and the ball, use a quadratic bezier. Else use a cubic bezier.

            yaw: float = packet.game_cars[
                self.agent.index].physics.rotation.yaw
            car_dir: Vector3 = Vector3(math.cos(yaw), math.sin(yaw), 0)
            desired_ball_dir: Vector3 = Vector3(
                self.agent.game_info.their_goal.center).modified(
                    z=ball.z) - ball
            car_to_ball: Vector3 = ball - car

            car_dir_right_of_line: bool = Vector3.dot_product(
                car_to_ball, car_dir.modified(y=-car_dir.y)) > 0
            ball_dir_right_of_line: bool = Vector3.dot_product(
                car_to_ball,
                desired_ball_dir.modified(y=-desired_ball_dir.y)) > 0

            if car_dir_right_of_line != ball_dir_right_of_line:
                # Quadratic bezier curve
                # Find intersection of the car direction and the desired ball direction for intermediate point of bezier curve.
                intermediate: Vector3 = calculations.line_line_intersection(
                    car, car + car_dir, ball, desired_ball_dir)
                self.path = pathing.quadratic_bezier(car, intermediate, ball)

                color = self.agent.renderer.red(
                ) if self.agent.team else self.agent.renderer.cyan()
                self.agent.renderer.draw_line_3d(car, intermediate, color)
                self.agent.renderer.draw_line_3d(intermediate, ball, color)
                self.agent.renderer.draw_string_3d(car, 1, 1, "P0", color)
                self.agent.renderer.draw_string_3d(intermediate, 1, 1, "P1",
                                                   color)
                self.agent.renderer.draw_string_3d(ball, 1, 1, "P2", color)
            else:
                # Cubic bezier
                # TODO: Implement cubic bezier path. Using linear curve in place of cubic bezier for now.
                self.path = pathing.linear_bezier(car, ball)

        return self.path
예제 #11
0
    def get_output(self, packet: GameTickPacket) -> SimpleControllerState:
        bot_rot = Vector3(packet.game_cars[self.agent.index].physics.rotation)
        self.bot_yaw = math.degrees(bot_rot.z)
        self.bot_vel = Vector3(
            packet.game_cars[self.agent.index].physics.velocity)
        self.bot_pos = Vector3(
            packet.game_cars[self.agent.index].physics.location)
        self.agent.game_info.read_packet(packet)

        bounce: Slice = ball_prediction.get_ground_bounces(
            self.agent.get_ball_prediction_struct())[0]
        self.arrive_on_time(
            Vector3(bounce.physics.location),
            bounce.game_seconds - packet.game_info.seconds_elapsed)
        self.aim(self.angle_to_target(Vector3(bounce.physics.location)))

        return self.controller
예제 #12
0
 def distance(self) -> float:
     """
     Returns the distance of the path.
     """
     dist = 0
     for i in range(len(self.path) - 1):
         dist += Vector3.distance(self.path[i], self.path[i + 1])
     return dist
예제 #13
0
    def closest_point_on_path(self) -> Vector3:
        """
        Determines the point on the path that the agent is closest to.

        :return: Point on the path that the agent is closest to
        """

        closest_dist: float = float("infinity")
        closest_point: Optional[Vector3] = None
        position: Vector3 = Vector3(self.agent.game_info.my_car.pos)

        for point in self.path:
            current_dist: float = Vector3.distance(point, position)
            if current_dist < closest_dist:
                closest_dist = current_dist
                closest_point = point

        if closest_point is None:
            raise Exception(
                "Closest point was None. self.path could be empty.")
        return closest_point
예제 #14
0
def get_ground_bounces(path: BallPrediction) -> List[Slice]:
    bounces: List[Slice] = []

    for i in range(1, path.num_slices):
        prev_ang_vel: Vector3 = Vector3(path.slices[i - 1].physics.angular_velocity)

        # Make sure it's never (0, 0, 0) to avoid ZeroDivisionError
        prev_ang_vel_normalised: Vector3 = Vector3(1, 1, 1)
        if prev_ang_vel != Vector3(0, 0, 0):
            prev_ang_vel_normalised = prev_ang_vel.normalised()

        current_slice: Slice = path.slices[i]
        current_ang_vel: Vector3 = Vector3(current_slice.physics.angular_velocity)

        # Make sure it's never (0, 0, 0) to avoid ZeroDivisionError
        current_ang_vel_normalised: Vector3 = Vector3(1, 1, 1)
        if current_ang_vel != Vector3(0, 0, 0):
            current_ang_vel_normalised: Vector3 = current_ang_vel.normalised()

        # Ball's angular velocity does not change in air; it only changes when bouncing.
        # Therefore, if the ball changes angular velocity between frames and the height is low, a bounce occurred.
        if prev_ang_vel_normalised != current_ang_vel_normalised and current_slice.physics.location.z < 125:
            bounces.append(current_slice)

    return bounces
예제 #15
0
    def get_output(self, packet: GameTickPacket) -> SimpleControllerState:
        bot = PhysicsObject(packet.game_cars[self.agent.index].physics)
        goal_location = Vector3(
            self.agent.get_field_info().goals[self.agent.team].location)
        goal_location.y = abs(goal_location.y) * math.copysign(
            1, bot.location.y)

        controller: SimpleControllerState = SimpleControllerState()
        controller.steer = gosling_steering(bot.location, bot.rotation.z,
                                            goal_location)
        controller.throttle = 1

        return controller
예제 #16
0
    def get_output(self, packet: GameTickPacket) -> SimpleControllerState:
        out: Optional[SimpleControllerState] = self.current_step.get_output(packet)

        if not self.current_step.cancellable and out is not None:
            return out

        self.current_step = self.choose_step(packet)
        out = self.current_step.get_output(packet)

        if out is not None:
            return out
        else:
            self.agent.logger.warning(f"Agent {self.agent.name} is returning an empty controller on the first frame of the step")
            return SimpleMoveStep(self.agent, Vector3(packet.game_ball.physics.location)).get_output(packet)
예제 #17
0
    def get_output(self, packet: GameTickPacket) -> SimpleControllerState:
        # Hit the ball blindly if the bot is between the ball and its own goal. This is to clear it away.
        # Hit the ball to the side if the ball is closer to its own goal than it is.

        self.agent.game_info.read_packet(packet)

        if self.dodge is not None:
            self.dodge.step(1 / 60)
            if not self.dodge.finished:
                return self.dodge.controls
            else:
                self.dodge = None

        controller: SimpleControllerState = SimpleControllerState()

        ball: Vector3 = Vector3(packet.game_ball.physics.location)
        bot: PhysicsObject = PhysicsObject(
            packet.game_cars[self.agent.index].physics)

        target: Vector3
        if abs(ball.y) < abs(bot.location.y):
            target = ball
        else:
            offset: float = 100 if ball.x > bot.location.x else -100
            offset *= -1 if self.agent.team == 1 else 1
            target = ball + Vector3(offset, 0, 0)
            bot_to_ball: Vector3 = ball - bot.location
            if abs(bot_to_ball.normalised().x) > 0.7:
                self.dodge = AirDodge(self.agent.game_info.my_car, 0.1,
                                      vec3(ball.x, ball.y, ball.z))

        controller.steer = gosling_steering(bot.location, bot.rotation.z,
                                            target)
        controller.boost = True
        controller.throttle = 1

        return controller
예제 #18
0
def closest_point(p1: Vector3, p2: Vector3, p3: Vector3) -> Vector3:
    """
    Determines the point closest to p3 on the line p1p2.

    :param p1: Starting point of the line
    :param p2: Ending point of the line
    :param p3: Point outside the line
    :return: Point closest to p3 on the line p1p2
    """
    k = ((p2.y - p1.y) * (p3.x - p1.x) - (p2.x - p1.x) *
         (p3.y - p1.y)) / ((p2.y - p1.y)**2 + (p2.x - p1.x)**2)
    x4 = p3.x - k * (p2.y - p1.y)
    y4 = p3.y + k * (p2.x - p1.x)

    return Vector3(x4, y4, 0)
예제 #19
0
    def prepare_kickoff(self, packet: GameTickPacket) -> None:
        bot_pos = Vector3(packet.game_cars[self.agent.index].physics.location)

        # Centre kickoff
        if abs(bot_pos.x) < 250:
            pad = self.get_closest_small_pad(bot_pos).location
            first_target: vec3 = vec3(
                pad.x, pad.y,
                pad.z) - vec3(0, 250, 0) * (1 if self.agent.team else -1)
            second_target: vec3 = vec3(0, 850,
                                       0) * (1 if self.agent.team else -1)

            self.kickoff_steps = [
                Drive(self.agent.game_info.my_car, first_target, 2400),
                AirDodge(self.agent.game_info.my_car, 0.075, vec3(0, 0, 0)),
                Drive(self.agent.game_info.my_car, second_target, 2400),
                AirDodge(self.agent.game_info.my_car, 0.075, vec3(0, 0, 0))
            ]
        # Off-centre kickoff
        elif abs(bot_pos.x) < 1000:
            target: vec3 = normalize(self.agent.game_info.my_car.pos) * 500

            self.kickoff_steps = [
                Drive(
                    self.agent.game_info.my_car,
                    vec3(self.agent.game_info.my_car.pos[0],
                         3477 * (1 if self.agent.team else -1), 0), 2400),
                AirDodge(self.agent.game_info.my_car, 0.075, vec3(0, 0, 0)),
                Drive(self.agent.game_info.my_car, target, 2400),
                AirDodge(self.agent.game_info.my_car, 0.075, vec3(0, 0, 0))
            ]
        # Diagonal kickoff
        else:
            pad = self.get_closest_small_pad(bot_pos).location
            car_to_pad: vec3 = vec3(pad.x, pad.y,
                                    pad.z) - self.agent.game_info.my_car.pos
            first_target: vec3 = self.agent.game_info.my_car.pos + 1.425 * car_to_pad
            second_target: vec3 = vec3(0, 150,
                                       0) * (1 if self.agent.team else -1)
            third_target: vec3 = normalize(
                self.agent.game_info.my_car.pos) * 850

            self.kickoff_steps = [
                Drive(self.agent.game_info.my_car, first_target, 2300),
                AirDodge(self.agent.game_info.my_car, 0.035, second_target),
                Drive(self.agent.game_info.my_car, third_target, 2400),
                AirDodge(self.agent.game_info.my_car, 0.1, vec3(0, 0, 0))
            ]
예제 #20
0
    def get_output(self,
                   packet: GameTickPacket) -> Optional[SimpleControllerState]:
        position: Vector3 = Vector3(
            packet.game_cars[self.agent.index].physics.location)
        yaw: float = packet.game_cars[self.agent.index].physics.rotation.yaw
        boost_pads: BoostList = self.agent.get_field_info().boost_pads
        boost_pad_states: BoostStateList = packet.game_boosts

        if self.boost_location is None:
            closest: Optional[Vector3] = self.closest_boost(
                position, boost_pads, boost_pad_states)
            if closest is None:
                return None
            self.boost_location = closest

        steer: float = gosling_steering(position, yaw, self.boost_location)
        handbrake: bool = calculations.angle_to_target(
            position, yaw, self.boost_location) > 1.75
        boost: bool = calculations.angle_to_target(position, yaw,
                                                   self.boost_location) < 0.35
        return SimpleControllerState(steer,
                                     1,
                                     boost=boost,
                                     handbrake=handbrake)
예제 #21
0
 def __init__(self, physics: game_data_struct.Physics):
     self.location: Vector3 = Vector3(physics.location)
     self.velocity: Vector3 = Vector3(physics.velocity)
     self.rotation: Vector3 = Vector3(physics.rotation)
     self.angular_velocity: Vector3 = Vector3(physics.angular_velocity)
예제 #22
0
 def generate_path(self, packet: GameTickPacket) -> List[Vector3]:
     # TODO: Use future ball predictions and return the first viable one, instead of using the current ball position
     ball = Vector3(packet.game_ball.physics.location)
     return self._generate_path_with_ball_position(packet, ball)