From 1ce04f9f4c10d9694152519432f5252c1c8c5893 Mon Sep 17 00:00:00 2001 From: Muk Chunpongtong <121708205+muxite@users.noreply.github.com> Date: Sat, 15 Nov 2025 07:50:11 -0800 Subject: [PATCH 01/18] Improved logic past original state using FSMs --- src/software/ai/hl/stp/play/BUILD | 105 +++-------- .../ai/hl/stp/play/kickoff_enemy/BUILD | 60 ++++++ .../play/kickoff_enemy/kickoff_enemy_play.cpp | 35 ++++ .../play/kickoff_enemy/kickoff_enemy_play.h | 29 +++ .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 165 +++++++++++++++++ .../kickoff_enemy/kickoff_enemy_play_fsm.h | 102 +++++++++++ .../kickoff_enemy_play_test.cpp | 30 +-- .../kickoff_enemy/kickoff_enemy_play_test.py | 96 ++++++++++ .../ai/hl/stp/play/kickoff_enemy_play.cpp | 151 --------------- .../ai/hl/stp/play/kickoff_enemy_play.h | 19 -- .../ai/hl/stp/play/kickoff_friendly/BUILD | 57 ++++++ .../kickoff_friendly_play.cpp | 35 ++++ .../kickoff_friendly_play.h | 17 +- .../kickoff_friendly_play_fsm.cpp | 173 ++++++++++++++++++ .../kickoff_friendly_play_fsm.h | 126 +++++++++++++ .../kickoff_friendly_play_test.cpp | 30 +-- .../kickoff_friendly_play_test.py | 96 ++++++++++ .../ai/hl/stp/play/kickoff_friendly_play.cpp | 138 -------------- src/software/ai/play_selection_fsm.cpp | 4 +- 19 files changed, 1042 insertions(+), 426 deletions(-) create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/BUILD create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h rename src/software/ai/hl/stp/play/{ => kickoff_enemy}/kickoff_enemy_play_test.cpp (75%) create mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py delete mode 100644 src/software/ai/hl/stp/play/kickoff_enemy_play.cpp delete mode 100644 src/software/ai/hl/stp/play/kickoff_enemy_play.h create mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/BUILD create mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp rename src/software/ai/hl/stp/play/{ => kickoff_friendly}/kickoff_friendly_play.h (50%) create mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp create mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h rename src/software/ai/hl/stp/play/{ => kickoff_friendly}/kickoff_friendly_play_test.cpp (77%) create mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py delete mode 100644 src/software/ai/hl/stp/play/kickoff_friendly_play.cpp diff --git a/src/software/ai/hl/stp/play/BUILD b/src/software/ai/hl/stp/play/BUILD index bd9c16119d..3120d760b4 100644 --- a/src/software/ai/hl/stp/play/BUILD +++ b/src/software/ai/hl/stp/play/BUILD @@ -1,45 +1,11 @@ -package(default_visibility = ["//visibility:public"]) - load("@simulated_tests_deps//:requirements.bzl", "requirement") +package(default_visibility = ["//visibility:public"]) + # We force linking for all plays so that the static variables required for the # "factory" design pattern to work are linked in # https://www.bfilipek.com/2018/02/static-vars-static-lib.html -cc_library( - name = "kickoff_enemy_play", - srcs = ["kickoff_enemy_play.cpp"], - hdrs = ["kickoff_enemy_play.h"], - deps = [ - ":play", - "//shared:constants", - "//software/ai/evaluation:enemy_threat", - "//software/ai/evaluation:possession", - "//software/ai/hl/stp/tactic/goalie:goalie_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", - ], - alwayslink = True, -) - -cc_library( - name = "kickoff_friendly_play", - srcs = ["kickoff_friendly_play.cpp"], - hdrs = ["kickoff_friendly_play.h"], - deps = [ - ":play", - "//shared:constants", - "//software/ai/evaluation:enemy_threat", - "//software/ai/hl/stp/tactic/chip:chip_tactic", - "//software/ai/hl/stp/tactic/move:move_tactic", - "//software/logger", - "//software/util/generic_factory", - ], - alwayslink = True, -) - cc_library( name = "shoot_or_chip_play", srcs = ["shoot_or_chip_play.cpp"], @@ -105,8 +71,6 @@ cc_library( cc_library( name = "all_plays", deps = [ - ":kickoff_enemy_play", - ":kickoff_friendly_play", ":shoot_or_chip_play", ":stop_play", "//software/ai/hl/stp/play/ball_placement:ball_placement_play", @@ -116,6 +80,8 @@ cc_library( "//software/ai/hl/stp/play/enemy_free_kick:enemy_free_kick_play", "//software/ai/hl/stp/play/example:example_play", "//software/ai/hl/stp/play/free_kick:free_kick_play", + "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", + "//software/ai/hl/stp/play/kickoff_enemy:kickoff_enemy_play", "//software/ai/hl/stp/play/halt_play", "//software/ai/hl/stp/play/hardware_challenge_plays:dribbling_parcour_play", "//software/ai/hl/stp/play/hardware_challenge_plays:pass_endurance_play", @@ -128,54 +94,9 @@ cc_library( ], ) -cc_test( - name = "kickoff_friendly_play_cpp_test", - srcs = ["kickoff_friendly_play_test.cpp"], - deps = [ - "//shared/test_util:tbots_gtest_main", - "//software/ai/hl/stp/play:kickoff_friendly_play", - "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", - "//software/simulated_tests/non_terminating_validation_functions", - "//software/simulated_tests/terminating_validation_functions", - "//software/simulated_tests/validation:validation_function", - "//software/test_util", - "//software/time:duration", - "//software/world", - ], -) -cc_test( - name = "kickoff_enemy_play_cpp_test", - srcs = ["kickoff_enemy_play_test.cpp"], - deps = [ - "//shared/test_util:tbots_gtest_main", - "//software/ai/hl/stp/play:kickoff_enemy_play", - "//software/geom/algorithms", - "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", - "//software/simulated_tests/non_terminating_validation_functions", - "//software/simulated_tests/terminating_validation_functions", - "//software/simulated_tests/validation:validation_function", - "//software/test_util", - "//software/time:duration", - "//software/world", - ], -) -py_test( - name = "kickoff_play_test", - srcs = [ - "kickoff_play_test.py", - ], - # TODO (#2619) Remove tag to run in parallel - tags = [ - "exclusive", - ], - deps = [ - "//software:conftest", - "//software/simulated_tests:validation", - requirement("pytest"), - ], -) + cc_test( name = "stop_play_test", @@ -277,3 +198,19 @@ py_test( requirement("pytest"), ], ) + +py_test( + name = "kickoff_play_test", + srcs = [ + "kickoff_play_test.py", + ], + # TODO (#2619) Remove tag to run in parallel + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD new file mode 100644 index 0000000000..d3394def16 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD @@ -0,0 +1,60 @@ +package(default_visibility = ["//visibility:public"]) + +load("@simulated_tests_deps//:requirements.bzl", "requirement") + +cc_library( + name = "kickoff_enemy_play", + srcs = [ + "kickoff_enemy_play.cpp", + "kickoff_enemy_play_fsm.cpp" + ], + hdrs = [ + "kickoff_enemy_play.h", + "kickoff_enemy_play_fsm.h", + ], + deps = [ + "//software/ai/hl/stp/play", + "//shared:constants", + "//software/ai/evaluation:enemy_threat", + "//software/ai/evaluation:possession", + "//software/ai/hl/stp/tactic/goalie:goalie_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", + ], + alwayslink = True, +) + +py_test( + name = "kickoff_enemy_play_test", + srcs = [ + "kickoff_enemy_play_test.py", + ], + # TODO (#2619) Remove tag to run in parallel + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) + +cc_test( + name = "kickoff_enemy_play_cpp_test", + srcs = ["kickoff_enemy_play_test.cpp"], + deps = [ + "//shared/test_util:tbots_gtest_main", + "//software/ai/hl/stp/play:kickoff_enemy_play", + "//software/geom/algorithms", + "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", + "//software/simulated_tests/non_terminating_validation_functions", + "//software/simulated_tests/terminating_validation_functions", + "//software/simulated_tests/validation:validation_function", + "//software/test_util", + "//software/time:duration", + "//software/world", + ], +) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp new file mode 100644 index 0000000000..f05fe15847 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp @@ -0,0 +1,35 @@ +#include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h" + +#include "shared/constants.h" +#include "software/util/generic_factory/generic_factory.h" + +KickoffEnemyPlay::KickoffEnemyPlay(TbotsProto::AiConfig config) + : Play(config, true), fsm{KickoffEnemyPlayFSM{config}}, control_params{} +{ +} + +void KickoffEnemyPlay::getNextTactics(TacticCoroutine::push_type &yield, + const WorldPtr &world_ptr) +{ + // Does not get called. + while (true) + { + yield({{}}); + } +} + +void KickoffEnemyPlay::updateTactics(const PlayUpdate &play_update) +{ + fsm.process_event(KickoffEnemyPlayFSM::Update(control_params, play_update)); +} + +std::vector KickoffEnemyPlay::getState() +{ + std::vector state; + state.emplace_back(objectTypeName(*this) + " - " + getCurrentFullStateName(fsm)); + return state; +} + + +// Register this play in the genericFactory +static TGenericFactory factory; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h new file mode 100644 index 0000000000..f41d898387 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h @@ -0,0 +1,29 @@ +#pragma once + +#include "proto/parameters.pb.h" +#include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" +#include "software/ai/hl/stp/play/play.h" + +/** + * A play that runs when its currently the enemy kick off. + */ + +class KickoffEnemyPlay : public Play +{ +public: + /** + * Creates an enemy kickoff play + * + * @param ai_config the play config for this play + */ + KickoffEnemyPlay(TbotsProto::AiConfig config); + + void getNextTactics(TacticCoroutine::push_type &yield, + const WorldPtr &world_ptr) override; + void updateTactics(const PlayUpdate &play_update) override; + std::vector getState() override; + +private: + FSM fsm; + KickoffEnemyPlayFSM::ControlParams control_params; +}; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp new file mode 100644 index 0000000000..0ff8528393 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -0,0 +1,165 @@ +#include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" + +KickoffEnemyPlayFSM::KickoffEnemyPlayFSM(const TbotsProto::AiConfig &ai_config) + : ai_config(ai_config), + shadow_enemy_tactics({ + std::make_shared(), + std::make_shared() + }), + move_tactics({ + std::make_shared(), // for robot 1 + std::make_shared(), // for robot 2 + std::make_shared(), // for robot 3 + std::make_shared(), // for robot 4 + std::make_shared() // for robot 5 + }) +{ + +} + +void KickoffEnemyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) +{ + // these positions are picked according to the followicreateKickoffSetupPositions();ng slide + // https://images.slideplayer.com/32/9922349/slides/slide_2.jpg + // since we only have 6 robots at the maximum, 3 robots will shadow threats + // up front, 1 robot is dedicated as the goalie, and the other 2 robots will defend + // either post (as show in the image) + // + // Positions 1,2 are the most important, 3,4,5 are a fallback + // if there aren't as many threats to shadow. Robots will be assigned + // to those positions in order of priority. The 5 positions shown below + // are in the same order as in the defense_position vector. + // + // +--------------------+--------------------+ + // | | | + // | | | + // | | | + // +--+ 2 4 | +--+ + // | | | | | + // | | +-+-+ | | + // | | 3 | | | | + // | | +-+-+ | | + // | | | | | + // +--+ 1 5 | +--+ + // | | | + // | | | + // | | | + // +--------------------+--------------------+ + if (!kickoff_setup_positions.empty()) { + return; + } + + kickoff_setup_positions = { + Point(world_ptr->field().friendlyGoalpostNeg().x() + + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, + -world_ptr->field().defenseAreaYLength() / 2.0), + Point(world_ptr->field().friendlyGoalpostPos().x() + + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().defenseAreaYLength() / 2.0), + Point(world_ptr->field().friendlyGoalCenter().x() + + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().friendlyGoalCenter().y()), + Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), + world_ptr->field().defenseAreaYLength() / 2.0), + Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), + -world_ptr->field().defenseAreaYLength() / 2.0), + }; +} + +void KickoffEnemyPlayFSM::assignShadowing( + const std::vector &enemy_threats, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index +) +{ + const auto shadower_count = std::min(2, enemy_threats.size()); + + for (size_t i = 0; i < shadower_count; i++) + { + // Assign the first 2 robots to shadow enemies, if the enemies exist + auto enemy_threat = enemy_threats.at(i); + // Shadow with a distance slightly more than the distance from the enemy + // robot to the center line, so we are always just on our side of the + // center line + double shadow_dist = std::fabs(enemy_threat.robot.position().x()) + + 2 * ROBOT_MAX_RADIUS_METERS; + // We shadow assuming the robots do not pass so we do not try block passes + // while shadowing, since we can't go on the enemy side to block the pass + // anyway + shadow_enemy_tactics.at(i)->updateControlParams(enemy_threat, + shadow_dist); + + tactics_to_run[0].emplace_back(shadow_enemy_tactics.at(i)); + } +} + +void KickoffEnemyPlayFSM::assignDefenders( + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index +) +{ + while (defense_position_index < move_tactics.size() - 1 && defense_position_index < kickoff_setup_positions.size()) + { + move_tactics.at(defense_position_index) + ->updateControlParams(kickoff_setup_positions.at(defense_position_index), + Angle::zero()); + tactics_to_run[0].emplace_back(move_tactics.at(defense_position_index)); + defense_position_index++; + } +} + +void KickoffEnemyPlayFSM::assignGoalBlocker( + const WorldPtr &world_ptr, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index +) +{ + move_tactics.back() + ->updateControlParams( + calculateBlockCone(world_ptr->field().friendlyGoalpostPos(), + world_ptr->field().friendlyGoalpostNeg(), + world_ptr->field().centerPoint(), + ROBOT_MAX_RADIUS_METERS), + Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT, + TbotsProto::ObstacleAvoidanceMode::AGGRESSIVE); + tactics_to_run[0].emplace_back(move_tactics.at(defense_position_index)); + defense_position_index++; +} + +void KickoffEnemyPlayFSM::kickoff(const Update &event) +{ + createKickoffSetupPositions(event.common.world_ptr); + WorldPtr world_ptr = event.common.world_ptr; + Team enemy_team = world_ptr->enemyTeam(); + PriorityTacticVector tactics_to_run = {{}}; + + // TODO: (Mathew): Minor instability with defenders and goalie when the ball and + // attacker are in the middle of the net + + // We find the nearest enemy robot closest to (0,0) then ignore it from the enemy + // team. Since the center circle is a motion constraint during enemy kickoff, the + // shadowing robot will navigate to the closest point that it can to shadow, which + // might not be ideal. (i.e robot won't block a straight shot on net) + auto robot = Team::getNearestRobot(world_ptr->enemyTeam().getAllRobots(), + world_ptr->field().centerPoint()); + if (robot.has_value()) + { + int robot_id = robot.value().id(); + enemy_team.removeRobotWithId(robot_id); + } + else + { + LOG(WARNING) << "No Robot on the Field!"; + } + + auto enemy_threats = + getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), + world_ptr->enemyTeam(), world_ptr->ball(), false); + + size_t defense_position_index = 0; + assignShadowing(enemy_threats, tactics_to_run, defense_position_index); + assignDefenders(tactics_to_run, defense_position_index); + assignGoalBlocker(world_ptr, tactics_to_run, defense_position_index); + + event.common.set_tactics(tactics_to_run); +} \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h new file mode 100644 index 0000000000..d7735932c8 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -0,0 +1,102 @@ +#pragma once + +#include "proto/parameters.pb.h" +#include "shared/constants.h" +#include "software/ai/evaluation/enemy_threat.h" +#include "software/ai/hl/stp/play/play.h" +#include "software/ai/hl/stp/play/play_fsm.h" +#include "software/ai/evaluation/possession.h" +#include "software/ai/hl/stp/tactic/shadow_enemy/shadow_enemy_tactic.h" +#include "software/geom/algorithms/calculate_block_cone.h" +#include "software/ai/hl/stp/tactic/move/move_tactic.h" +#include "software/logger/logger.h" + + + +struct KickoffEnemyPlayFSM +{ + class SetupState; + + struct ControlParams + { + }; + + DEFINE_PLAY_UPDATE_STRUCT_WITH_CONTROL_AND_COMMON_PARAMS + /** + * Creates a kickoff enemy play FSM + * + * @param ai_config the play config for this play FSM + */ + explicit KickoffEnemyPlayFSM(const TbotsProto::AiConfig& ai_config); + + /** + * create a vector of setup positions if not already existing. + * + * @param world_ptr the world pointer + */ + void createKickoffSetupPositions(const WorldPtr &world_ptr); + + /** + * add shadowing robots to tactics to run. + * + * @param enemy_threats the enemies that must be shadowed. + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ + void assignShadowing(const std::vector &enemy_threats, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index); + + /** + * add defenders to tactics to run. + * + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ + void assignDefenders(PriorityTacticVector &tactics_to_run, size_t &defense_position_index); + + /** + * add a goal blocker to tactics to run. + * + * @param world_ptr the world pointer + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ + void assignGoalBlocker( + const WorldPtr &world_ptr, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index + ); + + /** + * Action to organize the bots to be ready for enemy kickoff. + * + * @param event the FreeKickPlayFSM Update event + */ + void kickoff(const Update& event); + + + auto operator()() + { + using namespace boost::sml; + + DEFINE_SML_STATE(SetupState) + + DEFINE_SML_EVENT(Update) + + DEFINE_SML_ACTION(kickoff) + + + return make_transition_table( + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E / kickoff_A = SetupState_S + ); + } + +private: + TbotsProto::AiConfig ai_config; + std::vector> shadow_enemy_tactics; + std::vector> move_tactics; + std::vector kickoff_setup_positions; +}; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp similarity index 75% rename from src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp rename to src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp index e9ca71ca24..97101c343a 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp @@ -1,4 +1,4 @@ -#include "software/ai/hl/stp/play/kickoff_enemy_play.h" +#include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h" #include @@ -15,7 +15,7 @@ class KickoffEnemyPlayTest : public SimulatedErForceSimPlayTestFixture { - protected: +protected: TbotsProto::FieldType field_type = TbotsProto::FieldType::DIV_B; Field field = Field::createField(field_type); }; @@ -23,20 +23,20 @@ class KickoffEnemyPlayTest : public SimulatedErForceSimPlayTestFixture // TODO (#3105): Re-enable test once destinations are moved outside of obstacles TEST_F(KickoffEnemyPlayTest, DISABLED_test_kickoff_enemy_play) { - BallState ball_state(Point(0, 0), Vector(0, 0)); - auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( +BallState ball_state(Point(0, 0), Vector(0, 0)); +auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( {Point(-3, 2.5), Point(-3, 1.5), Point(-3, 0.5), Point(-3, -0.5), Point(-3, -1.5), Point(-3, -2.5)}); - setFriendlyGoalie(0); - auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( +setFriendlyGoalie(0); +auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), field.enemyDefenseArea().negXNegYCorner(), field.enemyDefenseArea().negXPosYCorner()}); - setEnemyGoalie(0); - setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); - setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_THEM); +setEnemyGoalie(0); +setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); +setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_THEM); - std::vector terminating_validation_functions = { +std::vector terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { // Two friendly robots in position to shadow enemy robots. Rectangles are @@ -54,10 +54,10 @@ TEST_F(KickoffEnemyPlayTest, DISABLED_test_kickoff_enemy_play) robotInPolygon(robotsDefendingRect, 2, world_ptr, yield); }}; - std::vector non_terminating_validation_functions = { +std::vector non_terminating_validation_functions = { robotsInFriendlyHalf, robotsNotInCenterCircle}; - runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); -} +runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); +} \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py new file mode 100644 index 0000000000..4fe6556579 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py @@ -0,0 +1,96 @@ +import sys + +import pytest + +import software.python_bindings as tbots_cpp +from proto.play_pb2 import Play, PlayName +from proto.import_all_protos import * +from proto.message_translation.tbots_protobuf import create_world_state +from proto.ssl_gc_common_pb2 import Team + + +@pytest.mark.parametrize("is_friendly_test", [True]) +def test_kickoff_play(simulated_test_runner, is_friendly_test): + def setup(*args): + # starting point must be Point + ball_initial_pos = tbots_cpp.Point(0, 0) + + # Setup Bots + blue_bots = [ + tbots_cpp.Point(-3, 2.5), + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(-3, 0.5), + tbots_cpp.Point(-3, -0.5), + tbots_cpp.Point(-3, -1.5), + tbots_cpp.Point(-3, -2.5), + ] + + yellow_bots = [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), + ] + + blue_play = Play() + yellow_play = Play() + + # Game Controller Setup + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.NORMAL_START, team=Team.BLUE + ) + if is_friendly_test: + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.BLUE + ) + blue_play.name = PlayName.KickoffFriendlyPlay + yellow_play.name = PlayName.KickoffEnemyPlay + else: + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.YELLOW + ) + blue_play.name = PlayName.KickoffEnemyPlay + yellow_play.name = PlayName.KickoffFriendlyPlay + + # Force play override here + simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + Play, yellow_play + ) + + # Create world state + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + # TODO- #2809 Validation + # params just have to be a list of length 1 to ensure the test runs at least once + simulated_test_runner.run_test( + setup=setup, + params=[0], + inv_always_validation_sequence_set=[[]], + inv_eventually_validation_sequence_set=[[]], + ag_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +if __name__ == "__main__": + # Run the test, -s disables all capturing at -vv increases verbosity + sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_enemy_play.cpp b/src/software/ai/hl/stp/play/kickoff_enemy_play.cpp deleted file mode 100644 index 694a0c6401..0000000000 --- a/src/software/ai/hl/stp/play/kickoff_enemy_play.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "software/ai/hl/stp/play/kickoff_enemy_play.h" - -#include "proto/parameters.pb.h" -#include "shared/constants.h" -#include "software/ai/evaluation/enemy_threat.h" -#include "software/ai/evaluation/possession.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/geom/algorithms/calculate_block_cone.h" -#include "software/util/generic_factory/generic_factory.h" - -KickoffEnemyPlay::KickoffEnemyPlay(TbotsProto::AiConfig config) : Play(config, true) {} - -void KickoffEnemyPlay::getNextTactics(TacticCoroutine::push_type &yield, - const WorldPtr &world_ptr) -{ - // 3 robots assigned to shadow enemies. Other robots will be assigned positions - // on the field to be evenly spread out - std::vector> shadow_enemy_tactics = { - std::make_shared(), std::make_shared()}; - - // these positions are picked according to the following slide - // https://images.slideplayer.com/32/9922349/slides/slide_2.jpg - // since we only have 6 robots at the maximum, 3 robots will shadow threats - // up front, 1 robot is dedicated as the goalie, and the other 2 robots will defend - // either post (as show in the image) - // - // Positions 1,2 are the most important, 3,4,5 are a fallback - // if there aren't as many threats to shadow. Robots will be assigned - // to those positions in order of priority. The 5 positions shown below - // are in the same order as in the defense_position vector. - // - // +--------------------+--------------------+ - // | | | - // | | | - // | | | - // +--+ 2 4 | +--+ - // | | | | | - // | | +-+-+ | | - // | | 3 | | | | - // | | +-+-+ | | - // | | | | | - // +--+ 1 5 | +--+ - // | | | - // | | | - // | | | - // +--------------------+--------------------+ - - std::vector defense_positions = { - Point(world_ptr->field().friendlyGoalpostNeg().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - -world_ptr->field().defenseAreaYLength() / 2.0), - Point(world_ptr->field().friendlyGoalpostPos().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().defenseAreaYLength() / 2.0), - Point(world_ptr->field().friendlyGoalCenter().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalCenter().y()), - Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), - world_ptr->field().defenseAreaYLength() / 2.0), - Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), - -world_ptr->field().defenseAreaYLength() / 2.0), - }; - // these move tactics will be used to go to those positions - std::vector> move_tactics = { - std::make_shared(), std::make_shared(), - std::make_shared(), std::make_shared(), - std::make_shared()}; - - // created an enemy_team for mutation - Team enemy_team = world_ptr->enemyTeam(); - - do - { - // TODO: (Mathew): Minor instability with defenders and goalie when the ball and - // attacker are in the middle of the net - - // We find the nearest enemy robot closest to (0,0) then ignore it from the enemy - // team. Since the center circle is a motion constraint during enemy kickoff, the - // shadowing robot will navigate to the closest point that it can to shadow, which - // might not be ideal. (i.e robot won't block a straight shot on net) - auto robot = Team::getNearestRobot(world_ptr->enemyTeam().getAllRobots(), - world_ptr->field().centerPoint()); - if (robot.has_value()) - { - int robot_id = robot.value().id(); - enemy_team.removeRobotWithId(robot_id); - } - else - { - LOG(WARNING) << "No Robot on the Field!"; - } - - auto enemy_threats = - getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), - world_ptr->enemyTeam(), world_ptr->ball(), false); - - PriorityTacticVector result = {{}}; - - // keeps track of the next defense position to assign - int defense_position_index = 0; - for (unsigned i = 0; i < defense_positions.size() - 1; ++i) - { - if (i < 2 && i < enemy_threats.size()) - { - // Assign the first 2 robots to shadow enemies, if the enemies exist - auto enemy_threat = enemy_threats.at(i); - // Shadow with a distance slightly more than the distance from the enemy - // robot to the center line, so we are always just on our side of the - // center line - double shadow_dist = std::fabs(enemy_threat.robot.position().x()) + - 2 * ROBOT_MAX_RADIUS_METERS; - // We shadow assuming the robots do not pass so we do not try block passes - // while shadowing, since we can't go on the enemy side to block the pass - // anyway - shadow_enemy_tactics.at(i)->updateControlParams(enemy_threat, - shadow_dist); - - result[0].emplace_back(shadow_enemy_tactics.at(i)); - } - else - { - // Once we are out of enemies to shadow, or are already shadowing 2 - // enemies, we move the rest of the robots to the defense positions - // listed above - move_tactics.at(defense_position_index) - ->updateControlParams(defense_positions.at(defense_position_index), - Angle::zero()); - result[0].emplace_back(move_tactics.at(defense_position_index)); - defense_position_index++; - } - } - - // update robot 3 to be directly between the ball and the friendly net - move_tactics.at(defense_position_index) - ->updateControlParams( - calculateBlockCone(world_ptr->field().friendlyGoalpostPos(), - world_ptr->field().friendlyGoalpostNeg(), - world_ptr->field().centerPoint(), - ROBOT_MAX_RADIUS_METERS), - Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT, - TbotsProto::ObstacleAvoidanceMode::AGGRESSIVE); - result[0].emplace_back(move_tactics.at(defense_position_index)); - - // yield the Tactics this Play wants to run, in order of priority - yield(result); - } while (true); -} - -// Register this play in the genericFactory -static TGenericFactory factory; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy_play.h b/src/software/ai/hl/stp/play/kickoff_enemy_play.h deleted file mode 100644 index b6eac6e826..0000000000 --- a/src/software/ai/hl/stp/play/kickoff_enemy_play.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "proto/parameters.pb.h" -#include "software/ai/hl/stp/play/play.h" - -/** - * A play that runs when its currently the enemies kick off, - * prioritizes defending the net and shadowing the robot - * that is nearest to the ball. Any remaining bots will block - * some odd angles to the net. - */ -class KickoffEnemyPlay : public Play -{ - public: - KickoffEnemyPlay(TbotsProto::AiConfig config); - - void getNextTactics(TacticCoroutine::push_type &yield, - const WorldPtr &world_ptr) override; -}; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD new file mode 100644 index 0000000000..a0a571eae2 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD @@ -0,0 +1,57 @@ +package(default_visibility = ["//visibility:public"]) + +load("@simulated_tests_deps//:requirements.bzl", "requirement") + +cc_library( + name = "kickoff_friendly_play", + srcs = [ + "kickoff_friendly_play.cpp", + "kickoff_friendly_play_fsm.cpp", + ], + hdrs = [ + "kickoff_friendly_play.h", + "kickoff_friendly_play_fsm.h", + ], + deps = [ + "//software/ai/hl/stp/play", + "//shared:constants", + "//software/ai/evaluation:enemy_threat", + "//software/ai/hl/stp/tactic/chip:chip_tactic", + "//software/ai/hl/stp/tactic/move:move_tactic", + "//software/ai/evaluation:find_open_areas", + "//software/logger", + "//software/util/generic_factory", + ], + alwayslink = True, +) + +cc_test( + name = "kickoff_friendly_play_cpp_test", + srcs = ["kickoff_friendly_play_test.cpp"], + deps = [ + "//shared/test_util:tbots_gtest_main", + "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", + "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", + "//software/simulated_tests/non_terminating_validation_functions", + "//software/simulated_tests/terminating_validation_functions", + "//software/simulated_tests/validation:validation_function", + "//software/test_util", + "//software/time:duration", + "//software/world", + ], +) + +py_test( + name = "kickoff_play_test", + srcs = [ + "kickoff_play_test.py", + ], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp new file mode 100644 index 0000000000..4061d9e700 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp @@ -0,0 +1,35 @@ +#include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h" + +#include "shared/constants.h" +#include "software/util/generic_factory/generic_factory.h" + + +KickoffFriendlyPlay::KickoffFriendlyPlay(TbotsProto::AiConfig config) + : Play(config, true), fsm{KickoffFriendlyPlayFSM{config}}, control_params{} +{ +} + +void KickoffFriendlyPlay::getNextTactics(TacticCoroutine::push_type &yield, + const WorldPtr &world_ptr) +{ + // Does not get called. + while (true) + { + yield({{}}); + } +} + +void KickoffFriendlyPlay::updateTactics(const PlayUpdate &play_update) +{ + fsm.process_event(KickoffFriendlyPlayFSM::Update(control_params, play_update)); +} + +std::vector KickoffFriendlyPlay::getState() +{ + std::vector state; + state.emplace_back(objectTypeName(*this) + " - " + getCurrentFullStateName(fsm)); + return state; +} + +// Register this play in the genericFactory +static TGenericFactory factory; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h similarity index 50% rename from src/software/ai/hl/stp/play/kickoff_friendly_play.h rename to src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index fda2ea59fe..88cf97f73e 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -1,17 +1,30 @@ #pragma once #include "proto/parameters.pb.h" +#include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" #include "software/ai/hl/stp/play/play.h" /** * A play that runs when its currently the friendly kick off, * only one robot grabs the ball and passes to another robot. */ + class KickoffFriendlyPlay : public Play { - public: +public: + /** + * Creates a friendly kickoff play + * + * @param ai_config the play config for this play + */ KickoffFriendlyPlay(TbotsProto::AiConfig config); void getNextTactics(TacticCoroutine::push_type &yield, const WorldPtr &world_ptr) override; -}; + void updateTactics(const PlayUpdate &play_update) override; + std::vector getState() override; + +private: + FSM fsm; + KickoffFriendlyPlayFSM::ControlParams control_params; +}; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp new file mode 100644 index 0000000000..fd77872609 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -0,0 +1,173 @@ +#include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" + +KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM(const TbotsProto::AiConfig &ai_config) + : ai_config(ai_config), + kickoff_chip_tactic(std::make_shared()), + shoot_tactic(std::make_shared()), + move_tactics({ + std::make_shared(), // for robot 1 + std::make_shared(), // for robot 2 + std::make_shared(), // for robot 3 + std::make_shared(), // for robot 4 + std::make_shared() // for robot 5 + }) +{ + +} + +void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) +{ + // Since we only have 6 robots at the maximum, the number one priority + // is the robot doing the kickoff up front. The goalie is the second most + // important, followed by 3 and 4 setup for offense. 5 and 6 will stay + // back near the goalie just in case the ball quickly returns to the friendly + // side of the field. + // + // +--------------------+--------------------+ + // | | | + // | 3 | | + // | | | + // +--+ 5 | +--+ + // | | | | | + // | | +-+-+ | | + // |2 | |1 | | | + // | | +-+-+ | | + // | | | | | + // +--+ 6 | +--+ + // | | | + // | 4 | | + // | | | + // +--------------------+--------------------+ + // + + if (kickoff_setup_positions.empty()) + { + kickoff_setup_positions = { + // Robot 1 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius(), 0)), + // Robot 2 + // Goalie positions will be handled by the goalie tactic + // Robot 3 + Point( + world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, + -1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 4 + Point( + world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, + 1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 5 + Point(world_ptr->field().friendlyGoalpostPos().x() + + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().friendlyGoalpostPos().y()), + // Robot 6 + Point(world_ptr->field().friendlyGoalpostNeg().x() + + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().friendlyGoalpostNeg().y()), + }; + } +} + + +void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) +{ + createKickoffSetupPositions(event.common.world_ptr); + + PriorityTacticVector tactics_to_run = {{}}; + + // first priority requires the ability to kick and chip. + move_tactics.at(0)->mutableRobotCapabilityRequirements() = { + RobotCapability::Kick, RobotCapability::Chip}; + + // set each tactic to its movement location. + for (unsigned i = 0; i < kickoff_setup_positions.size(); i++) + { + move_tactics.at(i)->updateControlParams(kickoff_setup_positions.at(i), + Angle::zero()); + tactics_to_run[0].emplace_back(move_tactics.at(i)); + } + + event.common.set_tactics(tactics_to_run); +} + +// taken from free kick fsm. +void KickoffFriendlyPlayFSM::shootBall(const Update &event) +{ + WorldPtr world_ptr = event.common.world_ptr; + PriorityTacticVector tactics_to_run = {{}}; + + Point ball_pos = world_ptr->ball().position(); + + shoot_tactic->updateControlParams( + ball_pos, (shot->getPointToShootAt() - ball_pos).orientation(), + BALL_MAX_SPEED_METERS_PER_SECOND); + tactics_to_run[0].emplace_back(shoot_tactic); + + + event.common.set_tactics(tactics_to_run); +} + +void KickoffFriendlyPlayFSM::chipBall(const Update &event) +{ + WorldPtr world_ptr = event.common.world_ptr; + + PriorityTacticVector tactics_to_run = {{}}; + + // adjust with testing to give us enough space to catch the ball before it goes out of bounds + double ballX = world_ptr->ball().position().x(); + double fieldX = world_ptr->field().enemyGoalCenter().x() - 2; + double negFieldY = world_ptr->field().enemyCornerNeg().y() + 0.3; + double posFieldY = world_ptr->field().enemyCornerPos().y() - 0.3; + + Rectangle target_area_rectangle = + Rectangle(Point(ballX, negFieldY), Point(fieldX, posFieldY)); + + // sort targets by distance to enemy goal center. + std::vector potential_chip_targets = findGoodChipTargets(*world_ptr, target_area_rectangle); + std::sort(potential_chip_targets.begin(), potential_chip_targets.end(), + [world_ptr](const Circle& first_circle, const Circle& second_circle) { + + return distance(world_ptr->field().enemyGoalCenter(), first_circle.origin()) < + distance(world_ptr->field().enemyGoalCenter(), second_circle.origin()); + }); + + Point target = world_ptr->field().centerPoint() + Vector(world_ptr->field().xLength() / 6, 0); + + if (!potential_chip_targets.empty()) + { + target = potential_chip_targets[0].origin(); + } + + kickoff_chip_tactic->updateControlParams( + world_ptr->ball().position(), + target + ); + + tactics_to_run[0].emplace_back(kickoff_chip_tactic); + + event.common.set_tactics(tactics_to_run); +} + +bool KickoffFriendlyPlayFSM::isSetupDone(const Update &event) +{ + return !event.common.world_ptr->gameState().isSetupState(); +} + +bool KickoffFriendlyPlayFSM::isPlaying(const Update& event) +{ + return event.common.world_ptr->gameState().isPlaying(); +} + +bool KickoffFriendlyPlayFSM::shotFound(const Update &event) +{ + shot = calcBestShotOnGoal(event.common.world_ptr->field(), + event.common.world_ptr->friendlyTeam(), + event.common.world_ptr->enemyTeam(), + event.common.world_ptr->ball().position(), TeamType::ENEMY); + return shot.has_value() && + shot->getOpenAngle() > + Angle::fromDegrees( + ai_config.attacker_tactic_config().min_open_angle_for_shot_deg()); +} \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h new file mode 100644 index 0000000000..7b4dce9cdf --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -0,0 +1,126 @@ +#pragma once + +#include "proto/parameters.pb.h" +#include "shared/constants.h" +#include "software/ai/evaluation/enemy_threat.h" +#include "software/ai/hl/stp/play/play.h" +#include "software/ai/hl/stp/play/play_fsm.h" +#include "software/ai/hl/stp/tactic/kick/kick_tactic.h" +#include "software/ai/hl/stp/tactic/move/move_tactic.h" +#include "software/ai/hl/stp/tactic/chip/chip_tactic.h" +#include "software/ai/evaluation/find_open_areas.h" +#include "software/logger/logger.h" + +struct KickoffFriendlyPlayFSM +{ + class SetupState; + class ShootState; + class ChipState; + + struct ControlParams + { + }; + + DEFINE_PLAY_UPDATE_STRUCT_WITH_CONTROL_AND_COMMON_PARAMS + /** + * Creates a kickoff friendly play FSM + * + * @param ai_config the play config for this play FSM + */ + explicit KickoffFriendlyPlayFSM(const TbotsProto::AiConfig& ai_config); + + /** + * create a vector of setup positions if not already existing. + * + * @param world_ptr the world pointer + */ + void createKickoffSetupPositions(const WorldPtr &world_ptr); + + + + /** + * Action to move robots to starting positions + * + * @param event the FreeKickPlayFSM Update event + */ + void setupKickoff(const Update& event); + + /** + * Action to shoot the ball at the net. + * + * @param event the FreeKickPlayFSM Update event + */ + void shootBall(const Update& event); + + /** + * Action to chip the ball forward over the defenders. + * + * @param event the FreeKickPlayFSM Update event + */ + void chipBall(const Update& event); + + /** + * Guard that checks if positions are set up. + * + * @param event the FreeKickPlayFSM Update event + */ + bool isSetupDone(const Update& event); + + /** + * Guard that checks if game has started (ball kicked). + * + * @param event the FreeKickPlayFSM Update event + */ + bool isPlaying(const Update& event); + + /** + * Guard that checks if a direct shot on the net is possible. + * + * @param event the FreeKickPlayFSM Update event + */ + bool shotFound(const Update& event); + + auto operator()() + { + using namespace boost::sml; + + DEFINE_SML_STATE(SetupState) + DEFINE_SML_STATE(ShootState) + DEFINE_SML_STATE(ChipState) + + DEFINE_SML_EVENT(Update) + + DEFINE_SML_ACTION(setupKickoff) + DEFINE_SML_ACTION(shootBall) + DEFINE_SML_ACTION(chipBall) + + DEFINE_SML_GUARD(isSetupDone) + DEFINE_SML_GUARD(shotFound) + DEFINE_SML_GUARD(isPlaying) + + return make_transition_table( + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + + // shoot directly at net if possible. + SetupState_S + Update_E[shotFound_G] = ShootState_S, + ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, + ShootState_S + Update_E[isPlaying_G] = X, + + // else chip over the defenders. + SetupState_S + Update_E = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, + + X + Update_E = X); + } + +private: + TbotsProto::AiConfig ai_config; + std::shared_ptr kickoff_chip_tactic; + std::shared_ptr shoot_tactic; + std::vector> move_tactics; + std::vector kickoff_setup_positions; + std::optional shot; +}; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp similarity index 77% rename from src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp rename to src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp index 26b164fcdf..3421bf5a3c 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp @@ -1,4 +1,4 @@ -#include "software/ai/hl/stp/play/kickoff_friendly_play.h" +#include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h" #include @@ -16,7 +16,7 @@ class KickoffFriendlyPlayTest : public SimulatedErForceSimPlayTestFixture { - protected: +protected: TbotsProto::FieldType field_type = TbotsProto::FieldType::DIV_B; Field field = Field::createField(field_type); }; @@ -24,20 +24,20 @@ class KickoffFriendlyPlayTest : public SimulatedErForceSimPlayTestFixture // TODO (#2608): re-enable when fixed TEST_F(KickoffFriendlyPlayTest, DISABLED_test_kickoff_friendly_play) { - BallState ball_state(Point(0, 0), Vector(0, 0)); - auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( +BallState ball_state(Point(0, 0), Vector(0, 0)); +auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( {Point(-3, 2.5), Point(-3, 1.5), Point(-3, 0.5), Point(-3, -0.5), Point(-3, -1.5), Point(-3, -2.5)}); - setFriendlyGoalie(0); - auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( +setFriendlyGoalie(0); +auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), field.enemyDefenseArea().negXNegYCorner(), field.enemyDefenseArea().negXPosYCorner()}); - setEnemyGoalie(0); - setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); - setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_US); +setEnemyGoalie(0); +setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); +setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_US); - std::vector terminating_validation_functions = { +std::vector terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { // Robot 4 is the only robot allowed to be in the center circle and start @@ -56,7 +56,7 @@ TEST_F(KickoffFriendlyPlayTest, DISABLED_test_kickoff_friendly_play) robotInPolygon(robotsDefensiveRect, 3, world_ptr, yield); }}; - std::vector non_terminating_validation_functions = { +std::vector non_terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { for (RobotId robot_id : {0, 1, 2, 3, 5}) @@ -68,7 +68,7 @@ TEST_F(KickoffFriendlyPlayTest, DISABLED_test_kickoff_friendly_play) } }}; - runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); -} +runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); +} \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py new file mode 100644 index 0000000000..4fe6556579 --- /dev/null +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py @@ -0,0 +1,96 @@ +import sys + +import pytest + +import software.python_bindings as tbots_cpp +from proto.play_pb2 import Play, PlayName +from proto.import_all_protos import * +from proto.message_translation.tbots_protobuf import create_world_state +from proto.ssl_gc_common_pb2 import Team + + +@pytest.mark.parametrize("is_friendly_test", [True]) +def test_kickoff_play(simulated_test_runner, is_friendly_test): + def setup(*args): + # starting point must be Point + ball_initial_pos = tbots_cpp.Point(0, 0) + + # Setup Bots + blue_bots = [ + tbots_cpp.Point(-3, 2.5), + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(-3, 0.5), + tbots_cpp.Point(-3, -0.5), + tbots_cpp.Point(-3, -1.5), + tbots_cpp.Point(-3, -2.5), + ] + + yellow_bots = [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), + ] + + blue_play = Play() + yellow_play = Play() + + # Game Controller Setup + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.NORMAL_START, team=Team.BLUE + ) + if is_friendly_test: + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.BLUE + ) + blue_play.name = PlayName.KickoffFriendlyPlay + yellow_play.name = PlayName.KickoffEnemyPlay + else: + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.YELLOW + ) + blue_play.name = PlayName.KickoffEnemyPlay + yellow_play.name = PlayName.KickoffFriendlyPlay + + # Force play override here + simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + Play, yellow_play + ) + + # Create world state + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + # TODO- #2809 Validation + # params just have to be a list of length 1 to ensure the test runs at least once + simulated_test_runner.run_test( + setup=setup, + params=[0], + inv_always_validation_sequence_set=[[]], + inv_eventually_validation_sequence_set=[[]], + ag_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +if __name__ == "__main__": + # Run the test, -s disables all capturing at -vv increases verbosity + sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly_play.cpp b/src/software/ai/hl/stp/play/kickoff_friendly_play.cpp deleted file mode 100644 index bec6ac1cc5..0000000000 --- a/src/software/ai/hl/stp/play/kickoff_friendly_play.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "software/ai/hl/stp/play/kickoff_friendly_play.h" - -#include "shared/constants.h" -#include "software/ai/evaluation/enemy_threat.h" -#include "software/ai/hl/stp/tactic/chip/chip_tactic.h" -#include "software/ai/hl/stp/tactic/move/move_tactic.h" -#include "software/util/generic_factory/generic_factory.h" - -KickoffFriendlyPlay::KickoffFriendlyPlay(TbotsProto::AiConfig config) : Play(config, true) -{ -} - -void KickoffFriendlyPlay::getNextTactics(TacticCoroutine::push_type &yield, - const WorldPtr &world_ptr) -{ - // Since we only have 6 robots at the maximum, the number one priority - // is the robot doing the kickoff up front. The goalie is the second most - // important, followed by 3 and 4 setup for offense. 5 and 6 will stay - // back near the goalie just in case the ball quickly returns to the friendly - // side of the field. - // - // +--------------------+--------------------+ - // | | | - // | 3 | | - // | | | - // +--+ 5 | +--+ - // | | | | | - // | | +-+-+ | | - // |2 | |1 | | | - // | | +-+-+ | | - // | | | | | - // +--+ 6 | +--+ - // | | | - // | 4 | | - // | | | - // +--------------------+--------------------+ - // - // This is a two part play: - // Part 1: Get into position, but don't touch the ball (ref kickoff) - // Part 2: Chip the ball over the defender (ref normal start) - - // the following positions are in the same order as the positions shown above, - // excluding the goalie for part 1 of this play - std::vector kickoff_setup_positions = { - // Robot 1 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius(), 0)), - // Robot 2 - // Goalie positions will be handled by the goalie tactic - // Robot 3 - Point( - world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - -1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 4 - Point( - world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - 1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 5 - Point(world_ptr->field().friendlyGoalpostPos().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostPos().y()), - // Robot 6 - Point(world_ptr->field().friendlyGoalpostNeg().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostNeg().y()), - }; - - // move tactics to use to move to positions defined above - std::vector> move_tactics = { - std::make_shared(), std::make_shared(), - std::make_shared(), std::make_shared(), - std::make_shared()}; - - // specific tactics - auto kickoff_chip_tactic = std::make_shared(); - - // Part 1: setup state (move to key positions) - while (world_ptr->gameState().isSetupState()) - { - auto enemy_threats = - getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), - world_ptr->enemyTeam(), world_ptr->ball(), false); - - PriorityTacticVector result = {{}}; - - // set the requirement that Robot 1 must be able to kick and chip - move_tactics.at(0)->mutableRobotCapabilityRequirements() = { - RobotCapability::Kick, RobotCapability::Chip}; - - // setup 5 kickoff positions in order of priority - for (unsigned i = 0; i < kickoff_setup_positions.size(); i++) - { - move_tactics.at(i)->updateControlParams(kickoff_setup_positions.at(i), - Angle::zero()); - result[0].emplace_back(move_tactics.at(i)); - } - - // yield the Tactics this Play wants to run, in order of priority - yield(result); - } - - // Part 2: not normal play, currently ready state (chip the ball) - while (!world_ptr->gameState().isPlaying()) - { - auto enemy_threats = - getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), - world_ptr->enemyTeam(), world_ptr->ball(), false); - - PriorityTacticVector result = {{}}; - - // TODO (#2612): This needs to be adjusted post field testing, ball needs to land - // exactly in the middle of the enemy field - kickoff_chip_tactic->updateControlParams( - world_ptr->ball().position(), - world_ptr->field().centerPoint() + - Vector(world_ptr->field().xLength() / 6, 0)); - result[0].emplace_back(kickoff_chip_tactic); - - // the robot at position 0 will be closest to the ball, so positions starting from - // 1 will be assigned to the rest of the robots - for (unsigned i = 1; i < kickoff_setup_positions.size(); i++) - { - move_tactics.at(i)->updateControlParams(kickoff_setup_positions.at(i), - Angle::zero()); - result[0].emplace_back(move_tactics.at(i)); - } - - // yield the Tactics this Play wants to run, in order of priority - yield(result); - } -} - - -// Register this play in the genericFactory -static TGenericFactory - factory; diff --git a/src/software/ai/play_selection_fsm.cpp b/src/software/ai/play_selection_fsm.cpp index c60e00a7c6..6af378b4db 100644 --- a/src/software/ai/play_selection_fsm.cpp +++ b/src/software/ai/play_selection_fsm.cpp @@ -5,8 +5,8 @@ #include "software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play.h" #include "software/ai/hl/stp/play/free_kick/free_kick_play.h" #include "software/ai/hl/stp/play/halt_play/halt_play.h" -#include "software/ai/hl/stp/play/kickoff_enemy_play.h" -#include "software/ai/hl/stp/play/kickoff_friendly_play.h" +#include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h" +#include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h" #include "software/ai/hl/stp/play/offense/offense_play.h" #include "software/ai/hl/stp/play/penalty_kick/penalty_kick_play.h" #include "software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play.h" From 1be72c4e45a6e0be23871bf8de1af8071c82be9a Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 15 Nov 2025 15:14:42 -0800 Subject: [PATCH 02/18] testing almost done except mystery halt issue --- .../ai/hl/stp/play/kickoff_enemy/BUILD | 15 --- .../kickoff_enemy/kickoff_enemy_play_test.py | 96 ------------------- .../ai/hl/stp/play/kickoff_friendly/BUILD | 15 --- .../kickoff_friendly_play_fsm.h | 4 +- .../kickoff_friendly_play_test.py | 96 ------------------- .../ai/hl/stp/play/kickoff_play_test.py | 84 ++++++++-------- 6 files changed, 47 insertions(+), 263 deletions(-) delete mode 100644 src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py delete mode 100644 src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD index d3394def16..5485b03841 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD @@ -26,21 +26,6 @@ cc_library( alwayslink = True, ) -py_test( - name = "kickoff_enemy_play_test", - srcs = [ - "kickoff_enemy_play_test.py", - ], - # TODO (#2619) Remove tag to run in parallel - tags = [ - "exclusive", - ], - deps = [ - "//software:conftest", - "//software/simulated_tests:validation", - requirement("pytest"), - ], -) cc_test( name = "kickoff_enemy_play_cpp_test", diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py deleted file mode 100644 index 4fe6556579..0000000000 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.py +++ /dev/null @@ -1,96 +0,0 @@ -import sys - -import pytest - -import software.python_bindings as tbots_cpp -from proto.play_pb2 import Play, PlayName -from proto.import_all_protos import * -from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team - - -@pytest.mark.parametrize("is_friendly_test", [True]) -def test_kickoff_play(simulated_test_runner, is_friendly_test): - def setup(*args): - # starting point must be Point - ball_initial_pos = tbots_cpp.Point(0, 0) - - # Setup Bots - blue_bots = [ - tbots_cpp.Point(-3, 2.5), - tbots_cpp.Point(-3, 1.5), - tbots_cpp.Point(-3, 0.5), - tbots_cpp.Point(-3, -0.5), - tbots_cpp.Point(-3, -1.5), - tbots_cpp.Point(-3, -2.5), - ] - - yellow_bots = [ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXPosYCorner(), - ] - - blue_play = Play() - yellow_play = Play() - - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.NORMAL_START, team=Team.BLUE - ) - if is_friendly_test: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.BLUE - ) - blue_play.name = PlayName.KickoffFriendlyPlay - yellow_play.name = PlayName.KickoffEnemyPlay - else: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.YELLOW - ) - blue_play.name = PlayName.KickoffEnemyPlay - yellow_play.name = PlayName.KickoffFriendlyPlay - - # Force play override here - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) - - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - # TODO- #2809 Validation - # params just have to be a list of length 1 to ensure the test runs at least once - simulated_test_runner.run_test( - setup=setup, - params=[0], - inv_always_validation_sequence_set=[[]], - inv_eventually_validation_sequence_set=[[]], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], - test_timeout_s=10, - ) - - -if __name__ == "__main__": - # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD index a0a571eae2..04de9f6ee0 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD @@ -40,18 +40,3 @@ cc_test( "//software/world", ], ) - -py_test( - name = "kickoff_play_test", - srcs = [ - "kickoff_play_test.py", - ], - tags = [ - "exclusive", - ], - deps = [ - "//software:conftest", - "//software/simulated_tests:validation", - requirement("pytest"), - ], -) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 7b4dce9cdf..94071fb9cd 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -104,12 +104,12 @@ struct KickoffFriendlyPlayFSM *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, + SetupState_S + Update_E[shotFound_G] = ShootState_S, ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, ShootState_S + Update_E[isPlaying_G] = X, // else chip over the defenders. - SetupState_S + Update_E = ChipState_S, + SetupState_S + Update_E[!shotFound_G] = ChipState_S, ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, ChipState_S + Update_E[isPlaying_G] = X, diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py deleted file mode 100644 index 4fe6556579..0000000000 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.py +++ /dev/null @@ -1,96 +0,0 @@ -import sys - -import pytest - -import software.python_bindings as tbots_cpp -from proto.play_pb2 import Play, PlayName -from proto.import_all_protos import * -from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team - - -@pytest.mark.parametrize("is_friendly_test", [True]) -def test_kickoff_play(simulated_test_runner, is_friendly_test): - def setup(*args): - # starting point must be Point - ball_initial_pos = tbots_cpp.Point(0, 0) - - # Setup Bots - blue_bots = [ - tbots_cpp.Point(-3, 2.5), - tbots_cpp.Point(-3, 1.5), - tbots_cpp.Point(-3, 0.5), - tbots_cpp.Point(-3, -0.5), - tbots_cpp.Point(-3, -1.5), - tbots_cpp.Point(-3, -2.5), - ] - - yellow_bots = [ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXPosYCorner(), - ] - - blue_play = Play() - yellow_play = Play() - - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.NORMAL_START, team=Team.BLUE - ) - if is_friendly_test: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.BLUE - ) - blue_play.name = PlayName.KickoffFriendlyPlay - yellow_play.name = PlayName.KickoffEnemyPlay - else: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.YELLOW - ) - blue_play.name = PlayName.KickoffEnemyPlay - yellow_play.name = PlayName.KickoffFriendlyPlay - - # Force play override here - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) - - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - # TODO- #2809 Validation - # params just have to be a list of length 1 to ensure the test runs at least once - simulated_test_runner.run_test( - setup=setup, - params=[0], - inv_always_validation_sequence_set=[[]], - inv_eventually_validation_sequence_set=[[]], - ag_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=[[]], - test_timeout_s=10, - ) - - -if __name__ == "__main__": - # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 31e3a2fa3c..7a96f83577 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -13,11 +13,7 @@ from software.simulated_tests.or_validation import OrValidation -@pytest.mark.parametrize("is_friendly_test", [True, False]) -def test_kickoff_play(simulated_test_runner, is_friendly_test): - ball_initial_pos = tbots_cpp.Point(0, 0) - - # Setup Bots +def setup_kickoff_attacker(): blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), @@ -26,57 +22,64 @@ def test_kickoff_play(simulated_test_runner, is_friendly_test): tbots_cpp.Point(-3, -1.5), tbots_cpp.Point(-3, -2.5), ] + return blue_bots + +def setup_kickoff_defender(): + # The defense has a hole in it from 0, 0 to the net. + # Robots that cannot move will allow the kicker to shoot directly into the net. yellow_bots = [ - tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 0.5), tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Point(2, -1.5), tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), ] + return yellow_bots + + +def init_world_state(runner, blue_bots, yellow_bots): + ball_initial = tbots_cpp.Point(0, 0) + runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + +def init_plays(simulated_test_runner, is_friendly, force_out): blue_play = Play() yellow_play = Play() - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) + kicking_team = Team.BLUE if is_friendly else Team.YELLOW + non_kicking_team = Team.YELLOW if is_friendly else Team.BLUE - if is_friendly_test: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.BLUE - ) - blue_play.name = PlayName.KickoffFriendlyPlay - yellow_play.name = PlayName.KickoffEnemyPlay + if not force_out: + blue_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.BLUE else PlayName.KickoffEnemyPlay + yellow_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.YELLOW else PlayName.KickoffEnemyPlay else: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.YELLOW - ) - blue_play.name = PlayName.KickoffEnemyPlay - yellow_play.name = PlayName.KickoffFriendlyPlay - - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.NORMAL_START, team=Team.BLUE - ) + blue_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.BLUE else PlayName.HaltPlay + yellow_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.YELLOW else PlayName.HaltPlay - # Force play override here + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.NORMAL_START, team=kicking_team) + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - # Always Validation +@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False)]) +def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): + ball_initial_pos = tbots_cpp.Point(0, 0) + + init_world_state(simulated_test_runner, setup_kickoff_attacker(), setup_kickoff_defender()) + init_plays(simulated_test_runner, is_friendly_test, force_open) + always_validation_sequence_set = [[]] ball_moves_at_rest_validation = BallAlwaysMovesFromRest( @@ -139,10 +142,13 @@ def test_kickoff_play(simulated_test_runner, is_friendly_test): simulated_test_runner.run_test( inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, + ci_cmd_with_delay=[ + (4, Command.Type.NORMAL_START, Team.BLUE), + ], test_timeout_s=10, ) if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-svv"])) + sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file From 197d7ff13fc8ea02c5358f784e055031dab6452d Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 22 Nov 2025 15:10:17 -0800 Subject: [PATCH 03/18] mystery halt issue persists, adjusted fsm --- .../stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h | 3 ++- src/software/ai/hl/stp/play/kickoff_play_test.py | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 94071fb9cd..121cbf0486 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -109,7 +109,8 @@ struct KickoffFriendlyPlayFSM ShootState_S + Update_E[isPlaying_G] = X, // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, + SetupState_S + Update_E = ChipState_S, + ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, ChipState_S + Update_E[isPlaying_G] = X, diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 7a96f83577..e02811198c 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -26,8 +26,6 @@ def setup_kickoff_attacker(): def setup_kickoff_defender(): - # The defense has a hole in it from 0, 0 to the net. - # Robots that cannot move will allow the kicker to shoot directly into the net. yellow_bots = [ tbots_cpp.Point(1, 0.5), tbots_cpp.Point(1, 2.5), @@ -67,13 +65,13 @@ def init_plays(simulated_test_runner, is_friendly, force_out): blue_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.BLUE else PlayName.HaltPlay yellow_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.YELLOW else PlayName.HaltPlay - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.NORMAL_START, team=kicking_team) simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=non_kicking_team) simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) -@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False)]) +@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)]) def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): ball_initial_pos = tbots_cpp.Point(0, 0) From 28d107a1d0872fdd9b4b77b56eaa13665b322c8c Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 24 Jan 2026 10:11:22 -0800 Subject: [PATCH 04/18] post merge checking --- src/software/ai/hl/stp/play/BUILD | 10 +- .../ai/hl/stp/play/kickoff_enemy/BUILD | 11 +- .../play/kickoff_enemy/kickoff_enemy_play.cpp | 4 +- .../play/kickoff_enemy/kickoff_enemy_play.h | 14 +-- .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 116 ++++++++---------- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 80 ++++++------ .../kickoff_enemy/kickoff_enemy_play_test.cpp | 28 ++--- .../ai/hl/stp/play/kickoff_friendly/BUILD | 8 +- .../kickoff_friendly_play.cpp | 5 +- .../kickoff_friendly/kickoff_friendly_play.h | 14 +-- .../kickoff_friendly_play_fsm.cpp | 116 +++++++++--------- .../kickoff_friendly_play_fsm.h | 82 ++++++------- .../kickoff_friendly_play_test.cpp | 28 ++--- .../ai/hl/stp/play/kickoff_play_test.py | 44 +++++-- 14 files changed, 285 insertions(+), 275 deletions(-) diff --git a/src/software/ai/hl/stp/play/BUILD b/src/software/ai/hl/stp/play/BUILD index 54d0932e3f..ba37daff60 100644 --- a/src/software/ai/hl/stp/play/BUILD +++ b/src/software/ai/hl/stp/play/BUILD @@ -81,13 +81,13 @@ cc_library( "//software/ai/hl/stp/play/enemy_free_kick:enemy_free_kick_play", "//software/ai/hl/stp/play/example:example_play", "//software/ai/hl/stp/play/free_kick:free_kick_play", - "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", - "//software/ai/hl/stp/play/kickoff_enemy:kickoff_enemy_play", "//software/ai/hl/stp/play/halt_play", "//software/ai/hl/stp/play/hardware_challenge_plays:dribbling_parcour_play", "//software/ai/hl/stp/play/hardware_challenge_plays:pass_endurance_play", "//software/ai/hl/stp/play/hardware_challenge_plays:scoring_from_contested_possession_play", "//software/ai/hl/stp/play/hardware_challenge_plays:scoring_with_static_defenders_play", + "//software/ai/hl/stp/play/kickoff_enemy:kickoff_enemy_play", + "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", "//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", @@ -95,10 +95,6 @@ cc_library( ], ) - - - - cc_test( name = "stop_play_test", srcs = ["stop_play_test.cpp"], @@ -214,4 +210,4 @@ py_test( "//software/simulated_tests:validation", requirement("pytest"), ], -) \ No newline at end of file +) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD index 5485b03841..04008711e4 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD @@ -1,22 +1,22 @@ -package(default_visibility = ["//visibility:public"]) - load("@simulated_tests_deps//:requirements.bzl", "requirement") +package(default_visibility = ["//visibility:public"]) + cc_library( name = "kickoff_enemy_play", srcs = [ "kickoff_enemy_play.cpp", - "kickoff_enemy_play_fsm.cpp" + "kickoff_enemy_play_fsm.cpp", ], hdrs = [ "kickoff_enemy_play.h", "kickoff_enemy_play_fsm.h", ], deps = [ - "//software/ai/hl/stp/play", "//shared:constants", "//software/ai/evaluation:enemy_threat", "//software/ai/evaluation:possession", + "//software/ai/hl/stp/play", "//software/ai/hl/stp/tactic/goalie:goalie_tactic", "//software/ai/hl/stp/tactic/move:move_tactic", "//software/ai/hl/stp/tactic/shadow_enemy:shadow_enemy_tactic", @@ -26,7 +26,6 @@ cc_library( alwayslink = True, ) - cc_test( name = "kickoff_enemy_play_cpp_test", srcs = ["kickoff_enemy_play_test.cpp"], @@ -42,4 +41,4 @@ cc_test( "//software/time:duration", "//software/world", ], -) \ No newline at end of file +) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp index f05fe15847..110c8b46bf 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp @@ -4,7 +4,7 @@ #include "software/util/generic_factory/generic_factory.h" KickoffEnemyPlay::KickoffEnemyPlay(TbotsProto::AiConfig config) - : Play(config, true), fsm{KickoffEnemyPlayFSM{config}}, control_params{} + : Play(config, true), fsm{KickoffEnemyPlayFSM{config}}, control_params{} { } @@ -32,4 +32,4 @@ std::vector KickoffEnemyPlay::getState() // Register this play in the genericFactory -static TGenericFactory factory; \ No newline at end of file +static TGenericFactory factory; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h index f41d898387..460c6b022c 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h @@ -10,12 +10,12 @@ class KickoffEnemyPlay : public Play { -public: + public: /** - * Creates an enemy kickoff play - * - * @param ai_config the play config for this play - */ + * Creates an enemy kickoff play + * + * @param ai_config the play config for this play + */ KickoffEnemyPlay(TbotsProto::AiConfig config); void getNextTactics(TacticCoroutine::push_type &yield, @@ -23,7 +23,7 @@ class KickoffEnemyPlay : public Play void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; -private: + private: FSM fsm; KickoffEnemyPlayFSM::ControlParams control_params; -}; \ No newline at end of file +}; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp index 0ff8528393..e8f14feecd 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -1,29 +1,26 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" KickoffEnemyPlayFSM::KickoffEnemyPlayFSM(const TbotsProto::AiConfig &ai_config) - : ai_config(ai_config), - shadow_enemy_tactics({ - std::make_shared(), - std::make_shared() - }), - move_tactics({ - std::make_shared(), // for robot 1 - std::make_shared(), // for robot 2 - std::make_shared(), // for robot 3 - std::make_shared(), // for robot 4 - std::make_shared() // for robot 5 - }) + : ai_config(ai_config), + shadow_enemy_tactics( + {std::make_shared(), std::make_shared()}), + move_tactics({ + std::make_shared(), // for robot 1 + std::make_shared(), // for robot 2 + std::make_shared(), // for robot 3 + std::make_shared(), // for robot 4 + std::make_shared() // for robot 5 + }) { - } void KickoffEnemyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) { - // these positions are picked according to the followicreateKickoffSetupPositions();ng slide - // https://images.slideplayer.com/32/9922349/slides/slide_2.jpg - // since we only have 6 robots at the maximum, 3 robots will shadow threats - // up front, 1 robot is dedicated as the goalie, and the other 2 robots will defend - // either post (as show in the image) + // these positions are picked according to the followicreateKickoffSetupPositions();ng + // slide https://images.slideplayer.com/32/9922349/slides/slide_2.jpg since we only + // have 6 robots at the maximum, 3 robots will shadow threats up front, 1 robot is + // dedicated as the goalie, and the other 2 robots will defend either post (as show in + // the image) // // Positions 1,2 are the most important, 3,4,5 are a fallback // if there aren't as many threats to shadow. Robots will be assigned @@ -45,32 +42,31 @@ void KickoffEnemyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) // | | | // | | | // +--------------------+--------------------+ - if (!kickoff_setup_positions.empty()) { + if (!kickoff_setup_positions.empty()) + { return; } kickoff_setup_positions = { - Point(world_ptr->field().friendlyGoalpostNeg().x() + + Point(world_ptr->field().friendlyGoalpostNeg().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - -world_ptr->field().defenseAreaYLength() / 2.0), - Point(world_ptr->field().friendlyGoalpostPos().x() + + -world_ptr->field().defenseAreaYLength() / 2.0), + Point(world_ptr->field().friendlyGoalpostPos().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().defenseAreaYLength() / 2.0), - Point(world_ptr->field().friendlyGoalCenter().x() + + world_ptr->field().defenseAreaYLength() / 2.0), + Point(world_ptr->field().friendlyGoalCenter().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalCenter().y()), - Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), - world_ptr->field().defenseAreaYLength() / 2.0), - Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), - -world_ptr->field().defenseAreaYLength() / 2.0), + world_ptr->field().friendlyGoalCenter().y()), + Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), + world_ptr->field().defenseAreaYLength() / 2.0), + Point(-(world_ptr->field().centerCircleRadius() + 2 * ROBOT_MAX_RADIUS_METERS), + -world_ptr->field().defenseAreaYLength() / 2.0), }; } -void KickoffEnemyPlayFSM::assignShadowing( - const std::vector &enemy_threats, - PriorityTacticVector &tactics_to_run, - size_t &defense_position_index -) +void KickoffEnemyPlayFSM::assignShadowing(const std::vector &enemy_threats, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index) { const auto shadower_count = std::min(2, enemy_threats.size()); @@ -81,47 +77,41 @@ void KickoffEnemyPlayFSM::assignShadowing( // Shadow with a distance slightly more than the distance from the enemy // robot to the center line, so we are always just on our side of the // center line - double shadow_dist = std::fabs(enemy_threat.robot.position().x()) + - 2 * ROBOT_MAX_RADIUS_METERS; + double shadow_dist = + std::fabs(enemy_threat.robot.position().x()) + 2 * ROBOT_MAX_RADIUS_METERS; // We shadow assuming the robots do not pass so we do not try block passes // while shadowing, since we can't go on the enemy side to block the pass // anyway - shadow_enemy_tactics.at(i)->updateControlParams(enemy_threat, - shadow_dist); + shadow_enemy_tactics.at(i)->updateControlParams(enemy_threat, shadow_dist); tactics_to_run[0].emplace_back(shadow_enemy_tactics.at(i)); } } -void KickoffEnemyPlayFSM::assignDefenders( - PriorityTacticVector &tactics_to_run, - size_t &defense_position_index -) +void KickoffEnemyPlayFSM::assignDefenders(PriorityTacticVector &tactics_to_run, + size_t &defense_position_index) { - while (defense_position_index < move_tactics.size() - 1 && defense_position_index < kickoff_setup_positions.size()) + while (defense_position_index < move_tactics.size() - 1 && + defense_position_index < kickoff_setup_positions.size()) { move_tactics.at(defense_position_index) - ->updateControlParams(kickoff_setup_positions.at(defense_position_index), - Angle::zero()); + ->updateControlParams(kickoff_setup_positions.at(defense_position_index), + Angle::zero()); tactics_to_run[0].emplace_back(move_tactics.at(defense_position_index)); defense_position_index++; } } -void KickoffEnemyPlayFSM::assignGoalBlocker( - const WorldPtr &world_ptr, - PriorityTacticVector &tactics_to_run, - size_t &defense_position_index -) +void KickoffEnemyPlayFSM::assignGoalBlocker(const WorldPtr &world_ptr, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index) { - move_tactics.back() - ->updateControlParams( - calculateBlockCone(world_ptr->field().friendlyGoalpostPos(), - world_ptr->field().friendlyGoalpostNeg(), - world_ptr->field().centerPoint(), - ROBOT_MAX_RADIUS_METERS), - Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT, - TbotsProto::ObstacleAvoidanceMode::AGGRESSIVE); + move_tactics.back()->updateControlParams( + calculateBlockCone(world_ptr->field().friendlyGoalpostPos(), + world_ptr->field().friendlyGoalpostNeg(), + world_ptr->field().centerPoint(), ROBOT_MAX_RADIUS_METERS), + Angle::zero(), TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT, + TbotsProto::ObstacleAvoidanceMode::AGGRESSIVE); tactics_to_run[0].emplace_back(move_tactics.at(defense_position_index)); defense_position_index++; } @@ -129,8 +119,8 @@ void KickoffEnemyPlayFSM::assignGoalBlocker( void KickoffEnemyPlayFSM::kickoff(const Update &event) { createKickoffSetupPositions(event.common.world_ptr); - WorldPtr world_ptr = event.common.world_ptr; - Team enemy_team = world_ptr->enemyTeam(); + WorldPtr world_ptr = event.common.world_ptr; + Team enemy_team = world_ptr->enemyTeam(); PriorityTacticVector tactics_to_run = {{}}; // TODO: (Mathew): Minor instability with defenders and goalie when the ball and @@ -153,8 +143,8 @@ void KickoffEnemyPlayFSM::kickoff(const Update &event) } auto enemy_threats = - getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), - world_ptr->enemyTeam(), world_ptr->ball(), false); + getAllEnemyThreats(world_ptr->field(), world_ptr->friendlyTeam(), + world_ptr->enemyTeam(), world_ptr->ball(), false); size_t defense_position_index = 0; assignShadowing(enemy_threats, tactics_to_run, defense_position_index); @@ -162,4 +152,4 @@ void KickoffEnemyPlayFSM::kickoff(const Update &event) assignGoalBlocker(world_ptr, tactics_to_run, defense_position_index); event.common.set_tactics(tactics_to_run); -} \ No newline at end of file +} diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index d7735932c8..cf57d13893 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -3,12 +3,12 @@ #include "proto/parameters.pb.h" #include "shared/constants.h" #include "software/ai/evaluation/enemy_threat.h" +#include "software/ai/evaluation/possession.h" #include "software/ai/hl/stp/play/play.h" #include "software/ai/hl/stp/play/play_fsm.h" -#include "software/ai/evaluation/possession.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/geom/algorithms/calculate_block_cone.h" -#include "software/ai/hl/stp/tactic/move/move_tactic.h" #include "software/logger/logger.h" @@ -27,53 +27,52 @@ struct KickoffEnemyPlayFSM * * @param ai_config the play config for this play FSM */ - explicit KickoffEnemyPlayFSM(const TbotsProto::AiConfig& ai_config); + explicit KickoffEnemyPlayFSM(const TbotsProto::AiConfig &ai_config); /** - * create a vector of setup positions if not already existing. - * - * @param world_ptr the world pointer - */ + * create a vector of setup positions if not already existing. + * + * @param world_ptr the world pointer + */ void createKickoffSetupPositions(const WorldPtr &world_ptr); /** - * add shadowing robots to tactics to run. - * - * @param enemy_threats the enemies that must be shadowed. - * @param tactics_to_run vector of tactics to run. - * @param defense_position_index index of robot for priority. - */ + * add shadowing robots to tactics to run. + * + * @param enemy_threats the enemies that must be shadowed. + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ void assignShadowing(const std::vector &enemy_threats, PriorityTacticVector &tactics_to_run, size_t &defense_position_index); /** - * add defenders to tactics to run. - * - * @param tactics_to_run vector of tactics to run. - * @param defense_position_index index of robot for priority. - */ - void assignDefenders(PriorityTacticVector &tactics_to_run, size_t &defense_position_index); + * add defenders to tactics to run. + * + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ + void assignDefenders(PriorityTacticVector &tactics_to_run, + size_t &defense_position_index); /** - * add a goal blocker to tactics to run. - * - * @param world_ptr the world pointer - * @param tactics_to_run vector of tactics to run. - * @param defense_position_index index of robot for priority. - */ - void assignGoalBlocker( - const WorldPtr &world_ptr, - PriorityTacticVector &tactics_to_run, - size_t &defense_position_index - ); + * add a goal blocker to tactics to run. + * + * @param world_ptr the world pointer + * @param tactics_to_run vector of tactics to run. + * @param defense_position_index index of robot for priority. + */ + void assignGoalBlocker(const WorldPtr &world_ptr, + PriorityTacticVector &tactics_to_run, + size_t &defense_position_index); /** - * Action to organize the bots to be ready for enemy kickoff. - * - * @param event the FreeKickPlayFSM Update event - */ - void kickoff(const Update& event); + * Action to organize the bots to be ready for enemy kickoff. + * + * @param event the FreeKickPlayFSM Update event + */ + void kickoff(const Update &event); auto operator()() @@ -88,15 +87,14 @@ struct KickoffEnemyPlayFSM return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E / kickoff_A = SetupState_S - ); + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E / kickoff_A = SetupState_S); } -private: + private: TbotsProto::AiConfig ai_config; std::vector> shadow_enemy_tactics; std::vector> move_tactics; std::vector kickoff_setup_positions; -}; \ No newline at end of file +}; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp index 97101c343a..6a363cceaf 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_test.cpp @@ -15,7 +15,7 @@ class KickoffEnemyPlayTest : public SimulatedErForceSimPlayTestFixture { -protected: + protected: TbotsProto::FieldType field_type = TbotsProto::FieldType::DIV_B; Field field = Field::createField(field_type); }; @@ -23,20 +23,20 @@ class KickoffEnemyPlayTest : public SimulatedErForceSimPlayTestFixture // TODO (#3105): Re-enable test once destinations are moved outside of obstacles TEST_F(KickoffEnemyPlayTest, DISABLED_test_kickoff_enemy_play) { -BallState ball_state(Point(0, 0), Vector(0, 0)); -auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( + BallState ball_state(Point(0, 0), Vector(0, 0)); + auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( {Point(-3, 2.5), Point(-3, 1.5), Point(-3, 0.5), Point(-3, -0.5), Point(-3, -1.5), Point(-3, -2.5)}); -setFriendlyGoalie(0); -auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( + setFriendlyGoalie(0); + auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), field.enemyDefenseArea().negXNegYCorner(), field.enemyDefenseArea().negXPosYCorner()}); -setEnemyGoalie(0); -setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); -setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_THEM); + setEnemyGoalie(0); + setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); + setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_THEM); -std::vector terminating_validation_functions = { + std::vector terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { // Two friendly robots in position to shadow enemy robots. Rectangles are @@ -54,10 +54,10 @@ std::vector terminating_validation_functions = { robotInPolygon(robotsDefendingRect, 2, world_ptr, yield); }}; -std::vector non_terminating_validation_functions = { + std::vector non_terminating_validation_functions = { robotsInFriendlyHalf, robotsNotInCenterCircle}; -runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); -} \ No newline at end of file + runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); +} diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD index 04de9f6ee0..b16f29953a 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD @@ -1,7 +1,7 @@ -package(default_visibility = ["//visibility:public"]) - load("@simulated_tests_deps//:requirements.bzl", "requirement") +package(default_visibility = ["//visibility:public"]) + cc_library( name = "kickoff_friendly_play", srcs = [ @@ -13,12 +13,12 @@ cc_library( "kickoff_friendly_play_fsm.h", ], deps = [ - "//software/ai/hl/stp/play", "//shared:constants", "//software/ai/evaluation:enemy_threat", + "//software/ai/evaluation:find_open_areas", + "//software/ai/hl/stp/play", "//software/ai/hl/stp/tactic/chip:chip_tactic", "//software/ai/hl/stp/tactic/move:move_tactic", - "//software/ai/evaluation:find_open_areas", "//software/logger", "//software/util/generic_factory", ], diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp index 4061d9e700..f3946728f5 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp @@ -5,7 +5,7 @@ KickoffFriendlyPlay::KickoffFriendlyPlay(TbotsProto::AiConfig config) - : Play(config, true), fsm{KickoffFriendlyPlayFSM{config}}, control_params{} + : Play(config, true), fsm{KickoffFriendlyPlayFSM{config}}, control_params{} { } @@ -32,4 +32,5 @@ std::vector KickoffFriendlyPlay::getState() } // Register this play in the genericFactory -static TGenericFactory factory; \ No newline at end of file +static TGenericFactory + factory; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index 88cf97f73e..bb8feee161 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -11,12 +11,12 @@ class KickoffFriendlyPlay : public Play { -public: + public: /** - * Creates a friendly kickoff play - * - * @param ai_config the play config for this play - */ + * Creates a friendly kickoff play + * + * @param ai_config the play config for this play + */ KickoffFriendlyPlay(TbotsProto::AiConfig config); void getNextTactics(TacticCoroutine::push_type &yield, @@ -24,7 +24,7 @@ class KickoffFriendlyPlay : public Play void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; -private: + private: FSM fsm; KickoffFriendlyPlayFSM::ControlParams control_params; -}; \ No newline at end of file +}; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index fd77872609..f509a0a371 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -1,18 +1,17 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM(const TbotsProto::AiConfig &ai_config) - : ai_config(ai_config), - kickoff_chip_tactic(std::make_shared()), - shoot_tactic(std::make_shared()), - move_tactics({ - std::make_shared(), // for robot 1 - std::make_shared(), // for robot 2 - std::make_shared(), // for robot 3 - std::make_shared(), // for robot 4 - std::make_shared() // for robot 5 - }) + : ai_config(ai_config), + kickoff_chip_tactic(std::make_shared()), + shoot_tactic(std::make_shared()), + move_tactics({ + std::make_shared(), // for robot 1 + std::make_shared(), // for robot 2 + std::make_shared(), // for robot 3 + std::make_shared(), // for robot 4 + std::make_shared() // for robot 5 + }) { - } void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) @@ -43,29 +42,31 @@ void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_p if (kickoff_setup_positions.empty()) { kickoff_setup_positions = { - // Robot 1 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius(), 0)), - // Robot 2 - // Goalie positions will be handled by the goalie tactic - // Robot 3 - Point( - world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - -1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 4 - Point( - world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - 1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 5 - Point(world_ptr->field().friendlyGoalpostPos().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostPos().y()), - // Robot 6 - Point(world_ptr->field().friendlyGoalpostNeg().x() + - world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostNeg().y()), + // Robot 1 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius(), 0)), + // Robot 2 + // Goalie positions will be handled by the goalie tactic + // Robot 3 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - + 4 * ROBOT_MAX_RADIUS_METERS, + -1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 4 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - + 4 * ROBOT_MAX_RADIUS_METERS, + 1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 5 + Point(world_ptr->field().friendlyGoalpostPos().x() + + world_ptr->field().defenseAreaXLength() + + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().friendlyGoalpostPos().y()), + // Robot 6 + Point(world_ptr->field().friendlyGoalpostNeg().x() + + world_ptr->field().defenseAreaXLength() + + 2 * ROBOT_MAX_RADIUS_METERS, + world_ptr->field().friendlyGoalpostNeg().y()), }; } } @@ -78,8 +79,8 @@ void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) PriorityTacticVector tactics_to_run = {{}}; // first priority requires the ability to kick and chip. - move_tactics.at(0)->mutableRobotCapabilityRequirements() = { - RobotCapability::Kick, RobotCapability::Chip}; + move_tactics.at(0)->mutableRobotCapabilityRequirements() = {RobotCapability::Kick, + RobotCapability::Chip}; // set each tactic to its movement location. for (unsigned i = 0; i < kickoff_setup_positions.size(); i++) @@ -95,14 +96,14 @@ void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) // taken from free kick fsm. void KickoffFriendlyPlayFSM::shootBall(const Update &event) { - WorldPtr world_ptr = event.common.world_ptr; + WorldPtr world_ptr = event.common.world_ptr; PriorityTacticVector tactics_to_run = {{}}; Point ball_pos = world_ptr->ball().position(); shoot_tactic->updateControlParams( - ball_pos, (shot->getPointToShootAt() - ball_pos).orientation(), - BALL_MAX_SPEED_METERS_PER_SECOND); + ball_pos, (shot->getPointToShootAt() - ball_pos).orientation(), + BALL_MAX_SPEED_METERS_PER_SECOND); tactics_to_run[0].emplace_back(shoot_tactic); @@ -115,35 +116,36 @@ void KickoffFriendlyPlayFSM::chipBall(const Update &event) PriorityTacticVector tactics_to_run = {{}}; - // adjust with testing to give us enough space to catch the ball before it goes out of bounds + // adjust with testing to give us enough space to catch the ball before it goes out of + // bounds double ballX = world_ptr->ball().position().x(); double fieldX = world_ptr->field().enemyGoalCenter().x() - 2; double negFieldY = world_ptr->field().enemyCornerNeg().y() + 0.3; double posFieldY = world_ptr->field().enemyCornerPos().y() - 0.3; Rectangle target_area_rectangle = - Rectangle(Point(ballX, negFieldY), Point(fieldX, posFieldY)); + Rectangle(Point(ballX, negFieldY), Point(fieldX, posFieldY)); // sort targets by distance to enemy goal center. - std::vector potential_chip_targets = findGoodChipTargets(*world_ptr, target_area_rectangle); - std::sort(potential_chip_targets.begin(), potential_chip_targets.end(), - [world_ptr](const Circle& first_circle, const Circle& second_circle) { - - return distance(world_ptr->field().enemyGoalCenter(), first_circle.origin()) < - distance(world_ptr->field().enemyGoalCenter(), second_circle.origin()); - }); - - Point target = world_ptr->field().centerPoint() + Vector(world_ptr->field().xLength() / 6, 0); + std::vector potential_chip_targets = + findGoodChipTargets(*world_ptr, target_area_rectangle); + std::sort( + potential_chip_targets.begin(), potential_chip_targets.end(), + [world_ptr](const Circle &first_circle, const Circle &second_circle) + { + return distance(world_ptr->field().enemyGoalCenter(), first_circle.origin()) < + distance(world_ptr->field().enemyGoalCenter(), second_circle.origin()); + }); + + Point target = + world_ptr->field().centerPoint() + Vector(world_ptr->field().xLength() / 6, 0); if (!potential_chip_targets.empty()) { target = potential_chip_targets[0].origin(); } - kickoff_chip_tactic->updateControlParams( - world_ptr->ball().position(), - target - ); + kickoff_chip_tactic->updateControlParams(world_ptr->ball().position(), target); tactics_to_run[0].emplace_back(kickoff_chip_tactic); @@ -155,7 +157,7 @@ bool KickoffFriendlyPlayFSM::isSetupDone(const Update &event) return !event.common.world_ptr->gameState().isSetupState(); } -bool KickoffFriendlyPlayFSM::isPlaying(const Update& event) +bool KickoffFriendlyPlayFSM::isPlaying(const Update &event) { return event.common.world_ptr->gameState().isPlaying(); } @@ -168,6 +170,6 @@ bool KickoffFriendlyPlayFSM::shotFound(const Update &event) event.common.world_ptr->ball().position(), TeamType::ENEMY); return shot.has_value() && shot->getOpenAngle() > - Angle::fromDegrees( + Angle::fromDegrees( ai_config.attacker_tactic_config().min_open_angle_for_shot_deg()); -} \ No newline at end of file +} diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 121cbf0486..e9f38192a9 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -3,12 +3,12 @@ #include "proto/parameters.pb.h" #include "shared/constants.h" #include "software/ai/evaluation/enemy_threat.h" +#include "software/ai/evaluation/find_open_areas.h" #include "software/ai/hl/stp/play/play.h" #include "software/ai/hl/stp/play/play_fsm.h" +#include "software/ai/hl/stp/tactic/chip/chip_tactic.h" #include "software/ai/hl/stp/tactic/kick/kick_tactic.h" #include "software/ai/hl/stp/tactic/move/move_tactic.h" -#include "software/ai/hl/stp/tactic/chip/chip_tactic.h" -#include "software/ai/evaluation/find_open_areas.h" #include "software/logger/logger.h" struct KickoffFriendlyPlayFSM @@ -30,11 +30,11 @@ struct KickoffFriendlyPlayFSM explicit KickoffFriendlyPlayFSM(const TbotsProto::AiConfig& ai_config); /** - * create a vector of setup positions if not already existing. - * - * @param world_ptr the world pointer - */ - void createKickoffSetupPositions(const WorldPtr &world_ptr); + * create a vector of setup positions if not already existing. + * + * @param world_ptr the world pointer + */ + void createKickoffSetupPositions(const WorldPtr& world_ptr); @@ -53,31 +53,31 @@ struct KickoffFriendlyPlayFSM void shootBall(const Update& event); /** - * Action to chip the ball forward over the defenders. - * - * @param event the FreeKickPlayFSM Update event - */ + * Action to chip the ball forward over the defenders. + * + * @param event the FreeKickPlayFSM Update event + */ void chipBall(const Update& event); /** - * Guard that checks if positions are set up. - * - * @param event the FreeKickPlayFSM Update event - */ + * Guard that checks if positions are set up. + * + * @param event the FreeKickPlayFSM Update event + */ bool isSetupDone(const Update& event); /** - * Guard that checks if game has started (ball kicked). - * - * @param event the FreeKickPlayFSM Update event - */ + * Guard that checks if game has started (ball kicked). + * + * @param event the FreeKickPlayFSM Update event + */ bool isPlaying(const Update& event); /** - * Guard that checks if a direct shot on the net is possible. - * - * @param event the FreeKickPlayFSM Update event - */ + * Guard that checks if a direct shot on the net is possible. + * + * @param event the FreeKickPlayFSM Update event + */ bool shotFound(const Update& event); auto operator()() @@ -99,29 +99,29 @@ struct KickoffFriendlyPlayFSM DEFINE_SML_GUARD(isPlaying) return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - - // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, - ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, - ShootState_S + Update_E[isPlaying_G] = X, - - // else chip over the defenders. - SetupState_S + Update_E = ChipState_S, - ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, - - X + Update_E = X); + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + + // shoot directly at net if possible. + SetupState_S + Update_E[shotFound_G] = ShootState_S, + ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, + ShootState_S + Update_E[isPlaying_G] = X, + + // else chip over the defenders. + SetupState_S + Update_E = ChipState_S, + ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, + + X + Update_E = X); } -private: + private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; std::shared_ptr shoot_tactic; std::vector> move_tactics; std::vector kickoff_setup_positions; std::optional shot; -}; \ No newline at end of file +}; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp index 3421bf5a3c..73a5dab569 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_test.cpp @@ -16,7 +16,7 @@ class KickoffFriendlyPlayTest : public SimulatedErForceSimPlayTestFixture { -protected: + protected: TbotsProto::FieldType field_type = TbotsProto::FieldType::DIV_B; Field field = Field::createField(field_type); }; @@ -24,20 +24,20 @@ class KickoffFriendlyPlayTest : public SimulatedErForceSimPlayTestFixture // TODO (#2608): re-enable when fixed TEST_F(KickoffFriendlyPlayTest, DISABLED_test_kickoff_friendly_play) { -BallState ball_state(Point(0, 0), Vector(0, 0)); -auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( + BallState ball_state(Point(0, 0), Vector(0, 0)); + auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( {Point(-3, 2.5), Point(-3, 1.5), Point(-3, 0.5), Point(-3, -0.5), Point(-3, -1.5), Point(-3, -2.5)}); -setFriendlyGoalie(0); -auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( + setFriendlyGoalie(0); + auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), field.enemyDefenseArea().negXNegYCorner(), field.enemyDefenseArea().negXPosYCorner()}); -setEnemyGoalie(0); -setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); -setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_US); + setEnemyGoalie(0); + setAiPlay(TbotsProto::PlayName::KickoffEnemyPlay); + setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_KICKOFF_US); -std::vector terminating_validation_functions = { + std::vector terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { // Robot 4 is the only robot allowed to be in the center circle and start @@ -56,7 +56,7 @@ std::vector terminating_validation_functions = { robotInPolygon(robotsDefensiveRect, 3, world_ptr, yield); }}; -std::vector non_terminating_validation_functions = { + std::vector non_terminating_validation_functions = { [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) { for (RobotId robot_id : {0, 1, 2, 3, 5}) @@ -68,7 +68,7 @@ std::vector non_terminating_validation_functions = { } }}; -runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); -} \ No newline at end of file + runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); +} diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index e02811198c..0fc123a299 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -51,7 +51,6 @@ def init_world_state(runner, blue_bots, yellow_bots): def init_plays(simulated_test_runner, is_friendly, force_out): - blue_play = Play() yellow_play = Play() @@ -59,23 +58,48 @@ def init_plays(simulated_test_runner, is_friendly, force_out): non_kicking_team = Team.YELLOW if is_friendly else Team.BLUE if not force_out: - blue_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.BLUE else PlayName.KickoffEnemyPlay - yellow_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.YELLOW else PlayName.KickoffEnemyPlay + blue_play.name = ( + PlayName.KickoffFriendlyPlay + if kicking_team == Team.BLUE + else PlayName.KickoffEnemyPlay + ) + yellow_play.name = ( + PlayName.KickoffFriendlyPlay + if kicking_team == Team.YELLOW + else PlayName.KickoffEnemyPlay + ) else: - blue_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.BLUE else PlayName.HaltPlay - yellow_play.name = PlayName.KickoffFriendlyPlay if kicking_team == Team.YELLOW else PlayName.HaltPlay + blue_play.name = ( + PlayName.KickoffFriendlyPlay + if kicking_team == Team.BLUE + else PlayName.HaltPlay + ) + yellow_play.name = ( + PlayName.KickoffFriendlyPlay + if kicking_team == Team.YELLOW + else PlayName.HaltPlay + ) - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=non_kicking_team) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=kicking_team + ) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=non_kicking_team + ) simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) -@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)]) +@pytest.mark.parametrize( + "is_friendly_test, force_open", + [(True, False), (True, True), (False, False), (False, True)], +) def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): ball_initial_pos = tbots_cpp.Point(0, 0) - init_world_state(simulated_test_runner, setup_kickoff_attacker(), setup_kickoff_defender()) + init_world_state( + simulated_test_runner, setup_kickoff_attacker(), setup_kickoff_defender() + ) init_plays(simulated_test_runner, is_friendly_test, force_open) always_validation_sequence_set = [[]] @@ -149,4 +173,4 @@ def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-svv"])) \ No newline at end of file + sys.exit(pytest.main([__file__, "-svv"])) From 9a304b4d3ef98babe41708183c5f19ff5753a262 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:11:08 +0000 Subject: [PATCH 05/18] [pre-commit.ci lite] apply automatic fixes --- docs/fsm-diagrams.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/fsm-diagrams.md b/docs/fsm-diagrams.md index 8059b8e10d..18ee7a94d3 100644 --- a/docs/fsm-diagrams.md +++ b/docs/fsm-diagrams.md @@ -159,6 +159,38 @@ Terminate:::terminate --> Terminate:::terminate : updateStop ``` +## [KickoffEnemyPlayFSM](/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h) + +```mermaid + +stateDiagram-v2 +classDef terminate fill:white,color:black,font-weight:bold +direction LR +[*] --> SetupState +SetupState --> SetupState : kickoff + +``` + +## [KickoffFriendlyPlayFSM](/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h) + +```mermaid + +stateDiagram-v2 +classDef terminate fill:white,color:black,font-weight:bold +direction LR +[*] --> SetupState +SetupState --> SetupState : [!isSetupDone]\nsetupKickoff +SetupState --> ShootState : [shotFound] +ShootState --> ShootState : [!isPlaying]\nshootBall +ShootState --> Terminate:::terminate : [isPlaying] +SetupState --> ChipState +ChipState --> ShootState : [shotFound && !isPlaying] +ChipState --> ChipState : [!isPlaying]\nchipBall +ChipState --> Terminate:::terminate : [isPlaying] +Terminate:::terminate --> Terminate:::terminate + +``` + ## [OffensePlayFSM](/src/software/ai/hl/stp/play/offense/offense_play_fsm.h) ```mermaid From 1be1c154eb566b7553343583ba77b0e6a8e412d0 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 21 Feb 2026 19:34:40 -0800 Subject: [PATCH 06/18] temporary --- .../kickoff_friendly/kickoff_friendly_play.h | 2 +- .../kickoff_friendly/kickoff_friendly_play_fsm.h | 13 +++++++++++++ src/software/ai/hl/stp/play/kickoff_play_test.py | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index bb8feee161..0e0f5ba165 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -24,7 +24,7 @@ class KickoffFriendlyPlay : public Play void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - private: + private:q FSM fsm; KickoffFriendlyPlayFSM::ControlParams control_params; }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index e9f38192a9..26934867ce 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -108,11 +108,24 @@ struct KickoffFriendlyPlayFSM ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, ShootState_S + Update_E[isPlaying_G] = X, +<<<<<<< HEAD // else chip over the defenders. SetupState_S + Update_E = ChipState_S, ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, ChipState_S + Update_E[isPlaying_G] = X, +||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) + // else chip over the defenders. + SetupState_S + Update_E = ChipState_S, + ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, +======= + // else chip over the defenders. + SetupState_S + Update_E[!shotFound_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, +>>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) X + Update_E = X); } diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 0fc123a299..8596b3fd38 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -26,6 +26,8 @@ def setup_kickoff_attacker(): def setup_kickoff_defender(): + # The defense has a hole in it from 0, 0 to the net. + # Robots that cannot move will allow the kicker to shoot directly into the net. yellow_bots = [ tbots_cpp.Point(1, 0.5), tbots_cpp.Point(1, 2.5), @@ -80,20 +82,34 @@ def init_plays(simulated_test_runner, is_friendly, force_out): else PlayName.HaltPlay ) +<<<<<<< HEAD simulated_test_runner.gamecontroller.send_gc_command( gc_command=Command.Type.KICKOFF, team=kicking_team ) simulated_test_runner.gamecontroller.send_gc_command( gc_command=Command.Type.KICKOFF, team=non_kicking_team ) +||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=non_kicking_team) +======= + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.NORMAL_START, team=kicking_team) + simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) +>>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) +<<<<<<< HEAD @pytest.mark.parametrize( "is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)], ) +||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) +@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)]) +======= +@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False)]) +>>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): ball_initial_pos = tbots_cpp.Point(0, 0) From 637c5162532261bf68e166f6ee36ad21d0bf2a60 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 04:17:17 +0000 Subject: [PATCH 07/18] [pre-commit.ci lite] apply automatic fixes --- docs/fsm-diagrams.md | 11 +++++++++-- .../kickoff_friendly/kickoff_friendly_play.h | 4 ++-- .../kickoff_friendly_play_fsm.h | 18 +++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/fsm-diagrams.md b/docs/fsm-diagrams.md index 18ee7a94d3..cd80677840 100644 --- a/docs/fsm-diagrams.md +++ b/docs/fsm-diagrams.md @@ -183,11 +183,18 @@ SetupState --> SetupState : [!isSetupDone]\nsetupKickoff SetupState --> ShootState : [shotFound] ShootState --> ShootState : [!isPlaying]\nshootBall ShootState --> Terminate:::terminate : [isPlaying] -SetupState --> ChipState +<<<<<< ChipState ChipState --> ShootState : [shotFound && !isPlaying] ChipState --> ChipState : [!isPlaying]\nchipBall ChipState --> Terminate:::terminate : [isPlaying] -Terminate:::terminate --> Terminate:::terminate +|||||||197d7ff13(mysteryhaltissuepersists,adjustedfsm)SetupState --> ChipState +ChipState --> ShootState : [shotFound && !isPlaying] +ChipState --> ChipState : [!isPlaying]\nchipBall +ChipState --> Terminate:::terminate : [isPlaying] + --> ======SetupState_S+Update_E[!shotFound_G]=ChipState +ChipState --> ChipState : [!isPlaying]\nchipBall +ChipState --> Terminate:::terminate : [isPlaying] +>>>>>>>parentof197d7ff13(mysteryhaltissuepersists,adjustedfsm)X --> Terminate:::terminate ``` diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index 0e0f5ba165..c9898511f2 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -24,7 +24,7 @@ class KickoffFriendlyPlay : public Play void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - private:q - FSM fsm; + private: + q FSM fsm; KickoffFriendlyPlayFSM::ControlParams control_params; }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 26934867ce..895331f8d9 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -115,16 +115,16 @@ struct KickoffFriendlyPlayFSM ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, ChipState_S + Update_E[isPlaying_G] = X, ||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) - // else chip over the defenders. - SetupState_S + Update_E = ChipState_S, - ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + // else chip over the defenders. + SetupState_S + Update_E = ChipState_S, + ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, ======= - // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + // else chip over the defenders. + SetupState_S + Update_E[!shotFound_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, >>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) X + Update_E = X); From 01f1b4931e1a8cc14aecd60fe4826b591f496b11 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 21 Feb 2026 20:17:40 -0800 Subject: [PATCH 08/18] removed merge conflicts --- .../kickoff_friendly/kickoff_friendly_play.h | 4 +- .../kickoff_friendly_play_fsm.h | 42 +++++++------------ .../ai/hl/stp/play/kickoff_play_test.py | 15 ------- 3 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index 0e0f5ba165..c9898511f2 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -24,7 +24,7 @@ class KickoffFriendlyPlay : public Play void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - private:q - FSM fsm; + private: + q FSM fsm; KickoffFriendlyPlayFSM::ControlParams control_params; }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 26934867ce..2c1925daf5 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -99,38 +99,24 @@ struct KickoffFriendlyPlayFSM DEFINE_SML_GUARD(isPlaying) return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - - // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, - ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, - ShootState_S + Update_E[isPlaying_G] = X, - -<<<<<<< HEAD - // else chip over the defenders. - SetupState_S + Update_E = ChipState_S, - ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, -||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) - // else chip over the defenders. - SetupState_S + Update_E = ChipState_S, - ChipState_S + Update_E[shotFound_G && !isPlaying_G] = ShootState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, -======= + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + + // shoot directly at net if possible. + SetupState_S + Update_E[shotFound_G] = ShootState_S, + ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, + ShootState_S + Update_E[isPlaying_G] = X, + // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, ->>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) + SetupState_S + Update_E[!shotFound_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, - X + Update_E = X); + X + Update_E = X); } - private: +private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; std::shared_ptr shoot_tactic; diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 8596b3fd38..c5fb5e3717 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -51,7 +51,6 @@ def init_world_state(runner, blue_bots, yellow_bots): ), ) - def init_plays(simulated_test_runner, is_friendly, force_out): blue_play = Play() yellow_play = Play() @@ -82,34 +81,20 @@ def init_plays(simulated_test_runner, is_friendly, force_out): else PlayName.HaltPlay ) -<<<<<<< HEAD simulated_test_runner.gamecontroller.send_gc_command( gc_command=Command.Type.KICKOFF, team=kicking_team ) simulated_test_runner.gamecontroller.send_gc_command( gc_command=Command.Type.KICKOFF, team=non_kicking_team ) -||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=non_kicking_team) -======= - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.NORMAL_START, team=kicking_team) - simulated_test_runner.gamecontroller.send_gc_command(gc_command=Command.Type.KICKOFF, team=kicking_team) ->>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) -<<<<<<< HEAD @pytest.mark.parametrize( "is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)], ) -||||||| 197d7ff13 (mystery halt issue persists, adjusted fsm) -@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False), (True, True), (False, False), (False, True)]) -======= -@pytest.mark.parametrize("is_friendly_test, force_open", [(True, False)]) ->>>>>>> parent of 197d7ff13 (mystery halt issue persists, adjusted fsm) def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): ball_initial_pos = tbots_cpp.Point(0, 0) From 46a7613b7800ecac5a9323909cfd155abff8888e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 04:28:36 +0000 Subject: [PATCH 09/18] [pre-commit.ci lite] apply automatic fixes --- docs/fsm-diagrams.md | 3 +-- .../kickoff_friendly_play_fsm.h | 26 +++++++++---------- .../ai/hl/stp/play/kickoff_play_test.py | 1 + 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/fsm-diagrams.md b/docs/fsm-diagrams.md index 18ee7a94d3..6365073db6 100644 --- a/docs/fsm-diagrams.md +++ b/docs/fsm-diagrams.md @@ -183,8 +183,7 @@ SetupState --> SetupState : [!isSetupDone]\nsetupKickoff SetupState --> ShootState : [shotFound] ShootState --> ShootState : [!isPlaying]\nshootBall ShootState --> Terminate:::terminate : [isPlaying] -SetupState --> ChipState -ChipState --> ShootState : [shotFound && !isPlaying] +SetupState --> ChipState : [!shotFound] ChipState --> ChipState : [!isPlaying]\nchipBall ChipState --> Terminate:::terminate : [isPlaying] Terminate:::terminate --> Terminate:::terminate diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 2c1925daf5..91b4ef0818 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -99,24 +99,24 @@ struct KickoffFriendlyPlayFSM DEFINE_SML_GUARD(isPlaying) return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, - ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, - ShootState_S + Update_E[isPlaying_G] = X, + // shoot directly at net if possible. + SetupState_S + Update_E[shotFound_G] = ShootState_S, + ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, + ShootState_S + Update_E[isPlaying_G] = X, - // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + // else chip over the defenders. + SetupState_S + Update_E[!shotFound_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, - X + Update_E = X); + X + Update_E = X); } -private: + private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; std::shared_ptr shoot_tactic; diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index c5fb5e3717..2fd3546c3d 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -51,6 +51,7 @@ def init_world_state(runner, blue_bots, yellow_bots): ), ) + def init_plays(simulated_test_runner, is_friendly, force_out): blue_play = Play() yellow_play = Play() From 20571a5b393d489635d82901d0220fe091c36145 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 21 Feb 2026 21:33:19 -0800 Subject: [PATCH 10/18] it works, testing doesnt tho --- .../play/kickoff_enemy/kickoff_enemy_play.cpp | 9 ++++--- .../play/kickoff_enemy/kickoff_enemy_play.h | 12 ++++------ .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 24 +++++++++---------- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 9 +++---- .../kickoff_friendly_play.cpp | 10 ++++---- .../kickoff_friendly/kickoff_friendly_play.h | 11 ++++----- .../kickoff_friendly_play_fsm.cpp | 23 +++++++++--------- .../kickoff_friendly_play_fsm.h | 11 +++++---- 8 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp index 110c8b46bf..fa7f08146f 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp @@ -1,10 +1,12 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h" +#include "proto/parameters.pb.h" #include "shared/constants.h" #include "software/util/generic_factory/generic_factory.h" -KickoffEnemyPlay::KickoffEnemyPlay(TbotsProto::AiConfig config) - : Play(config, true), fsm{KickoffEnemyPlayFSM{config}}, control_params{} +KickoffEnemyPlay::KickoffEnemyPlay( + std::shared_ptr ai_config_ptr) + : PlayBase(ai_config_ptr, false) { } @@ -32,4 +34,5 @@ std::vector KickoffEnemyPlay::getState() // Register this play in the genericFactory -static TGenericFactory factory; +static TGenericFactory> factory; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h index 460c6b022c..15ddc4a437 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h @@ -1,6 +1,8 @@ #pragma once #include "proto/parameters.pb.h" +#include "software/ai/hl/stp/play/play_fsm.hpp" +#include "software/ai/hl/stp/play/play_base.hpp" #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" #include "software/ai/hl/stp/play/play.h" @@ -8,22 +10,18 @@ * A play that runs when its currently the enemy kick off. */ -class KickoffEnemyPlay : public Play +class KickoffEnemyPlay : public PlayBase { public: /** * Creates an enemy kickoff play * - * @param ai_config the play config for this play + * @param ai_config_ptr the play config for this play */ - KickoffEnemyPlay(TbotsProto::AiConfig config); + explicit KickoffEnemyPlay(std::shared_ptr ai_config_ptr); void getNextTactics(TacticCoroutine::push_type &yield, const WorldPtr &world_ptr) override; void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - - private: - FSM fsm; - KickoffEnemyPlayFSM::ControlParams control_params; }; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp index e8f14feecd..54d0c298e7 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -1,22 +1,22 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" -KickoffEnemyPlayFSM::KickoffEnemyPlayFSM(const TbotsProto::AiConfig &ai_config) - : ai_config(ai_config), - shadow_enemy_tactics( - {std::make_shared(), std::make_shared()}), - move_tactics({ - std::make_shared(), // for robot 1 - std::make_shared(), // for robot 2 - std::make_shared(), // for robot 3 - std::make_shared(), // for robot 4 - std::make_shared() // for robot 5 - }) +KickoffEnemyPlayFSM::KickoffEnemyPlayFSM( + std::shared_ptr ai_config_ptr) + : PlayFSM(ai_config_ptr), + shadow_enemy_tactics{std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)}, + move_tactics{std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)} { } + void KickoffEnemyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) { - // these positions are picked according to the followicreateKickoffSetupPositions();ng + // these positions are picked according to the following: createKickoffSetupPositions(); // slide https://images.slideplayer.com/32/9922349/slides/slide_2.jpg since we only // have 6 robots at the maximum, 3 robots will shadow threats up front, 1 robot is // dedicated as the goalie, and the other 2 robots will defend either post (as show in diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index cf57d13893..8ddfe665e7 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -5,7 +5,7 @@ #include "software/ai/evaluation/enemy_threat.h" #include "software/ai/evaluation/possession.h" #include "software/ai/hl/stp/play/play.h" -#include "software/ai/hl/stp/play/play_fsm.h" +#include "software/ai/hl/stp/play/play_fsm.hpp" #include "software/ai/hl/stp/tactic/move/move_tactic.h" #include "software/ai/hl/stp/tactic/shadow_enemy/shadow_enemy_tactic.h" #include "software/geom/algorithms/calculate_block_cone.h" @@ -13,7 +13,7 @@ -struct KickoffEnemyPlayFSM +struct KickoffEnemyPlayFSM: PlayFSM { class SetupState; @@ -21,13 +21,14 @@ struct KickoffEnemyPlayFSM { }; - DEFINE_PLAY_UPDATE_STRUCT_WITH_CONTROL_AND_COMMON_PARAMS + /** * Creates a kickoff enemy play FSM * * @param ai_config the play config for this play FSM */ - explicit KickoffEnemyPlayFSM(const TbotsProto::AiConfig &ai_config); + explicit KickoffEnemyPlayFSM(std::shared_ptr ai_config_ptr); + /** * create a vector of setup positions if not already existing. diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp index f3946728f5..04853f8e25 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp @@ -1,11 +1,13 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h" +#include "proto/parameters.pb.h" #include "shared/constants.h" #include "software/util/generic_factory/generic_factory.h" -KickoffFriendlyPlay::KickoffFriendlyPlay(TbotsProto::AiConfig config) - : Play(config, true), fsm{KickoffFriendlyPlayFSM{config}}, control_params{} +KickoffFriendlyPlay::KickoffFriendlyPlay( + std::shared_ptr ai_config_ptr) + :PlayBase(ai_config_ptr, true) { } @@ -32,5 +34,5 @@ std::vector KickoffFriendlyPlay::getState() } // Register this play in the genericFactory -static TGenericFactory - factory; +static TGenericFactory> factory; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index c9898511f2..bc12cd1500 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -1,6 +1,8 @@ #pragma once #include "proto/parameters.pb.h" +#include "software/ai/hl/stp/play/play_fsm.hpp" +#include "software/ai/hl/stp/play/play_base.hpp" #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" #include "software/ai/hl/stp/play/play.h" @@ -9,22 +11,19 @@ * only one robot grabs the ball and passes to another robot. */ -class KickoffFriendlyPlay : public Play +class KickoffFriendlyPlay : public PlayBase { public: /** * Creates a friendly kickoff play * - * @param ai_config the play config for this play + * @param ai_config_ptr the play config for this play */ - KickoffFriendlyPlay(TbotsProto::AiConfig config); + explicit KickoffFriendlyPlay(std::shared_ptr ai_config_ptr); void getNextTactics(TacticCoroutine::push_type &yield, const WorldPtr &world_ptr) override; void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - private: - q FSM fsm; - KickoffFriendlyPlayFSM::ControlParams control_params; }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index f509a0a371..0004433e8a 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -1,16 +1,17 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" -KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM(const TbotsProto::AiConfig &ai_config) - : ai_config(ai_config), - kickoff_chip_tactic(std::make_shared()), - shoot_tactic(std::make_shared()), - move_tactics({ - std::make_shared(), // for robot 1 - std::make_shared(), // for robot 2 - std::make_shared(), // for robot 3 - std::make_shared(), // for robot 4 - std::make_shared() // for robot 5 - }) +KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM( + std::shared_ptr ai_config_ptr) + : PlayFSM(ai_config_ptr), + kickoff_chip_tactic(std::make_shared(ai_config_ptr)), + shoot_tactic(std::make_shared(ai_config_ptr)), + move_tactics{ + std::make_shared(ai_config_ptr), // robot 1 + std::make_shared(ai_config_ptr), // robot 2-5 + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr) + } { } diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 2c1925daf5..16047f0486 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -5,13 +5,13 @@ #include "software/ai/evaluation/enemy_threat.h" #include "software/ai/evaluation/find_open_areas.h" #include "software/ai/hl/stp/play/play.h" -#include "software/ai/hl/stp/play/play_fsm.h" +#include "software/ai/hl/stp/play/play_fsm.hpp" #include "software/ai/hl/stp/tactic/chip/chip_tactic.h" #include "software/ai/hl/stp/tactic/kick/kick_tactic.h" #include "software/ai/hl/stp/tactic/move/move_tactic.h" #include "software/logger/logger.h" -struct KickoffFriendlyPlayFSM +struct KickoffFriendlyPlayFSM : PlayFSM { class SetupState; class ShootState; @@ -21,13 +21,14 @@ struct KickoffFriendlyPlayFSM { }; - DEFINE_PLAY_UPDATE_STRUCT_WITH_CONTROL_AND_COMMON_PARAMS + /** * Creates a kickoff friendly play FSM * - * @param ai_config the play config for this play FSM + * @param ai_config_ptr the play config for this play FSM */ - explicit KickoffFriendlyPlayFSM(const TbotsProto::AiConfig& ai_config); + explicit KickoffFriendlyPlayFSM(std::shared_ptr ai_config_ptr); + /** * create a vector of setup positions if not already existing. From 9aced519461e927f2823a042626fca6053913eae Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 21 Feb 2026 21:35:11 -0800 Subject: [PATCH 11/18] works --- .../play/kickoff_enemy/kickoff_enemy_play.cpp | 7 +++-- .../play/kickoff_enemy/kickoff_enemy_play.h | 4 +-- .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 29 ++++++++++--------- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 5 ++-- .../kickoff_friendly_play.cpp | 7 +++-- .../kickoff_friendly/kickoff_friendly_play.h | 8 ++--- .../kickoff_friendly_play_fsm.cpp | 20 ++++++------- .../kickoff_friendly_play_fsm.h | 29 ++++++++++--------- .../ai/hl/stp/play/kickoff_play_test.py | 1 + 9 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp index fa7f08146f..c6502c3b14 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.cpp @@ -5,8 +5,8 @@ #include "software/util/generic_factory/generic_factory.h" KickoffEnemyPlay::KickoffEnemyPlay( - std::shared_ptr ai_config_ptr) - : PlayBase(ai_config_ptr, false) + std::shared_ptr ai_config_ptr) + : PlayBase(ai_config_ptr, false) { } @@ -35,4 +35,5 @@ std::vector KickoffEnemyPlay::getState() // Register this play in the genericFactory static TGenericFactory> factory; + std::shared_ptr> + factory; diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h index 15ddc4a437..275d41c790 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play.h @@ -1,10 +1,10 @@ #pragma once #include "proto/parameters.pb.h" -#include "software/ai/hl/stp/play/play_fsm.hpp" -#include "software/ai/hl/stp/play/play_base.hpp" #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" #include "software/ai/hl/stp/play/play.h" +#include "software/ai/hl/stp/play/play_base.hpp" +#include "software/ai/hl/stp/play/play_fsm.hpp" /** * A play that runs when its currently the enemy kick off. diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp index 54d0c298e7..e995730b93 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -1,26 +1,27 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" KickoffEnemyPlayFSM::KickoffEnemyPlayFSM( - std::shared_ptr ai_config_ptr) - : PlayFSM(ai_config_ptr), - shadow_enemy_tactics{std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr)}, - move_tactics{std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr)} + std::shared_ptr ai_config_ptr) + : PlayFSM(ai_config_ptr), + shadow_enemy_tactics{std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)}, + move_tactics{std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)} { } void KickoffEnemyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) { - // these positions are picked according to the following: createKickoffSetupPositions(); - // slide https://images.slideplayer.com/32/9922349/slides/slide_2.jpg since we only - // have 6 robots at the maximum, 3 robots will shadow threats up front, 1 robot is - // dedicated as the goalie, and the other 2 robots will defend either post (as show in - // the image) + // these positions are picked according to the following: + // createKickoffSetupPositions(); slide + // https://images.slideplayer.com/32/9922349/slides/slide_2.jpg since we only have 6 + // robots at the maximum, 3 robots will shadow threats up front, 1 robot is dedicated + // as the goalie, and the other 2 robots will defend either post (as show in the + // image) // // Positions 1,2 are the most important, 3,4,5 are a fallback // if there aren't as many threats to shadow. Robots will be assigned diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index 8ddfe665e7..196f067f8f 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -13,7 +13,7 @@ -struct KickoffEnemyPlayFSM: PlayFSM +struct KickoffEnemyPlayFSM : PlayFSM { class SetupState; @@ -27,7 +27,8 @@ struct KickoffEnemyPlayFSM: PlayFSM * * @param ai_config the play config for this play FSM */ - explicit KickoffEnemyPlayFSM(std::shared_ptr ai_config_ptr); + explicit KickoffEnemyPlayFSM( + std::shared_ptr ai_config_ptr); /** diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp index 04853f8e25..c8651e8e84 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.cpp @@ -6,8 +6,8 @@ KickoffFriendlyPlay::KickoffFriendlyPlay( - std::shared_ptr ai_config_ptr) - :PlayBase(ai_config_ptr, true) + std::shared_ptr ai_config_ptr) + : PlayBase(ai_config_ptr, true) { } @@ -35,4 +35,5 @@ std::vector KickoffFriendlyPlay::getState() // Register this play in the genericFactory static TGenericFactory> factory; + std::shared_ptr> + factory; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h index bc12cd1500..7fa06804fe 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play.h @@ -1,10 +1,10 @@ #pragma once #include "proto/parameters.pb.h" -#include "software/ai/hl/stp/play/play_fsm.hpp" -#include "software/ai/hl/stp/play/play_base.hpp" #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" #include "software/ai/hl/stp/play/play.h" +#include "software/ai/hl/stp/play/play_base.hpp" +#include "software/ai/hl/stp/play/play_fsm.hpp" /** * A play that runs when its currently the friendly kick off, @@ -19,11 +19,11 @@ class KickoffFriendlyPlay : public PlayBase * * @param ai_config_ptr the play config for this play */ - explicit KickoffFriendlyPlay(std::shared_ptr ai_config_ptr); + explicit KickoffFriendlyPlay( + std::shared_ptr ai_config_ptr); void getNextTactics(TacticCoroutine::push_type &yield, const WorldPtr &world_ptr) override; void updateTactics(const PlayUpdate &play_update) override; std::vector getState() override; - }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index 0004433e8a..5120a43af2 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -1,17 +1,15 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM( - std::shared_ptr ai_config_ptr) - : PlayFSM(ai_config_ptr), - kickoff_chip_tactic(std::make_shared(ai_config_ptr)), - shoot_tactic(std::make_shared(ai_config_ptr)), - move_tactics{ - std::make_shared(ai_config_ptr), // robot 1 - std::make_shared(ai_config_ptr), // robot 2-5 - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr) - } + std::shared_ptr ai_config_ptr) + : PlayFSM(ai_config_ptr), + kickoff_chip_tactic(std::make_shared(ai_config_ptr)), + shoot_tactic(std::make_shared(ai_config_ptr)), + move_tactics{std::make_shared(ai_config_ptr), // robot 1 + std::make_shared(ai_config_ptr), // robot 2-5 + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)} { } diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 16047f0486..f4ff1a6a44 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -27,7 +27,8 @@ struct KickoffFriendlyPlayFSM : PlayFSM * * @param ai_config_ptr the play config for this play FSM */ - explicit KickoffFriendlyPlayFSM(std::shared_ptr ai_config_ptr); + explicit KickoffFriendlyPlayFSM( + std::shared_ptr ai_config_ptr); /** @@ -100,24 +101,24 @@ struct KickoffFriendlyPlayFSM : PlayFSM DEFINE_SML_GUARD(isPlaying) return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + // src_state + event [guard] / action = dest_state + // PlaySelectionFSM will transition to OffensePlay after the kick. + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, - ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, - ShootState_S + Update_E[isPlaying_G] = X, + // shoot directly at net if possible. + SetupState_S + Update_E[shotFound_G] = ShootState_S, + ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, + ShootState_S + Update_E[isPlaying_G] = X, - // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + // else chip over the defenders. + SetupState_S + Update_E[!shotFound_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, - X + Update_E = X); + X + Update_E = X); } -private: + private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; std::shared_ptr shoot_tactic; diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index c5fb5e3717..2fd3546c3d 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -51,6 +51,7 @@ def init_world_state(runner, blue_bots, yellow_bots): ), ) + def init_plays(simulated_test_runner, is_friendly, force_out): blue_play = Play() yellow_play = Play() From fd7d6b5870423b51ce6d4fd5410534e86cc468e2 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 7 Mar 2026 06:23:47 -0800 Subject: [PATCH 12/18] removed shooting --- .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 2 +- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 2 +- .../kickoff_friendly_play_fsm.cpp | 140 ++++++--------- .../kickoff_friendly_play_fsm.h | 54 ++---- .../ai/hl/stp/play/kickoff_play_test.py | 160 ++++++------------ 5 files changed, 118 insertions(+), 240 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp index e995730b93..d77d2b5616 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -1,7 +1,7 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" KickoffEnemyPlayFSM::KickoffEnemyPlayFSM( - std::shared_ptr ai_config_ptr) + const std::shared_ptr& ai_config_ptr) : PlayFSM(ai_config_ptr), shadow_enemy_tactics{std::make_shared(ai_config_ptr), std::make_shared(ai_config_ptr)}, diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index 196f067f8f..8a769a466d 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -28,7 +28,7 @@ struct KickoffEnemyPlayFSM : PlayFSM * @param ai_config the play config for this play FSM */ explicit KickoffEnemyPlayFSM( - std::shared_ptr ai_config_ptr); + const std::shared_ptr& ai_config_ptr); /** diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index 5120a43af2..26e0b96ed4 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -1,15 +1,14 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM( - std::shared_ptr ai_config_ptr) - : PlayFSM(ai_config_ptr), - kickoff_chip_tactic(std::make_shared(ai_config_ptr)), - shoot_tactic(std::make_shared(ai_config_ptr)), - move_tactics{std::make_shared(ai_config_ptr), // robot 1 - std::make_shared(ai_config_ptr), // robot 2-5 - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr)} + const std::shared_ptr& ai_config_ptr) + : PlayFSM(ai_config_ptr), + kickoff_chip_tactic(std::make_shared(ai_config_ptr)), + move_tactics{std::make_shared(ai_config_ptr), // robot 1 + std::make_shared(ai_config_ptr), // robot 2-5 + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)} { } @@ -41,31 +40,31 @@ void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_p if (kickoff_setup_positions.empty()) { kickoff_setup_positions = { - // Robot 1 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius(), 0)), - // Robot 2 - // Goalie positions will be handled by the goalie tactic - // Robot 3 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - + // Robot 1 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius(), 0)), + // Robot 2 + // Goalie positions will be handled by the goalie tactic + // Robot 3 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - -1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 4 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - + -1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 4 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - 1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 5 - Point(world_ptr->field().friendlyGoalpostPos().x() + + 1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 5 + Point(world_ptr->field().friendlyGoalpostPos().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostPos().y()), - // Robot 6 - Point(world_ptr->field().friendlyGoalpostNeg().x() + + world_ptr->field().friendlyGoalpostPos().y()), + // Robot 6 + Point(world_ptr->field().friendlyGoalpostNeg().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostNeg().y()), + world_ptr->field().friendlyGoalpostNeg().y()), }; } } @@ -92,65 +91,48 @@ void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) event.common.set_tactics(tactics_to_run); } -// taken from free kick fsm. -void KickoffFriendlyPlayFSM::shootBall(const Update &event) + +void KickoffFriendlyPlayFSM::chipBall(const Update& event) { - WorldPtr world_ptr = event.common.world_ptr; - PriorityTacticVector tactics_to_run = {{}}; + const WorldPtr& world_ptr = event.common.world_ptr; + const auto& field = world_ptr->field(); + const Point ball_position = world_ptr->ball().position(); - Point ball_pos = world_ptr->ball().position(); + PriorityTacticVector tactics_to_run = {{}}; - shoot_tactic->updateControlParams( - ball_pos, (shot->getPointToShootAt() - ball_pos).orientation(), - BALL_MAX_SPEED_METERS_PER_SECOND); - tactics_to_run[0].emplace_back(shoot_tactic); + constexpr double enemy_x_padding_m = 2.0; + constexpr double sideline_padding_m = 0.3; + constexpr double fallback_target_x_fraction = 1.0 / 6.0; + const double min_chip_x = ball_position.x(); + const double max_chip_x = field.enemyGoalCenter().x() - enemy_x_padding_m; + const double min_chip_y = field.enemyCornerNeg().y() + sideline_padding_m; + const double max_chip_y = field.enemyCornerPos().y() - sideline_padding_m; - event.common.set_tactics(tactics_to_run); -} - -void KickoffFriendlyPlayFSM::chipBall(const Update &event) -{ - WorldPtr world_ptr = event.common.world_ptr; + const Rectangle chip_target_region(Point(min_chip_x, min_chip_y),Point(max_chip_x, max_chip_y)); + const Point fallback_target = field.centerPoint() + Vector(field.xLength() * fallback_target_x_fraction, 0.0); + const std::vector chip_targets = findGoodChipTargets(*world_ptr, chip_target_region); - PriorityTacticVector tactics_to_run = {{}}; + Point selected_target = fallback_target; - // adjust with testing to give us enough space to catch the ball before it goes out of - // bounds - double ballX = world_ptr->ball().position().x(); - double fieldX = world_ptr->field().enemyGoalCenter().x() - 2; - double negFieldY = world_ptr->field().enemyCornerNeg().y() + 0.3; - double posFieldY = world_ptr->field().enemyCornerPos().y() - 0.3; - - Rectangle target_area_rectangle = - Rectangle(Point(ballX, negFieldY), Point(fieldX, posFieldY)); - - // sort targets by distance to enemy goal center. - std::vector potential_chip_targets = - findGoodChipTargets(*world_ptr, target_area_rectangle); - std::sort( - potential_chip_targets.begin(), potential_chip_targets.end(), - [world_ptr](const Circle &first_circle, const Circle &second_circle) - { - return distance(world_ptr->field().enemyGoalCenter(), first_circle.origin()) < - distance(world_ptr->field().enemyGoalCenter(), second_circle.origin()); - }); - - Point target = - world_ptr->field().centerPoint() + Vector(world_ptr->field().xLength() / 6, 0); - - if (!potential_chip_targets.empty()) + if (!chip_targets.empty()) { - target = potential_chip_targets[0].origin(); + const auto best_target_it = std::min_element( + chip_targets.begin(), chip_targets.end(), + [&field](const Circle& a, const Circle& b) + { + return distance(field.enemyGoalCenter(), a.origin()) < distance(field.enemyGoalCenter(), b.origin()); + }); + + selected_target = best_target_it->origin(); } - kickoff_chip_tactic->updateControlParams(world_ptr->ball().position(), target); - + kickoff_chip_tactic->updateControlParams(ball_position, selected_target); tactics_to_run[0].emplace_back(kickoff_chip_tactic); - event.common.set_tactics(tactics_to_run); } + bool KickoffFriendlyPlayFSM::isSetupDone(const Update &event) { return !event.common.world_ptr->gameState().isSetupState(); @@ -160,15 +142,3 @@ bool KickoffFriendlyPlayFSM::isPlaying(const Update &event) { return event.common.world_ptr->gameState().isPlaying(); } - -bool KickoffFriendlyPlayFSM::shotFound(const Update &event) -{ - shot = calcBestShotOnGoal(event.common.world_ptr->field(), - event.common.world_ptr->friendlyTeam(), - event.common.world_ptr->enemyTeam(), - event.common.world_ptr->ball().position(), TeamType::ENEMY); - return shot.has_value() && - shot->getOpenAngle() > - Angle::fromDegrees( - ai_config.attacker_tactic_config().min_open_angle_for_shot_deg()); -} diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index f4ff1a6a44..6ed8772280 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -7,14 +7,12 @@ #include "software/ai/hl/stp/play/play.h" #include "software/ai/hl/stp/play/play_fsm.hpp" #include "software/ai/hl/stp/tactic/chip/chip_tactic.h" -#include "software/ai/hl/stp/tactic/kick/kick_tactic.h" #include "software/ai/hl/stp/tactic/move/move_tactic.h" #include "software/logger/logger.h" struct KickoffFriendlyPlayFSM : PlayFSM { class SetupState; - class ShootState; class ChipState; struct ControlParams @@ -28,7 +26,7 @@ struct KickoffFriendlyPlayFSM : PlayFSM * @param ai_config_ptr the play config for this play FSM */ explicit KickoffFriendlyPlayFSM( - std::shared_ptr ai_config_ptr); + const std::shared_ptr& ai_config_ptr); /** @@ -38,8 +36,6 @@ struct KickoffFriendlyPlayFSM : PlayFSM */ void createKickoffSetupPositions(const WorldPtr& world_ptr); - - /** * Action to move robots to starting positions * @@ -47,13 +43,6 @@ struct KickoffFriendlyPlayFSM : PlayFSM */ void setupKickoff(const Update& event); - /** - * Action to shoot the ball at the net. - * - * @param event the FreeKickPlayFSM Update event - */ - void shootBall(const Update& event); - /** * Action to chip the ball forward over the defenders. * @@ -66,63 +55,42 @@ struct KickoffFriendlyPlayFSM : PlayFSM * * @param event the FreeKickPlayFSM Update event */ - bool isSetupDone(const Update& event); + static bool isSetupDone(const Update& event); /** * Guard that checks if game has started (ball kicked). * * @param event the FreeKickPlayFSM Update event */ - bool isPlaying(const Update& event); - - /** - * Guard that checks if a direct shot on the net is possible. - * - * @param event the FreeKickPlayFSM Update event - */ - bool shotFound(const Update& event); + static bool isPlaying(const Update& event); auto operator()() { using namespace boost::sml; DEFINE_SML_STATE(SetupState) - DEFINE_SML_STATE(ShootState) DEFINE_SML_STATE(ChipState) DEFINE_SML_EVENT(Update) DEFINE_SML_ACTION(setupKickoff) - DEFINE_SML_ACTION(shootBall) DEFINE_SML_ACTION(chipBall) DEFINE_SML_GUARD(isSetupDone) - DEFINE_SML_GUARD(shotFound) DEFINE_SML_GUARD(isPlaying) - return make_transition_table( - // src_state + event [guard] / action = dest_state - // PlaySelectionFSM will transition to OffensePlay after the kick. - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - - // shoot directly at net if possible. - SetupState_S + Update_E[shotFound_G] = ShootState_S, - ShootState_S + Update_E[!isPlaying_G] / shootBall_A = ShootState_S, - ShootState_S + Update_E[isPlaying_G] = X, - - // else chip over the defenders. - SetupState_S + Update_E[!shotFound_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + SetupState_S + Update_E[isSetupDone_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, - X + Update_E = X); + X + Update_E = X + ); } - private: +private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; - std::shared_ptr shoot_tactic; std::vector> move_tactics; std::vector kickoff_setup_positions; - std::optional shot; -}; +}; \ No newline at end of file diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 2fd3546c3d..a648ca92b2 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -13,8 +13,8 @@ from software.simulated_tests.or_validation import OrValidation -def setup_kickoff_attacker(): - blue_bots = [ +def setup_kickoff_attackers(): + return [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), tbots_cpp.Point(-3, 0.5), @@ -22,13 +22,10 @@ def setup_kickoff_attacker(): tbots_cpp.Point(-3, -1.5), tbots_cpp.Point(-3, -2.5), ] - return blue_bots -def setup_kickoff_defender(): - # The defense has a hole in it from 0, 0 to the net. - # Robots that cannot move will allow the kicker to shoot directly into the net. - yellow_bots = [ +def setup_kickoff_defenders(): + return [ tbots_cpp.Point(1, 0.5), tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), @@ -36,143 +33,86 @@ def setup_kickoff_defender(): tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), ] - return yellow_bots def init_world_state(runner, blue_bots, yellow_bots): - ball_initial = tbots_cpp.Point(0, 0) runner.simulator_proto_unix_io.send_proto( WorldState, create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, - ball_location=ball_initial, + ball_location=tbots_cpp.Point(0, 0), ball_velocity=tbots_cpp.Vector(0, 0), ), ) -def init_plays(simulated_test_runner, is_friendly, force_out): - blue_play = Play() - yellow_play = Play() - - kicking_team = Team.BLUE if is_friendly else Team.YELLOW - non_kicking_team = Team.YELLOW if is_friendly else Team.BLUE - - if not force_out: - blue_play.name = ( - PlayName.KickoffFriendlyPlay - if kicking_team == Team.BLUE - else PlayName.KickoffEnemyPlay - ) - yellow_play.name = ( - PlayName.KickoffFriendlyPlay - if kicking_team == Team.YELLOW - else PlayName.KickoffEnemyPlay - ) - else: - blue_play.name = ( - PlayName.KickoffFriendlyPlay - if kicking_team == Team.BLUE - else PlayName.HaltPlay - ) - yellow_play.name = ( - PlayName.KickoffFriendlyPlay - if kicking_team == Team.YELLOW - else PlayName.HaltPlay - ) - - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=kicking_team - ) - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=non_kicking_team - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) -@pytest.mark.parametrize( - "is_friendly_test, force_open", - [(True, False), (True, True), (False, False), (False, True)], -) -def test_kickoff_play(simulated_test_runner, is_friendly_test, force_open): +def test_blue_kickoff_chip(simulated_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) + field = tbots_cpp.Field.createSSLDivisionBField() init_world_state( - simulated_test_runner, setup_kickoff_attacker(), setup_kickoff_defender() + simulated_test_runner, + setup_kickoff_attackers(), + setup_kickoff_defenders(), ) - init_plays(simulated_test_runner, is_friendly_test, force_open) - always_validation_sequence_set = [[]] + blue_play = Play() + blue_play.name = PlayName.KickoffFriendlyPlay - ball_moves_at_rest_validation = BallAlwaysMovesFromRest( - position=tbots_cpp.Point(0, 0), threshold=0.05 + yellow_play = Play() + yellow_play.name = PlayName.KickoffEnemyPlay + + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.BLUE + ) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.KICKOFF, team=Team.YELLOW ) - expected_center_circle_or_validation_set = [ - ball_moves_at_rest_validation, - NumberOfRobotsAlwaysStaysInRegion( - regions=[tbots_cpp.Field.createSSLDivisionBField().centerCircle()], - req_robot_cnt=0, - ), + blue_regions = [ + tbots_cpp.Field.createSSLDivisionBField().friendlyHalf(), + tbots_cpp.Field.createSSLDivisionBField().friendlyGoal(), + tbots_cpp.Field.createSSLDivisionBField().centerCircle() ] - friendly_half = tbots_cpp.Field.createSSLDivisionBField().friendlyHalf() - friendly_goal = tbots_cpp.Field.createSSLDivisionBField().friendlyGoal() - center_circle = tbots_cpp.Field.createSSLDivisionBField().centerCircle() - - friendly_regions = [friendly_half, friendly_goal, center_circle] + ball_moves_at_rest_validation = BallAlwaysMovesFromRest( + position=ball_initial_pos, threshold=0.05 + ) - if is_friendly_test: - # this expected_center_circle_or_validation_set version checks - # that either 0 or 1 robots are in centerCircle OR ball moves from center point - expected_center_circle_or_validation_set.append( + always_validations = [[ + OrValidation([ + ball_moves_at_rest_validation, NumberOfRobotsAlwaysStaysInRegion( regions=[tbots_cpp.Field.createSSLDivisionBField().centerCircle()], - req_robot_cnt=1, - ) - ) - else: - # Checks that 0 robots are in centerCircle OR ball moves from center point - friendly_regions.remove(center_circle) - - # Checks that there are 6 friendly robots in friendly_regions - # friendly_regions definition depends on if/else case above - expected_robot_regions_or_validations_set = [ - ball_moves_at_rest_validation, - NumberOfRobotsAlwaysStaysInRegion( - regions=friendly_regions, - req_robot_cnt=6, - ), - ] - - always_validation_sequence_set[0] = [ - OrValidation(expected_center_circle_or_validation_set), - OrValidation(expected_robot_regions_or_validations_set), - ] - - eventually_validation_sequence_set = [[]] - - # Eventually Validation - if is_friendly_test: - # Checks that ball leaves center point by 0.05 meters within 10 seconds of kickoff - eventually_validation_sequence_set[0].append( - BallEventuallyExitsRegion( - regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)] - ) - ) + req_robot_cnt=1 + ), + NumberOfRobotsAlwaysStaysInRegion( + regions=[tbots_cpp.Field.createSSLDivisionBField().centerCircle()], + req_robot_cnt=0 + ), + ]), + OrValidation([ + ball_moves_at_rest_validation, + NumberOfRobotsAlwaysStaysInRegion(regions=blue_regions, req_robot_cnt=6), + ]) + ]] + + eventually_validations = [[ + BallEventuallyExitsRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)]) + ]] simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ci_cmd_with_delay=[ - (4, Command.Type.NORMAL_START, Team.BLUE), - ], + inv_eventually_validation_sequence_set=eventually_validations, + inv_always_validation_sequence_set=always_validations, + ci_cmd_with_delay=[(4, Command.Type.NORMAL_START, Team.BLUE)], test_timeout_s=10, ) + if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity sys.exit(pytest.main([__file__, "-svv"])) From b9805f5929dd500ed82e07516d1abbd0c575873d Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 7 Mar 2026 06:35:20 -0800 Subject: [PATCH 13/18] updated docs --- docs/fsm-diagrams.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/fsm-diagrams.md b/docs/fsm-diagrams.md index 6365073db6..adfd5c8380 100644 --- a/docs/fsm-diagrams.md +++ b/docs/fsm-diagrams.md @@ -180,10 +180,7 @@ classDef terminate fill:white,color:black,font-weight:bold direction LR [*] --> SetupState SetupState --> SetupState : [!isSetupDone]\nsetupKickoff -SetupState --> ShootState : [shotFound] -ShootState --> ShootState : [!isPlaying]\nshootBall -ShootState --> Terminate:::terminate : [isPlaying] -SetupState --> ChipState : [!shotFound] +SetupState --> ChipState : [isSetupDone] ChipState --> ChipState : [!isPlaying]\nchipBall ChipState --> Terminate:::terminate : [isPlaying] Terminate:::terminate --> Terminate:::terminate From 2bc6999b27a614ad5d043bd92798ca43f1a6179a Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 7 Mar 2026 06:36:21 -0800 Subject: [PATCH 14/18] linted --- .../kickoff_enemy/kickoff_enemy_play_fsm.cpp | 2 +- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 2 +- .../kickoff_friendly_play_fsm.cpp | 90 ++++++++++--------- .../kickoff_friendly_play_fsm.h | 17 ++-- .../ai/hl/stp/play/kickoff_play_test.py | 53 ++++++----- 5 files changed, 88 insertions(+), 76 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp index d77d2b5616..c137d340bd 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.cpp @@ -1,7 +1,7 @@ #include "software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h" KickoffEnemyPlayFSM::KickoffEnemyPlayFSM( - const std::shared_ptr& ai_config_ptr) + const std::shared_ptr &ai_config_ptr) : PlayFSM(ai_config_ptr), shadow_enemy_tactics{std::make_shared(ai_config_ptr), std::make_shared(ai_config_ptr)}, diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index 8a769a466d..7770e55cff 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -28,7 +28,7 @@ struct KickoffEnemyPlayFSM : PlayFSM * @param ai_config the play config for this play FSM */ explicit KickoffEnemyPlayFSM( - const std::shared_ptr& ai_config_ptr); + const std::shared_ptr &ai_config_ptr); /** diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index 26e0b96ed4..b1032a4259 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -1,18 +1,18 @@ #include "software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h" KickoffFriendlyPlayFSM::KickoffFriendlyPlayFSM( - const std::shared_ptr& ai_config_ptr) - : PlayFSM(ai_config_ptr), - kickoff_chip_tactic(std::make_shared(ai_config_ptr)), - move_tactics{std::make_shared(ai_config_ptr), // robot 1 - std::make_shared(ai_config_ptr), // robot 2-5 - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr), - std::make_shared(ai_config_ptr)} + const std::shared_ptr& ai_config_ptr) + : PlayFSM(ai_config_ptr), + kickoff_chip_tactic(std::make_shared(ai_config_ptr)), + move_tactics{std::make_shared(ai_config_ptr), // robot 1 + std::make_shared(ai_config_ptr), // robot 2-5 + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr), + std::make_shared(ai_config_ptr)} { } -void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_ptr) +void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr& world_ptr) { // Since we only have 6 robots at the maximum, the number one priority // is the robot doing the kickoff up front. The goalie is the second most @@ -40,37 +40,37 @@ void KickoffFriendlyPlayFSM::createKickoffSetupPositions(const WorldPtr &world_p if (kickoff_setup_positions.empty()) { kickoff_setup_positions = { - // Robot 1 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius(), 0)), - // Robot 2 - // Goalie positions will be handled by the goalie tactic - // Robot 3 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - + // Robot 1 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius(), 0)), + // Robot 2 + // Goalie positions will be handled by the goalie tactic + // Robot 3 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - -1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 4 - Point(world_ptr->field().centerPoint() + - Vector(-world_ptr->field().centerCircleRadius() - + -1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 4 + Point(world_ptr->field().centerPoint() + + Vector(-world_ptr->field().centerCircleRadius() - 4 * ROBOT_MAX_RADIUS_METERS, - 1.0 / 3.0 * world_ptr->field().yLength())), - // Robot 5 - Point(world_ptr->field().friendlyGoalpostPos().x() + + 1.0 / 3.0 * world_ptr->field().yLength())), + // Robot 5 + Point(world_ptr->field().friendlyGoalpostPos().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostPos().y()), - // Robot 6 - Point(world_ptr->field().friendlyGoalpostNeg().x() + + world_ptr->field().friendlyGoalpostPos().y()), + // Robot 6 + Point(world_ptr->field().friendlyGoalpostNeg().x() + world_ptr->field().defenseAreaXLength() + 2 * ROBOT_MAX_RADIUS_METERS, - world_ptr->field().friendlyGoalpostNeg().y()), + world_ptr->field().friendlyGoalpostNeg().y()), }; } } -void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) +void KickoffFriendlyPlayFSM::setupKickoff(const Update& event) { createKickoffSetupPositions(event.common.world_ptr); @@ -95,13 +95,13 @@ void KickoffFriendlyPlayFSM::setupKickoff(const Update &event) void KickoffFriendlyPlayFSM::chipBall(const Update& event) { const WorldPtr& world_ptr = event.common.world_ptr; - const auto& field = world_ptr->field(); + const auto& field = world_ptr->field(); const Point ball_position = world_ptr->ball().position(); PriorityTacticVector tactics_to_run = {{}}; - constexpr double enemy_x_padding_m = 2.0; - constexpr double sideline_padding_m = 0.3; + constexpr double enemy_x_padding_m = 2.0; + constexpr double sideline_padding_m = 0.3; constexpr double fallback_target_x_fraction = 1.0 / 6.0; const double min_chip_x = ball_position.x(); @@ -109,20 +109,24 @@ void KickoffFriendlyPlayFSM::chipBall(const Update& event) const double min_chip_y = field.enemyCornerNeg().y() + sideline_padding_m; const double max_chip_y = field.enemyCornerPos().y() - sideline_padding_m; - const Rectangle chip_target_region(Point(min_chip_x, min_chip_y),Point(max_chip_x, max_chip_y)); - const Point fallback_target = field.centerPoint() + Vector(field.xLength() * fallback_target_x_fraction, 0.0); - const std::vector chip_targets = findGoodChipTargets(*world_ptr, chip_target_region); + const Rectangle chip_target_region(Point(min_chip_x, min_chip_y), + Point(max_chip_x, max_chip_y)); + const Point fallback_target = + field.centerPoint() + Vector(field.xLength() * fallback_target_x_fraction, 0.0); + const std::vector chip_targets = + findGoodChipTargets(*world_ptr, chip_target_region); Point selected_target = fallback_target; if (!chip_targets.empty()) { - const auto best_target_it = std::min_element( - chip_targets.begin(), chip_targets.end(), - [&field](const Circle& a, const Circle& b) - { - return distance(field.enemyGoalCenter(), a.origin()) < distance(field.enemyGoalCenter(), b.origin()); - }); + const auto best_target_it = + std::min_element(chip_targets.begin(), chip_targets.end(), + [&field](const Circle& a, const Circle& b) + { + return distance(field.enemyGoalCenter(), a.origin()) < + distance(field.enemyGoalCenter(), b.origin()); + }); selected_target = best_target_it->origin(); } @@ -133,12 +137,12 @@ void KickoffFriendlyPlayFSM::chipBall(const Update& event) } -bool KickoffFriendlyPlayFSM::isSetupDone(const Update &event) +bool KickoffFriendlyPlayFSM::isSetupDone(const Update& event) { return !event.common.world_ptr->gameState().isSetupState(); } -bool KickoffFriendlyPlayFSM::isPlaying(const Update &event) +bool KickoffFriendlyPlayFSM::isPlaying(const Update& event) { return event.common.world_ptr->gameState().isPlaying(); } diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index 6ed8772280..f55e9986b4 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -26,7 +26,7 @@ struct KickoffFriendlyPlayFSM : PlayFSM * @param ai_config_ptr the play config for this play FSM */ explicit KickoffFriendlyPlayFSM( - const std::shared_ptr& ai_config_ptr); + const std::shared_ptr& ai_config_ptr); /** @@ -79,18 +79,17 @@ struct KickoffFriendlyPlayFSM : PlayFSM DEFINE_SML_GUARD(isSetupDone) DEFINE_SML_GUARD(isPlaying) return make_transition_table( - *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, - SetupState_S + Update_E[isSetupDone_G] = ChipState_S, - ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, - ChipState_S + Update_E[isPlaying_G] = X, + *SetupState_S + Update_E[!isSetupDone_G] / setupKickoff_A = SetupState_S, + SetupState_S + Update_E[isSetupDone_G] = ChipState_S, + ChipState_S + Update_E[!isPlaying_G] / chipBall_A = ChipState_S, + ChipState_S + Update_E[isPlaying_G] = X, - X + Update_E = X - ); + X + Update_E = X); } -private: + private: TbotsProto::AiConfig ai_config; std::shared_ptr kickoff_chip_tactic; std::vector> move_tactics; std::vector kickoff_setup_positions; -}; \ No newline at end of file +}; diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index a648ca92b2..b0fde37ccf 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -47,8 +47,6 @@ def init_world_state(runner, blue_bots, yellow_bots): ) - - def test_blue_kickoff_chip(simulated_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) field = tbots_cpp.Field.createSSLDivisionBField() @@ -75,34 +73,46 @@ def test_blue_kickoff_chip(simulated_test_runner): blue_regions = [ tbots_cpp.Field.createSSLDivisionBField().friendlyHalf(), tbots_cpp.Field.createSSLDivisionBField().friendlyGoal(), - tbots_cpp.Field.createSSLDivisionBField().centerCircle() + tbots_cpp.Field.createSSLDivisionBField().centerCircle(), ] ball_moves_at_rest_validation = BallAlwaysMovesFromRest( position=ball_initial_pos, threshold=0.05 ) - always_validations = [[ - OrValidation([ - ball_moves_at_rest_validation, - NumberOfRobotsAlwaysStaysInRegion( - regions=[tbots_cpp.Field.createSSLDivisionBField().centerCircle()], - req_robot_cnt=1 + always_validations = [ + [ + OrValidation( + [ + ball_moves_at_rest_validation, + NumberOfRobotsAlwaysStaysInRegion( + regions=[ + tbots_cpp.Field.createSSLDivisionBField().centerCircle() + ], + req_robot_cnt=1, + ), + NumberOfRobotsAlwaysStaysInRegion( + regions=[ + tbots_cpp.Field.createSSLDivisionBField().centerCircle() + ], + req_robot_cnt=0, + ), + ] ), - NumberOfRobotsAlwaysStaysInRegion( - regions=[tbots_cpp.Field.createSSLDivisionBField().centerCircle()], - req_robot_cnt=0 + OrValidation( + [ + ball_moves_at_rest_validation, + NumberOfRobotsAlwaysStaysInRegion( + regions=blue_regions, req_robot_cnt=6 + ), + ] ), - ]), - OrValidation([ - ball_moves_at_rest_validation, - NumberOfRobotsAlwaysStaysInRegion(regions=blue_regions, req_robot_cnt=6), - ]) - ]] + ] + ] - eventually_validations = [[ - BallEventuallyExitsRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)]) - ]] + eventually_validations = [ + [BallEventuallyExitsRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)])] + ] simulated_test_runner.run_test( inv_eventually_validation_sequence_set=eventually_validations, @@ -112,7 +122,6 @@ def test_blue_kickoff_chip(simulated_test_runner): ) - if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity sys.exit(pytest.main([__file__, "-svv"])) From 059cff11feec6cefa11115fdf55eb01e85f829e8 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 7 Mar 2026 06:42:41 -0800 Subject: [PATCH 15/18] BUILD fix --- src/software/ai/hl/stp/play/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/software/ai/hl/stp/play/BUILD b/src/software/ai/hl/stp/play/BUILD index 314a01d720..46cf78cd4a 100644 --- a/src/software/ai/hl/stp/play/BUILD +++ b/src/software/ai/hl/stp/play/BUILD @@ -82,6 +82,8 @@ cc_library( "//software/ai/hl/stp/play/example:example_play", "//software/ai/hl/stp/play/free_kick:free_kick_play", "//software/ai/hl/stp/play/halt_play", + "//software/ai/hl/stp/play/kickoff_enemy:kickoff_enemy_play", + "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", "//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", From e01a89534eb0c1ffd432a3bea12f4d6756b04545 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 7 Mar 2026 06:53:55 -0800 Subject: [PATCH 16/18] added javadoc --- .../kickoff_enemy/kickoff_enemy_play_fsm.h | 9 ++++++++- .../kickoff_friendly_play_fsm.cpp | 2 ++ .../kickoff_friendly_play_fsm.h | 18 +++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h index 7770e55cff..bcb9a6447e 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_enemy/kickoff_enemy_play_fsm.h @@ -12,11 +12,18 @@ #include "software/logger/logger.h" - +/** + * This FSM implements the Kickoff Enemy Play. It manages kickoff when the enemy side is + * kicking. + * - Bots will shadow the enemy robots but stay on the correct side of the field. + */ struct KickoffEnemyPlayFSM : PlayFSM { class SetupState; + /** + * Control Parameters for a Kickoff Enemy Play + */ struct ControlParams { }; diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp index b1032a4259..c43190e511 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.cpp @@ -111,6 +111,7 @@ void KickoffFriendlyPlayFSM::chipBall(const Update& event) const Rectangle chip_target_region(Point(min_chip_x, min_chip_y), Point(max_chip_x, max_chip_y)); + const Point fallback_target = field.centerPoint() + Vector(field.xLength() * fallback_target_x_fraction, 0.0); const std::vector chip_targets = @@ -118,6 +119,7 @@ void KickoffFriendlyPlayFSM::chipBall(const Update& event) Point selected_target = fallback_target; + // Chooses a viable open circle that is closest to the enemy goal. if (!chip_targets.empty()) { const auto best_target_it = diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h index f55e9986b4..6dd163523e 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h +++ b/src/software/ai/hl/stp/play/kickoff_friendly/kickoff_friendly_play_fsm.h @@ -10,11 +10,24 @@ #include "software/ai/hl/stp/tactic/move/move_tactic.h" #include "software/logger/logger.h" + +/** + * This FSM implements the Kickoff Friendly Play. It manages kickoff when the friendly + * side is kicking. + * - It positions robots to starting points. + * - It stays ready to start the game. + * - It chips the ball into the largest open circle that is sufficiently close to the + * enemy net, but also reasonably far from the edges of the field. + * - Terminates after the ball is touched, passing control to OffensePlay. + */ struct KickoffFriendlyPlayFSM : PlayFSM { class SetupState; class ChipState; + /** + * Control Parameters for a Kickoff Friendly Play + */ struct ControlParams { }; @@ -45,7 +58,10 @@ struct KickoffFriendlyPlayFSM : PlayFSM /** * Action to chip the ball forward over the defenders. - * + * - Creates a rectangle within the enemy half of the field with padding. + * - Finds the largest open circles between enemy bots. + * - Chooses the largest viable open circle that is closest to the enemy net. + * - Defaults to a short chip if no open circle returned. * @param event the FreeKickPlayFSM Update event */ void chipBall(const Update& event); From 3fa2da8c63e6cfe9305ba65eb48430bf030f66c2 Mon Sep 17 00:00:00 2001 From: Muk Chunpongtong <121708205+Muxite@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:21:05 -0800 Subject: [PATCH 17/18] remove cc test --- .../ai/hl/stp/play/kickoff_enemy/BUILD | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD index 04008711e4..a4c26ccb3f 100644 --- a/src/software/ai/hl/stp/play/kickoff_enemy/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_enemy/BUILD @@ -26,19 +26,19 @@ cc_library( alwayslink = True, ) -cc_test( - name = "kickoff_enemy_play_cpp_test", - srcs = ["kickoff_enemy_play_test.cpp"], - deps = [ - "//shared/test_util:tbots_gtest_main", - "//software/ai/hl/stp/play:kickoff_enemy_play", - "//software/geom/algorithms", - "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", - "//software/simulated_tests/non_terminating_validation_functions", - "//software/simulated_tests/terminating_validation_functions", - "//software/simulated_tests/validation:validation_function", - "//software/test_util", - "//software/time:duration", - "//software/world", - ], -) +# cc_test( +# name = "kickoff_enemy_play_cpp_test", +# srcs = ["kickoff_enemy_play_test.cpp"], +# deps = [ +# "//shared/test_util:tbots_gtest_main", +# "//software/ai/hl/stp/play:kickoff_enemy_play", +# "//software/geom/algorithms", +# "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", +# "//software/simulated_tests/non_terminating_validation_functions", +# "//software/simulated_tests/terminating_validation_functions", +# "//software/simulated_tests/validation:validation_function", +# "//software/test_util", +# "//software/time:duration", +# "//software/world", +# ], +#) From 213d1c671eb87a36db8ac14adc5f3d9a902d5d84 Mon Sep 17 00:00:00 2001 From: Muk Chunpongtong <121708205+Muxite@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:25:30 -0800 Subject: [PATCH 18/18] Comment out cc test Comment out the kickoff_friendly_play_cpp_test target in BUILD file. --- .../ai/hl/stp/play/kickoff_friendly/BUILD | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD index b16f29953a..17a0943cd4 100644 --- a/src/software/ai/hl/stp/play/kickoff_friendly/BUILD +++ b/src/software/ai/hl/stp/play/kickoff_friendly/BUILD @@ -25,18 +25,18 @@ cc_library( alwayslink = True, ) -cc_test( - name = "kickoff_friendly_play_cpp_test", - srcs = ["kickoff_friendly_play_test.cpp"], - deps = [ - "//shared/test_util:tbots_gtest_main", - "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", - "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", - "//software/simulated_tests/non_terminating_validation_functions", - "//software/simulated_tests/terminating_validation_functions", - "//software/simulated_tests/validation:validation_function", - "//software/test_util", - "//software/time:duration", - "//software/world", - ], -) +#cc_test( +# name = "kickoff_friendly_play_cpp_test", +# srcs = ["kickoff_friendly_play_test.cpp"], +# deps = [ +# "//shared/test_util:tbots_gtest_main", +# "//software/ai/hl/stp/play/kickoff_friendly:kickoff_friendly_play", +# "//software/simulated_tests:simulated_er_force_sim_play_test_fixture", +# "//software/simulated_tests/non_terminating_validation_functions", +# "//software/simulated_tests/terminating_validation_functions", +# "//software/simulated_tests/validation:validation_function", +# "//software/test_util", +# "//software/time:duration", +# "//software/world", +# ], +#)