Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dbd64ef
convert stop play to fsm
annieisawesome2 Feb 21, 2026
890bad8
fix error
annieisawesome2 Feb 21, 2026
e730154
remove guard from getNextTactic
annieisawesome2 Feb 21, 2026
f3b89a6
add logic comment back
annieisawesome2 Feb 21, 2026
0c41f78
call stop play fsm
annieisawesome2 Feb 21, 2026
309c59c
Merge branch 'master' into stopplay-intelligent-positioning-fsm
StarrryNight Feb 28, 2026
863068b
update constructor
annieisawesome2 Mar 1, 2026
89c1b3b
Passing ai_config_ptr into each MoveTactic constructor
annieisawesome2 Mar 1, 2026
dd18187
fix error
annieisawesome2 Mar 1, 2026
674f549
removeal from transition table
annieisawesome2 Mar 1, 2026
ab8d2a2
add stop play python test
annieisawesome2 Mar 1, 2026
a04b21f
override cpp test
annieisawesome2 Mar 2, 2026
b9ec397
fix failure
annieisawesome2 Mar 2, 2026
b2a23f3
increase wait time for slowdown
annieisawesome2 Mar 2, 2026
eafbaf9
rename py test
annieisawesome2 Mar 3, 2026
9e3b88f
optimization of stop play positioning
annieisawesome2 Mar 3, 2026
cf75597
we can't add 2 points
annieisawesome2 Mar 3, 2026
362c8d7
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Mar 3, 2026
d35de68
nits
annieisawesome2 Mar 6, 2026
a3e4dec
Merge branch 'stopplay-intelligent-positioning-fsm' of https://github…
annieisawesome2 Mar 6, 2026
867e2cf
make value a constant
annieisawesome2 Mar 6, 2026
e25b3bf
reposition central bot and added tests for visualizing positions
annieisawesome2 Mar 8, 2026
b72cfa7
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Mar 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/fsm-diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ Terminate:::terminate --> Terminate:::terminate

```

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure where this came from.. had a divergent branch and did a merge..

## [StopPlayFSM](/src/software/ai/hl/stp/play/stop_play_fsm.h)

```mermaid

stateDiagram-v2
classDef terminate fill:white,color:black,font-weight:bold
direction LR
[*] --> StopState
StopState --> StopState : <i>updateStopPosition</i>

