def two_way_lidar_rect(tpl1: Tuple[float, float], tpl2: Tuple[float, float], lst_rect: List[FloatRect]) -> Tuple[float, float]: """ Returns the distance to the nearest object (FloatRect) along the line formed by the two points. This is "two way" so two values are returned for both "forwards" (1->2) and "backwards" (2->1). """ start = Point(*tpl1) end = Point(*tpl2) lst_front = [float("inf")] lst_back = [float("inf")] for rect in lst_rect: for side in rect.sides: intersection = Point(*get_line_intersection(side, (start, end))) if intersection.x is None or intersection.y is None: pass # doesn't intersect else: dist_endpoint = distance(intersection, end) dist_startpoint = distance(intersection, start) if dist_endpoint <= dist_startpoint: lst_front.append(dist_endpoint) if dist_startpoint <= dist_endpoint: lst_back.append(dist_startpoint) return min(lst_front), min(lst_back)
def _robot_state(self, sprRobot: Robot) -> List[float]: b = self.lstPosBalls[0] # type: Ball ball_angle = (angle_degrees(sprRobot.rectDbl.center, b.rectDbl.center) + 360) % 360 ball_dist = abs(distance(sprRobot.rectDbl.center, b.rectDbl.center)) """ Calc super primitive lidar extending front/back from center of bot """ top_a, top_b = sprRobot.rectDbl.side(FloatRect.SideType.TOP) bottom_a, bottom_b = sprRobot.rectDbl.side(FloatRect.SideType.BOTTOM) mid_top = Point(x=(top_a.x + top_b.x) / 2, y=(top_a.y + top_b.y) / 2) mid_bot = Point(x=(bottom_a.x + bottom_b.x) / 2, y=(bottom_a.y + bottom_b.y) / 2) """ Define list of things to check with lidar """ other_bots = filter(lambda x: not x is sprRobot, self.lstRobots) lst_objects = list(map(lambda x: x.rectDbl, other_bots)) + [self.rect_walls] lidar_front, lidar_back = TrashyPhysics.two_way_lidar_rect( mid_bot, mid_top, lst_objects) return [ float(sprRobot.dblRotation), float(ball_angle), float(ball_dist), float(lidar_front), float(lidar_back) ]
def ball_robot_collided(ball: 'Ball', bot: 'Robot') -> bool: """ If the ball/robot collided, either: (1) One of the corners of the bot is inside the ball -OR- (2) One of the edges of the bot is intersecting the circle -OR- (3) Entire ball is within the bot 3 should never happen (unless our ball speed goes nuts) and, if it does, the collision logic won't work anyways so... ignore for now. """ for corner in bot.rectDbl.corners: if distance(corner, ball.rectDbl.center) < const.BALL_RADIUS: return True # get diameters parallel/perp to the robot sides _rectBallInner.center = ball.rectDbl.center _rectBallInner.rotation = bot.rectDbl.rotation + 45 lst_diameters = [ Line(a=_rectBallInner.corner(FloatRect.CornerType.TOP_LEFT), b=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_RIGHT)), Line(a=_rectBallInner.corner(FloatRect.CornerType.TOP_RIGHT), b=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_LEFT)) ] # if those lines intersect, ball is colliding for side in bot.rectDbl.sides: for diameter in lst_diameters: tpl_i = get_line_intersection(side, diameter) if point_within_line(tpl_i, side) and point_within_line(tpl_i, diameter): return True return False
def on_step_end(self): super(ChasePosBall, self).on_step_end() for sprRobot in self.lstHappyBots: for sprBall in self.lstPosBalls: dist_now = distance(sprRobot.rectDbl.center, sprBall.rectDbl.center) dist_prior = distance(sprRobot.rectDblPriorStep.center, sprBall.rectDbl.center) self.reward_happy += ( dist_prior - dist_now) * const.POINTS_ROBOT_TRAVEL_MULT for sprRobot in self.lstGrumpyBots: for sprBall in self.lstPosBalls: dist_now = distance(sprRobot.rectDbl.center, sprBall.rectDbl.center) dist_prior = distance(sprRobot.rectDblPriorStep.center, sprBall.rectDbl.center) self.reward_grumpy += ( dist_prior - dist_now) * const.POINTS_ROBOT_TRAVEL_MULT
def __ponder(): Stephen.__assignments = {} lst_dist = [] for ball in Stephen.__env.lstBalls: if Stephen.__env.sprGrumpyGoal.ball_in_goal(ball) or \ Stephen.__env.sprHappyGoal.ball_in_goal(ball): pass # ignore else: for stephen in Stephen.__hive: lst_dist.append( (stephen, ball, distance(ball.rectDbl.center, stephen.robot.rectDbl.center)) ) lst_dist.sort(key=lambda s: s[2]) # sort by distance, asc claimed_balls = set() for stephen, ball, _ in lst_dist: if ball in claimed_balls: pass elif stephen in Stephen.__assignments: pass else: claimed_balls.add(ball) Stephen.__assignments[stephen] = ball
def get_game_state(self, int_team: int = None, obj_robot: Robot = None, obj_ball: Ball = None) -> np.ndarray: """ Validate """ if int_team == const.TEAM_HAPPY and obj_robot is None and len( self.lstHappyBots) == 0: return None if int_team == const.TEAM_GRUMPY and obj_robot is None and len( self.lstGrumpyBots) == 0: return None if obj_ball is None: obj_ball = self.lstPosBalls[0] if int_team is not None and obj_robot is not None: assert (obj_robot.intTeam == int_team) elif int_team is None and obj_robot is None: int_team = const.TEAM_HAPPY obj_robot = self.lstHappyBots[0] elif int_team is None: # and obj_robot is not None int_team = obj_robot.intTeam elif obj_robot is None: # and int_team is not None obj_robot = self.lstHappyBots[ 0] if int_team == const.TEAM_HAPPY else self.lstGrumpyBots[0] """ DO LIDAR Front of the robot = Right side of rect (0 rotation) Left side of robot = Top side of rect (90) etc. """ # TODO this shit is all f****d up # SideType is probably wrong # BUT you could still train with it, technically # it is reporting all the right information, it's just # not in the order that I'm assuming it is here... :( front_a, front_b = obj_robot.rectDbl.side(FloatRect.SideType.RIGHT) back_a, back_b = obj_robot.rectDbl.side(FloatRect.SideType.LEFT) mid_front = Point(x=(front_a.x + front_b.x) / 2, y=(front_a.y + front_b.y) / 2) mid_back = Point(x=(back_a.x + back_b.x) / 2, y=(back_a.y + back_b.y) / 2) """ Define list of things to check with lidar """ other_bots = filter(lambda x: not x is obj_robot, self.lstRobots) lst_objects = list(map(lambda x: x.rectDbl, other_bots)) + [self.rect_walls] lidar_front, lidar_back = TrashyPhysics.two_way_lidar_rect( mid_back, mid_front, lst_objects) lidar_front_l, lidar_back_r = TrashyPhysics.two_way_lidar_rect( obj_robot.rectDbl.corner(FloatRect.CornerType.BOTTOM_LEFT), obj_robot.rectDbl.corner(FloatRect.CornerType.TOP_RIGHT), lst_objects) lidar_front_r, lidar_back_l = TrashyPhysics.two_way_lidar_rect( obj_robot.rectDbl.corner(FloatRect.CornerType.TOP_LEFT), obj_robot.rectDbl.corner(FloatRect.CornerType.BOTTOM_RIGHT), lst_objects) # Cap lidar at... lidar_cap = 150 lidar_front = min(lidar_front, lidar_cap) lidar_back = min(lidar_back, lidar_cap) lidar_front_l = min(lidar_front_l, lidar_cap) lidar_front_r = min(lidar_front_r, lidar_cap) lidar_back_r = min(lidar_back_r, lidar_cap) lidar_back_l = min(lidar_back_l, lidar_cap) """ Gather remaining obs """ ball_angle = angle_degrees(obj_robot.rectDbl.center, obj_ball.rectDbl.center) ball_dist = distance(obj_robot.rectDbl.center, obj_ball.rectDbl.center) ball_dist = min(ball_dist, lidar_cap) goal_angle = angle_degrees(obj_robot.rectDbl.center, (const.ARENA_WIDTH, const.ARENA_HEIGHT)) bot_angle = obj_robot.dblRotation goal_dist_cap = max(const.GOAL_WIDTH, const.GOAL_HEIGHT) + lidar_cap bad_goal_dist = distance(obj_robot.rectDbl.center, (0, 0)) good_goal_dist = distance(obj_robot.rectDbl.center, (const.ARENA_WIDTH, const.ARENA_HEIGHT)) if good_goal_dist <= bad_goal_dist: goal_dist = min(good_goal_dist, goal_dist_cap) else: goal_dist = -1 * min(bad_goal_dist, goal_dist_cap) """ Standard output is for Happy bot chasing a Positive ball. If team is Grumpy, flip it. If ball is Negative, flip it. """ if (int_team == const.TEAM_HAPPY and obj_ball.is_negative) or \ (int_team == const.TEAM_GRUMPY and obj_ball.is_positive): # flip the observation goal_dist *= -1 ball_angle = (ball_angle + 180) % 360 goal_angle = (goal_angle + 180) % 360 bot_angle = (bot_angle + 180) % 360 return np.asarray([ float(bot_angle), float(ball_angle), float(ball_dist), float(goal_angle), float(goal_dist), float(lidar_front), float(lidar_front_l), float(lidar_front_r), float(lidar_back), float(lidar_back_l), float(lidar_back_r) ])
def apply_force_to_ball(spr_robot: 'Robot', spr_ball: 'Ball') -> None: rect_bot = spr_robot.rectDbl # type: FloatRect rect_bot_prev = spr_robot.rectDblPriorFrame # type: FloatRect # create list of diameter lines that are parallel/perpendicular to the sides of the bot's position _rectBallInner.rotation = 45 + rect_bot.rotation # fudging this for simplicity's sake _rectBallInner.center = spr_ball.rectDbl.center lst_diameters = [ Line( a=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_LEFT), b=_rectBallInner.corner(FloatRect.CornerType.TOP_RIGHT) ), Line( a=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_RIGHT), b=_rectBallInner.corner(FloatRect.CornerType.TOP_LEFT) ) ] """ CHECK FOR SURFACE COLLISION """ # if either of these lines intersect with a side of the bot, # treat this as a "surface collision" and not a corner collision dbl_contact_buffer = .5 for enm_side in FloatRect.SideType: bot_side = rect_bot.side(enm_side) bot_side_prev = rect_bot_prev.side(enm_side) for diameter in lst_diameters: tpl_i = get_line_intersection(bot_side, diameter) if point_within_line(tpl_i, bot_side) and point_within_line(tpl_i, diameter, buffer=dbl_contact_buffer): dist_a = distance(diameter.a, rect_bot.center) dist_b = distance(diameter.b, rect_bot.center) tpl_cp_ball = diameter.a if dist_a < dist_b else diameter.b tpl_cp_ball_opp = diameter.a if dist_a >= dist_b else diameter.b tpl_contact_buffer = Vec2D( x=(tpl_cp_ball_opp.x - tpl_cp_ball.x) * dbl_contact_buffer / (const.BALL_RADIUS + const.BALL_RADIUS), y=(tpl_cp_ball_opp.y - tpl_cp_ball.y) * dbl_contact_buffer / (const.BALL_RADIUS + const.BALL_RADIUS) ) # Calculate minimum distance for ball to leave the robot according to current contact vector tpl_contact = Vec2D(x=tpl_i[0] - tpl_cp_ball.x, y=tpl_i[1] - tpl_cp_ball.y) spr_ball.dbl_force_x += tpl_contact.x + tpl_contact_buffer.x spr_ball.dbl_force_y += tpl_contact.y + tpl_contact_buffer.y spr_ball.lngFrameMass = max(spr_robot.lngFrameMass, spr_ball.lngFrameMass) return # can only be one pending collision point with this logic """ CHECK FOR CORNER COLLISION """ for enm_corner in FloatRect.CornerType: bot_corner = rect_bot.corner(enm_corner) dbl_dist_center = distance(bot_corner, spr_ball.rectDbl.center) if dbl_dist_center < const.BALL_RADIUS + dbl_contact_buffer: bot_corner_prev = rect_bot_prev.corner(enm_corner) # Let's just... fudge this a bit... instead of actually calc'ing (weighted average) tpl_contact = Vec2D( x=spr_ball.rectDbl.centerx - (bot_corner.x * 3 + bot_corner_prev.x) / 4, y=spr_ball.rectDbl.centery - (bot_corner.y * 3 + bot_corner_prev.y) / 4) # Fudge this a bit, since we're not calc'ing the exact contact point on the ball contact_dist = distance((0, 0), tpl_contact) tpl_contact_buffer = Vec2D( x=tpl_contact.x * dbl_contact_buffer / contact_dist, y=tpl_contact.y * dbl_contact_buffer / contact_dist ) exit_dist = (const.BALL_RADIUS - dbl_dist_center) * 1.2 spr_ball.dbl_force_x += (tpl_contact.x * exit_dist / contact_dist) + tpl_contact_buffer.x spr_ball.dbl_force_y += (tpl_contact.y * exit_dist / contact_dist) + tpl_contact_buffer.y spr_ball.lngFrameMass = max(spr_robot.lngFrameMass, spr_ball.lngFrameMass) return
def balls_collided(spr1: 'Ball', spr2: 'Ball'): return distance(spr1.rectDbl.center, spr2.rectDbl.center) <= const.BALL_RADIUS + const.BALL_RADIUS
def bounce_balls(ball1: 'Ball', ball2: 'Ball') -> None: if ball1.rectDbl.center == ball2.rectDbl.center: raise Exception("Really tho?? The balls are in the EXACT same spot????") # Determine vector between two centers vec_1_2 = Vec2D(ball2.rectDbl.centerx - ball1.rectDbl.centerx, ball2.rectDbl.centery - ball1.rectDbl.centery) dist_1_2 = distance((0,0), vec_1_2) # Create vector in that direction of length radius vec_radius = Vec2D(vec_1_2.x * const.BALL_RADIUS / dist_1_2, vec_1_2.y * const.BALL_RADIUS / dist_1_2) # Determine point on Ball 1 closest to the center of Ball 2 tplPnt1 = Point(x=ball1.rectDbl.centerx + vec_radius.x, y=ball1.rectDbl.centery + vec_radius.y) # and vice versa tplPnt2 = Point(x=ball2.rectDbl.centerx - vec_radius.x, y=ball2.rectDbl.centery - vec_radius.y) buffer = 1.1 dblHalfDeltaX = (tplPnt2.x - tplPnt1.x) / 2 dblHalfDeltaY = (tplPnt2.y - tplPnt1.y) / 2 if ball1.lngFrameMass == ball2.lngFrameMass: """ Move balls out of each other. """ ball1.rectDbl.centerx += dblHalfDeltaX * buffer ball1.rectDbl.centery += dblHalfDeltaY * buffer ball2.rectDbl.centerx -= dblHalfDeltaX * buffer ball2.rectDbl.centery -= dblHalfDeltaY * buffer else: """ Only one ball moves. """ if ball1.lngFrameMass > ball2.lngFrameMass: ball2.rectDbl.centerx += (tplPnt1.x - tplPnt2.x) * buffer ball2.rectDbl.centery += (tplPnt1.y - tplPnt2.y) * buffer ball2.lngFrameMass = ball1.lngFrameMass # and now they are one else: ball1.rectDbl.centerx += (tplPnt2.x - tplPnt1.x) * buffer ball1.rectDbl.centery += (tplPnt2.y - tplPnt1.y) * buffer ball1.lngFrameMass = ball2.lngFrameMass # and now they are one """ Bounce! """ # Let's conserve some momentum with vector projection (ya i totally remembered how to do this) # projection of 1's velocity onto contact vector: tplVect1to2 = Vec2D(x=ball2.rectDbl.centerx - ball1.rectDbl.centerx, y=ball2.rectDbl.centery - ball1.rectDbl.centery) tplVect2to1 = Vec2D(x=tplVect1to2.x * -1, y=tplVect1to2.y * -1) dblCenterDist_Sq = tplVect1to2.x * tplVect1to2.x + tplVect1to2.y * tplVect1to2.y dblProjClcTerm1 = Div0(tplVect1to2.x * ball1.dbl_velocity_x + tplVect1to2.y * ball1.dbl_velocity_y, dblCenterDist_Sq) tplVel1 = Vec2D(x=dblProjClcTerm1 * tplVect1to2.x, y=dblProjClcTerm1 * tplVect1to2.y) dblProjClcTerm2 = Div0(tplVect2to1.x * ball2.dbl_velocity_x + tplVect2to1.y * ball2.dbl_velocity_y, dblCenterDist_Sq) tplVel2 = Vec2D(x=dblProjClcTerm2 * tplVect2to1.x, y=dblProjClcTerm2 * tplVect2to1.y) tplDiff = Vec2D(x=tplVel1.x - tplVel2.x, y=tplVel1.y - tplVel2.y) ball1.dbl_velocity_x -= tplDiff.x * const.BOUNCE_K_BALL ball1.dbl_velocity_y -= tplDiff.y * const.BOUNCE_K_BALL ball2.dbl_velocity_x += tplDiff.x * const.BOUNCE_K_BALL ball2.dbl_velocity_y += tplDiff.y * const.BOUNCE_K_BALL """ ... but also account for force """ if ball1.dbl_force_x > 0: ball1.dbl_velocity_x = max(ball1.dbl_velocity_x, ball1.dbl_force_x) elif ball1.dbl_force_x < 0: ball1.dbl_velocity_x = min(ball1.dbl_velocity_x, ball1.dbl_force_x) if ball1.dbl_force_y > 0: ball1.dbl_velocity_y = max(ball1.dbl_velocity_y, ball1.dbl_force_y) elif ball1.dbl_force_y < 0: ball1.dbl_velocity_y = min(ball1.dbl_velocity_y, ball1.dbl_force_y) if ball2.dbl_force_x > 0: ball2.dbl_velocity_x = max(ball2.dbl_velocity_x, ball2.dbl_force_x) elif ball2.dbl_force_x < 0: ball2.dbl_velocity_x = min(ball2.dbl_velocity_x, ball2.dbl_force_x) if ball2.dbl_force_y > 0: ball2.dbl_velocity_y = max(ball2.dbl_velocity_y, ball2.dbl_force_y) elif ball2.dbl_force_y < 0: ball2.dbl_velocity_y = min(ball2.dbl_velocity_y, ball2.dbl_force_y)
def bounce_ball_off_bot(spr_robot: 'Robot', spr_ball: 'Ball'): if spr_ball.dbl_velocity_x == spr_ball.dbl_velocity_y == 0: return # no velocity to "bounce" dbl_contact_buffer = .5 rect_bot = spr_robot.rectDbl # type: FloatRect rect_bot_prev = spr_robot.rectDblPriorFrame # type: FloatRect # create list of diameter lines that are parallel/perpendicular to the sides of the bot's position _rectBallInner.rotation = 45 + rect_bot.rotation # fudging this for simplicity's sake _rectBallInner.center = spr_ball.rectDbl.center lst_diameters = [ Line( a=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_LEFT), b=_rectBallInner.corner(FloatRect.CornerType.TOP_RIGHT) ), Line( a=_rectBallInner.corner(FloatRect.CornerType.BOTTOM_RIGHT), b=_rectBallInner.corner(FloatRect.CornerType.TOP_LEFT) ) ] """ CHECK FOR SURFACE COLLISION """ # if either of these lines intersect with a side of the bot, # treat this as a "surface collision" and not a corner collision for enm_side in FloatRect.SideType: bot_side = rect_bot.side(enm_side) bot_side_prev = rect_bot_prev.side(enm_side) for diameter in lst_diameters: tpl_i = get_line_intersection(bot_side, diameter) tpl_i_prev = get_line_intersection(bot_side_prev, diameter) if point_within_line(tpl_i, bot_side) and point_within_line(tpl_i, diameter): dist_a = distance(diameter.a, tpl_i_prev) dist_b = distance(diameter.b, tpl_i_prev) tpl_cp_ball = diameter.a if dist_a < dist_b else diameter.b tpl_cp_ball_opp = diameter.a if dist_a >= dist_b else diameter.b tpl_contact = Vec2D(x=tpl_cp_ball_opp.x - tpl_cp_ball.x, y=tpl_cp_ball_opp.y - tpl_cp_ball.y) tpl_ball_vel = Vec2D(x=spr_ball.dbl_velocity_x, y=spr_ball.dbl_velocity_y) # Projected components of the ball's velocity dbl_contact_dist_sq = tpl_contact.x ** 2 + tpl_contact.y ** 2 dbl_ball_proj_term = ((tpl_contact.x * tpl_ball_vel.x) + (tpl_contact.y * tpl_ball_vel.y)) \ / dbl_contact_dist_sq tpl_ball_proj = Vec2D(x=dbl_ball_proj_term * tpl_contact.x, y=dbl_ball_proj_term * tpl_contact.y) if (tpl_ball_proj.x < 0 and tpl_contact.x > 0) or (tpl_ball_proj.x > 0 and tpl_contact.x < 0): spr_ball.dbl_velocity_x = -tpl_ball_proj.x * const.BOUNCE_K_WALL * const.BOUNCE_K_ROBOT if (tpl_ball_proj.y < 0 and tpl_contact.y > 0) or (tpl_ball_proj.y > 0 and tpl_contact.y < 0): spr_ball.dbl_velocity_y = -tpl_ball_proj.y * const.BOUNCE_K_WALL * const.BOUNCE_K_ROBOT # Create a buffer vector tpl_contact_buffer = Vec2D( x=tpl_contact.x * dbl_contact_buffer / dbl_contact_dist_sq**.5, y=tpl_contact.y * dbl_contact_buffer / dbl_contact_dist_sq**.5 ) tpl_exit_point = Point(*tpl_i) spr_ball.rectDbl.centerx += (tpl_exit_point.x - tpl_cp_ball.x) + tpl_contact_buffer.x spr_ball.rectDbl.centery += (tpl_exit_point.y - tpl_cp_ball.y) + tpl_contact_buffer.y return """ CHECK FOR CORNER COLLISION """ for enm_corner in FloatRect.CornerType: bot_corner = rect_bot.corner(enm_corner) dbl_dist_center = distance(bot_corner, spr_ball.rectDbl.center) if dbl_dist_center < const.BALL_RADIUS: bot_corner_prev = rect_bot_prev.corner(enm_corner) # Let's just... fudge this a bit... instead of actually calc'ing (weighted average) tpl_cp_bot = Point(x=(bot_corner.x*3 + bot_corner_prev.x)/4, y=(bot_corner.y*3 + bot_corner_prev.y)/4) tpl_contact = Vec2D(x=spr_ball.rectDbl.centerx - tpl_cp_bot.x, y=spr_ball.rectDbl.centery - tpl_cp_bot.y) # Project the ball vel onto the contact vector tpl_ball_vel = Vec2D(x=spr_ball.dbl_velocity_x, y=spr_ball.dbl_velocity_y) dbl_contact_dist_sq = tpl_contact.x ** 2 + tpl_contact.y ** 2 dbl_ball_proj_term = ((tpl_contact.x * tpl_ball_vel.x) + (tpl_contact.y * tpl_ball_vel.y)) \ / dbl_contact_dist_sq tpl_ball_proj = Vec2D(x=dbl_ball_proj_term * tpl_contact.x, y=dbl_ball_proj_term * tpl_contact.y) # Apply bounce, if applicable if (tpl_ball_proj.x < 0 and tpl_contact.x > 0) or (tpl_ball_proj.x > 0 and tpl_contact.x < 0): spr_ball.dbl_velocity_x = -tpl_ball_proj.x * const.BOUNCE_K_WALL * const.BOUNCE_K_ROBOT if (tpl_ball_proj.y < 0 and tpl_contact.y > 0) or (tpl_ball_proj.y > 0 and tpl_contact.y < 0): spr_ball.dbl_velocity_y = -tpl_ball_proj.y * const.BOUNCE_K_WALL * const.BOUNCE_K_ROBOT # Resolve collision - contact point is fudged, fudge travel dist accordingly (by using bot corner prev) dbl_exit_dist = distance(bot_corner_prev, spr_ball.rectDbl.center) dbl_contact_dist = dbl_contact_dist_sq ** .5 spr_ball.rectDbl.centerx += tpl_contact.x * dbl_exit_dist / dbl_contact_dist spr_ball.rectDbl.centery += tpl_contact.y * dbl_exit_dist / dbl_contact_dist return
def _calc_ball_dist_sum(self): # Grumpy's goal is in the 0,0 corner, therefore higher distance is better for Grumpy team return sum( map(lambda x: distance((0, 0), x.rectDbl.center), self.lstPosBalls))