예제 #1
0
def flip_towards(s, target_pos):
    '''
    Takes into account velocity
    '''
    towards_target_dir = normalize(target_pos - s.car.pos)
    desired_vel = towards_target_dir * min(
        MAX_CAR_SPEED * 1.05, s.car.speed + FLIP_SPEED_CHANGE * 1.0)
    acceleration_dir = normalize(desired_vel - s.car.vel)
    return flip_in_direction(s, acceleration_dir)
예제 #2
0
            def pick_offset_target(predicted_slice):
                # pos = ball_pos(predicted_slice)
                # self.target_pos = self.get_target_pos(s)
                # pos += + 50 * UP
                # return pos

                future_ball = Ball(predicted_slice.physics)
                target_ball_pos = s.enemy_goal_center
                to_goal_dir = normalize(z0(target_ball_pos - future_ball.pos))

                # DONE: predict the ball by some small amount.
                # DONE: avoid ball when coming back
                # DONE: hit at an angle to change ball velocity
                desired_ball_speed = MAX_CAR_SPEED
                desired_ball_vel = MAX_CAR_SPEED * to_goal_dir
                desired_ball_vel_change = desired_ball_vel - future_ball.vel
                normal_dir = -normalize(
                    z0(desired_ball_vel))  # center of ball to hit point
                # alignment = dot(normal_dir, normalize(z0(s.car.pos - future_ball.pos)))
                # hit_radius = (0.9 if alignment > 0.7 else 1.5) * BALL_RADIUS
                hit_radius = 0.9 * BALL_RADIUS
                ball_hit_offset = hit_radius * normal_dir
                # ball_hit_offset = -0.8 * BALL_RADIUS * to_goal_dir
                target_pos = future_ball.pos + ball_hit_offset

                # Avoid the ball when coming back
                if dist(s.car.pos, target_ball_pos) < dist(
                        future_ball.pos, target_ball_pos):
                    # TODO: make sure options are in bounds
                    avoid_back_ball_radius = BALL_RADIUS * 5.0
                    options = [
                        future_ball.pos + avoid_back_ball_radius *
                        normalize(Vec3(1, -s.enemy_goal_dir, 0)),
                        future_ball.pos + avoid_back_ball_radius *
                        normalize(Vec3(-1, -s.enemy_goal_dir, 0)),
                    ]
                    best_avoid_option = min(options,
                                            key=lambda avoid_ball_pos: dist(
                                                s.car.pos, avoid_ball_pos))
                    # TODO: factor in current velocity maybe
                    target_pos = best_avoid_option

                target_pos += 10.0 * UP  # fudge
                return target_pos
예제 #3
0
def flip_in_direction(s, target_direction):
    '''
    returns a (pitch, yaw, roll) tuple which will make the car flip in the @target_direction

    Flip testing notes:
    I call it flipping, others call it air-rolling, but I dislike "rolling" in this context as it implies it's slow.
    Flipping will set your vertical speed to 0.
    "flip_forward" is the normalized direction for car.forward projected onto the horizontal plane.
    I define a "front flip" to be the flip where the cars nose turns towards the wheels.
    When front flipping, velocity is added in the flip_forward direction.
    This implies that if you pich your car up slightly more than 90degrees and front flip, you'll gain speed in the direction what used to be your backwards (before pitching up).
    car-orientation-roll does not affect flip_forward.
    '''
    target_direction = normalize(target_direction)
    flip_forward = normalize(z0(s.car.forward))
    flip_right = cross(flip_forward, UP)

    yaw = 0.0
    pitch = -dot(target_direction, flip_forward)
    roll = -dot(target_direction, flip_right)
    # pitch = 0
    return (pitch, yaw, roll)
