def should_defending(self): ball = self.info.ball car = self.info.my_car our_goal = self.info.my_goal.center car_to_ball = ball.pos - car.pos in_front_of_ball = distance_2d(ball.pos, our_goal) < distance_2d( car.pos, our_goal) backline_intersect = line_backline_intersect( self.info.my_goal.center[1], vec2(car.pos), vec2(car_to_ball)) return in_front_of_ball and abs(backline_intersect) < 2000
def should_dodge(agent): car = agent.info.my_car their_goal = agent.info.their_goal close_to_goal = distance_2d(car.pos, their_goal.center) < 4000 aiming_for_goal = abs( line_backline_intersect(their_goal.center[1], vec2(car.pos), vec2(car.forward()))) < 850 bot_to_target = agent.info.ball.pos - car.pos local_bot_to_target = dot(bot_to_target, agent.info.my_car.theta) angle_front_to_target = math.atan2(local_bot_to_target[1], local_bot_to_target[0]) close_to_ball = norm(vec2(bot_to_target)) < 850 good_angle = math.radians(-10) < angle_front_to_target < math.radians(10) return close_to_ball and close_to_goal and aiming_for_goal and good_angle
def shooting_target(agent): ball = agent.info.ball car = agent.info.my_car car_to_ball = ball.pos - car.pos backline_intersect = line_backline_intersect( agent.info.their_goal.center[1], vec2(car.pos), vec2(car_to_ball)) if -500 < backline_intersect < 500: goal_to_ball = normalize(car.pos - ball.pos) error = cap(distance_2d(ball.pos, car.pos) / 1000, 0, 1) else: # Right of the ball if -500 > backline_intersect: target = agent.info.their_goal.corners[3] + vec3(400, 0, 0) # Left of the ball elif 500 < backline_intersect: target = agent.info.their_goal.corners[2] - vec3(400, 0, 0) goal_to_ball = normalize(ball.pos - target) goal_to_car = normalize(car.pos - target) difference = goal_to_ball - goal_to_car error = cap(abs(difference[0]) + abs(difference[1]), 1, 10) goal_to_ball_2d = vec2(goal_to_ball[0], goal_to_ball[1]) test_vector_2d = dot(rotation(0.5 * math.pi), goal_to_ball_2d) test_vector = vec3(test_vector_2d[0], test_vector_2d[1], 0) distance = cap((40 + distance_2d(ball.pos, car.pos) * (error**2)) / 1.8, 0, 4000) location = ball.pos + vec3( (goal_to_ball[0] * distance), goal_to_ball[1] * distance, 0) # this adjusts the target based on the ball velocity perpendicular to the direction we're trying to hit it multiplier = cap(distance_2d(car.pos, location) / 1500, 0, 2) distance_modifier = cap( dot(test_vector, ball.vel) * multiplier, -1000, 1000) modified_vector = vec3(test_vector[0] * distance_modifier, test_vector[1] * distance_modifier, 0) location += modified_vector # another target adjustment that applies if the ball is close to the wall extra = 3850 - abs(location[0]) if extra < 0: location[0] = cap(location[0], -3850, 3850) location[1] = location[1] + (-sign(agent.team) * cap(extra, -800, 800)) return location
def can_dodge(agent, target): bot_to_target = target - agent.info.my_car.pos local_bot_to_target = dot(bot_to_target, agent.info.my_car.theta) angle_front_to_target = math.atan2(local_bot_to_target[1], local_bot_to_target[0]) distance_bot_to_target = norm(vec2(bot_to_target)) good_angle = math.radians(-10) < angle_front_to_target < math.radians(10) on_ground = agent.info.my_car.on_ground and agent.info.my_car.pos[2] < 100 going_fast = velocity_2d(agent.info.my_car.vel) > 1250 target_not_in_goal = not agent.info.my_goal.inside(target) return good_angle and distance_bot_to_target > 2000 and on_ground and going_fast and target_not_in_goal
def distance_2d(a, b): return norm(vec2(a - b))
def velocity_2d(vel): return norm(vec2(vel))
def step(self, dt): # direction of ball relative to center of car (where should we aim) # direction of ball relative to yaw of car (where should we aim verse where we are aiming) local_bot_to_ball = dot(self.ball.pos - self.car.pos, self.car.theta) angle_front_to_ball = math.atan2(local_bot_to_ball[1], local_bot_to_ball[0]) # distance between bot and ball distance = distance_2d(self.car.pos, self.ball.pos) # direction of ball velocity relative to yaw of car (which way the ball is moving verse which way we are moving) if velocity_2d(self.ball.vel) < 1e-10: angle_car_forward_to_ball_vel = 0 else: angle_car_forward_to_ball_vel = angle_between(z0(self.car.forward()), z0(self.ball.vel)) # magnitude of ball_bot_angle (squared) ball_bot_diff = (self.ball.vel[0] ** 2 + self.ball.vel[1] ** 2) - (self.car.vel[0] ** 2 + self.car.vel[1] ** 2) # p is the distance between ball and car # i is the magnitude of the ball's velocity (squared) the i term would normally # be the integral of p over time, but the ball's velocity is essentially that number # d is the relative speed between ball and car # note that bouncing a ball is distinctly different than balancing something that doesnt bounce # p_s is the x component of the distance to the ball # d_s is the one frame change of p_s, that's why p_s has to be global # we modify distance and ball_bot_diff so that only the component along the car's path is counted # if the ball is too far to the left, we don't want the bot to think it has to drive forward # to catch it distance_y = math.fabs(distance * math.cos(angle_front_to_ball)) distance_x = math.fabs(distance * math.sin(angle_front_to_ball)) # ball moving forward WRT car yaw? forward = False if math.fabs(angle_car_forward_to_ball_vel) < math.radians(90): forward = True # first we give the distance values signs if forward: d = ball_bot_diff i = (self.ball.vel[0] ** 2 + self.ball.vel[1] ** 2) else: d = -ball_bot_diff i = -(self.ball.vel[0] ** 2 + self.ball.vel[1] ** 2) if math.fabs(math.degrees(angle_front_to_ball)) < 90: p = distance_y else: p = -1 * distance_y # this is the PID correction. all of the callibration goes on right here # there is literature about how to set the variables but it doesn't work quite the same # because the car is only touching the ball (and interacting with the system) on bounces # we run the PID formula through tanh to give a value between -1 and 1 for steering input # if the ball is lower we have no velocity bias bias_v = 600000 # 600000 # just the basic PID if the ball is too low if self.ball.pos[2] < 120: correction = np.tanh((20 * p + .0015 * i + .006 * d) / 500) # if the ball is on top of the car we use our bias (the bias is in velocity units squared) else: correction = np.tanh((20 * p + .0015 * (i - bias_v) + .006 * d) / 500) # makes sure we don't get value over .99 so we dont exceed maximum thrust self.controls.throttle = correction * .99 # anything over .9 is boost if correction > .99: self.controls.boost = True else: self.controls.boost = False # this is the PID steering section # p_s is the x component of the distance to the ball (relative to the cars direction) # d_s is the on frame change in p_s # we use absolute value and then set the sign later d_s = math.fabs(self.p_s) - math.fabs(distance_x) self.p_s = math.fabs(distance_x) # give the values the correct sign if angle_front_to_ball < 0: self.p_s = -self.p_s d_s = -d_s # d_s is actually -d_s ...whoops d_s = -d_s max_bias = 35 backline_intersect = line_backline_intersect(self.goal.center[1], vec2(self.car.pos), vec2(self.car.forward())) if abs(backline_intersect) < 1000 or self.ball.pos[2] > 200: bias = 0 # Right of the ball elif -850 > backline_intersect: bias = max_bias # Left of the ball elif 850 < backline_intersect: bias = -max_bias # the correction settings can be altered to change performance correction = np.tanh((100 * (self.p_s + bias) + 1500 * d_s) / 8000) # apply the correction self.controls.steer = correction
def get_controls(self): if self.step == "Steer" or self.step == "Dodge2": self.step = "Catching" if self.step == "Catching": target = get_bounce(self) if target is None: self.step = "Defending" else: self.catching.target_pos = target[0] self.catching.target_speed = (distance_2d( self.info.my_car.pos, target[0]) + 50) / target[1] self.catching.step(self.FPS) self.controls = self.catching.controls ball = self.info.ball car = self.info.my_car if distance_2d(ball.pos, car.pos) < 150 and 65 < abs(ball.pos[2] - car.pos[2]) < 127: self.step = "Dribbling" self.dribble = Dribbling(self.info.my_car, self.info.ball, self.info.their_goal) if self.defending: self.step = "Defending" if not self.info.my_car.on_ground: self.step = "Recovery" ball = self.info.ball if abs(ball.vel[2]) < 100 and sign( self.team) * ball.vel[1] < 0 and sign( self.team) * ball.pos[1] < 0: self.step = "Shooting" elif self.step == "Dribbling": self.dribble.step(self.FPS) self.controls = self.dribble.controls ball = self.info.ball car = self.info.my_car bot_to_opponent = self.info.opponents[0].pos - self.info.my_car.pos local_bot_to_target = dot(bot_to_opponent, self.info.my_car.theta) angle_front_to_target = math.atan2(local_bot_to_target[1], local_bot_to_target[0]) opponent_is_near = norm(vec2(bot_to_opponent)) < 2000 opponent_is_in_the_way = math.radians( -10) < angle_front_to_target < math.radians(10) if not (distance_2d(ball.pos, car.pos) < 150 and 65 < abs(ball.pos[2] - car.pos[2]) < 127): self.step = "Catching" if self.defending: self.step = "Defending" if opponent_is_near and opponent_is_in_the_way: self.step = "Dodge" self.dodge = AirDodge(self.info.my_car, 0.25, self.info.their_goal.center) if not self.info.my_car.on_ground: self.step = "Recovery" elif self.step == "Defending": defending(self) elif self.step == "Dodge": self.dodge.step(self.FPS) self.controls = self.dodge.controls self.controls.boost = 0 if self.dodge.finished and self.info.my_car.on_ground: self.step = "Catching" elif self.step == "Recovery": self.recovery.step(self.FPS) self.controls = self.recovery.controls if self.info.my_car.on_ground: self.step = "Catching" elif self.step == "Shooting": shooting(self)