def direct_shot(self, car: Car, target: vec3) -> Maneuver: dodge_shot = DodgeStrike(car, self.info, target) ground_shot = GroundStrike(car, self.info, target) if car.boost > 40: # TODO aerial_strike = AerialStrike(car, self.info, target) fast_aerial = FastAerialStrike(car, self.info, target) better_aerial_strike = min( [aerial_strike, fast_aerial], key=lambda strike: strike.intercept.time) if better_aerial_strike.intercept.time < dodge_shot.intercept.time: if ground_distance(better_aerial_strike.intercept, self.info.their_goal.center) < 5000: return DoubleTouch(better_aerial_strike) return better_aerial_strike if (dodge_shot.intercept.time < ground_shot.intercept.time - 0.1 or ground_distance(dodge_shot.intercept, target) < 4000 or distance(ground_shot.intercept.ball.velocity, car.velocity) < 500): if (distance(dodge_shot.intercept.ground_pos, target) < 4000 and abs(dodge_shot.intercept.ground_pos[0]) < 3000): return CloseShot(car, self.info, target) return dodge_shot return ground_shot
def direct_shot(info: GameInfo, car: Car, target: vec3) -> Strike: dodge_shot = DodgeStrike(car, info, target) ground_shot = GroundStrike(car, info, target) if car.boost > 40: # TODO # aerial_strike = AerialStrike(car, info, target) fast_aerial = FastAerialStrike(car, info, target) better_aerial_strike = min([fast_aerial], key=lambda strike: strike.intercept.time) if (better_aerial_strike.intercept.time < dodge_shot.intercept.time and abs(better_aerial_strike.intercept.position[1] - info.their_goal.center[1]) > 500): if ground_distance(better_aerial_strike.intercept, info.their_goal.center) < 8000: return DoubleTouch(better_aerial_strike) return better_aerial_strike if (dodge_shot.intercept.time < ground_shot.intercept.time - 0.1 or ground_distance(dodge_shot.intercept, target) < 2000 or distance(ground_shot.intercept.ball.velocity, car.velocity) < 500 or is_opponent_close(info, 300)): if (distance(dodge_shot.intercept.ground_pos, target) < 4000 and abs(dodge_shot.intercept.ground_pos[0]) < 2000): return CloseShot(car, info, target) return dodge_shot return ground_shot
def step(self, dt): if not self.flicking: self.carry.step(dt) self.controls = self.carry.controls self.finished = self.carry.finished car = self.car ball = self.info.ball # check if it's a good idea to flick dir_to_target = ground_direction(car, self.target) if (distance(car, ball) < 150 and ground_distance(car, ball) < 100 and dot(car.forward(), dir_to_target) > 0.7 and norm(car.velocity) > clamp( distance(car, self.target) / 3, 1000, 1700) and dot(dir_to_target, ground_direction(car, ball)) > 0.9): self.flicking = True # flick if opponent is close for opponent in self.info.get_opponents(): if (distance(opponent.position + opponent.velocity, car) < max( 300.0, norm(opponent.velocity) * 0.5) and dot(opponent.velocity, direction(opponent, self.info.ball)) > 0.5): if distance(car.position, self.info.ball.position) < 200: self.flicking = True else: self.finished = True else: self.flick.target = self.info.ball.position + self.info.ball.velocity * 0.2 self.flick.step(dt) self.controls = self.flick.controls self.finished = self.flick.finished
def choose_maneuver(info: GameInfo, my_car: Car): ball = info.ball teammates = info.get_teammates(my_car) my_team = [my_car] + teammates their_goal = ground(info.their_goal.center) my_goal = ground(info.my_goal.center) # recovery if not my_car.on_ground: return Recovery(my_car) # kickoff if ball.position[0] == 0 and ball.position[1] == 0: # if I'm nearest to the ball, go for kickoff if min(my_team, key=lambda car: distance(car, ball)) is my_car: return kickoffs.choose_kickoff(info, my_car) if my_car.boost < 20: return Refuel(my_car, info) info.predict_ball() my_intercept = Intercept(my_car, info.ball_predictions) teammates_intercepts = [ Intercept(mate, info.ball_predictions) for mate in teammates ] our_intercepts = teammates_intercepts + [my_intercept] good_intercepts = [ i for i in our_intercepts if align(i.car.position, i.ball, their_goal) > 0.0 ] if good_intercepts: best_intercept = min(good_intercepts, key=lambda intercept: intercept.time) else: best_intercept = min(our_intercepts, key=lambda i: distance(i.car, my_goal)) if best_intercept is my_intercept: # if not completely out of position, go for a shot if (align(my_intercept.car.position, my_intercept.ball, their_goal) > 0 or ground_distance(my_intercept, my_goal) > 6000): return offense.any_shot(info, my_intercept.car, their_goal, my_intercept) # otherwise try to clear else: return defense.any_clear(info, my_intercept.car) # if I'm nearest to goal, stay far back if min(my_team, key=lambda car: distance(car, my_goal)) is my_car: return GeneralDefense(my_car, info, my_intercept.position, 7000) # otherwise get into position return GeneralDefense(my_car, info, my_intercept.position, 4000)
def set_kickoff_maneuvers(self, drones: List[Drone]): nearest_drone = min( drones, key=lambda drone: ground_distance(drone.car, self.info.ball)) nearest_drone.maneuver = kickoffs.choose_kickoff( self.info, nearest_drone.car) self.drone_going_for_ball = nearest_drone self.boost_reservations.clear() corner_drones = [ drone for drone in drones if abs(drone.car.position[0]) > 2000 ] if len(corner_drones) > 1: other_corner_drone = next(drone for drone in corner_drones if drone is not nearest_drone) nearest_pad = min( self.info.large_boost_pads, key=lambda pad: distance(other_corner_drone.car, pad)) other_corner_drone.maneuver = HalfFlipPickup( other_corner_drone.car, nearest_pad) self.boost_reservations[other_corner_drone] = nearest_pad self.defending_drone = max( drones, key=lambda drone: ground_distance(drone.car, self.info.ball)) self.defending_drone.maneuver = DriveBackwardsToGoal( self.defending_drone.car, self.info) for drone in drones: if drone not in corner_drones + [self.defending_drone ] + [self.drone_going_for_ball]: self.send_drone_for_boost(drone)
def step(self, dt): car = self.car if self.phase == 1: if norm(car.velocity) > 1400: self.phase = 2 self.action = AirDodge(car, 0.05, car.position + car.velocity) if self.phase == 2: self.action.controls.boost = self.action.state_timer < 0.1 if car.on_ground and self.action.finished: self.action = self.drive self.phase = 3 if self.phase == 3: if distance(car, vec3(0, 0, 93)) < norm(car.velocity) * 0.4: self.phase = 4 self.action = AirDodge(car, 0.05, self.info.ball.position) self.counter_fake_kickoff() if self.phase == 4: if self.action.finished: self.finished = True super().step(dt)
def detect_collisions(self, time_limit=0.5, dt=1 / 60) -> List[Tuple[int, int, float]]: """Returns a list of tuples, where the first two elements are indices of cars and the last is time from now until the collision. """ time_steps = int(time_limit / dt) predictions = [ self.predict_car_drive(i, time_limit=time_limit, dt=dt) for i in range(self.num_cars) ] collisions = [] for i in range(self.num_cars): for j in range(self.num_cars): if i >= j: continue for step in range(time_steps): pos1 = predictions[i][step] pos2 = predictions[j][step] if distance(pos1, pos2) < self.COLLISION_THRESHOLD: collisions.append((i, j, step * dt)) break return collisions
def any_shot(info: GameInfo, car: Car, target: vec3, intercept: Intercept, allow_dribble=False) -> Maneuver: ball = intercept.ball if (allow_dribble and (ball.position[2] > 100 or abs(ball.velocity[2]) > 250 or distance(car, info.ball) < 300) and abs(ball.velocity[2]) < 700 and ground_distance(car, ball) < 1500 and ground_distance(ball, info.my_goal.center) > 1000 and ground_distance(ball, info.their_goal.center) > 1000 and not is_opponent_close(info, info.ball.position[2] * 2 + 1000)): return CarryAndFlick(car, info, target) direct = direct_shot(info, car, target) if not isinstance(direct, GroundStrike) and intercept.time < car.time + 4.0: alignment = align(car.position, ball, target) if alignment < -0.3 and abs(ball.position[1] - target[1]) > 3000: return MirrorStrike(car, info, target) return direct
def step(self, dt): # slow down when we're about to pick up the boost, so we can turn faster afterwards if distance(self.car, self.pad) < norm(self.car.velocity) * 0.2: self.travel.drive.target_speed = 1400 self.travel.step(dt) self.controls = self.travel.controls # finish when someone picks up the pad if self.pad_was_active and self.pad.state == BoostPadState.Unavailable: self.finished = True self.pad_was_active = self.pad.state == BoostPadState.Available # finish when we picked the boost up but the previous condition somehow wasn't true if self.car.boost > 99 or distance(self.car, self.pad) < 100: self.finished = True
def step(self, dt): ball = Ball(self.ball) car = self.car # simulate ball until it gets near the floor while (ball.position[2] > 120 or ball.velocity[2] > 0) and ball.time < car.time + 10: ball.step(1/60) ball_local = local(car, ground(ball.position)) target = local(car, self.target) shift = ground(direction(ball_local, target)) shift[1] *= 1.8 shift = normalize(shift) max_turn = clamp(norm(car.velocity) / 800, 0, 1) max_shift = normalize(vec3(1 - max_turn, max_turn * sign(shift[1]), 0)) if abs(shift[1]) > abs(max_shift[1]) or shift[0] < 0: shift = max_shift shift *= clamp(car.boost, 40, 60) shift[1] *= clamp(norm(car.velocity)/1000, 1, 2) self._shift_direction = normalize(world(car, shift) - car.position) target = world(car, ball_local - shift) speed = distance(car.position, target) / max(0.001, ball.time - car.time) self.drive.target_speed = speed self.drive.target_pos = target self.drive.step(dt) self.controls = self.drive.controls self.finished = self.ball.position[2] < 100 or ground_distance(self.ball, self.car) > 2000
def counter_fake_kickoff(self): if any( distance(self.info.ball, opponent) < 1500 for opponent in self.info.get_opponents()): return self.phase = "anti-fake-kickoff" self.action = self.drive
def estimate_time(car: Car, target, speed, dd=1) -> float: travel = distance(car, target) / 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 __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 find_second_touch(self): self.info.predict_ball(duration=4.0) for i in range(0, len(self.info.ball_predictions), 5): ball = self.info.ball_predictions[i] if ball.position[2] < 500: break self.aerial.target = ball.position - direction( ball, self.aerial_strike.target) * 80 self.aerial.arrival_time = ball.time final_car = AerialStrike.simulate_flight(self.car, self.aerial, self._flight_path) if distance(final_car, self.aerial.target) < 50: return self.finished = True
def best_boostpad_to_pickup(car: Car, pads: Set[Pad], pos: vec3) -> Optional[Pad]: best_pad = None best_dist = math.inf for pad in pads: dist = distance(pos, pad.position) time_estimate = estimate_time(car, pad.position, estimate_max_car_speed(car)) if dist < best_dist and (pad.is_active or pad.timer < time_estimate): best_pad = pad best_dist = dist return best_pad
def step(self, dt): # update finished state even if we are not using the controls self.travel.step(dt) if self.travel.finished: # turn around to face the target direction if angle_to(self.car, self.face_target) > 0.3: self.drive.target_pos = self.face_target self.drive.target_speed = 1000 self.drive.step(dt) self.controls = self.drive.controls self.controls.handbrake = False else: self.stop.step(dt) self.controls = self.stop.controls else: self.pad = None # collect boost pads on the way (greedy algorithm, assumes first found is best) if self.car.boost < 90 and self.travel.interruptible(): to_target = ground_direction(self.car, self.travel.target) for pad in self.info.large_boost_pads + self.info.small_boost_pads: to_pad = ground_direction(self.car, pad) if (pad.is_active and distance(self.car, pad) < self.BOOST_LOOK_RADIUS and angle_between(to_target, to_pad) < self.BOOST_LOOK_ANGLE): self.pad = pad self.drive.target_pos = pad.position self.drive.target_speed = 2200 self.drive.step(dt) self.controls = self.drive.controls break # go to the actual target if self.pad is None: self.controls = self.travel.controls # don't waste boost during downtime if self.car.boost < 100 and ground_distance(self.car, self.travel.target) < 4000: self.controls.boost = False self.finished = self.travel.driving and self.car.time > self.start_time + self.DURATION
def choose_boostpad_to_pickup(info: GameInfo, car: Car, forbidden_pads: Set[BoostPad] = None) -> Optional[BoostPad]: if forbidden_pads is None: forbidden_pads = set() # consider pads which are available or going to spawn before we can reach them active_pads = {pad for pad in info.large_boost_pads if pad.state == BoostPadState.Available} soon_active_pads = {pad for pad in info.large_boost_pads if estimate_time(car, pad.position) * 0.7 > pad.timer} valid_pads = active_pads | soon_active_pads - forbidden_pads if not valid_pads: return None # a good candidate should be somewhere between us, our goal, and the ball # the easiest way to do that is to just take a weighted average of those positions pos = (info.ball.position + car.position * 2 + info.my_goal.center) / 4 # and pick the closest valid pad to that position return min(valid_pads, key=lambda pad: distance(pad.position, pos))
def step(self, dt): target = self.target_pos # don't try driving outside the arena target = Arena.clamp(target, 100) # smoothly escape goal if abs(self.car.position[1]) > Arena.size[1] - 50 and abs( self.car.position.x) < 1000: target = Arena.clamp(target, 200) target[0] = abs_clamp(target[0], 700) if not self.drive_on_walls: seam_radius = 100 if abs( self.car.position[1]) > Arena.size[1] - 100 else 200 if self.car.position[2] > seam_radius: target = ground(self.car) local_target = local(self.car, target) if self.backwards: local_target[0] *= -1 local_target[1] *= -1 # steering phi = math.atan2(local_target[1], local_target[0]) self.controls.steer = clamp11(3.0 * phi) # powersliding self.controls.handbrake = 0 if (abs(phi) > 1.5 and self.car.position[2] < 300 and (ground_distance(self.car, target) < 3500 or abs(self.car.position[0]) > 3500) and dot(normalize(self.car.velocity), self.car.forward()) > 0.85): self.controls.handbrake = 1 # forward velocity vf = dot(self.car.velocity, self.car.forward()) if self.backwards: vf *= -1 # speed controller if vf < self.target_speed: self.controls.throttle = 1.0 if self.target_speed > 1400 and vf < 2250 and self.target_speed - vf > 50: self.controls.boost = 1 else: self.controls.boost = 0 else: if (vf - self.target_speed) > 400: # 75 self.controls.throttle = -1.0 elif (vf - self.target_speed) > 100: if self.car.up()[2] > 0.85: self.controls.throttle = 0.0 else: self.controls.throttle = 0.01 self.controls.boost = 0 # backwards driving if self.backwards: self.controls.throttle *= -1 self.controls.steer *= -1 self.controls.boost = 0 self.controls.handbrake = 0 # don't boost if not facing target if abs(phi) > 0.3: self.controls.boost = 0 # finish when close if distance(self.car, self.target_pos) < 100: self.finished = True
def choose_maneuver(self, car: Car): info = self.info offense = self.offense ball = info.ball teammates = info.get_teammates(car) opponents = info.get_opponents(car) their_goal = ground(info.their_goal.center) my_goal = ground(info.my_goal.center) my_hit = Intercept(car, info.ball_predictions) their_best_hit = self.best_intercept(opponents) opponent = their_best_hit.car # recovery if not car.on_ground: return Recovery(car) # kickoff should_go = all( distance(mate, ball) > distance(car, ball) for mate in teammates) if should_go and ball.position[0] == 0 and ball.position[1] == 0: return KickoffStrategy.choose_kickoff(info, car) # don't save our own shots if info.about_to_score: if info.time_of_goal < their_best_hit.time - 2: return Stop(car) # save if info.about_to_be_scored_on: if align(car.position, my_hit.ball, their_goal) > 0.0: return offense.direct_shot(car, their_goal) return self.defense.any_clear(car) # fallback if align(car.position, my_hit.ball, my_goal) > 0.2: if (ground_distance(my_hit, my_goal) < 4000 and abs(car.position[1]) < abs(my_hit.position[1])): return self.defense.any_clear(car) return GeneralDefense(car, info, my_hit.ground_pos, 6000) # clear if (ground_distance(my_hit, my_goal) < 3500 and abs(my_hit.position[0]) < 3000 and ground_distance(car, my_goal) < 2500): if align(car.position, my_hit.ball, their_goal) > 0: return offense.direct_shot(car, their_goal) return self.defense.any_clear(car) if distance(their_best_hit, their_goal) < distance( their_best_hit, my_goal): opponents_align = -align(opponent.position, their_best_hit.ball, their_goal) else: opponents_align = align(opponent.position, their_best_hit.ball, my_goal) # 1v1 if not teammates: # I can get to ball faster than them if my_hit.time < their_best_hit.time - 0.8: strike = offense.any_shot(car, their_goal, my_hit) if not isinstance(strike, Strike): return strike if strike.intercept.time < their_best_hit.time - 0.8 \ and (not info.about_to_score or strike.intercept.time < info.time_of_goal - 1): if strike.intercept.time - car.time > 4 and car.boost < 30 \ and distance(strike.intercept.ground_pos, their_goal) > 3000 and distance(their_best_hit.ground_pos, my_goal) > 5000: return Refuel(car, info, my_hit.ground_pos) if abs(strike.intercept.ground_pos[0] ) > Arena.size[0] - 800 and car.boost < 30: return Refuel(car, info, my_hit.ground_pos) if abs(strike.intercept.ball.position[1] - their_goal[1]) > 300 or ground_distance( strike.intercept, their_goal) < 900: return strike # they are out of position if (opponents_align < -0.1 and my_hit.time < their_best_hit.time - opponents_align * 1.5): strike = offense.any_shot(car, their_goal, my_hit) if not isinstance(strike, Strike) or strike.intercept.is_viable \ and (not info.about_to_score or strike.intercept.time < info.time_of_goal - 0.5): if (car.boost < 40 and (distance(my_hit, their_goal) > 5000 or abs(my_hit.position[0]) > Arena.size[0] - 1500) and distance(opponent, their_best_hit) > 3000): return Refuel(car, info, my_hit.ground_pos) if not isinstance(strike, Strike) or abs( strike.intercept.ball.position[1] - their_goal[1]) > 300 or ground_distance( strike.intercept, their_goal) < 900: return strike if distance(their_best_hit.ball, my_goal) > 7000 and \ (distance(their_best_hit, opponent) > 3000 or align(opponent.position, their_best_hit.ball, my_goal) < 0) and car.boost < 30: return Refuel(car, info, my_hit.ground_pos) if car.boost < 35 and distance(their_best_hit, opponent) > 3000: refuel = Refuel(car, info, my_hit.ground_pos) if estimate_time(car, refuel.pad.position, 1400) < 1.5: return refuel if opponents_align < 0: return offense.any_shot(car, their_goal, my_hit) # teamplay else: if car.boost < 40: return Refuel(car, info, my_goal) else: return offense.any_shot(car, their_goal, my_hit) shadow_distance = 4000 + opponents_align * 1500 shadow_distance = max(shadow_distance, 3000) return GeneralDefense(car, info, their_best_hit.ground_pos, shadow_distance)
def step(self, dt): time_left = self.aerial.arrival_time - self.car.time if self.aerialing: to_ball = direction(self.car, self.info.ball) # freestyling if self.car.position[2] > 200: # if time_left > 0.7: # rotation = axis_to_rotation(self.car.forward() * 0.5) # self.aerial.up = dot(rotation, self.car.up()) # else: self.aerial.up = vec3(0, 0, -1) + xy(to_ball) self.aerial.target_orientation = look_at(to_ball, vec3(0, 0, -3) + to_ball) self.aerial.step(dt) self.controls = self.aerial.controls self.finished = self.aerial.finished and time_left < -0.3 else: super().step(dt) # simulate aerial from current state simulated_car = self.simulate_flight(self.car, self.aerial, self._flight_path) speed_towards_target = dot( self.car.velocity, ground_direction(self.car, self.aerial.target_position)) speed_needed = ground_distance( self.car, self.aerial.target_position) / time_left # too fast, slow down if speed_towards_target > speed_needed and angle_to( self.car, self.aerial.target_position) < 0.1: self.controls.throttle = -1 # if it ended up near the target, we could take off elif distance( simulated_car, self.aerial.target_position) < self.MAX_DISTANCE_ERROR: if angle_to(self.car, self.aerial.target_position) < 0.1 or norm( self.car.velocity) < 1000: if self.DELAY_TAKEOFF and ground_distance( self.car, self.aerial.target_position) > 1000: # extrapolate current state a small amount of time future_car = Car(self.car) time = 0.5 future_car.time += time displacement = future_car.velocity * time if norm(future_car.velocity) > 500\ else normalize(future_car.velocity) * 500 * time future_car.position += displacement # simulate aerial fot the extrapolated car again future_simulated_car = self.simulate_flight( future_car, self.aerial) # if the aerial is also successful, that means we should continue driving instead of taking off # this makes sure that we go for the most late possible aerials, which are the most effective if distance(future_simulated_car, self.aerial. target_position) > self.MAX_DISTANCE_ERROR: self.aerialing = True else: self.too_early = True else: self.aerialing = True else: # self.controls.boost = True self.controls.throttle = 1