예제 #4
0
def get_pitch_yaw_roll(car, forward, up=UP):
    """
    Returns a (pitch, yaw, roll) tuple which try to orient the car with the nose pointing
    in the @forward direction and with the roof pointing in the @up direction
    """
    forward = normalize(forward)
    desired_facing_angular_vel = -cross(car.forward, forward)
    desired_up_angular_vel = -cross(car.up, up)

    pitch = dot(desired_facing_angular_vel, car.right)
    yaw = -dot(desired_facing_angular_vel, car.up)
    roll = dot(desired_up_angular_vel, car.forward)

    pitch_vel = dot(car.angular_vel, car.right)
    yaw_vel = -dot(car.angular_vel, car.up)
    roll_vel = dot(car.angular_vel, car.forward)

    # avoid getting stuck in directly-opposite states
    if dot(car.up, up) < -.8 and dot(
            car.forward, forward
    ) > .8:  #abs(roll_vel) < .3 and dot(car.up, up) < -.98 and dot(car.forward, forward) > .8:
        if roll == 0:
            roll = 1
        roll *= 1e10
    if dot(car.forward, forward) < -.8:
        if pitch == 0:
            pitch = 1
        pitch *= 1e10
    # TODO: do this for pitch too. (yaw not required)

    if dot(car.forward, forward) < 0.0:
        pitch_vel *= -1

    # PID control to stop overshooting.
    roll = 3 * roll + 0.30 * roll_vel
    yaw = 3 * yaw + 0.70 * yaw_vel
    pitch = 3 * pitch + 0.90 * pitch_vel

    # only start adjusting roll once we're roughly facing the right way
    if dot(car.forward, forward) < 0:
        roll = 0

    # To debug a single-axis
    # pitch = 0
    # yaw = 0
    # roll = 0
    return (pitch, yaw, roll)
