def estimate_time(car: Car, target, dd=1) -> float: turning_radius = 1 / Drive.max_turning_curvature(norm(car.velocity) + 500) turning = angle_between(car.forward() * dd, direction( car, target)) * turning_radius / 1800 if turning < 0.5: turning = 0 dist = ground_distance(car, target) - 200 if dist < 0: return turning speed = dot(car.velocity, car.forward()) time = 0 result = None if car.boost > 0 and dd > 0: boost_time = car.boost / 33.33 result = BOOST.simulate_until_limit(speed, distance_limit=dist, time_limit=boost_time) dist -= result.distance_traveled time += result.time_passed speed = result.speed_reached if dist > 0 and speed < 1410: result = THROTTLE.simulate_until_limit(speed, distance_limit=dist) dist -= result.distance_traveled time += result.time_passed speed = result.speed_reached if result is None or not result.distance_limit_reached: time += dist / speed return time * 1.05 + turning
def estimate_time(car: Car, target, speed, dd=1) -> float: dist = distance(car, target) if dist < 100: return 0 travel = dist / speed turning = angle_between(car.forward() * dd, direction(car, target)) / math.pi * 2 if turning < 1: turning **= 2 acceleration = (speed * dd - dot(car.velocity, car.forward())) / 2100 * 0.2 * dd / max(car.boost / 20, 1) return travel + acceleration + turning * 0.7
def toPoint(car: Car, target: vec3): direction = normalize(target - car.position) turnAngleAmount = math.acos( max(-1, min(1, dot(car.forward(), direction)))) rotateToZeroAngle = -atan2(direction) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle) turnDirection = math.copysign(1, -towardsZeroVector[1]) return PID.fromAngle(car, turnAngleAmount * turnDirection)
def toPointReverse(car: Car, target: vec3): direction = normalize(target - car.position) turnAngleAmount = math.acos( max(-1, min(1, -dot(car.forward(), direction)))) rotateToZeroAngle = -atan2(direction) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle + math.pi) turnDirection = math.copysign(1, towardsZeroVector[1]) ANGLETERM = 3.5 ANGULARTERM = -0.3 angleP = turnDirection * min(2, ANGLETERM * turnAngleAmount) angleD = ANGULARTERM * car.angular_velocity[2] return min(1, max(-1, angleP + angleD))
def align(car: Car, target: vec3, direction: vec3): turnAngleAmount = math.acos(dot(car.forward(), direction)) rotateToZeroAngle = -atan2(direction) posOffset = rotate2(target - car.position, rotateToZeroAngle) towardsZeroVector = rotate2(car.forward(), rotateToZeroAngle) turnDirection = math.copysign(1, -towardsZeroVector[1]) turnRadius = 1 / RLUDrive.max_turning_curvature(norm(car.velocity)) ANGLETERM = 10 ANGULARTERM = -0.3 CORRECTIONTERM = 1 / 40 angleP = turnDirection * min(2, ANGLETERM * turnAngleAmount) angleD = ANGULARTERM * car.angular_velocity[2] finalOffset = posOffset[1] + turnDirection * turnRadius / .85 * ( 1 - math.cos(turnAngleAmount)) offsetCorrection = CORRECTIONTERM * finalOffset return min(1, max(-1, angleP + angleD + offsetCorrection))
def __init__(self, car: Car, use_boost=False): self.car = car self.use_boost = use_boost self.controls = Input() self.dodge = Dodge(car) self.dodge.duration = 0.12 self.dodge.direction = vec2(car.forward() * (-1)) self.s = 0.95 * sgn( dot(self.car.angular_velocity, self.car.up()) + 0.01) self.timer = 0.0 self.finished = False
def get_controls(self, car_state: CarState, car: Car): controls = SimpleControllerState() target_Vec3 = Vec3(self.location[0], self.location[1], self.location[2]) if angle_between(self.location - to_vec3(car_state.physics.location), car.forward()) > pi / 2: controls.boost = False controls.handbrake = True elif angle_between(self.location - to_vec3(car_state.physics.location), car.forward()) > pi / 4: controls.boost = False controls.handbrake = False else: controls.boost = self.boost controls.handbrake = False # Be smart about not using boost at max speed # if Vec3(car.physics.velocity).length() > self.boost_analysis.frames[-1].speed - 10: # controls.boost = False controls.steer = steer_toward_target(car_state, target_Vec3) controls.throttle = 1 return controls
def __init__(self, car: Car, target: vec3 = vec3(0, 0, 0), waste_boost=False): super().__init__(car) self.target = Arena.clamp(ground(target), 100) self.waste_boost = waste_boost self.finish_distance = 500 self._time_on_ground = 0 self.driving = True # decide whether to start driving backwards and halfflip later forward_estimate = estimate_time(car, self.target) backwards_estimate = estimate_time(car, self.target, -1) + 0.5 backwards = ( dot(car.velocity, car.forward()) < 500 and backwards_estimate < forward_estimate and (distance(car, self.target) > 3000 or distance(car, self.target) < 300) and car.position[2] < 200 ) self.drive = Drive(car, self.target, 2300, backwards) self.action = self.drive
def calculate(car: Car, ball: Ball, target: vec3, ball_predictions=None): # Init vars b = Ball(ball) dt = 1.0 / 60.0 # Generate predictions of ball path if ball_predictions is None: ball_predictions = [] for i in range(60 * 5): b.step(dt) ball_predictions.append(vec3(b.location)) # Gradually converge on ball location by aiming at a location, checking time to that location, # and then aiming at the ball's NEW position. Guaranteed to converge (typically in <10 iterations) # unless the ball is moving away from the car faster than the car's max boost speed intercept = Intercept(b.location) intercept.purpose = 'ball' intercept.boost = True intercept_ball_position = vec3(b.location) collision_achieved = False last_horizontal_error = None last_horizontal_offset = None i = 0 max_tries = 101 analyzer = BoostAnalysis() if intercept.boost else ThrottleAnalysis() while i < max_tries: i += 1 fake_car = Car(car) direction = normalize(intercept.location - car.location) fake_car.rotation = look_at(direction, fake_car.up()) for t in range(60 * 5): # Step car location with throttle/boost analysis data # Not super efficient but POITROAE frame = analyzer.travel_time(dt, norm(fake_car.velocity)) # print('in 1 frame I travel', frame.time, frame.distance, frame.speed) fake_car.location += direction * frame.distance fake_car.velocity = direction * frame.speed fake_car.time += dt ball_location = ball_predictions[t] # Check for collision p = closest_point_on_obb(fake_car.hitbox(), ball_location) if norm(p - ball_location) <= ball.collision_radius: direction_vector = p - (fake_car.location - normalize( fake_car.forward()) * 13.88) # octane center of mass direction_vector[2] = 0 target_direction_vector = target - ball_location target_direction_vector[2] = 0 intercept_ball_position = ball_location direction = atan2(direction_vector[1], direction_vector[0]) ideal_direction = atan2(target_direction_vector[1], target_direction_vector[0]) horizontal_error = direction - ideal_direction # intercept.location = vec3(ball_location) # intercept.time = fake_car.time # return intercept # Now descend the hit direction gradient # Kick off the gradient descent with an arbitrary seed value if last_horizontal_error is None: last_horizontal_error = horizontal_error last_horizontal_offset = 0 if horizontal_error > 0: horizontal_offset = 25 else: horizontal_offset = 25 intercept.location = ball_location - normalize( fake_car.left()) * horizontal_offset break # Recursive case of gradient descent if horizontal_offset == last_horizontal_offset: gradient = 0 else: gradient = (horizontal_error - last_horizontal_error ) / (horizontal_offset - last_horizontal_offset) if gradient == 0: predicted_horizontal_offset = horizontal_offset else: predicted_horizontal_offset = horizontal_offset - horizontal_error / gradient # Base case (convergence) if abs(gradient) < 0.0005: print(f'convergence in {i} iterations') print(f'gradient = {gradient}') print( f'last_horizontal_offset = {last_horizontal_offset}' ) print(f'direction = {degrees(direction)}') print(f'ideal direction = {degrees(ideal_direction)}') print(f'target = {target}') print(f'ball_location = {ball_location}') return intercept # Edge case exit: offset maxed out max_horizontal_offset = car.hitbox( ).half_width[1] + ball.collision_radius if predicted_horizontal_offset > max_horizontal_offset: predicted_horizontal_offset = max_horizontal_offset elif predicted_horizontal_offset < -max_horizontal_offset: predicted_horizontal_offset = -max_horizontal_offset last_horizontal_offset = horizontal_offset last_horizontal_error = horizontal_error horizontal_offset = predicted_horizontal_offset # Return the latest intercept location and continue descending the gradient intercept.location = ball_location - normalize( fake_car.left()) * predicted_horizontal_offset print(f'iteration {i}') print(f'gradient = {gradient}') print(f'horizontal_offset = {horizontal_offset}') print(f'horizontal_error = {degrees(horizontal_error)}') # print(f'ideal direction = {degrees(ideal_direction)}') break # Check for arrival if norm(fake_car.location - intercept.location) < ball.collision_radius / 2: intercept.location = ball_location break if i >= max_tries: print( f'Warning: max tries ({max_tries}) exceeded for calculating intercept' ) return intercept
def get_car_front_center(car: Car): return car.location + normalize(car.forward()) * car.hitbox( ).half_width[0] + normalize(car.up()) * car.hitbox().half_width[2]
def angle_to(car: Car, target: vec3, backwards=False) -> float: return abs(angle_between(xy(car.forward()) * (-1 if backwards else 1), ground_direction(car.position, target)))
def simulate(self, global_target=None): lol = 0 # Initialize the ball prediction # Estimate the probable duration of the jump and round it down to the floor decimal ball_prediction = self.get_ball_prediction_struct() if self.info.my_car.boost < 6: duration_estimate = math.floor( get_time_at_height(self.info.ball.position[2]) * 10) / 10 else: adjacent = norm( vec2(self.info.my_car.position - self.info.ball.position)) opposite = (self.info.ball.position[2] - self.info.my_car.position[2]) theta = math.atan(opposite / adjacent) t = get_time_at_height_boost(self.info.ball.position[2], theta, self.info.my_car.boost) duration_estimate = (math.ceil(t * 10) / 10) # Loop for 6 frames meaning adding 0.1 to the estimated duration. Keeps the time constraint under 0.3s for i in range(6): # Copy the car object and reset the values for the hitbox car = Car(self.info.my_car) # Create a dodge object on the copied car object # Direction is from the ball to the enemy goal # Duration is estimated duration plus the time added by the for loop # preorientation is the rotation matrix from the ball to the goal # TODO make it work on both sides # Test with preorientation. Currently it still picks a low duration at a later time meaning it # wont do any of the preorientation. dodge = Dodge(car) prediction_slice = ball_prediction.slices[round( 60 * (duration_estimate + i / 60))] physics = prediction_slice.physics ball_location = vec3(physics.location.x, physics.location.y, physics.location.z) # ball_location = vec3(0, ball_y, ball_z) dodge.duration = duration_estimate + i / 60 if dodge.duration > 1.4: break if global_target is not None: dodge.direction = vec2(global_target - ball_location) target = vec3(vec2(global_target)) + vec3( 0, 0, jeroens_magic_number * ball_location[2]) dodge.preorientation = look_at(target - ball_location, vec3(0, 0, 1)) else: dodge.target = ball_location dodge.direction = vec2(ball_location) + vec2(ball_location - car.position) dodge.preorientation = look_at(ball_location, vec3(0, 0, 1)) # Loop from now till the end of the duration fps = 30 for j in range(round(fps * dodge.duration)): lol = lol + 1 # Get the ball prediction slice at this time and convert the location to RLU vec3 prediction_slice = ball_prediction.slices[round(60 * j / fps)] physics = prediction_slice.physics ball_location = vec3(physics.location.x, physics.location.y, physics.location.z) dodge.step(1 / fps) T = dodge.duration - dodge.timer if T > 0: if dodge.timer < 0.2: dodge.controls.boost = 1 dodge.controls.pitch = 1 else: xf = car.position + 0.5 * T * T * vec3( 0, 0, -650) + T * car.velocity delta_x = ball_location - xf if angle_between(vec2(car.forward()), dodge.direction) < 0.3: if norm(delta_x) > 50: dodge.controls.boost = 1 dodge.controls.throttle = 0.0 else: dodge.controls.boost = 0 dodge.controls.throttle = clip( 0.5 * (200 / 3) * T * T, 0.0, 1.0) else: dodge.controls.boost = 0 dodge.controls.throttle = 0.0 else: dodge.controls.boost = 0 car.step(dodge.controls, 1 / fps) succesfull = self.dodge_succesfull(car, ball_location, dodge) if succesfull is not None: if succesfull: return True, j / fps, ball_location else: break return False, None, None