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)
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)
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)
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
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
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
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
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)
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)
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
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
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
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
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
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
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)
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
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)
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)) ]
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)
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)
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)