Skip to content

Commit 9b4aa32

Browse files
authored
Allow defenders to intercept passes without slowing down (UBC-Thunderbots#3479)
1 parent 499cba6 commit 9b4aa32

6 files changed

Lines changed: 123 additions & 42 deletions

File tree

src/software/ai/evaluation/intercept.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,55 @@ std::optional<std::pair<Point, Duration>> findBestInterceptForBall(const Ball &b
8787

8888
return std::make_pair(best_ball_intercept_pos, time_to_ball_pos);
8989
}
90+
91+
Point findOvershootInterceptPosition(const Robot &robot, const Point intercept_position,
92+
const Field &field, Duration ball_intercept_time,
93+
double step_speed, bool restrict_to_defense_area)
94+
{
95+
Point new_destination = intercept_position;
96+
Point base_position = intercept_position;
97+
Point best_position = intercept_position;
98+
double final_speed = step_speed;
99+
bool finished = false;
100+
double max_speed = robot.robotConstants().robot_max_speed_m_per_s;
101+
double max_acc = robot.robotConstants().robot_max_acceleration_m_per_s_2;
102+
103+
while (!finished)
104+
{
105+
Vector final_velocity =
106+
(new_destination - robot.position()).normalize(final_speed);
107+
108+
double extra_length = (final_speed * final_speed) / (2.0 * max_acc);
109+
new_destination = base_position + final_velocity.normalize(extra_length);
110+
111+
bool in_bounds = contains(field.fieldBoundary(), new_destination);
112+
bool in_defense = field.pointInFriendlyDefenseArea(new_destination);
113+
114+
if ((!restrict_to_defense_area ^ in_defense) && in_bounds)
115+
{
116+
if (restrict_to_defense_area)
117+
{
118+
best_position = new_destination;
119+
}
120+
121+
if (robot.getTimeToPosition(base_position, final_velocity) <
122+
ball_intercept_time)
123+
{
124+
best_position = new_destination;
125+
finished = true;
126+
}
127+
}
128+
else
129+
{
130+
finished = true;
131+
}
132+
133+
final_speed += step_speed;
134+
if (final_speed > max_speed)
135+
{
136+
finished = true;
137+
}
138+
}
139+
140+
return best_position;
141+
}

src/software/ai/evaluation/intercept.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,22 @@
2222
std::optional<std::pair<Point, Duration>> findBestInterceptForBall(const Ball &ball,
2323
const Field &field,
2424
const Robot &robot);
25+
26+
27+
/**
28+
* Attempts to find a reachable overshoot destination for intercepting the ball,
29+
* adjusting final speed in steps up to the robot's max speed.
30+
*
31+
* @param robot The robot attempting to intercept.
32+
* @param intercept_position The ideal intercept point without overshoot.
33+
* @param field The field.
34+
* @param ball_intercept_time Time the ball will take to reach the base position.
35+
* @param step_speed Speed increment for each overshoot iteration.
36+
* @param restrict_to_defense_area Whether to restrict overshoot to the friendly defense
37+
* area.
38+
* @return The best reachable intercept point (possibly overshot), or base_position if no
39+
* improvement.
40+
*/
41+
Point findOvershootInterceptPosition(const Robot &robot, const Point intercept_position,
42+
const Field &field, Duration ball_intercept_time,
43+
double step_speed, bool restrict_to_defense_area);

src/software/ai/hl/stp/tactic/goalie/goalie_fsm.cpp

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "software/ai/hl/stp/tactic/goalie/goalie_fsm.h"
22

33
#include "software/ai/evaluation/find_open_areas.h"
4+
#include "software/ai/evaluation/intercept.h"
45
#include "software/ai/hl/stp/tactic/move_primitive.h"
56
#include "software/math/math_functions.h"
67

@@ -183,53 +184,18 @@ void GoalieFSM::panic(const Update &event)
183184
Angle goalie_orientation =
184185
(event.common.world_ptr->ball().position() - goalie_pos).orientation();
185186

186-
Point save_pos = goalie_pos;
187-
// First we are trying to determine how much time do we have to reach the ball
187+
// Here we are determining how much time do we have to reach the ball
188188
Duration ball_intercept_time = Duration::fromSeconds(
189-
(event.common.world_ptr->ball().position() - save_pos).length() /
189+
(event.common.world_ptr->ball().position() - goalie_pos).length() /
190190
(std::max(std::numeric_limits<double>::epsilon(),
191191
event.common.world_ptr->ball().velocity().length())));
192192

193+
193194
if (event.common.robot.getTimeToPosition(goalie_pos) > ball_intercept_time)
194195
{
195-
Point new_destination = save_pos;
196-
double final_speed = GOALIE_STEP_SPEED_M_PER_S;
197-
bool finished = false;
198-
double max_speed = event.common.robot.robotConstants().robot_max_speed_m_per_s;
199-
double max_acc =
200-
event.common.robot.robotConstants().robot_max_acceleration_m_per_s_2;
201-
while (!finished)
202-
{
203-
Vector final_velocity =
204-
(new_destination - event.common.robot.position()).normalize(final_speed);
205-
206-
// What's happening here is we are using v_o^2=2*d*a to determine the extra
207-
// distance the goalie will be forced to travel if they intercept the ball
208-
// with final_speed and then immediately decelerate
209-
double extra_length = (final_speed * final_speed) / (2.0 * max_acc);
210-
new_destination = save_pos + final_velocity.normalize(extra_length);
211-
if (event.common.world_ptr->field().pointInFriendlyDefenseArea(
212-
new_destination))
213-
{
214-
goalie_pos = new_destination;
215-
if (event.common.robot.getTimeToPosition(save_pos, final_velocity) <
216-
ball_intercept_time)
217-
{
218-
// If we found that the robot can make it before the ball we consider
219-
// the while loop finished
220-
finished = true;
221-
}
222-
}
223-
else
224-
{
225-
finished = true;
226-
}
227-
final_speed += GOALIE_STEP_SPEED_M_PER_S;
228-
if (final_speed > max_speed)
229-
{
230-
finished = true;
231-
}
232-
}
196+
goalie_pos = findOvershootInterceptPosition(
197+
event.common.robot, goalie_pos, event.common.world_ptr->field(),
198+
ball_intercept_time, GOALIE_STEP_SPEED_M_PER_S, true);
233199
}
234200

235201
event.common.set_primitive(std::make_unique<MovePrimitive>(

src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.h"
22

33
#include "proto/message_translation/tbots_protobuf.h"
4+
#include "software/ai/evaluation/intercept.h"
45
#include "software/ai/hl/stp/tactic/move_primitive.h"
56
#include "software/geom/algorithms/closest_point.h"
67

@@ -69,6 +70,30 @@ void PassDefenderFSM::interceptBall(const Update& event)
6970
// that the defender can move to and intercept the pass
7071
intercept_position = closestPoint(
7172
robot_position, Line(ball.position(), ball.position() + ball.velocity()));
73+
74+
// Here we are using v_f^2=v_0^2+2*a*d to calculate the final
75+
// speed and after that the average speed
76+
double ball_vel = (event.common.world_ptr->ball().velocity().length());
77+
double ball_dis =
78+
(event.common.world_ptr->ball().position() - intercept_position).length();
79+
double final_vel =
80+
sqrt(ball_vel * ball_vel +
81+
2 * ball_dis *
82+
BALL_ROLLING_FRICTION_DECELERATION_METERS_PER_SECOND_SQUARED);
83+
double avg_vel = (final_vel + ball_vel) / 2.0;
84+
Duration ball_intercept_time = Duration::fromSeconds(
85+
ball_dis / (std::max(std::numeric_limits<double>::epsilon(), avg_vel)));
86+
87+
// Here we check if we can make it in time to the position and stop in time
88+
// otherwise we will attempt to overshoot the position and intercept it
89+
if (event.common.robot.getTimeToPosition(intercept_position) >
90+
ball_intercept_time)
91+
{
92+
intercept_position = findOvershootInterceptPosition(
93+
event.common.robot, intercept_position,
94+
event.common.world_ptr->field(), ball_intercept_time,
95+
DEFENDER_STEP_SPEED_M_PER_S, false);
96+
}
7297
}
7398

7499
auto face_ball_orientation = (ball.position() - robot_position).orientation();

src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct PassDefenderFSM : public DefenderFSMBase
4040

4141
// The maximum angle difference to determine if ball has been kicked in
4242
// the approximate direction of the defender
43-
static constexpr Angle MAX_PASS_ANGLE_DIFFERENCE = Angle::fromDegrees(30);
43+
static constexpr Angle MAX_PASS_ANGLE_DIFFERENCE = Angle::fromDegrees(35);
4444

4545
// The minimum angle difference between a ball's trajectory and
4646
// pass_orientation for which we can consider a pass to be deflected
@@ -139,5 +139,8 @@ struct PassDefenderFSM : public DefenderFSMBase
139139

140140
private:
141141
Angle pass_orientation;
142+
// The step amount between speeds we check that the defender is observed to
143+
// go at during the interception
144+
static constexpr double DEFENDER_STEP_SPEED_M_PER_S = 0.2;
142145
TbotsProto::PassDefenderConfig pass_defender_config;
143146
};

src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ def test_avoid_intercept_scenario(
209209
tbots_cpp.Point(2.2, 0),
210210
True,
211211
),
212+
# Intercept faster pass angled away from defender, steal
213+
(
214+
tbots_cpp.Point(2, 0),
215+
tbots_cpp.Vector(-4, 0),
216+
tbots_cpp.Point(-2, 1.25),
217+
tbots_cpp.Point(2.2, 0),
218+
True,
219+
),
212220
# Intercept diagonal pass, steal
213221
(
214222
tbots_cpp.Point(-1, -3),
@@ -217,6 +225,14 @@ def test_avoid_intercept_scenario(
217225
tbots_cpp.Point(-0.8, 0),
218226
True,
219227
),
228+
# Intercept faster diagonal pass, steal
229+
(
230+
tbots_cpp.Point(0.75, 0),
231+
tbots_cpp.Vector(-3.5, -2),
232+
tbots_cpp.Point(-3, 0),
233+
tbots_cpp.Point(1, 0),
234+
True,
235+
),
220236
# Enemy too close, no steal
221237
(
222238
tbots_cpp.Point(0, 0),

0 commit comments

Comments
 (0)