```

## [BallPlacementPlayFSM](/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_fsm.h)

```mermaid
Expand Down
33 changes: 28 additions & 5 deletions src/software/ai/hl/stp/play/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,20 @@ cc_library(

cc_library(
name = "stop_play",
srcs = ["stop_play.cpp"],
hdrs = ["stop_play.h"],
srcs = [
"stop_play.cpp",
"stop_play_fsm.cpp",
],
hdrs = [
"stop_play.h",
"stop_play_fsm.h",
],
deps = [
":play",
"//shared:constants",
"//software/ai/evaluation:enemy_threat",
"//software/ai/hl/stp/tactic/crease_defender:crease_defender_tactic",
"//software/ai/hl/stp/tactic/goalie:goalie_tactic",
"//software/ai/hl/stp/tactic/move:move_tactic",
"//software/logger",
"//software/util/generic_factory",
],
alwayslink = True,
Expand Down Expand Up @@ -175,7 +179,7 @@ py_test(
)

cc_test(
name = "stop_play_test",
name = "stop_play_cpp_test",
srcs = ["stop_play_test.cpp"],
deps = [
"//shared/test_util:tbots_gtest_main",
Expand All @@ -189,6 +193,25 @@ cc_test(
],
)

py_test(
name = "stop_play_test",
srcs = [
"stop_play_test.py",
],
# The default main would be stop_play_test.py; override to our file.
main = "stop_play_test.py",
# TODO (#2619) Remove tag to run in parallel
tags = [
"exclusive",
],
deps = [
"//software:conftest",
"//software/simulated_tests:speed_threshold_helpers",
"//software/simulated_tests:validation",
requirement("pytest"),
],
)

cc_test(
name = "shoot_or_chip_play_cpp_test",
srcs = ["shoot_or_chip_play_test.cpp"],
Expand Down
107 changes: 7 additions & 100 deletions src/software/ai/hl/stp/play/stop_play.cpp
Original file line number Diff line number Diff line change
@@ -1,116 +1,23 @@
#include "software/ai/hl/stp/play/stop_play.h"

#include "shared/constants.h"
#include "software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic.h"
#include "software/ai/hl/stp/tactic/goalie/goalie_tactic.h"
#include "software/ai/hl/stp/tactic/move/move_tactic.h"
#include "software/util/generic_factory/generic_factory.h"

StopPlay::StopPlay(std::shared_ptr<const TbotsProto::AiConfig> ai_config_ptr)
: Play(ai_config_ptr, true)
: PlayBase<StopPlayFSM>(ai_config_ptr, true)
{
goalie_tactic = std::make_shared<GoalieTactic>(ai_config_ptr);
goalie_tactic->updateMaxSpeedMode(TbotsProto::MaxAllowedSpeedMode::STOP_COMMAND);
}

void StopPlay::getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr)
{
// Robot assignments for the Stop Play
// - 1 robot will be the goalie
// - 2 robots will assist the goalie in blocking the ball, they will snap
// to the best fit semicircle around the defense area
// - 3 robots will stay within 0.5m of the ball, evenly spaced, also blocking the
// goal
//
// If x represents the ball and G represents the goalie, the following, also blocking
// the goal diagram depicts a possible outcome of this play
//
// +--------------------+--------------------+
// | | |
// | 4 x | |
// | 0 2 | |
// +--+ 1 3 | +--+
// | | | | |
// |G | +-+-+ | |
// | | | | | |
// | | +-+-+ | |
// | | | | |
// +--+ | +--+
// | | |
// | | |
// | | |
// +--------------------+--------------------+


TbotsProto::MaxAllowedSpeedMode stop_mode =
TbotsProto::MaxAllowedSpeedMode::STOP_COMMAND;

std::vector<std::shared_ptr<MoveTactic>> move_tactics = {
std::make_shared<MoveTactic>(ai_config_ptr),
std::make_shared<MoveTactic>(ai_config_ptr),
std::make_shared<MoveTactic>(ai_config_ptr)};

goalie_tactic = std::make_shared<GoalieTactic>(ai_config_ptr);
goalie_tactic->updateMaxSpeedMode(stop_mode);
std::array<std::shared_ptr<CreaseDefenderTactic>, 2> crease_defender_tactics = {
std::make_shared<CreaseDefenderTactic>(ai_config_ptr),
std::make_shared<CreaseDefenderTactic>(ai_config_ptr),
};

do
{
PriorityTacticVector result = {{}};

// a unit vector from the center of the goal to the ball, this vector will be used
// for positioning all the robots (excluding the goalie). The positioning vector
// will be used to position robots tangent to the goal_to_ball_unit_vector
Vector goal_to_ball_unit_vector =
(world_ptr->field().friendlyGoalCenter() - world_ptr->ball().position())
.normalize();
Vector robot_positioning_unit_vector = goal_to_ball_unit_vector.perpendicular();

// ball_defense_point_center is a point on the circle around the ball that the
// line from the center of the goal to the ball intersects. A robot will be placed
// on that line, and the other two will be on either side
// We add an extra robot radius as a buffer to be extra safe we don't break any
// rules by getting too close
Point ball_defense_point_center =
world_ptr->ball().position() +
(0.5 + 2 * ROBOT_MAX_RADIUS_METERS) * goal_to_ball_unit_vector;
Point ball_defense_point_left =
ball_defense_point_center -
robot_positioning_unit_vector * 4 * ROBOT_MAX_RADIUS_METERS;
Point ball_defense_point_right =
ball_defense_point_center +
robot_positioning_unit_vector * 4 * ROBOT_MAX_RADIUS_METERS;

move_tactics.at(0)->updateControlParams(
ball_defense_point_center,
(world_ptr->ball().position() - ball_defense_point_center).orientation(),
stop_mode, TbotsProto::ObstacleAvoidanceMode::SAFE);
move_tactics.at(1)->updateControlParams(
ball_defense_point_left,
(world_ptr->ball().position() - ball_defense_point_left).orientation(),
stop_mode, TbotsProto::ObstacleAvoidanceMode::SAFE);
move_tactics.at(2)->updateControlParams(
ball_defense_point_right,
(world_ptr->ball().position() - ball_defense_point_right).orientation(),
stop_mode, TbotsProto::ObstacleAvoidanceMode::SAFE);

std::get<0>(crease_defender_tactics)
->updateControlParams(world_ptr->ball().position(),
TbotsProto::CreaseDefenderAlignment::LEFT, stop_mode,
TbotsProto::BallStealMode::IGNORE);
std::get<1>(crease_defender_tactics)
->updateControlParams(world_ptr->ball().position(),
TbotsProto::CreaseDefenderAlignment::RIGHT, stop_mode,
TbotsProto::BallStealMode::IGNORE);
}

// insert all the tactics to the result
result[0].emplace_back(std::get<0>(crease_defender_tactics));
result[0].emplace_back(std::get<1>(crease_defender_tactics));
result[0].insert(result[0].end(), move_tactics.begin(), move_tactics.end());
yield(result);
} while (true);
void StopPlay::updateTactics(const PlayUpdate &play_update)
{
fsm.process_event(StopPlayFSM::Update(control_params, play_update));
}

// Register this play in the genericFactory
Expand Down
5 changes: 4 additions & 1 deletion src/software/ai/hl/stp/play/stop_play.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

#include "proto/parameters.pb.h"
#include "software/ai/hl/stp/play/play.h"
#include "software/ai/hl/stp/play/play_base.hpp"
#include "software/ai/hl/stp/play/stop_play_fsm.h"

/**
* This Play moves our robots in a formation while keeping them at least 0.5m from the
* ball. Additionally, the robots are limited to moving no more than 1.5m/s. This Play is
* used during the referee "Stop" command.
*/
class StopPlay : public Play
class StopPlay : public PlayBase<StopPlayFSM>
{
public:
StopPlay(std::shared_ptr<const TbotsProto::AiConfig> ai_config_ptr);

void getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr) override;
void updateTactics(const PlayUpdate &play_update) override;
};
104 changes: 104 additions & 0 deletions src/software/ai/hl/stp/play/stop_play_fsm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "software/ai/hl/stp/play/stop_play_fsm.h"

#include "shared/constants.h"

StopPlayFSM::StopPlayFSM(std::shared_ptr<const TbotsProto::AiConfig> ai_config_ptr)
: ai_config_ptr(ai_config_ptr),
move_tactics{std::make_shared<MoveTactic>(ai_config_ptr),
std::make_shared<MoveTactic>(ai_config_ptr),
std::make_shared<MoveTactic>(ai_config_ptr)},
crease_defender_tactics{std::make_shared<CreaseDefenderTactic>(ai_config_ptr),
std::make_shared<CreaseDefenderTactic>(ai_config_ptr)}
{
}

void StopPlayFSM::updateStopPosition(const Update& event)
{
// Robot assignments for the Stop Play
// - 1 robot will be the goalie
// - 2 robots will assist the goalie in blocking the ball, they will snap
// to the best fit semicircle around the defense area
// - 2 robots will stay within 0.5m of the ball, curved around the goal-to-ball
// line
// - 1 robot will position more centrally in our half to help stretch the field
//
// If x represents the ball and G represents the goalie, the following diagram
// depicts a possible outcome of this play
//
// +--------------------+--------------------+
// | | |
// | 4 x | |
// | 0 2 | |
// +--+ 1 | +--+
// | | | | |
// |G | +-+-+ | |
// | | | | | |
// | | +-+-+ | |
// | | 3 | | |
// +--+ | +--+
// | | |
// | | |
// | | |
// +--------------------+--------------------+
const WorldPtr& world_ptr = event.common.world_ptr;
TbotsProto::MaxAllowedSpeedMode stop_mode =
TbotsProto::MaxAllowedSpeedMode::STOP_COMMAND;

// A unit vector from the center of the goal to the ball; used for
// positioning all non-goalie robots. The perpendicular is used to place
// robots tangent to the goal-to-ball line.
Vector goal_to_ball_unit_vector =
(world_ptr->field().friendlyGoalCenter() - world_ptr->ball().position())
.normalize();
Vector robot_positioning_unit_vector = goal_to_ball_unit_vector.perpendicular();

// Points on the circle around the ball: center on the goal-ball line,
// and one offset. Extra robot radius buffer to stay within rules.
Point ball_defense_point_center =
world_ptr->ball().position() +
(0.5 + 2 * ROBOT_MAX_RADIUS_METERS) * goal_to_ball_unit_vector;
Point ball_defense_point_right =
ball_defense_point_center +
robot_positioning_unit_vector * 4 * ROBOT_MAX_RADIUS_METERS;

// A spread-out position for the support robot: we take the point along the
// goal-to-ball line at CENTRAL_SUPPORT_FRACTION, then mirror it across the
// field center so the support robot is on the opposite side of the field
// from the ball (stretching the field).
constexpr double CENTRAL_SUPPORT_FRACTION = 0.5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in header file

Point point_along_goal_ball =
world_ptr->field().friendlyGoalCenter() +
(world_ptr->ball().position() - world_ptr->field().friendlyGoalCenter()) *
CENTRAL_SUPPORT_FRACTION;

Point field_center = world_ptr->field().centerPoint();
Point central_support_point = field_center + (field_center - point_along_goal_ball);

move_tactics.at(0)->updateControlParams(
ball_defense_point_center,
(world_ptr->ball().position() - ball_defense_point_center).orientation(),
stop_mode, TbotsProto::ObstacleAvoidanceMode::SAFE);
move_tactics.at(1)->updateControlParams(
ball_defense_point_right,
(world_ptr->ball().position() - ball_defense_point_right).orientation(),
stop_mode, TbotsProto::ObstacleAvoidanceMode::SAFE);
move_tactics.at(2)->updateControlParams(
central_support_point,
(world_ptr->ball().position() - central_support_point).orientation(), stop_mode,
TbotsProto::ObstacleAvoidanceMode::SAFE);

std::get<0>(crease_defender_tactics)
->updateControlParams(world_ptr->ball().position(),
TbotsProto::CreaseDefenderAlignment::LEFT, stop_mode,
TbotsProto::BallStealMode::IGNORE);
std::get<1>(crease_defender_tactics)
->updateControlParams(world_ptr->ball().position(),
TbotsProto::CreaseDefenderAlignment::RIGHT, stop_mode,
TbotsProto::BallStealMode::IGNORE);

PriorityTacticVector result = {{}};
result[0].emplace_back(std::get<0>(crease_defender_tactics));
result[0].emplace_back(std::get<1>(crease_defender_tactics));
result[0].insert(result[0].end(), move_tactics.begin(), move_tactics.end());
event.common.set_tactics(result);
}
62 changes: 62 additions & 0 deletions src/software/ai/hl/stp/play/stop_play_fsm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include <memory>

#include "proto/parameters.pb.h"
#include "shared/constants.h"
#include "software/ai/hl/stp/play/play_fsm.hpp"
#include "software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic.h"
#include "software/ai/hl/stp/tactic/move/move_tactic.h"

struct StopPlayFSM
{
struct ControlParams
{
};
class StopState;

struct Update
{
Update(const ControlParams& control_params, const PlayUpdate& common)
: control_params(control_params), common(common)
{
}
ControlParams control_params;
PlayUpdate common;
};

/**
* Creates a Stop Play FSM
*
* @param ai_config_ptr shared pointer to the play config for this FSM
*/
explicit StopPlayFSM(std::shared_ptr<const TbotsProto::AiConfig> ai_config_ptr);

/**
* Action to position robots during Stop (goalie handled by Play; this sets
* crease defenders and robots near the ball).
*
* @param event the StopPlayFSM Update event
*/
void updateStopPosition(const Update& event);

auto operator()()
{
using namespace boost::sml;

DEFINE_SML_STATE(StopState)

DEFINE_SML_EVENT(Update)

DEFINE_SML_ACTION(updateStopPosition)

return make_transition_table(
// src_state + event [guard] / action = dest_state
*StopState_S + Update_E / updateStopPosition_A = StopState_S);
}

private:
std::shared_ptr<const TbotsProto::AiConfig> ai_config_ptr;
std::vector<std::shared_ptr<MoveTactic>> move_tactics;
std::array<std::shared_ptr<CreaseDefenderTactic>, 2> crease_defender_tactics;
};
Loading