diff --git a/src/software/ai/hl/stp/play/penalty_kick/BUILD b/src/software/ai/hl/stp/play/penalty_kick/BUILD index fb16e67107..19c4ecbc27 100644 --- a/src/software/ai/hl/stp/play/penalty_kick/BUILD +++ b/src/software/ai/hl/stp/play/penalty_kick/BUILD @@ -1,9 +1,10 @@ -package(default_visibility = ["//visibility:public"]) - +load("@simulated_tests_deps//:requirements.bzl", "requirement") # 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 +package(default_visibility = ["//visibility:public"]) + cc_library( name = "penalty_kick_play", srcs = [ @@ -37,18 +38,13 @@ cc_test( ], ) -cc_test( +py_test( name = "penalty_kick_play_test", - srcs = ["penalty_kick_play_test.cpp"], + srcs = ["penalty_kick_play_test.py"], + tags = ["exclusive"], deps = [ - ":penalty_kick_play", - "//shared/test_util:tbots_gtest_main", - "//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", + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp deleted file mode 100644 index 1b583279e1..0000000000 --- a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "software/ai/hl/stp/play/penalty_kick/penalty_kick_play.h" - -#include - -#include "software/simulated_tests/non_terminating_validation_functions/ball_in_play_or_scored_validation.h" -#include "software/simulated_tests/non_terminating_validation_functions/ball_never_moves_backward_validation.h" -#include "software/simulated_tests/non_terminating_validation_functions/robot_not_excessively_dribbling_validation.h" -#include "software/simulated_tests/non_terminating_validation_functions/robots_avoid_ball_validation.h" -#include "software/simulated_tests/simulated_er_force_sim_play_test_fixture.h" -#include "software/simulated_tests/terminating_validation_functions/friendly_scored_validation.h" -#include "software/simulated_tests/terminating_validation_functions/robot_state_validation.h" -#include "software/simulated_tests/validation/validation_function.h" -#include "software/test_util/equal_within_tolerance.h" -#include "software/test_util/test_util.h" -#include "software/time/duration.h" -#include "software/world/world.h" - -class PenaltyKickPlayTest : public SimulatedErForceSimPlayTestFixture -{ - protected: - TbotsProto::FieldType field_type = TbotsProto::FieldType::DIV_B; - Field field = Field::createField(field_type); -}; - -TEST_F(PenaltyKickPlayTest, test_penalty_kick_setup) -{ - BallState ball_state(field.friendlyPenaltyMark(), Vector(0, 0)); - auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( - {Point(-2, -2), Point(-3, -1), Point(-3, 0), Point(-3, 1), Point(-3, 2), - Point(2, 2.5)}); - setFriendlyGoalie(0); - auto enemy_robots = - TestUtil::createStationaryRobotStatesWithId({field.enemyGoalCenter()}); - setEnemyGoalie(0); - setAiPlay(TbotsProto::PlayName::PenaltyKickPlay); - setRefereeCommand(RefereeCommand::PREPARE_PENALTY_US, RefereeCommand::NORMAL_START); - - RobotId shooter_id = 5; - std::vector terminating_validation_functions = { - [shooter_id](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { - robotAtPosition(shooter_id, world_ptr, - world_ptr->field().friendlyPenaltyMark(), 0.3, yield); - }}; - - std::vector non_terminating_validation_functions = { - [](std::shared_ptr world_ptr, ValidationCoroutine::push_type& yield) - { - // making sure that the robot doesn't move the ball while the penalty is - // setting up - ASSERT_TRUE( - TestUtil::equalWithinTolerance(world_ptr->field().friendlyPenaltyMark(), - world_ptr->ball().position(), 1e-6)); - }, - [shooter_id](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { - // Wait 2 seconds for robots to start moving adequately far away from the ball - if (world_ptr->getMostRecentTimestamp() >= Timestamp::fromSeconds(2)) - { - robotsAvoidBall(1, {shooter_id}, world_ptr, yield); - } - }}; - - runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(9.5)); -} - -// TODO (#3106): Re-enable test once robot 1 and 2 tactic assignment do not oscillate -TEST_F(PenaltyKickPlayTest, DISABLED_test_penalty_kick_take) -{ - Vector behind_ball_direction = - (field.friendlyPenaltyMark() - field.enemyGoalCenter()).normalize(); - - Point behind_ball = field.friendlyPenaltyMark() + - behind_ball_direction.normalize(DIST_TO_FRONT_OF_ROBOT_METERS + - BALL_MAX_RADIUS_METERS + 0.1); - double non_shooter_x_pos = field.friendlyPenaltyMark().x() - 1.5; - BallState ball_state(field.friendlyPenaltyMark(), Vector(0, 0)); - auto friendly_robots = TestUtil::createStationaryRobotStatesWithId( - {Point(-4, 0), behind_ball, Point(non_shooter_x_pos, 0), - Point(non_shooter_x_pos, 1), Point(non_shooter_x_pos, -1), - Point(non_shooter_x_pos, 2)}); - setFriendlyGoalie(0); - Point goalie = Point(field.enemyGoalpostNeg().x(), 0); - auto enemy_robots = TestUtil::createStationaryRobotStatesWithId({goalie}); - setEnemyGoalie(0); - setAiPlay(TbotsProto::PlayName::PenaltyKickPlay); - setRefereeCommand(RefereeCommand::NORMAL_START, RefereeCommand::PREPARE_PENALTY_US); - - std::vector terminating_validation_functions = { - friendlyScored, - }; - - RobotId shooter_id = 1; - std::vector non_terminating_validation_functions = { - ballInPlay, - [shooter_id](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { ballNeverMovesBackward(world_ptr, yield); }, - [shooter_id](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { robotNotExcessivelyDribbling(shooter_id, world_ptr, yield); }, - [shooter_id](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { robotsAvoidBall(1, {shooter_id}, world_ptr, yield); }}; - - 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/penalty_kick/penalty_kick_play_test.py b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.py new file mode 100644 index 0000000000..f906eaea74 --- /dev/null +++ b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.py @@ -0,0 +1,215 @@ +import sys + +import pytest + +import software.python_bindings as tbots_cpp + + +from software.simulated_tests.ball_enters_region import BallEventuallyEntersRegion +from software.simulated_tests.ball_moves_forward import BallAlwaysMovesForward +from software.simulated_tests.friendly_has_ball_possession import ( + FriendlyAlwaysHasBallPossession, +) +from software.simulated_tests.friendly_team_scored import FriendlyTeamEventuallyScored + +from proto.message_translation.tbots_protobuf import create_world_state +from proto.import_all_protos import * +from proto.ssl_gc_common_pb2 import Team +from proto.play_pb2 import Play, PlayName + +from software.simulated_tests.robot_enters_region import ( + NumberOfRobotsEventuallyEntersRegion, +) + + +def test_penalty_kick_play_setup(simulated_test_runner): + ball_initial_pos = tbots_cpp.Point(-1.5, 0) + + def setup(*args): + # Setup Bots + blue_bots = [ + tbots_cpp.Point(3, 2.5), + tbots_cpp.Point(2, 1.5), + tbots_cpp.Point(-1, 0.5), + tbots_cpp.Point(1, -0.5), + tbots_cpp.Point(0, -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(), + ] + + # Force play override here + blue_play = Play() + blue_play.name = PlayName.PenaltyKickPlay + + yellow_play = Play() + yellow_play.name = PlayName.PenaltyKickEnemyPlay + + 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 + ) + + # 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 + ) + + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.PENALTY, team=Team.BLUE + ) + + # 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), + ), + ) + + field = tbots_cpp.Field.createSSLDivisionBField() + + # Always Validation + inv_always_validation_sequence_set = [[]] + + ag_always_validation_sequence_set = [[]] + + # Eventually Validation + inv_eventually_validation_sequence_set = [ + [ + NumberOfRobotsEventuallyEntersRegion( + region=tbots_cpp.Rectangle( + tbots_cpp.Point(-4.5, -3), tbots_cpp.Point(-2.5, 3) + ), + req_robot_cnt=5, + ), + NumberOfRobotsEventuallyEntersRegion( + region=tbots_cpp.Circle(ball_initial_pos, 0.5), req_robot_cnt=1 + ), + ] + ] + ag_eventually_validation_sequence_set = [[]] + + simulated_test_runner.run_test( + params=[0], + setup=setup, + inv_eventually_validation_sequence_set=inv_eventually_validation_sequence_set, + inv_always_validation_sequence_set=inv_always_validation_sequence_set, + ag_eventually_validation_sequence_set=ag_eventually_validation_sequence_set, + ag_always_validation_sequence_set=ag_always_validation_sequence_set, + test_timeout_s=15, + ) + + +def test_penalty_kick_play_kick(simulated_test_runner): + ball_initial_pos = tbots_cpp.Point(-1.5, 0) + + def setup(*args): + # 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(-2.5, 0), + tbots_cpp.Point(-2.5, 1.5), + tbots_cpp.Point(-2.5, -1.5), + tbots_cpp.Point(-2.5, 2.5), + tbots_cpp.Point(-2.5, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + ] + + # Force play override here + blue_play = Play() + blue_play.name = PlayName.PenaltyKickPlay + + yellow_play = Play() + yellow_play.name = PlayName.PenaltyKickEnemyPlay + + 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 + ) + + # 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 + ) + + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.PENALTY, team=Team.BLUE + ) + simulated_test_runner.gamecontroller.send_gc_command( + gc_command=Command.Type.NORMAL_START, team=Team.BLUE + ) + + # 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), + ), + ) + + field = tbots_cpp.Field.createSSLDivisionBField() + + # Always Validation + inv_always_validation_sequence_set = [[]] + + ag_always_validation_sequence_set = [ + [FriendlyAlwaysHasBallPossession(), BallAlwaysMovesForward(ball_initial_pos)] + ] + + # Eventually Validation + inv_eventually_validation_sequence_set = [ + [ + FriendlyTeamEventuallyScored(), + BallEventuallyEntersRegion(regions=[field.enemyDefenseArea()]), + ] + ] + ag_eventually_validation_sequence_set = [[]] + + simulated_test_runner.run_test( + params=[0], + setup=setup, + inv_eventually_validation_sequence_set=inv_eventually_validation_sequence_set, + inv_always_validation_sequence_set=inv_always_validation_sequence_set, + ag_eventually_validation_sequence_set=ag_eventually_validation_sequence_set, + ag_always_validation_sequence_set=ag_always_validation_sequence_set, + test_timeout_s=15, + ) + + +if __name__ == "__main__": + # Run the test, -s disables all capturing at -vv increases verbosity + sys.exit(pytest.main([__file__, "-svv"])) diff --git a/src/software/ai/hl/stp/tactic/penalty_kick/BUILD b/src/software/ai/hl/stp/tactic/penalty_kick/BUILD index 9871c1e0f5..a50611967c 100644 --- a/src/software/ai/hl/stp/tactic/penalty_kick/BUILD +++ b/src/software/ai/hl/stp/tactic/penalty_kick/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -34,7 +36,7 @@ cc_test( ) cc_test( - name = "penalty_kick_tactic_test", + name = "penalty_kick_tactic_test_cpp", srcs = ["penalty_kick_tactic_test.cpp"], deps = [ ":penalty_kick_tactic", @@ -48,3 +50,19 @@ cc_test( "//software/world", ], ) + +py_test( + name = "penalty_kick_tactic_test", + srcs = [ + "penalty_kick_tactic_test.py", + ], + # TODO (#2619) Remove tag to run in parallel + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py new file mode 100644 index 0000000000..b401a32f78 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.py @@ -0,0 +1,77 @@ +import software.python_bindings as tbots_cpp +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.ball_enters_region import ( + BallEventuallyEntersRegion, + BallAlwaysStaysInRegion, +) +from software.simulated_tests.ball_moves_forward import BallAlwaysMovesForward +from software.simulated_tests.friendly_has_ball_possession import ( + FriendlyAlwaysHasBallPossession, +) +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.import_all_protos import * + + +def test_penalty_kick_no_goalie(simulated_test_runner): + # Setup Robot + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + yellow_robot_locations=[], + blue_robot_locations=[tbots_cpp.Point(-3, 0)], + ball_location=tbots_cpp.Point(-1.5, 0), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + # Setup Tactic + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].penalty_kick.CopyFrom(PenaltyKickTactic()) + + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Setup no tactics on the enemy side + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Validation + + always_validation_sequence_set = [ + [ + BallAlwaysStaysInRegion( + regions=[ + tbots_cpp.Field.createSSLDivisionBField().fieldLines(), + tbots_cpp.Field.createSSLDivisionBField().enemyGoal(), + ] + ), + ] + ] + eventually_validation_sequence_set = [ + [ + BallEventuallyEntersRegion( + regions=[tbots_cpp.Field.createSSLDivisionBField().enemyGoal()] + ) + ] + ] + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=always_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[ + [ + FriendlyAlwaysHasBallPossession(), + BallAlwaysMovesForward(tbots_cpp.Point(0, 0)), + ] + ], + test_timeout_s=15, + ) + + +if __name__ == "__main__": + pytest_main(__file__)