diff --git a/docs/fsm-diagrams.md b/docs/fsm-diagrams.md
index 8059b8e10d..c16f08b4d2 100644
--- a/docs/fsm-diagrams.md
+++ b/docs/fsm-diagrams.md
@@ -206,6 +206,20 @@ Terminate:::terminate --> Terminate:::terminate
```
+## [ShootOrChipPlayFSM](/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.h)
+
+```mermaid
+
+stateDiagram-v2
+classDef terminate fill:white,color:black,font-weight:bold
+direction LR
+[*] --> ShootOrChipState
+ShootOrChipState --> ShootOrChipState : [!attackerDone]\nupdateShootOrChip
+ShootOrChipState --> Terminate:::terminate : [attackerDone]\nupdateShootOrChip
+Terminate:::terminate --> Terminate:::terminate : updateShootOrChip
+
+```
+
## [ShootOrPassPlayFSM](/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_fsm.h)
```mermaid
@@ -225,6 +239,33 @@ Terminate:::terminate --> AttemptShotState : startLookingForPass
```
+## [HaltTestPlayFSM](/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm.h)
+
+```mermaid
+
+stateDiagram-v2
+classDef terminate fill:white,color:black,font-weight:bold
+direction LR
+[*] --> HaltTestState
+HaltTestState --> HaltTestState : updateHalt
+Terminate:::terminate --> Terminate:::terminate : updateHalt
+
+```
+
+## [MoveTestPlayFSM](/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm.h)
+
+```mermaid
+
+stateDiagram-v2
+classDef terminate fill:white,color:black,font-weight:bold
+direction LR
+[*] --> MoveTestState
+MoveTestState --> MoveTestState : [!moveDone]\nupdateMove
+MoveTestState --> Terminate:::terminate : [moveDone]\nupdateMove
+Terminate:::terminate --> Terminate:::terminate : updateMove
+
+```
+
## [AttackerFSM](/src/software/ai/hl/stp/tactic/attacker/attacker_fsm.h)
```mermaid
diff --git a/src/software/ai/hl/stp/play/BUILD b/src/software/ai/hl/stp/play/BUILD
index 678a44e215..5de81bde83 100644
--- a/src/software/ai/hl/stp/play/BUILD
+++ b/src/software/ai/hl/stp/play/BUILD
@@ -40,30 +40,6 @@ cc_library(
alwayslink = True,
)
-cc_library(
- name = "shoot_or_chip_play",
- srcs = ["shoot_or_chip_play.cpp"],
- hdrs = ["shoot_or_chip_play.h"],
- deps = [
- ":play",
- "//proto/message_translation:tbots_protobuf",
- "//shared:constants",
- "//software/ai/evaluation:enemy_threat",
- "//software/ai/evaluation:find_open_areas",
- "//software/ai/evaluation:possession",
- "//software/ai/hl/stp/tactic/attacker:attacker_tactic",
- "//software/ai/hl/stp/tactic/crease_defender:crease_defender_tactic",
- "//software/ai/hl/stp/tactic/goalie:goalie_tactic",
- "//software/ai/hl/stp/tactic/halt:halt_tactic",
- "//software/ai/hl/stp/tactic/move:move_tactic",
- "//software/ai/hl/stp/tactic/shadow_enemy:shadow_enemy_tactic",
- "//software/logger",
- "//software/util/generic_factory",
- "//software/world:game_state",
- ],
- alwayslink = True,
-)
-
cc_library(
name = "stop_play",
srcs = ["stop_play.cpp"],
@@ -108,7 +84,6 @@ cc_library(
deps = [
":kickoff_enemy_play",
":kickoff_friendly_play",
- ":shoot_or_chip_play",
":stop_play",
"//software/ai/hl/stp/play/ball_placement:ball_placement_play",
"//software/ai/hl/stp/play/crease_defense:crease_defense_play",
@@ -125,6 +100,7 @@ cc_library(
"//software/ai/hl/stp/play/offense:offense_play",
"//software/ai/hl/stp/play/penalty_kick:penalty_kick_play",
"//software/ai/hl/stp/play/penalty_kick_enemy:penalty_kick_enemy_play",
+ "//software/ai/hl/stp/play/shoot_or_chip:shoot_or_chip_play",
"//software/ai/hl/stp/play/shoot_or_pass:shoot_or_pass_play",
],
)
@@ -193,36 +169,6 @@ cc_test(
],
)
-cc_test(
- name = "shoot_or_chip_play_cpp_test",
- srcs = ["shoot_or_chip_play_test.cpp"],
- deps = [
- "//shared/test_util:tbots_gtest_main",
- "//software/ai/hl/stp/play:shoot_or_chip_play",
- "//software/simulated_tests:simulated_er_force_sim_play_test_fixture",
- "//software/simulated_tests/validation:validation_function",
- "//software/test_util",
- "//software/time:duration",
- "//software/world",
- ],
-)
-
-py_test(
- name = "shoot_or_chip_play_test",
- srcs = [
- "shoot_or_chip_play_test.py",
- ],
- # TODO (#2619) Remove tag to run in parallel
- tags = [
- "exclusive",
- ],
- deps = [
- "//software:conftest",
- "//software/simulated_tests:validation",
- requirement("pytest"),
- ],
-)
-
cc_library(
name = "assigned_tactics_play",
srcs = ["assigned_tactics_play.cpp"],
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip/BUILD b/src/software/ai/hl/stp/play/shoot_or_chip/BUILD
new file mode 100644
index 0000000000..2e10e2e42e
--- /dev/null
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/BUILD
@@ -0,0 +1,62 @@
+load("@simulated_tests_deps//:requirements.bzl", "requirement")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "shoot_or_chip_play",
+ srcs = [
+ "shoot_or_chip_play.cpp",
+ "shoot_or_chip_play_fsm.cpp",
+ ],
+ hdrs = [
+ "shoot_or_chip_play.h",
+ "shoot_or_chip_play_fsm.h",
+ ],
+ deps = [
+ "//proto/message_translation:tbots_protobuf",
+ "//shared:constants",
+ "//software/ai/evaluation:enemy_threat",
+ "//software/ai/evaluation:find_open_areas",
+ "//software/ai/evaluation:possession",
+ "//software/ai/hl/stp/play",
+ "//software/ai/hl/stp/tactic/attacker:attacker_tactic",
+ "//software/ai/hl/stp/tactic/crease_defender:crease_defender_tactic",
+ "//software/ai/hl/stp/tactic/halt:halt_tactic",
+ "//software/ai/hl/stp/tactic/move:move_tactic",
+ "//software/ai/hl/stp/tactic/shadow_enemy:shadow_enemy_tactic",
+ "//software/logger",
+ "//software/util/generic_factory",
+ "//software/world:game_state",
+ ],
+ alwayslink = True,
+)
+
+cc_test(
+ name = "shoot_or_chip_play_cpp_test",
+ srcs = ["shoot_or_chip_play_test.cpp"],
+ deps = [
+ ":shoot_or_chip_play",
+ "//shared/test_util:tbots_gtest_main",
+ "//software/simulated_tests:simulated_er_force_sim_play_test_fixture",
+ "//software/simulated_tests/validation:validation_function",
+ "//software/test_util",
+ "//software/time:duration",
+ "//software/world",
+ ],
+)
+
+py_test(
+ name = "shoot_or_chip_play_test",
+ srcs = [
+ "shoot_or_chip_play_test.py",
+ ],
+ # TODO (#2619) Remove tag to run in parallel
+ tags = [
+ "exclusive",
+ ],
+ deps = [
+ "//software:conftest",
+ "//software/simulated_tests:validation",
+ requirement("pytest"),
+ ],
+)
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.cpp b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.cpp
new file mode 100644
index 0000000000..5d1ec92377
--- /dev/null
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.cpp
@@ -0,0 +1,31 @@
+#include "software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.h"
+
+#include "proto/message_translation/tbots_protobuf.h"
+#include "shared/constants.h"
+#include "software/logger/logger.h"
+#include "software/util/generic_factory/generic_factory.h"
+#include "software/world/game_state.h"
+
+ShootOrChipPlay::ShootOrChipPlay(
+ std::shared_ptr ai_config_ptr)
+ : PlayBase(ai_config_ptr, true)
+{
+}
+
+void ShootOrChipPlay::getNextTactics(TacticCoroutine::push_type &yield,
+ const WorldPtr &world_ptr)
+{
+ // This function doesn't get called, it should be removed once coroutines
+ // are phased out
+}
+
+
+void ShootOrChipPlay::updateTactics(const PlayUpdate &play_update)
+{
+ fsm.process_event(ShootOrChipPlayFSM::Update(control_params, play_update));
+}
+
+// Register this play in the genericFactory
+static TGenericFactory>
+ factory;
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play.h b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.h
similarity index 65%
rename from src/software/ai/hl/stp/play/shoot_or_chip_play.h
rename to src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.h
index f50b7e76c6..9690ef720b 100644
--- a/src/software/ai/hl/stp/play/shoot_or_chip_play.h
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.h
@@ -2,16 +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/shoot_or_chip/shoot_or_chip_play_fsm.h"
/**
* The Defense Play tries to grab the ball from the enemy that has it, and all other
* robots shadow the enemy robots in order of how threatening they are.
*/
-class ShootOrChipPlay : public Play
+class ShootOrChipPlay : public PlayBase
{
public:
ShootOrChipPlay(std::shared_ptr ai_config_ptr);
void getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr) override;
+
+ void updateTactics(const PlayUpdate &play_update) override;
};
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.cpp b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.cpp
new file mode 100644
index 0000000000..b5cafba8a9
--- /dev/null
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.cpp
@@ -0,0 +1,97 @@
+#include "software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.h"
+
+#include "proto/message_translation/tbots_protobuf.h"
+#include "shared/constants.h"
+#include "software/ai/evaluation/enemy_threat.h"
+#include "software/ai/evaluation/find_open_areas.h"
+#include "software/ai/evaluation/possession.h"
+#include "software/ai/hl/stp/tactic/attacker/attacker_tactic.h"
+#include "software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic.h"
+#include "software/ai/hl/stp/tactic/move/move_tactic.h"
+#include "software/ai/hl/stp/tactic/shadow_enemy/shadow_enemy_tactic.h"
+
+ShootOrChipPlayFSM::ShootOrChipPlayFSM(
+ std::shared_ptr ai_config_ptr)
+ : PlayFSM(ai_config_ptr),
+ crease_defender_tactics{
+ std::make_shared(ai_config_ptr),
+ std::make_shared(ai_config_ptr),
+ },
+ move_to_open_area_tactics{
+ std::make_shared(ai_config_ptr),
+ std::make_shared(ai_config_ptr),
+ },
+ attacker(std::make_shared(ai_config_ptr))
+{
+}
+
+void ShootOrChipPlayFSM::updateShootOrChip(const Update& event)
+{
+ /**
+ * Our general strategy here is:
+ * - 1 goalie
+ * - 2 crease defenders moving around the friendly defense box
+ * - 2 robots moving into the first and second largest open free space on
+ * the enemy half
+ * - 1 robot trying to shoot on the goal. If an enemy gets too close to this
+ * robot, it will chip to right in front of the robot in the largest open free area
+ */
+
+ // Figure out where the fallback chip target is
+ // Experimentally determined to be a reasonable value
+ double fallback_chip_target_x_offset = 1.5;
+
+ Point fallback_chip_target = event.common.world_ptr->field().enemyGoalCenter() -
+ Vector(fallback_chip_target_x_offset, 0);
+
+ // Update chipper
+ std::optional chip_target = fallback_chip_target;
+
+
+ PriorityTacticVector result = {{}};
+
+ // Update crease defenders
+ std::get<0>(crease_defender_tactics)
+ ->updateControlParams(event.common.world_ptr->ball().position(),
+ TbotsProto::CreaseDefenderAlignment::LEFT);
+ result[0].emplace_back(std::get<0>(crease_defender_tactics));
+ std::get<1>(crease_defender_tactics)
+ ->updateControlParams(event.common.world_ptr->ball().position(),
+ TbotsProto::CreaseDefenderAlignment::RIGHT);
+ result[0].emplace_back(std::get<1>(crease_defender_tactics));
+
+ // Update tactics moving to open areas
+ std::vector chip_targets = findGoodChipTargets(*event.common.world_ptr);
+ for (unsigned i = 0; i < chip_targets.size() && i < move_to_open_area_tactics.size();
+ i++)
+ {
+ // Face towards the ball
+ Angle orientation =
+ (event.common.world_ptr->ball().position() - chip_targets[i].origin())
+ .orientation();
+ // Move a bit backwards to make it more likely we'll receive the chip
+ Point position =
+ chip_targets[i].origin() -
+ Vector::createFromAngle(orientation).normalize(ROBOT_MAX_RADIUS_METERS);
+ ;
+ move_to_open_area_tactics[i]->updateControlParams(position, orientation);
+ result[0].emplace_back(move_to_open_area_tactics[i]);
+ }
+
+ if (!chip_targets.empty())
+ {
+ chip_target = chip_targets[0].origin();
+ }
+ attacker->updateControlParams(chip_target);
+
+ // We want this second in priority only to the goalie
+ result[0].insert(result[0].begin() + 1, attacker);
+
+ // set the the Tactics this Play wants to run, in order of priority
+ event.common.set_tactics(result);
+}
+
+bool ShootOrChipPlayFSM::attackerDone(const Update& event)
+{
+ return attacker->done();
+}
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.h b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.h
new file mode 100644
index 0000000000..e9edb745ac
--- /dev/null
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_fsm.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "proto/parameters.pb.h"
+#include "shared/constants.h"
+#include "software/ai/hl/stp/play/play_fsm.hpp"
+#include "software/ai/hl/stp/tactic/attacker/attacker_tactic.h"
+#include "software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic.h"
+#include "software/ai/hl/stp/tactic/move/move_tactic.h"
+
+struct ShootOrChipPlayFSM : PlayFSM
+{
+ struct ControlParams
+ {
+ };
+
+ class ShootOrChipState;
+
+ explicit ShootOrChipPlayFSM(
+ std::shared_ptr ai_config_ptr);
+
+ void updateShootOrChip(const Update& event);
+
+ bool attackerDone(const Update& event);
+
+
+
+ auto operator()()
+ {
+ using namespace boost::sml;
+
+ DEFINE_SML_STATE(ShootOrChipState)
+ DEFINE_SML_EVENT(Update)
+ DEFINE_SML_ACTION(updateShootOrChip)
+ DEFINE_SML_GUARD(attackerDone)
+
+ return make_transition_table(
+ // src_state + event [guard] / action = dest_state
+ *ShootOrChipState_S + Update_E[!attackerDone_G] / updateShootOrChip_A =
+ ShootOrChipState_S,
+ ShootOrChipState_S + Update_E[attackerDone_G] / updateShootOrChip_A = X,
+ X + Update_E / updateShootOrChip_A = X);
+ }
+
+ private:
+ std::array, 2> crease_defender_tactics;
+ std::array, 2> move_to_open_area_tactics;
+ std::shared_ptr attacker;
+};
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.cpp
similarity index 97%
rename from src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp
rename to src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.cpp
index c617194c6e..63ef6aeddc 100644
--- a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp
+++ b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.cpp
@@ -1,4 +1,4 @@
-#include "software/ai/hl/stp/play/shoot_or_chip_play.h"
+#include "software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play.h"
#include
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py b/src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py
similarity index 100%
rename from src/software/ai/hl/stp/play/shoot_or_chip_play_test.py
rename to src/software/ai/hl/stp/play/shoot_or_chip/shoot_or_chip_play_test.py
diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play.cpp b/src/software/ai/hl/stp/play/shoot_or_chip_play.cpp
deleted file mode 100644
index 30234e0d28..0000000000
--- a/src/software/ai/hl/stp/play/shoot_or_chip_play.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "software/ai/hl/stp/play/shoot_or_chip_play.h"
-
-#include "proto/message_translation/tbots_protobuf.h"
-#include "shared/constants.h"
-#include "software/ai/evaluation/enemy_threat.h"
-#include "software/ai/evaluation/find_open_areas.h"
-#include "software/ai/evaluation/possession.h"
-#include "software/ai/hl/stp/tactic/attacker/attacker_tactic.h"
-#include "software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic.h"
-#include "software/ai/hl/stp/tactic/halt/halt_tactic.h"
-#include "software/ai/hl/stp/tactic/move/move_tactic.h"
-#include "software/ai/hl/stp/tactic/shadow_enemy/shadow_enemy_tactic.h"
-#include "software/logger/logger.h"
-#include "software/util/generic_factory/generic_factory.h"
-#include "software/world/game_state.h"
-
-ShootOrChipPlay::ShootOrChipPlay(
- std::shared_ptr ai_config_ptr)
- : Play(ai_config_ptr, true)
-{
-}
-
-void ShootOrChipPlay::getNextTactics(TacticCoroutine::push_type &yield,
- const WorldPtr &world_ptr)
-{
- /**
- * Our general strategy here is:
- * - 1 goalie
- * - 2 crease defenders moving around the friendly defense box
- * - 2 robots moving into the first and second largest open free space on
- * the enemy half
- * - 1 robot trying to shoot on the goal. If an enemy gets too close to this
- * robot, it will chip to right in front of the robot in the largest open free area
- */
-
- std::array, 2> crease_defender_tactics = {
- std::make_shared(ai_config_ptr),
- std::make_shared(ai_config_ptr),
- };
-
- std::array, 2> move_to_open_area_tactics = {
- std::make_shared(ai_config_ptr),
- std::make_shared(ai_config_ptr)};
-
- // Figure out where the fallback chip target is
- // Experimentally determined to be a reasonable value
- double fallback_chip_target_x_offset = 1.5;
-
- Point fallback_chip_target =
- world_ptr->field().enemyGoalCenter() - Vector(fallback_chip_target_x_offset, 0);
-
- auto attacker = std::make_shared(ai_config_ptr);
- attacker->updateControlParams(fallback_chip_target);
-
- do
- {
- PriorityTacticVector result = {{}};
-
- // Update crease defenders
- std::get<0>(crease_defender_tactics)
- ->updateControlParams(world_ptr->ball().position(),
- TbotsProto::CreaseDefenderAlignment::LEFT);
- result[0].emplace_back(std::get<0>(crease_defender_tactics));
- std::get<1>(crease_defender_tactics)
- ->updateControlParams(world_ptr->ball().position(),
- TbotsProto::CreaseDefenderAlignment::RIGHT);
- result[0].emplace_back(std::get<1>(crease_defender_tactics));
-
- // Update tactics moving to open areas
- std::vector enemy_robot_points;
- for (auto const &robot : world_ptr->enemyTeam().getAllRobots())
- {
- enemy_robot_points.emplace_back(robot.position());
- }
- std::vector chip_targets = findGoodChipTargets(*world_ptr);
- for (unsigned i = 0;
- i < chip_targets.size() && i < move_to_open_area_tactics.size(); i++)
- {
- // Face towards the ball
- Angle orientation =
- (world_ptr->ball().position() - chip_targets[i].origin()).orientation();
- // Move a bit backwards to make it more likely we'll receive the chip
- Point position =
- chip_targets[i].origin() -
- Vector::createFromAngle(orientation).normalize(ROBOT_MAX_RADIUS_METERS);
- ;
- move_to_open_area_tactics[i]->updateControlParams(position, orientation);
- result[0].emplace_back(move_to_open_area_tactics[i]);
- }
-
- // Update chipper
- std::optional chip_target = std::nullopt;
- if (!chip_targets.empty())
- {
- chip_target = chip_targets[0].origin();
- }
- attacker->updateControlParams(chip_target);
-
- // We want this second in priority only to the goalie
- result[0].insert(result[0].begin() + 1, attacker);
-
- // yield the Tactics this Play wants to run, in order of priority
- yield(result);
-
- } while (!attacker->done());
-}
-
-// Register this play in the genericFactory
-static TGenericFactory>
- factory;
diff --git a/src/software/ai/hl/stp/play/test_plays/BUILD b/src/software/ai/hl/stp/play/test_plays/BUILD
index bc08c72b17..8197258f06 100644
--- a/src/software/ai/hl/stp/play/test_plays/BUILD
+++ b/src/software/ai/hl/stp/play/test_plays/BUILD
@@ -2,8 +2,13 @@ package(default_visibility = ["//visibility:public"])
cc_library(
name = "halt_test_play",
- srcs = ["halt_test_play.cpp"],
- hdrs = ["halt_test_play.h"],
+ srcs = [
+ "halt_test_play.cpp",
+ ],
+ hdrs = [
+ "halt_test_play.h",
+ "halt_test_play_fsm.h",
+ ],
deps = [
"//software/ai/hl/stp/play",
"//software/ai/hl/stp/tactic/halt:halt_tactic",
@@ -15,7 +20,10 @@ cc_library(
cc_library(
name = "move_test_play",
srcs = ["move_test_play.cpp"],
- hdrs = ["move_test_play.h"],
+ hdrs = [
+ "move_test_play.h",
+ "move_test_play_fsm.h",
+ ],
deps = [
"//software/ai/hl/stp/play",
"//software/ai/hl/stp/tactic/move:move_tactic",
@@ -23,3 +31,23 @@ cc_library(
],
alwayslink = True,
)
+
+cc_test(
+ name = "halt_test_play_fsm_test",
+ srcs = ["halt_test_play_fsm_test.cpp"],
+ deps = [
+ ":halt_test_play",
+ "//shared/test_util:tbots_gtest_main",
+ "//software/test_util",
+ ],
+)
+
+cc_test(
+ name = "move_test_play_fsm_test",
+ srcs = ["move_test_play_fsm_test.cpp"],
+ deps = [
+ ":move_test_play",
+ "//shared/test_util:tbots_gtest_main",
+ "//software/test_util",
+ ],
+)
diff --git a/src/software/ai/hl/stp/play/test_plays/halt_test_play.cpp b/src/software/ai/hl/stp/play/test_plays/halt_test_play.cpp
index 50b7cc7dc9..b4a2e695a3 100644
--- a/src/software/ai/hl/stp/play/test_plays/halt_test_play.cpp
+++ b/src/software/ai/hl/stp/play/test_plays/halt_test_play.cpp
@@ -1,27 +1,23 @@
#include "software/ai/hl/stp/play/test_plays/halt_test_play.h"
-#include "software/ai/hl/stp/tactic/halt/halt_tactic.h"
-#include "software/geom/algorithms/contains.h"
#include "software/util/generic_factory/generic_factory.h"
HaltTestPlay::HaltTestPlay(std::shared_ptr ai_config_ptr)
- : Play(ai_config_ptr, false)
+ : PlayBase(ai_config_ptr, false)
{
}
void HaltTestPlay::getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr)
{
- auto halt_test_tactic_1 = std::make_shared(ai_config_ptr);
- auto halt_test_tactic_2 = std::make_shared(ai_config_ptr);
- auto halt_test_tactic_3 = std::make_shared(ai_config_ptr);
-
- do
- {
- yield({{halt_test_tactic_1, halt_test_tactic_2, halt_test_tactic_3}});
- } while (true);
+ // This function doesn't get called and will be removed when coroutines are phased
+ // out.
}
+void HaltTestPlay::updateTactics(const PlayUpdate &play_update)
+{
+ fsm.process_event(HaltTestPlayFSM::Update(control_params, play_update));
+}
// Register this play in the genericFactory
static TGenericFactory>
diff --git a/src/software/ai/hl/stp/play/test_plays/halt_test_play.h b/src/software/ai/hl/stp/play/test_plays/halt_test_play.h
index aeeed0cfad..cbab38c25d 100644
--- a/src/software/ai/hl/stp/play/test_plays/halt_test_play.h
+++ b/src/software/ai/hl/stp/play/test_plays/halt_test_play.h
@@ -2,6 +2,8 @@
#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/test_plays/halt_test_play_fsm.h"
/**
* A test Play that halts 3 robots.
@@ -11,7 +13,7 @@
* This play is applicable when the ball's y coordinate is >= 0
* This play's invariant holds while the ball is within the field
*/
-class HaltTestPlay : public Play
+class HaltTestPlay : public PlayBase
{
public:
/**
@@ -23,4 +25,5 @@ class HaltTestPlay : public Play
void getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr) override;
+ void updateTactics(const PlayUpdate &play_update) override;
};
diff --git a/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm.h b/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm.h
new file mode 100644
index 0000000000..dcad1a6f03
--- /dev/null
+++ b/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "proto/parameters.pb.h"
+#include "shared/constants.h"
+#include "software/ai/hl/stp/play/play_fsm.hpp"
+#include "software/ai/hl/stp/tactic/halt/halt_tactic.h"
+
+struct HaltTestPlayFSM : PlayFSM
+{
+ struct ControlParams
+ {
+ };
+
+ class HaltTestState;
+
+ explicit HaltTestPlayFSM(std::shared_ptr ai_config_ptr)
+ : PlayFSM(ai_config_ptr),
+ halt_tactics({{
+ std::make_shared(ai_config_ptr),
+ std::make_shared(ai_config_ptr),
+ std::make_shared(ai_config_ptr),
+ }})
+ {
+ }
+
+ void updateHalt(const Update& event)
+ {
+ event.common.set_tactics(halt_tactics);
+ }
+
+ auto operator()()
+ {
+ using namespace boost::sml;
+
+ DEFINE_SML_STATE(HaltTestState)
+ DEFINE_SML_EVENT(Update)
+ DEFINE_SML_ACTION(updateHalt)
+
+ return make_transition_table(
+ // src_state + event [guard] / action = dest_state
+ *HaltTestState_S + Update_E / updateHalt_A = HaltTestState_S,
+ X + Update_E / updateHalt_A = X);
+ }
+
+ private:
+ PriorityTacticVector halt_tactics;
+};
diff --git a/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm_test.cpp b/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm_test.cpp
new file mode 100644
index 0000000000..3af71370dc
--- /dev/null
+++ b/src/software/ai/hl/stp/play/test_plays/halt_test_play_fsm_test.cpp
@@ -0,0 +1,29 @@
+#include "software/ai/hl/stp/play/test_plays/halt_test_play_fsm.h"
+
+#include
+
+#include "proto/parameters.pb.h"
+#include "software/test_util/equal_within_tolerance.h"
+#include "software/test_util/test_util.h"
+
+TEST(HaltTestPlayFSMTest, test_transitions)
+{
+ std::shared_ptr world = ::TestUtil::createBlankTestingWorld();
+ FSM fsm(HaltTestPlayFSM{std::make_shared()});
+
+ // Verify initial state
+ EXPECT_TRUE(fsm.is(boost::sml::state));
+
+ // Capture tactics from callback
+ PriorityTacticVector received_tactics;
+ fsm.process_event(HaltTestPlayFSM::Update(
+ HaltTestPlayFSM::ControlParams{},
+ PlayUpdate(
+ world, 3,
+ [&received_tactics](PriorityTacticVector new_tactics)
+ { received_tactics = std::move(new_tactics); },
+ InterPlayCommunication{}, [](InterPlayCommunication) {})));
+
+ // Verify state stays the same (single-state loop)
+ EXPECT_TRUE(fsm.is(boost::sml::state));
+}
diff --git a/src/software/ai/hl/stp/play/test_plays/move_test_play.cpp b/src/software/ai/hl/stp/play/test_plays/move_test_play.cpp
index 21ca1555ad..6acaea6495 100644
--- a/src/software/ai/hl/stp/play/test_plays/move_test_play.cpp
+++ b/src/software/ai/hl/stp/play/test_plays/move_test_play.cpp
@@ -4,34 +4,20 @@
#include "software/util/generic_factory/generic_factory.h"
MoveTestPlay::MoveTestPlay(std::shared_ptr ai_config_ptr)
- : Play(ai_config_ptr, false)
+ : PlayBase(ai_config_ptr, false)
{
}
void MoveTestPlay::getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr)
{
- auto move_test_tactic_friendly_goal = std::make_shared(ai_config_ptr);
- auto move_test_tactic_enemy_goal = std::make_shared(ai_config_ptr);
- auto move_test_tactic_center_field = std::make_shared(ai_config_ptr);
-
- do
- {
- move_test_tactic_friendly_goal->updateControlParams(
- world_ptr->field().friendlyGoalCenter(), Angle::zero(),
- TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
- TbotsProto::ObstacleAvoidanceMode::SAFE);
- move_test_tactic_enemy_goal->updateControlParams(
- world_ptr->field().enemyGoalCenter(), Angle::zero(),
- TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
- TbotsProto::ObstacleAvoidanceMode::SAFE);
- move_test_tactic_center_field->updateControlParams(
- Point(0, 0), Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
- TbotsProto::ObstacleAvoidanceMode::SAFE);
+ // This function doesn't get called, it should be removed once coroutines
+ // are phased out
+}
- yield({{move_test_tactic_center_field, move_test_tactic_friendly_goal,
- move_test_tactic_enemy_goal}});
- } while (!move_test_tactic_center_field->done());
+void MoveTestPlay::updateTactics(const PlayUpdate &play_update)
+{
+ fsm.process_event(MoveTestPlayFSM::Update(control_params, play_update));
}
// Register this play in the genericFactory
diff --git a/src/software/ai/hl/stp/play/test_plays/move_test_play.h b/src/software/ai/hl/stp/play/test_plays/move_test_play.h
index f1d03b8a70..92b7e55783 100644
--- a/src/software/ai/hl/stp/play/test_plays/move_test_play.h
+++ b/src/software/ai/hl/stp/play/test_plays/move_test_play.h
@@ -2,6 +2,8 @@
#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/test_plays/move_test_play_fsm.h"
/**
* A test Play that moves a robot to the friendly goal, a robot to the enemy goal, and
@@ -12,7 +14,7 @@
* This play is applicable when the ball's x and y coordinates are >= 0
* This play's invariant holds while the ball's x coordinate is >= 0
*/
-class MoveTestPlay : public Play
+class MoveTestPlay : public PlayBase
{
public:
/**
@@ -24,4 +26,6 @@ class MoveTestPlay : public Play
void getNextTactics(TacticCoroutine::push_type &yield,
const WorldPtr &world_ptr) override;
+
+ void updateTactics(const PlayUpdate &play_update) override;
};
diff --git a/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm.h b/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm.h
new file mode 100644
index 0000000000..ee99fcfe3e
--- /dev/null
+++ b/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "proto/parameters.pb.h"
+#include "shared/constants.h"
+#include "software/ai/hl/stp/play/play_fsm.hpp"
+#include "software/ai/hl/stp/tactic/move/move_tactic.h"
+
+struct MoveTestPlayFSM : PlayFSM
+{
+ struct ControlParams
+ {
+ };
+
+ class MoveTestState;
+
+ explicit MoveTestPlayFSM(std::shared_ptr ai_config_ptr)
+ : PlayFSM(ai_config_ptr),
+ move_test_tactic_friendly_goal(std::make_shared(ai_config_ptr)),
+ move_test_tactic_enemy_goal(std::make_shared(ai_config_ptr)),
+ move_test_tactic_center_field(std::make_shared(ai_config_ptr))
+ {
+ }
+
+ void updateMove(const Update& event)
+ {
+ move_test_tactic_friendly_goal->updateControlParams(
+ event.common.world_ptr->field().friendlyGoalCenter(), Angle::zero(),
+ TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
+ TbotsProto::ObstacleAvoidanceMode::SAFE);
+ move_test_tactic_enemy_goal->updateControlParams(
+ event.common.world_ptr->field().enemyGoalCenter(), Angle::zero(),
+ TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
+ TbotsProto::ObstacleAvoidanceMode::SAFE);
+ move_test_tactic_center_field->updateControlParams(
+ Point(0, 0), Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
+ TbotsProto::ObstacleAvoidanceMode::SAFE);
+
+ event.common.set_tactics(
+ {{move_test_tactic_center_field, move_test_tactic_friendly_goal,
+ move_test_tactic_enemy_goal}});
+ }
+
+ bool moveDone(const Update& event)
+ {
+ return move_test_tactic_center_field->done();
+ }
+
+
+ auto operator()()
+ {
+ using namespace boost::sml;
+
+ DEFINE_SML_STATE(MoveTestState)
+ DEFINE_SML_EVENT(Update)
+ DEFINE_SML_ACTION(updateMove)
+ DEFINE_SML_GUARD(moveDone)
+
+ return make_transition_table(
+ // src_state + event [guard] / action = dest_state
+ *MoveTestState_S + Update_E[!moveDone_G] / updateMove_A = MoveTestState_S,
+ MoveTestState_S + Update_E[moveDone_G] / updateMove_A = X,
+ X + Update_E / updateMove_A = X);
+ }
+
+ private:
+ std::shared_ptr move_test_tactic_friendly_goal;
+ std::shared_ptr move_test_tactic_enemy_goal;
+ std::shared_ptr move_test_tactic_center_field;
+};
diff --git a/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm_test.cpp b/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm_test.cpp
new file mode 100644
index 0000000000..e6a56b830a
--- /dev/null
+++ b/src/software/ai/hl/stp/play/test_plays/move_test_play_fsm_test.cpp
@@ -0,0 +1,32 @@
+#include "software/ai/hl/stp/play/test_plays/move_test_play_fsm.h"
+
+#include
+
+#include "proto/parameters.pb.h"
+#include "software/test_util/test_util.h"
+
+TEST(MoveTestPlayFSMTest, test_stays_in_move_state)
+{
+ std::shared_ptr world = ::TestUtil::createBlankTestingWorld();
+ FSM fsm(MoveTestPlayFSM{std::make_shared()});
+
+ // Verify initial state
+ EXPECT_TRUE(fsm.is(boost::sml::state));
+
+ // Capture tactics from callback
+ PriorityTacticVector received_tactics;
+ fsm.process_event(MoveTestPlayFSM::Update(
+ MoveTestPlayFSM::ControlParams{},
+ PlayUpdate(
+ world, 3,
+ [&received_tactics](PriorityTacticVector new_tactics)
+ { received_tactics = std::move(new_tactics); },
+ InterPlayCommunication{}, [](InterPlayCommunication) {})));
+
+ // Verify state stays the same (tactic not done yet)
+ EXPECT_TRUE(fsm.is(boost::sml::state));
+
+ // Verify we got exactly 1 priority level with 3 tactics
+ ASSERT_EQ(received_tactics.size(), 1);
+ EXPECT_EQ(received_tactics[0].size(), 3);
+}