예제 #5
0
    def get_output(self, game_tick_packet: GameTickPacket) -> SimpleControllerState:
        s = EasyGameState(game_tick_packet, self.team, self.index)

        car_obj = game_tick_packet.game_cars[self.index]
        car = car_obj.physics

        # Find the time/position where we should intercept the ball.
        car_y = car.location.y
        to_ball_y = game_tick_packet.game_ball.physics.location.y - car_y
        intercept_y = car_y + copysign(self.AIM_Z_DIST, to_ball_y)
        prediction_struct = self.get_ball_prediction_struct()
        ball_intercept = prediction_struct.slices[0]
        to_enemy_goal_y = 1 if self.team == 0 else -1
        desired_car_y = -5200 * to_enemy_goal_y + self.DESIRED_OFFSET_FROM_OWN_GOAL_Y * to_enemy_goal_y
        self.render_horizontal_line(desired_car_y, self.MIN_HIGH_JUMP_Z)

        for i, next_intercept in zip(range(prediction_struct.num_slices), prediction_struct.slices):
            if i == 0 or i == 1:
                continue
            if (
                abs(next_intercept.physics.location.y - intercept_y) <
                abs(ball_intercept.physics.location.y - intercept_y)
            ):
                ball_intercept = next_intercept
            else:
                # Do not search further: we do not care about backboard bounces which might come closer.
                break
        ball_is_travelling_towards_line = ball_intercept.physics.velocity.y * to_ball_y < 0
        ball_intercept.physics.location.y = intercept_y

        other_defenders = [
            car for car in s.allies if (
                abs(car.pos[TO_ORANGE] - intercept_y) < 5.0 * BALL_RADIUS
                and abs(car.pos[TO_STATUE]) < 1.2*GOAL_CENTER_TO_POST
            )
        ]

        seconds_until_intercept = ball_intercept.game_seconds - game_tick_packet.game_info.seconds_elapsed
        seconds_until_jump = seconds_until_intercept - self.JUMP_BEFORE_INTERCEPT_SECONDS
        seconds_until_doge = seconds_until_intercept - self.DODGE_BEFORE_INTERCEPT_SECONDS

        state = self.State.GROUND
        if game_tick_packet.game_info.is_kickoff_pause and abs(car_y - desired_car_y) > BALL_RADIUS:
            state = self.State.KICKOFF
        elif car_obj.has_wheel_contact:
            if ball_is_travelling_towards_line:
                if abs(ball_intercept.physics.location.x) > GOAL_CENTER_TO_POST:
                    # TODO: Side defence.
                    state = self.State.IDLE
                elif ball_intercept.physics.location.z > self.MAX_HIGH_JUMP_Z:
                    state = self.State.IDLE
                elif ball_intercept.physics.location.z > self.MIN_HIGH_JUMP_Z:
                    if seconds_until_intercept < self.high_jump_before_intercept(ball_intercept.physics.location.z):
                        state = self.State.HIGH_JUMP
                    else:
                        state = self.State.HIGH_JUMP_GROUND
                else:
                    if seconds_until_intercept < self.JUMP_BEFORE_INTERCEPT_SECONDS:
                        state = self.State.JUMPING
                    else:
                        state = self.State.GROUND
            else:
                state = self.State.IDLE
        else:
            if not ball_is_travelling_towards_line:
                state = self.State.IDLE
            elif ball_intercept.physics.location.z > self.MIN_HIGH_JUMP_Z:
                state = self.State.HIGH_JUMP
            else:
                if car.location.z <= self.HEIGHT_BEFORE_DODGING:
                    state = self.State.JUMPING
                elif car_obj.double_jumped:
                    state = self.State.IDLE
                else:
                    state = self.State.DODGING

        # Flip to the ball if convenient
        if state == self.State.IDLE:
            near_future_ball = ball_pos(bisect_ball_prediction(prediction_struct, lambda slice: (
                slice.game_seconds - s.time < self.JUMP_BEFORE_INTERCEPT_SECONDS
            )))
            if (
                    dist(near_future_ball, s.car.pos) < self.MAX_CONVENIENT_JUMP_DISTANCE
                    and near_future_ball[TO_CEILING] < self.MAX_CONVENIENT_JUMP_HEIGHT
                    and abs(near_future_ball[TO_ORANGE]) < abs(s.car.pos[TO_ORANGE])
                    ):
                state = self.State.JUMPING

        # Don't over commit. Rely on team mates to jump for it if they're closer and in line wit the ball.
        if state in [self.State.HIGH_JUMP, self.State.JUMPING, self.State.HIGH_JUMP_GROUND]:
            if any((
                        dist(other.pos, s.ball.pos) < dist(s.ball.pos, s.car.pos)
                         and 0.0 < dot(normalize(s.ball.pos - s.car.pos), other.pos - s.car.pos) < dist(s.ball.pos, s.car.pos)
                    ) for other in other_defenders):
                state = self.State.IDLE

        assert state


        # De-noise state
        self.state_history_counter[self.state_history.popleft()] -= 1
        self.state_history_counter[state] += 1
        self.state_history.append(state)
        state = self.state_history_counter.most_common(1)[0][0]

        if state != self.State.DODGING:
            self.ticks_in_dodge_state = 0
        out = SimpleControllerState()

        def clamp_into_goal(desired_x):
            max_abs_x = GOAL_CENTER_TO_POST - 1.5*BALL_RADIUS
            return clamp(desired_x, -max_abs_x, max_abs_x)
        def forward_adjust(desired_x):
            return (
                # PD-controller
                1.0 * (desired_x - car.location.x) +
                0.3 * (0 - car.velocity.x)
            )

        def avoid_others_x(desired_x):
            for other in other_defenders:
                other_x = clamp_into_goal(other.pos[TO_STATUE])*1.001
                self_x = s.car.pos[TO_STATUE]
                if abs(other_x - desired_x) < abs(self_x - desired_x):
                    desired_x = other_x + copysign(BALL_RADIUS*4.0, self_x-other_x)

                # other_ratio = (desired_x - self_x) / (desired_x - other_x)
                # # if there is a car between us and the desired_x, then don't go through that car.
                # if 0 < other_ratio < 1:
                #     desired_x = other_x + copysign(BALL_RADIUS*4.0, self_x-other_x)
            return desired_x

        if state == self.State.GROUND:
            # Drive to ball_intercept.physics.location.x
            out.throttle = forward_adjust(clamp_into_goal(avoid_others_x(ball_intercept.physics.location.x)))
            out.boost = out.throttle > 200.

        elif state == self.State.JUMPING:
            out.jump = True

        elif state == self.State.DODGING:
            self.ticks_in_dodge_state += 1
            if self.ticks_in_dodge_state < 2:
                out.jump = False
            elif self.ticks_in_dodge_state == 3:
                dodge_point = lerp(
                    struct_vector3_to_numpy(ball_intercept.physics.location),
                    s.ball.pos,
                    .5  # fudge
                )
                out.pitch, out.yaw, out.roll = flip_towards(
                    s,
                    dodge_point
                )
                # out.roll = 1
                # out.pitch = -0.008*forward_adjust(ball_intercept.physics.location.x)
                # length = 1/sqrt(out.roll**2 + out.pitch**2)
                # out.roll *= length
                # out.pitch *= length
                # if out.roll < .4:
                #     # if we really need to go forwards/back, don't go sideways at all
                #      out.roll = 0
                out.jump = True
            else:
                out.jump = False

        elif state == self.State.KICKOFF:
            target_pos = Vec3(copysign(GOAL_CENTER_TO_POST*0.8, s.car.pos[0]), desired_car_y, 0)  # corner boost
            # self.renderer.draw_rect_3d(target_pos, 100, 100, True, self.renderer.pink(), True)
            forward = True
            out.steer = get_steer_towards(s, target_pos)
            out.throttle = -(s.car.pos[TO_ORANGE] - .99 * desired_car_y) + .1 * s.car.vel[TO_ORANGE]

        elif state == self.State.IDLE:
            desired_car_x = 0.4 * (
                    game_tick_packet.game_ball.physics.location.x +
                    .5 * game_tick_packet.game_ball.physics.velocity.x
                )

            # Back and forth to go to goal
            time_ratio = (game_tick_packet.game_info.seconds_elapsed) % 2
            is_close_x = abs(car.location.x - desired_car_x) < 50
            if time_ratio < .5:
                desired_car_x *= -1
            desired_car_x = avoid_others_x(desired_car_x)
            desired_car_x = clamp_into_goal(desired_car_x)
            out.throttle = forward_adjust(desired_car_x)


            # Try to stay aligned with the x axis.
            car_yaw = zero_centered_angle(car.rotation.yaw)
            to_desired_car_y = desired_car_y - car.location.y
            desired_yaw = min(pi/3, max(-pi/3, 0.004 * to_desired_car_y))
            if desired_car_x < car.location.x:
                desired_yaw *= -1

            car_yaw_vel = car.angular_velocity.z
            out.steer = ( # PD-controller
                5.0 * (desired_yaw - car_yaw) +
                0.1 * (0 - car_yaw_vel)
            )
            if out.throttle < 1:
                out.steer *= -1
            if not car_obj.has_wheel_contact:
                desired_forward = normalize(Vec3(
                    1,
                    .001 * (desired_car_y - car.location.y),
                    0
                ))
                out.pitch, out.yaw, out.roll = get_pitch_yaw_roll(
                    s.car,
                    desired_forward,
                )

            # asign stuff just for rendering.
            seconds_until_intercept = 2.5
            ball_intercept.physics.location.x = desired_car_x
            ball_intercept.physics.location.z = 100

        elif state == self.State.HIGH_JUMP:
            z = car.location.z
            out.jump = not (110 < z < 120)
            if car_obj.double_jumped:
                out.pitch = min(1, max(-1, 5*(.9-car.rotation.pitch)))

        elif state == self.State.HIGH_JUMP_GROUND:
            if seconds_until_intercept == 0:
                desired_vel_x = 0  # avoid division by zero
            else:
                desired_vel_x = (ball_intercept.physics.location.x - car.location.x) / seconds_until_intercept
                desired_vel_x *= 1.1
            out.throttle = (desired_vel_x - car.velocity.x)

        else:
            self.logger.warn(f'invalid state {state}')

        self.render_ball_intercept(ball_intercept, seconds_until_intercept, state)

        out.throttle = clamp(out.throttle, -1, 1)
        out.steer = clamp(out.steer, -1, 1)

        out.steer = clamp11(out.steer)
        out.throttle = clamp11(out.throttle)
        out.pitch = clamp11(out.pitch)
        out.yaw = clamp11(out.yaw)
        out.roll = clamp11(out.roll)
        out.jump = bool(out.jump)
        out.boost = bool(out.boost)
        out.handbrake = bool(out.handbrake)
        return out
