def find_landing_orientation(car: Car) -> Mat33: # FIXME: This uses a cheap approximation of the walls to find landing orientation obj = DummyObject(car) prev_pos = obj.pos for i in range(100): predict.fall(obj, 0.1) # Checking for intersections for plane in Field.SIDE_WALLS_AND_GROUND: if intersects_plane(prev_pos, obj.pos, plane): # Bingo! fall_dir = normalize(obj.pos - prev_pos) left = -cross(fall_dir, plane.normal) forward = -cross(plane.normal, left) return Mat33.from_columns(forward, left, plane.normal) prev_pos = obj.pos # No wall/ground intersections found in fall # Default to looking in direction of velocity, but upright forward = normalize(xy( car.vel)) if norm(xy(car.vel)) > 20 else car.forward up = Vec3(z=1) left = cross(up, forward) return Mat33.from_columns(forward, left, up)
def draw_circle(bot, center: Vec3, normal: Vec3, radius: float, pieces: int): # Construct the arm that will be rotated arm = normalize(cross(normal, center)) * radius angle = 2 * math.pi / pieces rotation_mat = axis_to_rotation(angle * normalize(normal)) points = [center + arm] for i in range(pieces): arm = dot(rotation_mat, arm) points.append(center + arm) bot.renderer.draw_polyline_3d(points, bot.renderer.orange())
def circle(self, center: Vec3, normal: Vec3, radius: float, color): # Construct the arm that will be rotated pieces = int(radius**0.7) + 5 arm = normalize(vec.cross(normal, center)) * radius angle = 2 * math.pi / pieces rotation_mat = axis_to_rotation(angle * normalize(normal)) points = [center + arm] for i in range(pieces): arm = dot(rotation_mat, arm) points.append(center + arm) self.renderer.draw_polyline_3d(points, color)
def get_closest_dir_in_cone(self, direction, span_offset: float=0): if self.contains_direction(direction, span_offset): return normalize(direction) else: ang_to_right = abs(angle_between(direction, self.right_dir)) ang_to_left = abs(angle_between(direction, self.left_dir)) return self.right_dir if ang_to_right < ang_to_left else self.left_dir
def exec(self, bot) -> SimpleControllerState: if bot.info.time > 10 and bot.info.time > self.next_test: self.next_test = bot.info.time + DURATION dir = Vec3(random.random() - 0.5, random.random() - 0.5, random.random() - 0.5) self.ball_pos = CAR_POS + normalize(dir) * BALL_OFFSET bot.set_game_state( GameState( ball=BallState( Physics(location=self.ball_pos.to_desired_vec(), velocity=ANTI_GRAV.to_desired_vec())), cars={ bot.index: CarState( physics=Physics(location=CAR_POS.to_desired_vec(), velocity=ANTI_GRAV.to_desired_vec())) })) target_rot = looking_in_dir(self.ball_pos - CAR_POS) return bot.fly.align(bot, target_rot)
def exec(self, bot) -> SimpleControllerState: ct = bot.info.time - self._start_time controls = SimpleControllerState() controls.throttle = 1 car = bot.info.my_car # Target is allowed to be a function that takes bot as a parameter. Check what it is if callable(self.target): target = self.target(bot) else: target = self.target # To boost or not to boost, that is the question car_to_target = target - car.pos vel_p = proj_onto_size(car.vel, car_to_target) angle = angle_between(car_to_target, car.forward) controls.boost = self.boost and angle < self._boost_ang_req and vel_p < self._max_speed # States of dodge (note reversed order) # Land on ground if ct >= self._t_finishing: self._almost_finished = True if car.on_ground: self.done = True else: bot.maneuver = RecoveryManeuver(bot) self.done = True return controls elif ct >= self._t_second_unjump: # Stop pressing jump and rotate and wait for flip is done pass elif ct >= self._t_aim: if ct >= self._t_second_jump: controls.jump = 1 # Direction, yaw, pitch, roll if self.target is None: controls.roll = 0 controls.pitch = -1 controls.yaw = 0 else: target_local = dot(car_to_target, car.rot) target_local.z = 0 direction = normalize(target_local) controls.roll = 0 controls.pitch = -direction.x controls.yaw = sign(car.rot.get(2, 2)) * direction.y # Stop pressing jump elif ct >= self._t_first_unjump: pass # First jump else: controls.jump = 1 return controls
def find_landing_orientation(car: Car, num_points: int) -> Mat33: """ dummy = DummyObject(car) trajectory = [Vec3(dummy.pos)] for i in range(0, num_points): fall(dummy, 0.0333) # Apply physics and let car fall through the air trajectory.append(Vec3(dummy.pos)) up = dummy.pitch_surface_normal() if norm(up) > 0.0 and i > 10: up = normalize(up) forward = normalize(dummy.vel - dot(dummy.vel, up) * up) left = cross(up, forward) return Mat33.from_columns(forward, left, up) return Mat33(car.rot) """ forward = normalize(xy( car.vel)) if norm(xy(car.vel)) > 20 else car.forward up = Vec3(z=1) left = cross(up, forward) return Mat33.from_columns(forward, left, up)
def find_landing_orientation(car: Car) -> Mat33: # FIXME: If we knew the arena's mesh we could test if we are landing on a wall or something forward = normalize(xy( car.vel)) if norm(xy(car.vel)) > 20 else car.forward up = Vec3(z=1) left = cross(up, forward) return Mat33.from_columns(forward, left, up)
def find_landing_orientation(car: Car, num_points: int) -> Mat33: dummy = DummyObject(car) for i in range(0, num_points): fall(dummy, 0.0333) # Apply physics and let car fall through the air if i > 5 and sdf_contains(dummy.pos): up = normalize(sdf_normal(dummy.pos)) left = cross(normalize(dummy.vel), up) forward = cross(up, left) return Mat33.from_columns(forward, left, up) forward = normalize(xy( car.vel)) if norm(xy(car.vel)) > 20 else car.forward up = Vec3(z=1) left = cross(up, forward) return Mat33.from_columns(forward, left, up)
def towards(self, bot, target: Vec3, time: float, allowed_uncertainty: float = 0.3, dodge_hit: bool = True): ball_soon = ball_predict(bot, time) ball_soon_to_target_dir = normalize(target - ball_soon.pos) right = dot(axis_to_rotation(Vec3(z=allowed_uncertainty)), ball_soon_to_target_dir) left = dot(axis_to_rotation(Vec3(z=-allowed_uncertainty)), ball_soon_to_target_dir) aim_cone = AimCone(right, left) aim_cone.draw(ball_soon.pos, r=0, g=0) return self.with_aiming(bot, aim_cone, time, dodge_hit)
def stay_at(self, bot, point: Vec3, looking_at: Vec3): OKAY_DIST = 100 car = bot.info.my_car car_to_point = point - car.pos car_to_point_dir = normalize(point - car.pos) dist = norm(car_to_point) if dist > OKAY_DIST: return self.towards_point(bot, point, int(dist * 2)) else: look_dir = normalize(looking_at - car.pos) facing_correctly = dot(car.forward, look_dir) > 0.9 if facing_correctly: return SimpleControllerState() else: ctrls = SimpleControllerState() ctrls.throttle = 0.7 * sign(dot(car.forward, car_to_point_dir)) ctrls.steer = -ctrls.throttle * sign( dot(car.left, car_to_point_dir)) return ctrls
def sdf_normal(point: Vec3) -> Vec3: """ Returns the normalized gradient at the given point. At wall distance 0 this is the arena's surface normal. """ # SDF normals https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm d = 0.0004 return normalize( Vec3( sdf_wall_dist(point + Vec3(d, 0, 0)) - sdf_wall_dist(point - Vec3(d, 0, 0)), sdf_wall_dist(point + Vec3(0, d, 0)) - sdf_wall_dist(point - Vec3(0, d, 0)), sdf_wall_dist(point + Vec3(0, 0, d)) - sdf_wall_dist(point - Vec3(0, 0, d)), ))
def curve_from_arrival_dir(src: Vec3, target: Vec3, arrival_direction: Vec3, w=1): """ Returns a point that is equally far from src and target on the line going through target with the given direction """ dir = normalize(arrival_direction) tx = target.x ty = target.y sx = src.x sy = src.y dx = dir.x dy = dir.y t = - (tx * tx - 2 * tx * sx + ty * ty - 2 * ty * sy + sx * sx + sy * sy) / (2 * (tx * dx + ty * dy - sx * dx - sy * dy)) t = clip(t, -1700, 1700) return target + w * t * dir
def is_viable(self, car, current_time: float): up = car.up T = self.intercept_time - current_time xf = car.pos + car.vel * T + 0.5 * GRAVITY * T ** 2 vf = car.vel + GRAVITY * T if self.jumping: if self.jump_begin_time == -1: jump_elapsed = 0 else: jump_elapsed = current_time - self.jump_begin_time # How much longer we can press jump and still gain upward force tau = JUMP_MAX_DUR - jump_elapsed # Add jump pulse if jump_elapsed == 0: vf += up * JUMP_SPEED xf += up * JUMP_SPEED * T # Acceleration from holding jump vf += up * JUMP_ACCEL * tau xf += up * JUMP_ACCEL * tau * (T - 0.5 * tau) if self.do_second_jump: # Impulse from the second jump vf += up * JUMP_SPEED xf += up * JUMP_SPEED * (T - tau) delta_x = self.hit_pos - xf dir = normalize(delta_x) phi = angle_between(dir, car.forward) turn_time = 0.7 * (2 * math.sqrt(phi / 9)) tau1 = turn_time * clip(1 - 0.3 / phi, 0.02, 1) required_acc = (2 * norm(delta_x)) / ((T - tau1) ** 2) ratio = required_acc / BOOST_ACCEL tau2 = T - (T - tau1) * math.sqrt(1 - clip01(ratio)) velocity_estimate = vf + BOOST_ACCEL * (tau2 - tau1) * dir boost_estimate = (tau2 - tau1) * BOOST_PR_SEC enough_boost = boost_estimate < 0.95 * car.boost enough_time = abs(ratio) < 0.9 return norm(velocity_estimate) < 0.9 * MAX_SPEED and enough_boost and enough_time
def run(self, bot) -> SimpleControllerState: self.is_dribbling = True car = bot.info.my_car ball = bot.info.ball ball_landing = predict.next_ball_landing(bot) ball_to_goal = bot.info.opp_goal.pos - ball.pos # Decide on target pos and speed target = ball_landing.data["obj"].pos - self.offset_bias * normalize( ball_to_goal) dist = norm(target - bot.info.my_car.pos) speed = 1400 if ball_landing.time == 0 else dist / ball_landing.time # Do a flick? car_to_ball = ball.pos - car.pos dist = norm(car_to_ball) enemy, enemy_dist = bot.info.closest_enemy(ball.pos) if dist <= self.required_distance_to_ball_for_flick: self.flick_timer += bot.info.dt if self.flick_timer > self.wait_before_flick and enemy_dist < 900: bot.maneuver = DodgeManeuver( bot, bot.info.opp_goal.pos) # use flick_init_jump_duration? else: self.flick_timer = 0 if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, target, bot.renderer.pink()) return bot.drive.towards_point(bot, target, target_vel=speed, slide=False, can_keep_speed=False, can_dodge=True, wall_offset_allowed=0)
def update(self, bot): ball = bot.info.ball # Find closest foe to ball self.opp_closest_to_ball, self.opp_closest_to_ball_dist = argmin(bot.info.opponents, lambda opp: norm(opp.pos - ball.pos)) # Possession and on/off-site self.car_with_possession = None self.ally_with_possession = None self.opp_with_possession = None for car in bot.info.cars: # Effective position car.effective_pos = car.pos + xy(car.vel) * 0.8 # On site car_to_ball = ball.pos - car.pos car_to_ball_unit = normalize(car_to_ball) car.onsite = dot(Vec3(y=-car.team_sign), car_to_ball_unit) # Reach ball time car.reach_ball_time = predict.time_till_reach_ball(car, ball) reach01 = 1 - 0.9 * lin_fall(car.reach_ball_time, 4) ** 0.5 # Possession point_in_front = car.pos + car.vel * 0.5 ball_point_dist = norm(ball.pos - point_in_front) dist01 = 1000 / (1000 + ball_point_dist) # Halved after 1000 uu of dist, 1/3 at 2000 in_front01 = (dot(car.forward, car_to_ball_unit) + 1) / 2.0 car.possession = dist01 * in_front01 * reach01 if self.car_with_possession is None or car.possession > self.car_with_possession.possession: self.car_with_possession = car if car.team == bot.team and (self.ally_with_possession is None or car.possession > self.ally_with_possession.possession): self.ally_with_possession = car if car.team != bot.team and (self.opp_with_possession is None or car.possession > self.opp_with_possession.possession): self.opp_with_possession = car # Objectives if len(bot.info.team_cars) == 1: # No team mates. No roles bot.info.my_car.objective = bot.info.my_car.last_objective = Objective.SOLO return for car in bot.info.cars: car.last_objective = car.objective car.objective = Objective.UNKNOWN attacker, attacker_score = argmax(bot.info.team_cars, lambda ally: ((1.0 if ally.last_objective == Objective.GO_FOR_IT else 0.73) * ease_out(0.2 + 0.8 * ally.boost / 100, 2) # 50 boost is 0.85, 0 boost is 0.2 * ally.possession * ally.got_it_according_to_quick_chat_01(bot.info.time) * (1.0 if ally.onsite else 0.5) * (0 if ally.is_demolished else 1))) attacker.objective = Objective.GO_FOR_IT self.ideal_follow_up_pos = xy(ball.pos + bot.info.own_goal.pos) * 0.5 follower, follower_score = argmax([ally for ally in bot.info.team_cars if ally.objective == Objective.UNKNOWN], lambda ally: (1.0 if ally.last_objective == Objective.FOLLOW_UP else 0.73) * ease_out(0.2 * 0.8 * ally.boost / 100, 2) * (1 + ally.onsite / 2) * lin_fall(norm(ally.effective_pos - self.ideal_follow_up_pos), 3000) * (0 if ally.is_demolished else 1)) if follower is not None: follower.objective = Objective.FOLLOW_UP for car in bot.info.team_cars: if car.objective == Objective.UNKNOWN: car.objective = Objective.ROTATING
def __init__(self, right_most, left_most): # Right angle and direction self.right_ang = math.atan2(right_most.y, right_most.x) self.right_dir = normalize(right_most) self.left_ang = math.atan2(left_most.y, left_most.x) self.left_dir = normalize(left_most)
def exec(self, bot) -> SimpleControllerState: car = bot.info.my_car ball = bot.info.ball my_hit_time = predict.time_till_reach_ball(car, ball) shoot_controls = bot.shoot.with_aiming(bot, self.aim_cone, my_hit_time) if bot.do_rendering: self.aim_cone.draw(bot, bot.shoot.ball_when_hit.pos, b=0) hit_pos = bot.shoot.ball_when_hit.pos dist = norm(car.pos - hit_pos) closest_enemy, enemy_dist = bot.info.closest_enemy( 0.5 * (hit_pos + ball.pos)) if not bot.shoot.can_shoot and is_closer_to_goal_than( car.pos, hit_pos, bot.info.team): # Can't shoot but or at least on the right side: Chase goal_to_ball = normalize(hit_pos - bot.info.enemy_goal) offset_ball = hit_pos + goal_to_ball * Ball.RADIUS * 0.9 enemy_hit_time = predict.time_till_reach_ball(closest_enemy, ball) enemy_hit_pos = predict.ball_predict(bot, enemy_hit_time).pos if enemy_hit_time < 1.5 * my_hit_time: self.temp_utility_desire_boost -= bot.info.dt if bot.do_rendering: bot.renderer.draw_line_3d(closest_enemy.pos, enemy_hit_pos, bot.renderer.red()) return bot.drive.go_home(bot) if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, offset_ball, bot.renderer.yellow()) return bot.drive.go_towards_point(bot, offset_ball, target_vel=2200, slide=False, boost_min=0) elif not bot.shoot.aim_is_ok and hit_pos.y * -bot.info.team_sign > 4250 and abs( hit_pos.x) > 900 and not dist < 420: # hit_pos is an enemy corner and we are not close: Avoid enemy corners and just wait enemy_to_ball = normalize(hit_pos - closest_enemy.pos) wait_point = hit_pos + enemy_to_ball * enemy_dist # a point 50% closer to the center of the field wait_point = lerp(wait_point, ball.pos + Vec3(0, bot.info.team_sign * 3000, 0), 0.5) if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, wait_point, bot.renderer.yellow()) return bot.drive.go_towards_point(bot, wait_point, norm(car.pos - wait_point), slide=False, can_keep_speed=True, can_dodge=False) elif not bot.shoot.can_shoot: enemy_to_ball = normalize(xy(ball.pos - closest_enemy.pos)) ball_to_my_goal = normalize(xy(bot.info.own_goal - ball.pos)) dot_threat = dot( enemy_to_ball, ball_to_my_goal ) # 1 = enemy is in position, -1 = enemy is NOT in position if car.boost == 0 and ball.pos.y * bot.info.team_sign < 500 and dot_threat < 0.1: collect_center = ball.pos.y * bot.info.team_sign <= 0 collect_small = closest_enemy.pos.y * bot.info.team_sign <= 0 or enemy_dist < 900 pads = filter_pads(bot, bot.info.big_boost_pads, big_only=not collect_small, enemy_side=False, center=collect_center) bot.maneuver = CollectClosestBoostManeuver(bot, pads) # return home return bot.drive.go_home(bot) else: # Shoot ! if bot.shoot.using_curve and bot.do_rendering: rendering.draw_bezier( bot, [car.pos, bot.shoot.curve_point, hit_pos]) return shoot_controls
def exec(self, bot): if not self.announced_in_quick_chat: self.announced_in_quick_chat = True bot.send_quick_chat(QuickChats.CHAT_EVERYONE, QuickChats.Information_IGotIt) ct = bot.info.time car = bot.info.my_car up = car.up controls = SimpleControllerState() # Time remaining till intercept time T = self.intercept_time - bot.info.time # Expected future position xf = car.pos + car.vel * T + 0.5 * GRAVITY * T ** 2 # Expected future velocity vf = car.vel + GRAVITY * T # Is set to false while jumping to avoid FeelsBackFlipMan rotate = True if self.jumping: if self.jump_begin_time == -1: jump_elapsed = 0 self.jump_begin_time = ct else: jump_elapsed = ct - self.jump_begin_time # How much longer we can press jump and still gain upward force tau = JUMP_MAX_DUR - jump_elapsed # Add jump pulse if jump_elapsed == 0: vf += up * JUMP_SPEED xf += up * JUMP_SPEED * T rotate = False # Acceleration from holding jump vf += up * JUMP_SPEED * tau xf += up * JUMP_SPEED * tau * (T - 0.5 * tau) if self.do_second_jump: # Impulse from the second jump vf += up * JUMP_SPEED xf += up * JUMP_SPEED * (T - tau) if jump_elapsed < JUMP_MAX_DUR: controls.jump = True else: controls.jump = False if self.do_second_jump: if self.jump_pause_counter < 4: # Do a 4-tick pause between jumps self.jump_pause_counter += 1 else: # Time to start second jump # we do this by resetting our jump counter and pretend and our aerial started in the air self.jump_begin_time = -1 self.jumping = True self.do_second_jump = False else: # We are done jumping self.jumping = False else: controls.jump = False delta_pos = self.hit_pos - xf direction = normalize(delta_pos) car_to_hit_pos = self.hit_pos - car.pos dodging = self.dodge_begin_time != -1 if dodging: controls.jump = True # We are not pressing jump, so let's align the car if rotate and not dodging: if self.do_dodge and norm(car_to_hit_pos) < Ball.RADIUS + 80: # Start dodge self.dodge_begin_time = ct hit_local = dot(car_to_hit_pos, car.rot) hit_local.z = 0 dodge_direction = normalize(hit_local) controls.roll = 0 controls.pitch = -dodge_direction.x controls.yaw = sign(car.rot.get(2, 2)) * direction.y controls.jump = True else: # Adjust orientation if norm(delta_pos) > 50: pd = bot.fly.align(bot, looking_in_dir(delta_pos)) else: if self.target_rot is not None: pd = bot.fly.align(bot, self.target_rot) else: pd = bot.fly.align(bot, looking_in_dir(self.hit_pos - car.pos)) controls.roll = pd.roll controls.pitch = pd.pitch controls.yaw = pd.yaw if not dodging and angle_between(car.forward, direction) < 0.3: if norm(delta_pos) > 40: controls.boost = 1 controls.throttle = 0 else: controls.boost = 0 controls.throttle = clip01(0.5 * THROTTLE_AIR_ACCEL * T ** 2) else: controls.boost = 0 controls.throttle = 0 prediction = predict.ball_predict(bot, T) self.done = T < 0 if norm(self.hit_pos - prediction.pos) > 50: # Jump shot failed self.done = True bot.send_quick_chat(QuickChats.CHAT_EVERYONE, QuickChats.Apologies_Cursing) if bot.do_rendering: car_to_hit_dir = normalize(self.hit_pos - car.pos) color = bot.renderer.pink() rendering.draw_cross(bot, self.hit_pos, color, arm_length=100) rendering.draw_circle(bot, lerp(car.pos, self.hit_pos, 0.25), car_to_hit_dir, 40, 12, color) rendering.draw_circle(bot, lerp(car.pos, self.hit_pos, 0.5), car_to_hit_dir, 40, 12, color) rendering.draw_circle(bot, lerp(car.pos, self.hit_pos, 0.75), car_to_hit_dir, 40, 12, color) bot.renderer.draw_line_3d(car.pos, self.hit_pos, color) return controls
def __init__(self, right_most, left_most): self.right_ang = math.atan2(right_most.y, right_most.x) self.right_dir = normalize(right_most) self.left_ang = math.atan2(left_most.y, left_most.x) self.left_dir = normalize(left_most)
def with_aiming(self, bot, aim_cone: AimCone, time: float, dodge_hit: bool = True): # aim: | | | | # ball | bad | ok | good | # z pos: | | | | # -----------+-----------+-----------+-----------+ # too high | give | give | wait/ | # | up | up | improve | # -----------+ - - - - - + - - - - - + - - - - - + # medium | give | improve | aerial | # | up | aim | | # -----------+ - - - - - + - - - - - + - - - - - + # soon on | improve | slow | small | # ground | aim | curve | jump | # -----------+ - - - - - + - - - - - + - - - - - + # on ground | improve | fast | fast | # | aim?? | curve | straight | # -----------+ - - - - - + - - - - - + - - - - - + # FIXME if the ball is not on the ground we treat it as 'soon on ground' in all other cases self.controls = SimpleControllerState() self.aim_is_ok = False self.waits_for_fall = False self.ball_is_flying = False self.can_shoot = False self.using_curve = False self.curve_point = None self.ball_when_hit = None car = bot.info.my_car ball_soon = ball_predict(bot, time) car_to_ball_soon = ball_soon.pos - car.pos dot_facing_score = dot(normalize(car_to_ball_soon), normalize(car.forward)) vel_towards_ball_soon = proj_onto_size(car.vel, car_to_ball_soon) is_facing = 0 < dot_facing_score if ball_soon.pos.z < 110 or (ball_soon.pos.z < 475 and ball_soon.vel.z <= 0) or True: #FIXME Always true # The ball is on the ground or soon on the ground if 275 < ball_soon.pos.z < 475 and aim_cone.contains_direction( car_to_ball_soon): # Can we hit it if we make a small jump? vel_f = proj_onto_size(car.vel, xy(car_to_ball_soon)) car_expected_pos = car.pos + car.vel * time ball_soon_flat = xy(ball_soon.pos) diff = norm(car_expected_pos - ball_soon_flat) ball_in_front = dot(ball_soon.pos - car_expected_pos, car.vel) > 0 if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, car_expected_pos, bot.renderer.lime()) bot.renderer.draw_rect_3d(car_expected_pos, 12, 12, True, bot.renderer.lime()) if vel_f > 400: if diff < 150 and ball_in_front: bot.maneuver = SmallJumpManeuver( bot, lambda b: b.info.ball.pos) if 110 < ball_soon.pos.z: # and ball_soon.vel.z <= 0: # The ball is slightly in the air, lets wait just a bit more self.waits_for_fall = True ball_landing = next_ball_landing(bot, ball_soon, size=100) time = time + ball_landing.time ball_soon = ball_predict(bot, time) car_to_ball_soon = ball_soon.pos - car.pos self.ball_when_hit = ball_soon # The ball is on the ground, are we in position for a shot? if aim_cone.contains_direction(car_to_ball_soon) and is_facing: # Straight shot self.aim_is_ok = True self.can_shoot = True if norm(car_to_ball_soon) < 240 + Ball.RADIUS and aim_cone.contains_direction(car_to_ball_soon)\ and vel_towards_ball_soon > 300: bot.drive.start_dodge(bot) offset_point = xy( ball_soon.pos) - 50 * aim_cone.get_center_dir() speed = self.determine_speed(norm(car_to_ball_soon), time) self.controls = bot.drive.go_towards_point( bot, offset_point, target_vel=speed, slide=True, boost_min=0, can_keep_speed=False) return self.controls elif aim_cone.contains_direction(car_to_ball_soon, math.pi / 5): # Curve shot self.aim_is_ok = True self.using_curve = True self.can_shoot = True offset_point = xy( ball_soon.pos) - 50 * aim_cone.get_center_dir() closest_dir = aim_cone.get_closest_dir_in_cone( car_to_ball_soon) self.curve_point = curve_from_arrival_dir( car.pos, offset_point, closest_dir) self.curve_point.x = clip(self.curve_point.x, -Field.WIDTH / 2, Field.WIDTH / 2) self.curve_point.y = clip(self.curve_point.y, -Field.LENGTH / 2, Field.LENGTH / 2) if dodge_hit and norm(car_to_ball_soon) < 240 + Ball.RADIUS and angle_between(car.forward, car_to_ball_soon) < 0.5\ and aim_cone.contains_direction(car_to_ball_soon) and vel_towards_ball_soon > 300: bot.drive.start_dodge(bot) speed = self.determine_speed(norm(car_to_ball_soon), time) self.controls = bot.drive.go_towards_point( bot, self.curve_point, target_vel=speed, slide=True, boost_min=0, can_keep_speed=False) return self.controls else: # We are NOT in position! self.aim_is_ok = False pass else: if aim_cone.contains_direction(car_to_ball_soon): self.waits_for_fall = True self.aim_is_ok = True #self.can_shoot = False pass # Allow small aerial (wait if ball is too high) elif aim_cone.contains_direction(car_to_ball_soon, math.pi / 4): self.ball_is_flying = True pass # Aim is ok, but ball is in the air
class Field: WIDTH = 8192 WIDTH2 = WIDTH / 2 LENGTH = 10240 LENGTH2 = LENGTH / 2 HEIGHT = 2044 ZONE = Zone3d(Vec3(WIDTH2, LENGTH2, 0.0), Vec3(-WIDTH2, -LENGTH2, HEIGHT)) CORNER_WALL_AX_INTERSECT = 8064 GROUND = Plane(Vec3(), Vec3(z=1)) CEILING = Plane(Vec3(z=HEIGHT), Vec3(z=-1)) BLUE_BACKBOARD = Plane(Vec3(y=-LENGTH2), Vec3(y=1)) ORANGE_BACKBOARD = Plane(Vec3(y=LENGTH2), Vec3(y=-1)) LEFT_WALL = Plane(Vec3(x=WIDTH2), Vec3(x=-1)) # Blue POV RIGHT_WALL = Plane(Vec3(x=-WIDTH2), Vec3(x=1)) # Blue POV BLUE_RIGHT_CORNER_WALL = Plane(Vec3(y=-CORNER_WALL_AX_INTERSECT), normalize(Vec3(x=1, y=1))) BLUE_LEFT_CORNER_WALL = Plane(Vec3(y=-CORNER_WALL_AX_INTERSECT), normalize(Vec3(x=-1, y=1))) ORANGE_RIGHT_CORNER_WALL = Plane(Vec3(y=CORNER_WALL_AX_INTERSECT), normalize(Vec3(x=-1, y=-1))) ORANGE_LEFT_CORNER_WALL = Plane(Vec3(y=CORNER_WALL_AX_INTERSECT), normalize(Vec3(x=1, y=-1))) ALL_WALLS = [ GROUND, CEILING, BLUE_BACKBOARD, ORANGE_BACKBOARD, LEFT_WALL, RIGHT_WALL, BLUE_RIGHT_CORNER_WALL, BLUE_LEFT_CORNER_WALL, ORANGE_RIGHT_CORNER_WALL, ORANGE_LEFT_CORNER_WALL, ] SIDE_WALLS = [ BLUE_BACKBOARD, ORANGE_BACKBOARD, LEFT_WALL, RIGHT_WALL, BLUE_RIGHT_CORNER_WALL, BLUE_LEFT_CORNER_WALL, ORANGE_RIGHT_CORNER_WALL, ORANGE_LEFT_CORNER_WALL, ] SIDE_WALLS_AND_GROUND = [ GROUND, BLUE_BACKBOARD, ORANGE_BACKBOARD, LEFT_WALL, RIGHT_WALL, BLUE_RIGHT_CORNER_WALL, BLUE_LEFT_CORNER_WALL, ORANGE_RIGHT_CORNER_WALL, ORANGE_LEFT_CORNER_WALL, ]
def run(self, bot) -> SimpleControllerState: car = bot.info.my_car ball = bot.info.ball my_hit_time = predict.time_till_reach_ball(car, ball) reachable_ball = predict.ball_predict(bot, predict.time_till_reach_ball(car, ball)) ball_to_goal_right = bot.info.opp_goal.right_post - reachable_ball.pos ball_to_goal_left = bot.info.opp_goal.left_post - reachable_ball.pos aim_cone = AimCone(ball_to_goal_right, ball_to_goal_left) shoot_controls = bot.shoot.with_aiming(bot, aim_cone, my_hit_time) hit_pos = bot.shoot.ball_when_hit.pos dist = norm(car.pos - hit_pos) closest_enemy, enemy_dist = bot.info.closest_enemy(0.5 * (hit_pos + ball.pos)) if not bot.shoot.can_shoot and is_closer_to_goal_than(car.pos, hit_pos, bot.info.team): # Can't shoot but or at least on the right side: Chase goal_to_ball = normalize(hit_pos - bot.info.opp_goal.pos) offset_ball = hit_pos + goal_to_ball * Ball.RADIUS * 0.9 enemy_hit_time = predict.time_till_reach_ball(closest_enemy, ball) enemy_hit_pos = predict.ball_predict(bot, enemy_hit_time).pos if enemy_hit_time < 1.5 * my_hit_time: if bot.do_rendering: bot.renderer.draw_line_3d(closest_enemy.pos, enemy_hit_pos, bot.renderer.red()) return bot.drive.home(bot) if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, offset_ball, bot.renderer.yellow()) return bot.drive.towards_point(bot, offset_ball, target_vel=2200, slide=False, boost_min=0) elif len(bot.info.teammates) == 0 and not bot.shoot.aim_is_ok and hit_pos.y * -bot.info.team_sign > 4250 and abs(hit_pos.x) > 900 and not dist < 420: # hit_pos is an enemy corner and we are not close: Avoid enemy corners in 1s and just wait enemy_to_ball = normalize(hit_pos - closest_enemy.pos) wait_point = hit_pos + enemy_to_ball * enemy_dist # a point 50% closer to the center of the field wait_point = lerp(wait_point, ball.pos + Vec3(0, bot.info.team_sign * 3000, 0), 0.5) if bot.do_rendering: bot.renderer.draw_line_3d(car.pos, wait_point, bot.renderer.yellow()) return bot.drive.towards_point(bot, wait_point, norm(car.pos - wait_point), slide=False, can_keep_speed=True, can_dodge=False) elif bot.shoot.can_shoot: # Shoot ! if bot.do_rendering: aim_cone.draw(bot, bot.shoot.ball_when_hit.pos, r=0, b=0) if bot.shoot.using_curve: rendering.draw_bezier(bot, [car.pos, bot.shoot.curve_point, hit_pos]) return shoot_controls else: # We can't shoot at goal reliably # How about a shot to the corners then? corners = [ Vec3(-Field.WIDTH2, -bot.info.team_sign * Field.LENGTH2, 0), Vec3(Field.WIDTH2, -bot.info.team_sign * Field.LENGTH2, 0), ] for corner in corners: ctrls = bot.shoot.towards(bot, corner, bot.info.my_car.reach_ball_time) if bot.shoot.can_shoot: aim_cone.draw(bot, bot.shoot.ball_when_hit.pos, b=0) if bot.shoot.using_curve: rendering.draw_bezier(bot, [car.pos, bot.shoot.curve_point, hit_pos]) return ctrls enemy_to_ball = normalize(xy(ball.pos - closest_enemy.pos)) ball_to_my_goal = normalize(xy(bot.info.own_goal.pos - ball.pos)) dot_threat = dot(enemy_to_ball, ball_to_my_goal) # 1 = enemy is in position, -1 = enemy is NOT in position if car.boost <= 10 and ball.pos.y * bot.info.team_sign < 0 and dot_threat < 0.15: collect_center = ball.pos.y * bot.info.team_sign <= 0 collect_small = closest_enemy.pos.y * bot.info.team_sign <= 0 or enemy_dist < 900 pads = filter_pads(bot, bot.info.big_boost_pads, big_only=not collect_small, enemy_side=False, center=collect_center) bot.maneuver = CollectClosestBoostManeuver(bot, pads) # return home-ish return bot.drive.stay_at(bot, lerp(bot.info.own_goal.pos, ball.pos, 0.2), ball.pos)
def with_aiming(self, bot, aim_cone: AimCone, time: float, dodge_hit: bool = True): # aim: | | | | # ball | bad | ok | good | # z pos: | | | | # -----------+-----------+-----------+-----------+ # too high | give | give | wait/ | # > 1200 | up | up | improve | # -----------+ - - - - - + - - - - - + - - - - - + # medium | give | improve | aerial | # | up | aim | | # -----------+ - - - - - + - - - - - + - - - - - + # soon on | improve | slow | small | # ground | aim | curve | jump | # -----------+ - - - - - + - - - - - + - - - - - + # on ground | improve | fast | fast | # | aim?? | curve | straight | # -----------+ - - - - - + - - - - - + - - - - - + # FIXME if the ball is not on the ground we treat it as 'soon on ground' in all other cases self.controls = SimpleControllerState() self.aim_is_ok = False self.waits_for_fall = False self.ball_is_flying = False self.can_shoot = False self.using_curve = False self.curve_point = None car = bot.info.my_car ball_soon = ball_predict(bot, time) car_to_ball_soon = ball_soon.pos - car.pos dot_facing_score = dot(normalize(car_to_ball_soon), normalize(car.forward)) dot_facing_score_2d = dot(normalize(xy(car_to_ball_soon)), normalize(xy(car.forward))) vel_towards_ball_soon = proj_onto_size(car.vel, car_to_ball_soon) is_facing = 0.1 < dot_facing_score is_facing_2d = 0.3 < dot_facing_score self.ball_when_hit = ball_soon if ball_soon.pos.z < 110: # The ball is on the ground if 110 < ball_soon.pos.z: # and ball_soon.vel.z <= 0: # The ball is slightly in the air, lets wait just a bit more self.waits_for_fall = True ball_landing = next_ball_landing(bot, ball_soon, size=100) time = time + ball_landing.time ball_soon = ball_predict(bot, time) car_to_ball_soon = ball_soon.pos - car.pos self.ball_when_hit = ball_soon # The ball is on the ground, are we in position for a shot? if aim_cone.contains_direction(car_to_ball_soon) and is_facing: # Straight shot self.aim_is_ok = True self.can_shoot = True if norm(car_to_ball_soon) < 400 + Ball.RADIUS and aim_cone.contains_direction(car_to_ball_soon)\ and vel_towards_ball_soon > 300: bot.drive.start_dodge(bot, towards_ball=True) offset_point = xy(ball_soon.pos) - 50 * aim_cone.get_center_dir() speed = self._determine_speed(norm(car_to_ball_soon), time) self.controls = bot.drive.towards_point(bot, offset_point, target_vel=speed, slide=True, boost_min=0, can_keep_speed=False) return self.controls elif aim_cone.contains_direction(car_to_ball_soon, math.pi / 5): # Curve shot self.aim_is_ok = True self.using_curve = True self.can_shoot = True offset_point = xy(ball_soon.pos) - 50 * aim_cone.get_center_dir() closest_dir = aim_cone.get_closest_dir_in_cone(car_to_ball_soon) self.curve_point = curve_from_arrival_dir(car.pos, offset_point, closest_dir) self.curve_point.x = clip(self.curve_point.x, -Field.WIDTH / 2, Field.WIDTH / 2) self.curve_point.y = clip(self.curve_point.y, -Field.LENGTH / 2, Field.LENGTH / 2) if dodge_hit and norm(car_to_ball_soon) < 400 + Ball.RADIUS and angle_between(car.forward, car_to_ball_soon) < 0.5\ and aim_cone.contains_direction(car_to_ball_soon) and vel_towards_ball_soon > 300: bot.drive.start_dodge(bot, towards_ball=True) speed = self._determine_speed(norm(car_to_ball_soon), time) self.controls = bot.drive.towards_point(bot, self.curve_point, target_vel=speed, slide=True, boost_min=0, can_keep_speed=False) return self.controls else: # We are NOT in position! return None elif ball_soon.pos.z < 600 and ball_soon.vel.z <= 0: # Ball is on ground soon. Is it worth waiting? TODO if aim is bad, do a slow curve - or delete case? pass # --------------------------------------- # Ball is in the air, or going in the air if 200 < ball_soon.pos.z < 1400 and aim_cone.contains_direction(car_to_ball_soon) and is_facing_2d: # Can we hit it if we make jump shot or aerial shot? vel_f = proj_onto_size(car.vel, xy(car_to_ball_soon)) aerial = ball_soon.pos.z > 750 if vel_f > 400: # Some forward momentum is required flat_dist = norm(xy(car_to_ball_soon)) # This range should be good https://www.desmos.com/calculator/bx9imtiqi5 good_height = 0.3 * ball_soon.pos.z < flat_dist < 4 * ball_soon.pos.z if good_height: # Alternative ball positions alternatives = [ (ball_predict(bot, time * 0.8), time * 0.8), (ball_predict(bot, time * 0.9), time * 0.9), (ball_soon, time), (ball_predict(bot, time * 1.1), time * 1.1), (ball_predict(bot, time * 1.2), time * 1.2) ] for alt_ball, alt_time in alternatives: potential_small_jump_shot = JumpShotManeuver(bot, alt_ball.pos, bot.info.time + alt_time, do_second_jump=aerial) jump_shot_viable = potential_small_jump_shot.is_viable(car, bot.info.time) if jump_shot_viable: self.can_shoot = True self.aim_is_ok = True bot.maneuver = potential_small_jump_shot return bot.maneuver.exec(bot) self.ball_is_flying = True return self.controls