예제 #6
0
    def get_output(self, packet: GameTickPacket) -> SimpleControllerState:

        s = EasyGameState(packet, self.team, self.index)

        gravity_z = packet.game_info.world_gravity_z
        # trace(mag(s.car.vel))

        # Re plan our trajectory.
        if not s.car.on_ground:
            self.last_time_in_air = s.time
        if s.time - self.last_time_in_air > self.min_time_on_ground and s.time - self.last_replan_time > self.min_replan_period and packet.game_info.is_round_active:
            self.last_replan_time = s.time

            def pick_offset_target(predicted_slice):
                # pos = ball_pos(predicted_slice)
                # self.target_pos = self.get_target_pos(s)
                # pos += + 50 * UP
                # return pos

                future_ball = Ball(predicted_slice.physics)
                target_ball_pos = s.enemy_goal_center
                to_goal_dir = normalize(z0(target_ball_pos - future_ball.pos))

                # DONE: predict the ball by some small amount.
                # DONE: avoid ball when coming back
                # DONE: hit at an angle to change ball velocity
                desired_ball_speed = MAX_CAR_SPEED
                desired_ball_vel = MAX_CAR_SPEED * to_goal_dir
                desired_ball_vel_change = desired_ball_vel - future_ball.vel
                normal_dir = -normalize(
                    z0(desired_ball_vel))  # center of ball to hit point
                # alignment = dot(normal_dir, normalize(z0(s.car.pos - future_ball.pos)))
                # hit_radius = (0.9 if alignment > 0.7 else 1.5) * BALL_RADIUS
                hit_radius = 0.9 * BALL_RADIUS
                ball_hit_offset = hit_radius * normal_dir
                # ball_hit_offset = -0.8 * BALL_RADIUS * to_goal_dir
                target_pos = future_ball.pos + ball_hit_offset

                # Avoid the ball when coming back
                if dist(s.car.pos, target_ball_pos) < dist(
                        future_ball.pos, target_ball_pos):
                    # TODO: make sure options are in bounds
                    avoid_back_ball_radius = BALL_RADIUS * 5.0
                    options = [
                        future_ball.pos + avoid_back_ball_radius *
                        normalize(Vec3(1, -s.enemy_goal_dir, 0)),
                        future_ball.pos + avoid_back_ball_radius *
                        normalize(Vec3(-1, -s.enemy_goal_dir, 0)),
                    ]
                    best_avoid_option = min(options,
                                            key=lambda avoid_ball_pos: dist(
                                                s.car.pos, avoid_ball_pos))
                    # TODO: factor in current velocity maybe
                    target_pos = best_avoid_option

                target_pos += 10.0 * UP  # fudge
                return target_pos

            time_offset = 0.1
            predicted_slice = bisect_ball_prediction(
                self.get_ball_prediction_struct(), lambda slice: mag(
                    get_cannon_vel(s.car.pos, pick_offset_target(
                        slice), slice.game_seconds - s.time - time_offset, s.
                                   gravity_z)) > MAX_CAR_SPEED)
            vel = get_cannon_vel(s.car.pos,
                                 pick_offset_target(predicted_slice),
                                 predicted_slice.game_seconds - s.time,
                                 s.gravity_z)
            # if mag(vel) > MAX_CAR_SPEED:
            #     print(f'cannon was not powerful enough: {mag(vel)} > {MAX_CAR_SPEED}')
            self.set_game_state(
                GameState(cars={
                    self.index:
                    CarState(physics=Physics(
                        location=Vector3(*s.car.pos),
                        rotation=Rotator(
                            acos(mag(z0(vel)) / mag(vel)),
                            atan2(vel[1], vel[0]),
                            0,
                        ),
                        velocity=Vector3(*vel),
                        angular_velocity=Vector3(*(vel)),
                    ),
                             boost_amount=100)
                }, ))

        out = self.controller_state
        forward = s.car.vel + Vec3(0, 0, 0.1 * gravity_z)
        time_to_touchdown = max(solve_quadratic(.5 * gravity_z,
                                                s.car.vel[TO_CEILING],
                                                s.car.pos[TO_CEILING]),
                                default=0)
        is_landing = time_to_touchdown < self.prepare_to_land_period or (
            s.car.pos[TO_CEILING] < 100 and not s.car.vel[TO_CEILING] > 100)
        wall_sign = (-1 if s.car.pos[TO_STATUE] < 0 else 1)
        wall = 4096 * wall_sign
        if s.car.vel[TO_STATUE] == 0:
            time_to_wall = 10000
        else:
            time_to_wall = (wall - s.car.pos[TO_STATUE]) / s.car.vel[TO_STATUE]

        if time_to_wall < 0:
            time_to_wall = 10000
        time_to_ceiling = min([
            t for t in solve_quadratic(.5 * gravity_z, s.car.vel[TO_CEILING],
                                       s.car.pos[TO_CEILING] - 2000) if t > 0
        ],
                              default=10000)

        if time_to_ceiling < self.prepare_to_land_period or s.car.pos[
                TO_CEILING] > 1900:
            out.pitch, out.yaw, out.roll = get_pitch_yaw_roll(
                s.car,
                z0(forward),
                -UP,
            )
        elif time_to_wall < self.prepare_to_land_period:
            out.pitch, out.yaw, out.roll = get_pitch_yaw_roll(
                s.car,
                Vec3(0, forward[TO_ORANGE], forward[TO_CEILING]),
                Vec3(-wall_sign, 0, 0),
            )
        elif is_landing:
            out.pitch, out.yaw, out.roll = get_pitch_yaw_roll(
                s.car,
                z0(forward),
                UP,
            )
        else:
            out.pitch, out.yaw, out.roll = get_pitch_yaw_roll(
                s.car,
                forward,
                normalize(lerp(s.car.up, cross(forward, s.car.up), 0.1)),
            )
        out.boost = False
        out.throttle = 0
        out.throttle = 1 if is_landing else 0

        # Sanitize our output
        out.steer = clamp11(out.steer)
        out.throttle = clamp11(out.throttle)
        out.pitch = clamp11(out.pitch)
        out.yaw = clamp11(out.yaw)
        out.roll = clamp11(out.roll)
        out.jump = bool(out.jump)
        out.boost = bool(out.boost)
        out.handbrake = bool(out.handbrake)
        return out