From c8d07bc35397b47318b6e701f5858b4102259510 Mon Sep 17 00:00:00 2001 From: aabayomi Date: Sun, 3 Mar 2024 17:20:57 -0600 Subject: [PATCH 01/43] first release commit --- keepaway/.DS_Store | Bin 0 -> 10244 bytes keepaway/LICENSE | 21 + keepaway/README.md | 117 + keepaway/base/.DS_Store | Bin 0 -> 6148 bytes keepaway/base/__init__.py | 2 + keepaway/base/basic_tackle.py | 111 + keepaway/base/bhv_block.py | 46 + keepaway/base/bhv_kick.py | 192 ++ keepaway/base/bhv_move.py | 73 + keepaway/base/decision.py | 575 ++++ .../base/formation_dt/before_kick_off.conf | 15 + .../base/formation_dt/defense_formation.conf | 1512 +++++++++++ .../goalie_kick_opp_formation.conf | 15 + .../goalie_kick_our_formation.conf | 15 + .../formation_dt/kickin_our_formation.conf | 290 ++ .../base/formation_dt/offense_formation.conf | 1512 +++++++++++ .../formation_dt/setplay_opp_formation.conf | 602 +++++ .../formation_dt/setplay_our_formation.conf | 615 +++++ keepaway/base/generator_action.py | 142 + keepaway/base/generator_clear.py | 89 + keepaway/base/generator_dribble.py | 277 ++ keepaway/base/generator_pass.py | 858 ++++++ keepaway/base/generator_shoot.py | 302 +++ keepaway/base/goalie_decision.py | 137 + keepaway/base/main_coach.py | 21 + keepaway/base/main_player.py | 18 + keepaway/base/main_trainer.py | 19 + keepaway/base/sample_coach.py | 104 + keepaway/base/sample_communication.py | 842 ++++++ keepaway/base/sample_player.py | 101 + keepaway/base/sample_trainer.py | 27 + keepaway/base/set_play/__init__.py | 0 keepaway/base/set_play/bhv_goalie_set_play.py | 149 ++ keepaway/base/set_play/bhv_set_play.py | 175 ++ .../set_play/bhv_set_play_before_kick_off.py | 23 + keepaway/base/stamina_manager.py | 44 + keepaway/base/strategy.py | 36 + keepaway/base/strategy_formation.py | 109 + keepaway/base/tackle_generator.py | 292 ++ keepaway/base/tools.py | 357 +++ keepaway/base/view_tactical.py | 142 + keepaway/config/.DS_Store | Bin 0 -> 6148 bytes keepaway/config/__init__.py | 0 keepaway/config/server-config.yml | 41 + keepaway/config/team_config.py | 27 + keepaway/envs/.DS_Store | Bin 0 -> 6148 bytes keepaway/envs/__init__.py | 4 + keepaway/envs/keepaway_env.py | 371 +++ keepaway/envs/keepaway_wrapper.py | 61 + keepaway/envs/multiagentenv.py | 77 + keepaway/envs/policies/.DS_Store | Bin 0 -> 6148 bytes keepaway/envs/policies/__init__.py | 0 keepaway/envs/policies/always_hold.py | 24 + keepaway/envs/policies/handcoded_agent.py | 101 + keepaway/envs/policies/random_agent.py | 31 + keepaway/examples/.DS_Store | Bin 0 -> 6148 bytes keepaway/examples/test_alwayshold_agent.py | 41 + keepaway/examples/test_handcoded_agent.py | 39 + keepaway/examples/test_random_agent.py | 43 + keepaway/lib/.DS_Store | Bin 0 -> 8196 bytes keepaway/lib/__init__.py | 0 keepaway/lib/action/__init__.py | 0 keepaway/lib/action/basic_actions.py | 204 ++ keepaway/lib/action/go_to_point.py | 138 + keepaway/lib/action/hold_ball.py | 526 ++++ keepaway/lib/action/intercept.py | 511 ++++ keepaway/lib/action/intercept_info.py | 88 + keepaway/lib/action/intercept_player.py | 225 ++ keepaway/lib/action/intercept_self.py | 1151 ++++++++ keepaway/lib/action/intercept_table.py | 301 +++ keepaway/lib/action/kick_table.py | 1240 +++++++++ keepaway/lib/action/neck_body_to_ball.py | 25 + keepaway/lib/action/neck_body_to_point.py | 77 + keepaway/lib/action/neck_scan_field.py | 197 ++ keepaway/lib/action/neck_scan_players.py | 158 ++ keepaway/lib/action/neck_turn_to_ball.py | 138 + .../lib/action/neck_turn_to_ball_or_scan.py | 57 + keepaway/lib/action/neck_turn_to_point.py | 55 + keepaway/lib/action/neck_turn_to_relative.py | 16 + keepaway/lib/action/scan_field.py | 72 + keepaway/lib/action/smart_kick.py | 95 + keepaway/lib/action/stop_ball.py | 115 + keepaway/lib/action/turn_to_ball.py | 18 + keepaway/lib/action/turn_to_point.py | 23 + keepaway/lib/action/view_wide.py | 11 + keepaway/lib/coach/__init__.py | 0 keepaway/lib/coach/coach_agent.py | 332 +++ keepaway/lib/coach/gloabl_world_model.py | 244 ++ keepaway/lib/coach/global_object.py | 205 ++ keepaway/lib/debug/__init__.py | 0 keepaway/lib/debug/color.py | 38 + keepaway/lib/debug/debug.py | 36 + keepaway/lib/debug/debug_client.py | 240 ++ keepaway/lib/debug/level.py | 43 + keepaway/lib/debug/os_logger.py | 53 + keepaway/lib/debug/sw_logger.py | 203 ++ keepaway/lib/debug/timer.py | 28 + keepaway/lib/formation/__init__.py | 0 .../lib/formation/delaunay_triangulation.py | 122 + keepaway/lib/messenger/__init__.py | 0 .../lib/messenger/ball_goalie_messenger.py | 76 + keepaway/lib/messenger/ball_messenger.py | 64 + .../lib/messenger/ball_player_messenger.py | 89 + .../lib/messenger/ball_pos_vel_messenger.py | 89 + keepaway/lib/messenger/converters.py | 123 + keepaway/lib/messenger/free_form_messenger.py | 17 + keepaway/lib/messenger/goalie_messenger.py | 66 + .../lib/messenger/goalie_player_messenger.py | 84 + keepaway/lib/messenger/messenger.py | 139 + keepaway/lib/messenger/messenger_memory.py | 180 ++ .../lib/messenger/one_player_messenger.py | 69 + keepaway/lib/messenger/pass_messenger.py | 90 + .../messenger/player_pos_unum_messenger.py | 80 + keepaway/lib/messenger/recovery_message.py | 34 + keepaway/lib/messenger/stamina_messenger.py | 34 + .../lib/messenger/three_player_messenger.py | 78 + .../lib/messenger/two_player_messenger.py | 74 + keepaway/lib/network/__init__.py | 0 keepaway/lib/network/udp_socket.py | 51 + keepaway/lib/parser/__init__.py | 0 keepaway/lib/parser/global_message_parser.py | 129 + .../lib/parser/message_params_parser_see.py | 19 + .../parser/parser_message_fullstate_world.py | 157 ++ keepaway/lib/parser/parser_message_params.py | 52 + keepaway/lib/player/__init__.py | 0 keepaway/lib/player/action_effector.py | 980 +++++++ keepaway/lib/player/basic_client.py | 34 + keepaway/lib/player/localizer.py | 491 ++++ keepaway/lib/player/object.py | 190 ++ keepaway/lib/player/object_ball.py | 304 +++ keepaway/lib/player/object_player.py | 406 +++ keepaway/lib/player/object_self.py | 599 +++++ keepaway/lib/player/object_table.py | 580 ++++ keepaway/lib/player/player_agent.py | 675 +++++ keepaway/lib/player/sensor/__init__.py | 0 keepaway/lib/player/sensor/body_sensor.py | 248 ++ .../lib/player/sensor/say_message_builder.py | 2 + keepaway/lib/player/sensor/see_state.py | 66 + keepaway/lib/player/sensor/visual_sensor.py | 461 ++++ keepaway/lib/player/soccer_action.py | 163 ++ keepaway/lib/player/soccer_agent.py | 80 + keepaway/lib/player/stamina_model.py | 133 + keepaway/lib/player/trainer_agent.py | 202 ++ keepaway/lib/player/view_area.py | 41 + keepaway/lib/player/world_model.py | 2337 +++++++++++++++++ keepaway/lib/player_command/__init__.py | 0 keepaway/lib/player_command/coach_command.py | 149 ++ keepaway/lib/player_command/player_command.py | 193 ++ .../lib/player_command/player_command_body.py | 116 + .../player_command/player_command_sender.py | 35 + .../player_command/player_command_support.py | 139 + .../lib/player_command/trainer_command.py | 191 ++ keepaway/lib/rcsc/__init__.py | 0 keepaway/lib/rcsc/game_mode.py | 165 ++ keepaway/lib/rcsc/game_time.py | 39 + keepaway/lib/rcsc/player_type.py | 328 +++ keepaway/lib/rcsc/server_param.py | 1465 +++++++++++ keepaway/lib/rcsc/types.py | 405 +++ keepaway/requirements.txt | 5 + keepaway/setup.py | 28 + keepaway/tests/test_messages.py | 263 ++ keepaway/tests/test_visual_sensor.py | 11 + keepaway/tools/.DS_Store | Bin 0 -> 6148 bytes keepaway/tools/compile/accessor_maker.py | 33 + keepaway/tools/compile/clean.py | 63 + keepaway/tools/compile/clean.sh | 3 + keepaway/tools/compile/compile.py | 82 + keepaway/tools/compile/compile.sh | 12 + keepaway/utils/.DS_Store | Bin 0 -> 6148 bytes keepaway/utils/__init__.py | 0 keepaway/utils/decision.py | 124 + keepaway/utils/keeepawy_utils.py | 370 +++ keepaway/utils/keepaway_actions.py | 1426 ++++++++++ keepaway/utils/keepaway_agent.py | 1729 ++++++++++++ keepaway/utils/keepaway_communication.py | 117 + keepaway/utils/keepaway_env.py | 207 ++ keepaway/utils/keepaway_player.py | 224 ++ keepaway/utils/keepaway_player_agent.py | 962 +++++++ keepaway/utils/main_keepaway_player.py | 19 + keepaway/utils/tools.py | 435 +++ keepaway/utils/world_model_keepaway.py | 59 + 181 files changed, 37005 insertions(+) create mode 100644 keepaway/.DS_Store create mode 100644 keepaway/LICENSE create mode 100755 keepaway/README.md create mode 100644 keepaway/base/.DS_Store create mode 100755 keepaway/base/__init__.py create mode 100755 keepaway/base/basic_tackle.py create mode 100755 keepaway/base/bhv_block.py create mode 100755 keepaway/base/bhv_kick.py create mode 100755 keepaway/base/bhv_move.py create mode 100755 keepaway/base/decision.py create mode 100755 keepaway/base/formation_dt/before_kick_off.conf create mode 100755 keepaway/base/formation_dt/defense_formation.conf create mode 100755 keepaway/base/formation_dt/goalie_kick_opp_formation.conf create mode 100755 keepaway/base/formation_dt/goalie_kick_our_formation.conf create mode 100755 keepaway/base/formation_dt/kickin_our_formation.conf create mode 100755 keepaway/base/formation_dt/offense_formation.conf create mode 100755 keepaway/base/formation_dt/setplay_opp_formation.conf create mode 100755 keepaway/base/formation_dt/setplay_our_formation.conf create mode 100755 keepaway/base/generator_action.py create mode 100755 keepaway/base/generator_clear.py create mode 100755 keepaway/base/generator_dribble.py create mode 100755 keepaway/base/generator_pass.py create mode 100755 keepaway/base/generator_shoot.py create mode 100755 keepaway/base/goalie_decision.py create mode 100755 keepaway/base/main_coach.py create mode 100755 keepaway/base/main_player.py create mode 100755 keepaway/base/main_trainer.py create mode 100755 keepaway/base/sample_coach.py create mode 100755 keepaway/base/sample_communication.py create mode 100755 keepaway/base/sample_player.py create mode 100755 keepaway/base/sample_trainer.py create mode 100755 keepaway/base/set_play/__init__.py create mode 100755 keepaway/base/set_play/bhv_goalie_set_play.py create mode 100755 keepaway/base/set_play/bhv_set_play.py create mode 100755 keepaway/base/set_play/bhv_set_play_before_kick_off.py create mode 100755 keepaway/base/stamina_manager.py create mode 100755 keepaway/base/strategy.py create mode 100755 keepaway/base/strategy_formation.py create mode 100755 keepaway/base/tackle_generator.py create mode 100755 keepaway/base/tools.py create mode 100755 keepaway/base/view_tactical.py create mode 100644 keepaway/config/.DS_Store create mode 100644 keepaway/config/__init__.py create mode 100644 keepaway/config/server-config.yml create mode 100644 keepaway/config/team_config.py create mode 100644 keepaway/envs/.DS_Store create mode 100644 keepaway/envs/__init__.py create mode 100644 keepaway/envs/keepaway_env.py create mode 100644 keepaway/envs/keepaway_wrapper.py create mode 100644 keepaway/envs/multiagentenv.py create mode 100644 keepaway/envs/policies/.DS_Store create mode 100644 keepaway/envs/policies/__init__.py create mode 100644 keepaway/envs/policies/always_hold.py create mode 100644 keepaway/envs/policies/handcoded_agent.py create mode 100644 keepaway/envs/policies/random_agent.py create mode 100644 keepaway/examples/.DS_Store create mode 100644 keepaway/examples/test_alwayshold_agent.py create mode 100644 keepaway/examples/test_handcoded_agent.py create mode 100644 keepaway/examples/test_random_agent.py create mode 100644 keepaway/lib/.DS_Store create mode 100644 keepaway/lib/__init__.py create mode 100644 keepaway/lib/action/__init__.py create mode 100644 keepaway/lib/action/basic_actions.py create mode 100644 keepaway/lib/action/go_to_point.py create mode 100644 keepaway/lib/action/hold_ball.py create mode 100644 keepaway/lib/action/intercept.py create mode 100644 keepaway/lib/action/intercept_info.py create mode 100644 keepaway/lib/action/intercept_player.py create mode 100644 keepaway/lib/action/intercept_self.py create mode 100644 keepaway/lib/action/intercept_table.py create mode 100644 keepaway/lib/action/kick_table.py create mode 100644 keepaway/lib/action/neck_body_to_ball.py create mode 100644 keepaway/lib/action/neck_body_to_point.py create mode 100644 keepaway/lib/action/neck_scan_field.py create mode 100644 keepaway/lib/action/neck_scan_players.py create mode 100644 keepaway/lib/action/neck_turn_to_ball.py create mode 100644 keepaway/lib/action/neck_turn_to_ball_or_scan.py create mode 100644 keepaway/lib/action/neck_turn_to_point.py create mode 100644 keepaway/lib/action/neck_turn_to_relative.py create mode 100644 keepaway/lib/action/scan_field.py create mode 100644 keepaway/lib/action/smart_kick.py create mode 100644 keepaway/lib/action/stop_ball.py create mode 100644 keepaway/lib/action/turn_to_ball.py create mode 100644 keepaway/lib/action/turn_to_point.py create mode 100644 keepaway/lib/action/view_wide.py create mode 100644 keepaway/lib/coach/__init__.py create mode 100644 keepaway/lib/coach/coach_agent.py create mode 100644 keepaway/lib/coach/gloabl_world_model.py create mode 100644 keepaway/lib/coach/global_object.py create mode 100644 keepaway/lib/debug/__init__.py create mode 100644 keepaway/lib/debug/color.py create mode 100644 keepaway/lib/debug/debug.py create mode 100644 keepaway/lib/debug/debug_client.py create mode 100644 keepaway/lib/debug/level.py create mode 100644 keepaway/lib/debug/os_logger.py create mode 100644 keepaway/lib/debug/sw_logger.py create mode 100644 keepaway/lib/debug/timer.py create mode 100644 keepaway/lib/formation/__init__.py create mode 100644 keepaway/lib/formation/delaunay_triangulation.py create mode 100644 keepaway/lib/messenger/__init__.py create mode 100644 keepaway/lib/messenger/ball_goalie_messenger.py create mode 100644 keepaway/lib/messenger/ball_messenger.py create mode 100644 keepaway/lib/messenger/ball_player_messenger.py create mode 100644 keepaway/lib/messenger/ball_pos_vel_messenger.py create mode 100644 keepaway/lib/messenger/converters.py create mode 100644 keepaway/lib/messenger/free_form_messenger.py create mode 100644 keepaway/lib/messenger/goalie_messenger.py create mode 100644 keepaway/lib/messenger/goalie_player_messenger.py create mode 100644 keepaway/lib/messenger/messenger.py create mode 100644 keepaway/lib/messenger/messenger_memory.py create mode 100644 keepaway/lib/messenger/one_player_messenger.py create mode 100644 keepaway/lib/messenger/pass_messenger.py create mode 100644 keepaway/lib/messenger/player_pos_unum_messenger.py create mode 100644 keepaway/lib/messenger/recovery_message.py create mode 100644 keepaway/lib/messenger/stamina_messenger.py create mode 100644 keepaway/lib/messenger/three_player_messenger.py create mode 100644 keepaway/lib/messenger/two_player_messenger.py create mode 100644 keepaway/lib/network/__init__.py create mode 100644 keepaway/lib/network/udp_socket.py create mode 100644 keepaway/lib/parser/__init__.py create mode 100644 keepaway/lib/parser/global_message_parser.py create mode 100644 keepaway/lib/parser/message_params_parser_see.py create mode 100644 keepaway/lib/parser/parser_message_fullstate_world.py create mode 100644 keepaway/lib/parser/parser_message_params.py create mode 100644 keepaway/lib/player/__init__.py create mode 100644 keepaway/lib/player/action_effector.py create mode 100644 keepaway/lib/player/basic_client.py create mode 100644 keepaway/lib/player/localizer.py create mode 100644 keepaway/lib/player/object.py create mode 100644 keepaway/lib/player/object_ball.py create mode 100644 keepaway/lib/player/object_player.py create mode 100644 keepaway/lib/player/object_self.py create mode 100644 keepaway/lib/player/object_table.py create mode 100644 keepaway/lib/player/player_agent.py create mode 100644 keepaway/lib/player/sensor/__init__.py create mode 100644 keepaway/lib/player/sensor/body_sensor.py create mode 100644 keepaway/lib/player/sensor/say_message_builder.py create mode 100644 keepaway/lib/player/sensor/see_state.py create mode 100644 keepaway/lib/player/sensor/visual_sensor.py create mode 100644 keepaway/lib/player/soccer_action.py create mode 100644 keepaway/lib/player/soccer_agent.py create mode 100644 keepaway/lib/player/stamina_model.py create mode 100644 keepaway/lib/player/trainer_agent.py create mode 100644 keepaway/lib/player/view_area.py create mode 100644 keepaway/lib/player/world_model.py create mode 100644 keepaway/lib/player_command/__init__.py create mode 100644 keepaway/lib/player_command/coach_command.py create mode 100644 keepaway/lib/player_command/player_command.py create mode 100644 keepaway/lib/player_command/player_command_body.py create mode 100644 keepaway/lib/player_command/player_command_sender.py create mode 100644 keepaway/lib/player_command/player_command_support.py create mode 100644 keepaway/lib/player_command/trainer_command.py create mode 100644 keepaway/lib/rcsc/__init__.py create mode 100644 keepaway/lib/rcsc/game_mode.py create mode 100644 keepaway/lib/rcsc/game_time.py create mode 100644 keepaway/lib/rcsc/player_type.py create mode 100644 keepaway/lib/rcsc/server_param.py create mode 100644 keepaway/lib/rcsc/types.py create mode 100644 keepaway/requirements.txt create mode 100644 keepaway/setup.py create mode 100644 keepaway/tests/test_messages.py create mode 100644 keepaway/tests/test_visual_sensor.py create mode 100644 keepaway/tools/.DS_Store create mode 100755 keepaway/tools/compile/accessor_maker.py create mode 100644 keepaway/tools/compile/clean.py create mode 100755 keepaway/tools/compile/clean.sh create mode 100644 keepaway/tools/compile/compile.py create mode 100755 keepaway/tools/compile/compile.sh create mode 100644 keepaway/utils/.DS_Store create mode 100644 keepaway/utils/__init__.py create mode 100755 keepaway/utils/decision.py create mode 100644 keepaway/utils/keeepawy_utils.py create mode 100644 keepaway/utils/keepaway_actions.py create mode 100644 keepaway/utils/keepaway_agent.py create mode 100644 keepaway/utils/keepaway_communication.py create mode 100644 keepaway/utils/keepaway_env.py create mode 100755 keepaway/utils/keepaway_player.py create mode 100644 keepaway/utils/keepaway_player_agent.py create mode 100755 keepaway/utils/main_keepaway_player.py create mode 100755 keepaway/utils/tools.py create mode 100644 keepaway/utils/world_model_keepaway.py diff --git a/keepaway/.DS_Store b/keepaway/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a81113d2ca1aa5c43af6fbff11f24dc6d2e12fc6 GIT binary patch literal 10244 zcmeHMJ!}&(6n<{fa#2A91OkcW1PoNFAT15F0;G_pYX^b~;;+p$Aw6+9N4=z}6cuGd zPziy=1T#AutPBXm4g(`YCy+Y8#KQ9r7yB;l(n2d_N9T9v_n!UyeX%dk0RWP3mPY_= z0I=|4GQAh89);=I6)SzRC5w`vKRB(R?s(L}=hrc7(_j!V2p9wm0tNwtz-~YQ-`T8K zgF>3xAYc$M2!sf*{lUhI$()oOA^FyU6;A={kCj1v1zFs z#FQErFvRwUn2pNG>VEP~a?-<2TNgh!veP(%ImS_}uLxHOWXJ_aSsicw`ODyFyFHc} z!>wxc>?T_k+wHMz_%>KYrJRjQjBL{AIGvd88mlDW!kBp>HXZ4wFAFH?S`F~=CoDec z7lAp6FjJ^PpoSJUYQeEi*xHf~YIy6Hz8qV7FW+6EzWd$irPo3fefL9ytxBu|edOa` z(Lm9vW54{hd|ggnv*?cA+d>~d!y;h*6hEF0htjNvHS|_@Qo};VCZ7t%6n~wyUKV;- z{8vLiG%+wY9#+__g_Zj8jE5C!)v%_gKHUk?QY^Y?^|sK$8qDDUrWgba0tNwtfI(m@ z2qc8F&CdU)Hva#AD?=DxgMdL`mm@%ug<>I(LH;>ai^j{&+HJh%@M4A8b%f+DSn=a{ zl>9gzAJ}pHDZH31|AjB_b5eGM std output +# - textfile: loggers write on the files keepaway.based on players unum. (player-{unum}.txt, player-{unum}.err) +-o|--out [std|textfile] + +# change the host(serve) ip adderess. (defualt is localhost) +-H|--host new_ip_address + +# change the player port connection. (default is 6000) +-p|--player-port new_port + +# change the coach port connection. (default is 6002) +-P|--coach-port new_port + +# change the trainer port connection. (default is 6001) +--trainer-port new_port + +``` + +--- + +## Useful links + +- CYRUS team: [https://cyrus2d.com/](https://cyrus2d.com/) +- RoboCup: [https://www.robocup.org/](https://www.robocup.org/) +- Soccer Simulation 2D League: [https://rcsoccersim.github.io/](https://rcsoccersim.github.io/) +- Server documentation: [https://rcsoccersim.readthedocs.io/](https://rcsoccersim.readthedocs.io/) + +## Related Papers + +- Zare N, Amini O, Sayareh A, Sarvmaili M, Firouzkouhi A, Rad SR, Matwin S, Soares A. Cyrus2D keepaway.base: Source Code keepaway.base for RoboCup 2D Soccer Simulation League. InRoboCup 2022: Robot World Cup XXV 2023 Mar 24 (pp. 140-151). Cham: Springer International Publishing. [link](https://arxiv.org/abs/2211.08585) +- Zare N, Sarvmaili M, Sayareh A, Amini O, Matwin S, Soares A. Engineering Features to Improve Pass Prediction in Soccer Simulation 2D Games. InRobot World Cup 2022 (pp. 140-152). Springer, Cham. [link](https://www.researchgate.net/profile/Nader-Zare/publication/352414392_Engineering_Features_to_Improve_Pass_Prediction_in_Soccer_Simulation_2D_Games/links/60c9207fa6fdcc0c5c866520/Engineering-Features-to-Improve-Pass-Prediction-in-Soccer-Simulation-2D-Games.pdf) +- Zare N, Amini O, Sayareh A, Sarvmaili M, Firouzkouhi A, Matwin S, Soares A. Improving Dribbling, Passing, and Marking Actions in Soccer Simulation 2D Games Using Machine Learning. InRobot World Cup 2021 Jun 22 (pp. 340-351). Springer, Cham. [link](https://www.researchgate.net/profile/Nader-Zare/publication/355680673_Improving_Dribbling_Passing_and_Marking_Actions_in_Soccer_Simulation_2D_Games_Using_Machine_Learning/links/617971b0a767a03c14be3e42/Improving-Dribbling-Passing-and-Marking-Actions-in-Soccer-Simulation-2D-Games-Using-Machine-Learning.pdf) +- Akiyama, H., Nakashima, T.: Helios keepaway.base: An open source package for the robocup soccer 2d simulation. In Robot Soccer World Cup 2013 Jun 24 (pp. 528-535). Springer, Berlin, Heidelberg. + +## Cite and Support + +Please don't forget to cite our papers and star our GitHub repo if you haven't already! + +## Contributing + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/keepaway/base/.DS_Store b/keepaway/base/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..57adcf51d9a222fdb928449b1715b79937aa8d85 GIT binary patch literal 6148 zcmeHKJ5Iwu5S~5Dh{wBkjKZ%&gx&X?MItWO~<0m#9NTJ_@6^j%tDNJeQg^WTXu=c8!>h zDW)-<(4gk61FC>3@Y@vNv)jUH9Z*g)oZa&L4kl;aew?MbrQCddT<)1>Q8b!lBY61x z_q$uk^V8lc@8lca`FM_n|A1wwO{bL6C8c0HrE|0|z(1jBg)zPJ-j@2#bM|hU+roK; zf5{r_?tJB6;cbQp!6kZyn{jHjo`CzT%J=SVkSptu!n7t@5k#;(K51 zpDKWy&E~fpYOM;W0;<5L0(?IND2$DFz;Se)0<3fQh z)W{RVxNwXI;ui`Q4qZ4I`(>;nUpDeWF_O_tVBN`t4z*SVRDr4jYp&bj{C}|g{a+2z zo+_XU{3!*D7xkkawj{Z=wmHtV0m=}Cjr|ITHUyQVW82_Vyoq8Ba{)Jip SP.our_penalty_area_line_x() + 0.5 + or wm.ball().pos().abs_y() > SP.penalty_area_half_width() + 0.5) \ + and tackle_prob < wm.self().foul_probability(): + tackle_prob = wm.self().foul_probability() + use_foul = True + + if tackle_prob < self._min_prob: + return False + + self_min = wm.intercept_table().self_reach_cycle() + mate_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + + self_reach_point = wm.ball().inertia_point(self_min) + + self_goal = False + if self_reach_point.x() < - SP.pitch_half_length(): + ball_ray = Ray2D(wm.ball().pos(), wm.ball().vel().th()) + goal_line = Line2D(Vector2D(-SP.pitch_half_length(), +10), + Vector2D(-SP.pitch_half_length(), -10)) + + intersect = ball_ray.intersection(goal_line) + if intersect and intersect.is_valid() \ + and intersect.abs_y() < SP.goal_half_width() + 1.: + self_goal = True + + if not (wm.kickable_opponent() + or self_goal + or (opp_min < self_min - 3 and opp_min < mate_min - 3) + or (self_min >= 5 + and wm.ball().pos().dist2(SP.their_team_goal_pos()) < 10 **2 + and ((SP.their_team_goal_pos() - wm.self().pos()).th() - wm.self().body()).abs() < 45.)): + + return False + + return self.executeV14(agent, use_foul) + + def executeV14(self, agent: 'PlayerAgent', use_foul: bool): + wm = agent.world() + + result = TackleGenerator.instance().best_result(wm) + + ball_next = wm.ball().pos() + result._ball_vel + + log.debug_client().add_message(f"Basic{'Foul' if use_foul else 'Tackle'}{result._tackle_angle.degree()}") + tackle_dir = (result._tackle_angle - wm.self().body()).degree() + + agent.do_tackle(tackle_dir, use_foul) + agent.set_neck_action(NeckTurnToPoint(ball_next)) + return True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/keepaway/base/bhv_block.py b/keepaway/base/bhv_block.py new file mode 100755 index 00000000..65c42874 --- /dev/null +++ b/keepaway/base/bhv_block.py @@ -0,0 +1,46 @@ +from keepaway.lib.action.go_to_point import GoToPoint +from keepaway.base.strategy_formation import StrategyFormation +from keepaway.lib.action.intercept import Intercept +from keepaway.base.tools import Tools +from keepaway.base.stamina_manager import get_normal_dash_power +from pyrusgeom.soccer_math import * + +from typing import TYPE_CHECKING + +from keepaway.lib.debug.debug import log + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + +class Bhv_Block: + def execute(self, agent: 'PlayerAgent'): + wm = agent.world() + opp_min = wm.intercept_table().opponent_reach_cycle() + ball_pos = wm.ball().inertia_point(opp_min) + dribble_speed_etimate = 0.7 + dribble_angle_estimate = (Vector2D(-52.0, 0) - ball_pos).th() + blocker = 0 + block_cycle = 1000 + block_pos = Vector2D(0, 0) + for unum in range(1, 12): + tm = wm.our_player(unum) + if tm is None: + continue + if tm.unum() < 1: + continue + for c in range(1, 40): + dribble_pos = ball_pos + Vector2D.polar2vector(c * dribble_speed_etimate, dribble_angle_estimate) + turn_cycle = Tools.predict_player_turn_cycle(tm.player_type(), tm.body(), tm.vel().r(), tm.pos().dist(dribble_pos), (dribble_pos - tm.pos()).th(), 0.2, False) + tm_cycle = tm.player_type().cycles_to_reach_distance(tm.inertia_point(opp_min).dist(dribble_pos)) + turn_cycle + if tm_cycle <= opp_min + c: + if tm_cycle < block_cycle: + block_cycle = tm_cycle + blocker = unum + block_pos = dribble_pos + break + if blocker == wm.self_unum(): + GoToPoint(block_pos, 0.1, 100).execute(agent) + log.debug_client().add_message(f'block in ({round(block_pos.x(), 2)}, {round(block_pos.y(), 2)})') + log.debug_client().set_target(block_pos) + return True + return False diff --git a/keepaway/base/bhv_kick.py b/keepaway/base/bhv_kick.py new file mode 100755 index 00000000..f79d9a1a --- /dev/null +++ b/keepaway/base/bhv_kick.py @@ -0,0 +1,192 @@ +from keepaway.lib.action.hold_ball import HoldBall +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.action.smart_kick import SmartKick +from typing import List +from keepaway.base.generator_action import KickAction, ShootAction, KickActionType +from keepaway.base.generator_dribble import BhvDribbleGen +from keepaway.base.generator_pass import BhvPassGen +from keepaway.base.generator_shoot import BhvShhotGen +from keepaway.base.generator_clear import BhvClearGen +from keepaway.lib.rcsc.server_param import ServerParam +from pyrusgeom.line_2d import Line2D +from pyrusgeom.vector_2d import Vector2D +from keepaway.base.tools import Tools + +from typing import TYPE_CHECKING + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.pass_messenger import PassMessenger + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.player_agent import PlayerAgent + + +class BhvKick: + def __init__(self): + pass + + def execute(self, agent: "PlayerAgent"): + wm: "WorldModel" = agent.world() + shoot_candidate: ShootAction = BhvShhotGen().generator(wm) + if shoot_candidate: + log.debug_client().set_target(shoot_candidate.target_point) + log.debug_client().add_message( + "shoot" + + "to " + + shoot_candidate.target_point.__str__() + + " " + + str(shoot_candidate.first_ball_speed) + ) + SmartKick( + shoot_candidate.target_point, + shoot_candidate.first_ball_speed, + shoot_candidate.first_ball_speed - 1, + 3, + ).execute(agent) + agent.set_neck_action(NeckScanPlayers()) + return True + else: + action_candidates: List[KickAction] = [] + action_candidates += BhvPassGen().generator(wm) + # action_candidates += BhvDribbleGen().generator(wm) # TODO + + if len(action_candidates) == 0: + return self.no_candidate_action(agent) + + best_action: KickAction = max(action_candidates)# + + target = best_action.target_ball_pos + log.debug_client().set_target(target) + log.debug_client().add_message( + best_action.type.value + + "to " + + best_action.target_ball_pos.__str__() + + " " + + str(best_action.start_ball_speed) + ) + SmartKick( + target, + best_action.start_ball_speed, + best_action.start_ball_speed - 1, + 3, + ).execute(agent) + + if best_action.type is KickActionType.Pass: + agent.add_say_message( + PassMessenger( + best_action.target_unum, + best_action.target_ball_pos, + agent.effector().queued_next_ball_pos(), + agent.effector().queued_next_ball_vel(), + ) + ) + + agent.set_neck_action(NeckScanPlayers()) + return True + + def no_candidate_action(self, agent: "PlayerAgent"): + wm = agent.world() + opp_min = wm.intercept_table().opponent_reach_cycle() + if opp_min <= 3: + action_candidates = BhvClearGen().generator(wm) + if len(action_candidates) > 0: + best_action: KickAction = max(action_candidates) + target = best_action.target_ball_pos + log.debug_client().set_target(target) + log.debug_client().add_message( + best_action.type.value + + "to " + + best_action.target_ball_pos.__str__() + + " " + + str(best_action.start_ball_speed) + ) + SmartKick( + target, + best_action.start_ball_speed, + best_action.start_ball_speed - 2.0, + 2, + ).execute(agent) + + agent.set_neck_action(NeckScanPlayers()) + return HoldBall().execute(agent) + + # def kick_to(self,agent: "PlayerAgent",tar_pos,speed): + # SP = ServerParam.i() + # wm = agent.world() + + # ball_pos = wm.ball().pos() + # ball_vel = wm.ball().vel() + # travel_dist = tar_pos - ball_pos + # curr_pos = wm.self().pos() + + # # set polar + # cal_travel_speed = Tools.get_kick_travel(travel_dist.r(), speed) + # vel_des = Vector2D.polar2vector(cal_travel_speed, travel_dist.dir()) + + # # vel_des = (tar_pos - curr_pos).normalized() * speed + + # if (wm.predict_pos().dist(ball_pos + vel_des) < SP.ball_size() + SP.player_size() ): + # line_segment = Line2D(ball_pos,ball_pos + travel_dist) + # body_proj = line_segment.projection(curr_pos) + # dist = ball_pos.dist(body_proj) + # if (travel_dist.r() < dist): + # dist -= SP.ball_size() + SP.player_size() + # else: + # dist += SP.ball_size() + SP.player_size() + # # Log.log(102, "kick results in collision, change velDes from (%f,%f)", + # # velDes.getX(), velDes.getY()); + # travel_dist.set_polar(dist, travel_dist.th()) + + # dDistOpp = std::numeric_limits::max(); + # objOpp = WM->getClosestInSetTo(OBJECT_SET_OPPONENTS, + # OBJECT_BALL, &dDistOpp); + + # # // can never reach point + # if (travel_dist.r() > SP.ball_speed_max()): + # pow = SP.getMaxPower() + # speed = wm.self().kick_rate() * pow + # tmp = ball_vel.rotate(-travel_dist.th()).get_y() + # ang = travel_dist.th() - AngleDeg.asin_deg(tmp / speed) + # speedpred = (ball_vel + Vector2D.polar2vector(speed, ang)).r() + # # but ball acceleration in right direction is very high + # # player kick prop 0.85 - handcoded + # if (speedpred > 0.85 * SP.ball_accel_max()): + # # Log.log(102, "pos (%f,%f) too far, but can acc ball good to %f k=%f,%f", + # # velDes.getX(), velDes.getY(), dSpeedPred, dSpeed, tmp); + # # // shoot nevertheless + + # return accelerateBallToVelocity(vel_des) + # elif (wm.kick_power_rate() > 0.85 * SP.kick_power_rate()): + # # { + # # Log.log(102, "point too far, freeze ball"); // ball well-positioned + # # // freeze ball + # return freezeBall(); + # else: + # # Log.log(102, "point too far, reposition ball (k_r = %f)", + # # WM->getActualKickPowerRate() / (SS->getKickPowerRate())); + # # // else position ball better + + # return kickBallCloseToBody(0); + # # // can reach point + # else : + # Vector2D accBallDes = vel_des - vel_ball + # dPower = wm.get_kick_power_speed(accBallDes.getMagnitude()) + + # # // with current ball speed + # if (dPower <= 1.05 * SS->getMaxPower() or (dDistOpp < 2.0 && dPower <= 1.30 * + # SP.getMaxPower())): // 1.05 since cannot get ball fully perfect + # # Log.log(102, "point good and can reach point %f", dPower); + # # // perform shooting action + # return accelerateBallToVelocity(velDes); + # else: + # # Log.log(102, "point good, but reposition ball since need %f", dPower); + # SoccerCommand soc = kickBallCloseToBody(0); + # VecPosition posPredBall; + + # WM->predictBallInfoAfterCommand(soc, &posPredBall); + # dDistOpp = posPredBall.getDistanceTo(WM->getGlobalPosition(objOpp)); + # # // position ball better + # return soc; + + diff --git a/keepaway/base/bhv_move.py b/keepaway/base/bhv_move.py new file mode 100755 index 00000000..ddb74bf7 --- /dev/null +++ b/keepaway/base/bhv_move.py @@ -0,0 +1,73 @@ +from keepaway.base.basic_tackle import BasicTackle +from keepaway.lib.action.go_to_point import GoToPoint +from keepaway.base.strategy_formation import StrategyFormation +from keepaway.lib.action.intercept import Intercept +from keepaway.lib.action.neck_turn_to_ball import NeckTurnToBall +from keepaway.lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan +from keepaway.lib.action.turn_to_ball import TurnToBall +from keepaway.lib.action.turn_to_point import TurnToPoint +from keepaway.base.tools import Tools +from keepaway.base.stamina_manager import get_normal_dash_power +from keepaway.base.bhv_block import Bhv_Block +from pyrusgeom.vector_2d import Vector2D + +from typing import TYPE_CHECKING + +from keepaway.lib.debug.debug import log + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + from keepaway.lib.player.world_model import WorldModel + + +class BhvMove: + def __init__(self): + self._in_recovery_mode = False + pass + + def execute(self, agent: "PlayerAgent"): + wm: "WorldModel" = agent.world() + + if BasicTackle(0.8, 80).execute(agent): + return True + + # intercept + self_min = wm.intercept_table().self_reach_cycle() + tm_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + log.sw_log().block().add_text(f"self_min={self_min}") + log.sw_log().block().add_text(f"tm_min={tm_min}") + log.sw_log().block().add_text(f"opp_min={opp_min}") + + if not wm.exist_kickable_teammates() and ( + self_min <= 2 or (self_min <= tm_min and self_min < opp_min + 5) + ): + log.sw_log().block().add_text("INTERCEPTING") + log.debug_client().add_message("intercept") + if Intercept().execute(agent): + agent.set_neck_action(NeckTurnToBall()) + return True + + if opp_min < min(tm_min, self_min): + if Bhv_Block().execute(agent): + agent.set_neck_action(NeckTurnToBall()) + return True + st = StrategyFormation().i() + target = st.get_pos(agent.world().self().unum()) + + log.debug_client().set_target(target) + log.debug_client().add_message("bhv_move") + + dash_power, self._in_recovery_mode = get_normal_dash_power( + wm, self._in_recovery_mode + ) + dist_thr = wm.ball().dist_from_self() * 0.1 + + if dist_thr < 1.0: + dist_thr = 1.0 + + print("target", target) + if GoToPoint(target, dist_thr, dash_power).execute(agent): + agent.set_neck_action(NeckTurnToBallOrScan()) + return True + return False diff --git a/keepaway/base/decision.py b/keepaway/base/decision.py new file mode 100755 index 00000000..b1cff6e5 --- /dev/null +++ b/keepaway/base/decision.py @@ -0,0 +1,575 @@ +from keepaway.base import goalie_decision +from keepaway.base.strategy_formation import StrategyFormation +from keepaway.base.set_play.bhv_set_play import Bhv_SetPlay +from keepaway.base.bhv_kick import BhvKick +from keepaway.base.bhv_move import BhvMove +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.action.neck_turn_to_ball import NeckTurnToBall +from keepaway.lib.action.scan_field import ScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.action.hold_ball import HoldBall + +from keepaway.utils.keepaway_actions import SmartKick, GoToPoint, NeckTurnToBallOrScan,NeckBodyToPoint + +from keepaway.lib.action.turn_to_ball import TurnToBall +from keepaway.lib.action.neck_body_to_ball import NeckBodyToBall +from keepaway.lib.rcsc.server_param import ServerParam + +from keepaway.base.generator_pass import BhvPassGen +from keepaway.lib.action.intercept import Intercept + + +from pyrusgeom.soccer_math import * +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.vector_2d import Vector2D +from pyrusgeom.line_2d import Line2D +from pyrusgeom.segment_2d import Segment2D +from pyrusgeom.geom_2d import * + +from keepaway.base.tools import Tools +from typing import TYPE_CHECKING + +import math as Math + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.player_agent import PlayerAgent + +# TODO TACKLE GEN +# TODO GOAL KICK L/R +# TODO GOAL L/R +DEBUG = True + + +def get_decision(agent: "PlayerAgent"): + wm: "WorldModel" = agent.world() + + st = StrategyFormation().i() + st.update(wm) + + if wm.self().goalie(): + if goalie_decision.decision(agent): + return True + + if wm.game_mode().type() != GameModeType.PlayOn: + if Bhv_SetPlay().execute(agent): + return True + + log.sw_log().team().add_text( + f"is kickable? dist {wm.ball().dist_from_self()} " + f"ka {wm.self().player_type().kickable_area()} " + f"seen pos count {wm.ball().seen_pos_count()} " + f"is? {wm.self()._kickable}" + ) + if wm.self().is_kickable(): + ## TODO: check this + return BhvKick().execute(agent) + if BhvMove().execute(agent): + return True + log.os_log().warn("NO ACTION, ScanFIELD") + return ScanField().execute(agent) + + +# working code - should + + +def get_decision_keepaway( + agent: "PlayerAgent", + count_list, + barrier, + event_to_set, + event_to_wait, + obs, + last_action_time, + reward, + terminated, + full_world, +): + wm: "WorldModel" = agent.world() + + # void GetOpen::run() { + # int status = WM->isTmControllBall(); + # while (running() && status == WM->isTmControllBall()) { + # SoccerCommand soc; + # ObjectT fastest = WM->getFastestInSetTo(OBJECT_SET_TEAMMATES, OBJECT_BALL); + # int iCycles = WM->predictNrCyclesToObject(fastest, OBJECT_BALL); + # VecPosition posPassFrom = WM->predictPosAfterNrCycles(OBJECT_BALL, iCycles); + # posPassFrom = refineTarget(posPassFrom, WM->getBallPos()); + # ACT->putCommandInQueue(soc = player->getOpenForPassFromInRectangle(WM->getKeepawayRect(), posPassFrom)); + # ACT->putCommandInQueue(player->turnNeckToObject(OBJECT_BALL, soc)); + # Log.log(101, "GetOpen::run action"); + # Action(this)(); + # if (WM->isBallKickable()) break; + # } + + # bool WorldModel::isTmControllBall() { + # ObjectT K0 = getClosestInSetTo(OBJECT_SET_TEAMMATES, OBJECT_BALL); + # VecPosition B = getBallPos(); + # double WK0_dist_to_B = getGlobalPosition(K0).getDistanceTo(B); + # bool tmControllBall = WK0_dist_to_B < getMaximalKickDist(K0); + # return tmControllBall || isBallKickable(); + # } + + def get_marking_position(wm, pos, dDist): + """Returns the marking position.""" + ball_pos = wm.ball().pos() + ball_angle = (ball_pos - pos._pos).dir() + return pos._pos + Vector2D.polar2vector(dDist, ball_angle) + + def mark_opponent(wm, p, dDist, obj): # TODO: check this. + """Mark the given opponent.""" + + pos_mark = get_marking_position(wm, p, dDist) + pos_agent = p.pos() + pos_ball = wm.ball().pos() + if obj == "ball": + if pos_mark.dist(pos_agent) < 1.5: + TurnToBall().execute(agent) + else: + GoToPoint(pos_mark, 0.2, 100).execute(agent) + if pos_agent.dist(pos_mark) < 2.0: + ang_opp = (p.pos() - pos_agent).th() + ang_ball = (pos_ball - pos_agent).th() + normalized_ang_opp = AngleDeg.normalize_angle(ang_opp + 180) + + if ang_ball.is_within(ang_opp, normalized_ang_opp): + ang_opp += 80 + else: + ang_opp -= 80 + + ang_opp = AngleDeg.normalize_angle(ang_opp) + target = pos_agent + Vector2D(1.0, ang_opp, POLAR) + + return NeckBodyToPoint(target).execute(agent) + + return GoToPoint(pos_mark, 0.2, 100).execute(agent) + + def mark_most_open_opponent(wm): + """Mark the most open opponent.""" + keepers = wm.opponents() + if len(keepers) == 0: + return + else: + pos_from = keepers[0].pos() + min_player = None + min = 1000 + for p in keepers: + if p.pos_valid(): + point = p.pos() + if point.abs_y() == 37: + continue + num = get_in_set_in_cone(wm, 0.3, pos_from, point) + if num < min: + min = num + min_player = p + return mark_opponent(wm, min_player, 4.0, "ball") + + def do_heard_pass_receive(wm, agent): + """ + check on pass . + + """ + # print(" checking on pass ") + # print("pass ball changed .. ", len(wm.messenger_memory().balls())) + # print("pass time ", wm.messenger_memory().pass_time()) + # print("world model time ", wm.time()) + # print("length of pass ", wm.messenger_memory().pass_()) + # if len(wm.messenger_memory().pass_()) > 0: + # print("pass receiver ", wm.messenger_memory().pass_()[0]._receiver) + # print("memory players ", wm.messenger_memory().players()) + + if ( + wm.messenger_memory().pass_time() != wm.time() + or len(wm.messenger_memory().pass_()) == 0 + or wm.messenger_memory().pass_()[0]._receiver != wm.self().unum() + ): + # print("False") + return False + + self_min = wm.intercept_table().self_reach_cycle() + intercept_pos = wm.ball().inertia_point(self_min) + heard_pos = wm.messenger_memory().pass_()[0]._pos + + # print("heard pos ", heard_pos) + + log.sw_log().team().add_text( + f"(sample player do heard pass) heard_pos={heard_pos}, intercept_pos={intercept_pos}" + ) + + if ( + not wm.kickable_teammate() + and wm.ball().pos_count() <= 1 + and wm.ball().vel_count() <= 1 + and self_min < 20 + ): + print( + "sample player do heard pass) intercepting!", "i am ", wm.self().unum() + ) + + log.sw_log().team().add_text( + f"(sample player do heard pass) intercepting!, self_min={self_min}" + ) + log.debug_client().add_message("Comm:Receive:Intercept") + Intercept().execute(agent) + agent.set_neck_action(NeckTurnToBall()) + + else: + print("(sample player do heard pass) go to point!, cycle ", self_min, " i am ", wm.self().unum()) + + log.sw_log().team().add_text( + f"(sample player do heard pass) go to point!, cycle={self_min}" + ) + log.debug_client().set_target(heard_pos) + log.debug_client().add_message("Comm:Receive:GoTo") + + GoToPoint(heard_pos, 1.0, ServerParam.i().max_dash_power()).execute(agent) + agent.set_neck_action(NeckTurnToBall()) + + def get_in_set_in_cone(wm, radius, pos_from, pos_to): + """Returns the number of players in the given set in the cone from posFrom to posTo with the given radius.""" + conf_threshold = 0.88 + line_segments = Segment2D(pos_from, pos_to) + count = 0 + for p in wm._all_players: + if p.pos_valid(): + pos = p.pos() + pos_on_line = line_segments.nearest_point(pos) + ## projection not right yet . line.isInBetween(posOnLine, start, end) + if ( + (pos_on_line.dist(pos) < radius * pos_on_line.dist(pos_from)) + and (line_segments.projection(pos_on_line)) + and (pos_from.dist(pos) < pos_from.dist(pos_to)) + ): + count += 1 + return count + + def congestion(wm, point, consider_me): + """Returns the congestion at the given position.""" + congest = 0 + for p in wm._teammates: + if p.pos_valid() and p.pos() != point: + congest += 1 / p.pos().dist(point) + + for p in wm._opponents: + if p.pos_valid(): + congest += 1 / p.pos().dist(point) + return congest + + def least_congested_point_for_pass_in_rectangle(rect: Rect2D, pos_from): + """Returns the least congested point for a pass in the given rectangle.""" + + x_granularity = 5 # 5 samples by 5 samples + y_granularity = 5 + x_buffer = 0.15 # 15% border on each side + y_buffer = 0.15 + size = rect.size() + length = size.length() + width = size.width() + x_mesh = length * (1 - 2 * x_buffer) / (x_granularity - 1) + y_mesh = width * (1 - 2 * y_buffer) / (y_granularity - 1) + + start_x = rect.bottom_left().x() + x_buffer * length + start_y = rect.top_left().y() + y_buffer * width + + x = start_x + y = start_y + + # print("X and Y ", x, y) + + best_congestion = 1000 + point = Vector2D(x, y) + tmp = None + + for i in range(x_granularity): + for j in range(y_granularity): + point = Vector2D(x, y) + # print("point", point) + tmp = congestion(wm, point, True) + if ( + tmp < best_congestion + and get_in_set_in_cone(wm, 0.3, pos_from, point) == 0 + ): + best_congestion = tmp + best_point = point + y += y_mesh + x += x_mesh + y = start_y + + if best_congestion == 1000: + # take the point out of the rectangle -- meaning no point was valid. + best_point = rect.center() + return best_point + + def get_open(wm,best_point): + if wm.self().pos().dist(best_point) < 1.5: + NeckBodyToPoint(best_point).execute(agent) + else: + GoToPoint(best_point, 0.2, 100).execute(agent) + + + def keeper_support(wm, fastest, agent): + """Keeper support.""" + from keepaway.lib.messenger.one_player_messenger import OnePlayerMessenger + + sp = ServerParam.i() + first_ball_pos = wm.ball().pos() + min_reach_cycle = Tools.estimate_min_reach_cycle( + fastest._pos, + 1.0, + first_ball_pos, + first_ball_pos.th(), + ) + # print("min_reach_cycle ", min_reach_cycle) + ball_vel = wm.ball().vel() + ball_pos = wm.ball().pos() + first_ball_speed = ball_vel.r() + + dist_ball = ( + first_ball_speed + * (1 - pow(sp.ball_decay(), min_reach_cycle)) + / (1 - sp.ball_decay()) + ) + ball_angle = ball_vel.th() + ball_pos += Vector2D.polar2vector(dist_ball, ball_angle) + ball_vel += ball_vel * pow(sp.ball_decay(), min_reach_cycle) + pos_pass_from = ball_pos + + rect = wm.keepaway_rect() + best_point = least_congested_point_for_pass_in_rectangle(rect, pos_pass_from) + + + if do_heard_pass_receive(wm, agent) == False: + # print("i am ", wm.self()._unum,"no pass heard") + # print("i am ", wm.self()._unum, "going to ", best_point) + GoToPoint(best_point, 0.2, 100).execute(agent) + return + else: + print("pass was heard ") + # i am waiting for the pass. + agent.set_neck_action(NeckScanField()) + return + + # if wm.self().pos().dist(best_point) < 1.5: + # print("NeckScanField") + # return NeckBodyToPoint(wm.ball().pos()).execute(agent) + # # agent.set_neck_action(NeckScanPlayers()) + # else: + # print("i am ", wm.self()._unum, "going to ", best_point) + + # # agent.add_say_message(OnePlayerMessenger(wm.self().unum(), + # # best_point)) + # GoToPoint(best_point, 0.2, 100).execute(agent) + # return + + # # # # ObjectT lookObject = self._choose_look_object( 0.97 ) + + + + def search_ball(wm, agent): + return ScanField().execute(agent) + + def hold(wm, agent): + """ """ + from keepaway.lib.action.neck_scan_players import NeckScanPlayers + agent.set_neck_action(NeckScanPlayers()) + HoldBall().execute(agent) + return + + def keeper(wm: "WorldModel", agent: "PlayerAgent"): + # if wm.self()._is_new_episode() == True: + # wm.self().end_episode(wm._reward()) # + # wm.set_new_episode() + # self.world().set_last_action(-1) # + # time_start_episode = self.world().time() # + if DEBUG: + log.sw_log().world().add_text(f"ball pos = {wm.ball().pos()}") + + if wm.get_confidence("ball") < 0.90: + search_ball(wm, agent) + return + # move(wm, agent) + return + + def test_kick(wm, agent, t): + """Test implementation for kick + target + speed + speed threshold + max step + similar to the goalie kick + """ + + from keepaway.lib.action.smart_kick import SmartKick + + action_candidates = BhvPassGen().generator(wm) + + print("action candidates ", action_candidates) + + if len(action_candidates) == 0: + print("Holding the ball") + agent.set_neck_action(NeckScanPlayers()) + return HoldBall().execute(agent) + + best_action: KickAction = max(action_candidates) + target = best_action.target_ball_pos + print("Target : ", target) + log.debug_client().set_target(target) + log.debug_client().add_message( + best_action.type.value + + "to " + + best_action.target_ball_pos.__str__() + + " " + + str(best_action.start_ball_speed) + ) + + # print(" best action speed ", best_action.start_ball_speed) + + # SmartKick( + # target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3 + # ).execute(agent) + agent.set_neck_action(NeckScanPlayers()) + + return + + def interpret_keeper_action(wm, agent, action): + print("interpret actions , ", action) + + if action == 0: + print("Holding Ball ") + hold(wm, agent) + else: + print("Passing ") + k = wm.teammates_from_ball() + if len(k) > 0: + for tm in k: + if tm.unum() == action: + temp_pos = tm.pos() + # print("passing to player ", tm.unum(), "at pos ", temp_pos) + print( + "i am ", + wm.self().unum(),"at pos ",wm.self().pos(), + "passing to player ", + tm.unum(), + "at pos ", + temp_pos, + ) + # ball_to_player.rotate(-wm.ball().vel().th()) + agent.do_kick_to(tm, 1.5) + # agent.do_kick_2(tm, 1.5) + ## test pass logic + # agent.test_pass(tm, 1.5) + # test_kick(wm,agent,temp_pos) + agent.set_neck_action(NeckScanPlayers()) + # agent.do_kick(temp_pos, 0.8) + # k = wm.messenger_memory().players() + # if len(k) > 0: + # for tm in k: + # print("player num ", tm.unum_) + # if tm.unum_ == action: + # temp_pos = tm.pos_ + # print("i am ",wm.self().unum(), "passing to player ", tm.unum_, "at pos ", temp_pos) + # agent.do_kick_to(tm, 2.0) + # agent.set_neck_action(NeckScanPlayers()) + + else: + print("no teammates") + pass + return + + def keeper_with_ball_2( + wm: "WorldModel", agent: "PlayerAgent", actions, last_action_time + ): + # print("keeper with ball") + # action = actions[wm.self().unum()] + action = actions[wm.self().unum() - 1] + # print( + # "agent ", + # wm.self().unum(), + # " action ", + # actions, + # "my act ", + # actions[wm.self().unum() - 1], + # ) + interpret_keeper_action(wm, agent, action) + + if wm.our_team_name() == "keepers": + barrier.wait() + if wm.get_confidence("ball") < 0.90: + search_ball(wm, agent) + + # teammates_from_ball = wm.teammates_from_ball() + ball_pos = wm.ball().pos() + # GoToPoint(ball_pos, 0.2, 100).execute(agent) + + closest_keeper_from_ball = wm.all_teammates_from_ball() + if len(closest_keeper_from_ball) > 0: + if wm.self().unum() == closest_keeper_from_ball[0].unum(): + GoToPoint(ball_pos, 0.2, 100).execute(agent) + + if wm.self().pos().dist(ball_pos) < 5.0: + obs[wm.self().unum()] = wm._retrieve_observation() + if wm.self().is_kickable(): + wm._available_actions[wm.self().unum()] = 2 + # count_list[wm.self().unum()] = 2 + with count_list.get_lock(): + # print("action received ", list(count_list)) + pass + # print("ball is kickable, ", wm.self().unum()) + keeper_with_ball_2(wm, agent, count_list, last_action_time) + # return + else: + # fastest = wm.get_teammate_nearest_to_ball(1) + # print("i should be intercepting ") + fastest = wm.intercept_table().fastest_teammate() + # print("fastest player intercept table ", f) + # print("fastest player ", fastest) + ## TODO:: re-implement interception. this is not working properly. + ## . check with + if fastest is not None: + # print("Get Open") + keeper_support(wm, fastest, agent) + + ## old implementation + # if fastest is not None: + # # Intercept().execute(agent) + # if fastest.unum == wm.self()._unum: + # print("intercepting") + # # print("fastest player ", fastest._pos) + # Intercept().execute(agent) + # # agent.set_neck_action(NeckTurnToBall()) + # # # print("fastest") + # # # print("fastest player ", fastest._pos) + # keeper_support(wm, wm.self(), fastest, agent) + + ## Update State and Environment + ## calculate reward . + # with last_action_time.get_lock(): + # last_action = last_action_time.value + # with reward.get_lock(): + # reward.value = wm.reward(wm.get_current_cycle(), last_action) + + if wm.our_team_name() == "takers": + # pass + # barrier.wait() + if wm.get_confidence("ball") < 0.90: + search_ball(wm, agent) + + ball_pos = wm.ball().pos() + GoToPoint(ball_pos, 0.2, 100).execute(agent) + + # Maintain possession if you have the ball. + if wm.self().is_kickable() and (len(wm.teammates_from_ball()) == 0): + return HoldBall().execute(agent) + + closest_taker_from_ball = wm.teammates_from_ball() + if wm.self() not in closest_taker_from_ball: + mark_most_open_opponent(wm) + return NeckTurnToBall().execute(agent) + + d = closest_taker_from_ball.dist_to_ball() + if d < 0.3: + NeckTurnToBall().execute(agent) + NeckBodyToBall().execute(agent) + return + return Intercept().execute(agent) diff --git a/keepaway/base/formation_dt/before_kick_off.conf b/keepaway/base/formation_dt/before_kick_off.conf new file mode 100755 index 00000000..a603ab2d --- /dev/null +++ b/keepaway/base/formation_dt/before_kick_off.conf @@ -0,0 +1,15 @@ +Formation Static +# --------------------------------------------------------- +# move positions when playmode is BeforeKickOff or AfterGoal. +1 Goalie -49.0 0.0 +2 CenterBack -25.0 -5.0 +3 CenterBack -25.0 5.0 +4 SideBack -25.0 -10.0 +5 SideBack -25.0 10.0 +6 DefensiveHalf -25.0 0.0 +7 OffensiveHalf -15.0 -5.0 +8 OffensiveHalf -15.0 5.0 +9 SideForward -15.0 -10.0 +10 SideForward -15.0 10.0 +11 CenterForward -15.0 0.0 +# --------------------------------------------------------- diff --git a/keepaway/base/formation_dt/defense_formation.conf b/keepaway/base/formation_dt/defense_formation.conf new file mode 100755 index 00000000..cacfcc23 --- /dev/null +++ b/keepaway/base/formation_dt/defense_formation.conf @@ -0,0 +1,1512 @@ +Formation DelaunayTriangulation 2 +Begin Roles +1 Goalie 0 +2 CenterBack -1 +3 CenterBack 2 +4 SideBack -1 +5 SideBack 4 +6 DefensiveHalf 0 +7 OffensiveHalf -1 +8 OffensiveHalf 7 +9 SideForward -1 +10 SideForward 9 +11 CenterForward 0 +End Roles +Begin Samples 2 115 +----- 0 ----- +Ball 54.5 -36 +1 -50 0 +2 -0.72 -12 +3 -0.84 1.08 +4 4.9 -27.3 +5 10 8 +6 27.43 -16.5 +7 33.12 -27 +8 38.22 -3.5 +9 44.22 -30.85 +10 46 6.8 +11 46.28 -14 +----- 1 ----- +Ball 54.5 36 +1 -50 -0 +2 -0.84 -1.08 +3 -0.72 12 +4 10 -8 +5 4.9 27.3 +6 27.43 16.5 +7 38.22 3.5 +8 33.12 27 +9 46 -6.8 +10 44.22 30.85 +11 46.28 14 +----- 2 ----- +Ball 0 0 +1 -50 0 +2 -15.53 -5.42 +3 -15.53 5.42 +4 -11.56 -15.78 +5 -11.56 15.78 +6 -6.73 -1.87 +7 2.83 -10.81 +8 2.83 10.81 +9 9.3 -23.78 +10 9.3 23.78 +11 9.41 -3.12 +----- 3 ----- +Ball 54.5 0 +1 -50 0 +2 2.74 -6.07 +3 2.74 6.07 +4 7.21 -18.58 +5 7.21 18.58 +6 26.86 -3.2 +7 40.73 -3.77 +8 40.73 3.77 +9 48.97 -9.82 +10 48.97 9.82 +11 45.6 -1.65 +----- 4 ----- +Ball 36.57 -12.09 +1 -50 0 +2 -1.25 -9.96 +3 0.52 3.2 +4 5.09 -23.21 +5 7.36 15.67 +6 18.98 -7.3 +7 27.73 -14.57 +8 32.4 1.86 +9 39.17 -19.85 +10 42.41 10.78 +11 38.1 -8.45 +----- 5 ----- +Ball 36.57 12.09 +1 -50 -0 +2 0.52 -3.2 +3 -1.25 9.96 +4 7.36 -15.67 +5 5.09 23.21 +6 18.98 7.3 +7 32.4 -1.86 +8 27.73 14.57 +9 42.41 -10.78 +10 39.17 19.85 +11 38.1 8.45 +----- 6 ----- +Ball 48.51 -15.92 +1 -50 0 +2 0.51 -10.77 +3 3.07 3.38 +4 2.27 -23.46 +5 12.83 13.63 +6 25.2 -9.33 +7 34.83 -14.95 +8 39.34 -1.01 +9 45.03 -20.81 +10 46.88 6.76 +11 43.86 -8.86 +----- 7 ----- +Ball 48.51 15.92 +1 -50 -0 +2 3.07 -3.38 +3 0.51 10.77 +4 12.83 -13.63 +5 2.27 23.46 +6 25.2 9.33 +7 39.34 1.01 +8 34.83 14.95 +9 46.88 -6.76 +10 45.03 20.81 +11 43.86 8.86 +----- 8 ----- +Ball 42.76 0 +1 -50 0 +2 0.98 -5.97 +3 0.98 5.97 +4 6.85 -19.24 +5 6.85 19.24 +6 21.77 -2.76 +7 35.57 -5.84 +8 35.57 5.84 +9 44.37 -9.81 +10 44.37 9.81 +11 40.72 -2.36 +----- 9 ----- +Ball 48.66 -5.01 +1 -50 0 +2 1.54 -7.25 +3 2.33 5.02 +4 6.06 -20.44 +5 8.38 17.49 +6 24.7 -4.89 +7 37.7 -7.4 +8 39.2 2.37 +9 46.09 -15.6 +10 46.8 0.42 +11 43.57 -4.31 +----- 10 ----- +Ball 48.66 5.01 +1 -50 -0 +2 2.33 -5.02 +3 1.54 7.25 +4 8.38 -17.49 +5 6.06 20.44 +6 24.7 4.89 +7 39.2 -2.37 +8 37.7 7.4 +9 46.8 -0.42 +10 46.09 15.6 +11 43.57 4.31 +----- 11 ----- +Ball 50.57 -6.78 +1 -50 0 +2 1.66 -7.71 +3 2.75 4.77 +4 5.36 -20.8 +5 9.23 16.82 +6 25.62 -5.67 +7 38.2 -8.13 +8 40.06 1.43 +9 47 -17.67 +10 47.39 -0.36 +11 44.4 -4.93 +----- 12 ----- +Ball 50.57 6.78 +1 -50 -0 +2 2.75 -4.77 +3 1.66 7.71 +4 9.23 -16.82 +5 5.36 20.8 +6 25.62 5.67 +7 40.06 -1.43 +8 38.2 8.13 +9 47.39 0.36 +10 47 17.67 +11 44.4 4.93 +----- 13 ----- +Ball 52.49 -17.1 +1 -50 0 +2 0.95 -10.96 +3 3.74 3.5 +4 0.75 -23.35 +5 14.73 12.86 +6 27.02 -9.99 +7 36.71 -15.3 +8 40.72 -1.59 +9 47.62 -24.13 +10 47.64 7.59 +11 45.39 -8.87 +----- 14 ----- +Ball 52.49 17.1 +1 -50 -0 +2 3.74 -3.5 +3 0.95 10.96 +4 14.73 -12.86 +5 0.75 23.35 +6 27.02 9.99 +7 40.72 1.59 +8 36.71 15.3 +9 47.64 -7.59 +10 47.62 24.13 +11 45.39 8.87 +----- 15 ----- +Ball 52.49 -7.96 +1 -50 0 +2 1.82 -7.99 +3 3.1 4.66 +4 4.65 -20.95 +5 10.02 16.31 +6 26.49 -6.22 +7 38.78 -8.58 +8 40.75 0.8 +9 48.07 -19.82 +10 47.99 0.19 +11 45.16 -5.26 +----- 16 ----- +Ball 52.49 7.96 +1 -50 -0 +2 3.1 -4.66 +3 1.82 7.99 +4 10.02 -16.31 +5 4.65 20.95 +6 26.49 6.22 +7 40.75 -0.8 +8 38.78 8.58 +9 47.99 -0.19 +10 48.07 19.82 +11 45.16 5.26 +----- 17 ----- +Ball 49.25 -9.29 +1 -50 0 +2 1.25 -8.48 +3 2.73 4.33 +4 4.73 -21.62 +5 9.92 16.07 +6 25.18 -6.62 +7 37.01 -10.02 +8 39.69 0.74 +9 46.18 -17.93 +10 47.11 1.01 +11 43.96 -6.11 +----- 18 ----- +Ball 49.25 9.29 +1 -50 -0 +2 2.73 -4.33 +3 1.25 8.48 +4 9.92 -16.07 +5 4.73 21.62 +6 25.18 6.62 +7 39.69 -0.74 +8 37.01 10.02 +9 47.11 -1.01 +10 46.18 17.93 +11 43.96 6.11 +----- 19 ----- +Ball 46.74 0 +1 -50 0 +2 1.62 -6.08 +3 1.62 6.08 +4 7.1 -19.05 +5 7.1 19.05 +6 23.75 -0.9 +7 37.73 -4.99 +8 37.73 4.99 +9 45.7 -9.75 +10 45.7 9.75 +11 42.56 -2.13 +----- 20 ----- +Ball 42.61 -5.6 +1 -50 0 +2 0.5 -7.52 +3 1.36 4.72 +4 6.18 -20.97 +5 7.57 17.56 +6 21.91 -4.9 +7 34.21 -9.06 +8 36.27 3.21 +9 43.47 -15.1 +10 45.41 2.8 +11 40.91 -5.04 +----- 21 ----- +Ball 42.61 5.6 +1 -50 -0 +2 1.36 -4.72 +3 0.5 7.52 +4 7.57 -17.56 +5 6.18 20.97 +6 21.91 4.9 +7 36.27 -3.21 +8 34.21 9.06 +9 45.41 -2.8 +10 43.47 15.1 +11 40.91 5.04 +----- 22 ----- +Ball 45.86 -3.54 +1 -50 0 +2 1.23 -6.89 +3 1.78 5.23 +4 6.49 -20.18 +5 7.68 18.07 +6 23.37 -4.21 +7 36.61 -7.09 +8 37.8 3.47 +9 45.04 -13.82 +10 45.96 2.43 +11 42.32 -3.85 +----- 23 ----- +Ball 45.86 3.54 +1 -50 -0 +2 1.78 -5.23 +3 1.23 6.89 +4 7.68 -18.07 +5 6.49 20.18 +6 23.37 4.21 +7 37.8 -3.47 +8 36.61 7.09 +9 45.96 -2.43 +10 45.04 13.82 +11 42.32 3.85 +----- 24 ----- +Ball 46.89 -6.49 +1 -50 0 +2 1.14 -7.7 +3 2.17 4.7 +4 5.83 -20.99 +5 8.52 17.12 +6 23.98 -5.41 +7 36.48 -8.62 +8 38.57 2.07 +9 45.21 -15.64 +10 46.51 0.08 +11 42.89 -5.11 +----- 25 ----- +Ball 46.89 6.49 +1 -50 -0 +2 2.17 -4.7 +3 1.14 7.7 +4 8.52 -17.12 +5 5.83 20.99 +6 23.98 5.41 +7 38.57 -2.07 +8 36.48 8.62 +9 46.51 -0.08 +10 45.21 15.64 +11 42.89 5.11 +----- 26 ----- +Ball 38.63 0 +1 -50 0 +2 0.18 -5.93 +3 0.18 5.93 +4 6.41 -19.34 +5 6.41 19.34 +6 19.71 -2.62 +7 32.73 -6.84 +8 32.73 6.84 +9 42.2 -11.81 +10 42.2 11.81 +11 38.61 -2.57 +----- 27 ----- +Ball 39.22 -5.75 +1 -50 0 +2 -0.15 -7.62 +3 0.71 4.57 +4 6.1 -21.14 +5 6.93 17.6 +6 20.2 -4.84 +7 31.8 -9.98 +8 34.05 3.87 +9 41.58 -16.28 +10 44.01 6.35 +11 39.22 -5.35 +----- 28 ----- +Ball 39.22 5.75 +1 -50 -0 +2 0.71 -4.57 +3 -0.15 7.62 +4 6.93 -17.6 +5 6.1 21.14 +6 20.2 4.84 +7 34.05 -3.87 +8 31.8 9.98 +9 44.01 -6.35 +10 41.58 16.28 +11 39.22 5.35 +----- 29 ----- +Ball 30.37 -15.92 +1 -50 0 +2 -3.06 -11.84 +3 -0.92 2.05 +4 3.83 -24.4 +5 6 14.49 +6 15.44 -8.7 +7 21.07 -17.96 +8 27.44 1.55 +9 34.11 -24.3 +10 34.71 14.57 +11 34.7 -10.66 +----- 30 ----- +Ball 30.37 15.92 +1 -50 -0 +2 -0.92 -2.05 +3 -3.06 11.84 +4 6 -14.49 +5 3.83 24.4 +6 15.44 8.7 +7 27.44 -1.55 +8 21.07 17.96 +9 34.71 -14.57 +10 34.11 24.3 +11 34.7 10.66 +----- 31 ----- +Ball 0 -36 +1 -50 0 +2 -17.18 -19.96 +3 -16.68 -6.05 +4 -13.42 -30.3 +5 -7.16 6.63 +6 -8.38 -16.63 +7 -7.37 -25.18 +8 -4.81 -1.58 +9 13.45 -32.14 +10 10.92 18.27 +11 14.62 -20.6 +----- 32 ----- +Ball 0 36 +1 -50 -0 +2 -16.68 6.05 +3 -17.18 19.96 +4 -7.16 -6.63 +5 -13.42 30.3 +6 -8.38 16.63 +7 -4.81 1.58 +8 -7.37 25.18 +9 10.92 -18.27 +10 13.45 32.14 +11 14.62 20.6 +----- 33 ----- +Ball 44.53 -22.41 +1 -50 0 +2 -0.79 -13.56 +3 2.77 2.31 +4 0.88 -25.3 +5 14.3 11.33 +6 23.64 -11.92 +7 30.42 -20.33 +8 36.76 -1.94 +9 41.44 -22.71 +10 45.59 7.46 +11 42.36 -11.75 +----- 34 ----- +Ball 44.53 22.41 +1 -50 -0 +2 2.77 -2.31 +3 -0.79 13.56 +4 14.3 -11.33 +5 0.88 25.3 +6 23.64 11.92 +7 36.76 1.94 +8 30.42 20.33 +9 45.59 -7.46 +10 41.44 22.71 +11 42.36 11.75 +----- 35 ----- +Ball 44.09 -29.78 +1 -50 0 +2 -1.7 -16.43 +3 3.08 1.45 +4 2.15 -27.12 +5 15.32 8.96 +6 23.78 -15.03 +7 28.49 -25.4 +8 35.1 -2.94 +9 39.72 -25.13 +10 44.65 7.22 +11 42.31 -14.34 +----- 36 ----- +Ball 44.09 29.78 +1 -50 -0 +2 3.08 -1.45 +3 -1.7 16.43 +4 15.32 -8.96 +5 2.15 27.12 +6 23.78 15.03 +7 35.1 2.94 +8 28.49 25.4 +9 44.65 -7.22 +10 39.72 25.13 +11 42.31 14.34 +----- 37 ----- +Ball 29.19 -34.36 +1 -50 0 +2 -5.36 -18.8 +3 -0.74 -0.94 +4 2.3 -27.83 +5 9.4 8.12 +6 15.16 -16.5 +7 19.03 -27.25 +8 26.04 -3.11 +9 36.79 -29.36 +10 29.88 13.71 +11 34.81 -17.65 +----- 38 ----- +Ball 29.19 34.36 +1 -50 -0 +2 -0.74 0.94 +3 -5.36 18.8 +4 9.4 -8.12 +5 2.3 27.83 +6 15.16 16.5 +7 26.04 3.11 +8 19.03 27.25 +9 29.88 -13.71 +10 36.79 29.36 +11 34.81 17.65 +----- 39 ----- +Ball 33.03 -31.26 +1 -50 0 +2 -4.18 -17.73 +3 0.28 -0.08 +4 2.01 -27.43 +5 11.01 8.83 +6 17.29 -15.35 +7 20.92 -26.07 +8 28.24 -2.72 +9 38.57 -27.83 +10 34.68 12.1 +11 36.65 -16.3 +----- 40 ----- +Ball 33.03 31.26 +1 -50 -0 +2 0.28 0.08 +3 -4.18 17.73 +4 11.01 -8.83 +5 2.01 27.43 +6 17.29 15.35 +7 28.24 2.72 +8 20.92 26.07 +9 34.68 -12.1 +10 38.57 27.83 +11 36.65 16.3 +----- 41 ----- +Ball 23 -5.16 +1 -50 0 +2 -4.35 -7.68 +3 -3.76 4.1 +4 2.99 -20.69 +5 2.22 17.45 +6 10.52 -4.14 +7 15.33 -13.53 +8 18.29 8.05 +9 23.07 -25.95 +10 24.74 20.06 +11 29.04 -6 +----- 42 ----- +Ball 23 5.16 +1 -50 -0 +2 -3.76 -4.1 +3 -4.35 7.68 +4 2.22 -17.45 +5 2.99 20.69 +6 10.52 4.14 +7 18.29 -8.05 +8 15.33 13.53 +9 24.74 -20.06 +10 23.07 25.95 +11 29.04 6 +----- 43 ----- +Ball 28.16 0 +1 -50 0 +2 -2.39 -5.82 +3 -2.39 5.82 +4 4.32 -19.24 +5 4.32 19.24 +6 13.8 -2.33 +7 22.61 -9.6 +8 22.61 9.6 +9 29.71 -21.47 +10 29.71 21.47 +11 32.25 -1 +----- 44 ----- +Ball 34.65 -5.75 +1 -50 0 +2 -1.13 -7.7 +3 -0.31 4.4 +4 5.74 -21.22 +5 5.89 17.64 +6 17.72 -4.69 +7 27.93 -11.15 +8 30.34 4.95 +9 37.52 -18.78 +10 40.2 11.53 +11 36.7 -5.66 +----- 45 ----- +Ball 34.65 5.75 +1 -50 -0 +2 -0.31 -4.4 +3 -1.13 7.7 +4 5.89 -17.64 +5 5.74 21.22 +6 17.72 4.69 +7 30.34 -4.95 +8 27.93 11.15 +9 40.2 -11.53 +10 37.52 18.78 +11 36.7 5.66 +----- 46 ----- +Ball 19.91 -28.6 +1 -50 0 +2 -7.44 -17.45 +3 -4.43 -1.6 +4 -1.88 -26.53 +5 4.35 9.94 +6 8.44 -13.83 +7 11.6 -24.14 +8 21.83 -1.79 +9 28.02 -30.94 +10 24.91 17.54 +11 28.57 -16.62 +----- 47 ----- +Ball 19.91 28.6 +1 -50 -0 +2 -4.43 1.6 +3 -7.44 17.45 +4 4.35 -9.94 +5 -1.88 26.53 +6 8.44 13.83 +7 21.83 1.79 +8 11.6 24.14 +9 24.91 -17.54 +10 28.02 30.94 +11 28.57 16.62 +----- 48 ----- +Ball 14.3 -11.06 +1 -50 0 +2 -8.06 -10.45 +3 -7.15 1.87 +4 -1.04 -21.99 +5 -2.42 15.19 +6 4.24 -6.29 +7 5.05 -17.13 +8 13.68 6.13 +9 17.23 -26.9 +10 18.09 19.6 +11 22.95 -9.41 +----- 49 ----- +Ball 14.3 11.06 +1 -50 -0 +2 -7.15 -1.87 +3 -8.06 10.45 +4 -2.42 -15.19 +5 -1.04 21.99 +6 4.24 6.29 +7 13.68 -6.13 +8 5.05 17.13 +9 18.09 -19.6 +10 17.23 26.9 +11 22.95 9.41 +----- 50 ----- +Ball 11.35 -25.07 +1 -50 0 +2 -10.43 -16.57 +3 -8.68 -1.89 +4 -5.48 -25.58 +5 -2.46 11.03 +6 1.71 -12.12 +7 3.25 -22.2 +8 16.29 -0.28 +9 18.9 -29.64 +10 17.57 18.46 +11 22.04 -15.92 +----- 51 ----- +Ball 11.35 25.07 +1 -50 -0 +2 -8.68 1.89 +3 -10.43 16.57 +4 -2.46 -11.03 +5 -5.48 25.58 +6 1.71 12.12 +7 16.29 0.28 +8 3.25 22.2 +9 17.57 -18.46 +10 18.9 29.64 +11 22.04 15.92 +----- 52 ----- +Ball 9.58 0 +1 -50 0 +2 -9.77 -5.58 +3 -9.77 5.58 +4 -4.36 -17.44 +5 -4.36 17.44 +6 0.91 -0.62 +7 5.26 -12.1 +8 5.26 12.1 +9 14.64 -25.04 +10 14.64 25.04 +11 17.81 -1.03 +----- 53 ----- +Ball 18.58 0 +1 -50 0 +2 -5.66 -5.71 +3 -5.66 5.71 +4 0.77 -18.6 +5 0.77 18.6 +6 7.52 -2.11 +7 11.95 -11.59 +8 11.95 11.59 +9 20.57 -25.76 +10 20.57 25.76 +11 25.23 -0.34 +----- 54 ----- +Ball 3.83 -20.2 +1 -50 0 +2 -13.84 -14.96 +3 -13.2 -1.74 +4 -9.32 -24.18 +5 -9.54 11.72 +6 -4.47 -9.91 +7 -3.05 -19.66 +8 10.11 2 +9 11.32 -27.46 +10 11.22 18.5 +11 15.55 -14.21 +----- 55 ----- +Ball 3.83 20.2 +1 -50 -0 +2 -13.2 1.74 +3 -13.84 14.96 +4 -9.54 -11.72 +5 -9.32 24.18 +6 -4.47 9.91 +7 10.11 -2 +8 -3.05 19.66 +9 11.22 -18.5 +10 11.32 27.46 +11 15.55 14.21 +----- 56 ----- +Ball 6.19 -10.32 +1 -50 0 +2 -11.99 -10.37 +3 -11.54 1.49 +4 -6.34 -20.75 +5 -7.77 14.2 +6 -2.15 -5.86 +7 -0.16 -16.34 +8 9.32 6.62 +9 11.66 -29.11 +10 12.63 18.96 +11 16.23 -9.25 +----- 57 ----- +Ball 6.19 10.32 +1 -50 -0 +2 -11.54 -1.49 +3 -11.99 10.37 +4 -7.77 -14.2 +5 -6.34 20.75 +6 -2.15 5.86 +7 9.32 -6.62 +8 -0.16 16.34 +9 12.63 -18.96 +10 11.66 29.11 +11 16.23 9.25 +----- 58 ----- +Ball 10.47 -29.78 +1 -50 0 +2 -11.25 -18.18 +3 -9.28 -3 +4 -6.48 -26.52 +5 -1.47 9.34 +6 0.92 -14.14 +7 3.65 -23.76 +8 14.34 -1.54 +9 19.43 -31.04 +10 18.15 18.08 +11 21.84 -17.79 +----- 59 ----- +Ball 10.47 29.78 +1 -50 -0 +2 -9.28 3 +3 -11.25 18.18 +4 -1.47 -9.34 +5 -6.48 26.52 +6 0.92 14.14 +7 14.34 1.54 +8 3.65 23.76 +9 18.15 -18.08 +10 19.43 31.04 +11 21.84 17.79 +----- 60 ----- +Ball 13.27 -33.18 +1 -50 0 +2 -10.4 -19.06 +3 -7.76 -3.2 +4 -4.63 -27.14 +5 1.37 8.33 +6 3.2 -15.64 +7 7.31 -25.26 +8 15.53 -2.38 +9 22.52 -31.88 +10 21.76 17.76 +11 24.21 -18.79 +----- 61 ----- +Ball 13.27 33.18 +1 -50 -0 +2 -7.76 3.2 +3 -10.4 19.06 +4 1.37 -8.33 +5 -4.63 27.14 +6 3.2 15.64 +7 15.53 2.38 +8 7.31 25.26 +9 21.76 -17.76 +10 22.52 31.88 +11 24.21 18.79 +----- 62 ----- +Ball -16.96 -30.52 +1 -50 0 +2 -23.33 -18.45 +3 -23.18 -4.34 +4 -23.94 -27.93 +5 -22.14 9.35 +6 -19.24 -13.98 +7 -16.33 -22.81 +8 -14.1 -0.07 +9 0.44 -30.41 +10 -4.28 17.04 +11 0.06 -9.81 +----- 63 ----- +Ball -16.96 30.52 +1 -50 -0 +2 -23.18 4.34 +3 -23.33 18.45 +4 -22.14 -9.35 +5 -23.94 27.93 +6 -19.24 13.98 +7 -14.1 0.07 +8 -16.33 22.81 +9 -4.28 -17.04 +10 0.44 30.41 +11 0.06 9.81 +----- 64 ----- +Ball -4.28 -16.81 +1 -50 0 +2 -12.71 -15.79 +3 -14.88 -3.68 +4 -13.76 -23.07 +5 -12.63 11.13 +6 -8.8 -11.13 +7 -3.79 -19.5 +8 -3.98 5.9 +9 6.03 -29.81 +10 2.6 22.3 +11 5.59 -8.48 +----- 65 ----- +Ball -4.28 16.81 +1 -50 -0 +2 -14.88 3.68 +3 -12.71 15.79 +4 -12.63 -11.13 +5 -13.76 23.07 +6 -8.8 11.13 +7 -3.98 -5.9 +8 -3.79 19.5 +9 2.6 -22.3 +10 6.03 29.81 +11 5.59 8.48 +----- 66 ----- +Ball -7.08 -27.57 +1 -50 0 +2 -15.15 -19.34 +3 -16.57 -4.96 +4 -15.35 -27.21 +5 -13.48 10.45 +6 -10.91 -14.26 +7 -6.54 -22.94 +8 -6.22 1.56 +9 6.84 -30.97 +10 1.32 18.51 +11 6.91 -10.52 +----- 67 ----- +Ball -7.08 27.57 +1 -50 -0 +2 -16.57 4.96 +3 -15.15 19.34 +4 -13.48 -10.45 +5 -15.35 27.21 +6 -10.91 14.26 +7 -6.22 -1.56 +8 -6.54 22.94 +9 1.32 -18.51 +10 6.84 30.97 +11 6.91 10.52 +----- 68 ----- +Ball -7.96 -31.41 +1 -50 0 +2 -15.89 -20.09 +3 -17.14 -5.03 +4 -15.83 -28.57 +5 -13.66 11 +6 -11.44 -14.98 +7 -7.45 -24.07 +8 -6.86 0.18 +9 7.39 -31.3 +10 1.09 17.04 +11 8.36 -10.67 +----- 69 ----- +Ball -7.96 31.41 +1 -50 -0 +2 -17.14 5.03 +3 -15.89 20.09 +4 -13.66 -11 +5 -15.83 28.57 +6 -11.44 14.98 +7 -6.86 -0.18 +8 -7.45 24.07 +9 1.09 -17.04 +10 7.39 31.3 +11 8.36 10.67 +----- 70 ----- +Ball -23.89 -34.21 +1 -50 0 +2 -31.05 -17.31 +3 -29.37 -4.33 +4 -29.93 -27.83 +5 -28.55 8.6 +6 -24.69 -13.7 +7 -22.94 -23.29 +8 -19.22 -1.39 +9 -3.39 -30.07 +10 -7.83 15.33 +11 -3.41 -8.93 +----- 71 ----- +Ball -23.89 34.21 +1 -50 -0 +2 -29.37 4.33 +3 -31.05 17.31 +4 -28.55 -8.6 +5 -29.93 27.83 +6 -24.69 13.7 +7 -19.22 1.39 +8 -22.94 23.29 +9 -7.83 -15.33 +10 -3.39 30.07 +11 -3.41 8.93 +----- 72 ----- +Ball -54.5 0 +1 -50 0 +2 -47.53 -3.17 +3 -47.53 3.17 +4 -49.99 -6.88 +5 -49.99 6.88 +6 -44.06 1.02 +7 -41.64 -8.61 +8 -41.64 8.61 +9 -23.83 -22.2 +10 -23.83 22.2 +11 -30.03 4.57 +----- 73 ----- +Ball -19.61 -5.46 +1 -50 0 +2 -26.74 -7.22 +3 -27.37 1.07 +4 -27.57 -15.59 +5 -27.52 9.88 +6 -23.16 -4.67 +7 -18.04 -13.11 +8 -17.58 8.75 +9 -6.06 -26.78 +10 -6.72 24.53 +11 -8.99 -3.01 +----- 74 ----- +Ball -19.61 5.46 +1 -50 -0 +2 -27.37 -1.07 +3 -26.74 7.22 +4 -27.52 -9.88 +5 -27.57 15.59 +6 -23.16 4.67 +7 -17.58 -8.75 +8 -18.04 13.11 +9 -6.72 -24.53 +10 -6.06 26.78 +11 -8.99 3.01 +----- 75 ----- +Ball -7.96 -7.37 +1 -50 0 +2 -15.64 -9.85 +3 -17.18 0.07 +4 -16.91 -19.09 +5 -16.52 12.59 +6 -12.21 -6.37 +7 -7.34 -15.32 +8 -7.25 9.27 +9 1.69 -28.01 +10 0.38 24.81 +11 0.76 -4.5 +----- 76 ----- +Ball -7.96 7.37 +1 -50 -0 +2 -17.18 -0.07 +3 -15.64 9.85 +4 -16.52 -12.59 +5 -16.91 19.09 +6 -12.21 6.37 +7 -7.25 -9.27 +8 -7.34 15.32 +9 0.38 -24.81 +10 1.69 28.01 +11 0.76 4.5 +----- 77 ----- +Ball -5.31 0 +1 -50 0 +2 -14.82 -4.5 +3 -14.82 4.5 +4 -14.59 -16.26 +5 -14.59 16.26 +6 -9.26 -1.26 +7 -4.86 -12.66 +8 -4.86 12.66 +9 2.61 -26.72 +10 2.61 26.72 +11 2.2 -0.09 +----- 78 ----- +Ball -2.06 -11.35 +1 -50 0 +2 -11.37 -12.91 +3 -13.56 -2.16 +4 -12.2 -21.01 +5 -11.39 13.02 +6 -6.73 -8.68 +7 -1.72 -17.72 +8 -2.03 8.39 +9 6.48 -29.12 +10 4.06 24.04 +11 6.06 -6.47 +----- 79 ----- +Ball -2.06 11.35 +1 -50 -0 +2 -13.56 2.16 +3 -11.37 12.91 +4 -11.39 -13.02 +5 -12.2 21.01 +6 -6.73 8.68 +7 -2.03 -8.39 +8 -1.72 17.72 +9 4.06 -24.04 +10 6.48 29.12 +11 6.06 6.47 +----- 80 ----- +Ball -3.39 -5.9 +1 -50 0 +2 -12.7 -8.99 +3 -14.14 0.58 +4 -13.2 -18.92 +5 -12.79 14.39 +6 -7.76 -5.48 +7 -3.04 -15.36 +8 -3.16 10.48 +9 4.62 -28.05 +10 3.43 25.44 +11 4.7 -3.75 +----- 81 ----- +Ball -3.39 5.9 +1 -50 -0 +2 -14.14 -0.58 +3 -12.7 8.99 +4 -12.79 -14.39 +5 -13.2 18.92 +6 -7.76 5.48 +7 -3.16 -10.48 +8 -3.04 15.36 +9 3.43 -25.44 +10 4.62 28.05 +11 4.7 3.75 +----- 82 ----- +Ball -9.44 -24.77 +1 -50 0 +2 -16.56 -18.28 +3 -17.92 -4.5 +4 -17.58 -26.25 +5 -15.92 9.67 +6 -13.12 -13.42 +7 -8.86 -21.71 +8 -8.23 2.36 +9 4.27 -30.45 +10 -0.29 19.43 +11 3.59 -10.02 +----- 83 ----- +Ball -9.44 24.77 +1 -50 -0 +2 -17.92 4.5 +3 -16.56 18.28 +4 -15.92 -9.67 +5 -17.58 26.25 +6 -13.12 13.42 +7 -8.23 -2.36 +8 -8.86 21.71 +9 -0.29 -19.43 +10 4.27 30.45 +11 3.59 10.02 +----- 84 ----- +Ball -12.39 -12.39 +1 -50 0 +2 -18.61 -12.64 +3 -20.37 -1.72 +4 -20.71 -20.92 +5 -20.15 9.72 +6 -16.37 -8.83 +7 -11.58 -16.74 +8 -11.06 6.74 +9 -0.53 -28.52 +10 -2.47 23.22 +11 -2.1 -6.61 +----- 85 ----- +Ball -12.39 12.39 +1 -50 -0 +2 -20.37 1.72 +3 -18.61 12.64 +4 -20.15 -9.72 +5 -20.71 20.92 +6 -16.37 8.83 +7 -11.06 -6.74 +8 -11.58 16.74 +9 -2.47 -23.22 +10 -0.53 28.52 +11 -2.1 6.61 +----- 86 ----- +Ball -16.37 -15.78 +1 -50 0 +2 -22.13 -13.78 +3 -23.54 -2.52 +4 -24.22 -21.95 +5 -23.62 8.03 +6 -19.87 -9.96 +7 -15.41 -17.57 +8 -14.35 5.07 +9 -2.6 -28.7 +10 -4.82 22.01 +11 -4.48 -7.55 +----- 87 ----- +Ball -16.37 15.78 +1 -50 -0 +2 -23.54 2.52 +3 -22.13 13.78 +4 -23.62 -8.03 +5 -24.22 21.95 +6 -19.87 9.96 +7 -14.35 -5.07 +8 -15.41 17.57 +9 -4.82 -22.01 +10 -2.6 28.7 +11 -4.48 7.55 +----- 88 ----- +Ball -19.91 -18.28 +1 -50 0 +2 -25.82 -14.13 +3 -26.7 -3.02 +4 -27.37 -22.39 +5 -26.83 6.95 +6 -22.83 -10.5 +7 -18.77 -18.12 +8 -17.16 3.88 +9 -4.47 -28.73 +10 -6.79 21.05 +11 -6.47 -7.96 +----- 89 ----- +Ball -19.91 18.28 +1 -50 -0 +2 -26.7 3.02 +3 -25.82 14.13 +4 -26.83 -6.95 +5 -27.37 22.39 +6 -22.83 10.5 +7 -17.16 -3.88 +8 -18.77 18.12 +9 -6.79 -21.05 +10 -4.47 28.73 +11 -6.47 7.96 +----- 90 ----- +Ball -32.73 -29.19 +1 -50 0 +2 -39.69 -13.44 +3 -38.23 -4.58 +4 -37.42 -22.97 +5 -37.47 4.45 +6 -32.04 -11.42 +7 -30.22 -20.74 +8 -26.1 -0.09 +9 -10.38 -28.63 +10 -13.04 16.63 +11 -12 -7.92 +----- 91 ----- +Ball -32.73 29.19 +1 -50 -0 +2 -38.23 4.58 +3 -39.69 13.44 +4 -37.47 -4.45 +5 -37.42 22.97 +6 -32.04 11.42 +7 -26.1 0.09 +8 -30.22 20.74 +9 -13.04 -16.63 +10 -10.38 28.63 +11 -12 7.92 +----- 92 ----- +Ball -24.03 -17.55 +1 -50 0 +2 -30.55 -12.75 +3 -31.07 -2.95 +4 -31.18 -20.78 +5 -30.99 5.87 +6 -26.38 -9.76 +7 -22.45 -17.38 +8 -20.54 3.87 +9 -7.22 -28.24 +10 -9.15 21.01 +11 -9.46 -7.39 +----- 93 ----- +Ball -24.03 17.55 +1 -50 -0 +2 -31.07 2.95 +3 -30.55 12.75 +4 -30.99 -5.87 +5 -31.18 20.78 +6 -26.38 9.76 +7 -20.54 -3.87 +8 -22.45 17.38 +9 -9.15 -21.01 +10 -7.22 28.24 +11 -9.46 7.39 +----- 94 ----- +Ball -31.26 0 +1 -50 0 +2 -39.65 -2.16 +3 -39.65 2.16 +4 -38.28 -8.03 +5 -38.28 8.03 +6 -32.56 -0.43 +7 -27.38 -9.85 +8 -27.38 9.85 +9 -13.07 -24.74 +10 -13.07 24.74 +11 -18.33 1.17 +----- 95 ----- +Ball -29.34 -15.33 +1 -50 0 +2 -36.56 -10.26 +3 -36.79 -2.72 +4 -35.95 -17.42 +5 -36.13 4.57 +6 -30.72 -8.28 +7 -26.82 -15.97 +8 -24.81 4.35 +9 -10.73 -27.4 +10 -12.11 21.27 +11 -13.3 -6.21 +----- 96 ----- +Ball -29.34 15.33 +1 -50 -0 +2 -36.79 2.72 +3 -36.56 10.26 +4 -36.13 -4.57 +5 -35.95 17.42 +6 -30.72 8.28 +7 -24.81 -4.35 +8 -26.82 15.97 +9 -12.11 -21.27 +10 -10.73 27.4 +11 -13.3 6.21 +----- 97 ----- +Ball -37.01 -33.03 +1 -50 0 +2 -42.77 -12.81 +3 -40.82 -4.62 +4 -40.07 -23.04 +5 -40.14 3.64 +6 -34.61 -11.31 +7 -33.61 -21.66 +8 -28.64 -1.11 +9 -12.13 -28.47 +10 -14.85 14.96 +11 -13.43 -7.26 +----- 98 ----- +Ball -37.01 33.03 +1 -50 -0 +2 -40.82 4.62 +3 -42.77 12.81 +4 -40.14 -3.64 +5 -40.07 23.04 +6 -34.61 11.31 +7 -28.64 1.11 +8 -33.61 21.66 +9 -14.85 -14.96 +10 -12.13 28.47 +11 -13.43 7.26 +----- 99 ----- +Ball -54.5 -36 +1 -50 0 +2 -46.24 -10.29 +3 -44.48 -1.69 +4 -48.45 -21.85 +5 -48.06 2.31 +6 -43.6 -8.28 +7 -43.89 -21.18 +8 -38.36 -1.28 +9 -20.87 -26.29 +10 -22.49 12.44 +11 -22.8 -4.37 +----- 100 ----- +Ball -54.5 36 +1 -50 -0 +2 -44.48 1.69 +3 -46.24 10.29 +4 -48.06 -2.31 +5 -48.45 21.85 +6 -43.6 8.28 +7 -38.36 1.28 +8 -43.89 21.18 +9 -22.49 -12.44 +10 -20.87 26.29 +11 -22.8 4.37 +----- 101 ----- +Ball -48.66 -22.71 +1 -50 0 +2 -46.06 -8.98 +3 -45.54 -2.25 +4 -47.21 -15.63 +5 -47.31 2.84 +6 -41.76 -7.28 +7 -40.32 -17.03 +8 -36.69 1.74 +9 -19.94 -26.01 +10 -21.01 17.44 +11 -22.29 -5.05 +----- 102 ----- +Ball -48.66 22.71 +1 -50 -0 +2 -45.54 2.25 +3 -46.06 8.98 +4 -47.31 -2.84 +5 -47.21 15.63 +6 -41.76 7.28 +7 -36.69 -1.74 +8 -40.32 17.03 +9 -21.01 -17.44 +10 -19.94 26.01 +11 -22.29 5.05 +----- 103 ----- +Ball -39.52 -28.16 +1 -50 0 +2 -43.82 -11.32 +3 -42.6 -4.34 +4 -42.05 -20.13 +5 -42.42 2.74 +6 -36.63 -10.01 +7 -35.11 -19.75 +8 -30.74 0.19 +9 -14.51 -27.72 +10 -16.49 16.49 +11 -16.44 -6.95 +----- 104 ----- +Ball -39.52 28.16 +1 -50 -0 +2 -42.6 4.34 +3 -43.82 11.32 +4 -42.42 -2.74 +5 -42.05 20.13 +6 -36.63 10.01 +7 -30.74 -0.19 +8 -35.11 19.75 +9 -16.49 -16.49 +10 -14.51 27.72 +11 -16.44 6.95 +----- 105 ----- +Ball -39.22 -22.12 +1 -50 0 +2 -43.64 -10.02 +3 -43.11 -3.81 +4 -42.46 -17.18 +5 -42.88 2.69 +6 -36.9 -8.77 +7 -34.57 -17.6 +8 -31.14 1.89 +9 -15.29 -27.15 +10 -16.77 18.47 +11 -17.55 -6.43 +----- 106 ----- +Ball -39.22 22.12 +1 -50 -0 +2 -43.11 3.81 +3 -43.64 10.02 +4 -42.88 -2.69 +5 -42.46 17.18 +6 -36.9 8.77 +7 -31.14 -1.89 +8 -34.57 17.6 +9 -16.77 -18.47 +10 -15.29 27.15 +11 -17.55 6.43 +----- 107 ----- +Ball -41.58 -7.22 +1 -50 0 +2 -45.59 -4.05 +3 -45.51 -0.6 +4 -44.96 -8.55 +5 -45.08 3.99 +6 -38.84 -3.44 +7 -35.13 -11.83 +8 -33.99 6.61 +9 -17.94 -24.94 +10 -18.33 22.22 +11 -21.98 -1.47 +----- 108 ----- +Ball -41.58 7.22 +1 -50 -0 +2 -45.51 0.6 +3 -45.59 4.05 +4 -45.08 -3.99 +5 -44.96 8.55 +6 -38.84 3.44 +7 -33.99 -6.61 +8 -35.13 11.83 +9 -18.33 -22.22 +10 -17.94 24.94 +11 -21.98 1.47 +----- 109 ----- +Ball -34.06 -7.37 +1 -50 0 +2 -41.55 -5.15 +3 -41.6 -0.8 +4 -40.2 -10.73 +5 -40.73 4.73 +6 -34.41 -4.35 +7 -29.99 -12.46 +8 -28.91 6.92 +9 -14.14 -25.77 +10 -14.67 22.91 +11 -17.91 -2.58 +----- 110 ----- +Ball -34.06 7.37 +1 -50 -0 +2 -41.6 0.8 +3 -41.55 5.15 +4 -40.73 -4.73 +5 -40.2 10.73 +6 -34.41 4.35 +7 -28.91 -6.92 +8 -29.99 12.46 +9 -14.67 -22.91 +10 -14.14 25.77 +11 -17.91 2.58 +----- 111 ----- +Ball -48.22 -9.88 +1 -50 0 +2 -46.9 -4.98 +3 -46.74 -0.12 +4 -47.87 -9.24 +5 -47.87 4.14 +6 -41.91 -3.75 +7 -39.23 -12.38 +8 -37.64 5.54 +9 -20.84 -24.54 +10 -21.27 20.92 +11 -24.49 -1.57 +----- 112 ----- +Ball -48.22 9.88 +1 -50 -0 +2 -46.74 0.12 +3 -46.9 4.98 +4 -47.87 -4.14 +5 -47.87 9.24 +6 -41.91 3.75 +7 -37.64 -5.54 +8 -39.23 12.38 +9 -21.27 -20.92 +10 -20.84 24.54 +11 -24.49 1.57 +----- 113 ----- +Ball 15.33 -21.38 +1 -50 0 +2 -8.43 -14.97 +3 -6.57 -0.58 +4 -2.37 -25.05 +5 -0.76 12.47 +6 4.93 -10.62 +7 4.93 -21.15 +8 18.46 0.94 +9 21.33 -29.66 +10 20.06 18.78 +11 24.72 -14.15 +----- 114 ----- +Ball 15.33 21.38 +1 -50 -0 +2 -6.57 0.58 +3 -8.43 14.97 +4 -0.76 -12.47 +5 -2.37 25.05 +6 4.93 10.62 +7 18.46 -0.94 +8 4.93 21.15 +9 20.06 -18.78 +10 21.33 29.66 +11 24.72 14.15 +End Samples +End diff --git a/keepaway/base/formation_dt/goalie_kick_opp_formation.conf b/keepaway/base/formation_dt/goalie_kick_opp_formation.conf new file mode 100755 index 00000000..59602f65 --- /dev/null +++ b/keepaway/base/formation_dt/goalie_kick_opp_formation.conf @@ -0,0 +1,15 @@ +Formation Static +# --------------------------------------------------------- +# for opponent goal kick + 1 Goalie -49.0 0.0 + 2 CenterBack 0.0 -5.0 + 3 CenterBack 0.0 5.0 + 4 SideBack 0.0 -12.0 + 5 SideBack 0.0 12.0 + 6 DefensiveHalf 10.0 0.0 + 7 OffensiveHalf 15.0 -12.0 + 8 OffensiveHalf 15.0 12.0 + 9 SideForward 31.0 -17.5 +10 SideForward 31.0 17.5 +11 CenterForward 31.0 0.0 +# --------------------------------------------------------- diff --git a/keepaway/base/formation_dt/goalie_kick_our_formation.conf b/keepaway/base/formation_dt/goalie_kick_our_formation.conf new file mode 100755 index 00000000..5bfc5db8 --- /dev/null +++ b/keepaway/base/formation_dt/goalie_kick_our_formation.conf @@ -0,0 +1,15 @@ +Formation Static +# --------------------------------------------------------- +# for our goalie catch + 1 Goalie -49.0 0.0 + 2 CenterBack -33.5 -4.0 + 3 CenterBack -33.5 4.0 + 4 SideBack -33.0 -24.0 + 5 SideBack -33.0 24.0 + 6 DefensiveHalf -20.0 0.0 + 7 OffensiveHalf -20.5 -13.0 + 8 OffensiveHalf -20.5 13.0 + 9 SideForward -10.0 -27.0 +10 SideForward -10.0 27.0 +11 CenterForward -10.0 0.0 +# --------------------------------------------------------- diff --git a/keepaway/base/formation_dt/kickin_our_formation.conf b/keepaway/base/formation_dt/kickin_our_formation.conf new file mode 100755 index 00000000..78a12b01 --- /dev/null +++ b/keepaway/base/formation_dt/kickin_our_formation.conf @@ -0,0 +1,290 @@ +Formation DelaunayTriangulation 2 +Begin Roles +1 Goalie 0 +2 CenterBack -1 +3 CenterBack 2 +4 SideBack -1 +5 SideBack 4 +6 DefensiveHalf 0 +7 OffensiveHalf -1 +8 OffensiveHalf 7 +9 SideForward -1 +10 SideForward 9 +11 CenterForward 0 +End Roles +Begin Samples 2 21 +----- 0 ----- +Ball 54 0 +1 -50 0 +2 0 -9 +3 0 9 +4 7 -19 +5 7 19 +6 21 0 +7 35 -6 +8 35 6 +9 46 -9.5 +10 46 9.5 +11 46 0 +----- 1 ----- +Ball -54 0 +1 -50 -0 +2 -47 -2.5 +3 -47 2.5 +4 -47 -7 +5 -47 7 +6 -43 0 +7 -35 -13 +8 -35 13 +9 -22 -28 +10 -22 28 +11 -18.49 0 +----- 2 ----- +Ball 0 0 +1 -50 0 +2 -15.06 -4.84 +3 -15.18 3.68 +4 -12.58 -14.88 +5 -13.39 14.07 +6 -5.61 0 +7 0.11 -11.99 +8 0.11 11.99 +9 10.37 -23.99 +10 10.84 23.99 +11 10.84 0 +----- 3 ----- +Ball -54 -35 +1 -50 0 +2 -47.35 -11.81 +3 -46.51 -4.65 +4 -47.81 -26.33 +5 -45.56 4.77 +6 -41.23 -11.92 +7 -37.38 -21.36 +8 -27.94 1.74 +9 -22.23 -31.17 +10 -17.01 19.99 +11 -17.51 -11.55 +----- 4 ----- +Ball -54 35 +1 -50 -0 +2 -46.51 4.65 +3 -47.35 11.81 +4 -45.56 -4.77 +5 -47.81 26.33 +6 -41.23 11.92 +7 -27.94 -1.74 +8 -37.38 21.36 +9 -17.01 -19.99 +10 -22.23 31.17 +11 -17.51 11.55 +----- 5 ----- +Ball -36.02 -35 +1 -50 -0.01 +2 -39.12 -16.02 +3 -38.87 -6.58 +4 -36.39 -27.94 +5 -36.76 3.85 +6 -28.32 -15.28 +7 -22.23 -24.59 +8 -20.16 0.6 +9 -10.43 -32.54 +10 -7.44 19.44 +11 -7.2 -14.16 +----- 6 ----- +Ball -36.02 35 +1 -50 0.01 +2 -38.87 6.58 +3 -39.12 16.02 +4 -36.76 -3.85 +5 -36.39 27.94 +6 -28.32 15.28 +7 -20.16 -0.6 +8 -22.23 24.59 +9 -7.44 -19.44 +10 -10.43 32.54 +11 -7.2 14.16 +----- 7 ----- +Ball -12 -35 +1 -50 0 +2 -18.5 -21.61 +3 -18.5 -8.94 +4 -12.42 -34.65 +5 -18.38 4.72 +6 -9.07 -14.9 +7 -0.5 -22.48 +8 -5.96 0.12 +9 11.67 -32.29 +10 10.8 14.03 +11 8.2 -15.15 +----- 8 ----- +Ball -12 35 +1 -50 -0 +2 -18.5 8.94 +3 -18.5 21.61 +4 -18.38 -4.72 +5 -12.42 34.65 +6 -9.07 14.9 +7 -5.96 -0.12 +8 -0.5 22.48 +9 10.8 -14.03 +10 11.67 32.29 +11 8.2 15.15 +----- 9 ----- +Ball 38.13 -35 +1 -50 0 +2 -0.14 -16.53 +3 6.25 -1.8 +4 7.93 -28 +5 17.31 8.77 +6 24.88 -17.67 +7 36.3 -31.49 +8 32.09 -0.36 +9 46.75 -24.64 +10 44.23 -0.72 +11 44.59 -13.82 +----- 10 ----- +Ball 38.13 35 +1 -50 -0 +2 6.25 1.8 +3 -0.14 16.53 +4 17.31 -8.77 +5 7.93 28 +6 24.88 17.67 +7 32.09 0.36 +8 36.3 31.49 +9 44.23 0.72 +10 46.75 24.64 +11 44.59 13.82 +----- 11 ----- +Ball 35 -35 +1 -50 0 +2 1.68 -14.54 +3 6.49 -0.12 +4 6.37 -27.76 +5 15.86 8.65 +6 22.73 -17.39 +7 33.41 -32.69 +8 29.81 0 +9 43.03 -29.81 +10 41.7 -1.08 +11 42.31 -15.38 +----- 12 ----- +Ball 35 35 +1 -50 -0 +2 6.49 0.12 +3 1.68 14.54 +4 15.86 -8.65 +5 6.37 27.76 +6 22.73 17.39 +7 29.81 -0 +8 33.41 32.69 +9 41.7 1.08 +10 43.03 29.81 +11 42.31 15.38 +----- 13 ----- +Ball 24.88 -35 +1 -50 0 +2 -0.84 -21.03 +3 2.88 -5.53 +4 20.67 -32.93 +5 11.42 7.69 +6 14.54 -13.46 +7 26.08 -19.11 +8 31.01 -6.01 +9 44.23 -29.93 +10 39.54 -1.08 +11 41.34 -17.43 +----- 14 ----- +Ball 24.88 35 +1 -50 -0 +2 2.88 5.53 +3 -0.84 21.03 +4 11.42 -7.69 +5 20.67 32.93 +6 14.54 13.46 +7 31.01 6.01 +8 26.08 19.11 +9 39.54 1.08 +10 44.23 29.93 +11 41.34 17.43 +----- 15 ----- +Ball 12.98 -35 +1 -50 0 +2 -3.61 -21.51 +3 -0.12 -4.33 +4 9.86 -31.97 +5 8.29 8.17 +6 8.51 -16.2 +7 20.91 -20.67 +8 18.15 -1.2 +9 37.02 -31.25 +10 31.49 -1.08 +11 34.97 -15.38 +----- 16 ----- +Ball 12.98 35 +1 -50 -0 +2 -0.12 4.33 +3 -3.61 21.51 +4 8.29 -8.17 +5 9.86 31.97 +6 8.51 16.2 +7 18.15 1.2 +8 20.91 20.67 +9 31.49 1.08 +10 37.02 31.25 +11 34.97 15.38 +----- 17 ----- +Ball 0 -35 +1 -50 0 +2 -7.58 -23.22 +3 -9.06 -10.97 +4 -1.56 -32.69 +5 -5.37 4.29 +6 0 -16.95 +7 7.57 -22.71 +8 5.49 -0.12 +9 24.47 -30.18 +10 23.68 3.97 +11 20.91 -14.66 +----- 18 ----- +Ball 0 35 +1 -50 -0 +2 -9.06 10.97 +3 -7.58 23.22 +4 -5.37 -4.29 +5 -1.56 32.69 +6 0 16.95 +7 5.49 0.12 +8 7.57 22.71 +9 23.68 -3.97 +10 24.47 30.18 +11 20.91 14.66 +----- 19 ----- +Ball 54 -35 +1 -50 0 +2 -0.24 -14.54 +3 7.21 -0.48 +4 8.3 -27.3 +5 17.19 10.22 +6 24.76 -14.66 +7 39.78 -28.6 +8 38.65 -11.04 +9 51.54 -34.65 +10 46.27 -8.05 +11 48.07 -22.35 +----- 20 ----- +Ball 54 35 +1 -50 -0 +2 7.21 0.48 +3 -0.24 14.54 +4 17.19 -10.22 +5 8.3 27.3 +6 24.76 14.66 +7 38.65 11.04 +8 39.78 28.6 +9 46.27 8.05 +10 51.54 34.65 +11 48.07 22.35 +End Samples +End diff --git a/keepaway/base/formation_dt/offense_formation.conf b/keepaway/base/formation_dt/offense_formation.conf new file mode 100755 index 00000000..cacfcc23 --- /dev/null +++ b/keepaway/base/formation_dt/offense_formation.conf @@ -0,0 +1,1512 @@ +Formation DelaunayTriangulation 2 +Begin Roles +1 Goalie 0 +2 CenterBack -1 +3 CenterBack 2 +4 SideBack -1 +5 SideBack 4 +6 DefensiveHalf 0 +7 OffensiveHalf -1 +8 OffensiveHalf 7 +9 SideForward -1 +10 SideForward 9 +11 CenterForward 0 +End Roles +Begin Samples 2 115 +----- 0 ----- +Ball 54.5 -36 +1 -50 0 +2 -0.72 -12 +3 -0.84 1.08 +4 4.9 -27.3 +5 10 8 +6 27.43 -16.5 +7 33.12 -27 +8 38.22 -3.5 +9 44.22 -30.85 +10 46 6.8 +11 46.28 -14 +----- 1 ----- +Ball 54.5 36 +1 -50 -0 +2 -0.84 -1.08 +3 -0.72 12 +4 10 -8 +5 4.9 27.3 +6 27.43 16.5 +7 38.22 3.5 +8 33.12 27 +9 46 -6.8 +10 44.22 30.85 +11 46.28 14 +----- 2 ----- +Ball 0 0 +1 -50 0 +2 -15.53 -5.42 +3 -15.53 5.42 +4 -11.56 -15.78 +5 -11.56 15.78 +6 -6.73 -1.87 +7 2.83 -10.81 +8 2.83 10.81 +9 9.3 -23.78 +10 9.3 23.78 +11 9.41 -3.12 +----- 3 ----- +Ball 54.5 0 +1 -50 0 +2 2.74 -6.07 +3 2.74 6.07 +4 7.21 -18.58 +5 7.21 18.58 +6 26.86 -3.2 +7 40.73 -3.77 +8 40.73 3.77 +9 48.97 -9.82 +10 48.97 9.82 +11 45.6 -1.65 +----- 4 ----- +Ball 36.57 -12.09 +1 -50 0 +2 -1.25 -9.96 +3 0.52 3.2 +4 5.09 -23.21 +5 7.36 15.67 +6 18.98 -7.3 +7 27.73 -14.57 +8 32.4 1.86 +9 39.17 -19.85 +10 42.41 10.78 +11 38.1 -8.45 +----- 5 ----- +Ball 36.57 12.09 +1 -50 -0 +2 0.52 -3.2 +3 -1.25 9.96 +4 7.36 -15.67 +5 5.09 23.21 +6 18.98 7.3 +7 32.4 -1.86 +8 27.73 14.57 +9 42.41 -10.78 +10 39.17 19.85 +11 38.1 8.45 +----- 6 ----- +Ball 48.51 -15.92 +1 -50 0 +2 0.51 -10.77 +3 3.07 3.38 +4 2.27 -23.46 +5 12.83 13.63 +6 25.2 -9.33 +7 34.83 -14.95 +8 39.34 -1.01 +9 45.03 -20.81 +10 46.88 6.76 +11 43.86 -8.86 +----- 7 ----- +Ball 48.51 15.92 +1 -50 -0 +2 3.07 -3.38 +3 0.51 10.77 +4 12.83 -13.63 +5 2.27 23.46 +6 25.2 9.33 +7 39.34 1.01 +8 34.83 14.95 +9 46.88 -6.76 +10 45.03 20.81 +11 43.86 8.86 +----- 8 ----- +Ball 42.76 0 +1 -50 0 +2 0.98 -5.97 +3 0.98 5.97 +4 6.85 -19.24 +5 6.85 19.24 +6 21.77 -2.76 +7 35.57 -5.84 +8 35.57 5.84 +9 44.37 -9.81 +10 44.37 9.81 +11 40.72 -2.36 +----- 9 ----- +Ball 48.66 -5.01 +1 -50 0 +2 1.54 -7.25 +3 2.33 5.02 +4 6.06 -20.44 +5 8.38 17.49 +6 24.7 -4.89 +7 37.7 -7.4 +8 39.2 2.37 +9 46.09 -15.6 +10 46.8 0.42 +11 43.57 -4.31 +----- 10 ----- +Ball 48.66 5.01 +1 -50 -0 +2 2.33 -5.02 +3 1.54 7.25 +4 8.38 -17.49 +5 6.06 20.44 +6 24.7 4.89 +7 39.2 -2.37 +8 37.7 7.4 +9 46.8 -0.42 +10 46.09 15.6 +11 43.57 4.31 +----- 11 ----- +Ball 50.57 -6.78 +1 -50 0 +2 1.66 -7.71 +3 2.75 4.77 +4 5.36 -20.8 +5 9.23 16.82 +6 25.62 -5.67 +7 38.2 -8.13 +8 40.06 1.43 +9 47 -17.67 +10 47.39 -0.36 +11 44.4 -4.93 +----- 12 ----- +Ball 50.57 6.78 +1 -50 -0 +2 2.75 -4.77 +3 1.66 7.71 +4 9.23 -16.82 +5 5.36 20.8 +6 25.62 5.67 +7 40.06 -1.43 +8 38.2 8.13 +9 47.39 0.36 +10 47 17.67 +11 44.4 4.93 +----- 13 ----- +Ball 52.49 -17.1 +1 -50 0 +2 0.95 -10.96 +3 3.74 3.5 +4 0.75 -23.35 +5 14.73 12.86 +6 27.02 -9.99 +7 36.71 -15.3 +8 40.72 -1.59 +9 47.62 -24.13 +10 47.64 7.59 +11 45.39 -8.87 +----- 14 ----- +Ball 52.49 17.1 +1 -50 -0 +2 3.74 -3.5 +3 0.95 10.96 +4 14.73 -12.86 +5 0.75 23.35 +6 27.02 9.99 +7 40.72 1.59 +8 36.71 15.3 +9 47.64 -7.59 +10 47.62 24.13 +11 45.39 8.87 +----- 15 ----- +Ball 52.49 -7.96 +1 -50 0 +2 1.82 -7.99 +3 3.1 4.66 +4 4.65 -20.95 +5 10.02 16.31 +6 26.49 -6.22 +7 38.78 -8.58 +8 40.75 0.8 +9 48.07 -19.82 +10 47.99 0.19 +11 45.16 -5.26 +----- 16 ----- +Ball 52.49 7.96 +1 -50 -0 +2 3.1 -4.66 +3 1.82 7.99 +4 10.02 -16.31 +5 4.65 20.95 +6 26.49 6.22 +7 40.75 -0.8 +8 38.78 8.58 +9 47.99 -0.19 +10 48.07 19.82 +11 45.16 5.26 +----- 17 ----- +Ball 49.25 -9.29 +1 -50 0 +2 1.25 -8.48 +3 2.73 4.33 +4 4.73 -21.62 +5 9.92 16.07 +6 25.18 -6.62 +7 37.01 -10.02 +8 39.69 0.74 +9 46.18 -17.93 +10 47.11 1.01 +11 43.96 -6.11 +----- 18 ----- +Ball 49.25 9.29 +1 -50 -0 +2 2.73 -4.33 +3 1.25 8.48 +4 9.92 -16.07 +5 4.73 21.62 +6 25.18 6.62 +7 39.69 -0.74 +8 37.01 10.02 +9 47.11 -1.01 +10 46.18 17.93 +11 43.96 6.11 +----- 19 ----- +Ball 46.74 0 +1 -50 0 +2 1.62 -6.08 +3 1.62 6.08 +4 7.1 -19.05 +5 7.1 19.05 +6 23.75 -0.9 +7 37.73 -4.99 +8 37.73 4.99 +9 45.7 -9.75 +10 45.7 9.75 +11 42.56 -2.13 +----- 20 ----- +Ball 42.61 -5.6 +1 -50 0 +2 0.5 -7.52 +3 1.36 4.72 +4 6.18 -20.97 +5 7.57 17.56 +6 21.91 -4.9 +7 34.21 -9.06 +8 36.27 3.21 +9 43.47 -15.1 +10 45.41 2.8 +11 40.91 -5.04 +----- 21 ----- +Ball 42.61 5.6 +1 -50 -0 +2 1.36 -4.72 +3 0.5 7.52 +4 7.57 -17.56 +5 6.18 20.97 +6 21.91 4.9 +7 36.27 -3.21 +8 34.21 9.06 +9 45.41 -2.8 +10 43.47 15.1 +11 40.91 5.04 +----- 22 ----- +Ball 45.86 -3.54 +1 -50 0 +2 1.23 -6.89 +3 1.78 5.23 +4 6.49 -20.18 +5 7.68 18.07 +6 23.37 -4.21 +7 36.61 -7.09 +8 37.8 3.47 +9 45.04 -13.82 +10 45.96 2.43 +11 42.32 -3.85 +----- 23 ----- +Ball 45.86 3.54 +1 -50 -0 +2 1.78 -5.23 +3 1.23 6.89 +4 7.68 -18.07 +5 6.49 20.18 +6 23.37 4.21 +7 37.8 -3.47 +8 36.61 7.09 +9 45.96 -2.43 +10 45.04 13.82 +11 42.32 3.85 +----- 24 ----- +Ball 46.89 -6.49 +1 -50 0 +2 1.14 -7.7 +3 2.17 4.7 +4 5.83 -20.99 +5 8.52 17.12 +6 23.98 -5.41 +7 36.48 -8.62 +8 38.57 2.07 +9 45.21 -15.64 +10 46.51 0.08 +11 42.89 -5.11 +----- 25 ----- +Ball 46.89 6.49 +1 -50 -0 +2 2.17 -4.7 +3 1.14 7.7 +4 8.52 -17.12 +5 5.83 20.99 +6 23.98 5.41 +7 38.57 -2.07 +8 36.48 8.62 +9 46.51 -0.08 +10 45.21 15.64 +11 42.89 5.11 +----- 26 ----- +Ball 38.63 0 +1 -50 0 +2 0.18 -5.93 +3 0.18 5.93 +4 6.41 -19.34 +5 6.41 19.34 +6 19.71 -2.62 +7 32.73 -6.84 +8 32.73 6.84 +9 42.2 -11.81 +10 42.2 11.81 +11 38.61 -2.57 +----- 27 ----- +Ball 39.22 -5.75 +1 -50 0 +2 -0.15 -7.62 +3 0.71 4.57 +4 6.1 -21.14 +5 6.93 17.6 +6 20.2 -4.84 +7 31.8 -9.98 +8 34.05 3.87 +9 41.58 -16.28 +10 44.01 6.35 +11 39.22 -5.35 +----- 28 ----- +Ball 39.22 5.75 +1 -50 -0 +2 0.71 -4.57 +3 -0.15 7.62 +4 6.93 -17.6 +5 6.1 21.14 +6 20.2 4.84 +7 34.05 -3.87 +8 31.8 9.98 +9 44.01 -6.35 +10 41.58 16.28 +11 39.22 5.35 +----- 29 ----- +Ball 30.37 -15.92 +1 -50 0 +2 -3.06 -11.84 +3 -0.92 2.05 +4 3.83 -24.4 +5 6 14.49 +6 15.44 -8.7 +7 21.07 -17.96 +8 27.44 1.55 +9 34.11 -24.3 +10 34.71 14.57 +11 34.7 -10.66 +----- 30 ----- +Ball 30.37 15.92 +1 -50 -0 +2 -0.92 -2.05 +3 -3.06 11.84 +4 6 -14.49 +5 3.83 24.4 +6 15.44 8.7 +7 27.44 -1.55 +8 21.07 17.96 +9 34.71 -14.57 +10 34.11 24.3 +11 34.7 10.66 +----- 31 ----- +Ball 0 -36 +1 -50 0 +2 -17.18 -19.96 +3 -16.68 -6.05 +4 -13.42 -30.3 +5 -7.16 6.63 +6 -8.38 -16.63 +7 -7.37 -25.18 +8 -4.81 -1.58 +9 13.45 -32.14 +10 10.92 18.27 +11 14.62 -20.6 +----- 32 ----- +Ball 0 36 +1 -50 -0 +2 -16.68 6.05 +3 -17.18 19.96 +4 -7.16 -6.63 +5 -13.42 30.3 +6 -8.38 16.63 +7 -4.81 1.58 +8 -7.37 25.18 +9 10.92 -18.27 +10 13.45 32.14 +11 14.62 20.6 +----- 33 ----- +Ball 44.53 -22.41 +1 -50 0 +2 -0.79 -13.56 +3 2.77 2.31 +4 0.88 -25.3 +5 14.3 11.33 +6 23.64 -11.92 +7 30.42 -20.33 +8 36.76 -1.94 +9 41.44 -22.71 +10 45.59 7.46 +11 42.36 -11.75 +----- 34 ----- +Ball 44.53 22.41 +1 -50 -0 +2 2.77 -2.31 +3 -0.79 13.56 +4 14.3 -11.33 +5 0.88 25.3 +6 23.64 11.92 +7 36.76 1.94 +8 30.42 20.33 +9 45.59 -7.46 +10 41.44 22.71 +11 42.36 11.75 +----- 35 ----- +Ball 44.09 -29.78 +1 -50 0 +2 -1.7 -16.43 +3 3.08 1.45 +4 2.15 -27.12 +5 15.32 8.96 +6 23.78 -15.03 +7 28.49 -25.4 +8 35.1 -2.94 +9 39.72 -25.13 +10 44.65 7.22 +11 42.31 -14.34 +----- 36 ----- +Ball 44.09 29.78 +1 -50 -0 +2 3.08 -1.45 +3 -1.7 16.43 +4 15.32 -8.96 +5 2.15 27.12 +6 23.78 15.03 +7 35.1 2.94 +8 28.49 25.4 +9 44.65 -7.22 +10 39.72 25.13 +11 42.31 14.34 +----- 37 ----- +Ball 29.19 -34.36 +1 -50 0 +2 -5.36 -18.8 +3 -0.74 -0.94 +4 2.3 -27.83 +5 9.4 8.12 +6 15.16 -16.5 +7 19.03 -27.25 +8 26.04 -3.11 +9 36.79 -29.36 +10 29.88 13.71 +11 34.81 -17.65 +----- 38 ----- +Ball 29.19 34.36 +1 -50 -0 +2 -0.74 0.94 +3 -5.36 18.8 +4 9.4 -8.12 +5 2.3 27.83 +6 15.16 16.5 +7 26.04 3.11 +8 19.03 27.25 +9 29.88 -13.71 +10 36.79 29.36 +11 34.81 17.65 +----- 39 ----- +Ball 33.03 -31.26 +1 -50 0 +2 -4.18 -17.73 +3 0.28 -0.08 +4 2.01 -27.43 +5 11.01 8.83 +6 17.29 -15.35 +7 20.92 -26.07 +8 28.24 -2.72 +9 38.57 -27.83 +10 34.68 12.1 +11 36.65 -16.3 +----- 40 ----- +Ball 33.03 31.26 +1 -50 -0 +2 0.28 0.08 +3 -4.18 17.73 +4 11.01 -8.83 +5 2.01 27.43 +6 17.29 15.35 +7 28.24 2.72 +8 20.92 26.07 +9 34.68 -12.1 +10 38.57 27.83 +11 36.65 16.3 +----- 41 ----- +Ball 23 -5.16 +1 -50 0 +2 -4.35 -7.68 +3 -3.76 4.1 +4 2.99 -20.69 +5 2.22 17.45 +6 10.52 -4.14 +7 15.33 -13.53 +8 18.29 8.05 +9 23.07 -25.95 +10 24.74 20.06 +11 29.04 -6 +----- 42 ----- +Ball 23 5.16 +1 -50 -0 +2 -3.76 -4.1 +3 -4.35 7.68 +4 2.22 -17.45 +5 2.99 20.69 +6 10.52 4.14 +7 18.29 -8.05 +8 15.33 13.53 +9 24.74 -20.06 +10 23.07 25.95 +11 29.04 6 +----- 43 ----- +Ball 28.16 0 +1 -50 0 +2 -2.39 -5.82 +3 -2.39 5.82 +4 4.32 -19.24 +5 4.32 19.24 +6 13.8 -2.33 +7 22.61 -9.6 +8 22.61 9.6 +9 29.71 -21.47 +10 29.71 21.47 +11 32.25 -1 +----- 44 ----- +Ball 34.65 -5.75 +1 -50 0 +2 -1.13 -7.7 +3 -0.31 4.4 +4 5.74 -21.22 +5 5.89 17.64 +6 17.72 -4.69 +7 27.93 -11.15 +8 30.34 4.95 +9 37.52 -18.78 +10 40.2 11.53 +11 36.7 -5.66 +----- 45 ----- +Ball 34.65 5.75 +1 -50 -0 +2 -0.31 -4.4 +3 -1.13 7.7 +4 5.89 -17.64 +5 5.74 21.22 +6 17.72 4.69 +7 30.34 -4.95 +8 27.93 11.15 +9 40.2 -11.53 +10 37.52 18.78 +11 36.7 5.66 +----- 46 ----- +Ball 19.91 -28.6 +1 -50 0 +2 -7.44 -17.45 +3 -4.43 -1.6 +4 -1.88 -26.53 +5 4.35 9.94 +6 8.44 -13.83 +7 11.6 -24.14 +8 21.83 -1.79 +9 28.02 -30.94 +10 24.91 17.54 +11 28.57 -16.62 +----- 47 ----- +Ball 19.91 28.6 +1 -50 -0 +2 -4.43 1.6 +3 -7.44 17.45 +4 4.35 -9.94 +5 -1.88 26.53 +6 8.44 13.83 +7 21.83 1.79 +8 11.6 24.14 +9 24.91 -17.54 +10 28.02 30.94 +11 28.57 16.62 +----- 48 ----- +Ball 14.3 -11.06 +1 -50 0 +2 -8.06 -10.45 +3 -7.15 1.87 +4 -1.04 -21.99 +5 -2.42 15.19 +6 4.24 -6.29 +7 5.05 -17.13 +8 13.68 6.13 +9 17.23 -26.9 +10 18.09 19.6 +11 22.95 -9.41 +----- 49 ----- +Ball 14.3 11.06 +1 -50 -0 +2 -7.15 -1.87 +3 -8.06 10.45 +4 -2.42 -15.19 +5 -1.04 21.99 +6 4.24 6.29 +7 13.68 -6.13 +8 5.05 17.13 +9 18.09 -19.6 +10 17.23 26.9 +11 22.95 9.41 +----- 50 ----- +Ball 11.35 -25.07 +1 -50 0 +2 -10.43 -16.57 +3 -8.68 -1.89 +4 -5.48 -25.58 +5 -2.46 11.03 +6 1.71 -12.12 +7 3.25 -22.2 +8 16.29 -0.28 +9 18.9 -29.64 +10 17.57 18.46 +11 22.04 -15.92 +----- 51 ----- +Ball 11.35 25.07 +1 -50 -0 +2 -8.68 1.89 +3 -10.43 16.57 +4 -2.46 -11.03 +5 -5.48 25.58 +6 1.71 12.12 +7 16.29 0.28 +8 3.25 22.2 +9 17.57 -18.46 +10 18.9 29.64 +11 22.04 15.92 +----- 52 ----- +Ball 9.58 0 +1 -50 0 +2 -9.77 -5.58 +3 -9.77 5.58 +4 -4.36 -17.44 +5 -4.36 17.44 +6 0.91 -0.62 +7 5.26 -12.1 +8 5.26 12.1 +9 14.64 -25.04 +10 14.64 25.04 +11 17.81 -1.03 +----- 53 ----- +Ball 18.58 0 +1 -50 0 +2 -5.66 -5.71 +3 -5.66 5.71 +4 0.77 -18.6 +5 0.77 18.6 +6 7.52 -2.11 +7 11.95 -11.59 +8 11.95 11.59 +9 20.57 -25.76 +10 20.57 25.76 +11 25.23 -0.34 +----- 54 ----- +Ball 3.83 -20.2 +1 -50 0 +2 -13.84 -14.96 +3 -13.2 -1.74 +4 -9.32 -24.18 +5 -9.54 11.72 +6 -4.47 -9.91 +7 -3.05 -19.66 +8 10.11 2 +9 11.32 -27.46 +10 11.22 18.5 +11 15.55 -14.21 +----- 55 ----- +Ball 3.83 20.2 +1 -50 -0 +2 -13.2 1.74 +3 -13.84 14.96 +4 -9.54 -11.72 +5 -9.32 24.18 +6 -4.47 9.91 +7 10.11 -2 +8 -3.05 19.66 +9 11.22 -18.5 +10 11.32 27.46 +11 15.55 14.21 +----- 56 ----- +Ball 6.19 -10.32 +1 -50 0 +2 -11.99 -10.37 +3 -11.54 1.49 +4 -6.34 -20.75 +5 -7.77 14.2 +6 -2.15 -5.86 +7 -0.16 -16.34 +8 9.32 6.62 +9 11.66 -29.11 +10 12.63 18.96 +11 16.23 -9.25 +----- 57 ----- +Ball 6.19 10.32 +1 -50 -0 +2 -11.54 -1.49 +3 -11.99 10.37 +4 -7.77 -14.2 +5 -6.34 20.75 +6 -2.15 5.86 +7 9.32 -6.62 +8 -0.16 16.34 +9 12.63 -18.96 +10 11.66 29.11 +11 16.23 9.25 +----- 58 ----- +Ball 10.47 -29.78 +1 -50 0 +2 -11.25 -18.18 +3 -9.28 -3 +4 -6.48 -26.52 +5 -1.47 9.34 +6 0.92 -14.14 +7 3.65 -23.76 +8 14.34 -1.54 +9 19.43 -31.04 +10 18.15 18.08 +11 21.84 -17.79 +----- 59 ----- +Ball 10.47 29.78 +1 -50 -0 +2 -9.28 3 +3 -11.25 18.18 +4 -1.47 -9.34 +5 -6.48 26.52 +6 0.92 14.14 +7 14.34 1.54 +8 3.65 23.76 +9 18.15 -18.08 +10 19.43 31.04 +11 21.84 17.79 +----- 60 ----- +Ball 13.27 -33.18 +1 -50 0 +2 -10.4 -19.06 +3 -7.76 -3.2 +4 -4.63 -27.14 +5 1.37 8.33 +6 3.2 -15.64 +7 7.31 -25.26 +8 15.53 -2.38 +9 22.52 -31.88 +10 21.76 17.76 +11 24.21 -18.79 +----- 61 ----- +Ball 13.27 33.18 +1 -50 -0 +2 -7.76 3.2 +3 -10.4 19.06 +4 1.37 -8.33 +5 -4.63 27.14 +6 3.2 15.64 +7 15.53 2.38 +8 7.31 25.26 +9 21.76 -17.76 +10 22.52 31.88 +11 24.21 18.79 +----- 62 ----- +Ball -16.96 -30.52 +1 -50 0 +2 -23.33 -18.45 +3 -23.18 -4.34 +4 -23.94 -27.93 +5 -22.14 9.35 +6 -19.24 -13.98 +7 -16.33 -22.81 +8 -14.1 -0.07 +9 0.44 -30.41 +10 -4.28 17.04 +11 0.06 -9.81 +----- 63 ----- +Ball -16.96 30.52 +1 -50 -0 +2 -23.18 4.34 +3 -23.33 18.45 +4 -22.14 -9.35 +5 -23.94 27.93 +6 -19.24 13.98 +7 -14.1 0.07 +8 -16.33 22.81 +9 -4.28 -17.04 +10 0.44 30.41 +11 0.06 9.81 +----- 64 ----- +Ball -4.28 -16.81 +1 -50 0 +2 -12.71 -15.79 +3 -14.88 -3.68 +4 -13.76 -23.07 +5 -12.63 11.13 +6 -8.8 -11.13 +7 -3.79 -19.5 +8 -3.98 5.9 +9 6.03 -29.81 +10 2.6 22.3 +11 5.59 -8.48 +----- 65 ----- +Ball -4.28 16.81 +1 -50 -0 +2 -14.88 3.68 +3 -12.71 15.79 +4 -12.63 -11.13 +5 -13.76 23.07 +6 -8.8 11.13 +7 -3.98 -5.9 +8 -3.79 19.5 +9 2.6 -22.3 +10 6.03 29.81 +11 5.59 8.48 +----- 66 ----- +Ball -7.08 -27.57 +1 -50 0 +2 -15.15 -19.34 +3 -16.57 -4.96 +4 -15.35 -27.21 +5 -13.48 10.45 +6 -10.91 -14.26 +7 -6.54 -22.94 +8 -6.22 1.56 +9 6.84 -30.97 +10 1.32 18.51 +11 6.91 -10.52 +----- 67 ----- +Ball -7.08 27.57 +1 -50 -0 +2 -16.57 4.96 +3 -15.15 19.34 +4 -13.48 -10.45 +5 -15.35 27.21 +6 -10.91 14.26 +7 -6.22 -1.56 +8 -6.54 22.94 +9 1.32 -18.51 +10 6.84 30.97 +11 6.91 10.52 +----- 68 ----- +Ball -7.96 -31.41 +1 -50 0 +2 -15.89 -20.09 +3 -17.14 -5.03 +4 -15.83 -28.57 +5 -13.66 11 +6 -11.44 -14.98 +7 -7.45 -24.07 +8 -6.86 0.18 +9 7.39 -31.3 +10 1.09 17.04 +11 8.36 -10.67 +----- 69 ----- +Ball -7.96 31.41 +1 -50 -0 +2 -17.14 5.03 +3 -15.89 20.09 +4 -13.66 -11 +5 -15.83 28.57 +6 -11.44 14.98 +7 -6.86 -0.18 +8 -7.45 24.07 +9 1.09 -17.04 +10 7.39 31.3 +11 8.36 10.67 +----- 70 ----- +Ball -23.89 -34.21 +1 -50 0 +2 -31.05 -17.31 +3 -29.37 -4.33 +4 -29.93 -27.83 +5 -28.55 8.6 +6 -24.69 -13.7 +7 -22.94 -23.29 +8 -19.22 -1.39 +9 -3.39 -30.07 +10 -7.83 15.33 +11 -3.41 -8.93 +----- 71 ----- +Ball -23.89 34.21 +1 -50 -0 +2 -29.37 4.33 +3 -31.05 17.31 +4 -28.55 -8.6 +5 -29.93 27.83 +6 -24.69 13.7 +7 -19.22 1.39 +8 -22.94 23.29 +9 -7.83 -15.33 +10 -3.39 30.07 +11 -3.41 8.93 +----- 72 ----- +Ball -54.5 0 +1 -50 0 +2 -47.53 -3.17 +3 -47.53 3.17 +4 -49.99 -6.88 +5 -49.99 6.88 +6 -44.06 1.02 +7 -41.64 -8.61 +8 -41.64 8.61 +9 -23.83 -22.2 +10 -23.83 22.2 +11 -30.03 4.57 +----- 73 ----- +Ball -19.61 -5.46 +1 -50 0 +2 -26.74 -7.22 +3 -27.37 1.07 +4 -27.57 -15.59 +5 -27.52 9.88 +6 -23.16 -4.67 +7 -18.04 -13.11 +8 -17.58 8.75 +9 -6.06 -26.78 +10 -6.72 24.53 +11 -8.99 -3.01 +----- 74 ----- +Ball -19.61 5.46 +1 -50 -0 +2 -27.37 -1.07 +3 -26.74 7.22 +4 -27.52 -9.88 +5 -27.57 15.59 +6 -23.16 4.67 +7 -17.58 -8.75 +8 -18.04 13.11 +9 -6.72 -24.53 +10 -6.06 26.78 +11 -8.99 3.01 +----- 75 ----- +Ball -7.96 -7.37 +1 -50 0 +2 -15.64 -9.85 +3 -17.18 0.07 +4 -16.91 -19.09 +5 -16.52 12.59 +6 -12.21 -6.37 +7 -7.34 -15.32 +8 -7.25 9.27 +9 1.69 -28.01 +10 0.38 24.81 +11 0.76 -4.5 +----- 76 ----- +Ball -7.96 7.37 +1 -50 -0 +2 -17.18 -0.07 +3 -15.64 9.85 +4 -16.52 -12.59 +5 -16.91 19.09 +6 -12.21 6.37 +7 -7.25 -9.27 +8 -7.34 15.32 +9 0.38 -24.81 +10 1.69 28.01 +11 0.76 4.5 +----- 77 ----- +Ball -5.31 0 +1 -50 0 +2 -14.82 -4.5 +3 -14.82 4.5 +4 -14.59 -16.26 +5 -14.59 16.26 +6 -9.26 -1.26 +7 -4.86 -12.66 +8 -4.86 12.66 +9 2.61 -26.72 +10 2.61 26.72 +11 2.2 -0.09 +----- 78 ----- +Ball -2.06 -11.35 +1 -50 0 +2 -11.37 -12.91 +3 -13.56 -2.16 +4 -12.2 -21.01 +5 -11.39 13.02 +6 -6.73 -8.68 +7 -1.72 -17.72 +8 -2.03 8.39 +9 6.48 -29.12 +10 4.06 24.04 +11 6.06 -6.47 +----- 79 ----- +Ball -2.06 11.35 +1 -50 -0 +2 -13.56 2.16 +3 -11.37 12.91 +4 -11.39 -13.02 +5 -12.2 21.01 +6 -6.73 8.68 +7 -2.03 -8.39 +8 -1.72 17.72 +9 4.06 -24.04 +10 6.48 29.12 +11 6.06 6.47 +----- 80 ----- +Ball -3.39 -5.9 +1 -50 0 +2 -12.7 -8.99 +3 -14.14 0.58 +4 -13.2 -18.92 +5 -12.79 14.39 +6 -7.76 -5.48 +7 -3.04 -15.36 +8 -3.16 10.48 +9 4.62 -28.05 +10 3.43 25.44 +11 4.7 -3.75 +----- 81 ----- +Ball -3.39 5.9 +1 -50 -0 +2 -14.14 -0.58 +3 -12.7 8.99 +4 -12.79 -14.39 +5 -13.2 18.92 +6 -7.76 5.48 +7 -3.16 -10.48 +8 -3.04 15.36 +9 3.43 -25.44 +10 4.62 28.05 +11 4.7 3.75 +----- 82 ----- +Ball -9.44 -24.77 +1 -50 0 +2 -16.56 -18.28 +3 -17.92 -4.5 +4 -17.58 -26.25 +5 -15.92 9.67 +6 -13.12 -13.42 +7 -8.86 -21.71 +8 -8.23 2.36 +9 4.27 -30.45 +10 -0.29 19.43 +11 3.59 -10.02 +----- 83 ----- +Ball -9.44 24.77 +1 -50 -0 +2 -17.92 4.5 +3 -16.56 18.28 +4 -15.92 -9.67 +5 -17.58 26.25 +6 -13.12 13.42 +7 -8.23 -2.36 +8 -8.86 21.71 +9 -0.29 -19.43 +10 4.27 30.45 +11 3.59 10.02 +----- 84 ----- +Ball -12.39 -12.39 +1 -50 0 +2 -18.61 -12.64 +3 -20.37 -1.72 +4 -20.71 -20.92 +5 -20.15 9.72 +6 -16.37 -8.83 +7 -11.58 -16.74 +8 -11.06 6.74 +9 -0.53 -28.52 +10 -2.47 23.22 +11 -2.1 -6.61 +----- 85 ----- +Ball -12.39 12.39 +1 -50 -0 +2 -20.37 1.72 +3 -18.61 12.64 +4 -20.15 -9.72 +5 -20.71 20.92 +6 -16.37 8.83 +7 -11.06 -6.74 +8 -11.58 16.74 +9 -2.47 -23.22 +10 -0.53 28.52 +11 -2.1 6.61 +----- 86 ----- +Ball -16.37 -15.78 +1 -50 0 +2 -22.13 -13.78 +3 -23.54 -2.52 +4 -24.22 -21.95 +5 -23.62 8.03 +6 -19.87 -9.96 +7 -15.41 -17.57 +8 -14.35 5.07 +9 -2.6 -28.7 +10 -4.82 22.01 +11 -4.48 -7.55 +----- 87 ----- +Ball -16.37 15.78 +1 -50 -0 +2 -23.54 2.52 +3 -22.13 13.78 +4 -23.62 -8.03 +5 -24.22 21.95 +6 -19.87 9.96 +7 -14.35 -5.07 +8 -15.41 17.57 +9 -4.82 -22.01 +10 -2.6 28.7 +11 -4.48 7.55 +----- 88 ----- +Ball -19.91 -18.28 +1 -50 0 +2 -25.82 -14.13 +3 -26.7 -3.02 +4 -27.37 -22.39 +5 -26.83 6.95 +6 -22.83 -10.5 +7 -18.77 -18.12 +8 -17.16 3.88 +9 -4.47 -28.73 +10 -6.79 21.05 +11 -6.47 -7.96 +----- 89 ----- +Ball -19.91 18.28 +1 -50 -0 +2 -26.7 3.02 +3 -25.82 14.13 +4 -26.83 -6.95 +5 -27.37 22.39 +6 -22.83 10.5 +7 -17.16 -3.88 +8 -18.77 18.12 +9 -6.79 -21.05 +10 -4.47 28.73 +11 -6.47 7.96 +----- 90 ----- +Ball -32.73 -29.19 +1 -50 0 +2 -39.69 -13.44 +3 -38.23 -4.58 +4 -37.42 -22.97 +5 -37.47 4.45 +6 -32.04 -11.42 +7 -30.22 -20.74 +8 -26.1 -0.09 +9 -10.38 -28.63 +10 -13.04 16.63 +11 -12 -7.92 +----- 91 ----- +Ball -32.73 29.19 +1 -50 -0 +2 -38.23 4.58 +3 -39.69 13.44 +4 -37.47 -4.45 +5 -37.42 22.97 +6 -32.04 11.42 +7 -26.1 0.09 +8 -30.22 20.74 +9 -13.04 -16.63 +10 -10.38 28.63 +11 -12 7.92 +----- 92 ----- +Ball -24.03 -17.55 +1 -50 0 +2 -30.55 -12.75 +3 -31.07 -2.95 +4 -31.18 -20.78 +5 -30.99 5.87 +6 -26.38 -9.76 +7 -22.45 -17.38 +8 -20.54 3.87 +9 -7.22 -28.24 +10 -9.15 21.01 +11 -9.46 -7.39 +----- 93 ----- +Ball -24.03 17.55 +1 -50 -0 +2 -31.07 2.95 +3 -30.55 12.75 +4 -30.99 -5.87 +5 -31.18 20.78 +6 -26.38 9.76 +7 -20.54 -3.87 +8 -22.45 17.38 +9 -9.15 -21.01 +10 -7.22 28.24 +11 -9.46 7.39 +----- 94 ----- +Ball -31.26 0 +1 -50 0 +2 -39.65 -2.16 +3 -39.65 2.16 +4 -38.28 -8.03 +5 -38.28 8.03 +6 -32.56 -0.43 +7 -27.38 -9.85 +8 -27.38 9.85 +9 -13.07 -24.74 +10 -13.07 24.74 +11 -18.33 1.17 +----- 95 ----- +Ball -29.34 -15.33 +1 -50 0 +2 -36.56 -10.26 +3 -36.79 -2.72 +4 -35.95 -17.42 +5 -36.13 4.57 +6 -30.72 -8.28 +7 -26.82 -15.97 +8 -24.81 4.35 +9 -10.73 -27.4 +10 -12.11 21.27 +11 -13.3 -6.21 +----- 96 ----- +Ball -29.34 15.33 +1 -50 -0 +2 -36.79 2.72 +3 -36.56 10.26 +4 -36.13 -4.57 +5 -35.95 17.42 +6 -30.72 8.28 +7 -24.81 -4.35 +8 -26.82 15.97 +9 -12.11 -21.27 +10 -10.73 27.4 +11 -13.3 6.21 +----- 97 ----- +Ball -37.01 -33.03 +1 -50 0 +2 -42.77 -12.81 +3 -40.82 -4.62 +4 -40.07 -23.04 +5 -40.14 3.64 +6 -34.61 -11.31 +7 -33.61 -21.66 +8 -28.64 -1.11 +9 -12.13 -28.47 +10 -14.85 14.96 +11 -13.43 -7.26 +----- 98 ----- +Ball -37.01 33.03 +1 -50 -0 +2 -40.82 4.62 +3 -42.77 12.81 +4 -40.14 -3.64 +5 -40.07 23.04 +6 -34.61 11.31 +7 -28.64 1.11 +8 -33.61 21.66 +9 -14.85 -14.96 +10 -12.13 28.47 +11 -13.43 7.26 +----- 99 ----- +Ball -54.5 -36 +1 -50 0 +2 -46.24 -10.29 +3 -44.48 -1.69 +4 -48.45 -21.85 +5 -48.06 2.31 +6 -43.6 -8.28 +7 -43.89 -21.18 +8 -38.36 -1.28 +9 -20.87 -26.29 +10 -22.49 12.44 +11 -22.8 -4.37 +----- 100 ----- +Ball -54.5 36 +1 -50 -0 +2 -44.48 1.69 +3 -46.24 10.29 +4 -48.06 -2.31 +5 -48.45 21.85 +6 -43.6 8.28 +7 -38.36 1.28 +8 -43.89 21.18 +9 -22.49 -12.44 +10 -20.87 26.29 +11 -22.8 4.37 +----- 101 ----- +Ball -48.66 -22.71 +1 -50 0 +2 -46.06 -8.98 +3 -45.54 -2.25 +4 -47.21 -15.63 +5 -47.31 2.84 +6 -41.76 -7.28 +7 -40.32 -17.03 +8 -36.69 1.74 +9 -19.94 -26.01 +10 -21.01 17.44 +11 -22.29 -5.05 +----- 102 ----- +Ball -48.66 22.71 +1 -50 -0 +2 -45.54 2.25 +3 -46.06 8.98 +4 -47.31 -2.84 +5 -47.21 15.63 +6 -41.76 7.28 +7 -36.69 -1.74 +8 -40.32 17.03 +9 -21.01 -17.44 +10 -19.94 26.01 +11 -22.29 5.05 +----- 103 ----- +Ball -39.52 -28.16 +1 -50 0 +2 -43.82 -11.32 +3 -42.6 -4.34 +4 -42.05 -20.13 +5 -42.42 2.74 +6 -36.63 -10.01 +7 -35.11 -19.75 +8 -30.74 0.19 +9 -14.51 -27.72 +10 -16.49 16.49 +11 -16.44 -6.95 +----- 104 ----- +Ball -39.52 28.16 +1 -50 -0 +2 -42.6 4.34 +3 -43.82 11.32 +4 -42.42 -2.74 +5 -42.05 20.13 +6 -36.63 10.01 +7 -30.74 -0.19 +8 -35.11 19.75 +9 -16.49 -16.49 +10 -14.51 27.72 +11 -16.44 6.95 +----- 105 ----- +Ball -39.22 -22.12 +1 -50 0 +2 -43.64 -10.02 +3 -43.11 -3.81 +4 -42.46 -17.18 +5 -42.88 2.69 +6 -36.9 -8.77 +7 -34.57 -17.6 +8 -31.14 1.89 +9 -15.29 -27.15 +10 -16.77 18.47 +11 -17.55 -6.43 +----- 106 ----- +Ball -39.22 22.12 +1 -50 -0 +2 -43.11 3.81 +3 -43.64 10.02 +4 -42.88 -2.69 +5 -42.46 17.18 +6 -36.9 8.77 +7 -31.14 -1.89 +8 -34.57 17.6 +9 -16.77 -18.47 +10 -15.29 27.15 +11 -17.55 6.43 +----- 107 ----- +Ball -41.58 -7.22 +1 -50 0 +2 -45.59 -4.05 +3 -45.51 -0.6 +4 -44.96 -8.55 +5 -45.08 3.99 +6 -38.84 -3.44 +7 -35.13 -11.83 +8 -33.99 6.61 +9 -17.94 -24.94 +10 -18.33 22.22 +11 -21.98 -1.47 +----- 108 ----- +Ball -41.58 7.22 +1 -50 -0 +2 -45.51 0.6 +3 -45.59 4.05 +4 -45.08 -3.99 +5 -44.96 8.55 +6 -38.84 3.44 +7 -33.99 -6.61 +8 -35.13 11.83 +9 -18.33 -22.22 +10 -17.94 24.94 +11 -21.98 1.47 +----- 109 ----- +Ball -34.06 -7.37 +1 -50 0 +2 -41.55 -5.15 +3 -41.6 -0.8 +4 -40.2 -10.73 +5 -40.73 4.73 +6 -34.41 -4.35 +7 -29.99 -12.46 +8 -28.91 6.92 +9 -14.14 -25.77 +10 -14.67 22.91 +11 -17.91 -2.58 +----- 110 ----- +Ball -34.06 7.37 +1 -50 -0 +2 -41.6 0.8 +3 -41.55 5.15 +4 -40.73 -4.73 +5 -40.2 10.73 +6 -34.41 4.35 +7 -28.91 -6.92 +8 -29.99 12.46 +9 -14.67 -22.91 +10 -14.14 25.77 +11 -17.91 2.58 +----- 111 ----- +Ball -48.22 -9.88 +1 -50 0 +2 -46.9 -4.98 +3 -46.74 -0.12 +4 -47.87 -9.24 +5 -47.87 4.14 +6 -41.91 -3.75 +7 -39.23 -12.38 +8 -37.64 5.54 +9 -20.84 -24.54 +10 -21.27 20.92 +11 -24.49 -1.57 +----- 112 ----- +Ball -48.22 9.88 +1 -50 -0 +2 -46.74 0.12 +3 -46.9 4.98 +4 -47.87 -4.14 +5 -47.87 9.24 +6 -41.91 3.75 +7 -37.64 -5.54 +8 -39.23 12.38 +9 -21.27 -20.92 +10 -20.84 24.54 +11 -24.49 1.57 +----- 113 ----- +Ball 15.33 -21.38 +1 -50 0 +2 -8.43 -14.97 +3 -6.57 -0.58 +4 -2.37 -25.05 +5 -0.76 12.47 +6 4.93 -10.62 +7 4.93 -21.15 +8 18.46 0.94 +9 21.33 -29.66 +10 20.06 18.78 +11 24.72 -14.15 +----- 114 ----- +Ball 15.33 21.38 +1 -50 -0 +2 -6.57 0.58 +3 -8.43 14.97 +4 -0.76 -12.47 +5 -2.37 25.05 +6 4.93 10.62 +7 18.46 -0.94 +8 4.93 21.15 +9 20.06 -18.78 +10 21.33 29.66 +11 24.72 14.15 +End Samples +End diff --git a/keepaway/base/formation_dt/setplay_opp_formation.conf b/keepaway/base/formation_dt/setplay_opp_formation.conf new file mode 100755 index 00000000..fcacfcf1 --- /dev/null +++ b/keepaway/base/formation_dt/setplay_opp_formation.conf @@ -0,0 +1,602 @@ +Formation DelaunayTriangulation 2 +Begin Roles +1 Goalie 0 +2 CenterBack -1 +3 CenterBack 2 +4 SideBack -1 +5 SideBack 4 +6 DefensiveHalf 0 +7 OffensiveHalf -1 +8 OffensiveHalf 7 +9 SideForward -1 +10 SideForward 9 +11 CenterForward 0 +End Roles +Begin Samples 2 45 +----- 0 ----- +Ball 0 0 +1 -50 0 +2 -11.63 -4.6 +3 -11.9 4.06 +4 -10.09 -16.13 +5 -9.91 14.51 +6 -11.18 -0.36 +7 -6.58 -8.2 +8 -7.57 8.29 +9 -1.26 -11.99 +10 -1.8 12.17 +11 11.72 0 +----- 1 ----- +Ball -54.44 -20.73 +1 -50 0 +2 -47.41 -10.72 +3 -45.24 -5.14 +4 -50.02 -17.21 +5 -45.6 3.88 +6 -39.73 -9.8 +7 -40.83 -15.77 +8 -30.82 6.85 +9 -24.78 -29.47 +10 -14.69 21.98 +11 -14.9 -5.27 +----- 2 ----- +Ball -54.44 20.73 +1 -50 0 +2 -45.24 5.14 +3 -47.41 10.72 +4 -45.6 -3.88 +5 -50.02 17.21 +6 -39.73 9.8 +7 -30.82 -6.85 +8 -40.83 15.77 +9 -14.69 -21.98 +10 -24.78 29.47 +11 -14.9 5.27 +----- 3 ----- +Ball 45.24 0 +1 -50 0 +2 -0.18 -6.92 +3 -0.18 6.92 +4 6.47 -17.34 +5 6.47 17.34 +6 15.5 -0 +7 31.36 -6.2 +8 31.36 6.2 +9 36.39 -9.79 +10 36.39 9.79 +11 35.58 0.54 +----- 4 ----- +Ball -31.36 0 +1 -50 0 +2 -41.28 -3.97 +3 -41.19 3.97 +4 -40.38 -8.74 +5 -40.02 8.47 +6 -41.46 -0.27 +7 -34.88 -9.1 +8 -34.34 9.19 +9 -10.86 -21.61 +10 -10.75 21.75 +11 -19.2 0.27 +----- 5 ----- +Ball 22.08 0 +1 -50 0 +2 -0.09 -9.37 +3 0.18 7.75 +4 0.09 -19.47 +5 0.27 19.02 +6 1.35 0 +7 10.63 -6.13 +8 10.45 5.5 +9 13.16 -10.72 +10 13.43 11.36 +11 10.63 -0.27 +----- 6 ----- +Ball 11.72 0 +1 -50 0 +2 -2.25 -6.67 +3 -2.61 4.6 +4 -1.53 -14.15 +5 -1.44 14.78 +6 -1.44 -0.18 +7 1.98 -8.92 +8 1.71 8.56 +9 7.84 -13.16 +10 8.65 12.89 +11 0.99 0 +----- 7 ----- +Ball -15.95 -22.98 +1 -50 0 +2 -28.75 -17.21 +3 -28.57 -6.02 +4 -28.66 -24.78 +5 -28.3 4.67 +6 -25.34 -11.68 +7 -25.88 -20.3 +8 -21.02 -7.1 +9 0.09 -24.26 +10 2.02 15.41 +11 -12.76 -6.65 +----- 8 ----- +Ball -15.95 22.98 +1 -50 0 +2 -28.57 6.02 +3 -28.75 17.21 +4 -28.3 -4.67 +5 -28.66 24.78 +6 -25.34 11.68 +7 -21.02 7.1 +8 -25.88 20.3 +9 2.02 -15.41 +10 0.09 24.26 +11 -12.76 6.65 +----- 9 ----- +Ball 30.73 -36 +1 -50 0 +2 0.18 -11.95 +3 0.09 0.63 +4 0.45 -25.43 +5 3.05 8.45 +6 8.27 -18.15 +7 18.6 -28.03 +8 22.55 -14.64 +9 20.84 -33.15 +10 29.56 -5.66 +11 23.09 -22.91 +----- 10 ----- +Ball 30.73 36 +1 -50 0 +2 0.09 -0.63 +3 0.18 11.95 +4 3.05 -8.45 +5 0.45 25.43 +6 8.27 18.15 +7 22.55 14.64 +8 18.6 28.03 +9 29.56 5.66 +10 20.84 33.15 +11 23.09 22.91 +----- 11 ----- +Ball -54.5 -36 +1 -50 0 +2 -45.64 -14.73 +3 -45.24 -5.14 +4 -49.75 -24.6 +5 -45.6 3.88 +6 -39.84 -15.59 +7 -41.33 -23.81 +8 -34.88 -0.09 +9 -34.41 -31.45 +10 -13.66 14.2 +11 -15.54 -11.23 +----- 12 ----- +Ball -54.5 36 +1 -50 0 +2 -45.24 5.14 +3 -45.64 14.73 +4 -45.6 -3.88 +5 -49.75 24.6 +6 -39.84 15.59 +7 -34.88 0.09 +8 -41.33 23.81 +9 -13.66 -14.2 +10 -34.41 31.45 +11 -15.54 11.23 +----- 13 ----- +Ball -35.51 -20.1 +1 -50 0 +2 -35.85 -6.47 +3 -35.94 -0.54 +4 -35.51 -10.18 +5 -35.85 6.83 +6 -31.54 -9.91 +7 -26.05 -14.96 +8 -28.84 -0.72 +9 -21.72 -27.22 +10 -4.96 16.58 +11 -12.35 -9.19 +----- 14 ----- +Ball -35.51 20.1 +1 -50 0 +2 -35.94 0.54 +3 -35.85 6.47 +4 -35.85 -6.83 +5 -35.51 10.18 +6 -31.54 9.91 +7 -28.84 0.72 +8 -26.05 14.96 +9 -4.96 -16.58 +10 -21.72 27.22 +11 -12.35 9.19 +----- 15 ----- +Ball 40.11 -36 +1 -50 0 +2 -0.63 -13.93 +3 0.45 -1.08 +4 9.7 -29.65 +5 4.94 5.48 +6 11.18 -14.06 +7 26.5 -27.94 +8 29.92 -16.62 +9 31.09 -34.7 +10 32.43 -4.67 +11 31.98 -25.52 +----- 16 ----- +Ball 40.11 36 +1 -50 0 +2 0.45 1.08 +3 -0.63 13.93 +4 4.94 -5.48 +5 9.7 29.65 +6 11.18 14.06 +7 29.92 16.62 +8 26.5 27.94 +9 32.43 4.67 +10 31.09 34.7 +11 31.98 25.52 +----- 17 ----- +Ball 54.5 -36 +1 -50 0 +2 -0.54 -11.68 +3 0.09 0.09 +4 12.58 -26.14 +5 4.94 9.6 +6 20.39 -13.39 +7 35.49 -27.13 +8 35.49 -9.7 +9 39.53 -32.88 +10 46 -11.32 +11 37.2 -20.21 +----- 18 ----- +Ball 54.5 36 +1 -50 0 +2 0.09 -0.09 +3 -0.54 11.68 +4 4.94 -9.6 +5 12.58 26.14 +6 20.39 13.39 +7 35.49 9.7 +8 35.49 27.13 +9 46 11.32 +10 39.53 32.88 +11 37.2 20.21 +----- 19 ----- +Ball 19.65 -36 +1 -50 0 +2 -1.44 -23.27 +3 -0.36 -0.63 +4 -0.45 -32.99 +5 -0.09 10.51 +6 1.17 -17.16 +7 2.34 -27.67 +8 8.36 -11.86 +9 10.27 -29.92 +10 15.18 -1.26 +11 12.85 -22.73 +----- 20 ----- +Ball 19.65 36 +1 -50 0 +2 -0.36 0.63 +3 -1.44 23.27 +4 -0.09 -10.51 +5 -0.45 32.99 +6 1.17 17.16 +7 8.36 11.86 +8 2.34 27.67 +9 15.18 1.26 +10 10.27 29.92 +11 12.85 22.73 +----- 21 ----- +Ball 0.27 -36 +1 -50 0 +2 -18.57 -23.88 +3 -18.93 -8.47 +4 -15.68 -32.72 +5 -15.77 4.15 +6 -12.98 -19.2 +7 -12.08 -27.58 +8 -6.67 -7.48 +9 -6.58 -22.8 +10 8.56 9.37 +11 0.09 -18.75 +----- 22 ----- +Ball 0.27 36 +1 -50 0 +2 -18.93 8.47 +3 -18.57 23.88 +4 -15.77 -4.15 +5 -15.68 32.72 +6 -12.98 19.2 +7 -6.67 7.48 +8 -12.08 27.58 +9 8.56 -9.37 +10 -6.58 22.8 +11 0.09 18.75 +----- 23 ----- +Ball 5.59 -11.36 +1 -50 0 +2 -8.36 -12.85 +3 -7.1 -4.4 +4 -6.92 -21.11 +5 -5.59 6.22 +6 -5.12 -10.24 +7 -5.57 -17.61 +8 -1.98 -1.26 +9 -3.05 -19.68 +10 13.16 16.76 +11 6.94 -1.17 +----- 24 ----- +Ball 5.59 11.36 +1 -50 0 +2 -7.1 4.4 +3 -8.36 12.85 +4 -5.59 -6.22 +5 -6.92 21.11 +6 -5.12 10.24 +7 -1.98 1.26 +8 -5.57 17.61 +9 13.16 -16.76 +10 -3.05 19.68 +11 6.94 1.17 +----- 25 ----- +Ball 5.32 -20.37 +1 -50 0 +2 -9.61 -19.5 +3 -7.66 -7.93 +4 -8.45 -27.04 +5 -7.12 5.05 +6 -6.94 -13.52 +7 -6.65 -23.36 +8 -0.72 -4.15 +9 -5.93 -18.42 +10 11.43 14.05 +11 5.59 -8.29 +----- 26 ----- +Ball 5.32 20.37 +1 -50 0 +2 -7.66 7.93 +3 -9.61 19.5 +4 -7.12 -5.05 +5 -8.45 27.04 +6 -6.94 13.52 +7 -0.72 4.15 +8 -6.65 23.36 +9 11.43 -14.05 +10 -5.93 18.42 +11 5.59 8.29 +----- 27 ----- +Ball 6.04 -27.85 +1 -50 0 +2 -6.02 -21.65 +3 -5.77 -10.36 +4 -6.65 -29.47 +5 -4.69 1.98 +6 -2.34 -16.53 +7 -4.04 -25.43 +8 0.81 -3.7 +9 -1.35 -21.02 +10 11.65 12.19 +11 8.47 -14.06 +----- 28 ----- +Ball 6.04 27.85 +1 -50 0 +2 -5.77 10.36 +3 -6.02 21.65 +4 -4.69 -1.98 +5 -6.65 29.47 +6 -2.34 16.53 +7 0.81 3.7 +8 -4.04 25.43 +9 11.65 -12.19 +10 -1.35 21.02 +11 8.47 14.06 +----- 29 ----- +Ball 43.71 -26.77 +1 -50 0 +2 -0.54 -11.68 +3 0.09 0.09 +4 3.15 -22.59 +5 4.94 9.6 +6 18.69 -12.31 +7 27.58 -23.72 +8 32.7 -10.78 +9 31.36 -28.03 +10 40.79 -5.75 +11 31.98 -19.5 +----- 30 ----- +Ball 43.71 26.77 +1 -50 0 +2 0.09 -0.09 +3 -0.54 11.68 +4 4.94 -9.6 +5 3.15 22.59 +6 18.69 12.31 +7 32.7 10.78 +8 27.58 23.72 +9 40.79 5.75 +10 31.36 28.03 +11 31.98 19.5 +----- 31 ----- +Ball 21.9 -12.8 +1 -50 0 +2 0.08 -11.13 +3 0.03 2.01 +4 0.18 -23.54 +5 0.45 13.08 +6 7.55 -6.2 +7 7.28 -16.26 +8 11.5 -1.71 +9 11.86 -21.47 +10 21.65 15 +11 10.42 -11.05 +----- 32 ----- +Ball 21.9 12.8 +1 -50 0 +2 0.03 -2.01 +3 0.08 11.13 +4 0.45 -13.08 +5 0.18 23.54 +6 7.55 6.2 +7 11.5 1.71 +8 7.28 16.26 +9 21.65 -15 +10 11.86 21.47 +11 10.42 11.05 +----- 33 ----- +Ball 21.27 -21.99 +1 -50 0 +2 0.18 -13.61 +3 0.27 -3.24 +4 -0.18 -27.58 +5 0.54 8.56 +6 9.55 -10.09 +7 7.75 -20.55 +8 12.71 -7.39 +9 9.91 -25.05 +10 20.64 -0.99 +11 9.64 -16.49 +----- 34 ----- +Ball 21.27 21.99 +1 -50 0 +2 0.27 3.24 +3 0.18 13.61 +4 0.54 -8.56 +5 -0.18 27.58 +6 9.55 10.09 +7 12.71 7.39 +8 7.75 20.55 +9 20.64 0.99 +10 9.91 25.05 +11 9.64 16.49 +----- 35 ----- +Ball 35.69 -8.56 +1 -50 0 +2 -0.45 -10.42 +3 0.63 3.5 +4 3.45 -20.89 +5 5.09 13.48 +6 9.25 -3.23 +7 22.01 -14.46 +8 23.09 -3.41 +9 25.79 -17.88 +10 26.32 3.68 +11 24.51 -8.92 +----- 36 ----- +Ball 35.69 8.56 +1 -50 0 +2 0.63 -3.5 +3 -0.45 10.42 +4 5.09 -13.48 +5 3.45 20.89 +6 9.25 3.23 +7 23.09 3.41 +8 22.01 14.46 +9 26.32 -3.68 +10 25.79 17.88 +11 24.51 8.92 +----- 37 ----- +Ball 31 -28.39 +1 -50 0 +2 0.18 -10.36 +3 2.88 0.27 +4 0.36 -22.89 +5 9.88 9.61 +6 10.42 -9.34 +7 17.66 -23.88 +8 22.64 -15.18 +9 18.39 -28.75 +10 25.34 -2.16 +11 19.56 -19.47 +----- 38 ----- +Ball 31 28.39 +1 -50 0 +2 2.88 -0.27 +3 0.18 10.36 +4 9.88 -9.61 +5 0.36 22.89 +6 10.42 9.34 +7 22.64 15.18 +8 17.66 23.88 +9 25.34 2.16 +10 18.39 28.75 +11 19.56 19.47 +----- 39 ----- +Ball -25.96 -36 +1 -50 0 +2 -38.99 -16.71 +3 -37.91 -6.56 +4 -39.53 -27.31 +5 -36.57 4.94 +6 -32.43 -17.43 +7 -34.23 -24.89 +8 -19.65 -5.68 +9 -25.25 -22.37 +10 0.05 16.07 +11 -2.05 -11.45 +----- 40 ----- +Ball -25.96 36 +1 -50 0 +2 -37.91 6.56 +3 -38.99 16.71 +4 -36.57 -4.94 +5 -39.53 27.31 +6 -32.43 17.43 +7 -19.65 5.68 +8 -34.23 24.89 +9 0.05 -16.07 +10 -25.25 22.37 +11 -2.05 11.45 +----- 41 ----- +Ball -34.7 -36 +1 -50 -0 +2 -45.42 -16.67 +3 -41.17 -6.38 +4 -43.38 -26.2 +5 -42.27 5.02 +6 -40.74 -16.42 +7 -38.7 -22.37 +8 -26.68 -5.3 +9 -30.37 -23.56 +10 -6.21 10.04 +11 -15.91 -13.86 +----- 42 ----- +Ball -34.7 36 +1 -50 0 +2 -41.17 6.38 +3 -45.42 16.67 +4 -42.27 -5.02 +5 -43.38 26.2 +6 -40.74 16.42 +7 -26.68 5.3 +8 -38.7 22.37 +9 -6.21 -10.04 +10 -30.37 23.56 +11 -15.91 13.86 +----- 43 ----- +Ball -16.13 -36 +1 -50 0 +2 -30.37 -20.37 +3 -29.74 -7.57 +4 -30.19 -31.27 +5 -25.42 2.88 +6 -24.53 -18.78 +7 -26.86 -26.5 +8 -14.15 -4.87 +9 -17.07 -21.11 +10 3.1 15.08 +11 0.51 -11.31 +----- 44 ----- +Ball -16.13 36 +1 -50 0 +2 -29.74 7.57 +3 -30.37 20.37 +4 -25.42 -2.88 +5 -30.19 31.27 +6 -24.53 18.78 +7 -14.15 4.87 +8 -26.86 26.5 +9 3.1 -15.08 +10 -17.07 21.11 +11 0.51 11.31 +End Samples +End diff --git a/keepaway/base/formation_dt/setplay_our_formation.conf b/keepaway/base/formation_dt/setplay_our_formation.conf new file mode 100755 index 00000000..537c4835 --- /dev/null +++ b/keepaway/base/formation_dt/setplay_our_formation.conf @@ -0,0 +1,615 @@ +Formation DelaunayTriangulation 2 +Begin Roles +1 Goalie 0 +2 CenterBack -1 +3 CenterBack 2 +4 SideBack -1 +5 SideBack 4 +6 DefensiveHalf 0 +7 OffensiveHalf -1 +8 OffensiveHalf 7 +9 SideForward -1 +10 SideForward 9 +11 CenterForward 0 +End Roles +Begin Samples 2 46 +----- 0 ----- +Ball 0 0 +1 -50 0 +2 -15.22 -4.84 +3 -15.33 3.66 +4 -9.29 -15.12 +5 -10.84 13.69 +6 -0.71 -0.36 +7 0 -6.97 +8 0.48 6.73 +9 13.69 -20.13 +10 10.73 24 +11 16.08 0 +----- 1 ----- +Ball -54 0 +1 -50 -0 +2 -46.15 -1.92 +3 -46.03 2.88 +4 -45.07 -7.57 +5 -45.55 7.93 +6 -39.78 0 +7 -36.3 -15.98 +8 -37.02 11.78 +9 -22 -28 +10 -19.29 26.44 +11 -16.47 -0.84 +----- 2 ----- +Ball 36.26 0 +1 -50 0 +2 0 -7.38 +3 0 7.38 +4 4 -17.7 +5 4 17.7 +6 15.24 0.48 +7 27.75 -8.1 +8 28.1 9.76 +9 35.96 -16.2 +10 36.56 16.91 +11 35.61 0 +----- 3 ----- +Ball -41.78 0 +1 -50 0 +2 -42.99 -0.71 +3 -42.91 4.21 +4 -42.31 -7.69 +5 -42.91 9.01 +6 -36.66 0.36 +7 -33.41 -10.82 +8 -31.61 10.94 +9 -15.48 -29.41 +10 -17.51 27.15 +11 -18.27 -0.36 +----- 4 ----- +Ball -26.95 0 +1 -50 0 +2 -30.96 -1.91 +3 -30.25 7.74 +4 -29.9 -11.32 +5 -28.25 16.41 +6 -15.27 -7.63 +7 -17.43 -23.41 +8 -15.52 9.16 +9 -4.76 -27.75 +10 -5.34 26.47 +11 -0.51 0.51 +----- 5 ----- +Ball -17.5 0 +1 -50 0 +2 -23.22 -1.67 +3 -23.2 3.73 +4 -22.84 -8.89 +5 -22.96 12.26 +6 -16.1 1.2 +7 -11.9 -4.69 +8 -10.94 6.37 +9 1.2 -15.38 +10 0.12 18.51 +11 2.4 0 +----- 6 ----- +Ball 7.67 0 +1 -50 0 +2 -5.9 -5.31 +3 -6.02 5.19 +4 -3.07 -16.04 +5 -2.95 16.39 +6 6.25 -0.36 +7 11.66 -5.77 +8 11.78 6.13 +9 21.76 -22.52 +10 22.78 23.54 +11 17.43 0 +----- 7 ----- +Ball 49.5 -20.51 +1 -50 -0 +2 3 -8.85 +3 0 5 +4 5 -20 +5 12 15.5 +6 21.04 -6.95 +7 32.8 -18.96 +8 35.1 0.46 +9 45.46 -24.17 +10 44.84 10.61 +11 45.68 -10.85 +----- 8 ----- +Ball 49.5 20.51 +1 -50 0 +2 0 -5 +3 3 8.85 +4 12 -15.5 +5 5 20 +6 21.04 6.95 +7 35.1 -0.46 +8 32.8 18.96 +9 44.84 -10.61 +10 45.46 24.17 +11 45.68 10.85 +----- 9 ----- +Ball -54 -10 +1 -50.57 -6.44 +2 -48.18 -5.96 +3 -48.06 -1.07 +4 -49.5 -9.06 +5 -47.94 4.06 +6 -44.37 -2.62 +7 -43.73 -9.47 +8 -31.39 8.23 +9 -24.81 -29.58 +10 -18.82 25.56 +11 -20.87 -8.59 +----- 10 ----- +Ball -54 10 +1 -50.57 6.44 +2 -48.06 1.07 +3 -48.18 5.96 +4 -47.94 -4.06 +5 -49.5 9.06 +6 -44.37 2.62 +7 -31.39 -8.23 +8 -43.73 9.47 +9 -18.82 -25.56 +10 -24.81 29.58 +11 -20.87 8.59 +----- 11 ----- +Ball -6 0 +1 -50 0 +2 -9.67 -0.25 +3 -14 4.58 +4 -13.23 -12.34 +5 -8.91 12.34 +6 -5.09 -9.92 +7 -0.51 -21.38 +8 1.65 11.71 +9 10.26 -24 +10 10.73 24 +11 4.84 0 +----- 12 ----- +Ball -12 0 +1 -50 0 +2 -19.35 -3.12 +3 -19.11 3 +4 -17.91 -11.18 +5 -17.55 11.06 +6 -13.22 0.12 +7 -6.97 -6.85 +8 -7.57 5.29 +9 1.92 -21.51 +10 0.84 22.23 +11 5.65 0.36 +----- 13 ----- +Ball 35.78 -9.54 +1 -50 0 +2 1.68 -7.93 +3 4.93 6.85 +4 5 -20 +5 12.26 16.47 +6 16.55 -2.26 +7 21.79 -15.36 +8 26.44 1.07 +9 35.73 -17.03 +10 35.13 8.57 +11 33.94 -8.57 +----- 14 ----- +Ball 35.78 9.54 +1 -50 -0 +2 4.93 -7.33 +3 2.16 8.65 +4 12 -15.5 +5 5 20 +6 16.55 2.26 +7 26.44 -1.07 +8 21.79 15.36 +9 35.13 -8.57 +10 35.73 17.03 +11 33.94 8.57 +----- 15 ----- +Ball 54 -35 +1 -50 0 +2 -0.89 -12.98 +3 6.36 2.29 +4 8.3 -27.3 +5 18.32 10.56 +6 20.16 -10.85 +7 38.11 -27.75 +8 38.3 -4.2 +9 47.63 -30.13 +10 47.28 -2.38 +11 48.47 -19.77 +----- 16 ----- +Ball 54 35 +1 -50 -0 +2 6.36 -2.29 +3 -0.89 12.98 +4 18.32 -10.56 +5 8.3 27.3 +6 20.16 10.85 +7 38.3 4.2 +8 38.11 27.75 +9 47.28 2.38 +10 47.63 30.13 +11 48.47 19.77 +----- 17 ----- +Ball -12 -35 +1 -50 0 +2 -17.51 -22.27 +3 -18.01 -8.59 +4 -13.46 -32.39 +5 -18.72 4.06 +6 -7.5 -15.36 +7 -8.65 -28.96 +8 -5.96 0.12 +9 4.17 -31.84 +10 0.95 18.25 +11 5.48 -7.15 +----- 18 ----- +Ball -12 35 +1 -50 -0 +2 -18.01 8.59 +3 -17.51 22.27 +4 -18.72 -4.06 +5 -13.46 32.39 +6 -7.5 15.36 +7 -5.96 -0.12 +8 -7.57 30.17 +9 0.95 -18.25 +10 4.17 31.84 +11 5.48 7.15 +----- 19 ----- +Ball -36.02 -35 +1 -50 -0.01 +2 -37.79 -14.51 +3 -38.05 -7.25 +4 -36.32 -30.49 +5 -37.92 1.27 +6 -30.54 -19.34 +7 -26.08 -24.69 +8 -20.16 0.6 +9 -12.72 -32.32 +10 -7.44 19.44 +11 -7.62 -11.95 +----- 20 ----- +Ball -36.02 35 +1 -50 0.01 +2 -38.05 7.25 +3 -37.79 14.51 +4 -37.92 -1.27 +5 -36.32 30.49 +6 -30.54 19.34 +7 -20.16 -0.6 +8 -26.08 24.69 +9 -7.44 -19.44 +10 -12.72 32.32 +11 -7.62 11.95 +----- 21 ----- +Ball -54 -35 +1 -50 0 +2 -46.83 -11.96 +3 -46.51 -4.65 +4 -50.73 -32.15 +5 -45.56 4.77 +6 -41.99 -15.01 +7 -35.76 -22.52 +8 -23.79 0.76 +9 -22.39 -31.81 +10 -12.98 19.47 +11 -16.92 -13.11 +----- 22 ----- +Ball -54 35 +1 -50 -0 +2 -46.51 4.65 +3 -46.83 11.96 +4 -45.56 -4.77 +5 -50.73 32.15 +6 -41.99 15.01 +7 -23.79 -0.76 +8 -35.76 22.52 +9 -12.98 -19.47 +10 -22.39 31.81 +11 -16.92 13.11 +----- 23 ----- +Ball -17.5 -11 +1 -50 0 +2 -26.59 -10.18 +3 -26.47 -3.94 +4 -26.72 -14.63 +5 -26.21 2.54 +6 -19.98 -11.07 +7 -17.43 -18.96 +8 -15.27 -1.4 +9 -4.07 -30.92 +10 -1.02 17.69 +11 -3.82 -10.69 +----- 24 ----- +Ball -17.5 11 +1 -50 -0 +2 -26.47 3.94 +3 -26.59 10.18 +4 -26.21 -2.54 +5 -26.72 14.63 +6 -19.98 11.07 +7 -15.27 1.4 +8 -17.43 18.96 +9 -1.02 -17.69 +10 -4.07 30.92 +11 -3.82 10.69 +----- 25 ----- +Ball 36.08 -20.6 +1 -50 0 +2 2.57 -11.68 +3 0 2.08 +4 4.57 -22.24 +5 9.42 13.34 +6 17.57 -12.21 +7 32.96 -16.03 +8 28.91 -1.62 +9 38.94 -20.74 +10 39.06 2.16 +11 37.92 -10.18 +----- 26 ----- +Ball 36.08 20.6 +1 -50 -0 +2 0 -2.08 +3 2.57 11.68 +4 9.42 -13.34 +5 4.57 22.24 +6 17.57 12.21 +7 28.91 1.62 +8 32.96 16.03 +9 39.06 -2.16 +10 38.94 20.74 +11 37.92 10.18 +----- 27 ----- +Ball -26.95 -11 +1 -50 0 +2 -30.25 -11.07 +3 -29.69 -2.28 +4 -29.81 -18.03 +5 -26.92 7.69 +6 -21.75 -7.69 +7 -18.87 -29.81 +8 -12.5 5.05 +9 -4.64 -30.01 +10 -3.73 23.56 +11 -6.61 -10.94 +----- 28 ----- +Ball -26.95 11 +1 -50 -0 +2 -29.69 2.28 +3 -30.25 11.07 +4 -26.92 -7.69 +5 -29.81 18.03 +6 -21.75 7.69 +7 -12.5 -5.05 +8 -18.87 29.81 +9 -3.73 -23.56 +10 -4.64 30.01 +11 -6.61 10.94 +----- 29 ----- +Ball -48.97 -17.73 +1 -50 -7 +2 -47.24 -6.8 +3 -46.76 -0.19 +4 -46.68 -16.79 +5 -46.57 4.31 +6 -40.62 -8.41 +7 -37.14 -28.12 +8 -35.33 4.21 +9 -18.7 -31.91 +10 -20.48 23.1 +11 -17.62 -9.17 +----- 30 ----- +Ball -48.97 17.73 +1 -50 7 +2 -46.76 0.19 +3 -47.24 6.8 +4 -46.57 -4.31 +5 -46.68 16.79 +6 -40.62 8.41 +7 -35.33 -4.21 +8 -37.14 28.12 +9 -20.48 -23.1 +10 -18.7 31.91 +11 -17.62 9.17 +----- 31 ----- +Ball -26.95 -31.61 +1 -50 -0.02 +2 -30.65 -16.82 +3 -30.77 -6.44 +4 -30.06 -29.1 +5 -30.29 3.7 +6 -21.03 -15.74 +7 -24.04 -26.08 +8 -15.24 -1.07 +9 -4.96 -32.07 +10 -7.86 21.08 +11 -0.24 -11.31 +----- 32 ----- +Ball -26.95 31.61 +1 -50 0.02 +2 -30.77 6.44 +3 -30.65 16.82 +4 -30.29 -3.7 +5 -30.06 29.1 +6 -21.03 15.74 +7 -15.24 1.07 +8 -24.04 26.08 +9 -7.86 -21.08 +10 -4.96 32.07 +11 -0.24 11.31 +----- 33 ----- +Ball 15.14 0 +1 -50 0 +2 0.13 -4.07 +3 0.25 4.45 +4 0 -16.13 +5 0 16.13 +6 13.34 0.24 +7 17.07 -7.45 +8 18.15 6.85 +9 22.63 -28.94 +10 24.41 28.34 +11 22.59 0.12 +----- 34 ----- +Ball 23.26 -21.95 +1 -50 0 +2 2 -15.44 +3 4.45 -1.2 +4 4 -25.21 +5 7.81 9.98 +6 13.81 -12.62 +7 20.31 -21.63 +8 20.24 1.31 +9 28.58 -31.08 +10 27.63 19.29 +11 28.22 -12.15 +----- 35 ----- +Ball 23.26 21.95 +1 -50 -0 +2 4.45 1.2 +3 2 15.44 +4 7.81 -9.98 +5 4 25.21 +6 13.81 12.62 +7 20.24 -1.31 +8 20.31 21.63 +9 27.63 -19.29 +10 28.58 31.08 +11 28.22 12.15 +----- 36 ----- +Ball 0 -11 +1 -50 0 +2 -10.84 -11.19 +3 -10.46 -3.85 +4 -9.65 -21.67 +5 -9.54 9.16 +6 -1.32 -11.18 +7 1.44 -16.35 +8 2.16 0.96 +9 14.12 -27.99 +10 14.51 16.92 +11 12.72 -8.91 +----- 37 ----- +Ball 0 11 +1 -50 -0 +2 -10.46 3.85 +3 -10.84 11.19 +4 -9.54 -9.16 +5 -9.65 21.67 +6 -1.32 11.18 +7 2.16 -0.96 +8 1.44 16.35 +9 14.51 -16.92 +10 14.12 27.99 +11 12.72 8.91 +----- 38 ----- +Ball 0 -35 +1 -50 0 +2 -7.81 -20.31 +3 -8.65 -10.46 +4 -2.16 -33.05 +5 -5.37 4.29 +6 -0.48 -16.47 +7 2.88 -28.72 +8 5.49 -0.12 +9 15.14 -32.93 +10 14.89 18.34 +11 14.89 -12.5 +----- 39 ----- +Ball 0 35 +1 -50 -0 +2 -8.65 10.46 +3 -7.81 20.31 +4 -5.37 -4.29 +5 -2.16 33.05 +6 -0.48 16.47 +7 5.49 0.12 +8 2.88 28.72 +9 14.89 -18.34 +10 15.14 32.93 +11 14.89 12.5 +----- 40 ----- +Ball 22.74 -30.01 +1 -50 0 +2 -0.96 -16.59 +3 1.68 -2.28 +4 1.85 -27.71 +5 4.21 8.37 +6 12.38 -17.15 +7 21.03 -29.57 +8 18.62 0.38 +9 26.08 -24.88 +10 27 16.09 +11 27.75 -9.17 +----- 41 ----- +Ball 22.74 30.01 +1 -50 -0 +2 1.68 2.28 +3 -0.96 16.59 +4 4.21 -8.37 +5 1.85 27.71 +6 12.38 17.15 +7 18.62 -0.38 +8 21.03 29.57 +9 27 -16.09 +10 26.08 24.88 +11 27.75 9.17 +----- 42 ----- +Ball -12 -19 +1 -50 0 +2 -14.06 -18.51 +3 -18.1 -7.15 +4 -16.95 -24.28 +5 -17.43 3.61 +6 -8.29 -12.74 +7 -5.89 -20.67 +8 -1.53 3.94 +9 3.45 -30.49 +10 3.33 25.13 +11 2.38 -8.1 +----- 43 ----- +Ball -12 19 +1 -50 -0 +2 -18.1 7.15 +3 -14.06 18.51 +4 -17.43 -3.61 +5 -16.95 24.28 +6 -8.29 12.74 +7 -1.53 -3.94 +8 -5.89 20.67 +9 3.33 -25.13 +10 3.45 30.49 +11 2.38 8.1 +----- 44 ----- +Ball -42.1 -28.03 +1 -50 -0.04 +2 -42.16 -14.65 +3 -41.86 -6.87 +4 -42.99 -26.08 +5 -40.72 1.53 +6 -35.21 -15.5 +7 -37.26 -22.84 +8 -22.03 1.19 +9 -18.99 -32.33 +10 -12.76 21.71 +11 -10.36 -13.93 +----- 45 ----- +Ball -42.1 28.03 +1 -50 0.04 +2 -41.86 6.87 +3 -42.16 14.65 +4 -40.72 -1.53 +5 -42.99 26.08 +6 -35.21 15.5 +7 -22.03 -1.19 +8 -37.26 22.84 +9 -12.76 -21.71 +10 -18.99 32.33 +11 -10.36 13.93 +End Samples +End diff --git a/keepaway/base/generator_action.py b/keepaway/base/generator_action.py new file mode 100755 index 00000000..df723f93 --- /dev/null +++ b/keepaway/base/generator_action.py @@ -0,0 +1,142 @@ +from enum import Enum +from pyrusgeom.geom_2d import * + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + + +class KickActionType(Enum): + No = "0" + Pass = "Pass" + Dribble = "Dribble" + Clear = "Clear" + + +class KickAction: + def __init__(self): + self.target_ball_pos = Vector2D.invalid() + self.start_ball_pos = Vector2D.invalid() + self.target_unum = 0 + self.start_unum = 0 + self.start_ball_speed = 0 + self.type = KickActionType.No + self.eval = -1000 + self.index = 0 + self.min_opp_dist = 0.0 + + def calculate_min_opp_dist(self, wm: "WorldModel" = None): + if wm is None: + return 0.0 + for opp in wm.opponents(): + pass + # print(opp) + # if opp is not None and opp.unum() > 0 + return min( + [ + opp.pos().dist(self.target_ball_pos) + for opp in wm.opponents() + if opp is not None and opp.unum() > 0 + ] + ) + + def evaluate(self, wm: "WorldModel" = None): + ## changed 52 to 10 + self.min_opp_dist = self.calculate_min_opp_dist(wm) + self.eval = self.target_ball_pos.x() + max( + 0.0, 40.0 - self.target_ball_pos.dist(Vector2D(10, 0)) + ) + if self.min_opp_dist < 5.0: + self.eval -= list([30, 20, 10, 5, 2])[int(self.min_opp_dist)] + + def __gt__(self, other): + return self.eval > other.eval + + def __repr__(self): + return "{} Action {} to {} in ({}, {}) eval:{}".format( + self.type.value, + self.start_unum, + self.target_unum, + round(self.target_ball_pos.x(), 2), + round(self.target_ball_pos.y(), 2), + self.eval, + ) + + def __str__(self): + return self.__repr__() + + +class TackleAction: + def __init__(self, angle: AngleDeg = None, vel: Vector2D = None): + if angle is not None and vel is not None: + self._tackle_angle: AngleDeg = AngleDeg(angle) + self._ball_vel: Vector2D = vel.copy() + self._ball_speed: float = vel.r() + self._ball_move_angle: AngleDeg = vel.th() + self._score: float = 0.0 + return + self._tackle_angle: AngleDeg = AngleDeg(0) + self._ball_vel: Vector2D = Vector2D(0, 0) + self._ball_speed: float = 0.0 + self._ball_move_angle: AngleDeg = AngleDeg(0) + self._score: float = -float("inf") + + def clear(self): + self._tackle_angle: AngleDeg = AngleDeg(0) + self._ball_vel: Vector2D = Vector2D(0, 0) + self._ball_speed: float = 0.0 + self._ball_move_angle: AngleDeg = AngleDeg(0) + self._score: float = -float("inf") + + +class ShootAction: + def __init__( + self, + index, + target_point, + first_ball_speed, + ball_move_angle, + ball_move_dist, + ball_reach_step, + ): + self.index = index + self.target_point: Vector2D = target_point + self.first_ball_vel: Vector2D = Vector2D.polar2vector( + first_ball_speed, ball_move_angle + ) + self.first_ball_speed = first_ball_speed + self.ball_move_angle: AngleDeg = ball_move_angle + self.ball_move_dist = ball_move_dist + self.ball_reach_step = ball_reach_step + self.goalie_never_reach = True + self.opponent_never_reach = True + self.kick_step = 3 + self.score = 0 + + def __repr__(self): + return "{} target {} first_vel {}".format( + self.index, self.target_point, self.first_ball_vel + ) + + def __str__(self): + return self.__repr__() + + +class BhvKickGen: + def __init__(self): + self.candidates: list = [] + self.index = 0 + self.debug_list = [] + + def can_opponent_cut_ball(self, wm: "WorldModel", ball_pos, cycle): + for unum in range(1, 12): + opp: "PlayerObject" = wm.their_player(unum) + if opp.unum() == 0: + continue + opp_cycle = opp.pos().dist(ball_pos) - opp.player_type().kickable_area() + opp_cycle /= opp.player_type().real_speed_max() + if opp_cycle < cycle: + return True + return False diff --git a/keepaway/base/generator_clear.py b/keepaway/base/generator_clear.py new file mode 100755 index 00000000..557542e7 --- /dev/null +++ b/keepaway/base/generator_clear.py @@ -0,0 +1,89 @@ +from pyrusgeom.geom_2d import * +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.server_param import ServerParam as SP +from keepaway.base.generator_action import KickAction, KickActionType, BhvKickGen +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +debug_clear_ball = True + + +class BhvClearGen(BhvKickGen): + def generator(self, wm: 'WorldModel'): + log.sw_log().clear().add_text("=========start clear ball generator") + self.generate_clear_ball(wm) + log.sw_log().clear().add_text("=========generated clear ball actions:") + for candid in self.candidates: + log.sw_log().clear().add_text(f'{candid.index} {candid.target_ball_pos} {candid.eval}') + if debug_clear_ball: + for candid in self.debug_list: + if candid[2]: + log.sw_log().clear().add_message(candid[1].x(), candid[1].y(), '{}'.format(candid[0])) + log.sw_log().clear().add_circle(circle=Circle2D(candid[1], 0.2), color=Color(string='green')) + else: + log.sw_log().clear().add_message(candid[1].x(), candid[1].y(), '{}'.format(candid[0])) + log.sw_log().clear().add_circle(circle=Circle2D(candid[1], 0.2), color=Color(string='red')) + return self.candidates + + def add_to_candidate(self, wm: 'WorldModel', ball_pos: Vector2D): + action = KickAction() + action.target_ball_pos = ball_pos + action.start_ball_pos = wm.ball().pos() + action.start_ball_speed = 2.5 + action.type = KickActionType.Clear + action.index = self.index + + action.eval = 0 + if ball_pos.x() > SP.i().pitch_half_length(): + action.eval = 200.0 + elif ball_pos.x() < -SP.i().pitch_half_length(): + if ball_pos.abs_y() < 10: + action.eval = -200.0 + else: + action.eval = -80.0 + elif ball_pos.abs_y() > SP.i().pitch_half_width(): + action.eval = ball_pos.x() + elif ball_pos.x() < -30 and ball_pos.abs_y() < 20: + action.eval = -100.0 + else: + action.eval = ball_pos.dist(Vector2D(-SP.i().pitch_half_length(), 0.0)) + self.candidates.append(action) + self.debug_list.append((self.index, ball_pos, True)) + self.index += 1 + + def generate_clear_ball(self, wm: 'WorldModel'): + angle_div = 16 + angle_step = 360.0 / angle_div + + for a in range(angle_div): + ball_pos = wm.ball().pos() + angle = AngleDeg(a * angle_step) + speed = 2.5 + log.sw_log().clear().add_text(f'========= a:{a} speed:{speed} angle:{angle} ball:{ball_pos}') + for c in range(30): + ball_pos += Vector2D.polar2vector(speed, angle) + log.sw_log().clear().add_text(f'--->>>{ball_pos}') + speed *= SP.i().ball_decay() + if ball_pos.x() > SP.i().pitch_half_length(): + break + if ball_pos.x() < -SP.i().pitch_half_length(): + break + if ball_pos.abs_y() > SP.i().pitch_half_width(): + break + receiver_opp = 0 + for opp in wm.opponents(): + if not opp: + continue + if opp.unum() <= 0: + continue + opp_cycle = opp.pos().dist(ball_pos) / opp.player_type().real_speed_max() - opp.player_type().kickable_area() + opp_cycle -= min(0, opp.pos_count()) + if opp_cycle <= c: + receiver_opp = opp.unum() + break + if receiver_opp != 0: + break + self.add_to_candidate(wm, ball_pos) + diff --git a/keepaway/base/generator_dribble.py b/keepaway/base/generator_dribble.py new file mode 100755 index 00000000..89df033c --- /dev/null +++ b/keepaway/base/generator_dribble.py @@ -0,0 +1,277 @@ +from pyrusgeom.geom_2d import * +import pyrusgeom.soccer_math as smath + +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.server_param import ServerParam as SP +from keepaway.base.tools import Tools +import time +from keepaway.base.generator_action import BhvKickGen, KickActionType, KickAction + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + +debug_dribble = False +max_dribble_time = 0 + + +class BhvDribbleGen(BhvKickGen): + def generator(self, wm: 'WorldModel'): + global max_dribble_time + start_time = time.time() + self.generate_simple_dribble(wm) + + if debug_dribble: + for dribble in self.debug_list: + if dribble[2]: + log.sw_log().dribble().add_message( dribble[1].x(), dribble[1].y(), '{}'.format(dribble[0])) + log.sw_log().dribble().add_circle( cicle=Circle2D(dribble[1], 0.2), + color=Color(string='green')) + else: + log.sw_log().dribble().add_message( dribble[1].x(), dribble[1].y(), '{}'.format(dribble[0])) + log.sw_log().dribble().add_circle( cicle=Circle2D(dribble[1], 0.2), + color=Color(string='red')) + end_time = time.time() + if end_time - start_time > max_dribble_time: + max_dribble_time = end_time - start_time + log.sw_log().dribble().add_text( 'time:{} max is {}'.format(end_time - start_time, max_dribble_time)) + return self.candidates + + def generate_simple_dribble(self, wm: 'WorldModel'): + angle_div = 16 + angle_step = 360.0 / angle_div + + sp = SP.i() + ptype = wm.self().player_type() + + my_first_speed = wm.self().vel().r() + + for a in range(angle_div): + dash_angle = wm.self().body() + (angle_step * a) + + if wm.self().pos().x() < 16.0 and dash_angle.abs() > 100.0: + if debug_dribble: + log.sw_log().dribble().add_text( '#dash angle:{} cancel is not safe1'.format(dash_angle)) + continue + + if wm.self().pos().x() < -36.0 and wm.self().pos().abs_y() < 20.0 and dash_angle.abs() > 45.0: + if debug_dribble: + log.sw_log().dribble().add_text( '#dash angle:{} cancel is not safe2'.format(dash_angle)) + continue + + n_turn = 0 + + my_speed = my_first_speed * ptype.player_decay() + dir_diff = AngleDeg(angle_step * a).abs() + + while dir_diff > 10.0: + dir_diff -= ptype.effective_turn(sp.max_moment(), my_speed) + if dir_diff < 0.0: + dir_diff = 0.0 + my_speed *= ptype.player_decay() + n_turn += 1 + + if n_turn >= 3: + continue + + if angle_step * a < 180.0: + dash_angle -= dir_diff + else: + dash_angle += dir_diff + if debug_dribble: + log.sw_log().dribble().add_text( '#dash angle:{} turn:{}'.format(dash_angle, n_turn)) + self.simulate_kick_turns_dashes(wm, dash_angle, n_turn) + + def simulate_kick_turns_dashes(self, wm: 'WorldModel', dash_angle, n_turn): + max_dash = 8 + min_dash = 2 + + self_cache = [] + + self.create_self_cache(wm, dash_angle, n_turn, max_dash, self_cache) + if debug_dribble: + log.sw_log().dribble().add_text( '##self_cache:{}'.format(self_cache)) + sp = SP.i() + ptype = wm.self().player_type() + + # trap_rel = Vector2D.polar2vector(ptype.playerSize() + ptype.kickableMargin() * 0.2 + SP.ball_size(), dash_angle) + trap_rel = Vector2D.polar2vector(ptype.player_size() + ptype.kickable_margin() * 0.2 + 0, dash_angle) + + max_x = sp.pitch_half_length() - 1.0 + max_y = sp.pitch_half_width() - 1.0 + + for n_dash in range(max_dash, min_dash - 1, -1): + self.index += 1 + ball_trap_pos:Vector2D = self_cache[n_turn + n_dash] + trap_rel + + if ball_trap_pos.abs_x() > max_x or ball_trap_pos.abs_y() > max_y: + if debug_dribble: + log.sw_log().dribble().add_text( + '#index:{} target:{} our of field'.format(self.index, ball_trap_pos)) + self.debug_list.append((self.index, ball_trap_pos, False)) + continue + + term = (1.0 - pow(sp.ball_decay(), 1 + n_turn + n_dash ) ) / (1.0 - sp.ball_decay()) + first_vel: Vector2D = (ball_trap_pos - wm.ball().pos()) / term + kick_accel: Vector2D = first_vel - wm.ball().vel() + kick_power = kick_accel.r() / wm.self().kick_rate() + + if kick_power > sp.max_power() or kick_accel.r2() > pow(sp.ball_accel_max(), 2) or first_vel.r2() > pow( + sp.ball_speed_max(), 2): + if debug_dribble: + log.sw_log().dribble().add_text( + '#index:{} target:{} need more power, power:{}, accel:{}, vel:{}'.format( + self.index, ball_trap_pos, kick_power, kick_accel, first_vel)) + self.debug_list.append((self.index, ball_trap_pos, False)) + continue + + if (wm.ball().pos() + first_vel).dist2(self_cache[0]) < pow(ptype.player_size() + sp.ball_size() + 0.1, 2): + if debug_dribble: + log.sw_log().dribble().add_text( + '#index:{} target:{} in body, power:{}, accel:{}, vel:{}'.format( + self.index, ball_trap_pos, kick_power, kick_accel, first_vel)) + self.debug_list.append((self.index, ball_trap_pos, False)) + continue + + if self.check_opponent(wm, ball_trap_pos, 1 + n_turn + n_dash): + candidate = KickAction() + candidate.type = KickActionType.Dribble + candidate.start_ball_pos = wm.ball().pos() + candidate.target_ball_pos = ball_trap_pos + candidate.target_unum = wm.self().unum() + candidate.start_ball_speed = first_vel.r() + candidate.index = self.index + candidate.evaluate(wm) + self.candidates.append(candidate) + if debug_dribble: + log.sw_log().dribble().add_text( + '#index:{} target:{}, power:{}, accel:{}, vel:{} OK'.format( + self.index, ball_trap_pos, kick_power, kick_accel, first_vel)) + self.debug_list.append((self.index, ball_trap_pos, True)) + else: + if debug_dribble: + log.sw_log().dribble().add_text( + '#index:{} target:{}, power:{}, accel:{}, vel:{} Opponent catch it'.format( + self.index, ball_trap_pos, kick_power, kick_accel, first_vel)) + self.debug_list.append((self.index, ball_trap_pos, False)) + + def create_self_cache(self, wm: 'WorldModel', dash_angle, n_turn, n_dash, self_cache): + sp = SP.i() + ptype = wm.self().player_type() + + self_cache.clear() + + stamina_model = wm.self().stamina_model() + + my_pos = wm.self().pos() + my_vel = wm.self().vel() + + my_pos += my_vel + my_vel *= ptype.player_decay() + + self_cache.append(Vector2D(my_pos)) + + for i in range(n_turn): + my_pos += my_vel + my_vel *= ptype.player_decay() + self_cache.append(Vector2D(my_pos)) + stamina_model.simulate_waits(ptype, 1 + n_turn) + + unit_vec = Vector2D.polar2vector(1.0, dash_angle) + + for i in range(n_dash): + available_stamina = max(0.0, stamina_model.stamina() - sp.recover_dec_thr_value() - 300.0) + dash_power = min(available_stamina, sp.max_dash_power()) + dash_accel = unit_vec.set_length_vector(dash_power * ptype.dash_power_rate() * stamina_model.effort()) + + my_vel += dash_accel + my_pos += my_vel + my_vel *= ptype.player_decay() + + stamina_model.simulate_dash(ptype, dash_power) + self_cache.append(Vector2D(my_pos)) + + def check_opponent(self, wm: 'WorldModel', ball_trap_pos: Vector2D, dribble_step: int): + sp = SP.i() + ball_move_angle:AngleDeg = (ball_trap_pos - wm.ball().pos()).th() + + for o in range(12): + opp: 'PlayerObject' = wm.their_player(o) + if opp is None or opp.unum() == 0: + if debug_dribble: + log.sw_log().dribble().add_text( "###OPP {} is ghost".format(o)) + continue + + if opp.dist_from_self() > 20.0: + if debug_dribble: + log.sw_log().dribble().add_text( "###OPP {} is far".format(o)) + continue + + ptype = opp.player_type() + + control_area = (sp._catchable_area + if opp.goalie() + and ball_trap_pos.x() > sp.their_penalty_area_line_x() + and ball_trap_pos.abs_y() < sp.penalty_area_half_width() + else ptype.kickable_area()) + + opp_pos = opp.inertia_point( dribble_step ) + + ball_to_opp_rel = (opp.pos() - wm.ball().pos()).rotated_vector(-ball_move_angle) + + if ball_to_opp_rel.x() < -4.0: + if debug_dribble: + log.sw_log().dribble().add_text( "###OPP {} is behind".format(o)) + continue + + target_dist = opp_pos.dist(ball_trap_pos) + + if target_dist - control_area < 0.001: + if debug_dribble: + log.sw_log().dribble().add_text( "###OPP {} Catch, ball will be in his body".format(o)) + return False + + dash_dist = target_dist + dash_dist -= control_area * 0.5 + dash_dist -= 0.2 + n_dash = ptype.cycles_to_reach_distance(dash_dist) + + n_turn = 1 if opp.body_count() > 1 else Tools.predict_player_turn_cycle(ptype, + opp.body(), + opp.vel().r(), + target_dist, + (ball_trap_pos - opp_pos).th(), + control_area, + True) + + n_step = n_turn + n_dash if n_turn == 0 else n_turn + n_dash + 1 + + bonus_step = 0 + if ball_trap_pos.x() < 30.0: + bonus_step += 1 + + if ball_trap_pos.x() < 0.0: + bonus_step += 1 + + if opp.is_tackling(): + bonus_step = -5 + + if ball_to_opp_rel.x() > 0.5: + bonus_step += smath.bound(0, opp.pos_count(), 8) + else: + bonus_step += smath.bound(0, opp.pos_count(), 4) + + if n_step - bonus_step <= dribble_step: + if debug_dribble: + log.sw_log().dribble().add_text( + "###OPP {} catch n_step:{}, dr_step:{}, bonas:{}".format(o, n_step, dribble_step, + bonus_step)) + return False + else: + if debug_dribble: + log.sw_log().dribble().add_text( + "###OPP {} can't catch n_step:{}, dr_step:{}, bonas:{}".format(o, n_step, dribble_step, + bonus_step)) + return True + diff --git a/keepaway/base/generator_pass.py b/keepaway/base/generator_pass.py new file mode 100755 index 00000000..125393c4 --- /dev/null +++ b/keepaway/base/generator_pass.py @@ -0,0 +1,858 @@ +from pyrusgeom.geom_2d import * +import pyrusgeom.soccer_math as smath +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.server_param import ServerParam as SP +from keepaway.base.tools import Tools +import time +from keepaway.base.generator_action import KickAction, KickActionType, BhvKickGen + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.types import GameModeType + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + +debug_pass = True +max_pass_time = 0 + + +class BhvPassGen(BhvKickGen): + def __init__(self): + super().__init__() + self.best_pass: KickAction = None + self.receivers: list["PlayerObject"] = [] + + def generator(self, wm: "WorldModel"): + + print("Generating ..") + global max_pass_time + start_time = time.time() + self.update_receivers(wm) + + for r in self.receivers: + log.sw_log().pass_().add_text( + f"=============== Lead Pass to {r.unum()} pos: {r.pos()}" + ) + # if self.best_pass is not None \ + # and r.pos().x() < self.best_pass.target_ball_pos.x() - 5: + # break + self.generate_direct_pass(wm, r) + # self.generate_lead_pass(wm, r) + # self.generate_through_pass(wm, r) + + if debug_pass: + for candid in self.debug_list: + if candid[2]: + log.sw_log().pass_().add_message( + candid[1].x(), candid[1].y(), "{}".format(candid[0]) + ) + log.sw_log().pass_().add_circle( + circle=Circle2D(candid[1], 0.2), color=Color(string="green") + ) + else: + log.sw_log().pass_().add_message( + candid[1].x(), candid[1].y(), "{}".format(candid[0]) + ) + log.sw_log().pass_().add_circle( + circle=Circle2D(candid[1], 0.2), color=Color(string="red") + ) + end_time = time.time() + if end_time - start_time > max_pass_time: + max_pass_time = end_time - start_time + log.sw_log().pass_().add_text( + "time:{} max is {}".format(end_time - start_time, max_pass_time) + ) + return self.candidates + + def update_receivers(self, wm: "WorldModel"): + sp = SP.i() + log.sw_log().pass_().add_text("===update receivers".format()) + for tm in wm.teammates(): + if tm is None: + log.sw_log().pass_().add_text("-----<<< TM is none") + continue + if tm.unum() <= 0: + log.sw_log().pass_().add_text(f"-----<<< TM unum is {tm.unum()}") + continue + if tm.unum() == wm.self().unum(): + log.sw_log().pass_().add_text(f"-----<<< TM unum is {tm.unum()} (self)") + continue + if tm.pos_count() > 10: + log.sw_log().pass_().add_text( + f"-----<<< TM unum pos count {tm.pos_count()}" + ) + continue + if tm.is_tackling(): + log.sw_log().pass_().add_text(f"-----<<< TM is tackling") + continue + if tm.pos().x() > wm.offside_line_x(): + log.sw_log().pass_().add_text( + f"-----<<< TM is in offside {tm.pos().x()} > {wm.offside_line_x()}" + ) + continue + if tm.goalie() and tm.pos().x() < sp.our_penalty_area_line_x() + 15: + log.sw_log().pass_().add_text( + f"-----<<< TM is goalie and danger {tm.pos().x()} < {sp.our_penalty_area_line_x() + 15}" + ) + continue + log.sw_log().pass_().add_text(f"--->>>>> TM {tm.unum()} is added") + self.receivers.append(tm) + self.receivers = sorted(self.receivers, key=lambda p: p.pos().x(), reverse=True) + + def generate_direct_pass(self, wm: "WorldModel", receiver: "PlayerObject"): + sp = SP.i() + min_receive_step = 3 + max_direct_pass_dist = 0.8 * smath.inertia_final_distance( + sp.ball_speed_max(), sp.ball_decay() + ) + max_receive_ball_speed = sp.ball_speed_max() * pow( + sp.ball_decay(), min_receive_step + ) + min_direct_pass_dist = receiver.player_type().kickable_area() * 2.2 + if ( + receiver.pos().x() > sp.pitch_half_length() - 1.5 + or receiver.pos().x() < -sp.pitch_half_length() + 5.0 + or receiver.pos().abs_y() > sp.pitch_half_width() - 1.5 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#DPass to {} {}, out of field".format( + receiver.unum(), receiver.pos() + ) + ) + return + # TODO sp.ourTeamGoalPos() + print("ball pos in gen pass: ", wm.ball().pos()) + if receiver.pos().x() < wm.ball().pos().x() + 1.0 and receiver.pos().dist2( + Vector2D(-52.5, 0) + ) < pow(18.0, 2): + if debug_pass: + log.sw_log().pass_().add_text( + "#DPass to {} {}, danger near goal".format( + receiver.unum(), receiver.pos() + ) + ) + return + + ptype = receiver.player_type() + max_ball_speed = wm.self().kick_rate() * sp.max_power() + if wm.game_mode().type() == GameModeType.PlayOn: + max_ball_speed = sp.ball_speed_max() + + # TODO SP.defaultRealSpeedMax() + min_ball_speed = 1.0 + + receive_point = ptype.inertiaFinalPoint(receiver.pos(), receiver.vel()) + ball_move_dist = wm.ball().pos().dist(receive_point) + + if ( + ball_move_dist < min_direct_pass_dist + or max_direct_pass_dist < ball_move_dist + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#DPass to {} {}, far or close".format( + receiver.unum(), receiver.pos() + ) + ) + return + + if ( + wm.game_mode().type().is_goal_kick() + and receive_point.x() < sp.our_penalty_area_line_x() + 1.0 + and receive_point.abs_y() < sp.penalty_area_half_width() + 1.0 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#DPass to {} {}, in penalty area in goal kick mode".format( + receiver.unum(), receiver.pos() + ) + ) + return + + max_receive_ball_speed = min( + max_receive_ball_speed, + ptype.kickable_area() + + (sp.max_dash_power() * ptype.dash_power_rate() * ptype.effort_max()) + * 1.8, + ) + min_receive_ball_speed = ptype.real_speed_max() + + ball_move_angle = (receive_point - wm.ball().pos()).th() + + min_ball_step = sp.ball_move_step(sp.ball_speed_max(), ball_move_dist) + # TODO Penalty step + start_step = max(max(min_receive_step, min_ball_step), 0) + max_step = start_step + 2 + log.sw_log().pass_().add_text( + "#DPass to {} {}".format(receiver.unum(), receiver.pos()) + ) + self.create_pass( + wm, + receiver, + receive_point, + start_step, + max_step, + min_ball_speed, + max_ball_speed, + min_receive_ball_speed, + max_receive_ball_speed, + ball_move_dist, + ball_move_angle, + "D", + ) + + def generate_lead_pass(self, wm: "WorldModel", receiver): + sp = SP.i() + our_goal_dist_thr2 = pow(16.0, 2) + min_receive_step = 4 + max_receive_step = 20 + min_leading_pass_dist = 3.0 + max_leading_pass_dist = 0.8 * smath.inertia_final_distance( + sp.ball_speed_max(), sp.ball_decay() + ) + max_receive_ball_speed = sp.ball_speed_max() * pow( + sp.ball_decay(), min_receive_step + ) + + max_player_distance = 35 + if receiver.pos().dist(wm.ball().pos()) > max_player_distance: + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, player is far".format( + receiver.unum(), receiver.pos() + ) + ) + return + + abgle_divs = 8 + angle_step = 360.0 / abgle_divs + dist_divs = 4 + dist_step = 1.1 + + ptype = receiver.player_type() + max_ball_speed = wm.self().kick_rate() * sp.max_power() + if wm.game_mode().type() == GameModeType.PlayOn: + max_ball_speed = sp.ball_speed_max() + min_ball_speed = sp.default_player_speed_max() + + max_receive_ball_speed = min( + max_receive_ball_speed, + ptype.kickable_area() + + (sp.max_dash_power() * ptype.dash_power_rate() * ptype.effort_max()) + * 1.5, + ) + min_receive_ball_speed = 0.001 + + our_goal = Vector2D(-52.5, 0) + + angle_from_ball = (receiver.pos() - wm.ball().pos()).th() + for d in range(1, dist_divs + 1): + player_move_dist = dist_step * d + a_step = 2 if player_move_dist * 2.0 * math.pi / abgle_divs < 0.6 else 1 + for a in range(abgle_divs + 1): + angle = angle_from_ball + angle_step * a + receive_point = receiver.inertia_point(1) + Vector2D.from_polar( + player_move_dist, angle + ) + + move_dist_penalty_step = 0 + ball_move_line = Line2D(wm.ball().pos(), receive_point) + player_line_dist = ball_move_line.dist(receiver.pos()) + move_dist_penalty_step = int(player_line_dist * 0.3) + if ( + receive_point.x() > sp.pitch_half_length() - 3.0 + or receive_point.x() < -sp.pitch_half_length() + 5.0 + or receive_point.abs_y() > sp.pitch_half_width() - 3.0 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, out of field".format( + receiver.unum(), receive_point + ) + ) + continue + + if ( + receive_point.x() < wm.ball().pos().x() + and receive_point.dist2(our_goal) < our_goal_dist_thr2 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, pass is danger".format( + receiver.unum(), receive_point + ) + ) + continue + + if ( + wm.game_mode().type() + in [GameModeType.GoalKick_Right, GameModeType.GoalKick_Left] + and receive_point.x() < sp.our_penalty_area_line_x() + 1.0 + and receive_point.abs_y() < sp.penalty_area_half_width() + 1.0 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, in penalty area".format( + receiver.unum(), receive_point + ) + ) + return + + ball_move_dist = wm.ball().pos().dist(receive_point) + + if ( + ball_move_dist < min_leading_pass_dist + or max_leading_pass_dist < ball_move_dist + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, so far or so close".format( + receiver.unum(), receive_point + ) + ) + continue + + nearest_receiver = Tools.get_nearest_teammate( + wm, receive_point, self.receivers + ) + if nearest_receiver.unum() != receiver.unum(): + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}, {} is closer than receiver ".format( + receiver.unum(), receive_point, nearest_receiver.unum() + ) + ) + continue + + receiver_step = ( + self.predict_receiver_reach_step(receiver, receive_point, True, "L") + + move_dist_penalty_step + ) + ball_move_angle = (receive_point - wm.ball().pos()).th() + + min_ball_step = sp.ball_move_step(sp.ball_speed_max(), ball_move_dist) + + start_step = max(max(min_receive_step, min_ball_step), receiver_step) + # ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT + # max_step = std::max(max_receive_step, start_step + 3); + # else + if debug_pass: + log.sw_log().pass_().add_text( + "#####LPass to {} {}".format(receiver.unum(), receive_point) + ) + max_step = start_step + 3 + self.create_pass( + wm, + receiver, + receive_point, + start_step, + max_step, + min_ball_speed, + max_ball_speed, + min_receive_ball_speed, + max_receive_ball_speed, + ball_move_dist, + ball_move_angle, + "L", + ) + + def generate_through_pass(self, wm: "WorldModel", receiver: "PlayerObject"): + sp = SP.i() + teammate_min_x = 0.0 + target_min_x = 10.0 + min_receive_step = 4 + min_pass_dist = 3.0 + max_pass_dist = 0.8 * smath.inertia_final_distance( + sp.ball_speed_max(), sp.ball_decay() + ) + max_receive_ball_speed = sp.ball_speed_max() * pow( + sp.ball_decay(), min_receive_step + ) + + max_player_distance = 35 + if receiver.pos().dist(wm.ball().pos()) > max_player_distance: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, player is far".format( + receiver.unum(), receiver.pos() + ) + ) + return + if receiver.pos().x() < teammate_min_x: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, player is far".format( + receiver.unum(), receiver.pos() + ) + ) + return + if receiver.pos().x() < wm.offside_line_x() - 5.0: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, player is not close to offside line".format( + receiver.unum(), receiver.pos() + ) + ) + return + if receiver.pos().x() > wm.offside_line_x() - 0.5: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, player is in offside".format( + receiver.unum(), receiver.pos() + ) + ) + return + if wm.ball().pos().x() < -10.0 or wm.ball().pos().x() > 30.0: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, ball x is low or high".format( + receiver.unum(), receiver.pos() + ) + ) + return + + min_angle = -30 + max_angle = 30 + angle_step = 10 + dist_divs = 25 + dist_step = 1.0 + + ptype = receiver.player_type() + max_ball_speed = wm.self().kick_rate() * sp.max_power() + if wm.game_mode().type() == GameModeType.PlayOn: + max_ball_speed = sp.ball_speed_max() + min_ball_speed = sp.default_player_speed_max() + + max_receive_ball_speed = min( + max_receive_ball_speed, + ptype.kickable_area() + + (sp.max_dash_power() * ptype.dash_power_rate() * ptype.effort_max()) + * 1.5, + ) + min_receive_ball_speed = 0.001 + + our_goal = Vector2D(-52.5, 0) + + angle_from_ball = (receiver.pos() - wm.ball().pos()).th() + for d in range(5, dist_divs + 1): + player_move_dist = dist_step * d + for a in range(min_angle, max_angle + 1, angle_step): + receive_point = receiver.inertia_point(1) + Vector2D.from_polar( + player_move_dist, a + ) + + move_dist_penalty_step = 0 + ball_move_line = Line2D(wm.ball().pos(), receive_point) + player_line_dist = ball_move_line.dist(receiver.pos()) + move_dist_penalty_step = int(player_line_dist * 0.3) + if ( + receive_point.x() > sp.pitch_half_length() - 3.0 + or receive_point.x() < -sp.pitch_half_length() + 5.0 + or receive_point.abs_y() > sp.pitch_half_width() - 3.0 + ): + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, out of field".format( + receiver.unum(), receive_point + ) + ) + continue + + if receive_point.x() < target_min_x: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, pass is danger".format( + receiver.unum(), receive_point + ) + ) + continue + + ball_move_dist = wm.ball().pos().dist(receive_point) + + if ball_move_dist < min_pass_dist or max_pass_dist < ball_move_dist: + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, so far or so close".format( + receiver.unum(), receive_point + ) + ) + continue + + nearest_receiver = Tools.get_nearest_teammate( + wm, receive_point, self.receivers + ) + if nearest_receiver.unum() != receiver.unum(): + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}, {} is closer than receiver ".format( + receiver.unum(), receive_point, nearest_receiver.unum() + ) + ) + continue + + receiver_step = ( + self.predict_receiver_reach_step(receiver, receive_point, True, "T") + + move_dist_penalty_step + ) + ball_move_angle = (receive_point - wm.ball().pos()).th() + + min_ball_step = sp.ball_move_step(sp.ball_speed_max(), ball_move_dist) + + start_step = max(max(min_receive_step, min_ball_step), receiver_step) + # ifdef CREATE_SEVERAL_CANDIDATES_ON_SAME_POINT + # max_step = std::max(max_receive_step, start_step + 3); + # else + if debug_pass: + log.sw_log().pass_().add_text( + "#####TPass to {} {}".format(receiver.unum(), receive_point) + ) + max_step = start_step + 3 + self.create_pass( + wm, + receiver, + receive_point, + start_step, + max_step, + min_ball_speed, + max_ball_speed, + min_receive_ball_speed, + max_receive_ball_speed, + ball_move_dist, + ball_move_angle, + "T", + ) + + def predict_receiver_reach_step( + self, receiver, pos: Vector2D, use_penalty, pass_type + ): + ptype = receiver.player_type() + + target_dist = receiver.inertia_point(1).dist(pos) + n_turn = ( + 1 + if receiver.body_count() > 0 + else Tools.predict_player_turn_cycle( + ptype, + receiver.body(), + receiver.vel().r(), + target_dist, + (pos - receiver.inertia_point(1)).th(), + ptype.kickable_area(), + False, + ) + ) + dash_dist = target_dist + + # if use_penalty: + # dash_dist += receiver.penalty_distance_; + + if pass_type == "L": + dash_dist *= 1.05 + + dash_angle = (pos - receiver.pos()).th() + + if ( + dash_angle.abs() > 90.0 + or receiver.body_count() > 1 + or (dash_angle - receiver.body()).abs() > 30.0 + ): + n_turn += 1 + + n_dash = ptype.cycles_to_reach_distance(dash_dist) + + n_step = n_turn + n_dash if n_turn == 0 else n_turn + n_dash + 1 + return n_step + + def create_pass( + self, + wm: "WorldModel", + receiver, + receive_point: Vector2D, + min_step, + max_step, + min_first_ball_speed, + max_first_ball_speed, + min_receive_ball_speed, + max_receive_ball_speed, + ball_move_dist, + ball_move_angle: AngleDeg, + description, + ): + sp = SP.i() + + for step in range(min_step, max_step + 1): + self.index += 1 + first_ball_speed = smath.calc_first_term_geom_series( + ball_move_dist, sp.ball_decay(), step + ) + + if first_ball_speed < min_first_ball_speed: + if debug_pass: + log.sw_log().pass_().add_text( + "##Pass {},to {} {}, step:{}, ball_speed:{}, first ball speed is low".format( + self.index, + receiver.unum(), + receiver.pos(), + step, + first_ball_speed, + ) + ) + self.debug_list.append((self.index, receive_point, False)) + break + + if max_first_ball_speed < first_ball_speed: + if debug_pass: + log.sw_log().pass_().add_text( + "##Pass {},to {} {}, step:{}, ball_speed:{}, first ball speed is high".format( + self.index, + receiver.unum(), + receiver.pos(), + step, + first_ball_speed, + ) + ) + self.debug_list.append((self.index, receive_point, False)) + continue + + receive_ball_speed = first_ball_speed * pow(sp.ball_decay(), step) + + if receive_ball_speed < min_receive_ball_speed: + if debug_pass: + log.sw_log().pass_().add_text( + "##Pass {},to {} {}, step:{}, ball_speed:{}, rball_speed:{}, receive ball speed is low".format( + self.index, + receiver.unum(), + receiver.pos(), + step, + first_ball_speed, + receive_ball_speed, + ) + ) + self.debug_list.append((self.index, receive_point, False)) + break + + if max_receive_ball_speed < receive_ball_speed: + if debug_pass: + log.sw_log().pass_().add_text( + "##Pass {},to {} {}, step:{}, ball_speed:{}, rball_speed:{}, receive ball speed is high".format( + self.index, + receiver.unum(), + receiver.pos(), + step, + first_ball_speed, + receive_ball_speed, + ) + ) + self.debug_list.append((self.index, receive_point, False)) + continue + + + + kick_count = Tools.predict_kick_count( + wm, wm.self().unum(), first_ball_speed, ball_move_angle + ) + + o_step, o_unum, o_intercepted_pos = self.predict_opponents_reach_step( + wm, + wm.ball().pos(), + first_ball_speed, + ball_move_angle, + receive_point, + step + (kick_count - 1) + 5, + description, + ) + + failed = False + if description == "T": + if o_step <= step: + failed = True + else: + if o_step <= step + (kick_count - 1): + failed = True + if failed: + if debug_pass: + log.sw_log().pass_().add_text( + "------<<<<>>>> OK Pass {} to {} {}, opp {} step {} max_step {}".format( + self.index, + receiver.unum(), + receive_point, + o_unum, + o_step, + max_step, + ) + ) + self.debug_list.append((self.index, receive_point, True)) + candidate = KickAction() + candidate.type = KickActionType.Pass + candidate.start_ball_pos = wm.ball().pos() + candidate.target_ball_pos = receive_point + candidate.target_unum = receiver.unum() + candidate.start_ball_speed = first_ball_speed + candidate.evaluate(wm) + + + self.candidates.append(candidate) + + if self.best_pass is None or candidate.eval > self.best_pass.eval: + self.best_pass = candidate + + find_another_pass = False + if not find_another_pass: + break + + if o_step <= step + 3: + break + + if min_step + 3 <= step: + break + + def predict_opponents_reach_step( + self, + wm: "WorldModel", + first_ball_pos: Vector2D, + first_ball_speed, + ball_move_angle: AngleDeg, + receive_point: Vector2D, + max_cycle, + description, + ): + first_ball_vel = Vector2D.polar2vector(first_ball_speed, ball_move_angle) + min_step = 1000 + min_opp = 0 + intercepted_pos = None + for opp in wm.opponents(): + if opp is None or opp.unum() == 0: + continue + step, intercepted_pos = self.predict_opponent_reach_step( + wm, + opp, + first_ball_pos, + first_ball_vel, + ball_move_angle, + receive_point, + max_cycle, + description, + ) + if step < min_step: + min_step = step + min_opp = opp.unum() + return min_step, min_opp, intercepted_pos + + def predict_opponent_reach_step( + self, + wm: "WorldModel", + opp: "PlayerObject", + first_ball_pos: Vector2D, + first_ball_vel: Vector2D, + ball_move_angle: AngleDeg, + receive_point: Vector2D, + max_cycle, + description, + ): + sp = SP.i() + + penalty_area = Rect2D( + Vector2D(sp.their_penalty_area_line_x(), -sp.penalty_area_half_width()), + Size2D(sp.penalty_area_length(), sp.penalty_area_width()), + ) + CONTROL_AREA_BUF = 0.15 + + opponent = opp + ptype = opponent.player_type() + min_cycle = Tools.estimate_min_reach_cycle( + opponent.pos(), ptype.real_speed_max(), first_ball_pos, ball_move_angle + ) + + if min_cycle < 0: + return 1000, None + + for cycle in range(max(1, min_cycle), max_cycle + 1): + ball_pos = smath.inertia_n_step_point( + first_ball_pos, first_ball_vel, cycle, sp.ball_decay() + ) + control_area = ( + sp.catchable_area() + if opponent.is_goalie() and penalty_area.contains(ball_pos) + else ptype.kickable_area() + ) + + inertia_pos = ptype.inertia_point(opponent.pos(), opponent.vel(), cycle) + target_dist = inertia_pos.dist(ball_pos) + + dash_dist = target_dist + if ( + description == "T" + and first_ball_vel.x() > 2.0 + and ( + receive_point.x() > wm.offside_line_x() or receive_point.x() > 30.0 + ) + ): + pass + else: + dash_dist -= Tools.estimate_virtual_dash_distance(opp) + if dash_dist - control_area - CONTROL_AREA_BUF < 0.001: + return cycle, ball_pos + + if ( + description == "T" + and first_ball_vel.x() > 2.0 + and ( + receive_point.x() > wm.offside_line_x() or receive_point.x() > 30.0 + ) + ): + dash_dist -= control_area + else: + if receive_point.x() < 25.0: + dash_dist -= control_area + 0.5 + else: + dash_dist -= control_area + 0.2 + + if dash_dist > ptype.real_speed_max() * ( + cycle + min(opponent.pos_count(), 5) + ): + continue + + n_dash = ptype.cycles_to_reach_distance(dash_dist) + if n_dash > cycle + opponent.pos_count(): + continue + + n_turn = 0 + if opponent.body_count() > 1: + n_turn = Tools.predict_player_turn_cycle( + ptype, + opponent.body(), + opponent.vel().r(), + target_dist, + (ball_pos - inertia_pos).th(), + control_area, + True, + ) + + n_step = n_turn + n_dash if n_turn == 0 else n_turn + n_dash + 1 + + bonus_step = 0 + if opponent.is_tackling(): + bonus_step = -5 + if n_step - bonus_step <= cycle: + return cycle, ball_pos + return 1000, None diff --git a/keepaway/base/generator_shoot.py b/keepaway/base/generator_shoot.py new file mode 100755 index 00000000..ad68bc28 --- /dev/null +++ b/keepaway/base/generator_shoot.py @@ -0,0 +1,302 @@ +from pyrusgeom.geom_2d import * +import pyrusgeom.soccer_math as smath + +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.server_param import ServerParam as SP +from keepaway.base.tools import Tools +import time +from keepaway.base.generator_action import ShootAction, BhvKickGen +from keepaway.lib.action.kick_table import calc_max_velocity +from keepaway.lib.rcsc.types import GameModeType + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + + +debug_shoot = False +max_shoot_time = 0 + + +class BhvShhotGen(BhvKickGen): + def generator(self, wm: 'WorldModel') -> ShootAction: + global max_shoot_time + start_time = time.time() + self.total_count = 0 + goal_l = Vector2D(SP.i().pitch_half_length(), -SP.i().goal_half_width()) + goal_r = Vector2D(SP.i().pitch_half_length(), +SP.i().goal_half_width()) + + goal_l._y += min(1.5, 0.6 + goal_l.dist(wm.ball().pos()) * 0.042) + goal_r._y -= min(1.5, 0.6 + goal_r.dist(wm.ball().pos()) * 0.042) + + if wm.self().pos().x() > SP.i().pitch_half_length() - 1.0 and wm.self().pos().abs_y() < SP.i().goal_half_width(): + goal_l._x = wm.self().pos().x() + 1.5 + goal_r._x = wm.self().pos().x() + 1.5 + + DIST_DIVS = 25 + dist_step = abs(goal_l.y() - goal_r.y()) / (DIST_DIVS - 1) + + for i in range(DIST_DIVS): + self.total_count += 1 + target_point = Vector2D(goal_l.x(), goal_l.y() + dist_step * i) + log.sw_log().shoot().add_text( "#shoot {} to {}".format(self.total_count, target_point)) + self.create_shoot(wm, target_point) + if len(self.candidates) == 0: + return None + + self.evaluate_courses(wm) + self.candidates = sorted(self.candidates, key=lambda candidate: candidate.score, reverse=True) + # + # end_time = time.time() + # if end_time - start_time > max_shoot_time: + # max_pass_time = end_time - start_time + # log.sw_log().pass_().add_text( 'time:{} max is {}'.format(end_time - start_time, max_shoot_time)) + return self.candidates[0] + + def create_shoot(self, wm: 'WorldModel', target_point: Vector2D): + ball_move_angle = (target_point - wm.ball().pos()).th() + goalie = wm.get_opponent_goalie() + if goalie is None or (goalie.unum() > 0 and 5 < goalie.pos_count() < 30): + # TODO and wm.dirCount( ball_move_angle ) > 3 + log.sw_log().shoot().add_text( "#shoot {} didnt see goalie".format(self.total_count)) + return + + sp = SP.i() + + ball_speed_max = sp.ball_speed_max() if wm.game_mode().type() == GameModeType.PlayOn \ + or wm.game_mode().is_penalty_kick_mode() \ + else wm.self().kick_rate() * sp.max_power() + + ball_move_dist = wm.ball().pos().dist(target_point) + + max_one_step_vel = calc_max_velocity(ball_move_angle, wm.self().kick_rate(), wm.ball().vel()) + max_one_step_speed = max_one_step_vel.r() + + first_ball_speed = max((ball_move_dist + 5.0) * (1.0 - sp.ball_decay()), max_one_step_speed, 1.5) + + over_max = False + while not over_max: + if first_ball_speed > ball_speed_max - 0.001: + over_max = True + first_ball_speed = ball_speed_max + + not_failed = self.check_shoot(wm, target_point, first_ball_speed, ball_move_angle, ball_move_dist) + if not_failed: + if first_ball_speed <= max_one_step_speed + 0.001: + self.candidates[-1].kick_step = 1 + + if self.candidates[-1].goalie_never_reach \ + and self.candidates[-1].opponent_never_reach: + return + + first_ball_speed += 0.3 + + def check_shoot(self, wm: 'WorldModel', target_point: Vector2D, first_ball_speed, ball_move_angle: AngleDeg, ball_move_dist): + sp = SP.i() + + + ball_reach_step = int( + math.ceil(smath.calc_length_geom_series(first_ball_speed, ball_move_dist, sp.ball_decay()))) + + if ball_reach_step == -1: + log.sw_log().shoot().add_text( 'Cant arrive to target') + return False + log.sw_log().shoot().add_text( '{} {} {} {} {}'.format(first_ball_speed, ball_move_dist, sp.ball_decay(), smath.calc_length_geom_series(first_ball_speed, ball_move_dist, sp.ball_decay()), math.ceil(smath.calc_length_geom_series(first_ball_speed, ball_move_dist, sp.ball_decay())))) + course = ShootAction(self.total_count, target_point, first_ball_speed, ball_move_angle, ball_move_dist, + ball_reach_step) + + log.sw_log().shoot().add_text( 'course: {}'.format(course)) + if ball_reach_step <= 1: + course.ball_reach_step = 1 + self.candidates.append(course) + log.sw_log().shoot().add_text( 'Yes') + return True + opponent_x_thr = sp.their_penalty_area_line_x() - 30.0 + opponent_y_thr = sp.penalty_area_half_width() + + for o in range(1, 12): + opp = wm.their_player(o) + if opp.unum() < 1: + log.sw_log().shoot().add_text( '## opp {} can not, unum') + continue + if opp.is_tackling(): + log.sw_log().shoot().add_text( '## opp {} can not, tackle') + continue + if opp.pos().x() < opponent_x_thr: + log.sw_log().shoot().add_text( '## opp {} can not, xthr') + continue + if opp.pos().abs_y() > opponent_y_thr: + log.sw_log().shoot().add_text( '## opp {} can not, ythr') + continue + + if (ball_move_angle - (opp.pos() - wm.ball().pos()).th()).abs() > 90.0: + log.sw_log().shoot().add_text( '## opp {} can not, angle') + continue + + if opp.goalie(): + if self.maybe_goalie_catch(opp, course, wm): + return False + log.sw_log().shoot().add_text( '## opp {} can not, goalie catch') + continue + + if opp.pos_count() > 10: + log.sw_log().shoot().add_text( '## opp {} can not, pos count') + continue + if opp.is_ghost() and opp.pos_count() > 5: + log.sw_log().shoot().add_text( '## opp {} can not, ghost') + continue + + if self.opponent_can_reach(opp, course, wm): + return False + log.sw_log().shoot().add_text( '## opp {} can not, cant reach') + self.candidates.append(course) + return True + + def maybe_goalie_catch(self, goalie: 'PlayerObject', course: ShootAction, wm: 'WorldModel'): + penalty_area = Rect2D(Vector2D(SP.i().their_penalty_area_line_x(), -SP.i().penalty_area_half_width()), + Size2D(SP.i().penalty_area_length(), SP.i().penalty_area_width())) + CONTROL_AREA_BUF = 0.15 + sp = SP.i() + ptype = goalie.player_type() + min_cycle = Tools.estimate_min_reach_cycle(goalie.pos(), ptype.real_speed_max(), wm.ball().pos(), + course.ball_move_angle) + if min_cycle < 0: + return False + + goalie_speed = goalie.vel().r() + seen_dist_noise = goalie.dist_from_self() * 0.02 + max_cycle = course.ball_reach_step + + for cycle in range(min_cycle, max_cycle): + ball_pos = smath.inertia_n_step_point(wm.ball().pos(), course.first_ball_vel, cycle, sp.ball_decay()) + if ball_pos.x() > sp.pitch_half_length(): + break + in_penalty_area = penalty_area.contains(ball_pos) + control_area = sp._catchable_area if in_penalty_area else ptype.kickable_area() + + inertia_pos = goalie.inertia_point(cycle) + target_dist = inertia_pos.dist(ball_pos) + + if in_penalty_area: + target_dist -= seen_dist_noise + + if target_dist - control_area - CONTROL_AREA_BUF < 0.001: + return True + + dash_dist = float(target_dist) + if cycle > 1: + dash_dist -= control_area * 0.9 + dash_dist *= 0.999 + + n_dash = ptype.cycles_to_reach_distance(dash_dist) + + if n_dash > cycle + goalie.pos_count(): + continue + n_turn = 0 + if goalie.body_count() <= 1: + Tools.predict_player_turn_cycle(ptype, goalie.body(), goalie_speed, target_dist, + (ball_pos - inertia_pos).th(), control_area + 0.1, True) + n_step = n_turn + n_dash + if n_turn == 0: + n_step += 1 + + bonus_step = smath.bound(0, goalie.pos_count(), 5) if in_penalty_area else smath.bound(0, + goalie.pos_count() - 1, + 1) + if not in_penalty_area: + bonus_step -= 1 + + if n_step <= cycle + bonus_step: + return True + + if in_penalty_area and n_step <= cycle + goalie.pos_count() + 1: + course.goalie_never_reach_ = False + return False + + def opponent_can_reach(self, opponent, course: ShootAction, wm: 'WorldModel'): + sp = SP.i() + ptype = opponent.player_type() + control_area = ptype.kickable_area() + min_cycle = Tools.estimate_min_reach_cycle(opponent.pos(), ptype.real_speed_max(), wm.ball().pos(), + course.ball_move_angle) + if min_cycle < 0: + return False + + opponent_speed = opponent.vel().r() + max_cycle = course.ball_reach_step + maybe_reach = False + nearest_step_diff = 1000 + for cycle in range(min_cycle, max_cycle): + ball_pos = smath.inertia_n_step_point(wm.ball().pos(), course.first_ball_vel, cycle, sp.ball_decay()) + inertia_pos = opponent.inertia_point(cycle) + target_dist = inertia_pos.dist(ball_pos) + if target_dist - control_area < 0.001: + return True + dash_dist = float(target_dist) + if cycle > 1: + dash_dist -= control_area * 0.8 + n_dash = ptype.cycles_to_reach_distance(dash_dist) + + if n_dash > cycle + opponent.pos_count(): + continue + + n_turn = 1 + if opponent.body_count() == 0: + n_turn = Tools.predict_player_turn_cycle(ptype, opponent.body(), opponent_speed, target_dist, + (ball_pos - inertia_pos).th(), control_area, True) + n_step = n_turn + n_dash + if n_turn == 0: + n_step += 1 + bonus_step = smath.bound(0, opponent.pos_count(), 1) + penalty_step = -1 + + if opponent.is_tackling(): + penalty_step -= 5 + + if n_step <= cycle + bonus_step + penalty_step: + return True + + if n_step <= cycle + opponent.pos_count() + 1: + maybe_reach = True + diff = cycle + opponent.pos_count() - n_step + if diff < nearest_step_diff: + nearest_step_diff = diff + if maybe_reach: + course.opponent_never_reach = False + + return False + + def evaluate_courses(self, wm: 'WorldModel'): + y_dist_thr2 = pow(8.0, 2) + + sp = SP.i() + goalie = wm.get_opponent_goalie() + goalie_angle = (goalie.pos() - wm.ball().pos()).th() if goalie else 180.0 + + for it in self.candidates: + score = 1.0 + + if it.kick_step == 1: + score += 50.0 + + if it.goalie_never_reach: + score += 100.0 + + if it.opponent_never_reach: + score += 100.0 + + goalie_rate = 1.0 + if goalie.unum() > 0: + variance2 = 1.0 if it.goalie_never_reach else pow(10.0, 2) + angle_diff = (it.ball_move_angle - goalie_angle).abs() + goalie_rate = 1.0 - math.exp(-pow(angle_diff, 2) / (2.0 * variance2) ) + + y_rate = 1.0 + if it.target_point.dist2(wm.ball().pos()) > y_dist_thr2: + y_dist = max(0.0, it.target_point.abs_y() - 4.0 ) + y_rate = math.exp(-pow(y_dist, 2.0) / (2.0 * pow( sp.goal_half_width() - 1.5, 2))) + + score *= goalie_rate + score *= y_rate + it.score_ = score diff --git a/keepaway/base/goalie_decision.py b/keepaway/base/goalie_decision.py new file mode 100755 index 00000000..28cb57cc --- /dev/null +++ b/keepaway/base/goalie_decision.py @@ -0,0 +1,137 @@ + +from typing import TYPE_CHECKING + +from pyrusgeom.line_2d import Line2D +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.size_2d import Size2D +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.base.basic_tackle import BasicTackle +from keepaway.base.generator_action import KickAction +from keepaway.base.generator_pass import BhvPassGen +from keepaway.keepaway.base.set_play.bhv_goalie_set_play import Bhv_GoalieSetPlay +from keepaway.lib.action.go_to_point import GoToPoint +from keepaway.lib.action.hold_ball import HoldBall +from keepaway.lib.action.intercept import Intercept +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.action.neck_turn_to_ball import NeckTurnToBall +from keepaway.lib.action.smart_kick import SmartKick +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +DEBUG = True + +def decision(agent: 'PlayerAgent'): + SP = ServerParam.i() + wm = agent.world() + + our_penalty = Rect2D(Vector2D(-SP.pitch_half_length(), -SP.penalty_area_half_width() + 1), + Size2D(SP.penalty_area_length() - 1, SP.penalty_area_width() - 2)) + + log.os_log().debug(f'########## gdc={wm.time().cycle()}') + log.os_log().debug(f'########## gd gmt={wm.game_mode().type()}') + if wm.game_mode().type() != GameModeType.PlayOn: + if Bhv_GoalieSetPlay().execute(agent): + return True + return False + + if (wm.time().cycle() > wm.self().catch_time().cycle() + SP.catch_ban_cycle() + and wm.ball().dist_from_self() < SP.catchable_area() - 0.05 + and our_penalty.contains(wm.ball().pos())): + + agent.do_catch() + agent.set_neck_action(NeckTurnToBall()) + elif wm.self().is_kickable(): + do_kick(agent) + else: + do_move(agent) + + return True + +def do_kick(agent: 'PlayerAgent'): + wm = agent.world() + + action_candidates = BhvPassGen().generator(wm) + + if len(action_candidates) == 0: + agent.set_neck_action(NeckScanPlayers()) + return HoldBall().execute(agent) + + best_action: KickAction = max(action_candidates) + target = best_action.target_ball_pos + log.debug_client().set_target(target) + log.debug_client().add_message(best_action.type.value + 'to ' + best_action.target_ball_pos.__str__() + ' ' + str( + best_action.start_ball_speed)) + SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3).execute(agent) + agent.set_neck_action(NeckScanPlayers()) + return True + + +def do_move(agent: 'PlayerAgent'): + SP = ServerParam.i() + wm = agent.world() + + if BasicTackle(0.8, 90.).execute(agent): + return True + + self_min = wm.intercept_table().self_reach_cycle() + tm_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + + if self_min < tm_min and self_min < opp_min - 10: + Intercept(False).execute(agent) + agent.set_neck_action(NeckTurnToBall()) + return True + + ball_pos = wm.ball().inertia_point(min(tm_min, opp_min)) + + post1 = Vector2D(-SP.pitch_half_length(), + SP.goal_half_width()) + post2 = Vector2D(-SP.pitch_half_length(), - SP.goal_half_width()) + + ball_to_post1 = (post1 - ball_pos).th() + ball_to_post2 = (post2 - ball_pos).th() + ball_dir = (ball_to_post1 + ball_to_post2).degree()/ 2 + + ball_move_line = Line2D(ball_pos, ball_dir) + + margin = min(-SP.pitch_half_length() + 3, ball_pos.x() - 0.1) + goalie_move_line = Line2D(Vector2D(margin, -SP.pitch_half_width()), + Vector2D(margin, +SP.pitch_half_width())) + + target = goalie_move_line.intersection(ball_move_line) + target.set_y(bound(-SP.goal_half_width(), target.y(), SP.goal_half_width())) + + if DEBUG: + log.sw_log().positioning().add_line( + start=Vector2D(ball_pos.x(), ball_move_line.get_y(ball_pos.x())), + end=Vector2D(-SP.pitch_half_length(), ball_move_line.get_y(-SP.pitch_half_length())), + color=Color(string='red')) + log.sw_log().positioning().add_line( + start=Vector2D(goalie_move_line.get_x(-30), -30), + end=Vector2D(goalie_move_line.get_x(+30), +30), + color=Color(string='red')) + + if target: + GoToPoint(target, 0.2, 100).execute(agent) + agent.set_neck_action(NeckTurnToBall()) + return True + + return False + + + + + + + + + + diff --git a/keepaway/base/main_coach.py b/keepaway/base/main_coach.py new file mode 100755 index 00000000..0de5b361 --- /dev/null +++ b/keepaway/base/main_coach.py @@ -0,0 +1,21 @@ +#!/usr/bin/python3 +from keepaway.lib.player.basic_client import BasicClient +from keepaway.base.sample_coach import SampleCoach + +import team_config +import sys + + +def main(): + agent = SampleCoach() + print(agent.handle_start()) + if not agent.handle_start(): + # print("Failed to start") + agent.handle_exit() + return + print("Starting Coach") + agent.run() + + +if __name__ == "__main__": + main() diff --git a/keepaway/base/main_player.py b/keepaway/base/main_player.py new file mode 100755 index 00000000..5f7a38c5 --- /dev/null +++ b/keepaway/base/main_player.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +from keepaway.base.sample_player import SamplePlayer +from keepaway.lib.player.basic_client import BasicClient +from keepaway.lib.player.player_agent import PlayerAgent +import sys +import team_config + + +def main(): + agent = SamplePlayer() + if not agent.handle_start(): + agent.handle_exit() + return + agent.run() + + +if __name__ == "__main__": + main() diff --git a/keepaway/base/main_trainer.py b/keepaway/base/main_trainer.py new file mode 100755 index 00000000..5cbee293 --- /dev/null +++ b/keepaway/base/main_trainer.py @@ -0,0 +1,19 @@ +#!/usr/bin/python3 +from keepaway.base.sample_trainer import SampleTrainer +from keepaway.lib.player.basic_client import BasicClient + +import sys + + +def main(): + agent = SampleTrainer() + if not agent.handle_start(): + agent.handle_exit() + return + agent.run() + + +if __name__ == "__main__": + goalie = False + # if len(sys.argv) > 1 and sys.argv[1] == "g": + main() diff --git a/keepaway/base/sample_coach.py b/keepaway/base/sample_coach.py new file mode 100755 index 00000000..3a8844fd --- /dev/null +++ b/keepaway/base/sample_coach.py @@ -0,0 +1,104 @@ +import functools +from keepaway.lib.coach.coach_agent import CoachAgent +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.types import HETERO_DEFAULT, HETERO_UNKNOWN + +import team_config + +def real_speed_max(lhs: PlayerType, rhs: PlayerType) -> bool: + if abs(lhs.real_speed_max() - rhs.real_speed_max()) < 0.005: + return lhs.cycles_to_reach_max_speed() < rhs.cycles_to_reach_max_speed() + return lhs.real_speed_max() > rhs.real_speed_max() + +class SampleCoach(CoachAgent): + def __init__(self): + super().__init__() + + self._first_sub = False + + def action_impl(self): + # TODO Send Team Graphic + + self.do_substitute() + + def do_substitute(self): + if (not self._first_sub + and self.world().time().cycle() == 0 + and self.world().time().stopped_cycle() > 10): + + self.do_first_subsititute() + self._first_sub = True + return + + def do_first_subsititute(self): + candidates: list[PlayerType] = [] + + for i, pt in enumerate(self.world().player_types()): + if pt is None: + log.os_log().error(f"(sample coach first sub) pt is None! index={i}") + continue + + for i in range(1): # TODO PLAYER PARAM + candidates.append(pt) + + unum_order = [11,2,3,10,9,6,4,5,7,8] + + self.subsititute_to(1, HETERO_DEFAULT) + for pt in candidates: + if pt.id() == HETERO_DEFAULT: + candidates.remove(pt) + break + + for unum in unum_order: + p = self.world().our_player(unum) + if p is None: + log.os_log().error(f"(sample coach sub to) player is None! unum={unum}") + continue + type = self.get_fastest_type(candidates) + if type != HETERO_UNKNOWN: + self.subsititute_to(unum, type) + + def subsititute_to(self, unum, type): + if self.world().time().cycle() > 0 and self.world().our_subsititute_count() >= 3: # TODO PLAYER PARAM + log.os_log().error(f"(sample coach subsititute to) WARNING: {team_config.TEAM_NAME} coach: over the substitution max." + f"cannot change player({unum}) to {type}") + return + + if type not in self.world().available_player_type_id(): # IMP FUNC + log.os_log().error(f"(sample coach subsititute to) type is not available. type={type}") + return + + self.do_change_player_type(unum, type) + log.os_log().error(f"(sample coach subsititute to) player({unum})'s type changed to {type}") + + def get_fastest_type(self, candidates: list[PlayerType]): + if len(candidates) == 0: + return HETERO_UNKNOWN + + candidates.sort(key=functools.cmp_to_key(real_speed_max)) + + best_type: PlayerType = None + max_speed = 0 + min_cycle = 100 + + for candidate in candidates: + if candidate.real_speed_max() < max_speed - 0.01: + break + + if candidate.cycles_to_reach_max_speed() < min_cycle: + best_type = candidate + max_speed = best_type.real_speed_max() + min_cycle = best_type.cycles_to_reach_max_speed() + continue + + if candidate.cycles_to_reach_max_speed() == min_cycle: + if candidate.get_one_step_stamina_consumption() < best_type.get_one_step_stamina_consumption(): + best_type = candidate + max_speed = best_type.real_speed_max() + + if best_type is not None: + id = best_type.id() + candidates.remove(best_type) + return id + return HETERO_UNKNOWN \ No newline at end of file diff --git a/keepaway/base/sample_communication.py b/keepaway/base/sample_communication.py new file mode 100755 index 00000000..4f2f3582 --- /dev/null +++ b/keepaway/base/sample_communication.py @@ -0,0 +1,842 @@ +from math import exp + +from pyrusgeom.soccer_math import bound + +import team_config +from keepaway.base.strategy import Strategy +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.ball_goalie_messenger import BallGoalieMessenger +from keepaway.lib.messenger.ball_messenger import BallMessenger +from keepaway.lib.messenger.ball_player_messenger import BallPlayerMessenger +from keepaway.lib.messenger.ball_pos_vel_messenger import BallPosVelMessenger +from keepaway.lib.messenger.goalie_messenger import GoalieMessenger +from keepaway.lib.messenger.goalie_player_messenger import GoaliePlayerMessenger +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.messenger.one_player_messenger import OnePlayerMessenger +from keepaway.lib.messenger.recovery_message import RecoveryMessenger +from keepaway.lib.messenger.stamina_messenger import StaminaMessenger +from keepaway.lib.messenger.three_player_messenger import ThreePlayerMessenger +from keepaway.lib.messenger.two_player_messenger import TwoPlayerMessenger +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import UNUM_UNKNOWN, GameModeType, SideID + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + from keepaway.lib.player.object_player import PlayerObject + from keepaway.lib.player.world_model import WorldModel + + +class ObjectScore: + def __init__(self, n=UNUM_UNKNOWN, score=-1000, player=None): + self.number = n + self.score = score + self.player: PlayerObject = player + + +class SampleCommunication: + def __init__(self): + self._current_sender_unum: int = UNUM_UNKNOWN + self._next_sender_unum: int = UNUM_UNKNOWN + self._ball_send_time: GameTime = GameTime(0, 0) + self._teammate_send_time: list[GameTime] = [GameTime(0, 0) for i in range(12)] + self._opponent_send_time: list[GameTime] = [GameTime(0, 0) for i in range(12)] + + def should_say_ball(self, agent: "PlayerAgent"): + wm = agent.world() + ef = agent.effector() + + if wm.ball().seen_pos_count() > 0 or wm.ball().seen_vel_count() > 2: + return False + if ( + wm.game_mode().type() != GameModeType.PlayOn + and ef.queued_next_ball_vel().r2() < 0.5**2 + ): + return False + if wm.kickable_teammate(): + return False + + ball_vel_changed = False + current_ball_speed = wm.ball().vel().r() + + if wm.prev_ball().vel_valid(): + prev_ball_speed = wm.prev_ball().vel().r() + angle_diff = (wm.ball().vel().th() - wm.prev_ball().vel().th()).abs() + + log.sw_log().communication().add_text( + f"(sample communication)" + f"prev vel={wm.prev_ball().vel()}, r={prev_ball_speed}" + f"current_vel={wm.ball().vel()}, r={wm.ball().vel()}" + ) + + if ( + current_ball_speed > prev_ball_speed + 0.1 + or ( + prev_ball_speed > 0.5 + and current_ball_speed + < prev_ball_speed * ServerParam.i().ball_decay() / 2 + ) + or (prev_ball_speed > 0.5 and angle_diff > 20.0) + ): + log.sw_log().communication().add_text( + f"(sample communication) ball vel changed" + ) + ball_vel_changed = True + + if wm.self().is_kickable(): + if ( + ball_vel_changed + and wm.last_kicker_side() != wm.our_side() + and not wm.kickable_opponent() + ): + log.sw_log().communication().add_text( + "(sample communication) ball vel changed. opponent kicked. no opponent kicker" + ) + return True + if ef.queued_next_ball_kickable() and current_ball_speed < 1.0: + return False + if ball_vel_changed and ef.queued_next_ball_vel().r() > 1.0: + log.sw_log().communication().add_text( + "(sample communication) kickable. ball vel changed" + ) + return True + log.sw_log().communication().add_text( + "(sample communication) kickable. but no say" + ) + return False + + ball_nearest_teammate: PlayerObject = None + second_ball_nearest_teammate: PlayerObject = None + + for p in wm.teammates_from_ball(): + if p is None: + continue + if p.is_ghost() or p.pos_count() >= 10: + continue + + if ball_nearest_teammate is None: + ball_nearest_teammate = p + elif second_ball_nearest_teammate is None: + second_ball_nearest_teammate = p + break + + our_min = min( + wm.intercept_table().self_reach_cycle(), + wm.intercept_table().teammate_reach_cycle(), + ) + opp_min = wm.intercept_table().opponent_reach_cycle() + + if ( + ball_nearest_teammate is None + or ball_nearest_teammate.dist_from_ball() > wm.ball().dist_from_self() - 3.0 + ): + log.sw_log().communication().add_text( + "(sample communication) maybe nearest to ball" + ) + + if ball_vel_changed or (opp_min <= 1 and current_ball_speed > 1.0): + log.sw_log().communication().add_text( + "(sample communication) nearest to ball. ball vel changed?" + ) + return True + + if ( + ball_nearest_teammate is not None + and wm.ball().dist_from_self() < 20.0 + and 1.0 < ball_nearest_teammate.dist_from_ball() < 6.0 + and (opp_min <= our_min + 1 or ball_vel_changed) + ): + log.sw_log().communication().add_text( + "(sample communication) support nearset player" + ) + return True + return False + + def should_say_opponent_goalie(self, agent: "PlayerAgent"): + wm = agent.world() + goalie: PlayerObject = wm.get_their_goalie() + + if goalie is None: + return False + + if ( + goalie.seen_pos_count() == 0 + and goalie.body_count() == 0 + and goalie.unum() != UNUM_UNKNOWN + and goalie.unum_count() == 0 + and goalie.dist_from_self() < 25.0 + and 51.0 - 16.0 < goalie.pos().x() < 52.5 + and goalie.pos().abs_y() < 20.0 + ): + goal_pos = ServerParam.i().their_team_goal_pos() + ball_next = wm.ball().pos() + wm.ball().vel() + + if ball_next.dist2(goal_pos) < 18**2: + return True + return False + + def update_player_send_time(self, wm: "WorldModel", side: SideID, unum: int): + if not (1 <= unum <= 11): + log.os_log().error( + f"(sample communication) illegal player number. unum={unum}" + ) + return + + if side == wm.our_side(): + self._teammate_send_time[unum] = wm.time().copy() + else: + self._opponent_send_time[unum] = wm.time().copy() + + def say_ball_and_players(self, agent: "PlayerAgent"): + # print("communication starting .. ") + SP = ServerParam.i() + wm = agent.world() + ef = agent.effector() + + current_len = ef.get_say_message_length() + + should_say_ball = self.should_say_ball(agent) + should_say_goalie = self.should_say_opponent_goalie(agent) + goalie_say_situation = False # self.goalie_say_situation # TODO IMP FUNC + + if ( + not should_say_ball + and not should_say_goalie + and not goalie_say_situation + and self._current_sender_unum != wm.self().unum() + and current_len == 0 + ): + log.sw_log().communication().add_text( + "(sample communication) say ball and players: no send situation" + ) + return False + + available_len = SP.player_say_msg_size() - current_len + mm = wm.messenger_memory() + + objects = [ObjectScore(i, 1000) for i in range(23)] + objects[0].score = wm.time().cycle() - mm.ball_time().cycle() + + for p in mm.player_record(): + n = round(p[1].unum_) + if not (1 <= n <= 22): + continue + + objects[n].score = wm.time().cycle() - p[0].cycle() + + if 1 <= wm.their_goalie_unum() <= 11: + n = wm.their_goalie_unum() + 11 + diff = wm.time().cycle() - mm.goalie_time().cycle() + objects[n].score = min(objects[n].score, diff) + + if wm.self().is_kickable(): + if ef.queued_next_ball_kickable(): + objects[0].score = -1000 + else: + objects[0].score = 1000 + elif ( + wm.ball().seen_pos_count() > 0 + or wm.ball().seen_vel_count() > 1 + or wm.kickable_teammate() + ): + objects[0].score = -1000 + elif should_say_ball: + objects[0].score = 1000 + elif objects[0].score > 0: + if wm.prev_ball().vel_valid(): + angle_diff = (wm.ball().vel().th() - wm.prev_ball().vel().th()).abs() + prev_speed = wm.prev_ball().vel().r() + current_speed = wm.ball().vel().r() + + if ( + current_speed > prev_speed + 0.1 + or ( + prev_speed > 0.5 + and current_speed < prev_speed * SP.ball_decay() * 0.5 + ) + or (prev_speed > 0.5 and angle_diff > 20.0) + ): + objects[0].score = 1000 + else: + objects[0].score /= 2 + + variance = 30 + x_rate = 1 + y_rate = 0.5 + + min_step = min( + wm.intercept_table().opponent_reach_cycle(), + wm.intercept_table().self_reach_cycle(), + wm.intercept_table().teammate_reach_cycle(), + ) + ball_pos = wm.ball().inertia_point(min_step) + + for i in range(1, 12): + p = wm.our_player(i) + if p is None or p.unum_count() >= 2: + objects[i].score = -1000 + else: + d = ( + ((p.pos().x() - ball_pos.x()) * x_rate) ** 2 + + ((p.pos().y() - ball_pos.y()) * y_rate) ** 2 + ) ** 0.5 + objects[i].score *= exp(-(d**2) / (2 * variance**2)) + objects[i].score *= 0.3 ** p.unum_count() + objects[i].player = p + + p = wm.their_player(i) + if p is None or p.unum_count() >= 2: + objects[i + 11].score = -1000 + else: + d = ( + ((p.pos().x() - ball_pos.x()) * x_rate) ** 2 + + ((p.pos().y() - ball_pos.y()) * y_rate) ** 2 + ) ** 0.5 + objects[i + 11].score *= exp(-(d**2) / (2 * variance**2)) + objects[i + 11].score *= 0.3 ** p.unum_count() + objects[i + 11].player = p + + objects = list(filter(lambda x: x.score > 0.1, objects)) + objects.sort(key=lambda x: x.score, reverse=True) + + can_send_ball = False + send_ball_and_player = False + send_players: list[ObjectScore] = [] + + for o in objects: + if o.number == 0: + can_send_ball = True + if len(send_players) == 1: + send_ball_and_player = True + break + else: + send_players.append(o) + if can_send_ball: + send_ball_and_player = True + break + + if len(send_players) >= 3: + break + + if should_say_ball: + can_send_ball = True + + ball_vel = ef.queued_next_ball_vel() + if wm.self().is_kickable() and ef.queued_next_ball_kickable(): + ball_vel.assign(0, 0) + log.sw_log().communication().add_text( + "(sample communication) next cycle is kickable" + ) + + if wm.kickable_opponent() or wm.kickable_teammate(): + ball_vel.assign(0, 0) + + if ( + can_send_ball + and not send_ball_and_player + and available_len >= Messenger.SIZES[Messenger.Types.BALL] + ): + if available_len >= Messenger.SIZES[Messenger.Types.BALL_PLAYER]: + agent.add_say_message( + BallPlayerMessenger( + ef.queued_next_ball_pos(), + ball_vel, + wm.self().unum(), + ef.queued_next_self_pos(), + ef.queued_next_self_body(), + ) + ) + self.update_player_send_time(wm, wm.our_side(), wm.self().unum()) + else: + agent.add_say_message( + BallMessenger(ef.queued_next_ball_pos(), ball_vel) + ) + + self._ball_send_time = wm.time().copy() + log.sw_log().communication().add_text("(sample communication) only ball") + return True + + if send_ball_and_player: + if ( + should_say_goalie + and available_len >= Messenger.SIZES[Messenger.Types.BALL_GOALIE] + ): + goalie = wm.get_their_goalie() + agent.add_say_message( + BallGoalieMessenger( + ef.queued_next_ball_pos(), + ball_vel, + goalie.pos() + goalie.vel(), + goalie.body(), + ) + ) + self._ball_send_time = wm.time().copy() + self.update_player_send_time(wm, goalie.side(), goalie.unum()) + + log.sw_log().communication().add_text( + "(sample communication) ball and goalie" + ) + return True + + if available_len >= Messenger.SIZES[Messenger.Types.BALL_PLAYER]: + p = send_players[0].player + if p.unum() == wm.self().unum(): + agent.add_say_message( + BallPlayerMessenger( + ef.queued_next_ball_pos(), + ball_vel, + wm.self().unum(), + ef.queued_next_self_pos(), + ef.queued_next_self_body(), + ) + ) + else: + agent.add_say_message( + BallPlayerMessenger( + ef.queued_next_ball_pos(), + ball_vel, + send_players[0].number, + p.pos() + p.vel(), + p.body(), + ) + ) + + self._ball_send_time = wm.time().copy() + self.update_player_send_time(wm, p.side(), p.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) ball and player {p.side()}{p.unum()}" + ) + return True + + if wm.ball().pos().x() > 34 and wm.ball().pos().abs_y() < 20: + goalie: PlayerObject = wm.get_their_goalie() + + if ( + goalie is not None + and goalie.seen_pos_count() == 0 + and goalie.body_count() == 0 + and goalie.pos().x() > 53.0 - 16 + and goalie.pos().abs_y() < 20.0 + and goalie.unum() != UNUM_UNKNOWN + and goalie.dist_from_self() < 25 + ): + if available_len >= Messenger.SIZES[Messenger.Types.GOALIE_PLAYER]: + player: PlayerObject = None + for p in send_players: + if ( + p.player.unum() != goalie.unum() + and p.player.side() != goalie.side() + ): + player = p.player + break + + if player is not None: + goalie_pos = goalie.pos() + goalie.vel() + goalie_pos.assign( + bound(53.0 - 16.0, goalie_pos.x(), 52.9), + bound(-20, goalie_pos.y(), 20), + ) + agent.add_say_message( + GoaliePlayerMessenger( + goalie.unum(), + goalie_pos, + goalie.body(), + ( + player.unum() + if player.side() == wm.our_side() + else player.unum() + 11 + ), + player.pos() + player.vel(), + ) + ) + self.update_player_send_time(wm, goalie.side(), goalie.unum()) + self.update_player_send_time(wm, player.side(), player.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) say goalie and player: " + f"goalie({goalie.unum()}): p={goalie.pos()} b={goalie.body()}" + f"player({player.side()}{player.unum()}: {player.pos()})" + ) + return True + + if available_len >= Messenger.SIZES[Messenger.Types.GOALIE]: + goalie_pos = goalie.pos() + goalie.vel() + goalie_pos.assign( + bound(53.0 - 16.0, goalie_pos.x(), 52.9), + bound(-20, goalie_pos.y(), 20), + ) + agent.add_say_message( + GoalieMessenger(goalie.unum(), goalie_pos, goalie.body()) + ) + self._ball_send_time = wm.time().copy() + self._opponent_send_time[goalie.unum()] = wm.time().copy() + + log.sw_log().communication().add_text( + f"(sample communication) say goalie info:" + f"{goalie.unum()} {goalie.pos()} {goalie.body()}" + ) + return True + + if ( + len(send_players) >= 3 + and available_len >= Messenger.SIZES[Messenger.Types.THREE_PLAYER] + ): + for o in send_players: + log.os_log().debug(o.player) + log.os_log().debug(o.player.pos()) + p0 = send_players[0].player + p1 = send_players[1].player + p2 = send_players[2].player + + agent.add_say_message( + ThreePlayerMessenger( + send_players[0].number, + p0.pos() + p0.vel(), + send_players[1].number, + p1.pos() + p1.vel(), + send_players[2].number, + p2.pos() + p2.vel(), + ) + ) + self.update_player_send_time(wm, p0.side(), p0.unum()) + self.update_player_send_time(wm, p1.side(), p1.unum()) + self.update_player_send_time(wm, p2.side(), p2.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) three players:" + f"{p0.side()}{p0.unum()}" + f"{p1.side()}{p1.unum()}" + f"{p2.side()}{p2.unum()}" + ) + return True + + if ( + len(send_players) >= 2 + and available_len >= Messenger.SIZES[Messenger.Types.TWO_PLAYER] + ): + p0 = send_players[0].player + p1 = send_players[1].player + + agent.add_say_message( + TwoPlayerMessenger( + send_players[0].number, + p0.pos() + p0.vel(), + send_players[1].number, + p1.pos() + p1.vel(), + ) + ) + self.update_player_send_time(wm, p0.side(), p0.unum()) + self.update_player_send_time(wm, p1.side(), p1.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) two players:" + f"{p0.side()}{p0.unum()}" + f"{p1.side()}{p1.unum()}" + ) + return True + + if ( + len(send_players) >= 1 + and available_len >= Messenger.SIZES[Messenger.Types.GOALIE] + ): + p0 = send_players[0].player + if ( + p0.side() == wm.their_side() + and p0.goalie() + and p0.pos().x() > 53.0 - 16.0 + and p0.pos().abs_y() < 20 + and p0.dist_from_self() < 25 + ): + goalie_pos = p0.pos() + p0.vel() + goalie_pos.assign( + bound(53.0 - 16.0, goalie_pos.x(), 52.9), + bound(-20, goalie_pos.y(), 20), + ) + agent.add_say_message(GoalieMessenger(p0.unum(), goalie_pos, p0.body())) + + self.update_player_send_time(wm, p0.side(), p0.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) goalie:" f"{p0.side()}{p0.unum()}" + ) + return True + + if ( + len(send_players) >= 1 + and available_len >= Messenger.SIZES[Messenger.Types.ONE_PLAYER] + ): + p0 = send_players[0].player + + agent.add_say_message( + OnePlayerMessenger(send_players[0].number, p0.pos() + p0.vel()) + ) + + self.update_player_send_time(wm, p0.side(), p0.unum()) + + log.sw_log().communication().add_text( + f"(sample communication) one player:" f"{p0.side()}{p0.unum()}" + ) + return True + + return False + + def update_current_sender(self, agent: "PlayerAgent"): + wm = agent.world() + if agent.effector().get_say_message_length() > 0: + self._current_sender_unum = wm.self().unum() + return + + self._current_sender_unum = UNUM_UNKNOWN + candidate_unum: list[int] = [] + + if wm.ball().pos().x() < -10.0 or wm.game_mode().type() != GameModeType.PlayOn: + for unum in range(1, 12): + candidate_unum.append(unum) + else: + goalie_unum = ( + wm.our_goalie_unum() if wm.our_goalie_unum() != UNUM_UNKNOWN else 1 + ) # TODO STRATEGY.GOALIE_UNUM() + for unum in range(1, 12): + if unum != goalie_unum: + candidate_unum.append(unum) + val = ( + wm.time().cycle() + wm.time().stopped_cycle() + if wm.time().stopped_cycle() > 0 + else wm.time().cycle() + ) + current = val % len(candidate_unum) + next = (val + 1) % len(candidate_unum) + + self._current_sender_unum = candidate_unum[current] + self._next_sender_unum = candidate_unum[next] + + def say_recovery(self, agent: "PlayerAgent"): + current_len = agent.effector().get_say_message_length() + available_len = ServerParam.i().player_say_msg_size() - current_len + if available_len < Messenger.SIZES[Messenger.Types.RECOVERY]: + return False + + agent.add_say_message(RecoveryMessenger(agent.world().self().recovery())) + log.sw_log().communication().add_text( + "(sample communication) say self recovery" + ) + return True + + def say_stamina(self, agent: "PlayerAgent"): + current_len = agent.effector().get_say_message_length() + if current_len == 0: + return False + available_len = ServerParam.i().player_say_msg_size() - current_len + if available_len < Messenger.SIZES[Messenger.Types.STAMINA]: + return False + agent.add_say_message(StaminaMessenger(agent.world().self().stamina())) + log.sw_log().communication().add_text("(sample communication) say self stamina") + return True + + def attention_to_someone(self, agent: "PlayerAgent"): # TODO IMP FUNC + wm = agent.world() + ef = agent.effector() + + if ( + wm.self().pos().x() > wm.offside_line_x() - 15.0 + and wm.intercept_table().self_reach_cycle() <= 3 + ): + if ( + self._current_sender_unum != wm.self().unum() + and self._current_sender_unum != UNUM_UNKNOWN + ): + agent.do_attentionto(wm.our_side(), self._current_sender_unum) + player = wm.our_player(self._current_sender_unum) + if player is not None: + log.debug_client().add_circle(player.pos(), 3.0, color="#000088") + log.debug_client().add_line( + player.pos(), wm.self().pos(), "#000088" + ) + log.debug_client().add_message( + f"AttCurSender{self._current_sender_unum}" + ) + else: + candidates: list[PlayerObject] = [] + for p in wm.teammates_from_self(): + if ( + p.goalie() + or p.unum() == UNUM_UNKNOWN + or p.pos().x() > wm.offside_line_x() + 1.0 + ): + continue + if p.dist_from_self() > 20.0: + break + + candidates.append(p) + + self_next = ef.queued_next_self_pos() + + target_teammate: PlayerObject = None + max_x = -100000 + for p in candidates: + diff = p.pos() + p.vel() - self_next + x = diff.x() * (1.0 - diff.abs_y() / 40) + if x > max_x: + max_x = x + target_teammate = p + + if target_teammate is not None: + log.sw_log().communication().add_text( + f"(attentionto someone) most front teammate" + ) + log.debug_client().add_message( + f"AttFrontMate{target_teammate.unum()}" + ) + log.debug_client().add_circle( + target_teammate.pos(), 3.0, color="#000088" + ) + log.debug_client().add_line( + target_teammate.pos(), wm.self().pos(), "#000088" + ) + agent.do_attentionto(wm.our_side(), target_teammate.unum()) + return + + if wm.self().attentionto_unum() > 0: + log.sw_log().communication().add_text( + "(attentionto someone) attentionto off. maybe ball owner" + ) + log.debug_client().add_message("AttOffBOwner") + agent.do_attentionto_off() + return + + fastest_teammate = wm.intercept_table().fastest_teammate() + self_min = wm.intercept_table().self_reach_cycle() + mate_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + + if ( + fastest_teammate is not None + and fastest_teammate.unum() != UNUM_UNKNOWN + and mate_min <= 1 + and mate_min < self_min + and mate_min <= opp_min + 1 + and mate_min <= 5 + min(4, fastest_teammate.pos_count()) + and wm.ball().inertia_point(mate_min).dist2(ef.queued_next_self_pos()) + < 35.0**2 + ): + log.debug_client().add_message(f"AttBallOwner{fastest_teammate.unum()}") + log.debug_client().add_circle(fastest_teammate.pos(), 3.0, color="#000088") + log.debug_client().add_line( + fastest_teammate.pos(), wm.self().pos(), "#000088" + ) + agent.do_attentionto(wm.our_side(), fastest_teammate.unum()) + return + + nearest_teammate = wm.get_teammate_nearest_to_ball(5) + if ( + nearest_teammate is not None + and nearest_teammate.unum() != UNUM_UNKNOWN + and opp_min <= 3 + and opp_min <= mate_min + and opp_min <= self_min + and nearest_teammate.dist_from_self() < 45.0 + and nearest_teammate.dist_from_ball() < 20.0 + ): + log.debug_client().add_message( + f"AttBallNearest(1){nearest_teammate.unum()}" + ) + log.debug_client().add_circle(nearest_teammate.pos(), 3.0, color="#000088") + log.debug_client().add_line( + nearest_teammate.pos(), wm.self().pos(), "#000088" + ) + agent.do_attentionto(wm.our_side(), nearest_teammate.unum()) + return + + if ( + nearest_teammate is not None + and nearest_teammate.unum() != UNUM_UNKNOWN + and wm.ball().pos_count() >= 3 + and nearest_teammate.dist_from_ball() < 20.0 + ): + log.debug_client().add_message( + f"AttBallNearest(2){nearest_teammate.unum()}" + ) + log.debug_client().add_circle(nearest_teammate.pos(), 3.0, color="#000088") + log.debug_client().add_line( + nearest_teammate.pos(), wm.self().pos(), "#000088" + ) + agent.do_attentionto(wm.our_side(), nearest_teammate.unum()) + return + + if ( + nearest_teammate is not None + and nearest_teammate.unum() != 45.0 + and nearest_teammate.dist_from_self() < 45.0 + and nearest_teammate.dist_from_ball() < 3.5 + ): + log.debug_client().add_message( + f"AttBallNearest(3){nearest_teammate.unum()}" + ) + log.debug_client().add_circle(nearest_teammate.pos(), 3.0, color="#000088") + log.debug_client().add_line( + nearest_teammate.pos(), wm.self().pos(), "#000088" + ) + agent.do_attentionto(wm.our_side(), nearest_teammate.unum()) + return + + if ( + self._current_sender_unum != wm.self().unum() + and self._current_sender_unum != UNUM_UNKNOWN + ): + log.debug_client().add_message(f"AttCurSender{self._current_sender_unum}") + player = wm.our_player(self._current_sender_unum) + if player is not None: + log.debug_client().add_circle(player.pos(), 3.0, color="#000088") + log.debug_client().add_line(player.pos(), wm.self().pos(), "#000088") + agent.do_attentionto(wm.our_side(), self._current_sender_unum) + else: + log.debug_client().add_message(f"AttOff") + agent.do_attentionto_off() + + # print("fastest teammate", fastest_teammate) + + def execute(self, agent: "PlayerAgent"): + # if not team_config.USE_COMMUNICATION: + # # print("False") + # return False + + self.update_current_sender(agent) + + wm = agent.world() + penalty_shootout = wm.game_mode().is_penalty_kick_mode() + + say_recovery = False + + # if ( + # wm.game_mode().type() == GameModeType.PlayOn + # and not penalty_shootout + # and self._current_sender_unum == wm.self().unum() + # and wm.self().recovery() < ServerParam.i().recover_init() - 0.002 + # ): + # say_recovery = True + # self.say_recovery(agent) + + # if ( + # wm.game_mode().type() == GameModeType.BeforeKickOff + # or wm.game_mode().type().is_after_goal() + # or penalty_shootout + # ): + # return say_recovery + + # if not ( + # wm.game_mode().type() == GameModeType.BeforeKickOff + # or wm.game_mode().type().is_kick_off() + # ): + + self.say_ball_and_players(agent) + + # print("said ball and players") + self.say_stamina(agent) + + self.attention_to_someone(agent) # TODO IMP FUNC + + return True diff --git a/keepaway/base/sample_player.py b/keepaway/base/sample_player.py new file mode 100755 index 00000000..f80b5454 --- /dev/null +++ b/keepaway/base/sample_player.py @@ -0,0 +1,101 @@ +from keepaway.base.decision import get_decision +from keepaway.base.sample_communication import SampleCommunication +from keepaway.base.view_tactical import ViewTactical +from keepaway.lib.action.go_to_point import GoToPoint +from keepaway.lib.action.intercept import Intercept +from keepaway.lib.action.neck_body_to_ball import NeckBodyToBall +from keepaway.lib.action.neck_turn_to_ball import NeckTurnToBall +from keepaway.lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan +from keepaway.lib.action.scan_field import ScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from keepaway.lib.player.player_agent import PlayerAgent +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType + + +class SamplePlayer(PlayerAgent): + def __init__(self): + super().__init__() + + self._communication = SampleCommunication() + + def action_impl(self): + wm = self.world() + if self.do_preprocess(): + return + + get_decision(self) + + def do_preprocess(self): + wm = self.world() + + print("do_preprocess ball position: ", wm.ball().pos()) + + if wm.self().is_frozen(): + self.set_view_action(ViewTactical()) + self.set_neck_action(NeckTurnToBallOrScan()) + return True + + if not wm.self().pos_valid(): + self.set_view_action(ViewTactical()) + ScanField().execute(self) + return True + + count_thr = 10 if wm.self().goalie() else 5 + if wm.ball().pos_count() > count_thr or ( + wm.game_mode().type() is not GameModeType.PlayOn + and wm.ball().seen_pos_count() > count_thr + 10 + ): + self.set_view_action(ViewTactical()) + NeckBodyToBall().execute(self) + return True + + self.set_view_action(ViewTactical()) + + if self.do_heard_pass_receive(): + return True + + return False + + def do_heard_pass_receive(self): + wm = self.world() + + if ( + wm.messenger_memory().pass_time() != wm.time() + or len(wm.messenger_memory().pass_()) == 0 + or wm.messenger_memory().pass_()[0]._receiver != wm.self().unum() + ): + return False + + self_min = wm.intercept_table().self_reach_cycle() + intercept_pos = wm.ball().inertia_point(self_min) + heard_pos = wm.messenger_memory().pass_()[0]._pos + + log.sw_log().team().add_text( + f"(sample palyer do heard pass) heard_pos={heard_pos}, intercept_pos={intercept_pos}" + ) + + if ( + not wm.kickable_teammate() + and wm.ball().pos_count() <= 1 + and wm.ball().vel_count() <= 1 + and self_min < 20 + ): + log.sw_log().team().add_text( + f"(sample palyer do heard pass) intercepting!, self_min={self_min}" + ) + log.debug_client().add_message("Comm:Receive:Intercept") + Intercept().execute(self) + self.set_neck_action(NeckTurnToBall()) + else: + log.sw_log().team().add_text( + f"(sample palyer do heard pass) go to point!, cycle={self_min}" + ) + log.debug_client().set_target(heard_pos) + log.debug_client().add_message("Comm:Receive:GoTo") + + GoToPoint(heard_pos, 0.5, ServerParam.i().max_dash_power()).execute(self) + self.set_neck_action(NeckTurnToBall()) + + # TODO INTENTION?!? diff --git a/keepaway/base/sample_trainer.py b/keepaway/base/sample_trainer.py new file mode 100755 index 00000000..9ff1bc7d --- /dev/null +++ b/keepaway/base/sample_trainer.py @@ -0,0 +1,27 @@ +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.player.trainer_agent import TrainerAgent +from keepaway.lib.rcsc.types import GameModeType + + +class SampleTrainer(TrainerAgent): + def __init__(self): + super().__init__() + + def action_impl(self): + if self.world().team_name_left() == "": # TODO left team name... # TODO is empty... + self.do_teamname() + return + self.sample_action() + + def sample_action(self): + log.sw_log().block().add_text( "Sample Action") + + wm = self.world() + ballpos = wm.ball().pos() + if ballpos.abs_x() > 10 or ballpos.abs_y() > 10: + for i in range(1, 12): + self.do_move_player(wm.team_name_l(), i, Vector2D(-40, i * 5 - 30)) + self.do_move_ball(Vector2D(0, 0), Vector2D(0, 0)) + self.do_change_mode(GameModeType.PlayOn) diff --git a/keepaway/base/set_play/__init__.py b/keepaway/base/set_play/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/keepaway/base/set_play/bhv_goalie_set_play.py b/keepaway/base/set_play/bhv_goalie_set_play.py new file mode 100755 index 00000000..512a6c17 --- /dev/null +++ b/keepaway/base/set_play/bhv_goalie_set_play.py @@ -0,0 +1,149 @@ +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.vector_2d import Vector2D + +from keepaway.base.generator_action import KickAction, KickActionType +from keepaway.base.generator_pass import BhvPassGen +from keepaway.lib.action.hold_ball import HoldBall +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.action.smart_kick import SmartKick +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.pass_messenger import PassMessenger +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class Bhv_GoalieSetPlay: + _first_move: bool = False + _second_move: bool = False + _wait_count: int = 0 + + def __init__(self): + pass + + def execute(self, agent: 'PlayerAgent'): + log.sw_log().team().add_text("Bhv_GoalieSetPlay execute") + + SP = ServerParam.i() + wm = agent.world() + gm = wm.game_mode() + + if not gm.type().is_goalie_catch_ball() or gm.side() != wm.our_side() or not wm.self().is_kickable(): + log.os_log().debug(f'### goalie set play gm.catch?={gm.type().is_goalie_catch_ball()}') + log.os_log().debug(f'### goalie set play gm.side,ourside={gm.side()}, {wm.our_side()}') + log.os_log().debug(f'### goalie set play iskick?={wm.self().is_kickable()}') + log.sw_log().team().add_text('not a goalie catch mode') + return False + + time_diff = wm.time().cycle() - agent.effector().catch_time().cycle() + log.os_log().debug(f'### goalie set play catch_time={agent.effector().catch_time()}') + log.os_log().debug(f'### goalie set play time diff={time_diff}') + if time_diff <= 2: + Bhv_GoalieSetPlay._first_move = False + Bhv_GoalieSetPlay._second_move = False + Bhv_GoalieSetPlay._wait_count = 0 + + self.do_wait(agent) + return True + + if not Bhv_GoalieSetPlay._first_move: + move_point = Vector2D(SP.our_penalty_area_line_x() - 1.5, -13. if wm.ball().pos().y() < 0 else 13.) + log.os_log().debug(f'### goalie set play move_point={move_point}') + Bhv_GoalieSetPlay._first_move = True + Bhv_GoalieSetPlay._second_move = False + Bhv_GoalieSetPlay._wait_count = 0 + + agent.do_move(move_point.x(), move_point.y()) + agent.set_neck_action(NeckScanField()) + return True + + our_penalty_area = Rect2D(Vector2D(-SP.pitch_half_length(), -40), + Vector2D(-36, +40)) + if time_diff < 50. \ + or wm.set_play_count() < 3 \ + or (time_diff < SP.drop_ball_time() - 15 + and (wm.self().stamina() < SP.stamina_max() * 0.9 + or wm.exist_teammates_in(our_penalty_area, 20, True))): + self.do_wait(agent) + return True + + if not Bhv_GoalieSetPlay._second_move: + move_point = self.get_kick_point(agent) + log.os_log().debug(f'goalie set play move_point 2 ={move_point}') + agent.do_move(move_point.x(), move_point.y()) + agent.set_neck_action(NeckScanField()) + Bhv_GoalieSetPlay._second_move = True + Bhv_GoalieSetPlay._wait_count = 0 + return True + + Bhv_GoalieSetPlay._wait_count += 1 + + if Bhv_GoalieSetPlay._wait_count < 5 or wm.see_time() != wm.time(): + self.do_wait(agent) + return True + + Bhv_GoalieSetPlay._first_move = False + Bhv_GoalieSetPlay._second_move = False + Bhv_GoalieSetPlay._wait_count = 0 + + self.do_kick(agent) + + return True + + def get_kick_point(self, agent: 'PlayerAgent'): + keepaway.base_x = -43. + basy_y = 10. + + candids: list[tuple[Vector2D, float]] = [] + points = [ + Vector2D(keepaway.base_x, basy_y), + Vector2D(keepaway.base_x, -basy_y), + Vector2D(keepaway.base_x, 0), + ] + + for p in points: + score = 0 + for o in agent.world().opponents_from_self(): + score += 1. / o.pos().dist2(p) + candids.append((p, score)) + + best_pos = candids[0][0] + min_score = 100000 + for c in candids: + if c[1] < min_score: + min_score = c[1] + best_pos = c[0] + + return best_pos + + def do_kick(self, agent: 'PlayerAgent'): + wm = agent.world() + action_candidates: list[KickAction] = [] + action_candidates += BhvPassGen().generator(wm) + + if len(action_candidates) == 0: + agent.set_neck_action(NeckScanField()) + return HoldBall().execute(agent) + + best_action: KickAction = max(action_candidates) + + target = best_action.target_ball_pos + log.debug_client().set_target(target) + log.debug_client().add_message( + best_action.type.value + 'to ' + best_action.target_ball_pos.__str__() + ' ' + str( + best_action.start_ball_speed)) + SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3).execute(agent) + + if best_action.type is KickActionType.Pass: + agent.add_say_message(PassMessenger(best_action.target_unum, + best_action.target_ball_pos, + agent.effector().queued_next_ball_pos(), + agent.effector().queued_next_ball_vel())) + + agent.set_neck_action(NeckScanField()) + + def do_wait(self, agent: 'PlayerAgent'): + agent.set_neck_action(NeckScanField()) diff --git a/keepaway/base/set_play/bhv_set_play.py b/keepaway/base/set_play/bhv_set_play.py new file mode 100755 index 00000000..10d6ef02 --- /dev/null +++ b/keepaway/base/set_play/bhv_set_play.py @@ -0,0 +1,175 @@ +from keepaway.base.set_play.bhv_set_play_before_kick_off import Bhv_BeforeKickOff +from keepaway.base.strategy_formation import * +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan +from keepaway.lib.action.scan_field import ScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from keepaway.lib.action.go_to_point import * +from keepaway.lib.messenger.pass_messenger import PassMessenger +from keepaway.lib.rcsc.types import GameModeType +from keepaway.base.generator_action import KickAction +from keepaway.base.generator_pass import BhvPassGen +from keepaway.lib.action.smart_kick import SmartKick + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.object_player import PlayerObject + from keepaway.lib.player.player_agent import PlayerAgent + + +class Bhv_SetPlay: + _kickable_time: int = -1 + _waiting = False + def __init__(self): + pass + + def execute(self, agent: 'PlayerAgent'): + if self.kick(agent): + return True + + log.sw_log().team().add_text( "Bhv_SetPlay") + wm: WorldModel = agent.world() + game_mode = wm.game_mode().type() + game_side = wm.game_mode().side() + + if game_mode is GameModeType.BeforeKickOff or game_mode.is_after_goal(): + return Bhv_BeforeKickOff().execute(agent) + + st = StrategyFormation.i() + target = st.get_pos(wm.self().unum()) + if wm.game_mode().side() is wm.our_side(): + nearest_tm_dist = 1000 + nearest_tm = 0 + for i in range(1, 12): + tm: 'PlayerObject' = wm.our_player(i) + if tm is None: + continue + if tm.unum() == i: + dist = tm.pos().dist(wm.ball().pos()) + if dist < nearest_tm_dist: + nearest_tm_dist = dist + nearest_tm = i + if nearest_tm is wm.self().unum(): + target = wm.ball().pos() + if GoToPoint(target, 0.5, 100).execute(agent): + agent.set_neck_action(NeckTurnToBallOrScan()) + return True + else: + ScanField().execute(agent) + return True + + @staticmethod + def is_kicker(agent): + wm = agent.world() + if wm.game_mode().mode_name() == "goalie_catch" and \ + wm.game_mode().side() == wm.our_side() and \ + not wm.self().goalie(): + log.sw_log().team().add_text( "(is_kicker) goalie free kick") + return False + if not wm.self().goalie() and \ + wm.game_mode().mode_name() == "goal_kick" and \ + wm.game_mode().side() == wm.our_side(): + return False + if wm.self().goalie() and \ + wm.game_mode().mode_name() == "goal_kick" and \ + wm.game_mode().side() == wm.our_side(): + return True + st = StrategyFormation().i() + kicker_unum = 0 + min_dist2 = 1000000 + second_kicker_unum = 0 + second_min_dist2 = 1000000 + for unum in range(1, 12): + if unum == wm.our_goalie_unum(): + continue + + home_pos = st.get_pos(unum) + if not home_pos.is_valid(): + continue + + d2 = home_pos.dist2(wm.ball().pos()) + if d2 < second_min_dist2: + second_kicker_unum = unum + second_min_dist2 = d2 + + if second_min_dist2 < min_dist2: + # swaping (fun in python :)) + second_min_dist2, min_dist2 = min_dist2, second_min_dist2 + second_kicker_unum, kicker_unum = kicker_unum, second_kicker_unum + + log.sw_log().team().add_text( f"(is kicker) kicker_unum={kicker_unum}, second_kicker_unum={second_kicker_unum}") + + kicker: 'PlayerObject' = None + second_kicker: 'PlayerObject' = None + + if kicker_unum != 0: + kicker = wm.our_player(kicker_unum) + if second_kicker_unum != 0: + second_kicker = wm.our_player(second_kicker_unum) + + if kicker is None: + if len(wm.teammates_from_ball()) > 0 and \ + wm.teammates_from_ball()[0].dist_from_ball() < wm.ball().dist_from_self() * 0.9: + log.sw_log().team().add_text( "(is kicker) first kicker") + return False + + log.sw_log().team().add_text( "(is_kicker) self(1)") + return True + + if kicker is not None and \ + second_kicker is not None and \ + (kicker.unum() == wm.self().unum() or \ + second_kicker.unum() == wm.self().unum()): + if min_dist2 ** 0.5 < (second_min_dist2 ** 0.5) * 0.95: + log.sw_log().team().add_text( f"(is kicker) kicker->unum={kicker.unum()} (1)") + return kicker.unum() == wm.self().unum() + elif kicker.dist_from_ball() < second_kicker.dist_from_ball() * 0.95: + log.sw_log().team().add_text( f"(is kicker) kicker->unum={kicker.unum()} (2)") + return kicker.unum() == wm.self().unum() + elif second_kicker.dist_from_ball() < kicker.dist_from_ball() * 0.95: + log.sw_log().team().add_text( f"(is kicker) kicker->unum={kicker.unum()} (3)") + return second_kicker.unum() == wm.self().unum() + elif len(wm.teammates_from_ball()) > 0 and \ + wm.teammates_from_ball()[0].dist_from_ball() < wm.self().dist_from_ball() * 0.95: + log.sw_log().team().add_text( "(is kicker) other kicker") + return False + else: + log.sw_log().team().add_text( "(is kicker) self (2)") + return True + return kicker.unum() == wm.self().unum() + + def kick(self, agent: 'PlayerAgent'): + wm = agent.world() + + if not wm.self().is_kickable(): + return False + + if not Bhv_SetPlay._waiting: + Bhv_SetPlay._kickable_time = wm.time().cycle() + Bhv_SetPlay._waiting = True + + if Bhv_SetPlay._waiting and wm.time().cycle() - Bhv_SetPlay._kickable_time > 30: + Bhv_SetPlay._waiting = False + else: + ScanField().execute(agent) + return True + + action_candidates: list[KickAction] = [] + action_candidates += BhvPassGen().generator(wm) + if len(action_candidates) == 0: + return False + + best_action: KickAction = max(action_candidates) + + target = best_action.target_ball_pos + log.debug_client().set_target(target) + log.debug_client().add_message(f'{best_action.type.value} to {best_action.target_ball_pos} {best_action.start_ball_speed}') + SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3).execute(agent) + agent.add_say_message(PassMessenger(best_action.target_unum, + best_action.target_ball_pos, + agent.effector().queued_next_ball_pos(), + agent.effector().queued_next_ball_vel())) + + agent.set_neck_action(NeckScanPlayers()) + return True diff --git a/keepaway/base/set_play/bhv_set_play_before_kick_off.py b/keepaway/base/set_play/bhv_set_play_before_kick_off.py new file mode 100755 index 00000000..a7a9241e --- /dev/null +++ b/keepaway/base/set_play/bhv_set_play_before_kick_off.py @@ -0,0 +1,23 @@ +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.action.scan_field import ScanField +from keepaway.lib.debug.level import Level +from pyrusgeom.angle_deg import AngleDeg +from keepaway.base.strategy_formation import StrategyFormation + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent +class Bhv_BeforeKickOff: + def __init__(self): + pass + + def execute(self, agent: 'PlayerAgent'): + unum = agent.world().self().unum() + st = StrategyFormation.i() + target = st.get_pos(unum) + if target.dist(agent.world().self().pos()) > 1.: + agent.do_move(target.x(), target.y()) + agent.set_neck_action(NeckScanField()) + return True + ScanField().execute(agent) + return True diff --git a/keepaway/base/stamina_manager.py b/keepaway/base/stamina_manager.py new file mode 100755 index 00000000..d96a44cc --- /dev/null +++ b/keepaway/base/stamina_manager.py @@ -0,0 +1,44 @@ +from keepaway.lib.rcsc.server_param import ServerParam as SP +import pyrusgeom.soccer_math as smath + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +def get_normal_dash_power(wm: 'WorldModel', s_recover_mode: bool): + if wm.self().stamina_model().capacity_is_empty(): + return min(SP.i().max_dash_power(), wm.self().stamina() + wm.self().player_type().extra_stamina()) + + self_min = wm.intercept_table().self_reach_cycle() + mate_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + ball_min_reach_cycle = min(self_min, mate_min, opp_min) + + if wm.self().stamina_model().capacity_is_empty(): + s_recover_mode = False + elif wm.self().stamina() < SP.i().stamina_max() * 0.5: + s_recover_mode = True + elif wm.self().stamina() > SP.i().stamina_max() * 0.7: + s_recover_mode = False + + dash_power = SP.i().max_dash_power() + my_inc = wm.self().player_type().stamina_inc_max() * wm.self().recovery() + + # TODO wm.ourDefenseLineX() > wm.self().pos().x + # TODO wm.ball().pos().x() < wm.ourDefenseLineX() + 20.0 + if wm.self().unum() <= 5 and wm.ball().inertia_point(ball_min_reach_cycle).x() < -20.0: + dash_power = SP.i().max_dash_power() + elif s_recover_mode: + dash_power = my_inc - 25.0 + if dash_power < 0.0: + dash_power = 0.0 + elif wm.exist_kickable_teammates() and wm.ball().dist_from_self() < 20.0: + dash_power = min(my_inc * 1.1, SP.i().max_dash_power()) + elif wm.self().pos().x() > wm.offside_line_x(): + dash_power = SP.i().max_dash_power() + elif wm.ball().pos().x() > 25.0 and wm.ball().pos().x() > wm.self().pos().x() + 10.0 and self_min < opp_min - 6 and mate_min < opp_min - 6: + dash_power = smath.bound(SP.i().max_dash_power() * 0.1, my_inc * 0.5, SP.i().max_dash_power()) + else: + dash_power = min(my_inc * 1.7, SP.i().max_dash_power()) + return dash_power, s_recover_mode diff --git a/keepaway/base/strategy.py b/keepaway/base/strategy.py new file mode 100755 index 00000000..46f93c8b --- /dev/null +++ b/keepaway/base/strategy.py @@ -0,0 +1,36 @@ +from pyrusgeom.geom_2d import * + + +class _Strategy: + def __init__(self): + self._base_poses = [] + self._base_poses.append([Vector2D(-45, 0), 7, 10]) + self._base_poses.append([Vector2D(-30, -10), 7, 10]) + self._base_poses.append([Vector2D(-30, 10), 7, 10]) + self._base_poses.append([Vector2D(-30, -20), 7, 10]) + self._base_poses.append([Vector2D(-30, 20), 7, 10]) + self._base_poses.append([Vector2D(0, -15), 15, 15]) + self._base_poses.append([Vector2D(0, 15), 15, 15]) + self._base_poses.append([Vector2D(0, 0), 15, 15]) + self._base_poses.append([Vector2D(30, -15), 15, 15]) + self._base_poses.append([Vector2D(30, 15), 15, 15]) + self._base_poses.append([Vector2D(30, 0), 15, 15]) + self._poses = [Vector2D(0, 0) for i in range(11)] + + def update(self, wm): + ball_pos = wm.ball().pos() + for p in range(len(self._poses)): + x = ball_pos.x() / 52.5 * self._base_poses[p - 1][1] + self._base_poses[p - 1][0].x() + y = ball_pos.y() / 52.5 * self._base_poses[p - 1][1] + self._base_poses[p - 1][0].y() + self._poses[p - 1] = Vector2D(x, y) + + def get_pos(self, unum): + return self._poses[unum - 1] + + +class Strategy: + _i: _Strategy = _Strategy() + + @staticmethod + def i() -> _Strategy: + return Strategy._i diff --git a/keepaway/base/strategy_formation.py b/keepaway/base/strategy_formation.py new file mode 100755 index 00000000..72fdfa51 --- /dev/null +++ b/keepaway/base/strategy_formation.py @@ -0,0 +1,109 @@ +from keepaway.formation.delaunay_triangulation import * +import os +from enum import Enum +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.types import GameModeType + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +class Situation(Enum): + OurSetPlay_Situation = 0, + OppSetPlay_Situation = 1, + Defense_Situation = 2, + Offense_Situation = 3, + PenaltyKick_Situation = 4 + + +class _StrategyFormation: + def __init__(self): + pwd = '.' + if "keepaway.base" in os.listdir('.'): + pwd = 'keepaway.base' + self.before_kick_off_formation: Formation = Formation(f'{pwd}/formation_dt/before_kick_off.conf') + self.defense_formation: Formation = Formation(f'{pwd}/formation_dt/defense_formation.conf') + self.offense_formation: Formation = Formation(f'{pwd}/formation_dt/offense_formation.conf') + self.goalie_kick_opp_formation: Formation = Formation(f'{pwd}/formation_dt/goalie_kick_opp_formation.conf') + self.goalie_kick_our_formation: Formation = Formation(f'{pwd}/formation_dt/goalie_kick_our_formation.conf') + self.kickin_our_formation: Formation = Formation(f'{pwd}/formation_dt/kickin_our_formation.conf') + self.setplay_opp_formation: Formation = Formation(f'{pwd}/formation_dt/setplay_opp_formation.conf') + self.setplay_our_formation: Formation = Formation(f'{pwd}/formation_dt/setplay_our_formation.conf') + self._poses = [Vector2D(0, 0) for i in range(11)] + self.current_situation = Situation.Offense_Situation + self.current_formation = self.offense_formation + + def update(self, wm: 'WorldModel'): + log.os_log().debug(f'form{wm.time().cycle()},{wm.time().stopped_cycle()}, {wm.game_mode().type()} {wm.game_mode().is_our_set_play(wm.our_side())}') + tm_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + self_min = wm.intercept_table().self_reach_cycle() + all_min = min(tm_min, opp_min, self_min) + ball_pos = wm.ball().inertia_point(all_min) + + if wm.game_mode().type() is GameModeType.PlayOn: + thr = 0 + if wm.ball().inertia_point(min(self_min, tm_min, opp_min)).x() > 0: + thr += 1 + if wm.self().unum() > 6: + thr += 1 + if min(tm_min, self_min) < opp_min + thr: + self.current_situation = Situation.Offense_Situation + else: + self.current_situation = Situation.Defense_Situation + else: + if wm.game_mode().is_penalty_kick_mode(): + self.current_situation = Situation.PenaltyKick_Situation + elif wm.game_mode().is_our_set_play(wm.our_side()): + self.current_situation = Situation.OurSetPlay_Situation + else: + self.current_situation = Situation.OppSetPlay_Situation + + if wm.game_mode().type() is GameModeType.PlayOn: + if self.current_situation is Situation.Offense_Situation: + self.current_formation = self.offense_formation + else: + self.current_formation = self.defense_formation + + elif wm.game_mode().type() in [GameModeType.BeforeKickOff, GameModeType.AfterGoal_Left, + GameModeType.AfterGoal_Right]: + self.current_formation = self.before_kick_off_formation + + elif wm.game_mode().type() in [GameModeType.GoalKick_Left, GameModeType.GoalKick_Right, GameModeType.GoalieCatchBall_Left, GameModeType.GoalieCatchBall_Right]: # Todo add Goal Catch!! + if wm.game_mode().is_our_set_play(wm.our_side()): + self.current_formation = self.goalie_kick_our_formation + else: + self.current_formation = self.goalie_kick_opp_formation + + else: + if wm.game_mode().is_our_set_play(wm.our_side()): + if wm.game_mode().type() in [GameModeType.KickIn_Right, GameModeType.KickIn_Left, + GameModeType.CornerKick_Right, GameModeType.CornerKick_Left]: + self.current_formation = self.kickin_our_formation + else: + self.current_formation = self.setplay_our_formation + else: + self.current_formation = self.setplay_opp_formation + + self.current_formation.update(ball_pos) + self._poses = self.current_formation.get_poses() + + if self.current_formation is self.before_kick_off_formation or wm.game_mode().type() in \ + [GameModeType.KickOff_Left, GameModeType.KickOff_Right]: + for pos in self._poses: + pos._x = min(pos.x(), -0.5) + else: + pass # Todo add offside line + # for pos in self._poses: + # pos._x = math.min(pos.x(), ) + + def get_pos(self, unum): + return self._poses[unum - 1] + + +class StrategyFormation: + _i: _StrategyFormation = _StrategyFormation() + + @staticmethod + def i() -> _StrategyFormation: + return StrategyFormation._i diff --git a/keepaway/base/tackle_generator.py b/keepaway/base/tackle_generator.py new file mode 100755 index 00000000..9eefb15a --- /dev/null +++ b/keepaway/base/tackle_generator.py @@ -0,0 +1,292 @@ +from math import exp + +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.line_2d import Line2D +from pyrusgeom.ray_2d import Ray2D +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.segment_2d import Segment2D +from pyrusgeom.soccer_math import inertia_final_point, inertia_n_step_point +from pyrusgeom.vector_2d import Vector2D + +from keepaway.base.generator_action import TackleAction +from keepaway.base.tools import Tools +from keepaway.lib.rcsc.game_time import GameTime + + + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + + +class TackleGenerator: + ANGLE_DIVS = 40 + + + _update_time: GameTime = GameTime() + _instance = None + def __init__(self): + self._candidates: list[TackleAction] = [] + self._best_result: TackleAction = None + + @staticmethod + def instance() -> 'TackleGenerator': + if TackleGenerator._instance: + return TackleGenerator._instance + TackleGenerator._instance = TackleGenerator() + return TackleGenerator._instance + + + def generate(self, wm: 'WorldModel'): + if TackleGenerator._update_time == wm.time(): + return + + self._update_time = wm.time().copy() + + self.clear() + + if wm.self().is_kickable(): + return + + if wm.self().tackle_probability() < 0.001 and wm.self().foul_probability() < 0.001: + return + + if wm.time().stopped_cycle() > 0: + return + + if wm.game_mode().time() is not GameModeType.PlayOn and not wm.game_mode().is_penalty_kick_mode(): + return + + self.calculate(wm) + + def clear(self): + self._best_result = TackleAction() + self._candidates.clear() + + def best_result(self, wm: 'WorldModel'): + self.generate(wm) + return self._best_result + + + def calculate(self, wm: 'WorldModel'): + SP = ServerParam.i() + + min_angle = SP.min_moment() + max_angle = SP.max_moment() + angle_step = abs(max_angle - min_angle) / TackleGenerator.ANGLE_DIVS + + ball_rel_angle = wm.ball().angle_from_self() - wm.self().body() + tackle_rate = SP.tackle_power_rate() * (1 - 0.5*ball_rel_angle.abs()/ 180.) + + for a in range(TackleGenerator.ANGLE_DIVS): + dir = AngleDeg(min_angle + angle_step*a) + eff_power = SP.max_back_tackle_power() \ + + (SP.max_tackle_power() - SP.max_back_tackle_power()) \ + * (1. - (dir.abs() / 180.)) + eff_power *= tackle_rate + + angle = wm.self().body() + dir + accel = Vector2D(r=eff_power, a=angle) + + vel = wm.ball().vel() + accel + speed = vel.r() + if speed > SP.ball_speed_max(): + vel.set_length(SP.ball_speed_max()) + + self._candidates.append(TackleAction(angle, vel)) + + self._best_result.clear() + + for tackle in self._candidates: + tackle._score = self.evaluate(wm, tackle) + + if tackle._score > self._best_result._score: + self._best_result = tackle + + def evaluate(self, wm, result: TackleAction): + SP = ServerParam.i() + ball_end_point = inertia_final_point(wm.ball().pos(), + result._ball_vel, + SP.ball_decay()) + + ball_line = Segment2D(wm.ball().pos(), ball_end_point) + ball_speed = result._ball_speed + ball_move_angle = result._ball_move_angle + + if ball_end_point.x() > SP.pitch_half_length() \ + and wm.ball().pos().dist2(SP.their_team_goal_pos()) < 20**2: + + goal_line = Line2D(Vector2D(SP.pitch_half_length(), 10), + Vector2D(SP.pitch_half_length(), -10)) + intersect = ball_line.intersection(goal_line) + if intersect and intersect.is_valid() and intersect.abs_y() < SP.goal_half_width(): + shoot_score = 1000000. + + speed_rate = 1. - exp(-ball_speed**2 / (2* (SP.ball_speed_max()/2)**2)) + y_rate = exp(-intersect.abs_y()**2/(2*SP.goal_width()**2)) + + shoot_score *= speed_rate * y_rate + + return shoot_score + + if ball_end_point.x() < -SP.pitch_half_length(): + goal_line = Line2D(Vector2D(-SP.pitch_half_length(), 10), + Vector2D(-SP.pitch_half_length(), -10)) + intersect = ball_line.intersection(goal_line) + if intersect and intersect.is_valid() and intersect.abs_y() < SP.goal_half_width() + 1.: + y_penalty = -10000.0 *exp(-(intersect.abs_y() - SP.goal_half_width())**2 /(2*(SP.ball_speed_max()*0.5)**2)) + speed_bonus = 10000 * exp(-ball_speed**2/(2* (SP.ball_speed_max()*0.5)**2)) + + shoot_score = y_penalty + speed_bonus + return shoot_score + + opponent_reach_step = self.predict_opponents_reach_step(wm, wm.ball().pos(), result._ball_vel, ball_move_angle) + final_point = inertia_n_step_point(wm.ball().pos(), result._ball_vel, opponent_reach_step, SP.ball_decay()) + + final_segment = Segment2D(wm.ball().pos(), final_point) + pitch = Rect2D.from_center(0., 0., SP.pitch_length(), SP.pitch_width()) + intersections = pitch.intersection(final_segment) + + if len(intersections) > 0: + final_point = intersections[0] + + our_goal_angle = (SP.our_team_goal_pos() - wm.ball().pos()).th() + our_goal_angle_diff = (our_goal_angle - ball_move_angle).abs() + our_goal_angle_rate = 1 - exp(-our_goal_angle_diff**2 / (2*40**2)) + + y_rate = 1 \ + if final_point.abs_y() > SP.pitch_half_width() - 0.1 \ + else exp(-(final_point.abs_y() - SP.pitch_half_width())/ (2*SP.pitch_half_width() *0.7) ** 2) + + opp_rate = 1- exp(-opponent_reach_step**2 / (2*30**2)) + score = 10000. *our_goal_angle_rate*y_rate*opp_rate + + return score + + def predict_opponents_reach_step(self, + wm: 'WorldModel', + first_ball_pos: Vector2D, + first_ball_vel: Vector2D, + ball_move_angle: AngleDeg): + first_min_step = 50 + + SP = ServerParam.i() + ball_end_point = inertia_final_point(first_ball_pos, first_ball_vel, SP.ball_decay()) + + if ball_end_point.abs_x() > SP.pitch_half_length() or ball_end_point.abs_y() > SP.pitch_half_width(): + pitch = Rect2D.from_center(0., 0., SP.pitch_length(), SP.pitch_width()) + ball_ray = Ray2D(first_ball_pos, ball_move_angle) + + intersections = pitch.intersection(ball_ray) + if len(intersections) == 1: + first_min_step = SP.ball_move_step(first_ball_vel.r(), first_ball_pos.dist(intersections[0])) + + min_step = first_min_step + for o in wm.their_players(): + step = self.predict_opponent_reach_step(o, + first_ball_pos, + first_ball_vel, + ball_move_angle, + min_step) + + if step < min_step: + min_step = step + + return 1000 if min_step == first_min_step else min_step + + def predict_opponent_reach_step(self, + opponent: 'PlayerObject', + first_ball_pos: Vector2D, + first_ball_vel: Vector2D, + ball_move_angle: AngleDeg, + max_cycle): + SP = ServerParam.i() + ptype = opponent.player_type() + + opponent_speed = opponent.vel().r() + + min_cycle = Tools.estimate_min_reach_cycle(opponent.pos(), + ptype.real_speed_max(), + first_ball_pos, + ball_move_angle) + + if min_cycle < 0: + min_cycle = 10 + + for cycle in range(min_cycle, max_cycle): + ball_pos = inertia_n_step_point(first_ball_pos, + first_ball_vel, + cycle, + SP.ball_decay()) + + if ball_pos.abs_x() > SP.pitch_half_length() or ball_pos.abs_y() > SP.pitch_half_width(): + return 1000 + + inertia_pos = opponent.inertia_point(cycle) + target_dist = inertia_pos.dist(ball_pos) + + if target_dist - ptype.kickable_area() < 0.001: + return cycle + + dash_dist = target_dist + if cycle > 1: + dash_dist -= ptype.kickable_area() + dash_dist -= 0.5 + + if dash_dist > ptype.real_speed_max()*cycle: + continue + + n_dash = ptype.cycles_to_reach_distance(dash_dist) + if n_dash > cycle: + continue + + n_turn = 0 if opponent.body_count() > 1 else Tools.predict_player_turn_cycle(ptype, + opponent.body(), + opponent_speed, + target_dist, + (ball_pos - inertia_pos).th(), + ptype.kickable_area(), + True) + n_step = n_turn + n_dash if n_turn == 0 else n_turn + n_dash + 1 + + if opponent.is_tackling(): + n_step += 5 + + n_step -= min(3, opponent.pos_count()) + if n_step <= cycle: + return cycle + return 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/keepaway/base/tools.py b/keepaway/base/tools.py new file mode 100755 index 00000000..e801b7a6 --- /dev/null +++ b/keepaway/base/tools.py @@ -0,0 +1,357 @@ +import math + +from pyrusgeom.geom_2d import * +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.game_mode import GameModeType +from keepaway.lib.action.kick_table import calc_max_velocity +import pyrusgeom.soccer_math as sm + +# from keepaway.lib.rcsc.types import CommandType +from keepaway.lib.player_command.player_command import CommandType +from pyrusgeom.vector_2d import Vector2D +from pyrusgeom.angle_deg import AngleDeg + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + + +class Tools: + @staticmethod + def predict_player_turn_cycle( + ptype: PlayerType, + player_body: AngleDeg, + player_speed, + target_dist, + target_angle: AngleDeg, + dist_thr, + use_back_dash, + ): + sp = ServerParam.i() + n_turn = 0 + angle_diff = (target_angle - player_body).abs() + + if ( + use_back_dash + and target_dist < 5.0 + and angle_diff > 90.0 + and sp.min_dash_power() < -sp.max_dash_power() + 1.0 + ): + angle_diff = abs(angle_diff - 180.0) + + turn_margin = 180.0 + if dist_thr < target_dist: + turn_margin = max(15.0, AngleDeg.asin_deg(dist_thr / target_dist)) + + speed = float(player_speed) + while angle_diff > turn_margin: + angle_diff -= ptype.effective_turn(sp.max_moment(), speed) + speed *= ptype.player_decay() + n_turn += 1 + + return n_turn + + @staticmethod + def predict_kick_count( + wm: "WorldModel", kicker, first_ball_speed, ball_move_angle: AngleDeg + ): + if ( + wm.game_mode().type() != GameModeType.PlayOn + and not wm.game_mode().is_penalty_kick_mode() + ): + return 1 + + if kicker == wm.self().unum() and wm.self().is_kickable(): + max_vel = calc_max_velocity( + ball_move_angle, wm.self().kick_rate(), wm.ball().vel() + ) + if max_vel.r2() >= pow(first_ball_speed, 2): + return 1 + if first_ball_speed > 2.5: + return 3 + elif first_ball_speed > 1.5: + return 2 + return 1 + + @staticmethod + def estimate_min_reach_cycle( + player_pos: Vector2D, + player_speed_max, + target_first_point: Vector2D, + target_move_angle: AngleDeg, + ): + target_to_player: Vector2D = (player_pos - target_first_point).rotated_vector( + -target_move_angle + ) + if target_to_player.x() < -1.0: + return -1 + else: + return max(1, int(target_to_player.abs_y() / player_speed_max)) + + @staticmethod + def get_nearest_teammate( + wm: "WorldModel", position: Vector2D, players: list["PlayerObject"] = None + ): + if players is None: + players = wm.teammates() + best_player: "PlayerObject" = None + min_dist2 = 1000 + for player in players: + d2 = player.pos().dist2(position) + if d2 < min_dist2: + min_dist2 = d2 + best_player = player + + return best_player + + @staticmethod + def estimate_virtual_dash_distance(player: "PlayerObject"): + pos_count = min(10, player.pos_count(), player.seen_pos_count()) + max_speed = player.player_type().real_speed_max() * 0.8 + + d = 0.0 + for i in range(pos_count): + d += max_speed * math.exp(-(i**2) / 15) + + return d + + @staticmethod + def get_end_speed_from_first_speed(end_speed, cycles, decay): + SS = ServerParam.i() + if decay < 0.0: + decay = SS.ball_decay() + return end_speed * math.pow(1.0 / SS.ball_decay(), cycles) + + @staticmethod + def get_kick_travel(distance, target_speed): + SS = ServerParam.i() + if target_speed < 0.0001: + return sm.calc_first_term_geom_series(distance, SS.ball_decay()) + steps = sm.calc_length_geom_series( + target_speed, 1.0 / SS.ball_decay(), distance + ) + + sp = Tools.get_end_speed_from_first_speed(target_speed, steps, SS.ball_decay()) + # print("speed first : ", sp) + return sp + + @staticmethod + def predict_stamina_after_dash(dash_power, stamina): + sta = stamina.stamina() + eff = stamina.effort() + rec = stamina.recovery() + # // double negative value when dashed backwards + sta -= dash_power if dash_power > 0.0 else -2 * dash_power + if sta < 0: + sta = 0 + # // stamina below recovery threshold, lower recovery + if ( + sta <= ServerParam.i().recover_dec_thr() * ServerParam.i().stamina_max() + and rec > ServerParam.i().recover_min() + ): + rec -= ServerParam.i().recover_dec() + # // stamina below effort decrease threshold, lower effort + if ( + sta <= ServerParam.i().effort_dec_thr() * ServerParam.i().stamina_max() + and eff > ServerParam.i().effort_min() + ): + eff -= ServerParam.i().effort_dec() + # // stamina higher than effort incr threshold, raise effort and check maximum + if ( + sta >= ServerParam.i().effort_inc_thr() * ServerParam.i().stamina_max() + and eff < 1.0 + ): + eff += ServerParam.i().effort_inc() + if eff > 1.0: + eff = 1.0 + # // increase stamina with (new) recovery value and check for maximum + sta += rec * ServerParam.i().stamina_inc_max() + if sta > ServerParam.i().stamina_max(): + sta = ServerParam.i().stamina_max() + stamina.set_stamina(sta) + stamina.set_effort(eff) + stamina.set_recovery(rec) + + @staticmethod + def predict_state_after_dash(p, dash_power, pos, vel, stamina, direction): + """Predict the state of the object after dash.""" + + SP = ServerParam.i() + # get acceleration associated with actual power + effort = stamina.effort() if stamina else p.effort_max() + acc = dash_power * p.player_type().dash_power_rate() * effort + + # add it to the velocity; negative acceleration in backward direction + if acc > 0: + vel += Vector2D.polar2vector(acc, direction) + else: + vel += Vector2D.polar2vector(abs(acc), direction + 180) + + # check if velocity doesn't exceed maximum speed + if vel.r() > SP.default_player_speed_max(): + vel.set_r(SP.player_speed_max()) + + # add velocity to current global position and decrease velocity + pos += vel + vel *= p.player_type().player_decay() + # print("pos: ", pos) + # print("vel: ", vel) + + if stamina: + stamina.simulate_dash(p.player_type(), dash_power) + # Tools.predict_stamina_after_dash(dash_power, stamina) + + @staticmethod + def predict_pos_after_n_cycles(p, cycles, dash_power): + """Returns the position of player object after n cycles.""" + + SP = ServerParam.i() + # if p == BallObject: + # d = Geometry.get_sum_geom_series( + # vel.r(), + # SP.ball_decay(), + # cycles, + # ) + # pos += Vector2D(d, vel.th(), POLAR) + # vel *= math.pow(SP.ball_decay(),cycles) + + # if p == ptype.player_type: + update = True + ptype = p.player_type() + direction = p.body() + # print("direction: ", direction) + stamina = p.stamina_model() + + for i in range(cycles): + Tools.predict_state_after_dash( + p, dash_power, p.pos(), p.vel(), stamina, direction + ) + if update: + pos = p.pos() + vel = p.vel() + return pos + + @staticmethod + def predict_ball_after_command(wm: "WorldModel", command, pos, vel): + ball_pos = wm.ball().pos() + ball_vel = wm.ball().vel() + if command.type() == CommandType.KICK: + kick_angle = command.kick_dir() + power = command.kick_power() + angle = AngleDeg.normalize_angle(kick_angle + wm.self().body().degree()) + ball_vel += Vector2D.polar2vector( + wm.self().player_type().kick_power_rate() * power, angle + ) + if ball_vel.r() > ServerParam.i().ball_speed_max(): + ball_vel.set_length(ServerParam.i().ball_speed_max()) + # print("ang: ", angle, "kick_rate: ", wm.self().player_type().kick_power_rate()) + # print("update for kick: ", power, angle) + ball_pos += ball_vel + ball_vel *= ServerParam.i().ball_decay() + return ball_pos, ball_vel + + # @staticmethod + # def predict_pos_after_command(wm: "WorldModel", command): + + # @staticmethod + # def predict_state_after_dash( + # dash_power, + # pos: Vector2D, + # vel: Vector2D, + # stamina, + # d_direction, + # p : "PlayerObject", + # ): + # SS = ServerParam.i() + + # d_effort = stamina.get_effort() if stamina else p.stamina_model().get_effort() + # d_acc = dash_power * SS.dash_power_rate() * d_effort + + # # add it to the velocity; negative acceleration in backward direction + # if d_acc > 0: + # vel += Vector2D.polar2vector(d_acc, d_direction) + # else: + # vel += Vector2D.polar2vector(abs(d_acc), Vector2D.normalize_angle(d_direction + 180)) + + # # check if velocity doesn't exceed maximum speed + # if vel.r() > SS.default_player_speed_max(): + # vel.set_length(SS.default_player_speed_max()) + + # # add velocity to current global position and decrease velocity + # pos += vel + # vel *= p.player_type().player_decay() + + # # if stamina is provided, predict its value after dash + # if stamina: + # predict_stamina_after_dash(d_actual_power, stamina) + + # @staticmethod + # def predict_stamina_after_dash(dash_power): + # SS = ServerParam.i() + # sta = me.stamina_model() + # eff = sta.effort() + # rec = True + + # # double negative value when dashed backwards + # sta -= dash_power if dash_power > 0.0 else -2 * dash_power + # if sta < 0: + # sta = 0 + + # # stamina below recovery threshold, lower recovery + # if sta <= SS.recover_dec_thr * SS.stamina_max and rec > SS.recover_min: + # rec -= SS.recover_dec + + # # stamina below effort decrease threshold, lower effort + # if sta <= SS.effort_dec_thr * SS.stamina_max and eff > SS.effort_min: + # eff -= SS.effort_dec + + # # stamina higher than effort increase threshold, raise effort and check maximum + # if sta >= SS.effort_inc_thr * SS.stamina_max and eff < 1.0: + # eff += SS.effort_inc + # if eff > 1.0: + # eff = 1.0 + + # # increase stamina with (new) recovery value and check for maximum + # sta += rec * SS.stamina_inc_max + # if sta > SS.stamina_max: + # sta = SS.stamina_max + + # stamina.set_stamina(sta) + # stamina.set_effort(eff) + # stamina.set_recovery(rec) + + # @staticmethod + # def estimate_position( + # player: "PlayerObject", + # cycles, + # dash_power, + # pos: Vector2D, + # vel: Vector2D, + # ): + + # vel = wm.self()._vel if vel is None else vel + # pos = wm.self()._pos if pos is None else pos + + # d_direction = 0.0 + # stamina = wm.self().stamina_model() + + # if player: + # d_direction = player.body() + # stamina = player.stamina_model() + # elif wm.self().time() > wm.self().last_dash_time() + 2: + # d_direction = wm.self().body() + + # for i in range(int(cycles)): + # predict_state_after_dash( + # dash_power, pos, vel, stamina, d_direction, obj + # ) + + # if pos not None: + # pos = pos + vel * cycles: + # if vel not None: + # vel = vel * player.player_type().player_decay() ** cycles + + # return pos diff --git a/keepaway/base/view_tactical.py b/keepaway/base/view_tactical.py new file mode 100755 index 00000000..6d14fb36 --- /dev/null +++ b/keepaway/base/view_tactical.py @@ -0,0 +1,142 @@ + +from typing import TYPE_CHECKING + +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.player.soccer_action import ViewAction +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType, ViewWidth + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class ViewTactical(ViewAction): + PREV1 = None + PREV2 = None + + def __init__(self): + pass + + def execute(self, agent: 'PlayerAgent'): + ViewTactical.PREV2 = ViewTactical.PREV1 + ViewTactical.PREV1 = agent.world().self().view_width().width() + + best_width = None + gm = agent.world().game_mode().type() + if (gm is GameModeType.BeforeKickOff + or gm.is_after_goal() + or gm.is_penalty_setup() + or gm.is_penalty_ready() + or gm.is_penalty_miss() + or gm.is_penalty_score()): + + best_width = ViewWidth.WIDE + elif gm.is_penalty_taken(): + best_width = ViewWidth.NARROW + + elif gm.is_goalie_catch_ball() and gm.side() == agent.world().our_side(): + best_width = self.get_our_goalie_free_kick_view_width(agent) + else: + best_width = self.get_default_view_width(agent) + + if ViewTactical.PREV1 == 180.0 and ViewTactical.PREV2 == 180.0 and agent.world().see_time().cycle() == agent.world().time().cycle() - 2: + best_width = ViewWidth.WIDE + elif ViewTactical.PREV1 == 120.0 and best_width.width() == 60.0 and agent.world().see_time().cycle() == agent.world().time().cycle() - 1: + best_width = ViewWidth.NORMAL + + return agent.do_change_view(best_width) + + def get_default_view_width(self, agent: 'PlayerAgent'): + wm = agent.world() + ef = agent.effector() + SP = ServerParam.i() + + if not wm.ball().pos_valid(): + return ViewWidth.WIDE + + self_min = wm.intercept_table().self_reach_cycle() + mate_min = wm.intercept_table().teammate_reach_cycle() + opp_min = wm.intercept_table().opponent_reach_cycle() + ball_reach_cycle = min(self_min, mate_min, opp_min) + + ball_pos = wm.ball().inertia_point(ball_reach_cycle) + ball_dist = ef.queued_next_self_pos().dist(ball_pos) + + if wm.self().goalie() and not wm.self().is_kickable(): + goal_pos = Vector2D(- SP.pitch_half_length(), 0) + if ball_dist > 10 or ball_pos.x() > SP.our_penalty_area_line_x() or ball_pos.dist(goal_pos) > 20: + ball_angle = ef.queued_next_angle_from_body(ef.queued_next_ball_pos()) # TODO IMP FUNC + angle_diff = ball_angle.abs() - SP.max_neck_angle() + if angle_diff > ViewWidth.NORMAL.width()/2 -3: + return ViewWidth.WIDE + if angle_diff > ViewWidth.NARROW.width()/2 -3: + return ViewWidth.NORMAL + + if ball_dist > 40: + return ViewWidth.WIDE + + if ball_dist > 20: + return ViewWidth.NORMAL + + if ball_dist > 10: + ball_angle = ef.queued_next_angle_from_body(ef.queued_next_ball_pos()) + if ball_angle.abs() > 120: + return ViewWidth.WIDE + + teammate_ball_dist = 1000. + opponent_ball_dist = 1000. + + if len(wm.teammates_from_ball()) > 0: + teammate_ball_dist = wm.teammates_from_ball()[0].dist_from_ball() + if len(wm.opponents_from_ball()) > 0: + opponent_ball_dist = wm.opponents_from_ball()[0].dist_from_ball() + + if (not wm.self().goalie() + and teammate_ball_dist > 5 + and opponent_ball_dist > 5 + and ball_dist > 10 + and wm.ball().dist_from_self() > 10): + + return ViewWidth.NORMAL + return ViewWidth.NARROW + + def get_our_goalie_free_kick_view_width(self, agent: 'PlayerAgent'): + if agent.world().self().goalie(): + return ViewWidth.WIDE + return self.get_default_view_width(agent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/keepaway/config/.DS_Store b/keepaway/config/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..797fd239dafa98cece549b5794a0df924b1b867b GIT binary patch literal 6148 zcmeHKJ5B>J5S@V(E2T+E>6S7#Fwt^?9AF_EM2bYa4F#P~K+h>S1rp*C9DytF=0jk` zk_HJOG$Yw>Y|o5mpR~4zh(#bf!J@ifgM7=FDKNU4x|V1i>Lu9es(x()3TO8El>!2wW6nrMwk(@0AiQY8cBWe?ogT&aliR=R7 zc1|_hGBqurP<;$2qyY^np`gaL6;=VOz*SR#&u$B+HK2mt;q3lBzd?4g=MUtl&{IC0 zZf1^TzaM2X0>k;7v=2Uh`}*iIcKnA8*74=+Vz@esFK|FGDo9dBDdw9Z)|An#EX?@l zQIVzPWOw(6*xGJfyMDuQoR;(2AIh0O2`AIC7mi=@*E1=zXju-U=V>wv+P5Fed=jR4 zGFA?0oPhG`MViNQ)|1mbE|ssZ8=Pjd8MN=r=Lh>8cmKiBqT|jV9(6lz_prNIG@YG$ z_m7_jZ?jP@-}Af?B@rf--7>g@4=B`@6_wBSo7`47m{c)bj`~>q^E|%wS?dmCD_Q&BEV9hFE6|f4dD!}o} zRvK04K&GAmzy_+7q0Day_Hhk*1}lwdfeGyj)ULujF@$zUy=!=$!AhfcCt)5R!i+4; z3q`2W(Z8$cBs`5aw+dJV))m;+pKZSXpKN~ruRGZttI#va~iti#RLz~M5pl7hsh#HvvBcNoknN{GQD)1Y0^qi6a literal 0 HcmV?d00001 diff --git a/keepaway/envs/__init__.py b/keepaway/envs/__init__.py new file mode 100644 index 00000000..3fbb1fa7 --- /dev/null +++ b/keepaway/envs/__init__.py @@ -0,0 +1,4 @@ +# from keepaway.envs.keepaway_wrapper import KeepawayEnv +# __all__ = [ +# "KeepawayEnv", +# ] \ No newline at end of file diff --git a/keepaway/envs/keepaway_env.py b/keepaway/envs/keepaway_env.py new file mode 100644 index 00000000..13625a8f --- /dev/null +++ b/keepaway/envs/keepaway_env.py @@ -0,0 +1,371 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import atexit +from warnings import warn +from operator import attrgetter +import copy +import numpy as np +import enum +import math +from absl import logging +from subprocess import Popen +import yaml +from optparse import OptionParser +import time +from keepaway.lib.player.world_model import WorldModel +import multiprocessing +import keepaway.utils.main_keepaway_player as kp +import atexit +import keepaway.base.main_coach as main_c +from keepaway.envs.multiagentenv import MultiAgentEnv +import os + +config_dir = os.path.dirname(os.getcwd()) + "/config" +print(config_dir) + +class KeepawayEnv(MultiAgentEnv): + """Keepaway environment for multi-agent reinforcement learning scenarios version 0.1.0.""" + + def __init__(self, pitch_size=20, sparse_reward=False): + """ + Initialize a keepaway environment. + --------------------------------- + Parameters: + + """ + + self.num_keepers = 3 + self.num_takers = 2 + self.pitch_size = pitch_size + self.sparse_reward = sparse_reward + self.actions = self.num_keepers # 0: hold, 1: pass + self._episode_count = 0 + self._episode_steps = 0 + self._total_steps = 0 + self.force_restarts = 0 + self.episode_limit = 1000 + self.timeouts = 0 + self.continuing_episode = False + self.num_agents = self.num_keepers + self.num_takers + + self._last_action = None + manager = multiprocessing.Manager() + self._world = WorldModel("real", manager) # for all agents + # self._world.observations = {agent: None for agent in range(self.num_keepers)} + + self._lock = self._world + self._event = multiprocessing.Event() + self._barrier = multiprocessing.Barrier(3) + + ### Event implementation + self._event_from_subprocess = multiprocessing.Event() + self._main_process_event = ( + multiprocessing.Event() + ) # To be set by main process to wake up all subprocesses + + self._actions = [0] * 3 + # Create a shared list to hold the count for each process + self._shared_values = multiprocessing.Array("i", self._actions) + + self._obs = self._world._obs + # Use a joint value instead + self.last_action_time = 0 + self._last_action_time = multiprocessing.Value("i", self.last_action_time) + self._proximity_adj_mat = self._world._adjacency_matrix + + ## reward + self._reward = self._world._reward + ## episode + self._terminated = self._world._terminated + self._episode_reward = [] + self._proximity_threshold = 2 + + self._keepers = [ + multiprocessing.Process( + target=kp.main, + args=( + "keepers", + i, + False, + self._shared_values, + self._barrier, + self._lock, + self._event, + self._event_from_subprocess, + self._main_process_event, + self._world, + self._obs, + self._last_action_time, + self._reward, + self._terminated, + self._proximity_adj_mat, + self._proximity_threshold, + ), + name="keeper", + ) + for i in range(self.num_keepers) + ] + + self._takers = [ + multiprocessing.Process( + target=kp.main, + args=( + "takers", + i, + False, + self._shared_values, + self._barrier, + self._lock, + self._event, + self._event_from_subprocess, + self._main_process_event, + self._world, + self._obs, + self._last_action_time, + self._reward, + self._terminated, + self._proximity_adj_mat, + self._proximity_threshold, + ), + name="takers", + ) + for i in range(self.num_takers) + ] + + ## uncomment to include some coaching + # self._coach = [multiprocessing.Process(target=main_c.main)] + # coach = mp.Process(target=main_c.main) + # coach.start() + + self._render = [] + self._sleep = time + + def _launch_monitor(self) -> int: + """Launches the monitor.""" + logging.debug("Built the command to connect to the server") + + monitor_cmd = f"soccerwindow2 &" + # monitor_cmd = f"rcssmonitor --server-port={6000}" + popen = Popen(monitor_cmd, shell=True) + return popen + + def _parse_options(self, args=None, **defaults): + """ + Parses the given list of args, defaulting to sys.argv[1:]. + Retrieve other options from the YAML config file. + """ + + # Load the default values from YAML file + with open(f"{config_dir}/server-config.yml", "r") as ymlfile: + config = yaml.safe_load(ymlfile) + + parser = OptionParser() + options, _ = parser.parse_args(args) + # Merging command-line options with YAML defaults + for key, value in config.items(): + if not getattr(options, key, None): + setattr(options, key, value) + return options + + def _launch_server(self, options): + """Launch the RCSS Server and Monitor""" + + log_name = f"{time.strftime('%Y%m%d%H%M%S')}" + + server_options = [ + f"server::{opt}={val}" + for opt, val in { + "coach": int(options.coach), + "coach_port": int(options.coach_port), + "forbid_kick_off_offside": 1, + "half_time": -1, + "keepaway": int(not options.coach), + "keepaway_start": options.game_start, + "keepaway_length": int(options.field_length), + "keepaway_width": int(options.field_width), + "keepaway_logging": 1 if options.log_keepaway else 0, + "keepaway_log_dir": options.log_dir if options.log_keepaway else None, + "keepaway_log_fixed": 1 if options.log_keepaway else 0, + "keepaway_log_fixed_name": log_name if options.log_keepaway else None, + "game_log_compression": 0, + "game_log_dir": options.game_log_dir if options.log_game else None, + "game_log_fixed": 1 if options.log_game else 0, + "game_log_fixed_name": log_name if options.log_game else None, + "game_log_version": 5 if options.log_game else 0, + "game_logging": 1, + "olcoach_port": options.online_coach_port, + "port": options.port, + "stamina_inc_max": 3500, + "fullstate_l": int(options.fullstate), + "fullstate_r": int(options.fullstate), + "synch_mode": int(options.synch_mode), + "synch_offset": 60, + "synch_see_offset": 0, + "text_log_compression": 0, + "text_log_dir": options.game_log_dir if options.log_text else None, + "text_log_fixed": 1 if options.log_text else 0, + "text_log_fixed_name": log_name if options.log_text else None, + "text_logging": 1, + "use_offside": 0, + "visible_angle": 360 if not options.restricted_vision else None, + }.items() + if val is not None + ] + + # Build rcssserver command, and fork it off. + # print(server_options) + + command = ["rcssserver"] + server_options + popen = Popen(command) + + return popen + + def _launch_game(self): + """Launch a keepaway game instance.""" + print("launching game") + options = self._parse_options() + self._render.append(self._launch_server(options)) + self._render.append(self._launch_monitor()) + + def reset(self): + """Reset the environment. Required after each full episode.""" + + print("resetting") + self._episode_steps = 0 + self._total_steps = 0 + self.last_action = None + self._episode_reward = [] + # self._reward = self._world._reward + # self._terminated = + # self._world._terminated = multiprocessing.Value('b', False) + if self._world._terminated.get_lock(): + self._world._terminated.value = False + # self._world._terminated = multiprocessing.Value('b', False) + return self._obs + + def reward(self): + """ + returns the reward for the current state + """ + + r = self._world.time().cycle() - self._terminal_time.cycle() + return r + # return self._reward.value + + def _restart(self): + self.full_restart() + + def full_restart(self): + """Restart the environment. Required after each full episode.""" + # TODO process management utility + + self._launch_game() + self.force_restarts += 1 + + def start(self): + if self._episode_count == 0: + for i in range(self.num_keepers): + self._keepers[i].start() + + self._sleep.sleep(0.5) + + for i in range(self.num_takers): + self._takers[i].start() + + self._sleep.sleep(2.0) + # print("starting coach") + # self._coach[0].start() + + atexit.register(self.close) + self._episode_count += 1 + else: + print("already started") + + def close(self): + """Close the environment. No other method calls possible afterwards.""" + + for p in self._keepers: + p.terminate() + for r in self._render: + r.terminate() + for t in self._takers: + t.terminate() + + # self._coach[0].terminate() + + def get_avail_agent_actions(self, agent_id): + """Returns the available actions for agent_id.""" + return self._shared_values[agent_id] + + def get_obs(self): + # obs = np.frombuffer(self._obs, dtype=np.float64) + return self._obs + + def get_proximity_adj_mat(self): + adjacent_matrix = np.frombuffer(self._proximity_adj_mat, dtype=np.float64) + return adjacent_matrix + + def step(self, actions): + """A single environment step. Returns reward, terminated, info. + + Args: + actions: list of actions for each agent + + Returns: + Tuple of time-step data for each agent + + + applies all actions to the + send an action signal and return observation. + """ + + # do i need this .. + if not actions: + self.agents = [] + return {}, {}, {}, {}, {} + + # actions_int = [int(a) for a in actions] + actions_int = actions + self._total_steps += 1 + self._episode_steps += 1 + total_reward = 0 + ## not needed just for keepsake. + info = {} + terminated = False + + # passes the actions from the main process to the subprocesses. + # this should update the shared values. + + self._actions = copy.deepcopy(actions_int) + + # print("actions ", self._actions) + self._shared_values = multiprocessing.Array("i", self._actions) + self._observation = self._world._obs + game_state = self._world._terminated.value + if game_state == 1: + terminated = True + ## check details for sparse ( sparse implementation will take into account the + ## number of successful passes) + if not self.sparse_reward: + # total_reward += self.reward() + total_reward = self._reward.value + pass + else: + ## not implemented yet + # total_reward = self._reward.value + total_reward = self._reward.value + + self._terminal_time = self._world.time() + + # elif self._episode_steps >= self.episode_limit: + # terminated = True + # total_reward += self.reward() + # if self.continuing_episode: + # info["episode_limit"] = True + # self.timeouts += 1 + + if terminated: + self._episode_count += 1 + return total_reward, terminated, info diff --git a/keepaway/envs/keepaway_wrapper.py b/keepaway/envs/keepaway_wrapper.py new file mode 100644 index 00000000..7576b222 --- /dev/null +++ b/keepaway/envs/keepaway_wrapper.py @@ -0,0 +1,61 @@ +# # Using local gym +import sys +import os + +# current_file_path = os.path.dirname(os.path.abspath(__file__)) +# sys.path.append(current_file_path + "/../../") + +import dowel +from dowel import logger, tabular +from garage.misc.prog_bar_counter import ProgBarCounter +import time +from absl import logging +from keepaway.envs import KeepawayEnv +import gym +# from gym.utils import seeding +# from envs.ma_gym.envs.utils.observation_space import MultiAgentObservationSpace +import numpy as np + + +class KeepawayWrapper(KeepawayEnv): + def __init__(self, centralized, *args, **kwargs): + super().__init__(*args, **kwargs) + self.reward_range = (-np.inf, np.inf) + self.viewer = None + self.action_space = gym.spaces.Discrete(self.n_actions) + self.agent_obs_dim = np.sum( + [ + np.prod(self.get_obs_move_feats_size()), + np.prod(self.get_obs_enemy_feats_size()), + np.prod(self.get_obs_ally_feats_size()), + np.prod(self.get_obs_own_feats_size()), + ] + ) + self.observation_space_low = [0] * self.agent_obs_dim + self.observation_space_high = [1] * self.agent_obs_dim + self.observation_space = gym.spaces.Box( + low=np.array(self.observation_space_low), + high=np.array(self.observation_space_high), + ) + self.centralized = centralized + if centralized: + self.observation_space = gym.spaces.Box( + low=np.array(self.observation_space_low * self.n_agents), + high=np.array(self.observation_space_high * self.n_agents), + ) + self.pickleable = False + + def reset(self): + obses = super().reset()[0] + if not self.centralized: + return obses + else: + return np.concatenate(obses) + + def step(self, actions): + reward, terminated, info = super().step(actions) + if not self.centralized: + return super().get_obs(), [reward] * self.n_agents, \ + [terminated] * self.n_agents, info + else: + return np.concatenate(super().get_obs()), reward, terminated, info diff --git a/keepaway/envs/multiagentenv.py b/keepaway/envs/multiagentenv.py new file mode 100644 index 00000000..22b89456 --- /dev/null +++ b/keepaway/envs/multiagentenv.py @@ -0,0 +1,77 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +class MultiAgentEnv(object): + def step(self, actions): + """Returns reward, terminated, info.""" + raise NotImplementedError + + def get_obs(self): + """Returns all agent observations in a list.""" + raise NotImplementedError + + def get_obs_agent(self, agent_id): + """Returns observation for agent_id.""" + raise NotImplementedError + + def get_capabilities(self): + """Returns the capabilities of all agents in a list.""" + raise NotImplementedError + + def get_capabilities_agent(self, agent_id): + """Returns the capabilities of a single agent.""" + raise NotImplementedError + + def get_obs_size(self): + """Returns the size of the observation.""" + raise NotImplementedError + + def get_state(self): + """Returns the global state.""" + raise NotImplementedError + + def get_state_size(self): + """Returns the size of the global state.""" + raise NotImplementedError + + def get_cap_size(self): + """Returns the size of the own capabilities of the agent.""" + raise NotImplementedError + + def get_avail_actions(self): + """Returns the available actions of all agents in a list.""" + raise NotImplementedError + + def get_avail_agent_actions(self, agent_id): + """Returns the available actions for agent_id.""" + raise NotImplementedError + + def get_total_actions(self): + """Returns the total number of actions an agent could ever take.""" + raise NotImplementedError + + def reset(self): + """Returns initial observations and states.""" + raise NotImplementedError + + + def close(self): + raise NotImplementedError + + + def save_replay(self): + """Save a replay.""" + raise NotImplementedError + + def get_env_info(self): + env_info = { + "state_shape": self.get_state_size(), + "obs_shape": self.get_obs_size(), + "cap_shape": self.get_cap_size(), + "n_actions": self.get_total_actions(), + "n_agents": self.n_agents, + "episode_limit": self.episode_limit, + } + return env_info diff --git a/keepaway/envs/policies/.DS_Store b/keepaway/envs/policies/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..131d72d5eca838215c0381b205c5419ff8c66a6d GIT binary patch literal 6148 zcmeHKOHRWu5S@W8A}XOSS^5BMdIPr#C+GpvHbqpTRkEqn?yMexJF($X@WwL~B0@Jv z2%#Cte*V0%Cr^s)AtIjcR%4MU4M=u zz`@NGa0OfeSHKnckpj50#fqV#*RFso;0l}+knAWSD?DGr5LR47!SsahDlM~i7okH+xb0t;k-N651CFJ6}@%^T!F3vm)af4 z{eOpFrZ>s&x_HkOa0ULE0z4>Z self.distance_threshold: + return 0 + + for i in range(self.num_keepers): + if i == agent_id - 1: + pass + else: + scores[i] = ( + self.dist_weight * obs["state_vars"][7 + i] + + obs["state_vars"][9 + i] + ) ## verify this indices + + # print("scores : ", scores) + best = np.argmax(scores) + # print(best) + if scores[best] > self.hold_distance: + # print("returning best ", best + 1) + return best + 1 + else: + return 0 + + def get_actions(self, obs, greedy=False): + """Returns the actions for the agents in the keepaway domain.""" + + # TODO: check this to make sure scores are computed correctly and simultaneously + # verify with available actions. + agent_ids = obs.keys() # {1, 2, 3} + # print(agent_ids) + ## Hold threshold (alpha) + ## beta : Dist/Ang ratio (beta) + + ## Check if no observations are available. + actions = [None] * len(agent_ids) + # print("observation ", obs.values()) + for id, agent_obs in obs.items(): + if agent_obs is None: + actions[id - 1] = 0 + else: + a = self.select_agent_action(agent_obs, id) + # print(id,a) + actions[id - 1] = a + + # print("selected ", actions) + # print("actions : ", actions) + # return [1,1,1] , {} + return actions, {} diff --git a/keepaway/envs/policies/random_agent.py b/keepaway/envs/policies/random_agent.py new file mode 100644 index 00000000..f1984918 --- /dev/null +++ b/keepaway/envs/policies/random_agent.py @@ -0,0 +1,31 @@ +""" +keepaway.base Implementation for hand-coded policy for keepaway. + +Random policy keepaway.baseline adapted from Adaptive Behavior '05 article +* Stone, Sutton, and Kuhlmann. + +""" + +import random + + +class RandomPolicy(object): + def __init__(self): + pass + + def get_actions(self, obs, num_keepers=None, greedy=False): + """ + Returns a random action for each agent. + """ + agents_ids = obs.keys() + actions = [] + for idx in agents_ids: + a = random.randint(0, num_keepers) + if a != idx: + actions.append(a) + else: + l = [num for num in range(0, num_keepers + 1) if num != idx] + actions.append(random.choice(l)) + + # print("randoms actions: ", actions) + return actions, {} diff --git a/keepaway/examples/.DS_Store b/keepaway/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e96c06f1a4b4640bf9a415411874ba9742a10f84 GIT binary patch literal 6148 zcmeHKOHRW;47DLhk-DHeSk4uC0Hh%bC+GnJO;NjOm2A3T7jZ21Y&aFq9|{$r8zh7( zTbg+uk3Df-l<^D^akX2{h^9nTq6xAnBO>O}sWW#jfUIk@^gu1G>5-a|h5n;Sa-SgE zhI)EJ?w|QLo7HUIcH7?4E~=kBw_RN?Hr)aq<;&I0#q-p-Vh0H7SBRiNt`6_~^T z%#MR0ED*L(poOwuG1$UkPwtl;2SW=d)+b{ffAYuWh5dxgN!^LFVf4X3FmTAgxecdM z|L^cCjb8E(Lt+#R1Oq3=02lSVuJBS`Tfe=Y)Y^o0k0v61gD4Q_T8e>MiZPIV^K<8Dq`1gU>pRLkm!PeKVaY;N&PUM literal 0 HcmV?d00001 diff --git a/keepaway/examples/test_alwayshold_agent.py b/keepaway/examples/test_alwayshold_agent.py new file mode 100644 index 00000000..16b64b4e --- /dev/null +++ b/keepaway/examples/test_alwayshold_agent.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, division, print_function + +import time +from absl import logging + +logging.set_verbosity(logging.DEBUG) +from keepaway.envs.keepaway_env import KeepawayEnv +from keepaway.envs.policies.always_hold import AlwaysHoldPolicy + + +def main(): + env = KeepawayEnv() + episodes = 20 + env._launch_game() + agents = env.num_keepers + policy = AlwaysHoldPolicy() + + for e in range(episodes): + print(f"Episode {e}") + env.reset() + terminated = False + episode_reward = 0 + env.start() + + while not terminated: + obs = env.get_obs() + actions, agent_infos = policy.get_actions(obs, greedy=True) + # print("actions ", actions) + + reward, terminated, info = env.step(actions) + # print("reward ", reward, "terminated ", terminated, "info ", info) + # print("matrix jfjfj ", env.get_proximity_adj_mat()) + time.sleep(0.15) + episode_reward += reward + + print("closing game") + env.close() + + +if __name__ == "__main__": + main() diff --git a/keepaway/examples/test_handcoded_agent.py b/keepaway/examples/test_handcoded_agent.py new file mode 100644 index 00000000..50d164cb --- /dev/null +++ b/keepaway/examples/test_handcoded_agent.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import, division, print_function + +import time +from absl import logging +logging.set_verbosity(logging.DEBUG) +from keepaway.envs.keepaway_env import KeepawayEnv +from keepaway.envs.policies.handcoded_agent import HandcodedPolicy + + +def main(): + env = KeepawayEnv() + episodes = 20 + env._launch_game() + agents = env.num_keepers + policy = HandcodedPolicy() + + for e in range(episodes): + print(f"Episode {e}") + env.reset() + terminated = False + episode_reward = 0 + env.start() + + while not terminated: + obs = env.get_obs() + actions, agent_infos = policy.get_actions(obs,greedy=True) + # print("actions ", actions) + + reward, terminated, info = env.step(actions) + # print("reward ", reward, "terminated ", terminated, "info ", info) + # print("matrix jfjfj ", env.get_proximity_adj_mat()) + time.sleep(0.15) + episode_reward += reward + + print("closing game") + env.close() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/keepaway/examples/test_random_agent.py b/keepaway/examples/test_random_agent.py new file mode 100644 index 00000000..918b1602 --- /dev/null +++ b/keepaway/examples/test_random_agent.py @@ -0,0 +1,43 @@ +from __future__ import absolute_import, division, print_function + +import time +from absl import logging +from keepaway.envs.keepaway_env import KeepawayEnv +from envs.policies.random_agent import RandomPolicy + +logging.set_verbosity(logging.DEBUG) + + +def main(): + env = KeepawayEnv() + episodes = 10 + print("Training episodes") + print("launching game") + env._launch_game() + agents = env.num_keepers + policy = RandomPolicy() + + for e in range(episodes): + print(f"Episode {e}") + env.reset() + terminated = False + episode_reward = 0 + env.start() + + while not terminated: + obs = env.get_obs() + actions, agent_infos = policy.get_actions(obs, agents, greedy=False) + # print(actions) + reward, terminated, info = env.step(actions) + # print("reward ", reward, "terminated ", terminated, "info ", info) + # print("reward ", reward, "terminated ", terminated, "info ", info) + # print("matrix jfjfj ", env.get_proximity_adj_mat()) + time.sleep(0.15) + episode_reward += reward + + print("closing game") + env.close() + + +if __name__ == "__main__": + main() diff --git a/keepaway/lib/.DS_Store b/keepaway/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5383488a1f699546b561d39b3115fbbeedb34ff6 GIT binary patch literal 8196 zcmeHMzi$&U6n;*dkg5owszMzYZij(xY#dc!MykYyLi2+Zk;_FXrDXHQShh|`MS_Wu z4KWl1e*ydf{Zsh-?40iToB%>X5!jLaqVxCsK7V(%^DacBb`E>nL|a7EL^byEI+{Q+ z{iaq*&0V<;tKd)6r9ISr>QlRdju)Z=QGuvHR3Iu46<7uZaAup*oOABm-q=P3q5}V= z0%m_mP>oHU)z`tjt;+s;iOV)V;dES3RD#^x%)b@^)`)ZD)RUCaIn4G&9kv}QupU0 zv%J;n4D${UW*NTv@b>HNMd;pdpvU`V7QDI}{FdPPhw2!DK?v^^a^)tN3bG8R8jN$SA zB_A;lm4Ft0VNJmOfVzTn{p4A>&*L{+lZCIxrP43r*3M1bSe9FX6-#f+b2VsP48c0t4GI4q~*u*DyS_?`f2d{Sq% dwYY-v_g@5re 0.0: + # # backward case + # return agent.do_tackle(- sp.maxBackTacklePower()) + # return False + + ball_rel_angle = wm.ball().rpos().th() - wm.self().body() + + eff_power = sp.max_back_tackle_power() + ( + (sp.max_tackle_power() - sp.max_back_tackle_power()) * (1.0 - target_rel_angle.abs() / 180.0)) + eff_power *= sp.tackle_power_rate() + eff_power *= 1.0 - 0.5 * (ball_rel_angle.abs() / 180.0) + + vel = wm.ball().vel() + Vector2D.polar2vector(eff_power, target_angle) + + if ((vel.th() - target_angle).abs() > 90.0 # never accelerate to the target direction + or vel.r() < self._min_speed): # too small speed + return False + + return agent.do_tackle(target_rel_angle.degree(), True) # TODO need Check + + +class SetFocusToPoint(FocusPointAction): + def __init__(self, next_focus_point: Vector2D): + super().__init__() + self.next_focus_point: Vector2D = next_focus_point + + def execute(self, agent: 'PlayerAgent'): + next_view_width = agent.effector().queued_next_view_width().width() + my_next_pos = agent.effector().queued_next_self_pos() + my_next_face = agent.effector().queued_next_self_face() + current_focus_point_dist = agent.world().self().focus_point_dist() + current_focus_point_dir = agent.world().self().focus_point_dir() + current_focus_point_dir = AngleDeg(min_max(-next_view_width / 2.0, current_focus_point_dir.degree(), next_view_width / 2.0)) + next_focus_point_dist = my_next_pos.dist(self.next_focus_point) + if not (0.0 < next_focus_point_dist < 40.0): + log.os_log().info(f'(FocusToPoint execute) Next focus point dist should be 0<{next_focus_point_dist}<40') + next_focus_point_dist = min_max(0.0, next_focus_point_dist, 40.0) + change_focus_moment_dist = next_focus_point_dist - current_focus_point_dist + + original_next_focus_point_dir_to_pos = (self.next_focus_point - my_next_pos).th() + next_focus_point_dir = (self.next_focus_point - my_next_pos).th() - my_next_face + if not (-next_view_width / 2.0 < next_focus_point_dir.degree() < next_view_width / 2.0): + log.os_log().info(f'(FocusToPoint execute) Next focus point dir should be {-next_view_width / 2.0}<{next_focus_point_dir.degree()}<{next_view_width/2.0}') + next_focus_point_dir = AngleDeg(min_max(-next_view_width / 2.0, next_focus_point_dir.degree(), next_view_width / 2.0)) + if abs(next_focus_point_dir.abs() - next_view_width) / 2.0 < 0.001: + positive = AngleDeg(next_view_width / 2.0) + my_next_face + negative = AngleDeg(-next_view_width / 2.0) + my_next_face + if (positive - original_next_focus_point_dir_to_pos).abs() < (negative - original_next_focus_point_dir_to_pos).abs(): + next_focus_point_dir = AngleDeg(next_view_width / 2.0) + else: + next_focus_point_dir = AngleDeg(-next_view_width / 2.0) + change_focus_moment_dir = next_focus_point_dir - current_focus_point_dir + return agent.do_change_focus(change_focus_moment_dist, change_focus_moment_dir) + + +class SetFocusToBall(FocusPointAction): + def __init__(self): + super().__init__() + + def execute(self, agent: 'PlayerAgent'): + next_ball_pos = agent.effector().queued_next_ball_pos() + return SetFocusToPoint(next_ball_pos).execute(agent) + + +class SetFocusToSelf(FocusPointAction): + def __init__(self): + super().__init__() + + def execute(self, agent: 'PlayerAgent'): + next_self_pos = agent.effector().queued_next_self_pos() + return SetFocusToPoint(next_self_pos).execute(agent) + + +class SetFocusToFlags(FocusPointAction): + # TODO this one and others should be implemented + pass + +""" + \ class Arm_Off + \ brief turn off the pointing arm +""" + + +class ArmOff(ArmAction): + """ + \ brief execute action + \ param agent the agent itself + \ return True if action is performed + """ + + def execute(self, agent: 'PlayerAgent'): + if agent.world().self().arm_movable() > 0: + return False + return agent.doPointtoOff() diff --git a/keepaway/lib/action/go_to_point.py b/keepaway/lib/action/go_to_point.py new file mode 100644 index 00000000..5e79ffad --- /dev/null +++ b/keepaway/lib/action/go_to_point.py @@ -0,0 +1,138 @@ +from keepaway.lib.rcsc.server_param import ServerParam as SP +import pyrusgeom.soccer_math as smath +from pyrusgeom.geom_2d import * + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +class GoToPoint: + _dir_thr: float + + def __init__(self, target, dist_thr, max_dash_power, dash_speed=-1.0, cycle=100, save_recovery=True, dir_thr=15.0): + self._target = target + self._dist_thr = dist_thr + self._max_dash_power = max_dash_power + self._dash_speed = dash_speed + self._cycle = cycle + self._save_recovery = save_recovery + self._dir_thr = dir_thr + self._back_mode = False + + def execute(self, agent): + if math.fabs(self._max_dash_power) < 0.1 or math.fabs(self._dash_speed) < 0.01: + agent.do_turn(0) + return True + + wm: 'WorldModel' = agent.world() + inertia_point: Vector2D = wm.self().inertia_point(self._cycle) + target_rel: Vector2D = self._target - inertia_point + + target_dist = target_rel.r() + if target_dist < self._dist_thr: + agent.do_turn(0.0) + return False + + self.check_collision(agent) + + if self.do_turn(agent): + return True + + if self.do_dash(agent): + return True + + agent.do_turn(0) + return False + + def do_turn(self, agent): + wm: 'WorldModel' = agent.world() + + inertia_pos: Vector2D = wm.self().inertia_point(self._cycle) + target_rel: Vector2D = self._target - inertia_pos + target_dist = target_rel.r() + max_turn = wm.self().player_type().effective_turn(SP.i().max_moment(), wm.self().vel().r()) + turn_moment: AngleDeg = target_rel.th() - wm.self().body() + if turn_moment.abs() > max_turn and turn_moment.abs() > 90.0 and target_dist < 2.0 and wm.self().stamina_model().stamina() > SP.i().recover_dec_thr_value() + 500.0: + effective_power = SP.i().max_dash_power() * wm.self().dash_rate() + effective_back_power = SP.i().min_dash_power() * wm.self().dash_rate() + if math.fabs(effective_back_power) > math.fabs(effective_power) * 0.75: + self._back_mode = True + turn_moment += 180.0 + + turn_thr = 180.0 + if self._dist_thr < target_dist: + turn_thr = AngleDeg.asin_deg(self._dist_thr / target_dist) + turn_thr = max(self._dir_thr, turn_thr) + + if turn_moment.abs() < turn_thr: + return False + return agent.do_turn(turn_moment) + + def do_dash(self, agent): + wm: 'WorldModel' = agent.world() + + inertia_pos: Vector2D = wm.self().inertia_point(self._cycle) + target_rel: Vector2D = self._target - inertia_pos + + accel_angle: AngleDeg = wm.self().body() + if self._back_mode: + accel_angle += 180.0 + + target_rel.rotate(-accel_angle) + first_speed = smath.calc_first_term_geom_series(target_rel.x(), wm.self().player_type().player_decay(), + self._cycle) + first_speed = smath.bound(- wm.self().player_type().player_speed_max(), first_speed, + wm.self().player_type().player_speed_max()) + if self._dash_speed > 0.0: + if first_speed > 0.0: + first_speed = min(first_speed, self._dash_speed) + else: + first_speed = max(first_speed, -self._dash_speed) + rel_vel = wm.self().vel() + rel_vel.rotate(-accel_angle) + required_accel = first_speed - rel_vel.x() + if math.fabs(required_accel) < 0.05: + return False + dash_power = required_accel / wm.self().dash_rate() + dash_power = min(dash_power, self._max_dash_power) + if self._back_mode: + dash_power = -dash_power + dash_power = SP.i().normalize_dash_power(dash_power) + # TODO check stamina check for save recovery + return agent.do_dash(dash_power) + + def check_collision(self, agent): + wm: 'WorldModel' = agent.world() + + collision_dist = wm.self().player_type().player_size() + SP.i().goal_post_radius() + 0.2 + + goal_post_l = Vector2D(-SP.i().pitch_half_length() + SP.i().goal_post_radius(), + -SP.i().goal_half_width() - SP.i().goal_post_radius()) + goal_post_r = Vector2D(-SP.i().pitch_half_length() + SP.i().goal_post_radius(), + +SP.i().goal_half_width() + SP.i().goal_post_radius()) + + dist_post_l = wm.self().pos().dist2(goal_post_l) + dist_post_r = wm.self().pos().dist2(goal_post_r) + + nearest_post = goal_post_l + if dist_post_l > dist_post_r: + nearest_post = goal_post_r + + dist_post = min(dist_post_l, dist_post_r) + if dist_post > collision_dist + wm.self().player_type().real_speed_max() + 0.5: + return + + post_circle = Circle2D(nearest_post, collision_dist) + move_line = Segment2D(wm.self().pos(), self._target) + if len(post_circle.intersection(move_line)) == 0: + return + + post_angle: AngleDeg = AngleDeg((nearest_post - wm.self().pos()).th()) + new_target: Vector2D = nearest_post + + if post_angle.is_left_of(wm.self().body()): + new_target += Vector2D.from_polar(collision_dist + 0.1, post_angle + 90.0) + else: + new_target += Vector2D.from_polar(collision_dist + 0.1, post_angle - 90.0) + + self._target = new_target diff --git a/keepaway/lib/action/hold_ball.py b/keepaway/lib/action/hold_ball.py new file mode 100644 index 00000000..663e643b --- /dev/null +++ b/keepaway/lib/action/hold_ball.py @@ -0,0 +1,526 @@ +""" + \ file hold_ball.py + \ brief stay there and keep the ball from opponent players. +""" +import functools + +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.soccer_math import * +from pyrusgeom.angle_deg import AngleDeg +from keepaway.lib.action.stop_ball import StopBall +from keepaway.lib.action.basic_actions import TurnToPoint +from keepaway.lib.player.soccer_action import BodyAction +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.size_2d import Size2D +from pyrusgeom.line_2d import Line2D + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.player_agent import PlayerAgent + +""" + \ struct KeepPoint + \ brief keep point info + """ + +DEFAULT_SCORE = 100.0 + + +def KeepPointCmp(item1, item2) -> bool: + return item1.score_ < item2.score_ + + +class KeepPoint: + """ + \ brief construct with arguments + \ param pos global position + \ param krate kick rate at the position + \ param score initial score + """ + + def __init__(self, pos=Vector2D.invalid(), kickrate=0.0, score=-10000000.0): + self.pos_ = pos + self.kick_rate_ = kickrate + self.score_ = score + + """ + \ brief reset object value + """ + + def reset(self): + self.pos_.invalidate() + self.kick_rate_ = 0.0 + self.score_ = -10000000.0 + + +class HoldBall(BodyAction): + """ + \ brief accessible from global. + """ + + def __init__(self, do_turn=False, turn_target_point=Vector2D(0.0, 0.0), kick_target_point=Vector2D.invalid()): + super().__init__() + self._do_turn = do_turn + self._turn_target_point = turn_target_point + self._kick_target_point = kick_target_point + + """ + \ brief execute action + \ param agent the agent itself + \ return True if action is performed + """ + + def execute(self, agent: 'PlayerAgent'): + wm: 'WorldModel' = agent.world() + if not wm.self().is_kickable(): + log.sw_log().kick().add_text( "not kickable") + return False + + if not wm.ball().vel_valid(): + return StopBall().execute(agent) + + if self.keepReverse(agent): + return True + + if self.turnToPoint(agent): + return True + + if self.keepFront(agent): + return True + + if self.avoidOpponent(agent): + return True + + log.sw_log().kick().add_text( "only stop ball") + + return StopBall().execute(agent) + + """ + \ brief kick the ball to avoid opponent + \ param agent itself + \ return True if action is performed + """ + + def avoidOpponent(self, agent: 'PlayerAgent'): + wm: 'WorldModel' = agent.world() + point = self.searchKeepPoint(wm) + if not point.is_valid(): + log.sw_log().kick().add_text( "avoidOpponent() no candidate point") + return False + ball_move = point - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + kick_accel_r = kick_accel.r() + agent.do_kick(kick_accel_r / wm.self().kick_rate(), + kick_accel.th() - wm.self().body()) + return True + + """ + \ brief search the best keep point + \ param wm reference to the WorldModel instance + \ return estimated best keep point. if no point, is returned. + """ + + def searchKeepPoint(self, wm: 'WorldModel'): + s_last_update_time = GameTime(0, 0) + s_keep_points = [] + s_best_keep_point = KeepPoint() + if s_last_update_time != wm.time(): + s_best_keep_point.reset() + s_keep_points = self.createKeepPoints(wm, s_keep_points) + self.evaluateKeepPoints(wm, s_keep_points) + if s_keep_points: + s_best_keep_point = max(s_keep_points, key=functools.cmp_to_key(KeepPointCmp)) + return s_best_keep_point.pos_ + + """ + \ brief create candidate keep points + \ param wm reference to the WorldModel instance + \ param keep_points reference to the variable container + """ + + def createKeepPoints(self, wm: 'WorldModel', candidates): + param = ServerParam.i() + + max_pitch_x = param.pitch_half_length() - 0.2 + max_pitch_y = param.pitch_half_width() - 0.2 + + dir_divs = 20 + dir_step = 360.0 / dir_divs + + near_dist = wm.self().player_type().player_size() + param.ball_size() + wm.self().player_type().kickable_margin() * 0.4 + mid_dist = wm.self().player_type().player_size() + param.ball_size() + wm.self().player_type().kickable_margin() * 0.6 + far_dist = wm.self().player_type().player_size() + param.ball_size() + wm.self().player_type().kickable_margin() * 0.8 + + # candidates = [] * dir_divs * 2 + + my_next = wm.self().pos() + wm.self().vel() + + my_noise = wm.self().vel().r() * param.player_rand() + current_dir_diff_rate = (wm.ball().angle_from_self() - wm.self().body()).abs() / 180.0 + current_dist_rate = ( + wm.ball().dist_from_self() - wm.self().player_type().player_size() - param.ball_size()) / wm.self().player_type().kickable_margin() + current_pos_rate = 0.5 + 0.25 * (current_dir_diff_rate + current_dist_rate) + current_speed_rate = 0.5 + 0.5 * (wm.ball().vel().r() / (param.ball_speed_max() * param.ball_decay())) + + angles = [-180 + a*dir_step for a in range(dir_divs)] + for d in angles: + angle = AngleDeg(d) + dir_diff = (angle - wm.self().body()).abs() + unit_pos = Vector2D.polar2vector(1.0, angle) + + # near side point + near_pos = my_next + unit_pos.set_length_vector(near_dist) + if (near_pos.abs_x() < max_pitch_x + and near_pos.abs_y() < max_pitch_y): + ball_move = near_pos - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + + # can kick to the point by 1 step kick + if kick_accel.r() < param.max_power() * wm.self().kick_rate(): + near_krate = wm.self().player_type().kick_rate(near_dist, dir_diff) + # can stop the ball by 1 step kick + if ball_move.r() * param.ball_decay() < param.max_power() * near_krate: + candidates.append(KeepPoint(near_pos, + near_krate, + DEFAULT_SCORE)) + # middle point + mid_pos = my_next + unit_pos.set_length_vector(mid_dist) + if (mid_pos.abs_x() < max_pitch_x + and mid_pos.abs_y() < max_pitch_y): + ball_move = mid_pos - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + kick_power = kick_accel.r() / wm.self().kick_rate() + + # can kick to the point by 1 step kick + if kick_power < param.max_power(): + # check move noise + move_dist = ball_move.r() + ball_noise = move_dist * param.ball_rand() + + max_kick_rand = wm.self().player_type().kick_rand() * (kick_power / param.max_power()) * ( + current_pos_rate + current_speed_rate) + # move noise is small + if ((my_noise + ball_noise + max_kick_rand) * 0.95 + < wm.self().player_type().kickable_area() - mid_dist - 0.1): + mid_krate = wm.self().player_type().kick_rate(mid_dist, dir_diff) + # can stop the ball by 1 step kick + if move_dist * param.ball_decay() < param.max_power() * mid_krate: + candidates.append(KeepPoint(mid_pos, + mid_krate, + DEFAULT_SCORE)) + + # far side point + far_pos = my_next + unit_pos.set_length_vector(far_dist) + if (far_pos.abs_x() < max_pitch_x + and far_pos.abs_y() < max_pitch_y): + ball_move = far_pos - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + kick_power = kick_accel.r() / wm.self().kick_rate() + + # can kick to the point by 1 step kick + if kick_power < param.max_power(): + # check move noise + move_dist = ball_move.r() + ball_noise = move_dist * param.ball_rand() + max_kick_rand = wm.self().player_type().kick_rand() * (kick_power / param.max_power()) * ( + current_pos_rate + current_speed_rate) + # move noise is small + if ((my_noise + ball_noise + max_kick_rand) * 0.95 + < wm.self().player_type().kickable_area() - far_dist - 0.1): + far_krate = wm.self().player_type().kick_rate(far_dist, dir_diff) + # can stop the ball by 1 step kick + if move_dist * param.ball_decay(): + candidates.append(KeepPoint(far_pos, + far_krate, + DEFAULT_SCORE)) + return candidates + + """ + \ brief evaluate candidate keep points + \ param wm reference to the WorldModel instance + \ param keep_points reference to the variable container + """ + + def evaluateKeepPoints(self, wm: 'WorldModel', keep_points): + for it in keep_points: + it.score_ = self.evaluateKeepPoint(wm, it.pos_) + if it.score_ < DEFAULT_SCORE: + it.score_ += it.pos_.dist(wm.ball().pos()) + else: + it.score_ += it.kick_rate_ * 1000.0 + + """ + \ brief evaluate the keep point + \ param wm reference to the WorldModel instance + \ param keep_point keep point value + """ + + def evaluateKeepPoint(self, wm: 'WorldModel', + keep_point: Vector2D): + penalty_area = Rect2D(Vector2D(ServerParam.i().their_penalty_area_line_x(), + - ServerParam.i().penalty_area_half_width()), + Size2D(ServerParam.i().penalty_area_length(), + ServerParam.i().penalty_area_width())) + consider_dist = (ServerParam.i().tackle_dist() + + ServerParam.i().default_player_speed_max() + + 1.0) + param = ServerParam.i() + score = DEFAULT_SCORE + + my_next = wm.self().pos() + wm.self().vel() + if len(wm.opponents_from_ball()) == 0: + return score + for o in wm.opponents_from_ball(): + if o is None or o.player_type() is None: + continue + if o.dist_from_ball() > consider_dist: + break + if o.pos_count() > 10: + continue + if o.is_ghost(): + continue + if o.is_tackling(): + continue + + player_type = o.player_type() + opp_next = o.pos() + o.vel() + control_area = o.player_type().catchable_area() if ( + o.goalie() and penalty_area.contains(o.pos()) and penalty_area.contains( + keep_point)) else o.player_type().kickable_area() + opp_dist = opp_next.dist(keep_point) + + if opp_dist < control_area * 0.5: + + score -= 100.0 + + elif opp_dist < control_area + 0.1: + + score -= 50.0 + + elif opp_dist < param.tackle_dist() - 0.2: + + score -= 25.0 + + if o.body_count() == 0: + opp_body = o.body() + + elif o.vel().r() > 0.2: # o.velCount() <= 1 #and + opp_body = o.vel().th() + + else: + opp_body = (my_next - opp_next).th() + + # + # check opponent body line + # + + opp_line = Line2D(p=opp_next, a=opp_body) + line_dist = opp_line.dist(keep_point) + if line_dist < control_area: + + if line_dist < control_area * 0.8: + score -= 20.0 + else: + score -= 10.0 + + player_2_pos = keep_point - opp_next + player_2_pos.rotate(- opp_body) + + # + # check tackle probability + # + tackle_dist = param.tackle_dist() if (player_2_pos.x() > 0.0) else param.tackle_back_dist() + if tackle_dist > 1.0e-5: + tackle_prob = (pow(player_2_pos.abs_x() / tackle_dist, + param.tackle_exponent()) + + pow(player_2_pos.abs_y() / param.tackle_width(), + param.tackle_exponent())) + if (tackle_prob < 1.0 + and 1.0 - tackle_prob > 0.7): # success probability + score -= 30.0 + + # + # check kick or tackle possibility after dash + # + max_accel = (param.max_dash_power() + * player_type.dash_power_rate() + * player_type.effort_max()) + + if (player_2_pos.abs_y() < control_area + and player_2_pos.x() > 0.0 + and (player_2_pos.abs_x() < max_accel + or (player_2_pos - Vector2D(max_accel, 0.0)).r() < control_area + 0.1)): + # next kickable + score -= 20.0 + elif (player_2_pos.abs_y() < param.tackle_width() * 0.7 + and player_2_pos.x() > 0.0 + and player_2_pos.x() - max_accel < param.tackle_dist() - 0.25): + score -= 10.0 + + ball_move_dist = (keep_point - wm.ball().pos()).r() + if ball_move_dist > wm.self().player_type().kickable_area() * 1.6: + next_ball_dist = my_next.dist(keep_point) + threshold = wm.self().player_type().kickable_area() - 0.4 + rate = 1.0 - 0.5 * max(0.0, (next_ball_dist - threshold) / 0.4) + score *= rate + return score + + """ + \ brief if possible, to face target point + \ param agent agent itself + \ return True if action is performed + """ + + def turnToPoint(self, agent: 'PlayerAgent'): + param = ServerParam.i() + max_pitch_x = param.pitch_half_length() - 0.2 + max_pitch_y = param.pitch_half_width() - 0.2 + wm = agent.world() + my_next = wm.self().pos() + wm.self().vel() + ball_next = wm.ball().pos() + wm.ball().vel() + + if (ball_next.abs_x() > max_pitch_x + or ball_next.abs_y() > max_pitch_y): + return False + + my_noise = wm.self().vel().r() * param.player_rand() + ball_noise = wm.ball().vel().r() * param.ball_rand() + + next_ball_dist = my_next.dist(ball_next) + + if (next_ball_dist > (wm.self().player_type().kickable_area() + - my_noise + - ball_noise + - 0.15)): + return False + + face_point = Vector2D(0.0, 0.0) + face_point.x = param.pitch_half_length() - 5.0 + + if self._do_turn: + face_point = self._turn_target_point + my_inertia = wm.self().inertia_final_point() + target_angle = (face_point - my_inertia).th() + + if (wm.self().body() - target_angle).abs() < 5.0: + return False + + score = self.evaluateKeepPoint(wm, ball_next) + if score < DEFAULT_SCORE: + return False + + TurnToPoint(face_point, 100).execute(agent) + return True + + """ + \ brief keep the ball at body front + \ param agent itself + \ return True if action is performed + """ + + def keepFront(self, agent: 'PlayerAgent'): + param = ServerParam.i() + max_pitch_x = param.pitch_half_length() - 0.2 + max_pitch_y = param.pitch_half_width() - 0.2 + + wm = agent.world() + front_keep_dist = wm.self().player_type().player_size() + param.ball_size() + 0.05 + + my_next = wm.self().pos() + wm.self().vel() + + front_pos = my_next + Vector2D.polar2vector(front_keep_dist, wm.self().body()) + + if (front_pos.abs_x() > max_pitch_x + or front_pos.abs_y() > max_pitch_y): + return False + + ball_move = front_pos - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + kick_power = kick_accel.r() / wm.self().kick_rate() + + # can kick to the point by 1 step kick + if kick_power > param.max_power(): + return False + + score = self.evaluateKeepPoint(wm, front_pos) + + if score < DEFAULT_SCORE: + return False + + agent.do_kick(kick_power, + kick_accel.th() - wm.self().body()) + return True + + """ + \ brief keep the ball at reverse point from the kick target point + \ param agent itself + """ + + def keepReverse(self, agent: 'PlayerAgent'): + if not self._kick_target_point.is_valid(): + return False + + param = ServerParam.i() + max_pitch_x = param.pitch_half_length() - 0.2 + max_pitch_y = param.pitch_half_width() - 0.2 + wm = agent.world() + + my_inertia = wm.self().pos() + wm.self().vel() + + my_noise = wm.self().vel().r() * param.player_rand() + current_dir_diff_rate = (wm.ball().angle_from_self() - wm.self().body()).abs() / 180.0 + + current_dist_rate = (wm.ball().dist_from_self() + - wm.self().player_type().player_size() + - param.ball_size()) / wm.self().player_type().kickable_margin() + + current_pos_rate = 0.5 + 0.25 * (current_dir_diff_rate + current_dist_rate) + current_speed_rate = 0.5 + 0.5 * (wm.ball().vel().r() / (param.ball_speed_max() * param.ball_decay())) + + keep_angle = (my_inertia - self._kick_target_point).th() + dir_diff = (keep_angle - wm.self().body()).abs() + min_dist = (wm.self().player_type().player_size() + + param.ball_size() + + 0.2) + + keep_dist = wm.self().player_type().player_size() + wm.self().player_type().kickable_margin() * 0.7 + param.ball_size() + + while keep_dist > min_dist: + + keep_pos = my_inertia + Vector2D.polar2vector(keep_dist, keep_angle) + + keep_dist -= 0.05 + if (keep_pos.abs_x() > max_pitch_x + or keep_pos.abs_y() > max_pitch_y): + continue + + ball_move = keep_pos - wm.ball().pos() + kick_accel = ball_move - wm.ball().vel() + kick_power = kick_accel.r() / wm.self().kick_rate() + + if kick_power > param.max_power(): + continue + + move_dist = ball_move.r() + ball_noise = move_dist * param.ball_rand() + max_kick_rand = wm.self().player_type().kick_rand() * (kick_power / param.max_power()) * ( + current_pos_rate + current_speed_rate) + + if (my_noise + ball_noise + max_kick_rand) > wm.self().player_type().kickable_area() - keep_dist - 0.1: + continue + + new_krate = wm.self().player_type().kick_rate(keep_dist, dir_diff) + if move_dist * param.ball_decay() > new_krate * param.max_power(): + continue + + score = self.evaluateKeepPoint(wm, keep_pos) + if score >= DEFAULT_SCORE: + agent.do_kick(kick_power, + kick_accel.th() - wm.self().body()) + return True + + return False diff --git a/keepaway/lib/action/intercept.py b/keepaway/lib/action/intercept.py new file mode 100644 index 00000000..cf385b5e --- /dev/null +++ b/keepaway/lib/action/intercept.py @@ -0,0 +1,511 @@ +import math + +from keepaway.lib.action.basic_actions import TurnToPoint +from keepaway.lib.action.go_to_point import GoToPoint +from keepaway.lib.action.intercept_info import InterceptInfo +from keepaway.lib.action.intercept_table import InterceptTable +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.soccer_math import inertia_n_step_distance, bound, calc_first_term_geom_series, min_max +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.player.object_player import PlayerObject +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + from keepaway.lib.player.world_model import WorldModel + + +class Intercept: + def __init__(self, save_recovery: bool = True, + face_point: Vector2D = Vector2D.invalid()): + self._save_recovery = save_recovery + self._face_point = face_point + + def execute(self, agent: 'PlayerAgent'): + wm = agent.world() + + if not wm.ball().pos_valid(): + return False + + # if self.do_kickable_opponent_check(agent): + # return True + + table = wm.intercept_table() # what is here? + # print (" intercept : ", table.self_reach_cycle()) + if table.self_reach_cycle() > 100: + final_point = wm.ball().inertia_final_point() + log.sw_log().intercept().add_text( + f"table.self_reach_cycle() > 100 (GoToPoint)") + log.sw_log().intercept().add_circle( + center=final_point, + r=0.5, + color=Color(string='red')) + print ("GoToPoint in intercept ", final_point) + GoToPoint(final_point, + 2, + ServerParam.i().max_dash_power()).execute(agent) + return True + + best_intercept: InterceptInfo = self.get_best_intercept(wm, table) + + print ("Best Intercept : ", best_intercept) + + target_point = wm.ball().inertia_point(best_intercept.reach_cycle()) + if best_intercept.dash_cycle() == 0: + print("best_intercept.dash_cycle() == 0") + face_point = self._face_point.copy() + if not face_point.is_valid(): + face_point.assign(50.5, wm.self().pos().y() * 0.75) + + log.sw_log().intercept().add_text( + f"best_intercept.dash_cycle() == 0 (TurnToPoint)") + log.sw_log().intercept().add_circle( + center=face_point, + r=0.5, + color=Color(string='red')) + print ("Turning to Point in intercept ", final_point) + TurnToPoint(face_point, + best_intercept.reach_cycle()).execute(agent) + return True + + if best_intercept.turn_cycle() > 0: + print("Turning to Point in intercept") + my_inertia = wm.self().inertia_point(best_intercept.reach_cycle()) + target_angle = (target_point - my_inertia).th() + if best_intercept.dash_power() < 0: + target_angle -= 180 + + log.sw_log().intercept().add_text( + f"best_intercept.turn_cycle() > 0 (do_turn)") + return agent.do_turn(target_angle - wm.self().body()) + + if self.do_wait_turn(agent, target_point, best_intercept): + return True + + if self._save_recovery and not wm.self().stamina_model().capacity_is_empty(): + print("save recovery") + consumed_stamina = best_intercept.dash_power() + if best_intercept.dash_power() < 0: + consumed_stamina *= -2 + + if (wm.self().stamina() - consumed_stamina + < ServerParam.i().recover_dec_thr_value() + 1): + log.sw_log().intercept().add_text( + f"last if (do turn)") + agent.do_turn(0) + return False + + log.sw_log().intercept().add_text( + f"do inertia dash (do dash)") + log.sw_log().intercept().add_circle( + center=target_point, + r=0.5, + color=Color(string='red')) + + print ("Do inertia dash in intercept ", target_point) + return self.do_inertia_dash(agent, + target_point, + best_intercept) + + def do_kickable_opponent_check(self, + agent: 'PlayerAgent') -> bool: + wm = agent.world() + if wm.ball().dist_from_self() < 2 and wm.exist_kickable_opponents(): + opp: PlayerObject = wm.opponents_from_ball()[0] + if opp is not None: + goal_pos = Vector2D(-ServerParam.i().pitch_half_length(), 0) + my_next = wm.self().pos() + wm.self().vel() + attack_pos = opp.pos() + opp.vel() + + if attack_pos.dist2(goal_pos) > my_next.dist2(goal_pos): + log.sw_log().intercept().add_text( + f"do_kickable_opp_check (GoToPoint)") + log.sw_log().intercept().add_circle( + center=attack_pos, + r=0.5, + color=Color(string='red')) + GoToPoint(attack_pos, + 0.1, + ServerParam.i().max_dash_power(), + -1, + 1, + True, + 15).execute(agent) + return True + return False + + def get_best_intercept(self, wm: 'WorldModel', + table: InterceptTable) -> InterceptInfo: + SP = ServerParam.i() + cache = table.self_cache() + + if len(cache) == 0: + return InterceptInfo() + + goal_pos = Vector2D(65, 0) + our_goal_pos = Vector2D(-SP.pitch_half_length(), 0) + # max_pitch_x = SP.pitch_half_length() - 1 + # max_pitch_y = SP.pitch_half_width() - 1 + + max_pitch_x = SP.keepaway_length()/2 - 1 + max_pitch_y = SP.keepaway_width()/2 - 1 + + penalty_x = SP.our_penalty_area_line_x() + penalty_y = SP.penalty_area_half_width() + speed_max = wm.self().player_type().real_speed_max() * 0.9 + opp_min = table.opponent_reach_cycle() + mate_min = table.teammate_reach_cycle() + + attacker_best: InterceptInfo = None + attacker_score = 0.0 + + forward_best: InterceptInfo = None + forward_score = 0.0 + + noturn_best: InterceptInfo = None + noturn_score = 10000.0 + + nearest_best: InterceptInfo = None + nearest_score = 10000.0 + + goalie_best: InterceptInfo = None + goalie_score = -10000.0 + + goalie_aggressive_best: InterceptInfo = None + goalie_aggressive_score = -10000.0 + + MAX = len(cache) + for i in range(MAX): + + if self._save_recovery and cache[i].mode() != InterceptInfo.Mode.NORMAL: + continue + + cycle = cache[i].reach_cycle() + self_pos = wm.self().inertia_point(cycle) + ball_pos = wm.ball().inertia_point(cycle) + ball_vel = wm.ball().vel() * SP.ball_decay() ** cycle + + if ball_pos.abs_x() > max_pitch_x or \ + ball_pos.abs_y() > max_pitch_y: + continue + + if (wm.self().goalie() + and wm.last_kicker_side() != wm.our_side() + and ball_pos.x() < penalty_x - 1 + and ball_pos.abs_y() < penalty_y - 1 + and cycle < opp_min - 1): + if ((cache[i].turn_cycle() == 0 + and cache[i].ball_dist() < wm.self().player_type().catchable_area() * 0.5) + or cache[i].ball_dist() < 0.01): + d = ball_pos.dist2(our_goal_pos) + if d > goalie_score: + goalie_score = d + goalie_best = cache[i] + + attacker = False + if (ball_vel.x() > 0.5 + and ball_vel.r2() > speed_max ** 2 + and cache[i].dash_power() >= 0 + and ball_pos.x() < 47 + and (ball_pos.x() > 35 + or ball_pos.x() > wm.offside_line_x())): + attacker = True + + opp_rate = 0.95 if attacker else 0.7 + if cycle >= opp_min * opp_rate: + continue + + # attacker type + if attacker: + goal_dist = 100 - min(100, ball_pos.dist(goal_pos)) + x_diff = 47 - ball_pos.x() + score = ((goal_dist / 100) + * math.e ** (-(x_diff ** 2) / (2 * 100))) + + if score > attacker_score: + attacker_best = cache[i] + attacker_score = score + + continue + + # no turn type + if cache[i].turn_cycle() == 0: + score = cycle + if score < noturn_score: + noturn_best = cache[i] + noturn_score = score + continue + + # forward type + if ball_vel.x() > 0.1 and \ + cycle <= opp_min - 5 and \ + ball_vel.r2() > 0.6 ** 2: + score = (100 ** 2 + - min(100 ** 2, ball_pos.dist2(goal_pos))) + + if score > forward_score: + forward_best = cache[i] + forward_score = score + continue + + # other: select nearest one + d = self_pos.dist2(ball_pos) + if d < nearest_score: + nearest_best = cache[i] + nearest_score = d + + if goalie_aggressive_best is not None: + return goalie_aggressive_best + + if goalie_best is not None: + return goalie_best + + if attacker_best is not None: + return attacker_best + + if noturn_best is not None and forward_best is not None: + noturn_ball_vel = (wm.ball().vel() + * SP.ball_decay() ** noturn_best.reach_cycle()) + noturn_ball_speed = noturn_ball_vel.r() + if (noturn_ball_vel.x() > 0.1 + and (noturn_ball_speed > speed_max + or noturn_best.reach_cycle() <= forward_best.reach_cycle() + 2)): + return noturn_best + + if forward_best is not None: + return forward_best + + fastest_pos = wm.ball().inertia_point(cache[0].reach_cycle()) + fastest_vel = wm.ball().vel() * SP.ball_decay() ** cache[0].reach_cycle() + + if ((fastest_pos.x() > -33 + or fastest_pos.abs_y() > 20) + and (cache[0].reach_cycle() >= 10 + or fastest_vel.r() < 1.2)): + return cache[0] + + if noturn_best is not None and nearest_best is not None: + noturn_self_pos = wm.self().inertia_point(noturn_best.reach_cycle()) + noturn_ball_pos = wm.ball().inertia_point(noturn_best.reach_cycle()) + nearest_self_pos = wm.self().inertia_point(nearest_best.reach_cycle()) + nearest_ball_pos = wm.ball().inertia_point(nearest_best.reach_cycle()) + + if noturn_self_pos.dist2(noturn_ball_pos) < nearest_self_pos.dist2(nearest_ball_pos): + return noturn_best + + if nearest_best.reach_cycle() <= noturn_best.reach_cycle() + 2: + nearest_ball_vel = (wm.ball().vel() + * SP.ball_decay() ** nearest_best.reach_cycle()) + nearest_ball_speed = nearest_ball_vel.r() + if nearest_ball_speed < 0.7: + return nearest_best + + noturn_ball_vel = (wm.ball().vel() + * SP.ball_decay() ** noturn_best.reach_cycle()) + if (nearest_best.ball_dist() < wm.self().player_type().kickable_area() - 0.4 + and nearest_best.ball_dist() < noturn_best.ball_dist() + and noturn_ball_vel.x() < 0.5 + and noturn_ball_vel.r2() > 1 ** 2 + and noturn_ball_pos.x() > nearest_ball_pos.x()): + return nearest_best + + nearest_self_pos = wm.self().inertia_point(nearest_best.reach_cycle()) + if nearest_ball_speed > 0.7 and \ + nearest_self_pos.dist2(nearest_ball_pos) < wm.self().player_type().kickable_area(): + return nearest_best + return noturn_best + + if noturn_best is not None: + return noturn_best + + if nearest_best is not None: + return nearest_best + + if (wm.self().pos().x() > 40 + and wm.ball().vel().r() > 1.8 + and wm.ball().vel().th().abs() < 100 + and cache[0].reach_cycle() > 1): + chance_best: InterceptInfo = None + for i in range(MAX): + if (cache[i].reach_cycle() <= cache[0].reach_cycle() + 3 + and cache[i].reach_cycle() <= opp_min - 2): + chance_best = cache[i] + + if chance_best is not None: + return chance_best + + return cache[0] + + def do_wait_turn(self, + agent: 'PlayerAgent', + target_point: Vector2D, + info: InterceptInfo): + wm = agent.world() + opp: PlayerObject = wm.get_opponent_nearest_to_self(5) + if opp is not None and opp.dist_from_self() < 3: + return False + opp_min = wm.intercept_table().opponent_reach_cycle() + if info.reach_cycle() > opp_min - 5: + return False + + my_inertia = wm.self().inertia_point(info.reach_cycle()) + target_rel = (target_point - my_inertia).rotated_vector(-wm.self().body()) + target_dist = target_rel.r() + + ball_travel = inertia_n_step_distance(wm.ball().vel().r(), + info.reach_cycle(), + ServerParam.i().ball_decay()) + ball_noise = ball_travel * ServerParam.i().ball_rand() + + if info.reach_cycle() == 1 and info.turn_cycle() == 1: + face_point = self._face_point + if not face_point.is_valid(): + face_point.assign(50.5, wm.self().pos().y() * 0.9) + + log.sw_log().intercept().add_text( + f"do wait turn (1) (TurnToPoint)") + log.sw_log().intercept().add_circle( + center=face_point, + r=0.5, + color=Color(string='red')) + TurnToPoint(face_point).execute(agent) + return True + + extra_buf = 0.1 * bound(0, info.reach_cycle() - 1, 4) + angle_diff = (wm.ball().vel().th() - wm.self().body()).abs() + if angle_diff < 10 or 170 < angle_diff: + extra_buf = 0 + + dist_buf = wm.self().player_type().kickable_area() - 0.3 + extra_buf + dist_buf -= 0.1 * wm.ball().seen_pos_count() + + if target_dist > dist_buf: + return False + + face_point = self._face_point + if info.reach_cycle() > 2: + face_point = my_inertia + (wm.ball().pos() - my_inertia).rotated_vector(90) + if face_point.x() < my_inertia.x(): + face_point = my_inertia + (wm.ball().pos() - my_inertia).rotated_vector(-90) + + if not face_point.is_valid(): + face_point.assign(50.5, wm.self().pos().y() * 0.9) + + face_rel = face_point - my_inertia + face_angle = face_rel.th() + + faced_rel = target_point - my_inertia + faced_rel.rotate(face_angle) + if faced_rel.abs_y() > wm.self().player_type().kickable_area() - ball_noise - 0.2: + return False + + log.sw_log().intercept().add_text( + f"do wait turn (2)(TurnToPoint)") + log.sw_log().intercept().add_circle( + center=face_point, + r=0.5, + color=Color(string='red')) + TurnToPoint(face_point).execute(agent) + return True + + def do_inertia_dash(self, + agent: 'PlayerAgent', + target_point: Vector2D, + info: InterceptInfo): + wm = agent.world() + ptype = wm.self().player_type() + + if info.reach_cycle() == 1: + agent.do_dash(info.dash_power(), info.dash_angle()) + return True + + target_rel = target_point - wm.self().pos() + target_rel.rotate(-wm.self().body()) + + accel_angle = wm.self().body() + if info.dash_power() < 0: + accel_angle += 180 + + ball_vel = wm.ball().vel() * ServerParam.i().ball_decay() ** info.reach_cycle() + if ((not wm.self().goalie() + or wm.last_kicker_side() == wm.our_side()) + and wm.self().body().abs() < 50): + buf = 0.3 + if info.reach_cycle() >= 8: + buf = 0 + elif target_rel.abs_y() > ptype.kickable_area() - 0.25: + buf = 0 + elif target_rel.x() < 0: + if info.reach_cycle() >= 3: + buf = 0.5 + elif target_rel.x() < 0.3: + if info.reach_cycle() >= 3: + buf = 0.5 + elif target_rel.abs_y() < 0.5: + if info.reach_cycle() >= 3: + buf = 0.5 + if info.reach_cycle() == 2: + buf = min(target_rel.x(), 0.5) + elif ball_vel.r() < 1.6: + buf = 0.4 + else: + if info.reach_cycle() >= 4: + buf = 0.3 + elif info.reach_cycle() == 3: + buf = 0.3 + elif info.reach_cycle() == 2: + buf = min(target_rel.x(), 0.3) + + target_rel -= Vector2D(buf, 0) + + used_power = info.dash_power() + if (wm.ball().seen_pos_count() <= 2 + and wm.ball().vel().r() * ServerParam.i().ball_decay() ** info.reach_cycle() < ptype.kickable_area() * 1.5 + and info.dash_angle().abs() < 5 + and target_rel.abs_x() < (ptype.kickable_area() + + ptype.dash_rate(wm.self().effort()) + * ServerParam.i().max_dash_power() + * 0.8)): + first_speed = calc_first_term_geom_series(target_rel.x(), + ptype.player_decay(), + info.reach_cycle()) + first_speed = min_max(-ptype.player_speed_max(), + first_speed, + ptype.player_speed_max()) + rel_vel = wm.self().vel().rotated_vector(-wm.self().body()) + required_accel = first_speed - rel_vel.x() + used_power = required_accel / wm.self().dash_rate() + used_power /= ServerParam.i().dash_dir_rate(info.dash_angle().degree()) + + used_power = ServerParam.i().normalize_dash_power(used_power) + if self._save_recovery: + used_power = wm.self().get_safety_dash_power(used_power) + + if (info.reach_cycle() >= 4 + and (target_rel.abs_x() < 0.5 + or abs(used_power) < 5)): + my_inertia = wm.self().inertia_point(info.reach_cycle()) + face_point = self._face_point + if not face_point.is_valid(): + face_point.assign(50.5, wm.self().pos().y() * 0.75) + face_angle = (face_point - my_inertia).th() + + ball_next = wm.ball().pos() + wm.ball().vel() + ball_angle = (ball_next - my_inertia).th() + # normal_half_width = ViewWidth.width(ViewWidth.Mode.NORMAL) # TODO FIX THIS after view mode + normal_half_width = ServerParam.i().visible_angle() + if ((ball_angle - face_angle).abs() + > (ServerParam.i().max_neck_angle() + + normal_half_width + - 10)): + face_point = Vector2D(my_inertia.x(), face_point.y()) + if ball_next.y() > my_inertia.y() + 1: + face_point = Vector2D(face_point.x(), 50) + elif ball_next.y() < my_inertia.y() - 1: + face_point = Vector2D(face_point.x(), -50) + TurnToPoint(face_point).execute(agent) + agent.do_dash(used_power, info.dash_angle()) + return True diff --git a/keepaway/lib/action/intercept_info.py b/keepaway/lib/action/intercept_info.py new file mode 100644 index 00000000..64595297 --- /dev/null +++ b/keepaway/lib/action/intercept_info.py @@ -0,0 +1,88 @@ +from enum import Enum + +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.vector_2d import Vector2D + + +class InterceptInfo: + class Mode(Enum): + NORMAL = 0 + EXHAUST = 100 + + def __init__(self, mode: Mode = Mode.EXHAUST, turn_cycle: int = 10000, dash_cycle: int = 10000, + dash_power: float = 100000, dash_angle: float = 0, + self_pos: Vector2D = Vector2D(-10000, 0), ball_dist: float = 100000, stamina: float = 0): + self._valid: bool = True + self._mode: InterceptInfo.Mode = mode + self._turn_cycle: int = turn_cycle + self._dash_cycle: int = dash_cycle + self._dash_power: float = dash_power + self._dash_angle: AngleDeg = AngleDeg(dash_angle) + self._self_pos: Vector2D = self_pos.copy() # object in object then copy + self._ball_dist: float = ball_dist + self._stamina: float = stamina + if self._mode == InterceptInfo.Mode.EXHAUST and \ + self._turn_cycle == 10000 and \ + self._dash_cycle == 10000 and \ + self._dash_power == 100000.0 and \ + self._dash_angle == AngleDeg(0.0) and \ + self._self_pos == Vector2D(-10000.0, 0.0) and \ + self._ball_dist == 10000000.0 and \ + self._stamina == 0.0: + self._valid = False + + def init(self, mode: Mode = Mode.EXHAUST, turn_cycle: int = 10000, dash_cycle: int = 10000, + dash_power: float = 100000, dash_angle: float = 0, + self_pos: Vector2D = Vector2D(-10000, 0), ball_dist: float = 100000, stamina: float = 0): + self.__init__(mode, turn_cycle, dash_cycle, dash_power, dash_angle, self_pos, ball_dist, stamina) + + def is_valid(self): + return self._valid + + def mode(self): + return self._mode + + def turn_cycle(self): + return self._turn_cycle + + def dash_cycle(self): + return self._dash_cycle + + def reach_cycle(self): + return self._dash_cycle + self._turn_cycle + + def dash_power(self): + return self._dash_power + + def dash_angle(self): + return self._dash_angle + + def self_pos(self): + return self._self_pos + + def ball_dist(self): + return self._ball_dist + + def stamina(self): + return self._stamina + + @staticmethod + def compare(lhs, rhs): + return True if lhs.reach_cycle() < rhs.reach_cycle() else ( + lhs.reach_cycle() == rhs.reach_cycle() and lhs.turn_cycle() < rhs.turn_cycle()) + + def __lt__(self, other): + if self.reach_cycle() < other.reach_cycle(): + return True + return self.reach_cycle() == other.reach_cycle() and self.turn_cycle() < other.turn_cycle() + + def __repr__(self): + return (f"(mode: {self._mode}," + f"each_cycle: {self.reach_cycle()}," + f"turn_cycle: {self._turn_cycle}," + f"dash_cycle: {self._dash_cycle}," + f"dash_power: {self._dash_power}," + f"dash_angle: {self._dash_angle.degree()}," + f"self_pos: {self._self_pos}," + f"ball_dist: {self._ball_dist}," + f"stamina: {self._stamina})") diff --git a/keepaway/lib/action/intercept_player.py b/keepaway/lib/action/intercept_player.py new file mode 100644 index 00000000..0576c4c8 --- /dev/null +++ b/keepaway/lib/action/intercept_player.py @@ -0,0 +1,225 @@ +from math import floor + +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound, inertia_n_step_point +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.player.object_player import PlayerObject +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class PlayerIntercept: + def __init__(self, wm: 'WorldModel', ball_cache): + self._wm = wm + self._ball_cache = ball_cache + + def predict(self, + player: PlayerObject, + player_type: PlayerType, + max_cycle: int): + wm = self._wm + penalty_x_abs = ServerParam.i().pitch_half_length() - ServerParam.i().penalty_area_length() + penalty_y_abs = ServerParam.i().penalty_area_half_width() + + pos_count = min(player.seen_pos_count(), player.pos_count()) + player_pos = (player.seen_pos() + if player.seen_pos_count() <= player.pos_count() + else player.pos()) + min_cycle = 0 + ball_to_player = player_pos - wm.ball().pos() + ball_to_player.rotate(-wm.ball().vel().th()) + min_cycle = int(floor(ball_to_player.abs_y() + / player_type.real_speed_max())) + + if player.is_tackling(): + min_cycle += max(0, + ServerParam.i().tackle_cycles() - player.tackle_count() - 2) + + min_cycle = max(0, + min_cycle - min(player.seen_pos_count(), + player.pos_count())) + if min_cycle > max_cycle: + return self.predict_final(player, player_type) + + MAX_LOOP = min(max_cycle, + len(self._ball_cache)) + for cycle in range(min_cycle, MAX_LOOP): + ball_pos: Vector2D = self._ball_cache[cycle] + control_area = (player_type.catchable_area() + if (player.goalie() + and ball_pos.abs_x() > penalty_x_abs + and ball_pos.abs_y() < penalty_y_abs) + else player_type.kickable_area()) + + if (control_area + player_type.real_speed_max() * (cycle + pos_count) + 0.5 + < player_pos.dist(ball_pos)): + # never reach + continue + if self.can_reach_after_turn_dash(cycle, + player, + player_type, + control_area, + ball_pos): + return cycle + + return self.predict_final(player, player_type) + + def predict_final(self, player: PlayerObject, player_type: PlayerType): + wm = self._wm + penalty_x_abs = ServerParam.i().pitch_half_length() - ServerParam.i().penalty_area_length() + penalty_y_abs = ServerParam.i().penalty_area_half_width() + + pos_count = min(player.seen_pos_count(), player.pos_count()) + ppos = (player.seen_pos() + if player.seen_pos_count() <= player.pos_count() + else player.pos()) + pvel = (player.seen_vel() + if player.seen_vel_count() <= player.vel_count() + else player.vel()) + + ball_pos = self._ball_cache[-1] + ball_step = len(self._ball_cache) + + control_area = (player_type.catchable_area() + if (player.goalie() + and ball_pos.abs_x() > penalty_x_abs + and ball_pos.abs_y() < penalty_y_abs) + else player_type.kickable_area()) + + n_turn = self.predict_turn_cycle(100, + player, + player_type, + control_area, + ball_pos) + + inertia_pos = player_type.inertia_point(ppos, pvel, 100) + dash_dist = inertia_pos.dist(ball_pos) + dash_dist -= control_area + + if player.side() != wm.our_side(): + dash_dist -= player.dist_from_self() * 0.03 + + if dash_dist < 0: + return ball_step + + n_dash = player_type.cycles_to_reach_distance(dash_dist) + + if player.side() != wm.our_side(): + n_dash -= bound(0, pos_count - n_turn, 10) + else: + n_dash -= bound(0, pos_count - n_turn, 1) + + n_dash = max(1, n_dash) + + return max(ball_step, n_turn + n_dash) + + def predict_turn_cycle(self, + cycle: int, + player: PlayerObject, + player_type: PlayerType, + control_area: float, + ball_pos: Vector2D): + ppos = (player.seen_pos() + if player.seen_pos_count() <= player.pos_count() + else player.pos()) + pvel = (player.seen_vel() + if player.seen_vel_count() <= player.vel_count() + else player.vel()) + + inertia_pos = player_type.inertia_point(ppos, pvel, cycle) + target_rel = ball_pos - inertia_pos + target_dist = target_rel.r() + turn_margin = 180 + if control_area < target_dist: + turn_margin = AngleDeg.asin_deg(control_area / target_dist) + turn_margin = max(turn_margin, 12) + + angle_diff = (target_rel.th() - player.body()).abs() + + if (target_dist < 5 # XXX MAGIC NUMBER XXX :| + and angle_diff > 90): + # assume back dash + angle_diff = 180 - angle_diff + + n_turn = 0 + speed = player.vel().r() + while angle_diff > turn_margin: + max_turn = player_type.effective_turn(ServerParam.i().max_moment(), + speed) + angle_diff -= max_turn + speed *= player_type.player_decay() + n_turn += 1 + + return n_turn + + def can_reach_after_turn_dash(self, + cycle: int, + player: PlayerObject, + player_type: PlayerType, + control_area: float, + ball_pos: Vector2D): + n_turn = self.predict_turn_cycle(cycle, + player, + player_type, + control_area, + ball_pos) + + n_dash = cycle - n_turn + if n_dash < 0: + return False + + return self.can_reach_after_dash(n_turn, + n_dash, + player, + player_type, + control_area, + ball_pos) + + def can_reach_after_dash(self, + n_turn: int, + max_dash: int, + player: PlayerObject, + player_type: PlayerType, + control_area: float, + ball_pos: Vector2D): + wm = self._wm + pos_count = min(player.seen_pos_count(), player.pos_count()) + ppos = (player.seen_pos() + if player.seen_pos_count() <= player.pos_count() + else player.pos()) + pvel = (player.seen_vel() + if player.seen_vel_count() <= player.vel_count() + else player.vel()) + + player_pos = inertia_n_step_point(ppos, pvel, + n_turn + max_dash, + player_type.player_decay()) + + player_to_ball = ball_pos - player_pos + player_to_ball_dist = player_to_ball.r() + player_to_ball_dist -= control_area + + if player_to_ball_dist < 0: + return True + + estimate_dash = player_type.cycles_to_reach_distance(player_to_ball_dist) + n_dash = estimate_dash + if player.side != wm.our_side(): + n_dash -= bound(0, + pos_count - n_turn, + min(6, wm.ball().seen_pos_count() + 1)) + else: + n_dash -= bound(0, + pos_count - n_turn, + min(1, wm.ball().seen_pos_count())) + + if player.is_tackling(): + n_dash += max(0, ServerParam.i().tackle_cycles() - player.tackle_count() - 2) + + if n_dash <= max_dash: + return True + return False diff --git a/keepaway/lib/action/intercept_self.py b/keepaway/lib/action/intercept_self.py new file mode 100644 index 00000000..c518e2a1 --- /dev/null +++ b/keepaway/lib/action/intercept_self.py @@ -0,0 +1,1151 @@ +from math import ceil +from keepaway.lib.action.intercept_info import InterceptInfo +from keepaway.lib.debug.debug import log +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.line_2d import Line2D +from pyrusgeom.segment_2d import Segment2D +from pyrusgeom.soccer_math import bound, calc_first_term_geom_series, min_max +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.player.object_ball import BallObject +from keepaway.lib.player.object_player import PlayerObject +from keepaway.lib.player.stamina_model import StaminaModel +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +control_area_buf = 0.15 + +DEBUG = True + + +class SelfIntercept: + def __init__(self, wm, ball_cache): + self._wm: 'WorldModel' = wm + self._ball_cache = ball_cache + + self._max_short_step = 5 + self._min_turn_thr = 12.5 + self._back_dash_thr_angle = 100 + + def predict(self, max_cycle, self_cache: list): + if len(self._ball_cache) < 2: + log.sw_log().intercept().add_text("no ball position cache :(") + return + + save_recovery: bool = self._wm.self().stamina_model().capacity() != 0 + + self.predict_one_step(self_cache) + self.predict_short_step(max_cycle, save_recovery, self_cache) + self.predict_long_step(max_cycle, save_recovery, self_cache) + + self_cache.sort() # TODO check this + log.sw_log().intercept().add_text("self pred all sorted intercept") + for ii in self_cache: + log.sw_log().intercept().add_text(f"{ii}") + + def predict_one_step(self, self_cache): + wm = self._wm + ball_next: Vector2D = wm.ball().pos() + wm.ball().vel() + goalie_mode: bool = self.is_goalie_mode(ball_next) + control_area: float = wm.self().player_type().catchable_area() if \ + goalie_mode else \ + wm.self().player_type().kickable_area() + + # dist is to far never reach with one dash + if wm.ball().dist_from_self() > \ + ServerParam.i().ball_speed_max() \ + + wm.self().player_type().real_speed_max() \ + + control_area: + return + if self.predict_no_dash(self_cache): + return + self.predict_one_dash(self_cache) + + def predict_no_dash(self, self_cache) -> bool: + log.sw_log().intercept().add_text("=================== predict_no_dash ======================") + SP = ServerParam.i() + wm: 'WorldModel' = self._wm + me: PlayerObject = wm.self() + + my_next: Vector2D = me.pos() + me.vel() + ball_next: Vector2D = wm.ball().pos() + wm.ball().vel() + goalie_mode: bool = self.is_goalie_mode(ball_next) + control_area: float = me.player_type().catchable_area() if \ + goalie_mode else \ + me.player_type().kickable_area() + next_ball_rel: Vector2D = (ball_next - my_next).rotated_vector(-me.body()) + ball_noise: float = wm.ball().vel().r() * SP.ball_rand() + next_ball_dist: float = next_ball_rel.r() + + # out of control area + if next_ball_dist > control_area - 0.15 - ball_noise: + log.sw_log().intercept().add_text("----->>>> NO ball is out of intercept no dash area") + return False + + # if goalie immediately success + if goalie_mode: + stamina_model: StaminaModel = me.stamina_model() + stamina_model.simulate_wait(me.player_type()) + + self_cache.append(InterceptInfo(InterceptInfo.Mode.NORMAL, + 1, 0, + 0, 0, + my_next, + next_ball_dist, + stamina_model.stamina())) + log.sw_log().intercept().add_text("------>>>>> OK goal mode success") + return True + + # check kick effectiveness + ptype: PlayerType = me.player_type() + if next_ball_dist > ptype.player_size() + SP.ball_size(): + kick_rate: float = ptype.kick_rate(next_ball_dist, + next_ball_rel.th().degree()) + next_ball_vel: Vector2D = wm.ball().vel() * SP.ball_decay() + if SP.max_power() * kick_rate <= next_ball_vel.r() * SP.ball_decay() * 1.1: + log.sw_log().intercept().add_text("------>>>>> NO can not control the ball") + return False + + # at least, player can stop the ball + stamina_model = me.stamina_model() + self_cache.append(InterceptInfo(InterceptInfo.Mode.NORMAL, + 1, 0, # 1 turn + 0, 0, + my_next, + next_ball_dist, + stamina_model.stamina())) + log.sw_log().intercept().add_text("----->>>>> OK can control with out dash") + return True + + def is_goalie_mode(self, ball_next, x_limit=None, abs_y_limit=None) -> bool: + wm = self._wm + if x_limit is None: + x_limit = ServerParam.i().our_penalty_area_line_x() + if abs_y_limit is None: + abs_y_limit = ServerParam.i().penalty_area_half_width() + + return (wm.self().goalie() and + wm.last_kicker_side() != wm.our_side() and + ball_next.x() < x_limit and + ball_next.abs_y() < abs_y_limit) + + def predict_one_dash(self, self_cache): + log.sw_log().intercept().add_text("=================== predict_one_dash ======================") + tmp_cache = [] + + SP = ServerParam.i() + wm: 'WorldModel' = self._wm + ball: BallObject = wm.ball() + me: PlayerObject = wm.self() + ptype: PlayerType = me.player_type() + + ball_next: Vector2D = ball.pos() + ball.vel() + goalie_mode: bool = self.is_goalie_mode(ball_next) + control_area: float = ptype.catchable_area() if \ + goalie_mode else \ + ptype.kickable_area() + dash_angle_step: float = max(5, SP.dash_angle_step()) + min_dash_angle = (SP.min_dash_angle() + if -180 < SP.min_dash_angle() and SP.max_dash_angle() < 180 + else dash_angle_step * int(-180 / dash_angle_step)) + max_dash_angle = (SP.max_dash_angle() + dash_angle_step * 0.5 + if -180 < SP.min_dash_angle() and SP.max_dash_angle() < 180 + else dash_angle_step * int(180 / dash_angle_step) - 1) + + n_steps = int((max_dash_angle - min_dash_angle) / dash_angle_step) + dirs = [min_dash_angle + d * dash_angle_step for d in range(n_steps)] + for dash_dir in dirs: + dash_angle: AngleDeg = me.body() + SP.discretize_dash_angle(SP.normalize_dash_angle(dash_dir)) + dash_rate: float = me.dash_rate() * SP.dash_dir_rate(dash_dir) + log.sw_log().intercept().add_text(f"----- dash dir={dash_dir}, angle={dash_angle}, dash_rate={dash_rate}") + + # check recovery save dash + forward_dash_power = bound(0, + me.stamina() - SP.recover_dec_thr_value() - 1, + SP.max_dash_power()) + back_dash_power = bound(SP.min_dash_power(), + (me.stamina() - SP.recover_dec_thr_value() - 1) * -0.5, + 0) + + max_forward_accel = Vector2D.polar2vector(forward_dash_power * dash_rate, + dash_angle) + max_back_accel = Vector2D.polar2vector(back_dash_power * dash_rate, + dash_angle) + ptype.normalize_accel(me.vel(), max_forward_accel) + ptype.normalize_accel(me.vel(), max_back_accel) + + info: InterceptInfo = InterceptInfo() + if self.predict_one_dash_adjust(dash_angle, + max_forward_accel, + max_back_accel, + control_area, + info): + log.sw_log().intercept().add_text("---->>>> OK Register 1 dash intercept(1)") + tmp_cache.append(info) + continue + + # check max_power_dash + if abs(forward_dash_power - SP.max_dash_power()) < 1 and \ + abs(back_dash_power - SP.min_dash_power()) < 1: + log.sw_log().intercept().add_text("---->>>> NO max dash power") + continue + + max_forward_accel = Vector2D.polar2vector(SP.max_dash_power() * dash_rate, + dash_angle) + max_back_accel = Vector2D.polar2vector(SP.min_dash_power() * dash_rate, + dash_angle) + ptype.normalize_accel(me.vel(), max_forward_accel) + ptype.normalize_accel(me.vel(), max_back_accel) + + info: InterceptInfo = InterceptInfo() + if self.predict_one_dash_adjust(dash_angle, + max_forward_accel, + max_back_accel, + control_area, + info): + log.sw_log().intercept().add_text("---->>>> OK Register 1 dash intercept(2)") + tmp_cache.append(info) + continue + + if len(tmp_cache) == 0: + log.sw_log().intercept().add_text("======>>>>> No one dash intercept") + return + best: InterceptInfo = tmp_cache[0] + for it in tmp_cache: + if best.ball_dist() > it.ball_dist() or \ + (abs(best.ball_dist() - it.ball_dist()) < 0.001 and + best.stamina() < it.stamina()): + best = it + log.sw_log().intercept().add_text(f'=====>>>> best one dash: {str(best)}') + self_cache.append(best) + + def predict_one_dash_adjust(self, + dash_angle: AngleDeg, + max_forward_accel: Vector2D, + max_back_accel: Vector2D, + control_area: float, + info: InterceptInfo): + SP = ServerParam.i() + wm = self._wm + me = wm.self() + + control_buf = control_area - 0.075 + dash_dir: AngleDeg = dash_angle - me.body() + ball_next = wm.ball().pos() + wm.ball().vel() + me_next = me.pos() + me.vel() + + ball_rel: Vector2D = (ball_next - me_next).rotated_vector(-dash_angle) + forward_accel_rel: Vector2D = max_forward_accel.rotated_vector(-dash_angle) + back_accel_rel: Vector2D = max_back_accel.rotated_vector(-dash_angle) + dash_rate = me.dash_rate() * SP.dash_dir_rate(dash_dir.degree()) + # debug_print( + # f"self pred one dash adjust dir={dash_dir}, ball_rel={ball_rel} ,_____ max_forward_accel={max_forward_accel} rel={forward_accel_rel} , _____ max_back_accel={max_back_accel} rel={back_accel_rel}") + log.sw_log().intercept().add_text( + f"self pred one dash adjust dir={dash_dir}, ball_rel={ball_rel}") + log.sw_log().intercept().add_text( + f"_____ max_forward_accel={max_forward_accel} rel={forward_accel_rel}") + log.sw_log().intercept().add_text( + f"_____ max_back_accel={max_back_accel} rel={back_accel_rel}") + + if ball_rel.abs_y() > control_buf or \ + Segment2D(forward_accel_rel, back_accel_rel).dist(ball_rel) > control_buf: + return False + + dash_power = -1000 + + # small x difference + # player can put the ball on his side + if back_accel_rel.x() < ball_rel.x() < forward_accel_rel.x(): + dash_power = self.get_one_step_dash_power(ball_rel, + dash_angle, + forward_accel_rel.x(), + back_accel_rel.x()) + log.sw_log().intercept().add_text( + f"self pred one dash adjust (1). dash_power={dash_power}") + + # big x difference x (>0) + if dash_power < -999 and \ + forward_accel_rel.x() < ball_rel.x(): + enable_ball_dist = ball_rel.dist(forward_accel_rel) + if enable_ball_dist < control_buf: + dash_power = forward_accel_rel.x() / dash_rate + log.sw_log().intercept().add_text( + f"self pred one dash adjust (2). not best." + + f"next_ball_dist={enable_ball_dist}, dash_power={dash_power}") + + # big x difference (<0) + if dash_power < -999 and \ + ball_rel.x() < back_accel_rel.x(): + enable_ball_dist = ball_rel.dist(back_accel_rel) + if enable_ball_dist < control_buf: + dash_power = back_accel_rel.x() / dash_rate + log.sw_log().intercept().add_text( + f"self pred one dash adjust (3). not best." + + f"next_ball_dist={enable_ball_dist}, dash_power={dash_power}") + + # check if adjustable + if dash_power < -999 and \ + back_accel_rel.x() < ball_rel.x() < forward_accel_rel.x(): + dash_power = ball_rel.x() / dash_rate + log.sw_log().intercept().add_text( + f"self pred one dash adjust (4). not best." + + f"just adjust X. dash_power={dash_power}") + + # register + if dash_power < -999: + log.sw_log().intercept().add_text("self pred one dash adjust XXX FAILED") + return False + + mode = InterceptInfo.Mode.NORMAL + accel = Vector2D.polar2vector(dash_power * dash_rate, dash_angle) + my_vel = me.vel() + accel + my_pos = me.pos() + my_vel + + stamina_model = me.stamina_model() + stamina_model.simulate_dash(me.player_type(), dash_power) + + if stamina_model.stamina() < SP.recover_dec_thr_value() and \ + not stamina_model.capacity_is_empty(): + mode = InterceptInfo.Mode.EXHAUST + + info.init(mode, 0, 1, dash_power, dash_dir.degree(), + my_pos, + my_pos.dist(ball_next), + stamina_model.stamina()) + log.sw_log().intercept().add_text( + f"self pred one dash adjust Success! " + f"power={info.dash_power()}, " + f"rel_dir={info.dash_angle()}, " + f"angle={dash_angle.degree()}" + f"my_pos={my_pos}" + f"ball_dist={info.ball_dist()}" + f"stamina={stamina_model.stamina()}") + return True + + def get_one_step_dash_power(self, + next_ball_rel: Vector2D, + dash_angle: AngleDeg, + max_forward_accel_x: float, + max_back_accel_x): + wm = self._wm + + dash_rate = wm.self().dash_rate() * ServerParam.i().dash_dir_rate(dash_angle.degree()) + ptype = wm.self().player_type() + best_ctrl_dist_forward = (ptype.player_size() + + ptype.kickable_margin() / 2 + + ServerParam.i().ball_size()) + best_ctrl_dist_backward = (ptype.player_size() + + 0.3 * ptype.kickable_margin() + + ServerParam.i().ball_size()) + + # y diff is longer than best dist. + # just put the ball on player's side + if next_ball_rel.abs_y() > best_ctrl_dist_forward: # TODO FIX COMPLEX + return next_ball_rel.x() / dash_rate + # if next_ball_rel.y()**2 > best_ctrl_dist_forward**2: + # return next_ball_rel.x() / dash_rate + forward_trap_accel_x = (next_ball_rel.x() + - (best_ctrl_dist_forward ** 2 + - next_ball_rel.y() ** 2) ** 0.5) + backward_trap_accel_x = (next_ball_rel.x() + + (best_ctrl_dist_backward ** 2 + - next_ball_rel.y() ** 2) ** 0.5) + + best_accel_x = 10000 + min_power = 10000 + + x_step = (backward_trap_accel_x - forward_trap_accel_x) / 5 + # debug_print("forward_trap_accel_x:", forward_trap_accel_x, "| backward_trap_accel_x :", backward_trap_accel_x, + # "| X_step :", + # x_step) + accels = [forward_trap_accel_x + a * x_step for a in range(5)] + for accel_x in accels: + if (0 <= accel_x < max_forward_accel_x) or \ + (max_back_accel_x < accel_x < 0): + power = accel_x / dash_rate + if abs(power) < abs(min_power): + best_accel_x = accel_x + min_power = power + + if min_power < 1000: + return min_power + return -1000 + + def predict_short_step(self, max_cycle, save_recovery, self_cache): + tmp_cache = [] + max_loop = min(self._max_short_step, max_cycle) + + SP = ServerParam.i() + wm = self._wm + ball = wm.ball() + me = wm.self() + ptype = me.player_type() + + pen_area_x = SP.our_penalty_area_line_x() - 0.5 + pen_area_y = SP.penalty_area_half_width() - 0.5 + + ball_to_self = (me.pos() - ball.pos()).rotated_vector(-ball.vel().th()) + min_cycle = int(ceil((ball_to_self.abs_y() - ptype.kickable_area()) + / ptype.real_speed_max())) + + if min_cycle >= max_loop: + return + if min_cycle < 2: + min_cycle = 2 + + ball_pos = ball.inertia_point(min_cycle - 1) + ball_vel = ball.vel() * SP.ball_decay() ** (min_cycle - 1) + + for cycle in range(min_cycle, max_loop + 1): + tmp_cache = [] + ball_pos += ball_vel + ball_vel *= SP.ball_decay() + log.sw_log().intercept().add_text(f"self pred short cycle {cycle}: bpos={ball_pos}, bvel={ball_vel}") + + goalie_mode = self.is_goalie_mode(ball_pos, pen_area_x, pen_area_y) + control_area = (ptype.catchable_area() + if goalie_mode + else ptype.kickable_area()) + if (control_area + ptype.real_speed_max() * cycle) ** 2 < me.pos().dist2(ball_pos): + log.sw_log().intercept().add_text("self pred short too far") + continue + + self.predict_turn_dash_short(cycle, ball_pos, control_area, save_recovery, # forward dash + False, + max(0.1, control_area - 0.4), + tmp_cache) + + self.predict_turn_dash_short(cycle, ball_pos, control_area, save_recovery, # forward dash + False, + max(0.1, control_area - control_area_buf), + tmp_cache) + + self.predict_turn_dash_short(cycle, ball_pos, control_area, save_recovery, # back dash + True, + max(0.1, control_area - 0.4), + tmp_cache) + + self.predict_turn_dash_short(cycle, ball_pos, control_area, save_recovery, # back dash + True, + control_area - control_area_buf, + tmp_cache) + + if cycle <= 2: + self.predict_omni_dash_short(cycle, ball_pos, control_area, save_recovery, + False, tmp_cache) + + if len(tmp_cache) == 0: + continue + + safety_ball_dist = max(control_area - 0.2 - ball.pos().dist(ball_pos) * SP.ball_rand(), + ptype.player_size() + SP.ball_size() + ptype.kickable_margin() * 0.4) + best: InterceptInfo = tmp_cache[0] + for it in tmp_cache[1:]: + if best.ball_dist() < safety_ball_dist and \ + it.ball_dist() < safety_ball_dist: + if best.turn_cycle() > it.turn_cycle(): + best = it + elif best.turn_cycle() == it.turn_cycle() and \ + best.stamina() < it.stamina(): + best = it + else: + if best.turn_cycle() >= it.turn_cycle() and \ + (best.ball_dist() > it.ball_dist() + or (abs(best.ball_dist() - it.ball_dist()) < 0.001 + and best.stamina() < it.stamina())): + best = it + self_cache.append(best) + + def predict_omni_dash_short(self, + cycle: int, + ball_pos: Vector2D, + control_area: float, + save_recovery: bool, + back_dash: bool, + self_cache: list): + SP = ServerParam.i() + wm = self._wm + + me = wm.self() + ptype = me.player_type() + + body_angle = me.body() + 180 if back_dash else me.body() + my_inertia = me.inertia_point(cycle) + target_line = Line2D(p=ball_pos, a=body_angle) + + if target_line.dist(my_inertia) < control_area - 0.4: + return + + recover_dec_thr = SP.recover_dec_thr_value() + 1 + dash_angle_step = max(15, SP.dash_angle_step()) + min_dash_angle = (SP.min_dash_angle() + if -180 < SP.min_dash_angle() and SP.max_dash_angle() < 180 + else dash_angle_step * int(-180 / dash_angle_step)) + max_dash_angle = (SP.max_dash_angle() + dash_angle_step * 0.5 + if -180 < SP.min_dash_angle() and SP.max_dash_angle() < 180 + else dash_angle_step * int(180 / dash_angle_step) - 1) + + target_angle = (ball_pos - my_inertia).th() + + n_steps = int((max_dash_angle - min_dash_angle) / dash_angle_step) + dirs = [min_dash_angle + d * dash_angle_step for d in range(n_steps)] + for dirr in dirs: + if abs(dirr) < 1: + continue + + dash_angle = body_angle + SP.discretize_dash_angle(SP.normalize_dash_angle(dirr)) + if (dash_angle - target_angle).abs() > 91: + continue + + first_dash_power = 0 + my_pos = me.pos() + my_vel = me.vel() + stamina_model = me.stamina_model() + + n_omni_dash, first_dash_power = self.predict_adjust_omni_dash(cycle, + ball_pos, + control_area, + save_recovery, + back_dash, + dirr, + my_pos, + my_vel, + stamina_model, + first_dash_power) + if n_omni_dash < 0: + continue + if n_omni_dash == 0: + continue + + # check target point direction + inertia_pos = ptype.inertia_point(my_pos, my_vel, cycle - n_omni_dash) + target_rel = (ball_pos - inertia_pos).rotated_vector(-body_angle) + + if ((back_dash and target_rel.x() > 0) or + (not back_dash and target_rel.x() < 0)): + continue + + # dash to the body direction + body_accel_unit = Vector2D.polar2vector(1, body_angle) + for n_dash in range(n_omni_dash + 1, cycle + 1): + first_speed = calc_first_term_geom_series((ball_pos - my_pos).rotated_vector(-body_angle).x(), + ptype.player_decay(), + cycle - n_dash + 1) + rel_vel = my_vel.rotated_vector(-body_angle) + required_accel = first_speed - rel_vel.x() + dash_power = required_accel / (ptype.dash_rate(stamina_model.effort())) + if back_dash: + dash_power = -dash_power + + available_stamina = (max(0, stamina_model.stamina() - recover_dec_thr) + if save_recovery + else stamina_model.stamina() + ptype.extra_stamina()) + + if back_dash: + dash_power = bound(SP.min_dash_power(), dash_power, 0) + dash_power = max(dash_power, available_stamina * -0.5) + else: + dash_power = bound(0, dash_power, SP.max_dash_power()) + dash_power = min(available_stamina, dash_power) + + accel_mag = abs(dash_power) * ptype.dash_rate(stamina_model.effort()) + accel = body_accel_unit * accel_mag + + my_vel += accel + my_pos += my_vel + my_vel *= ptype.player_decay() + stamina_model.simulate_dash(ptype, dash_power) + + my_move = my_pos - me.pos() + if my_pos.dist2(ball_pos) < (control_area - control_area_buf) ** 2 or \ + my_move.r() > (ball_pos - me.pos()).rotated_vector(-my_move.th()).abs_x(): + mode = (InterceptInfo.Mode.EXHAUST + if stamina_model.recovery() < me.stamina_model().recovery() + and not stamina_model.capacity_is_empty() + else InterceptInfo.Mode.NORMAL) + self_cache.append(InterceptInfo(mode, + 0, cycle, + first_dash_power, dirr, + my_pos, + my_pos.dist(ball_pos), + stamina_model.stamina())) + + def predict_adjust_omni_dash(self, + cycle: int, + ball_pos: Vector2D, + control_area: float, + save_recovery: bool, + back_dash: bool, + dash_rel_dir: float, + my_pos: Vector2D, + my_vel: Vector2D, + stamina_model: StaminaModel, + first_dash_power) -> tuple: + SP = ServerParam.i() + wm = self._wm + me = wm.self() + ptype = me.player_type() + + recover_dec_thr = SP.recover_dec_thr_value() + 1 + max_omni_dash = min(2, cycle) + + body_angle = me.body() + 180 if back_dash else me.body() + target_line = Line2D(p=ball_pos, a=body_angle) + my_inertia = me.inertia_point(cycle) + + if target_line.dist(my_inertia) < control_area - 0.4: + first_dash_power = 0 + return 0, first_dash_power + + dash_angle = body_angle + SP.discretize_dash_angle(SP.normalize_dash_angle(dash_rel_dir)) + accel_unit = Vector2D.polar2vector(1, dash_angle) + dash_dir_rate = SP.dash_dir_rate(dash_rel_dir) + + # dash simulation + for n_omni_dash in range(1, max_omni_dash + 1): + first_speed = calc_first_term_geom_series(max(0, target_line.dist(my_pos)), + ptype.player_decay(), + cycle - n_omni_dash + 1) + rel_vel = my_vel.rotated_vector(-dash_angle) + required_accel = first_speed - rel_vel.x() + + if abs(required_accel) < 0.01: + return n_omni_dash - 1, first_dash_power + + dash_power = required_accel / (ptype.dash_rate(stamina_model.effort()) + * dash_dir_rate) + available_stamina = (max(0, stamina_model.stamina() - recover_dec_thr) + if save_recovery + else stamina_model.stamina() + ptype.extra_stamina()) + if back_dash: + dash_power = bound(SP.min_dash_power(), dash_power, 0) + dash_power = max(dash_power, available_stamina * -0.5) + else: + dash_power = bound(0, dash_power, SP.max_dash_power()) + dash_power = min(available_stamina, dash_power) + + if n_omni_dash == 1: + first_dash_power = dash_power + + accel_mag = (abs(dash_power) + * ptype.dash_rate(stamina_model.effort()) + * dash_dir_rate) + accel = accel_unit * accel_mag + my_vel += accel + my_pos += my_vel + my_vel *= ptype.player_decay() + + stamina_model.simulate_dash(ptype, dash_power) + inertia_pos = ptype.inertia_point(my_pos, my_vel, cycle - n_omni_dash) + + if target_line.dist(inertia_pos) < control_area - control_area_buf: + return n_omni_dash, first_dash_power + return -1, first_dash_power + + def predict_turn_dash_short(self, + cycle: int, + ball_pos: Vector2D, + control_area: float, + save_recovery: bool, + back_dash: bool, + turn_margin_control_area: float, + self_cache: list): + dash_angle = self._wm.self().body() + n_turn = self.predict_turn_cycle_short(cycle, ball_pos, control_area, back_dash, + turn_margin_control_area, + dash_angle) + if n_turn > cycle: + return + + self.predict_dash_cycle_short(cycle, n_turn, ball_pos, dash_angle, + control_area, save_recovery, back_dash, + self_cache) + + def predict_dash_cycle_short(self, + cycle: int, + n_turn: int, + ball_pos: Vector2D, + dash_angle: AngleDeg, + control_area: float, + save_recovery: bool, + back_dash: bool, + self_cache): + SP = ServerParam.i() + wm = self._wm + + me = wm.self() + ptype = me.player_type() + + recover_dec_thr = SP.recover_dec_thr_value() + 1 + max_dash = cycle - n_turn + + my_inertia = me.inertia_point(cycle) + my_pos = me.inertia_point(n_turn) + my_vel = me.vel() * ptype.player_decay() ** n_turn + + stamina_model = me.stamina_model() + stamina_model.simulate_waits(ptype, n_turn) + + if my_inertia.dist2(ball_pos) < (control_area - control_area_buf) ** 2: + my_final_pos = my_inertia.copy() + tmp_stamina = stamina_model.copy() + tmp_stamina.simulate_waits(ptype, cycle - n_turn) + self_cache.append(InterceptInfo(InterceptInfo.Mode.NORMAL, + n_turn, cycle - n_turn, + 0, 0, + my_final_pos, + my_final_pos.dist(ball_pos), + tmp_stamina.stamina())) + + target_angle: AngleDeg = (ball_pos - my_inertia).th() + if (target_angle - dash_angle).abs() > 90: + log.sw_log().intercept().add_text( + "self pred short target_angle - dash_angle > 90") + return + + accel_unit = Vector2D.polar2vector(1, dash_angle) + first_dash_power = 0 + for n_dash in range(1, max_dash + 1): + log.sw_log().intercept().add_text( + f"self pred short dash {n_dash}: max_dash={max_dash}") + ball_rel = (ball_pos - my_pos).rotated_vector(-dash_angle) + first_speed = calc_first_term_geom_series(ball_rel.x(), + ptype.player_decay(), + max_dash - n_dash + 1) + rel_vel = my_vel.rotated_vector(-dash_angle) + required_accel = first_speed - rel_vel.x() + dash_power = required_accel / ptype.dash_rate(stamina_model.effort()) + if back_dash: + dash_power = -dash_power + + available_stamina = (max(0, stamina_model.stamina() - recover_dec_thr) + if save_recovery + else stamina_model.stamina() + ptype.extra_stamina()) + if back_dash: + dash_power = bound(SP.min_dash_power(), dash_power, 0) + dash_power = max(dash_power, available_stamina * -0.5) + else: + dash_power = bound(0, dash_power, SP.max_dash_power()) + dash_power = min(available_stamina, dash_power) + + if n_dash == 1: + first_dash_power = dash_power + + accel_mag = abs(dash_power * ptype.dash_rate(stamina_model.effort())) + accel: Vector2D = accel_unit * accel_mag + + my_vel += accel + my_pos += my_vel + my_vel *= ptype.player_decay() + + stamina_model.simulate_dash(ptype, dash_power) + + if my_pos.dist2(ball_pos) < (control_area - control_area_buf) ** 2 or \ + me.pos().dist2(my_pos) > me.pos().dist2(ball_pos): + mode = (InterceptInfo.Mode.EXHAUST + if stamina_model.stamina() < SP.recover_dec_thr_value() + and not stamina_model.capacity_is_empty() + else InterceptInfo.Mode.NORMAL) + + self_cache.append(InterceptInfo(mode, n_turn, cycle - n_turn, + first_dash_power, 180 if back_dash else 0, + my_pos, + my_pos.dist(ball_pos), + stamina_model.stamina())) + + def predict_turn_cycle_short(self, + cycle: int, + ball_pos: Vector2D, + _: float, + back_dash: bool, + turn_margin_control_area: float, + result_dash_angle: AngleDeg) -> int: + SP = ServerParam.i() + wm = self._wm + max_moment = SP.max_moment() + + me = wm.self() + ptype = me.player_type() + + dist_thr = turn_margin_control_area + inertia_pos = me.inertia_point(cycle) + target_dist = (ball_pos - inertia_pos).r() + target_angle = (ball_pos - inertia_pos).th() + + n_turn = 0 + body_angle = me.body() + 180 if back_dash else me.body() + angle_diff = (target_angle - body_angle).abs() + + turn_margin = 180 + if dist_thr < target_dist: + turn_margin = max(self._min_turn_thr, + AngleDeg.asin_deg(dist_thr / target_dist)) + if angle_diff > turn_margin: + my_speed = me.vel().r() + while angle_diff > turn_margin: + angle_diff -= ptype.effective_turn(max_moment, my_speed) + my_speed *= ptype.player_decay() + n_turn += 1 + + result_dash_angle.set_degree(body_angle.degree()) + if n_turn > 0: + angle_diff = max(0, angle_diff) + if (target_angle - body_angle).degree() > 0: + result_dash_angle.set_degree((target_angle - angle_diff).degree()) + else: + result_dash_angle.set_degree((target_angle + angle_diff).degree()) + + log.sw_log().intercept().add_text( + f"self pred short cycle {cycle}: " + f"turn={n_turn}, " + f"turn_margin={turn_margin}" + f"turn_momment={result_dash_angle.degree() - body_angle.degree()}" + f"first_angle_diff={target_angle.degree() - body_angle.degree()}" + f"final_angle={angle_diff}" + f"dash_angle={result_dash_angle}") + return n_turn + + def predict_long_step(self, max_cycle: int, save_recovery: bool, self_cache: list): + if DEBUG: + log.sw_log().intercept().add_text('=========================== Long Step =============================') + tmp_cache = [] + SP = ServerParam.i() + wm = self._wm + ball = wm.ball() + me = wm.self() + ptype = me.player_type() + + # calc y distance from ball line + ball_to_self = me.pos() - ball.pos() + ball_to_self.rotate(-ball.vel().th()) + start_cycle = int(ceil((ball_to_self.abs_y() + - ptype.kickable_area() + - 0.2) + / ptype.real_speed_max())) + # if start_cycle <= self._max_short_step: + # start_cycle = self._max_short_step + 1 + + ball_pos = ball.inertia_point(start_cycle - 1) + ball_vel = ball.vel() * SP.ball_decay() ** (start_cycle - 1) + found = False + + max_loop = max_cycle + tmp_cache = [] + for cycle in range(start_cycle, max_loop): + ball_pos += ball_vel + ball_vel *= SP.ball_decay() + if DEBUG: + log.sw_log().intercept().add_text(f'$$$ c: {cycle} b: {ball_pos}') + if ball_pos.abs_x() > SP.pitch_half_length() + 10 or \ + ball_pos.abs_y() > SP.pitch_half_width() + 10: + log.sw_log().intercept().add_text('-------> out of field') + log.sw_log().intercept().add_circle(cx=ball_pos.x(), cy=ball_pos.y(), r=0.3, color='r') + break + + goalie_mode = self.is_goalie_mode(ball_pos) + control_area = ptype.catchable_area() if goalie_mode else ptype.kickable_area() + + # reach point is to far never reach + if control_area + ptype.real_speed_max() * cycle < me.pos().dist(ball_pos): + log.sw_log().intercept().add_text('-------> to far never reach') + log.sw_log().intercept().add_circle(cx=ball_pos.x(), cy=ball_pos.y(), r=0.3, color='r') + continue + + res, n_turn, back_dash, result_recovery = self.can_reach_after_turn_long_dash(cycle, + ball_pos, + control_area, + save_recovery, + self_cache) + if res: + log.sw_log().intercept().add_text(f'-------> {res} turn:{n_turn} back_dash: {back_dash}') + if not found: + max_loop = min(max_cycle, cycle + 10) + found = True + log.sw_log().intercept().add_circle(cx=ball_pos.x(), cy=ball_pos.y(), r=0.3, color='green') + else: + log.sw_log().intercept().add_text('-------> res not found') + log.sw_log().intercept().add_circle(cx=ball_pos.x(), cy=ball_pos.y(), r=0.3, color='red') + + # not registered any intercept + if not found and save_recovery: + self.predict_final(max_cycle, self_cache) + if len(self_cache) == 0: + self.predict_final(max_cycle, self_cache) + + def can_reach_after_turn_long_dash(self, + cycle, + ball_pos, + control_area, + save_recovery, + self_cache) -> tuple: + dash_angle = self._wm.self().body() + result_recovery = 0 + n_turn, dash_angle, back_dash = self.predict_turn_cycle(cycle, + ball_pos, + control_area, + dash_angle) + if n_turn > cycle: + return False, n_turn, back_dash, result_recovery + + res, result_recovery = self.can_reach_after_dash(n_turn, max(0, cycle - n_turn), + ball_pos, control_area, + save_recovery, + dash_angle, back_dash, + result_recovery, + self_cache) + return res, n_turn, back_dash, result_recovery + + def predict_turn_cycle(self, cycle: int, + ball_pos: Vector2D, + control_area: float, + dash_angle: AngleDeg) -> tuple: + wm = self._wm + ptype = wm.self().player_type() + + back_dash = False + n_turn = 0 + + inertia_pos = wm.self().inertia_point(cycle) + target_rel = ball_pos - inertia_pos + target_angle = target_rel.th() + + angle_diff = (target_angle - dash_angle).degree() + diff_is_positive = True if angle_diff > 0 else False + angle_diff = abs(angle_diff) + + target_dist = target_rel.r() + turn_margin = 180 + control_buf = control_area - 0.25 + control_buf = max(0.5, control_buf) + if control_buf < target_dist: + turn_margin = AngleDeg.asin_deg(control_buf / target_dist) + turn_margin = max(turn_margin, self._min_turn_thr) + + # check back dash possibility + if self.can_back_dash_chase(cycle, target_dist, angle_diff): + back_dash = True + dash_angle += 180 + angle_diff = 180 - angle_diff + + # predict turn cycles + max_moment = ServerParam.i().max_moment() * (1 - ServerParam.i().player_rand()) + player_speed = wm.self().vel().r() + while angle_diff > turn_margin: + max_turnable = ptype.effective_turn(max_moment, player_speed) + angle_diff -= max_turnable + player_speed *= ptype.player_decay() + n_turn += 1 + + # update dash angle + if n_turn > 0: + angle_diff = max(0.0, angle_diff) + dash_angle = target_angle + (angle_diff + if diff_is_positive + else -angle_diff) + + return n_turn, dash_angle, back_dash + + def can_back_dash_chase(self, cycle: int, + target_dist: float, + angle_diff: float): + wm = self._wm + + if angle_diff < self._back_dash_thr_angle: + return False + + if (not wm.self().goalie() + or wm.last_kicker_side() == wm.our_side()) and cycle >= 5: + return False + + if (wm.self().goalie() + and wm.last_kicker_side() != wm.our_side() + and cycle >= 5): + if cycle >= 15: + return False + + goal = Vector2D(-ServerParam.i().pitch_half_length(), 0) + bpos = wm.ball().inertia_point(cycle) + if goal.dist(bpos) > 21: + return False + + # check stamina consumed by one step + total_consume = -ServerParam.i().min_dash_power() * 2 * cycle + total_recover = (wm.self().player_type().stamina_inc_max() + * wm.self().recovery() + * (cycle - 1)) + result_stamina = (wm.self().stamina() + - total_consume + + total_recover) + + if result_stamina < ServerParam.i().recover_dec_thr_value() + 205: + return False + + return True + + def can_reach_after_dash(self, + n_turn: int, + n_dash: int, + ball_pos: Vector2D, + control_area: float, + save_recovery: bool, + dash_angle: AngleDeg, + back_dash: bool, + result_recovery, + self_cache: list): + PLAYER_NOISE_RATE = 1 - ServerParam.i().player_rand() * 0.01 + MAX_POWER = ServerParam.i().max_dash_power() + + SP = ServerParam.i() + wm = self._wm + ptype = wm.self().player_type() + + my_inertia = wm.self().inertia_point(n_turn + n_dash) + recover_dec_thr = SP.recover_dec_thr() * SP.stamina_max() + + dash_angle_minus = -dash_angle + ball_rel = (ball_pos - wm.self().pos()).rotated_vector(dash_angle_minus) + ball_noise = (wm.ball().pos().dist(ball_pos) + * SP.ball_rand() + * 0.5) + noised_ball_x = ball_rel.x() + ball_noise + + # prepare loop variables + # ORIGIN: first player pos. + # X - axis: dash angle + tmp_pos = ptype.inertia_travel(wm.self().vel(), n_turn) + tmp_pos.rotate(dash_angle_minus) + + tmp_vel = wm.self().vel() + tmp_vel *= ptype.player_decay() ** n_turn + tmp_vel.rotate(dash_angle_minus) + + stamina_model = wm.self().stamina_model() + stamina_model.simulate_waits(ptype, n_turn) + + prev_effort = stamina_model.effort() + dash_power_abs = MAX_POWER + # only consider about x of dash accel vector, + # because current orientation is player's dash angle (included back dash case) + # NOTE: dash_accel_x must be positive value. + dash_accel_x = dash_power_abs * ptype.dash_rate(stamina_model.effort()) + + can_over_speed_max = ptype.can_over_speed_max(dash_power_abs, + stamina_model.effort()) + first_dash_power = dash_power_abs * (-1 if back_dash else 1) + for i in range(n_dash): + # update dash power and accel + available_power = (max(0, stamina_model.stamina() - recover_dec_thr) + if save_recovery + else stamina_model.stamina() + ptype.extra_stamina()) + if back_dash: + available_power *= 0.5 + available_power = min_max(0, available_power, MAX_POWER) + + must_update_power = False + if (available_power < dash_power_abs + or stamina_model.effort() < prev_effort + or (not can_over_speed_max + and dash_power_abs < available_power)): + must_update_power = True + + if must_update_power: + dash_power_abs = available_power + dash_accel_x = dash_power_abs * ptype.dash_rate(stamina_model.effort()) + can_over_speed_max = ptype.can_over_speed_max(dash_power_abs, + stamina_model.effort()) + if i == 0: + first_dash_power = dash_power_abs * (-1 if back_dash else 1) + + # update vel + tmp_vel.add_x(dash_accel_x) + # power conservation, update accel magnitude and dashpower + if can_over_speed_max and tmp_vel.r2() > ptype.player_speed_max2(): + tmp_vel.sub_x(dash_accel_x) + max_dash_x = (ptype.player_speed_max2() - tmp_vel.y() ** 2) ** 0.5 + + dash_accel_x = max_dash_x - tmp_vel.x() + dash_power_abs = abs(dash_accel_x / ptype.dash_rate(stamina_model.effort())) + tmp_vel.add_x(dash_accel_x) + can_over_speed_max = ptype.can_over_speed_max(dash_power_abs, + stamina_model.effort()) + + tmp_pos += tmp_vel + tmp_vel *= ptype.player_decay() + stamina_model.simulate_dash(ptype, dash_power_abs * (-1 if back_dash else 1)) + + if tmp_pos.x() * PLAYER_NOISE_RATE + 0.1 > noised_ball_x: + result_recovery = stamina_model.recovery() + inertia_pos = ptype.inertia_point(tmp_pos, tmp_vel, n_dash - (i + 1)) + my_final_pos = wm.self().pos() + tmp_pos.rotate(dash_angle) + if my_inertia.dist2(my_final_pos) > 0.01: + my_final_pos = Line2D(p1=my_inertia, p2=my_final_pos).projection(ball_pos) + stamina_model.simulate_waits(ptype, n_dash - (i + 1)) + mode = (InterceptInfo.Mode.EXHAUST + if stamina_model.recovery() < wm.self().recovery() + and not stamina_model.capacity_is_empty() + else InterceptInfo.Mode.NORMAL) + self_cache.append(InterceptInfo(mode, + n_turn, n_dash, + first_dash_power, 180.0 if back_dash else 0, + my_final_pos, + my_final_pos.dist(ball_pos), + stamina_model.stamina())) + return True, result_recovery + + player_travel = tmp_pos.r() + player_noise = player_travel * SP.player_rand() * 0.5 + last_ball_dist = ball_rel.dist(tmp_pos) + buf = 0.2 + + buf += player_noise + buf += ball_noise + + if last_ball_dist < max(control_area - 0.225, control_area - buf): + my_final_pos = wm.self().pos() + tmp_pos.rotate(dash_angle) + result_recovery = stamina_model.recovery() + mode = (InterceptInfo.Mode.EXHAUST + if stamina_model.recovery() < wm.self().recovery() + and not stamina_model.capacity_is_empty() + else InterceptInfo.Mode.NORMAL) + self_cache.append(InterceptInfo(mode, + n_turn, n_dash, + first_dash_power, 180.0 if back_dash else 0, + my_final_pos, my_final_pos.dist(ball_pos), + stamina_model.stamina())) + return True, result_recovery + return False, result_recovery + + def predict_final(self, max_cycle: int, self_cache: list): + wm = self._wm + me = wm.self() + ptype = me.player_type() + + my_final_pos = me.inertia_point(100) + ball_final_pos = wm.ball().inertia_point(100) + goalie_mode = self.is_goalie_mode(ball_final_pos) + control_area = ptype.catchable_area() - 0.15 if goalie_mode else ptype.kickable_area() + dash_angle = me.body() + n_turn, dash_angle, back_dash = self.predict_turn_cycle(100, + ball_final_pos, + control_area, + dash_angle) + dash_dist = my_final_pos.dist(ball_final_pos) + dash_dist -= control_area + n_dash = ptype.cycles_to_reach_distance(dash_dist) + + if max_cycle > n_turn + n_dash: + n_dash = max_cycle - n_turn + + stamina_model = me.stamina_model() + stamina_model.simulate_waits(ptype, n_turn) + stamina_model.simulate_dashes(ptype, n_dash, ServerParam.i().max_dash_power()) + self_cache.append(InterceptInfo(InterceptInfo.Mode.NORMAL, + n_turn, n_dash, + ServerParam.i().max_dash_power(), 0, + ball_final_pos, + 0, + stamina_model.stamina())) diff --git a/keepaway/lib/action/intercept_table.py b/keepaway/lib/action/intercept_table.py new file mode 100644 index 00000000..4e5fe679 --- /dev/null +++ b/keepaway/lib/action/intercept_table.py @@ -0,0 +1,301 @@ +from typing import Union +from keepaway.lib.action.intercept_info import InterceptInfo +from keepaway.lib.action.intercept_player import PlayerIntercept +from keepaway.lib.action.intercept_self import SelfIntercept +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.player.object_player import PlayerObject +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class InterceptTable: + DEBUG = True + + def __init__(self): + self._last_update_time: GameTime = GameTime(-10, -100) + self._max_cycle: int = 30 + + self._ball_cache: list[Vector2D] = [] + self._self_cache: list[Vector2D] = [] + + self._self_reach_cycle = 1000 + self._self_exhaust_reach_cycle = 1000 + self._teammate_reach_cycle = 1000 + self._second_teammate_reach_cycle = 1000 + self._goalie_reach_cycle = 1000 + self._opponent_reach_cycle = 1000 + self._second_opponent_reach_cycle = 1000 + + self._fastest_teammate: Union[None, PlayerObject] = None + self._second_teammate: Union[None, PlayerObject] = None + self._fastest_opponent: Union[None, PlayerObject] = None + self._second_opponent: Union[None, PlayerObject] = None + + def self_reach_cycle(self): + return self._self_reach_cycle + + def fastest_teammate(self): + return self._fastest_teammate + + def self_exhaust_reach_cycle(self): + return self._self_exhaust_reach_cycle + + def teammate_reach_cycle(self): + return self._teammate_reach_cycle + + def second_teammate_reach_cycle(self): + return self._second_teammate_reach_cycle + + def goalie_reach_cycle(self): + return self._goalie_reach_cycle + + def opponent_reach_cycle(self): + return self._opponent_reach_cycle + + def second_opponent_reach_cycle(self): + return self._second_opponent_reach_cycle + + def update(self, wm: 'WorldModel'): + if InterceptTable.DEBUG: + log.sw_log().intercept().add_text( f"(intercept update) started {'#'*20}") + + if self._last_update_time == wm.time(): # TODO uncomment it + if InterceptTable.DEBUG: + log.sw_log().intercept().add_text( "(intercept update) intercept updated before! it called agein") + return + + self._last_update_time = wm.time().copy() + self.clear() + + if wm.game_mode().type() == GameModeType.TimeOver or \ + wm.game_mode().type() == GameModeType.BeforeKickOff: + if InterceptTable.DEBUG: + log.sw_log().intercept().add_text( "(intercept update) GAMEMODE RETURN") + return + + if not wm.self().pos().is_valid() or not wm.ball().pos().is_valid(): + log.sw_log().intercept().add_text( "(intercept update) self pos or ball pos is not valid") + return + + self.create_ball_cache(wm) + self.predict_self(wm) + self.predict_opponent(wm) + self.predict_teammate(wm) + + if self._fastest_teammate is not None: + log.sw_log().intercept().add_text( + f"Intercept Teammate, fastest reach step={self._teammate_reach_cycle}" + f"teammate {self._fastest_teammate.unum()} {self._fastest_teammate.pos()}") + if self._second_teammate is not None: + log.sw_log().intercept().add_text( + f"Intercept Teammate2nd, fastest reach step={self._second_teammate_reach_cycle}" + f"teammate {self._second_teammate.unum()} {self._second_teammate.pos()}") + if self._fastest_opponent is not None: + log.sw_log().intercept().add_text( + f"Intercept Opponent, fastest reach step={self._opponent_reach_cycle}" + f"teammate {self._fastest_opponent.unum()} {self._fastest_opponent.pos()}") + if self._second_opponent is not None: + log.sw_log().intercept().add_text( + f"Intercept Opponent2nd, fastest reach step={self._second_opponent_reach_cycle}" + f"teammate {self._second_opponent.unum()} {self._second_opponent.pos()}") + + def clear(self): + self._ball_cache = [] + + self._self_reach_cycle = 1000 + self._self_exhaust_reach_cycle = 1000 + self._teammate_reach_cycle = 1000 + self._second_teammate_reach_cycle = 1000 + self._goalie_reach_cycle = 1000 + self._opponent_reach_cycle = 1000 + self._second_opponent_reach_cycle = 1000 + + self._fastest_teammate = None + self._second_teammate = None + self._fastest_opponent = None + self._second_opponent = None + + self._self_cache = [] + + def create_ball_cache(self, wm): + SP = ServerParam.i() + # pitch_max_x = SP.pitch_half_length() + 5 + # pitch_max_y = SP.pitch_half_width() + 5 + + pitch_max_x = SP.keepaway_length()/2 + 5 + pitch_max_y = SP.keepaway_width()/2 + 5 + + ball_decay = SP.ball_decay() + + ball_pos: Vector2D = wm.ball().pos() + ball_vel: Vector2D = wm.ball().vel() + + self._ball_cache.append(ball_pos) + + if wm.self().is_kickable(): + return + + for cycle in range(1, self._max_cycle + 1): + ball_pos += ball_vel + ball_vel *= ball_decay + self._ball_cache.append(ball_pos.copy()) + + if cycle >= 5 and ball_vel.r2() < 0.01 ** 2: + # ball stopped + break + + if ball_pos.abs_x() > pitch_max_x or ball_pos.abs_y() > pitch_max_y: + # out of pitch + break + + if len(self._ball_cache) == 1: + self._ball_cache.append(ball_pos.copy()) + + for b in self._ball_cache: + log.sw_log().intercept().add_circle( r=0.1, center=b, fill=True, color=Color(string="blue")) + + def predict_self(self, wm): + if wm.self().is_kickable(): + log.sw_log().intercept().add_text( "Intercept predict self already kickable") + return + + max_cycle = min(self._max_cycle, len(self._ball_cache)) + predictor = SelfIntercept(wm, self._ball_cache) + predictor.predict(max_cycle, self._self_cache) + + if len(self._self_cache) == 0: + log.sw_log().intercept().add_text( + "Intercept self, self cache is empty") + return + + min_cycle = self._self_reach_cycle + exhaust_min_cycle = self._self_exhaust_reach_cycle + + for it in self._self_cache: + if it.mode() == InterceptInfo.Mode.NORMAL: + if it.reach_cycle() < min_cycle: + min_cycle = it.reach_cycle() + break + elif it.mode() == InterceptInfo.Mode.EXHAUST: + if it.reach_cycle() < exhaust_min_cycle: + exhaust_min_cycle = it.reach_cycle() + break + + log.sw_log().intercept().add_text( + f"Intercept self, solution size={len(self._self_cache)}") + self._self_reach_cycle = min_cycle + self._self_exhaust_reach_cycle = exhaust_min_cycle + + def predict_opponent(self, wm: 'WorldModel'): + opponents = wm.opponents_from_ball() + + if wm.exist_kickable_opponents(): + log.sw_log().intercept().add_text( + "Intercept Opponent. exits kickable opponent") + self._opponent_reach_cycle = 0 + for o in opponents: + if o.is_ghost() or o.pos_count() > wm.ball().pos_count() + 1: + continue + self._fastest_opponent = o + log.sw_log().intercept().add_text( + f"fastest opp {self._fastest_opponent}") + break + return + + min_cycle = 1000 + second_min_cycle = 1000 + + predictor = PlayerIntercept(wm, self._ball_cache) + for it in opponents: + if it.pos_count() >= 15: + continue + + player_type = it.player_type() + if player_type is None: + log.sw_log().intercept().add_text( + f"intercept opponents faild to get player{it.unum()} type") + continue + cycle = predictor.predict(it, player_type, + second_min_cycle) + log.sw_log().intercept().add_text( + f"opp{it.unum()} {it.pos()} " + f"type={player_type.id()} cycle={cycle}") + + if cycle < second_min_cycle: + second_min_cycle = cycle + self._second_opponent = it + + if second_min_cycle < min_cycle: + # swap :) + min_cycle, second_min_cycle = second_min_cycle, min_cycle + self._fastest_opponent, self._second_opponent = self._second_opponent, self._fastest_opponent + + if self._second_opponent is not None and second_min_cycle < 1000: + self._second_opponent_reach_cycle = second_min_cycle + + if self._fastest_opponent is not None and min_cycle < 1000: + self._opponent_reach_cycle = min_cycle + + def predict_teammate(self, wm: 'WorldModel'): + teammates = wm.teammates_from_ball() + + if wm.exist_kickable_teammates(): + log.sw_log().intercept().add_text( + "Intercept Teammates. exits kickable teammate") + self._teammate_reach_cycle = 0 + for t in teammates: + if t.is_ghost() or t.pos_count() > wm.ball().pos_count() + 1: + continue + self._fastest_teammate = t + log.sw_log().intercept().add_text( + f"fastest tm {self._fastest_teammate}") + break + return + + min_cycle = 1000 + second_min_cycle = 1000 + + predictor = PlayerIntercept(wm, self._ball_cache) + for it in teammates: + if it.pos_count() >= 10: + continue + + player_type = it.player_type() + if player_type is None: + log.sw_log().intercept().add_text( + f"intercept teammate faild to get player{it.unum()} type") + continue + + cycle = predictor.predict(it, player_type, + second_min_cycle) + log.sw_log().intercept().add_text( + f"tm{it.unum()} {it.pos()} " + f"type={player_type.id()} cycle={cycle}") + + if it.goalie(): + self._goalie_reach_cycle = cycle + elif cycle < second_min_cycle: + second_min_cycle = cycle + self._second_teammate = it + + if second_min_cycle < min_cycle: + # swap :) + min_cycle, second_min_cycle = second_min_cycle, min_cycle + self._fastest_teammate, self._second_teammate = self._second_teammate, self._fastest_teammate + + if self._second_teammate is not None and second_min_cycle < 1000: + self._second_teammate_reach_cycle = second_min_cycle + + if self._fastest_teammate is not None and min_cycle < 1000: + self._teammate_reach_cycle = min_cycle + + def self_cache(self) -> list[Vector2D]: + return self._self_cache diff --git a/keepaway/lib/action/kick_table.py b/keepaway/lib/action/kick_table.py new file mode 100644 index 00000000..be6d5204 --- /dev/null +++ b/keepaway/lib/action/kick_table.py @@ -0,0 +1,1240 @@ +""" + \ file kick_table.py + \ brief kick table class File to generate smart kick. +""" +import functools + +from keepaway.lib.debug.debug import log +# from typing import List +# from enum import Enum + +from keepaway.lib.debug.level import Level +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.server_param import ServerParam +from pyrusgeom.soccer_math import * +from pyrusgeom.ray_2d import Ray2D +from pyrusgeom.circle_2d import Circle2D +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.rect_2d import Rect2D +from pyrusgeom.size_2d import Size2D +from pyrusgeom.math_values import EPSILON +from keepaway.lib.rcsc.game_time import * + + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +""" + \ brief compare operation function + \ param item1 left hand side variable + \ param item2 right hand side variable + \ return compared result +""" + + +def table_cmp(item1, item2) -> bool: + if item1.max_speed_ == item2.max_speed_: + return item1.power_ < item2.power_ + + return item1.max_speed_ > item2.max_speed_ + + +def sequence_cmp(item1, item2) -> bool: + return item1.score_ > item2.score_ + + +""" + \ enum Flag + \ brief status bit flags +""" + +# class Flag(Enum): +SAFETY: hex = 0x0000 +NEXT_TACKLEABLE: hex = 0x0001 +NEXT_KICKABLE: hex = 0x0002 +TACKLEABLE: hex = 0x0004 +KICKABLE: hex = 0x0008 +SELF_COLLISION: hex = 0x0010 +RELEASE_INTERFERE: hex = 0x0020 +MAYBE_RELEASE_INTERFERE: hex = 0x0040 +OUT_OF_PITCH: hex = 0x0080 +KICK_MISS_POSSIBILITY: hex = 0x0100 +NOT_SAFETY: hex = 0xffff +NOT_NEXT_TACKLEABLE: hex = 0xfffe +NOT_NEXT_KICKABLE: hex = 0xfffd +NOT_TACKLEABLE: hex = 0xfffb +NOT_KICKABLE: hex = 0xfff7 +NOT_SELF_COLLISION: hex = 0xffef +NOT_RELEASE_INTERFERE: hex = 0xffdf +NOT_MAYBE_RELEASE_INTERFERE: hex = 0xffbf +NOT_OUT_OF_PITCH: hex = 0xff7f +NOT_KICK_MISS_POSSIBILITY: hex = 0xfeff + +NEAR_SIDE_RATE = 0.3 # < kickable margin rate for the near side sub-target +MID_RATE = 0.5 # < kickable margin rate for the middle distance sub-target +FAR_SIDE_RATE = 0.7 # < kickable margin rate for the far side sub-target +MAX_DEPTH = 2 +STATE_DIVS_NEAR = 8 +STATE_DIVS_MID = 12 +STATE_DIVS_FAR = 15 +# STATE_DIVS_NEAR = 4 +# STATE_DIVS_MID = 8 +# STATE_DIVS_FAR = 8 +NUM_STATE = STATE_DIVS_NEAR + STATE_DIVS_MID + STATE_DIVS_FAR + +DEST_DIR_DIVS: int = 72 # step: 5 degree + +MAX_TABLE_SIZE: int = 128 + +""" + \ class State + \ brief class to represent a kick intermediate state +""" + + +class State: + # < index of self point + # < distance from self + # < position relative to player's body + # < kick rate + # < status bit flag + + """ + \ brief construct an illegal state object + OR + \ brief construct a legal state object. flag is set to SAFETY. + \ param index index number of self state + \ param dist distance from self + \ param pos global position + \ param kick_rate kick rate at self state + """ + + def __init__(self, *args): + if len(args) == 0: + self.index_ = -1 + self.dist_ = 0.0 + self.pos_ = Vector2D(0, 0) + self.kick_rate_ = 0.0 + self.flag_ = NOT_SAFETY + else: + self.index_: int = args[0] + self.dist_: float = args[1] + self.pos_: Vector2D = args[2].copy() + self.kick_rate_: float = args[3] + self.flag_ = SAFETY + + +""" + \ class Path + \ brief used as a heuristic knowledge. path representation between two states +""" + + +class Path: + # < index of origin state + # < index of destination state + # < reachable ball max speed + # < kick power to generate max_speed_ + + """ + \ brief construct a kick path object + \ param origin index of origin state + \ param destination index of destination state + """ + + def __init__(self, origin=0, destination=0): + self.origin_ = origin + self.dest_ = destination + self.max_speed_ = 0.0 + self.power_ = 1000.0 + + def __gt__(self, other): + if self.max_speed_ == other.max_speed_: + return self.power_ < other.power_ + + return self.max_speed_ > other.max_speed_ + + def copy(self): + return Path(self.origin_, self.dest_) + + +""" + \ class Sequence + \ brief simulated kick sequence + """ + + +class Sequence: + # < safety level flags. usually the combination of State flags + # < ball positions + # < released ball speed + # < estimated last kick power + # < evaluated score of self sequence + + """ + \ brief construct an illegal sequence object + OR + \ brief copy constructor + \ param arg another instance + """ + + def __init__(self, *args): + if len(args) == 0: + self.flag_ = 0x0000 + self.pos_list_ = [] + self.speed_ = 0.0 + self.power_ = 10000.0 + self.score_ = 0.0 + if len(args) == 1: + self.flag_ = args[0].flag_ + self.pos_list_ = args[0].pos_list_ + self.speed_ = args[0].speed_.copy() + self.power_ = args[0].power_.copy() + self.score_ = args[0].score_ + + def __repr__(self): + return "[{} Step, next_pos = {}, speed = {}, pos_list = {}, flag = {}, score [{}]]".format( + len(self.pos_list_), self.pos_list_[0], self.speed_, self.pos_list_, self.flag_, self.score_) + + +""" + \ brief calculate the distance of near side sub-target + \ param player_type calculated PlayerType + \ return distance from the center of the player +""" + + +def calc_near_dist(player_type: PlayerType): + # 0.3 + 0.6*0.3 + 0.085 = 0.565 + # near: 0.3 + 0.7*0.3 + 0.085 = 0.595 + # 0.3 + 0.8*0.3 + 0.085 = 0.625 + return bound(player_type.player_size() + ServerParam.i().ball_size() + 0.1, + (player_type.player_size() + + (player_type.kickable_margin() * NEAR_SIDE_RATE) + + ServerParam.i().ball_size()), + player_type.kickable_area() - 0.2) + + +""" + \ brief calculate the distance of middle distance sub-target + \ param player_type calculated PlayerType + \ return distance from the center of the player +""" + + +def calc_mid_dist(player_type: PlayerType): + # 0.3 + 0.6*0.5 + 0.085 = 0.705 + # mid: 0.3 + 0.7*0.5 + 0.085 = 0.735 + # 0.3 + 0.8*0.5 + 0.085 = 0.765 + return bound(player_type.player_size() + ServerParam.i().ball_size() + 0.1, + (player_type.player_size() + + (player_type.kickable_margin() * MID_RATE) + + ServerParam.i().ball_size()), + player_type.kickable_area() - 0.2) + + +""" + \ brief calculate the distance of far side sub-target + \ param player_type calculated PlayerType + \ return distance from the center of the player +""" + + +def calc_far_dist(player_type: PlayerType): + # 0.3 + 0.6*0.7 + 0.085 = 0.865 (=0.985-0.12 . 0.785) + # far: 0.3 + 0.7*0.7 + 0.085 = 0.875 (=1.085-0.21) + # 0.3 + 0.8*0.7 + 0.085 = 0.945 (=1.185-0.24) + + # 0.3 + 0.6*0.68 + 0.085 = 0.793 (=0.985-0.192 . 0.760) + # far: 0.3 + 0.7*0.68 + 0.085 = 0.861 (=1.085-0.224 . 0.860) + # 0.3 + 0.8*0.68 + 0.085 = 0.929 (=1.185-0.256) + + # 0.3 + 0.6*0.675 + 0.085 = 0.79 (=0.985-0.195) + # far: 0.3 + 0.7*0.675 + 0.085 = 0.8575 (=1.085-0.2275) + # 0.3 + 0.8*0.675 + 0.085 = 0.925 (=1.185-0.26) + + return bound(player_type.player_size() + ServerParam.i().ball_size() + 0.1, + (player_type.player_size() + + (player_type.kickable_margin() * FAR_SIDE_RATE) + + ServerParam.i().ball_size()), + player_type.kickable_area() - 0.2) + # player_type.kickable_area() - 0.22 ) + + +""" + \ brief calculate maximum velocity for the target angle by one step kick with krate and ball_vel + \ param target_angle target angle of the next ball velocity + \ param krate current kick rate + \ param ball_vel current ball velocity + \ return maximum velocity for the target angle + """ + + +def calc_max_velocity(target_angle: AngleDeg, + krate, + ball_vel: Vector2D): + ball_speed_max2 = ServerParam.i().ball_speed_max() ** 2 + max_accel = min(ServerParam.i().max_power() * krate, + ServerParam.i().ball_accel_max()) + + desired_ray = Ray2D(Vector2D(0.0, 0.0), target_angle) + next_reachable_circle = Circle2D(ball_vel, max_accel) + + num = next_reachable_circle.intersection(desired_ray) + if len(num) == 0: + return Vector2D(0.0, 0.0) + + vel1 = num[0] + + if len(num) == 1: + if vel1.r2() > ball_speed_max2: + # next inertia ball point is within reachable circle. + if next_reachable_circle.contains(Vector2D(0.0, 0.0)): + # can adjust angle at least + vel1.set_length(ServerParam.i().ball_speed_max()) + + else: + # failed + vel1.assign(0.0, 0.0) + + return vel1 + + vel2 = num[1] + # + # num == 2 + # ball reachable circle does not contain the current ball pos. + + length1 = vel1.r2() + length2 = vel2.r2() + + if length1 < length2: + vel1, vel2 = vel2, vel1 + length1, length2 = length2, length1 + + if length1 > ball_speed_max2: + if length2 > ball_speed_max2: + # failed + vel1.assign(0.0, 0.0) + + else: + vel1.set_length(ServerParam.i().ball_speed_max()) + + return vel1 + + +class _KickTable: + debug_print_DEBUG: bool = False # debug_prints IN KICKTABLE + + def __init__(self): + self._old_player_type_id = -1 + self._player_size = 0.0 + self._kickable_margin = 0.0 + self._ball_size = 0.0 + self._state_cache = [] + for i in range(MAX_DEPTH): + self._state_cache.append([]) + for j in range(NUM_STATE): + self._state_cache[i].append(0.0) + # not static state list + self._state_list = [] + self._tables = [] + + self._current_state = State() + + self._state_cache = [[State()]] * DEST_DIR_DIVS + + self._candidates = [] # : list[Sequence] = [] + + """ + \ brief create heuristic table + \ return result of table creation + """ + + def create_tables(self, player_type: PlayerType): + if player_type.id() == self._old_player_type_id: + return + self._old_player_type_id = player_type.id() + player_type = PlayerType() # default type + + if (math.fabs(self._player_size - player_type.player_size()) < EPS + and math.fabs(self._kickable_margin - player_type.kickable_margin()) < EPS + and math.fabs(self._ball_size - ServerParam.i().ball_size()) < EPS): + return False + self._player_size = player_type.player_size() + self._kickable_margin = player_type.kickable_margin() + self._ball_size = ServerParam.i().ball_size() + + self.create_state_list(player_type) + + angle_step = 360.0 / DEST_DIR_DIVS + angles = [AngleDeg(-180 + i * angle_step) for i in range(DEST_DIR_DIVS)] + # TODO this functions should be checked + from multiprocessing import Pool + pool = Pool(4) + self._tables = pool.map(self.create_table, angles) + + # for i in range(len(angles)): + # self._tables.append(self.create_table(angles[i])) + return True + + """ + \ brief create static state list + """ + + def create_state_list(self, player_type: PlayerType): + near_dist = calc_near_dist(player_type) + mid_dist = calc_mid_dist(player_type) + far_dist = calc_far_dist(player_type) + + near_angle_step = 360.0 / STATE_DIVS_NEAR + mid_angle_step = 360.0 / STATE_DIVS_MID + far_angle_step = 360.0 / STATE_DIVS_FAR + + index = 0 + self._state_list.clear() + + for near in range(STATE_DIVS_NEAR): + angle = AngleDeg(-180.0 + (near_angle_step * near)) + pos = Vector2D.polar2vector(near_dist, angle) + krate = player_type.kick_rate(near_dist, angle.degree()) + self._state_list.append(State(index, near_dist, pos, krate)) + index += 1 + + for mid in range(STATE_DIVS_MID): + angle = AngleDeg(-180.0 + (mid_angle_step * mid)) + pos = Vector2D.polar2vector(mid_dist, angle) + krate = player_type.kick_rate(mid_dist, angle.degree()) + self._state_list.append(State(index, mid_dist, pos, krate)) + index += 1 + + for far in range(STATE_DIVS_FAR): + angle = AngleDeg(-180.0 + (far_angle_step * far)) + pos = Vector2D.polar2vector(far_dist, angle) + krate = player_type.kick_rate(far_dist, angle.degree()) + self._state_list.append(State(index, far_dist, pos, krate)) + index += 1 + + """ + \ brief create table for angle + \ param angle target angle relative to body angle + \ param table reference to the container variable + """ + + def create_table(self, angle: AngleDeg): + # max_combination = NUM_STATE * NUM_STATE + res: list = [None] * (NUM_STATE * NUM_STATE) + max_state = len(self._state_list) + + for origin in range(max_state): + for dest in range(max_state): + vel = self._state_list[dest].pos_ - self._state_list[origin].pos_ + max_vel = calc_max_velocity(angle, + self._state_list[dest].kick_rate_, + vel) + accel = max_vel - vel + path = Path(origin, dest) + path.max_speed_ = max_vel.r() + path.power_ = accel.r() / self._state_list[dest].kick_rate_ + res[max_state * origin + dest] = path + res.sort(key=functools.cmp_to_key(table_cmp)) + if len(res) > MAX_TABLE_SIZE: + res = res[:MAX_TABLE_SIZE + 1] + return res + + + """ + \ brief update internal state + \ param world reference to the WorldModel + """ + + def update_state(self, world: 'WorldModel'): + + if KickTable.S_UPDATE_TIME == world.time(): + return + + KickTable.S_UPDATE_TIME = world.time().copy() + + self.create_state_cache(world) + + """ + \ brief implementation of the state update + \ param world reference to the WorldModel + """ + + def create_state_cache(self, world: 'WorldModel'): + + param = ServerParam.i() + # pitch = Rect2D(Vector2D(- param.pitch_half_length(), - param.pitch_half_width()), Size2D(param.pitch_length(), + # param.pitch_width())) + ## TODO - changed to keepaway sized pitch + pitch = world.keepaway_rect() + + self_type = world.self().player_type() + near_dist = calc_near_dist(self_type) + mid_dist = calc_mid_dist(self_type) + far_dist = calc_far_dist(self_type) + + rpos = world.ball().rpos() + rpos.rotate(- world.self().body()) + + dist = rpos.r() + angle = rpos.th() + + if math.fabs(dist - near_dist) < math.fabs(dist - far_dist): + dir_div = STATE_DIVS_NEAR + else: + dir_div = STATE_DIVS_FAR + + self._current_state.index_ = (round(dir_div * round(angle.degree() + 180.0) / 360.0)) + if self._current_state.index_ >= dir_div: + self._current_state.index_ = 0 + + # self._current_state.pos_ = world.ball().rpos() + self._current_state.pos_ = world.ball().pos() + self._current_state.kick_rate_ = world.self().kick_rate() + + self.check_interfere_at(world, self._current_state) # 0 + + # + # create future state + # + + self_pos = world.self().pos() + self_vel = world.self().vel() + + for i in range(MAX_DEPTH): + self._state_cache[i].clear() + + self_pos += self_vel + self_vel *= self_type.player_decay() + + index = 0 + for near in range(STATE_DIVS_NEAR): + pos = self._state_list[index].pos_.copy() + krate = self_type.kick_rate(near_dist, pos.th().degree()) + + pos.rotate(world.self().body()) + pos.set_length(near_dist) + pos += self_pos + self._state_cache[i].append(State(index, near_dist, pos, krate)) + self.check_interfere_at(world, self._state_cache[i][-1]) # i + 1 + if not pitch.contains(pos): + self._state_cache[i][-1].flag_ |= OUT_OF_PITCH + + index += 1 + + for mid in range(STATE_DIVS_MID): + pos = self._state_list[index].pos_.copy() + krate = self_type.kick_rate(mid_dist, pos.th().degree()) + + pos.rotate(world.self().body()) + pos.set_length(mid_dist) + pos += self_pos + + self._state_cache[i].append(State(index, mid_dist, pos, krate)) + self.check_interfere_at(world, self._state_cache[i][-1]) # i + 1 + if not pitch.contains(pos): + self._state_cache[i][-1].flag_ |= OUT_OF_PITCH + + index += 1 + + for far in range(STATE_DIVS_FAR): + pos = self._state_list[index].pos_.copy() + krate = self_type.kick_rate(far_dist, pos.th().degree()) + + pos.rotate(world.self().body()) + pos.set_length(far_dist) + pos += self_pos + + self._state_cache[i].append(State(index, far_dist, pos, krate)) + self.check_interfere_at(world, self._state_cache[i][-1]) # i + 1 + if not pitch.contains(pos): + self._state_cache[i][-1].flag_ |= OUT_OF_PITCH + + index += 1 + + """ + \ brief update collision flag of state caches for the target_point and first_speed + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + """ + + def check_collision_after_release(self, world: 'WorldModel', target_point: Vector2D, first_speed): + + self_type = world.self().player_type() + + collide_dist2 = pow(self_type.player_size() + ServerParam.i().ball_size(), 2) + + self_pos = world.self().pos() + self_vel = world.self().vel() + + # check the release kick from current state + + self_pos += self_vel + self_vel *= self_type.player_decay() + + release_pos = (target_point - self._current_state.pos_) + release_pos.set_length(first_speed) + + if self_pos.dist2(release_pos) < collide_dist2: + + self._current_state.flag_ |= SELF_COLLISION + + else: + self._current_state.flag_ &= NOT_SELF_COLLISION + + # check the release kick from future state + + for i in range(MAX_DEPTH): + self_pos += self_vel + self_vel *= self_type.player_decay() + + for it in self._state_cache[i]: + release_pos = (target_point - it.pos_) + release_pos.set_length(first_speed) + + if self_pos.dist2(release_pos) < collide_dist2: + + it.flag_ |= SELF_COLLISION + else: + it.flag_ &= NOT_SELF_COLLISION + + """ + \ brief update interfere level at state + \ param world reference to the WorldModel + \ param cycle the cycle delay for state + \ param state reference to the State variable to be updated + """ + + @staticmethod + def check_interfere_at(world: 'WorldModel', + # cycle, # not needed + state: State): + # cycle += 0 Check need + penalty_area = Rect2D(Vector2D(ServerParam.i().their_penalty_area_line_x(), + - ServerParam.i().penalty_area_half_width()), + Size2D(ServerParam.i().penalty_area_length(), + ServerParam.i().penalty_area_width())) + flag = SAFETY + OFB = world.opponents_from_ball() + if len(OFB) == 0: + state.flag_ = SAFETY + return + for o in OFB: + if o is None or o.player_type() is None: + continue + if o.pos_count() >= 8: + continue + if o.is_ghost(): + continue + if o.dist_from_ball() > 10.0: + break + + opp_next = o.pos() + o.vel() + opp_dist = opp_next.dist(state.pos_) + + if o.is_tackling(): + if opp_dist < (o.playerTypePtr().player_size() + + ServerParam.i().ball_size()): + flag |= KICKABLE + break + + continue + + control_area = o.player_type().catchable_area() if ( + o.goalie() and penalty_area.contains(o.pos()) and penalty_area.contains(state.pos_ + )) else o.player_type().kickable_area() + + # + # check kick possibility + # + if not o.is_ghost() and o.pos_count() <= 2 and opp_dist < control_area + 0.15: + flag |= KICKABLE + break + + opp_body = o.body() if o.body_count() <= 1 else (state.pos_ - opp_next).th() + player_2_pos = Vector2D(state.pos_ - opp_next) + player_2_pos.rotate(- opp_body) + # + # check tackle possibility + # + tackle_dist = ServerParam.i().tackle_dist() if player_2_pos.x() > 0.0 else ServerParam.i().tackle_back_dist() + if tackle_dist > EPSILON: + tackle_prob = (pow(player_2_pos.abs_x() / tackle_dist, + ServerParam.i().tackle_exponent()) + pow( + player_2_pos.abs_y() / ServerParam.i().tackle_width(), + ServerParam.i().tackle_exponent())) + if tackle_prob < 1.0 and 1.0 - tackle_prob > 0.7: # success probability + flag |= TACKLEABLE + + # check kick or tackle possibility after dash + + player_type = o.player_type() + max_accel = (ServerParam.i().max_dash_power() + * player_type.dash_power_rate() + * player_type.effort_max()) + + if player_2_pos.abs_y() < control_area and ( + player_2_pos.abs_x() < max_accel or (player_2_pos + Vector2D(max_accel, 0.0)).r() < control_area or ( + player_2_pos - Vector2D(max_accel, 0.0)).r() < control_area): + flag |= NEXT_KICKABLE + elif (player_2_pos.abs_y() < ServerParam.i().tackle_width() * 0.7 + and player_2_pos.x() > 0.0 + and player_2_pos.x() - max_accel < ServerParam.i().tackle_dist() - 0.3): + flag |= NEXT_TACKLEABLE + + state.flag_ = flag + + """ + \ brief update interfere level after release kick for all states + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + \ brief update interfere level after release kick for each state + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + \ param cycle the cycle delay for state + \ param state reference to the State variable to be updated + """ + + def check_interfere_after_release(self, *args): # , **kwargs):): + if len(args) == 3: + world: 'WorldModel' = args[0] + target_point: Vector2D = args[1] + first_speed: float = args[2] + self.check_interfere_after_release(world, target_point, first_speed, 1, self._current_state) + + for i in range(MAX_DEPTH): + for state in self._state_cache[i]: + state.flag_ &= NOT_RELEASE_INTERFERE + state.flag_ &= NOT_MAYBE_RELEASE_INTERFERE + + self.check_interfere_after_release(world, target_point, first_speed, i + 2, state) + elif len(args) == 5: + world: 'WorldModel' = args[0] + target_point: Vector2D = args[1] + first_speed: float = args[2] + cycle: int = args[3] + state: State = args[4] + + penalty_area = Rect2D(Vector2D(ServerParam.i().their_penalty_area_line_x(), + - ServerParam.i().penalty_area_half_width()), + Size2D(ServerParam.i().penalty_area_length(), + ServerParam.i().penalty_area_width())) + + ball_pos = target_point - state.pos_ + ball_pos.set_length(first_speed) + ball_pos += state.pos_ + + OFB = world.opponents_from_ball() + if len(OFB) == 0: + state.flag_ = SAFETY + return + for o in OFB: + if o is None or o.player_type() is None: + continue + if o.pos_count() >= 8: + continue + if o.is_ghost(): + continue + if o.dist_from_ball() > 10.0: + break + opp_pos = o.inertia_point(cycle) + if not opp_pos.is_valid(): + opp_pos = o.pos() + o.vel() + + if o.is_tackling(): + if opp_pos.dist(ball_pos) < (o.player_type().player_size() + ServerParam.i().ball_size()): + state.flag_ |= RELEASE_INTERFERE + continue + control_area = o.player_type().catchable_area() if ( + o.goalie() and penalty_area.contains(o.pos()) and penalty_area.contains( + state.pos_)) else o.player_type().kickable_area() + + control_area += 0.1 + control_area2 = pow(control_area, 2) + + if ball_pos.dist2(opp_pos) < control_area2: + if cycle <= 1: + state.flag_ |= RELEASE_INTERFERE + + else: + state.flag_ |= RELEASE_INTERFERE + else: # if cycle <= 1 : + opp_body = o.body() if o.body_count() <= 1 else (ball_pos - opp_pos).th() + player_2_pos = ball_pos - opp_pos + player_2_pos.rotate(- opp_body) + + tackle_dist = ServerParam.i().tackle_dist() if player_2_pos.x() > 0.0 else ServerParam.i().tackle_back_dist() + if tackle_dist > EPSILON: + tackle_prob = (pow(player_2_pos.abs_x() / tackle_dist, + ServerParam.i().tackle_exponent()) + pow( + player_2_pos.abs_y() / ServerParam.i().tackle_width(), + ServerParam.i().tackle_exponent())) + if tackle_prob < 1.0 and 1.0 - tackle_prob > 0.8: # success probability + state.flag_ |= MAYBE_RELEASE_INTERFERE + player_type = o.player_type() + max_accel = (ServerParam.i().max_dash_power() + * player_type.dash_power_rate() + * player_type.effort_max()) * 0.8 + if (player_2_pos.abs_y() < control_area - 0.1 + and (player_2_pos.abs_x() < max_accel + or (player_2_pos + Vector2D(max_accel, 0.0)).r() < control_area - 0.25 + or (player_2_pos - Vector2D(max_accel, 0.0)).r() < control_area - 0.25)): + state.flag_ |= MAYBE_RELEASE_INTERFERE + + elif (player_2_pos.abs_y() < ServerParam.i().tackle_width() * 0.7 + and player_2_pos.x() - max_accel < ServerParam.i().tackle_dist() - 0.5): + state.flag_ |= MAYBE_RELEASE_INTERFERE + + """ + \ brief simulate one step kick + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + """ + + def simulate_one_step(self, world: 'WorldModel', target_point: Vector2D, first_speed): + if self._current_state.flag_ & SELF_COLLISION: + return False + + if self._current_state.flag_ & RELEASE_INTERFERE: + return False + + current_max_accel = min(self._current_state.kick_rate_ * ServerParam.i().max_power(), + ServerParam.i().ball_accel_max()) + target_vel = (target_point - world.ball().pos()) + target_vel.set_length(first_speed) + + accel = target_vel - world.ball().vel() + accel_r = accel.r() + if accel_r > current_max_accel: + max_vel = calc_max_velocity(target_vel.th(), + self._current_state.kick_rate_, + world.ball().vel()) + accel = max_vel - world.ball().vel() + self._candidates.append(Sequence()) + self._candidates[-1].flag_ = self._current_state.flag_ + self._candidates[-1].pos_list_.append(world.ball().pos() + max_vel) + self._candidates[-1].speed_ = max_vel.r() + self._candidates[-1].power_ = accel.r() / self._current_state.kick_rate_ + return False + + self._candidates.append(Sequence()) + self._candidates[-1].flag_ = self._current_state.flag_ + self._candidates[-1].pos_list_.append(world.ball().pos() + target_vel) + self._candidates[-1].speed_ = first_speed + self._candidates[-1].power_ = accel_r / self._current_state.kick_rate_ + """ + dlog.addText( Logger.KICK, + "ok__ 1 step: target_vel=(%.2f %.2f)%.3f required_accel=%.3f < max_accel=%.3f" + " kick_rate=%f power=%.1f", + target_vel.x, target_vel.y, + first_speed, + accel_r, + current_max_accel, + self._current_state.kick_rate_, + self._candidates[-1].power_ ) + """ + return True + + """ + \ brief simulate two step kicks + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + """ + + def simulate_two_step(self, world: 'WorldModel', target_point: Vector2D, first_speed): + max_power = ServerParam.i().max_power() + accel_max = ServerParam.i().ball_accel_max() + ball_decay = ServerParam.i().ball_decay() + + self_type = world.self().player_type() + + current_max_accel = min(self._current_state.kick_rate_ * max_power, accel_max) + + param = ServerParam.i() + my_kickable_area = self_type.kickable_area() + + my_noise = world.self().vel().r() * param.player_rand() + current_dir_diff_rate = (world.ball().angle_from_self() - world.self().body()).abs() / 180.0 + + current_dist_rate = ((world.ball().dist_from_self() + - self_type.player_size() + - param.ball_size()) + / self_type.kickable_margin()) + current_pos_rate = 0.5 + 0.25 * (current_dir_diff_rate + current_dist_rate) + + current_speed_rate = 0.5 + 0.5 * (world.ball().vel().r() / ( + param.ball_speed_max() * param.default_player_decay())) + # my_final_pos = world.self().pos() + world.self().vel() + world.self().vel() * self_type.player_decay() + + success_count = 0 + max_speed2 = 0.0 + for i in range(NUM_STATE): + state = self._state_cache[0][i] + + if state.flag_ & OUT_OF_PITCH: + continue + + if state.flag_ & KICKABLE: + continue + + if state.flag_ & SELF_COLLISION: + continue + + if state.flag_ & RELEASE_INTERFERE: + return False + + kick_miss_flag = SAFETY + target_vel = (target_point - state.pos_).set_length_vector(first_speed) + + vel = state.pos_ - world.ball().pos() + accel = vel - world.ball().vel() + accel_r = accel.r() + + if accel_r > current_max_accel: + continue + kick_power = accel_r / world.self().kick_rate() + ball_noise = vel.r() * param.ball_rand() + max_kick_rand = self_type.kick_rand() * (kick_power / param.max_power()) * ( + current_pos_rate + current_speed_rate) + if ((my_noise + ball_noise + max_kick_rand) # * 0.9 + > my_kickable_area - state.dist_ - 0.05): # 0.1 ) + kick_miss_flag |= KICK_MISS_POSSIBILITY + + vel *= ball_decay + accel = target_vel - vel + accel_r = accel.r() + + if accel_r > min(state.kick_rate_ * max_power, accel_max): + + if success_count == 0: + max_vel = calc_max_velocity(target_vel.th(), + state.kick_rate_, + vel) + d2 = max_vel.r2() + if max_speed2 < d2: + if max_speed2 == 0.0: + self._candidates.append(Sequence()) + + max_speed2 = d2 + accel = max_vel - vel + self._candidates[-1].flag_ = ((self._current_state.flag_ & NOT_RELEASE_INTERFERE) + | state.flag_) + self._candidates[-1].pos_list_.clear() + self._candidates[-1].pos_list_.append(state.pos_.copy()) + self._candidates[-1].pos_list_.append(state.pos_ + max_vel) + self._candidates[-1].speed_ = math.sqrt(max_speed2) + self._candidates[-1].power_ = accel.r() / state.kick_rate_ + success_count += 1 + continue + self._candidates.append(Sequence()) + self._candidates[-1].flag_ = ((self._current_state.flag_ & NOT_RELEASE_INTERFERE) + | state.flag_ + | kick_miss_flag) + self._candidates[-1].pos_list_.append(state.pos_.copy()) + self._candidates[-1].pos_list_.append(state.pos_ + target_vel) + self._candidates[-1].speed_ = first_speed + self._candidates[-1].power_ = accel_r / state.kick_rate_ + success_count += 1 + return success_count > 0 + + """ + \ brief simulate three step kicks + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + """ + + def simulate_three_step(self, world: 'WorldModel', + target_point: Vector2D, + first_speed): + + max_power = ServerParam.i().max_power() + accel_max = ServerParam.i().ball_accel_max() + ball_decay = ServerParam.i().ball_decay() + + current_max_accel = min(self._current_state.kick_rate_ * max_power, + accel_max) + current_max_accel2 = current_max_accel * current_max_accel + + param = ServerParam.i() + + self_type = world.self().player_type() + + my_kickable_area = self_type.kickable_area() + + my_noise1 = world.self().vel().r() * param.player_rand() + current_dir_diff_rate = (world.ball().angle_from_self() - world.self().body()).abs() / 180.0 + current_dist_rate = ((world.ball().dist_from_self() + - self_type.player_size() + - param.ball_size()) + / self_type.kickable_margin()) + current_pos_rate = 0.5 + 0.25 * (current_dir_diff_rate + current_dist_rate) + current_speed_rate = 0.5 + 0.5 * (world.ball().vel().r() + / (param.ball_speed_max() * param.ball_decay())) + + target_rel_angle = (target_point - world.self().pos()).th() - world.self().body() + angle_deg = target_rel_angle.degree() + 180.0 + target_angle_index = round(DEST_DIR_DIVS * (angle_deg / 360.0)) + if target_angle_index >= DEST_DIR_DIVS: + target_angle_index = 0 + + table = self._tables[target_angle_index] + + success_count = 0 + max_speed2 = 0.0 + + count = 0 + + for it in table: + if count > MAX_TABLE_SIZE: + break + if success_count > 10: + break + state_1st = self._state_cache[0][it.origin_] + state_2nd = self._state_cache[1][it.dest_] + + if state_1st.flag_ & OUT_OF_PITCH: + continue + + if state_2nd.flag_ & OUT_OF_PITCH: + continue + + if state_1st.flag_ & KICKABLE: + continue + + if state_2nd.flag_ & KICKABLE: + continue + + if state_2nd.flag_ & SELF_COLLISION: + continue + + if state_2nd.flag_ & RELEASE_INTERFERE: + return False + + target_vel = (target_point - state_2nd.pos_).set_length_vector(first_speed) + + kick_miss_flag = SAFETY + + vel1 = state_1st.pos_ - world.ball().pos() + accel = vel1 - world.ball().vel() + accel_r2 = accel.r2() + + if accel_r2 > current_max_accel2: + continue + + kick_power = math.sqrt(accel_r2) / world.self().kick_rate() + ball_noise = vel1.r() * param.ball_rand() + max_kick_rand = self_type.kick_rand() * (kick_power / param.max_power()) * ( + current_pos_rate + current_speed_rate) + + if ((my_noise1 + ball_noise + max_kick_rand) # * 0.95 + > my_kickable_area - state_1st.dist_ - 0.05): # 0.1 ) + kick_miss_flag |= KICK_MISS_POSSIBILITY + + vel1 *= ball_decay + vel2 = state_2nd.pos_ - state_1st.pos_ + accel = vel2 - vel1 + accel_r2 = accel.r2() + + if accel_r2 > pow(min(state_1st.kick_rate_ * max_power, accel_max), 2): + continue + + vel2 *= ball_decay + + accel = target_vel - vel2 + accel_r2 = accel.r2() + if accel_r2 > pow(min(state_2nd.kick_rate_ * max_power, accel_max), 2): + if success_count == 0: + max_vel = calc_max_velocity(target_vel.th(), + state_2nd.kick_rate_, + vel2) + d2 = max_vel.r2() + if max_speed2 < d2: + if max_speed2 == 0.0: + self._candidates.append(Sequence()) + + max_speed2 = d2 + accel = max_vel - vel2 + + self._candidates[-1].flag_ = ((self._current_state.flag_ & NOT_RELEASE_INTERFERE) + | (state_1st.flag_ & NOT_RELEASE_INTERFERE) + | state_2nd.flag_) + self._candidates[-1].pos_list_.clear() + self._candidates[-1].pos_list_.append(state_1st.pos_.copy()) + self._candidates[-1].pos_list_.append(state_2nd.pos_.copy()) + self._candidates[-1].pos_list_.append(state_2nd.pos_ + max_vel) + self._candidates[-1].speed_ = math.sqrt(max_speed2) + self._candidates[-1].power_ = accel.r() / state_2nd.kick_rate_ + continue + self._candidates.append(Sequence()) + self._candidates[-1].flag_ = ((self._current_state.flag_ & NOT_RELEASE_INTERFERE) + | (state_1st.flag_ & NOT_RELEASE_INTERFERE) + | state_2nd.flag_ + | kick_miss_flag) + self._candidates[-1].pos_list_.append(state_1st.pos_.copy()) + self._candidates[-1].pos_list_.append(state_2nd.pos_.copy()) + self._candidates[-1].pos_list_.append(state_2nd.pos_ + target_vel) + self._candidates[-1].speed_ = first_speed + self._candidates[-1].power_ = math.sqrt(accel_r2) / state_2nd.kick_rate_ + success_count += 1 + return success_count > 0 + + """ + \ brief evaluate candidate kick sequences + \ param first_speed required first speed + \ param allowable_speed required first speed threshold + """ + + def evaluate(self, first_speed, allowable_speed): + power_thr1 = ServerParam.i().max_power() * 0.94 + power_thr2 = ServerParam.i().max_power() * 0.9 + for it in self._candidates: + n_kick = len(it.pos_list_) + + it.score_ = 1000.0 + + if it.speed_ < first_speed: + if n_kick > 1 or it.speed_ < allowable_speed: + it.score_ = -10000.0 + it.score_ -= (first_speed - it.speed_) * 100000.0 + + else: + it.score_ -= 50.0 + + if it.flag_ & TACKLEABLE: + it.score_ -= 500.0 + + if it.flag_ & NEXT_TACKLEABLE: + it.score_ -= 300.0 + + if it.flag_ & NEXT_KICKABLE: + it.score_ -= 600.0 + + if it.flag_ & MAYBE_RELEASE_INTERFERE: + if n_kick == 1: + it.score_ -= 250.0 + + else: + it.score_ -= 200.0 + + if n_kick == 3: + it.score_ -= 200.0 + + elif n_kick == 2: + it.score_ -= 50.0 + + if n_kick > 1: + if it.power_ > power_thr1: + it.score_ -= 75.0 + + elif it.power_ > power_thr2: + it.score_ -= 25.0 + + it.score_ -= it.power_ * 0.5 + + if it.flag_ & KICK_MISS_POSSIBILITY: + it.score_ -= 30.0 + + """ + \ brief simulate kick sequence + \ param world reference to the WorldModel + \ param target_point kick target point + \ param first_speed required first speed + \ param allowable_speed required first speed threshold + \ param max_step maximum size of kick sequence + \ param sequence reference to the result variable + \ return if successful kick is found, True, False is returned but kick sequence is generated anyway. + """ + + def simulate(self, world, target_point: Vector2D, first_speed, allowable_speed, max_step, sequence: Sequence): + + if len(self._state_list) == 0: + log.sw_log().kick().add_text( 'there isnt any state list') + # if _KickTable.debug_print_DEBUG: + # debug_print("False , Len == 0") + return False + + target_speed = bound(0.0, + first_speed, + ServerParam.i().ball_speed_max()) + speed_thr = bound(0.0, + allowable_speed, + target_speed) + + self._candidates.clear() + + self.update_state(world) + + self.check_collision_after_release(world, + target_point, + target_speed) + self.check_interfere_after_release(world, + target_point, + target_speed) + + if (max_step >= 1 + and self.simulate_one_step(world, + target_point, + target_speed)): + if _KickTable.debug_print_DEBUG: + log.sw_log().kick().add_text( "simulate() found 1 step") + if (max_step >= 2 + and self.simulate_two_step(world, + target_point, + target_speed)): + if _KickTable.debug_print_DEBUG: + log.sw_log().kick().add_text( "simulate() found 2 step") + if (max_step >= 3 + and self.simulate_three_step(world, + target_point, + target_speed)): + if _KickTable.debug_print_DEBUG: + log.sw_log().kick().add_text( "simulate() found 3 step") + + self.evaluate(target_speed, speed_thr) + log.sw_log().kick().add_text( "candidate number:{}".format(len(self._candidates))) + if not self._candidates: + # if _KickTable.debug_print_DEBUG: + # debug_print("False -> candidates len == ", len(self._candidates)) + rtn_list = [False, sequence] + return rtn_list + sequence = max(self._candidates, key=functools.cmp_to_key(sequence_cmp)) # TODO : CMP Check + if _KickTable.debug_print_DEBUG or True: + for tmp in self._candidates: + log.sw_log().kick().add_text( + f"simulate() result next_pos={sequence.pos_list_[0]} flag={sequence.flag_} n_kick={len(sequence.pos_list_)} speed= {sequence.speed_} power={sequence.power_} score={sequence.score_}") + log.os_log().info(f"Smart kick : {sequence.speed_ >= target_speed - EPS} -> seq speed is {sequence.speed_} & tar speed eps is {target_speed - EPS}") + rtn_list = [sequence.speed_ >= target_speed - EPS, sequence] + return rtn_list + + """ + \ brief get the candidate kick sequences + \ return reference to the container of Sequence + """ + + def candidates(self): + return self.candidates + + +""" + \ brief singleton interface + \ return reference to the singleton instance + """ + + +class KickTable: + S_UPDATE_TIME = GameTime(-1, 0) + + _instance: _KickTable = _KickTable() + + @staticmethod + def instance() -> _KickTable: + return KickTable._instance diff --git a/keepaway/lib/action/neck_body_to_ball.py b/keepaway/lib/action/neck_body_to_ball.py new file mode 100644 index 00000000..98d9a719 --- /dev/null +++ b/keepaway/lib/action/neck_body_to_ball.py @@ -0,0 +1,25 @@ +from pyrusgeom.angle_deg import AngleDeg + +from keepaway.lib.action.neck_body_to_point import NeckBodyToPoint +from keepaway.lib.action.scan_field import ScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import NeckAction + +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class NeckBodyToBall(NeckAction): + def __init__(self, angle_buf: Union[AngleDeg,float] = 5.): + self._angle_buf = float(angle_buf) + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('BodyToBall/') + wm = agent.world() + if wm.ball().pos_valid(): + ball_next = wm.ball().pos() + wm.ball().vel() + + return NeckBodyToPoint(ball_next, self._angle_buf).execute(agent) + return ScanField().execute(agent) diff --git a/keepaway/lib/action/neck_body_to_point.py b/keepaway/lib/action/neck_body_to_point.py new file mode 100644 index 00000000..32fe37af --- /dev/null +++ b/keepaway/lib/action/neck_body_to_point.py @@ -0,0 +1,77 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.action.neck_turn_to_relative import NeckTurnToRelative +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import NeckAction + +from typing import TYPE_CHECKING, Union + +from keepaway.lib.rcsc.server_param import ServerParam + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class NeckBodyToPoint(NeckAction): + def __init__(self, point: Vector2D, angle_buf: Union[AngleDeg, float] = 5.): + super().__init__() + self._point = point.copy() + self._angle_buf = float(angle_buf) + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('BodyToPoint/') + SP = ServerParam.i() + wm = agent.world() + + angle_buf = bound(0., self._angle_buf, 180.) + + my_next = wm.self().pos() + wm.self().vel() + target_rel_angle = (self._point - my_next).th() - wm.self().body() + + if SP.min_neck_angle() + angle_buf < target_rel_angle.degree() < SP.max_neck_angle() - angle_buf: + agent.do_turn(0.) + agent.set_neck_action(NeckTurnToRelative(target_rel_angle)) + return True + + max_turn = wm.self().player_type().effective_turn(SP.max_moment(),wm.self().vel().r()) + if target_rel_angle.abs() < max_turn: + agent.do_turn(target_rel_angle) + agent.set_neck_action(NeckTurnToRelative(0.)) + return True + + agent.do_turn(target_rel_angle) + if target_rel_angle.degree() > 0.: + target_rel_angle -= max_turn + else: + target_rel_angle += max_turn + + agent.set_neck_action(NeckTurnToRelative(target_rel_angle)) + return True + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/keepaway/lib/action/neck_scan_field.py b/keepaway/lib/action/neck_scan_field.py new file mode 100644 index 00000000..8906758a --- /dev/null +++ b/keepaway/lib/action/neck_scan_field.py @@ -0,0 +1,197 @@ +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from keepaway.lib.player.soccer_action import NeckAction +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import GameModeType, ViewWidth +from keepaway.lib.player.world_model import WorldModel + +from pyrusgeom.geom_2d import AngleDeg, Rect2D, Size2D, Vector2D + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + + +class NeckScanField(NeckAction): + DEBUG = True + + INVALID_ANGLE = -360. + + _last_calc_time = GameTime(0, 0) + _last_calc_view_width = ViewWidth.NORMAL + _cached_target_angle = 0.0 + + def __init__(self): + super().__init__() + + def execute(self, agent: 'PlayerAgent'): + from keepaway.lib.action.neck_scan_players import NeckScanPlayers + log.debug_client().add_message('NeckScanField/') + wm = agent.world() + ef = agent.effector() + + if (NeckScanField._last_calc_time == wm.time() + and NeckScanField._last_calc_view_width != ef.queued_next_view_width()): + + agent.do_turn_neck(NeckScanField._cached_target_angle - ef.queued_next_self_body() - wm.self().neck()) + return True + + NeckScanField._last_calc_time = wm.time().copy() + NeckScanField._last_calc_view_width = ef.queued_next_view_width() + + angle = self.calc_angle_for_wide_pitch_edge(agent) + if angle != NeckScanField.INVALID_ANGLE: + NeckScanField._cached_target_angle = angle + agent.do_turn_neck(AngleDeg(NeckScanField._cached_target_angle) - ef.queued_next_self_body() - wm.self().neck()) + return True + + + existed_ghost = False + for p in wm.all_players(): + if p.is_ghost() and p.dist_from_self() < 30: + existed_ghost = True + break + + if NeckScanField.DEBUG: + log.sw_log().world().add_text( f"(NSF EXE) existed_ghost={existed_ghost}") + log.sw_log().world().add_text( f"(NSF EXE) dir_counts={wm._dir_count}") + + if not existed_ghost: + angle = NeckScanPlayers.get_best_angle(agent) + if angle != NeckScanField.INVALID_ANGLE: + NeckScanField._cached_target_angle = angle + agent.do_turn_neck(AngleDeg(NeckScanField._cached_target_angle) - ef.queued_next_self_body() - wm.self().neck()) + return True + + gt = wm.game_mode().type() + consider_patch = ( + gt is GameModeType.PlayOn + or ( + not gt.is_ind_free_kick() + and not gt.is_back_pass() + and wm.ball().dist_from_self() < wm.self().player_type().player_size() + 0.15 + ) + ) + angle = self.calc_angle_default(agent, consider_patch) + + if consider_patch and (AngleDeg(angle) - wm.self().face()).abs() < 5: + angle = self.calc_angle_default(agent, False) + + NeckScanField._cached_target_angle = angle + agent.do_turn_neck(AngleDeg(NeckScanField._cached_target_angle) - ef.queued_next_self_body() - wm.self().neck()) + + return True + + def calc_angle_default(self, agent: 'PlayerAgent', consider_patch: bool): + SP = ServerParam.i() + pitch_rect = Rect2D( + Vector2D( -SP.pitch_half_length(), -SP.pitch_half_width()), + Size2D(SP.pitch_length(), SP.pitch_width()) + ) + + expand_pitch_rect = Rect2D( + Vector2D( -SP.pitch_half_length() - 3, -SP.pitch_half_width()- 3), + Size2D(SP.pitch_length()+6, SP.pitch_width()+6) + ) + + goalie_rect = Rect2D( + Vector2D(SP.pitch_half_length() - 3, -15.), + Size2D(10, 30) + ) + + wm = agent.world() + ef = agent.effector() + + next_view_width = ef.queued_next_view_width().width() + + left_start = ef.queued_next_self_body() + SP.min_neck_angle() - (next_view_width*0.5) + scan_range = SP.max_neck_angle() - SP.min_neck_angle() + next_view_width + shrinked_next_view_width = next_view_width - WorldModel.DIR_STEP * 1.5 + sol_angle = left_start + scan_range * 0.5 + if scan_range < shrinked_next_view_width: + return sol_angle.degree() + + tmp_angle = left_start.copy() + size_of_view_width = round(shrinked_next_view_width / WorldModel.DIR_STEP) + dir_count:list[int] = [] + + for _ in range(size_of_view_width): + dir_count.append(wm.dir_count(tmp_angle)) + + if NeckScanField.DEBUG: + log.sw_log().world().add_text( f"(NSF CAD) dir_count={dir_count[-1]}") + + tmp_angle += WorldModel.DIR_STEP + + max_count_sum = 0 + add_dir = shrinked_next_view_width + + my_next = ef.queued_next_self_pos() + + while True: + tmp_count_sum = sum(dir_count) + angle = tmp_angle - shrinked_next_view_width *0.5 + + if tmp_count_sum > max_count_sum: + update = True + if consider_patch: + face_point = my_next + Vector2D(r=20,a=angle) + if not pitch_rect.contains(face_point) and not goalie_rect.contains(face_point): + update = False + + if update: + left_face_point = my_next + Vector2D(r=20, a=angle - next_view_width * 0.5) + if not expand_pitch_rect.contains(left_face_point) and not goalie_rect.contains(left_face_point): + update = False + + if update: + right_face_point = my_next + Vector2D(r=20, a=angle + next_view_width * 0.5) + if not expand_pitch_rect.contains(right_face_point) and not goalie_rect.contains(right_face_point): + update = False + + if update: + sol_angle = angle + max_count_sum = tmp_count_sum + dir_count = dir_count[1:] + add_dir += WorldModel.DIR_STEP + tmp_angle += WorldModel.DIR_STEP + dir_count.append(wm.dir_count(tmp_angle)) + + if add_dir > scan_range: + break + return sol_angle.degree() + + def calc_angle_for_wide_pitch_edge(self, agent: 'PlayerAgent'): + SP = ServerParam.i() + wm = agent.world() + ef = agent.effector() + + if ef.queued_next_view_width() is not ViewWidth.WIDE: + return NeckScanField.INVALID_ANGLE + + gt = wm.game_mode().type() + if gt is not GameModeType.PlayOn and not gt.is_goal_kick() and wm.ball().dist_from_self() > 2: + return NeckScanField.INVALID_ANGLE + + next_self_pos = wm.self().pos() + wm.self().vel() + pitch_x_thr = SP.pitch_half_length() - 15. + pitch_y_thr = SP.pitch_half_length() - 10. # TODO WIDTH MAYBE(it was on librcsc tho...) + + target_angle = NeckScanField.INVALID_ANGLE + + if next_self_pos.abs_y() > pitch_y_thr: + target_pos = Vector2D(SP.pitch_half_length() - 7., 0.) + target_pos.set_x(min(target_pos.x(), target_pos.x() * 0.7 *next_self_pos.x() * 0.3)) + + if next_self_pos.abs_y() > pitch_y_thr: + target_angle = (target_pos - next_self_pos).th().degree() + + if next_self_pos.abs_x() > pitch_x_thr: + target_pos = Vector2D(SP.pitch_half_length() *0.5, 0) + + if next_self_pos.abs_x() > pitch_x_thr: + target_angle = (target_pos - next_self_pos).th().degree() + + return target_angle diff --git a/keepaway/lib/action/neck_scan_players.py b/keepaway/lib/action/neck_scan_players.py new file mode 100644 index 00000000..bd151a26 --- /dev/null +++ b/keepaway/lib/action/neck_scan_players.py @@ -0,0 +1,158 @@ +from math import exp +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from keepaway.lib.player.soccer_action import NeckAction +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.player.world_model import WorldModel + +from pyrusgeom.soccer_math import bound +from pyrusgeom.geom_2d import Vector2D, AngleDeg + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.types import ViewWidth +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + +class NeckScanPlayers(NeckAction): + DEBUG = True + + INVALID_ANGLE = -360.0 + + _last_calc_time = GameTime(0, 0) + _last_calc_view_width = ViewWidth.NORMAL + _cached_target_angle = 0.0 + _last_calc_min_neck_angle = 0. + _last_calc_max_neck_angle = 0. + + def __init__(self, min_neck_angle: float=INVALID_ANGLE, max_neck_angle: float= INVALID_ANGLE): + super().__init__() + + self._min_neck_angle = min_neck_angle + self._max_neck_angle = max_neck_angle + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('ScanPlayers/') + wm = agent.world() + ef = agent.effector() + + if NeckScanPlayers.DEBUG: + log.sw_log().world().add_text( f"(NSP exe) last={NeckScanPlayers._last_calc_time}|wm-time={wm.time()}") + + if (NeckScanPlayers._last_calc_time != wm.time() + or NeckScanPlayers._last_calc_view_width != ef.queued_next_view_width() + or abs(NeckScanPlayers._last_calc_min_neck_angle - self._min_neck_angle) > 1.0e-3 + or abs(NeckScanPlayers._last_calc_max_neck_angle - self._max_neck_angle) > 1.0e-3): + + NeckScanPlayers._last_calc_time = wm.time().copy() + NeckScanPlayers._last_calc_view_width = ef.queued_next_view_width() + NeckScanPlayers._last_calc_min_neck_angle = self._min_neck_angle + NeckScanPlayers._last_calc_max_neck_angle = self._max_neck_angle + + NeckScanPlayers._cached_target_angle = NeckScanPlayers.get_best_angle(agent, self._min_neck_angle, self._max_neck_angle) + + if NeckScanPlayers._cached_target_angle == NeckScanPlayers.INVALID_ANGLE: + return NeckScanField().execute(agent) + + target_angle = AngleDeg(NeckScanPlayers._cached_target_angle) + agent.do_turn_neck(target_angle - ef.queued_next_self_body().degree() - wm.self().neck().degree()) + return True + + @staticmethod + def get_best_angle(agent: 'PlayerAgent', min_neck_angle: float= INVALID_ANGLE, max_neck_angle:float = INVALID_ANGLE): + wm = agent.world() + + if len(wm.all_players()) < 22: + if NeckScanPlayers.DEBUG: + log.sw_log().world().add_text( f"(NSP GBA) all players are less than 22, n={len(wm.all_players())}") + return NeckScanPlayers.INVALID_ANGLE + + SP = ServerParam.i() + ef = agent.effector() + + next_self_pos = ef.queued_next_self_pos() + next_self_body = ef.queued_next_self_body() + view_width = ef.queued_next_view_width().width() + view_half_width = view_width/2 + + neck_min = SP.min_neck_angle() if min_neck_angle == NeckScanPlayers.INVALID_ANGLE else bound(SP.min_neck_angle(), min_neck_angle, SP.max_neck_angle()) + neck_max = SP.max_neck_angle() if max_neck_angle == NeckScanPlayers.INVALID_ANGLE else bound(SP.min_neck_angle(), max_neck_angle, SP.max_neck_angle()) + neck_step = max(1, (neck_max - neck_min)/36) + + best_dir = NeckScanPlayers.INVALID_ANGLE + best_score = 0. + + dirs = [neck_min + d*neck_step for d in range(36)] + for dir in dirs: + left_angle = next_self_body+(dir - (view_half_width - 0.01)) + right_angle = next_self_body + (dir + (view_half_width - 0.01)) + + score = NeckScanPlayers.calculate_score(wm, next_self_pos, left_angle, right_angle) # TODO IMP FUNC + + if NeckScanPlayers.DEBUG: + log.sw_log().world().add_text( f"body={next_self_body}|dir={dir}|score={score}") + + if score > best_score: + best_dir = dir + best_score = score + + if best_dir == NeckScanPlayers.INVALID_ANGLE or abs(best_score) < 1.0e-5: + return NeckScanPlayers.INVALID_ANGLE + + angle = next_self_body + best_dir + return angle.degree() + + @staticmethod + def calculate_score(wm: WorldModel, next_self_pos: Vector2D, left_angle: AngleDeg, right_angle: AngleDeg): + score = 0. + view_buffer = 90. + + it = wm.intercept_table() + our_min = min(it.self_reach_cycle(), it.teammate_reach_cycle()) + opp_min = it.opponent_reach_cycle() + + our_ball = (our_min <= opp_min) + + reduced_left_angle = left_angle + 5. + reduced_right_angle = right_angle - 5. + + for p in wm.all_players(): + if p.is_self(): + continue + + pos = p.pos() + p.vel() + angle = (pos - next_self_pos).th() + + if not angle.is_right_of(reduced_left_angle) or not angle.is_left_of(reduced_right_angle): + continue + + if p.ghost_count() >= 5: + continue + + pos_count= p.seen_pos_count() + if p.is_ghost() and p.ghost_count() % 2 == 1: + pos_count = min(2, pos_count) + + pos_count += 1 + + if our_ball: + if p.side() == wm.our_side() and (p.pos().x() > wm.ball().pos().x() - 10 or p.pos().x() > 30): + pos_count *=2 + + keepaway.base_val = pos_count**2 + rate = exp(-(p.dist_from_self() ** 2) / (2*(20**2))) + + score += keepaway.base_val * rate + buf = min((angle-left_angle).abs(), (angle-right_angle).abs()) + + if buf < view_buffer: + view_buffer = buf + + rate = 1+ view_buffer/90 + score*= rate + return score + + + \ No newline at end of file diff --git a/keepaway/lib/action/neck_turn_to_ball.py b/keepaway/lib/action/neck_turn_to_ball.py new file mode 100644 index 00000000..b36dea81 --- /dev/null +++ b/keepaway/lib/action/neck_turn_to_ball.py @@ -0,0 +1,138 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound + +from keepaway.lib.action.basic_actions import NeckAction + +from typing import TYPE_CHECKING + +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.action.neck_scan_players import NeckScanPlayers +from keepaway.lib.debug.debug import log +from keepaway.lib.rcsc.server_param import ServerParam + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + +class NeckTurnToBall(NeckAction): + def __init__(self): + super().__init__() + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('TurnToBall/') + SP = ServerParam.i() + wm = agent.world() + + if not wm.ball().pos_valid(): + NeckScanField().execute(agent) + return True + + my_next = agent.effector().queued_next_self_pos() + my_body_next = agent.effector().queued_next_self_body() + + ball_next = agent.effector().queued_next_ball_pos() # TODO IMP FUNC + ball_angle_next = (ball_next - my_next).th() + ball_rel_angle_next = ball_angle_next - my_body_next + + next_view_width = agent.effector().queued_next_view_width().width() + + if ball_rel_angle_next.abs() > SP.max_neck_angle() + next_view_width*0.5: + NeckScanField().execute(agent) + return True + + if wm.intercept_table().opponent_reach_cycle() <= 1: + neck_moment = ball_rel_angle_next - wm.self().neck() + agent.do_turn_neck(neck_moment) + return True + + view_half = max(0, next_view_width * 0.5 - 10.) + + opp = wm.get_opponent_nearest_to_ball(10) + mate = wm.get_teammate_nearest_to_ball(10) + + ball_dist = my_next.dist(ball_next) + + if (SP.visible_distance() * 0.7 < ball_dist < 15 + and (wm.kickable_teammate() + or wm.kickable_opponent() + or (opp and opp.dist_from_ball() < opp.player_type().kickable_area()+0.3) + or (mate and mate.dist_from_ball() < mate.player_type().kickable_area() + 0.3) + ) + ): + view_half = max(0, next_view_width*0.5 - 20) + + if (len(wm.opponents_from_self()) >= 11 + and (wm.ball().pos().x() > 0 + or wm.ball().pos().abs_y() > SP.pitch_half_width() - 8 + or not opp + or opp.dist_from_ball() > 3)): + best_angle = NeckScanPlayers.INVALID_ANGLE + + if ball_dist > SP.visible_distance() - 0.3 or wm.ball().seen_pos_count() > 0: + min_neck_angle = bound(SP.min_neck_angle(), + ball_rel_angle_next.degree() - view_half, + SP.max_neck_angle()) + + max_neck_angle = bound(SP.min_neck_angle(), + ball_rel_angle_next.degree() + view_half, + SP.max_neck_angle()) + + best_angle = NeckScanPlayers.get_best_angle(agent, min_neck_angle, max_neck_angle) + + else: + best_angle = NeckScanPlayers.get_best_angle(agent) + + if best_angle != NeckScanPlayers.INVALID_ANGLE: + target_angle = best_angle + neck_moment = AngleDeg(target_angle - my_body_next.degree() - wm.self().neck().degree()) + + agent.do_turn_neck(neck_moment) + return True + + left_rel_angle = bound(SP.min_neck_angle(), ball_rel_angle_next.degree() - view_half, SP.max_neck_angle()) + right_rel_angle = bound(SP.min_neck_angle(), ball_rel_angle_next.degree() + view_half, SP.max_neck_angle()) + + left_sum_count = 0 + right_sum_count = 0 + + _, left_sum_count, _ = wm.dir_range_count(my_body_next + left_rel_angle, next_view_width) + _, right_sum_count, _ = wm.dir_range_count(my_body_next + right_rel_angle, next_view_width) + + if left_sum_count > right_sum_count: + agent.do_turn_neck(left_rel_angle - wm.self().neck().degree()) + else: + agent.do_turn_neck(right_rel_angle - wm.self().neck().degree()) + + return True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/keepaway/lib/action/neck_turn_to_ball_or_scan.py b/keepaway/lib/action/neck_turn_to_ball_or_scan.py new file mode 100644 index 00000000..126d7b42 --- /dev/null +++ b/keepaway/lib/action/neck_turn_to_ball_or_scan.py @@ -0,0 +1,57 @@ +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.action.neck_turn_to_ball import NeckTurnToBall +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import NeckAction + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.server_param import ServerParam + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class NeckTurnToBallOrScan(NeckAction): + def __init__(self, count_thr: int = 5): + super().__init__() + self._count_thr = count_thr + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('TurnToBallOrScan/') + wm = agent.world() + ef = agent.effector() + SP = ServerParam.i() + + if wm.ball().pos_count() <= self._count_thr: + return NeckScanField().execute(agent) + + ball_next = ef.queued_next_ball_pos() + my_next = ef.queued_next_self_pos() + + if (wm.ball().pos_count() <= 0 + and not wm.kickable_opponent() + and not wm.kickable_teammate() + and my_next.dist(ball_next) < SP.visible_distance() - 0.2): + return NeckScanField().execute(agent) + + my_next_body = ef.queued_next_self_body() + next_view_width = ef.queued_next_view_width().width() + + if ((ball_next - my_next).th() - my_next_body).abs() > SP.max_neck_angle() + next_view_width * 0.5 + 2: + return NeckScanField().execute(agent) + + return NeckTurnToBall().execute(agent) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/keepaway/lib/action/neck_turn_to_point.py b/keepaway/lib/action/neck_turn_to_point.py new file mode 100644 index 00000000..f6fcf309 --- /dev/null +++ b/keepaway/lib/action/neck_turn_to_point.py @@ -0,0 +1,55 @@ +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.action.neck_scan_field import NeckScanField +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import NeckAction + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.server_param import ServerParam + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class NeckTurnToPoint(NeckAction): + def __init__(self, point: Vector2D = None, points: list[Vector2D] = None): + self._points: list[Vector2D] = [] + if point: + self._points.append(point) + else: + self._points = list(points) + + def execute(self, agent: 'PlayerAgent'): + log.debug_client().add_message('TurnToPoint/') + ef = agent.effector() + wm = agent.world() + SP = ServerParam.i() + + next_pos = ef.queued_next_self_pos() + next_body = ef.queued_next_self_body() + next_view_width = ef.queued_next_view_width().width()/2 + + for p in self._points: + rel_pos = p - next_pos + rel_angle = rel_pos.th() - next_body + + if rel_angle.abs() < SP.max_neck_angle() + next_view_width - 5.: + return agent.do_turn_neck(rel_angle - agent.world().self().neck()) + + NeckScanField().execute(agent) + return True + + + + + + + + + + + + + + diff --git a/keepaway/lib/action/neck_turn_to_relative.py b/keepaway/lib/action/neck_turn_to_relative.py new file mode 100644 index 00000000..6d88a8e2 --- /dev/null +++ b/keepaway/lib/action/neck_turn_to_relative.py @@ -0,0 +1,16 @@ +from pyrusgeom.angle_deg import AngleDeg + +from keepaway.lib.player.soccer_action import NeckAction + +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class NeckTurnToRelative(NeckAction): + def __init__(self, rel_angle: Union[AngleDeg, float]): + self._angle_rel_to_body: AngleDeg = AngleDeg(rel_angle) + + def execute(self, agent: 'PlayerAgent'): + return agent.do_turn_neck(self._angle_rel_to_body - agent.world().self().neck()) \ No newline at end of file diff --git a/keepaway/lib/action/scan_field.py b/keepaway/lib/action/scan_field.py new file mode 100644 index 00000000..0f4e2e45 --- /dev/null +++ b/keepaway/lib/action/scan_field.py @@ -0,0 +1,72 @@ +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.action.neck_body_to_point import NeckBodyToPoint +from keepaway.lib.action.neck_turn_to_relative import NeckTurnToRelative +from keepaway.lib.action.view_wide import ViewWide +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import BodyAction + +from typing import TYPE_CHECKING + +from keepaway.lib.rcsc.types import ViewWidth + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class ScanField(BodyAction): + def __init__(self): + pass + + def execute(self, agent: "PlayerAgent"): + log.debug_client().add_message("ScanField/") + wm = agent.world() + if not wm.self().pos_valid(): + agent.do_turn(60.0) + agent.set_neck_action(NeckTurnToRelative(0)) + return True + + if wm.ball().pos_valid(): + self.scan_all_field(agent) + return True + + self.find_ball(agent) + return True + + def find_ball(self, agent: "PlayerAgent"): + wm = agent.world() + # print(wm.ball().pos()) + # print("find ball/") + + if agent.effector().queued_next_view_width() is not ViewWidth.WIDE: + agent.set_view_action(ViewWide()) + + my_next = wm.self().pos() + wm.self().vel() + face_angle = ( + (wm.ball().seen_pos() - my_next).th() + if wm.ball().seen_pos().is_valid() + else (my_next * -1).th() + ) + + search_flag = wm.ball().lost_count() // 3 + if search_flag % 2 == 1: + face_angle += 180.0 + + face_point = my_next + Vector2D(r=10, a=face_angle) + NeckBodyToPoint(face_point).execute(agent) + + def scan_all_field(self, agent: "PlayerAgent"): + wm = agent.world() + # print(wm.ball().pos()) + # print("find ball/") + + if agent.effector().queued_next_view_width() is not ViewWidth.WIDE: + agent.set_view_action(ViewWide()) + + turn_moment = ( + wm.self().view_width().width() + + agent.effector().queued_next_view_width().width() + ) + turn_moment /= 2 + agent.do_turn(turn_moment) + agent.set_neck_action(NeckTurnToRelative(wm.self().neck())) diff --git a/keepaway/lib/action/smart_kick.py b/keepaway/lib/action/smart_kick.py new file mode 100644 index 00000000..c9195537 --- /dev/null +++ b/keepaway/lib/action/smart_kick.py @@ -0,0 +1,95 @@ +""" + \ file smart_kick.py + \ brief smart kick action class file. +""" +from keepaway.lib.debug.debug import log +from keepaway.lib.player.soccer_action import * +from keepaway.lib.action.kick_table import KickTable, Sequence +from keepaway.lib.action.stop_ball import StopBall +from keepaway.lib.action.hold_ball import HoldBall +from keepaway.lib.debug.level import Level +from keepaway.lib.rcsc.server_param import ServerParam +from pyrusgeom.soccer_math import * + + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + +# from keepaway.lib.player.player_agent import * +# from pyrusgeom.soccer_math import * + + +class SmartKick(BodyAction): + debug_print_DEBUG: bool = True # debug_prints IN SMARTKICK + + def __init__(self, target_point: Vector2D, first_speed, first_speed_thr, max_step): + super().__init__() + # target point where the ball should move to + self._target_point = target_point + # desired ball first speed + self._first_speed: float = first_speed + # threshold value for the ball first speed + self._first_speed_thr: float = first_speed_thr + # maximum number of kick steps + self._max_step: int = max_step + # result kick sequence holder + self._sequence = Sequence() + + def execute(self, agent: 'PlayerAgent'): + log.sw_log().kick().add_text( "Body_SmartKick") + log.os_log().debug(f'c{agent.world().time().cycle()}kick{self._target_point} {self._first_speed}') + log.sw_log().kick().add_text(f'c{agent.world().time().cycle()}kick{self._target_point} {self._first_speed}') + wm = agent.world() + if not wm.self().is_kickable(): + if SmartKick.debug_print_DEBUG: + log.os_log().info("----- NotKickable -----") + log.sw_log().kick().add_text("not kickable") + return False + if not wm.ball().vel_valid(): + if SmartKick.debug_print_DEBUG: + log.os_log().info("-- NonValidBall -> StopBall --") + log.sw_log().kick().add_text("unknown ball vel") + return StopBall().execute(agent) + + first_speed = min(self._first_speed, ServerParam.i().ball_speed_max()) + first_speed_thr = max(0.0, self._first_speed_thr) + max_step = max(1, self._max_step) + ans = KickTable.instance().simulate(wm, + self._target_point, + first_speed, + first_speed_thr, + max_step, + self._sequence) + + print("sequence , ", ans) + + if ans[0] and SmartKick.debug_print_DEBUG: + log.os_log().info(f"Smart kick : {ans[0]} seq -> speed : {ans[1].speed_} power : {ans[1].power_} score : {ans[1].score_} flag : {ans[1].flag_} next_pos : {ans[1].pos_list_[0]} {len(ans[1].pos_list_)} step {ans[1].pos_list_}") + log.sw_log().kick().add_text(f"Smart kick : {ans[0]} seq -> speed : {ans[1].speed_} power : {ans[1].power_} score : {ans[1].score_} flag : {ans[1].flag_} next_pos : {ans[1].pos_list_[0]} {len(ans[1].pos_list_)} step {ans[1].pos_list_}") + + if ans[0]: + self._sequence = ans[1] + if self._sequence.speed_ >= first_speed_thr: # double check + vel = self._sequence.pos_list_[0] - wm.ball().pos() + kick_accel = vel - wm.ball().vel() + if SmartKick.debug_print_DEBUG: + log.os_log().debug(f"Kick Vel : {vel}, Kick Power : {kick_accel.r() / wm.self().kick_rate()}, Kick Angle : {kick_accel.th() - wm.self().body()}") + log.sw_log().kick().add_text(f"Kick Vel : {vel}, Kick Power : {kick_accel.r() / wm.self().kick_rate()}, Kick Angle : {kick_accel.th() - wm.self().body()}") + + # print("Performing agent kick ") + + agent.do_kick(kick_accel.r() / wm.self().kick_rate(), + kick_accel.th() - wm.self().body()) + if SmartKick.debug_print_DEBUG: + log.os_log().debug(f"----------------#### Player Number {wm.self().unum()} 'DO_KICK'ed in SmartKick at Time: {wm.time().cycle()} ####----------------") + log.sw_log().kick().add_text(f"----------------#### Player Number {wm.self().unum()} 'DO_KICK'ed in SmartKick at Time: {wm.time().cycle()} ####----------------") + return True + + # failed to search the kick sequence + log.sw_log().kick().add_text("----->>>>>Hold Ball") + HoldBall(False, self._target_point, self._target_point).execute(agent) + return False + + def sequence(self): + return self._sequence diff --git a/keepaway/lib/action/stop_ball.py b/keepaway/lib/action/stop_ball.py new file mode 100644 index 00000000..e1a57ecb --- /dev/null +++ b/keepaway/lib/action/stop_ball.py @@ -0,0 +1,115 @@ +""" + \ file body_stop_ball.py + \ brief kick the ball to keep a current positional relation. +""" + +from keepaway.lib.player.soccer_action import * +from pyrusgeom.soccer_math import * +from pyrusgeom.angle_deg import AngleDeg +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.player_agent import PlayerAgent +""" + \ class Body_StopBall + \ brief stop the ball, possible as. +""" + + +class StopBall(BodyAction): + """ + \ brief accessible from global. + """ + + def __init__(self): + super().__init__() + self._accel_radius = 0.0 + self._accel_angle = AngleDeg() + + """ + \ brief execute action + \ param agent the agent itself + \ return True if action is performed + """ + + def execute(self, agent: 'PlayerAgent'): + wm: 'WorldModel' = agent.world() + if not wm.self().is_kickable(): + return False + if not wm.ball().vel_valid(): # Always true until NFS nice :) + required_accel = wm.self().vel() - (wm.self().pos() - wm.ball().pos()) + kick_power = required_accel.r() / wm.self().kick_rate() + kick_power *= 0.5 + agent.do_kick(min(kick_power, ServerParam.i().max_power()), + required_accel.th() - wm.self().body()) + return True + + self._accel_radius = 0.0 + self._accel_angle = AngleDeg() + + self.calcAccel(agent) + + if self._accel_radius < 0.02: + agent.do_turn(0.0) + return False + kick_power = 0.0 + # kick_power = self._accel_radius / wm.self().kickRate() + # kick_power = min(kick_power, i.maxPower()) + + return agent.do_kick(kick_power, + self._accel_angle - wm.self().body()) + + def calcAccel(self, agent): + + wm: 'WorldModel' = agent.world() + + safety_dist = wm.self().player_type().player_size() + ServerParam.i().ball_size() + 0.1 + + target_dist = wm.ball().dist_from_self() + if target_dist < safety_dist: + target_dist = safety_dist + + if target_dist > wm.self().player_type().kickable_area() - 0.1: + target_dist = wm.self().player_type().kickable_area() - 0.1 + + target_rel = wm.self().pos() - wm.ball().pos() + target_rel.set_length(target_dist) + + required_accel = wm.self().vel() + required_accel += target_rel # target relative to current + required_accel -= wm.self().pos() - wm.ball().pos() # vel = pos diff + required_accel -= wm.ball().vel() # required accel + + self._accel_radius = required_accel.r() + + if self._accel_radius < 0.01: + return None + + # check max accel with player's kick rate + + max_accel = ServerParam.i().max_power() * wm.self().kick_rate() + if max_accel > self._accel_radius: + # can accelerate -. can stop ball successfully + self._accel_angle = required_accel.th() + return None + + ################################## + # keep the ball as much as possible near the best point + + next_ball_to_self = wm.self().vel() + next_ball_to_self -= wm.self().pos() - wm.ball().pos() + next_ball_to_self -= wm.ball().vel() + + keep_dist = wm.self().player_type().player_size() + wm.self().player_type().kickable_margin() * 0.4 + + self._accel_radius = min(max_accel, next_ball_to_self.r() - keep_dist) + self._accel_angle = next_ball_to_self.th() + + if self._accel_radius < 0.0: # == next_ball_dist < keep_dist + # next ball dist will be closer than keep dist. + # -. kick angle must be reversed. + self._accel_radius *= -1.0 + self._accel_radius = min(self._accel_radius, max_accel) + self._accel_angle -= 180.0 diff --git a/keepaway/lib/action/turn_to_ball.py b/keepaway/lib/action/turn_to_ball.py new file mode 100644 index 00000000..3334eac7 --- /dev/null +++ b/keepaway/lib/action/turn_to_ball.py @@ -0,0 +1,18 @@ +from keepaway.lib.action.turn_to_point import TurnToPoint + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class TurnToBall: + def __init__(self, cycle: int = 1) -> None: + self._cycle: int = cycle + + def execute(self, agent: "PlayerAgent"): + if not agent.world().ball().pos_valid(): + return False + + ball_point = agent.world().ball().inertia_point(self._cycle) + return TurnToPoint(ball_point, self._cycle).execute(agent) diff --git a/keepaway/lib/action/turn_to_point.py b/keepaway/lib/action/turn_to_point.py new file mode 100644 index 00000000..1ac5fc76 --- /dev/null +++ b/keepaway/lib/action/turn_to_point.py @@ -0,0 +1,23 @@ +from pyrusgeom.vector_2d import Vector2D + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + +class TurnToPoint: + def __init__(self, point: Vector2D, cycle: int = 1) -> None: + self._point: Vector2D = point + self._cycle: int = cycle + + def execute(self, agent: 'PlayerAgent'): + self_player = agent.world().self() + if not self_player.pos_valid(): + return agent.do_turn(60) + + my_point = self_player.inertia_point(self._cycle) + target_rel_angle = (self._point - my_point).th() - self_player.body() + + agent.do_turn(target_rel_angle) + if target_rel_angle.abs() < 1: + return False + return True \ No newline at end of file diff --git a/keepaway/lib/action/view_wide.py b/keepaway/lib/action/view_wide.py new file mode 100644 index 00000000..dafab8dd --- /dev/null +++ b/keepaway/lib/action/view_wide.py @@ -0,0 +1,11 @@ +from keepaway.lib.player.soccer_action import ViewAction +from keepaway.lib.rcsc.types import ViewWidth + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.player_agent import PlayerAgent + + +class ViewWide(ViewAction): + def execute(self, agent: 'PlayerAgent'): + return agent.do_change_view(ViewWidth.WIDE) \ No newline at end of file diff --git a/keepaway/lib/coach/__init__.py b/keepaway/lib/coach/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/coach/coach_agent.py b/keepaway/lib/coach/coach_agent.py new file mode 100644 index 00000000..4a9f1044 --- /dev/null +++ b/keepaway/lib/coach/coach_agent.py @@ -0,0 +1,332 @@ +import logging +import time + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.free_form_messenger import FreeFormMessenger +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.network.udp_socket import IPAddress +from keepaway.lib.coach.gloabl_world_model import GlobalWorldModel +from keepaway.lib.player.world_model import WorldModel +from keepaway.lib.player.soccer_agent import SoccerAgent +from keepaway.lib.player_command.coach_command import ( + CoachChangePlayerTypeCommand, + CoachCommand, + CoachDoneCommand, + CoachEyeCommand, + CoachLookCommand, + CoachFreeFormMessageCommand, + CoachInitCommand, + CoachSendCommands, + CoachTeamnameCommand, + CoachCheckBallCommand, +) +from keepaway.lib.rcsc.game_mode import GameMode +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import HETERO_DEFAULT, HETERO_UNKNOWN, GameModeType + +# import team_config +from keepaway.config import team_config + + +# TODO PLAYER PARAMS? + + +class CoachAgent(SoccerAgent): + def __init__(self): + super().__init__() + self._think_received: bool = True + self._server_cycle_stopped: bool = True + self._current_time: GameTime = GameTime(-1, 0) + self._game_mode: GameMode = GameMode() + self._world: GlobalWorldModel = GlobalWorldModel() + # self._world: WorldModel = WorldModel("full") + self._is_synch_mode: bool = True + self._last_body_command: list[CoachCommand] = [] + self._free_from_messages: list[FreeFormMessenger] = [] + + def send_init_command(self): + # TODO check reconnection + # TODO make config class for these data + # com = CoachInitCommand(team_config.TEAM_NAME, team_config.COACH_VERSION) + + com = CoachInitCommand("keepers", team_config.COACH_VERSION) + + print(com.str()) + + if self._client.send_message(com.str()) <= 0: + print("ERROR failed to connect to server") + + log.os_log().error("ERROR failed to connect to server") + self._client.set_server_alive(False) + return False + return True + + def send_bye_command(self): + if self._client.is_server_alive() is True: + # TODO Coach Bye Command needs to be implemented + # com = PlayerByeCommand() + # self._agent._client.send_message(com.str()) + self._client.set_server_alive(False) + + @property # TODO REMOVE PROPERTY + def think_received(self): + return self._think_received + + def analyze_init(self, message): + self.init_dlog(message) + self.do_eye(True) + + def see_parser(self, message: str): + if not message.startswith("(player_type"): + self.parse_cycle_info(message, True) + + self.world().parse(message) + self.world().update_after_see(self._current_time) + + def parse_cycle_info(self, message: str, by_see_global: bool): + cycle = int(message.split(" ")[1]) + self.update_current_time(cycle, by_see_global) + + def update_current_time(self, new_time: int, by_see_global: bool): + if self._server_cycle_stopped: + if new_time != self._current_time.cycle(): + self._current_time.assign(new_time, 0) + else: + if by_see_global: + self._current_time.assign( + self._current_time.cycle(), + self._current_time.stopped_cycle() + 1, + ) + else: + self._current_time.assign(new_time, 0) + + def hear_parser(self, message: str): + self.parse_cycle_info(message, False) + + _, cycle, sender = tuple(message.split(" ")[:3]) + cycle = int(cycle) + + if sender[0].isnumeric() or sender[0] == "-": # PLAYER MESSAGE + self.hear_player_parser(message) + elif sender == "referee": + self.hear_referee_parser(message) + + def hear_player_parser(self, message): + pass + + def update_server_status(self): + if self._server_cycle_stopped: + self._server_cycle_stopped = False + + if self._game_mode.is_server_cycle_stopped_mode(): + self._server_cycle_stopped = True + + def hear_referee_parser(self, message: str): + mode = message.split(" ")[-1].strip(")") + self._game_mode.update(mode, self._current_time) + + # TODO CARDS AND OTHER STUFF + + self.update_server_status() + + if self._game_mode.type() is GameModeType.TimeOver: + self.send_bye_command() + return + self.world().update_game_mode(self._game_mode, self._current_time) + # TODO FULL STATE WORLD update + + def analyze_change_player_type(self, msg: str): + data = msg.strip("()").split(" ") + n = len(data) + if n == 4: + pass + elif n == 3: + unum, type = int(data[1]), int(data[2].removesuffix(")\x00")) + self.world().change_player_type(self.world().our_side(), unum, type) + elif n == 2: + unum = int(data[1].removesuffix(")\x00")) + self.world().change_player_type( + self.world().their_side(), unum, HETERO_UNKNOWN + ) + + def handle_start(self): + # print(self._client) + # print(team_config.COACH_PORT) + + if self._client is None: + return False + + if team_config.COACH_VERSION < 18: + log.os_log().error( + "PYRUS2D keepaway.base code does not support coach version less than 18." + ) + print("PYRUS2D keepaway.base code does not support coach version less than 18.") + self._client.set_server_alive(False) + return False + + # TODO check for config.host not empty + + # print(self._client.connect_to(IPAddress(team_config.HOST, 6002))) + + if not self._client.connect_to( + IPAddress(team_config.HOST, team_config.COACH_PORT) + ): + log.os_log().error("ERROR failed to connect to server") + print("ERROR failed to connect to server") + self._client.set_server_alive(False) + return False + + # print("coach agent handle start 2") + # print(self.send_init_command()) + + if not self.send_init_command(): + return False + return True + + def handle_exit(self): + if self._client.is_server_alive(): + self.send_bye_command() + # log.os_log().info(f"player( {self._real_world.self_unum()} ): finished") + + # def run(self): + # while self._client.is_server_alive(): + # # self.do_check_ball() + # length, message, server_address = self._client.recv_message() + # if len(message) != 0: + # print("coach ", message) + # self.parse_message(message.decode()) + # else: + # self._client.set_server_alive(False) + # break + + def run(self): + # print("coach agent run") + last_time_rec = time.time() + while True: + while True: + self.do_check_ball() + length, message, server_address = self._client.recv_message() + if len(message) != 0: + print("coach ", message) + + self.parse_message(message.decode()) + last_time_rec = time.time() + break + elif time.time() - last_time_rec > 3: + self._client.set_server_alive(False) + break + if self.think_received: + last_time_rec = time.time() + break + + if not self._client.is_server_alive(): + log.os_log().info(f"{team_config.TEAM_NAME} Agent : Server Down") + break + + if self.think_received: + self.action() + self._think_received = False + # TODO elif for not sync mode + + def do_check_ball(self): + # command = CoachCheckBallCommand() + # print(command.str()) + command = CoachTeamnameCommand() + self._client.send_message(command.str()) + + def parse_message(self, message): + if message.find("(init") != -1: # TODO Use startwith instead of find + self.analyze_init(message) + elif message.find("(server_param") != -1: + ServerParam.i().parse(message) + elif message.find("(player_param") != -1: + pass # TODO + elif message.find("(change_player_type") != -1: + self.analyze_change_player_type(message) + elif message.find("(see") != -1 or message.find("(player_type") != -1: + self.see_parser(message) + self._think_received = True + elif message.find("(hear") != -1: + self.hear_parser(message) + elif message.find("think") != -1: + self._think_received = True + elif message.find("(ok") != -1: + self._client.send_message(CoachDoneCommand().str()) + else: + self._client.send_message(CoachTeamnameCommand().str()) + + def init_dlog(self, message): + log.setup(self.world().team_name_l(), "coach", self._current_time) + + def world(self) -> GlobalWorldModel: + return self._world + + def full_world(self) -> GlobalWorldModel: + return self._world + + def action(self): + self.action_impl() + commands = self._last_body_command + # if self.world().our_side() == SideID.RIGHT: + # PlayerCommandReverser.reverse(commands) # unused :\ # its useful :) # nope not useful at all :( + commands.append(CoachDoneCommand()) + for com in commands: + self._client.send_message(com.str()) + log.sw_log().flush() + self._last_body_command = [] + + def action_impl(self): + pass + + def do_teamname(self): + command = CoachTeamnameCommand() + self._last_body_command.append(command) + return True + + def send_command(self, commands): # TODO it should be boolean + self._client.send_message(CoachSendCommands.all_to_str(commands)) + + def do_eye(self, on: bool): + self._client.send_message(CoachEyeCommand(on).str()) + + def do_look(self): + self._client.send_message(CoachLookCommand().str()) + + def do_change_player_type(self, unum: int, type: int): + if not 1 <= unum <= 11: + log.os_log().error( + f"(coach agent do change player type) illegal unum! unum={unum}" + ) + return False + + if not HETERO_DEFAULT <= type < 18: # TODO Player PARAM + log.os_log().error( + f"(coach agent do change player type) illegal player type! type={type}" + ) + return False + + self._last_body_command.append(CoachChangePlayerTypeCommand(unum, type)) + return True + + def do_change_player_types(self, types: list[tuple[int, int]]): + if len(types) == 0: + log.os_log().error(f"(coach agent do change player types) list is empty") + return False + + for t in types: + self.do_change_player_type(t[0], t[1]) + + return True + + def add_free_form_message(self, message: FreeFormMessenger): + self._free_from_messages.append(message) + + def send_free_form_message(self): + if not self.world().can_send_free_form(): + self._free_from_messages.clear() + return + + msg = Messenger.encode_all(self._world, self._free_from_messages) + self._last_body_command.append(CoachFreeFormMessageCommand(msg)) + self.world().inc_free_form_send_count() diff --git a/keepaway/lib/coach/gloabl_world_model.py b/keepaway/lib/coach/gloabl_world_model.py new file mode 100644 index 00000000..134bc69a --- /dev/null +++ b/keepaway/lib/coach/gloabl_world_model.py @@ -0,0 +1,244 @@ +from keepaway.lib.coach.global_object import GlobalPlayerObject, GlobalBallObject +from keepaway.lib.debug.debug import log +from keepaway.lib.parser.global_message_parser import GlobalFullStateWorldMessageParser +from keepaway.lib.player.object_player import * +from keepaway.lib.player.object_ball import * +from keepaway.lib.rcsc.game_mode import GameMode +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.types import HETERO_DEFAULT, HETERO_UNKNOWN, GameModeType + + +class GlobalWorldModel: + def __init__(self): + self._player_types = [PlayerType() for _ in range(18)] + self._team_name_l: str = "" + self._team_name_r: str = "" + self._our_side: SideID = SideID.NEUTRAL + self._our_players: list[GlobalPlayerObject] = [GlobalPlayerObject() for _ in range(11)] + self._their_players: list[GlobalPlayerObject] = [GlobalPlayerObject() for _ in range(11)] + self._unknown_player: list[GlobalPlayerObject] = [GlobalPlayerObject() for _ in range(22)] + self._ball: GlobalBallObject = GlobalBallObject() + self._time: GameTime = GameTime(-1, 0) + self._game_mode: GameMode = GameMode() + self._last_kicker_side: SideID = SideID.NEUTRAL + self._yellow_card_left: list[bool] = [False for _ in range(11)] + self._yellow_card_right: list[bool] = [False for _ in range(11)] + self._red_card_left: list[bool] = [False for _ in range(11)] + self._red_card_right: list[bool] = [False for _ in range(11)] + + self._last_playon_start: int = 0 + self._freeform_allowed_count: int = ServerParam.i().coach_say_count_max() + self._freeform_send_count: int = 0 + + self._subsititute_count: dict[SideID, int] = { + SideID.LEFT: 0, + SideID.RIGHT: 0 + } + + self._our_player_type_id: list[int] = [HETERO_DEFAULT for _ in range(11)] + self._their_player_type_id: list[int] = [HETERO_DEFAULT for _ in range(11)] + + self._our_player_type_used_count: list[int] = [11] + self._their_player_type_used_count: list[int] = [11] + + self._available_player_type_id: list[int] = [HETERO_DEFAULT] + + + def player_types(self): + return self._player_types + + def ball(self) -> GlobalBallObject: + return self._ball + + def our_side(self): + return SideID.RIGHT if self._our_side == 'r' else SideID.LEFT if self._our_side == 'l' else SideID.NEUTRAL + + def their_side(self): + return self.our_side().invert() + + def our_player(self, unum): + return self._our_players[unum - 1] + + def their_player(self, unum): + return self._their_players[unum - 1] + + def time(self): + return self._time.copy() + + def available_player_type_id(self): + return self._available_player_type_id + + def parse(self, message): + if message.find("see_global") != -1: + self.fullstate_parser(message) + elif 0 < message.find("player_type") < 3: + self.player_type_parser(message) + elif message.find("sense_body") != -1: + pass + elif message.find("init") != -1: + pass + + def fullstate_parser(self, message): + parser = GlobalFullStateWorldMessageParser() + parser.parse(message) + self._team_name_l = parser.dic()['teams']['team_left'] + self._team_name_r = parser.dic()['teams']['team_right'] + + # TODO vmode counters and arm + + self._ball.init_str(parser.dic()['b']) + + if 'players' in parser.dic(): + for player_dic in parser.dic()['players']: + player = GlobalPlayerObject() + player.init_dic(player_dic) + # player.set_player_type(self._player_types[player.type()]) + if player.side().value == self._our_side: + self._our_players[player.unum() - 1] = player + elif player.side() == SideID.NEUTRAL: + self._unknown_player[player.unum() - 1] = player + else: + self._their_players[player.unum() - 1] = player + # TODO check reversion + + def __repr__(self): + # Fixed By MM _ temp + return "(time: {})(ball: {})(tm: {})(opp: {})".format(self._time, self.ball(), self._our_players, + self._their_players) + + def self_parser(self, message): + message = message.split(" ") + self._self_unum = int(message[2]) + self._our_side = message[1] + + def update_after_see(self, current_time: GameTime): + self._time = current_time.copy() + + def player_type_parser(self, message): + new_player_type = PlayerType() + new_player_type.parse(message) + self._player_types[new_player_type.id()] = new_player_type + self._available_player_type_id.append(new_player_type.id()) + + def reverse(self): + self.ball().reverse() + Object.reverse_list(self._our_players) + Object.reverse_list(self._their_players) + + def team_name_l(self): + return self._team_name_l + + def team_name_r(self): + return self._team_name_r + + def game_mode(self): + return self._game_mode + + def last_kicker_side(self) -> SideID: + return self._last_kicker_side + + def can_send_free_form(self): + if 0 <= self._freeform_allowed_count <= self._freeform_send_count: + return False + + if self.game_mode().type() is not GameModeType.PlayOn: + return True + + playon_period = self.time().cycle() - self._last_playon_start + wait_period = ServerParam.i().freeform_wait_period() + if playon_period > wait_period: + playon_period %= wait_period + return playon_period < ServerParam.i().freeform_send_period() + return False + + def inc_free_form_send_count(self): + self._freeform_send_count += 1 + + def update_game_mode(self, game_mode: GameMode, current_time: GameTime): + pk_mode = game_mode.is_penalty_kick_mode() + if not pk_mode and self._game_mode.type() is not GameModeType.PlayOn: + if game_mode.type() != self.game_mode().type(): + self._last_set_play_start_time = current_time.copy() + self._set_play_count = 0 + + if game_mode.type().is_goal_kick(): + self._ball.update_only_vel(Vector2D(0, 0), 0) + + if self._game_mode.type() is not GameModeType.PlayOn and game_mode.type() is GameModeType.PlayOn: + self._last_playon_start = current_time.cycle() + + self._game_mode = game_mode.copy() + self._time = current_time.copy() + + def our_subsititute_count(self): + return self._subsititute_count[self.our_side()] + + def their_subsititute_count(self): + return self._subsititute_count[self.our_side().invert()] + + def change_player_type(self, side: SideID, unum: int, type: int): + if side is SideID.NEUTRAL or not(1<=unum<=11): + log.os_log().error(f"(change player type) unum or side is not standard. side={side} unum={unum}") + return + + player_types = len(self.player_types()) + if type != HETERO_UNKNOWN and not (HETERO_DEFAULT <= type < type): + log.os_log().error(f"(change player type) undefined type. type={type}") + return + + if side == self.our_side() or (self.our_side() is SideID.NEUTRAL and side is SideID.LEFT): + self._our_player_type_id[unum - 1] = type + + if self._time.cycle() > 0: + self._subsititute_count[self.our_side()] += 1 + + self._our_player_type_used_count = [0 for _ in range(player_types)] + for i in range(11): + pt = self._our_player_type_id[i] + if pt != HETERO_UNKNOWN: + self._our_player_type_used_count[pt] += 1 + # TODO CARD + else: + self._their_player_type_id[unum - 1] = type + + if self._time.cycle() > 0: + self._subsititute_count[self.our_side().invert()] += 1 + + self._their_player_type_used_count = [0 for _ in range(player_types)] + for i in range(11): + pt = self._their_player_type_id[i] + if pt != HETERO_UNKNOWN: + self._their_player_type_used_count[pt] += 1 + # TODO CARD + + if side == self.our_side(): + for pt in self._available_player_type_id: + if pt == type: + self._available_player_type_id.remove(pt) + break + + def update_player_type(self): + if self.our_side() is SideID.NEUTRAL: + return + + self._our_player_type_used_count = [0 for _ in range(18)] + for i in range(11): + pt = self._our_player_type_id[i] + if pt != HETERO_UNKNOWN: + self._our_player_type_used_count[pt] += 1 + + self._their_player_type_used_count = [0 for _ in range(18)] + for i in range(11): + pt = self._their_player_type_id[i] + if pt != HETERO_UNKNOWN: + self._their_player_type_used_count[pt] += 1 + + def team_name_left(self): + return self._team_name_l + + def team_name_right(self): + return self._team_name_r + + + + \ No newline at end of file diff --git a/keepaway/lib/coach/global_object.py b/keepaway/lib/coach/global_object.py new file mode 100644 index 00000000..3c2e3af3 --- /dev/null +++ b/keepaway/lib/coach/global_object.py @@ -0,0 +1,205 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.rcsc.player_type import PlayerType +from keepaway.lib.rcsc.server_param import ServerParam +from keepaway.lib.rcsc.types import SideID, Card + + +class GlobalBallObject: + def __init__(self): + self._pos: Vector2D = Vector2D(0, 0) + self._vel: Vector2D = Vector2D(0, 0) + + def init_str(self, string: str): + data = string.split(" ") + self._pos = Vector2D(float(data[0]), float(data[1])) + self._vel = Vector2D(float(data[2]), float(data[3])) + + + def pos(self) -> Vector2D: + return self._pos + + def vel(self) -> Vector2D: + return self._vel + + def set_pos(self, x, y): + self._pos.assign(x, y) + + def set_vel(self, x, y): + self._vel.assign(x, y) + + def reverse(self): + self._pos.reverse() + self._vel.reverse() + + +class GlobalPlayerObject: + def __init__(self): + self._side: SideID = SideID.NEUTRAL + self._unum: int = -1 + self._goalie: bool = False + self._type = -1 + self._player_type: PlayerType = PlayerType() + self._pos: Vector2D = Vector2D.invalid() + self._vel: Vector2D = Vector2D(0, 0) + self._body: AngleDeg = AngleDeg(0) + self._face: AngleDeg = AngleDeg(0) + self._recovery: float = ServerParam.i().recover_init() + self._pointto_cycle: int = 0 + self._pointto_angle: AngleDeg = AngleDeg(0) + self._kicked: bool = False + self._tackle_cycle: int = 0 + self._charged_cycle: int = 0 + self._card: Card = Card.NO_CARD + + def init_dic(self, dic: dict): + self._unum = int(dic["unum"]) + self._pos = Vector2D(float(dic["pos_x"]), float(dic["pos_y"])) + self._vel = Vector2D(float(dic["vel_x"]), float(dic["vel_y"])) + self._side = SideID.RIGHT if dic["side_id"] == 'r' else SideID.LEFT if dic["side_id"] == 'l' else SideID.NEUTRAL + self._body = AngleDeg(float(dic["body"])) + self._face = AngleDeg(float(dic["neck"])) + self._goalie = True if "goalie" in dic else False + self._point_to = Vector2D.invalid() + if "pointto_dist" in dic: + self._point_to = Vector2D.polar2vector(float(dic["pointto_dist"]), float(dic["pointto_dir"])) + self._kicked = True if "kick" in dic else False + if "tackle" in dic: + self._tackle_cycle = 1 + if "charged" in dic: + self._charged_cycle = 1 + if "card" in dic: + self._card = Card.YELLOW if dic["card"] == "y" else Card.RED + + def side(self) -> SideID: + return self._side + + def unum(self) -> int: + return self._unum + + def goalie(self) -> bool: + return self._goalie + + def type(self) -> int: + return self._type + + def player_type(self) -> PlayerType: + return self._player_type + + def pos(self) -> Vector2D: + return self._pos + + def vel(self) -> Vector2D: + return self._vel + + def body(self) -> AngleDeg: + return self._body + + def face(self) -> AngleDeg: + return self._face + + def recovery(self) -> float: + return self._recovery + + def pointto_cycle(self) -> int: + return self._pointto_cycle + + def pointto_angle(self) -> AngleDeg: + return self._pointto_angle + + def is_pointing(self) -> bool: + return self._pointto_cycle > 0 + + def kicked(self) -> bool: + return self._kicked + + def tackle_cycle(self) -> int: + return self._tackle_cycle + + def is_tackling(self) -> bool: + return self._tackle_cycle > 0 + + def charged_cycle(self) -> int: + return self._charged_cycle + + def is_charged(self): + return self._charged_cycle > 0 + + def set_team(self, + side: SideID, + unum: int, + goalie: bool): + self._side = side + self._unum = unum + self._goalie = goalie + if goalie: + self._type = 0 + + def set_player_type(self, type: int, player_type: PlayerType): + self._type = type + self._player_type = player_type + + def set_pos(self, x, y): + self._pos.assign(x, y) + + def set_vel(self, x, y): + self._vel.assign(x, y) + + def set_angle(self, b: AngleDeg, n: AngleDeg): + self._body = b.copy() + self._face = b + n + + def set_recovery(self, r: float): + self._recovery = r + + def set_arm(self, angle: AngleDeg): + self._pointto_cycle = 1 + self._pointto_angle = angle.copy() + + def set_kick(self, on: bool): + self._kicked = on + + def set_tackle(self): + self._tackle_cycle = 1 + + def set_charged(self): + self._charged_cycle = 1 + + def set_card(self, card: Card): + self._card = card + + def update(self, p): + p: GlobalPlayerObject = GlobalPlayerObject() + self._side = p._side + self._unum = p._unum + self._goalie = p._goalie + + self._pos = p._pos.copy() + self._vel = p._vel.copy() + + self._body = p._body.copy() + self._face = p._face.copy() + + if p.is_pointing(): + self._pointto_cycle += 1 + self._pointto_angle = p._pointto_angle.copy() + else: + self._pointto_cycle = 0 + + self._kicked = p._kicked + + if p.is_tackling(): + self._tackle_cycle += 1 + else: + self._tackle_cycle = 0 + + if p.is_charged(): + self._charged_cycle += 1 + else: + self._charged_cycle = 0 + + def reverse(self): + self._pos.reverse() + self._vel.reverse() + self._body.reverse() + self._face.reverse() diff --git a/keepaway/lib/debug/__init__.py b/keepaway/lib/debug/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/debug/color.py b/keepaway/lib/debug/color.py new file mode 100644 index 00000000..c07fd1f3 --- /dev/null +++ b/keepaway/lib/debug/color.py @@ -0,0 +1,38 @@ +class Color: + def __init__(self, red: int = 0, green: int = 0, blue: int = 0, string: str = None): + self._hex: str = "" + if string is None: + red = ('0' if red < 16 else "") + hex(red).split("x")[1] + green = ('0' if green < 16 else "") + hex(green).split("x")[1] + blue = ('0' if blue < 16 else "") + hex(blue).split("x")[1] + self._hex = f"#{red}{green}{blue}" + else: + if string[0] == '#': + self._hex = string + else: + string = string.lower() + if string in ['white', 'w']: + self.__init__(255, 255, 255) + elif string in ['black', 'b']: + self.__init__(0, 0, 0) + elif string in ['red', 'r']: + self.__init__(255, 0, 0) + elif string in ['green', 'g']: + self.__init__(0, 255, 0) + elif string in ['blue', 'b']: + self.__init__(0, 0, 255) + elif string in ['yellow', 'y']: + self.__init__(255, 248, 27) + elif string in ['ping', 'p']: + self.__init__(241, 27, 255) + elif string in ['gray']: + self.__init__(151, 151, 151) + + def hex(self): + return self._hex + + def color(self): + return self._hex + + def __repr__(self): + return self._hex diff --git a/keepaway/lib/debug/debug.py b/keepaway/lib/debug/debug.py new file mode 100644 index 00000000..f27c481a --- /dev/null +++ b/keepaway/lib/debug/debug.py @@ -0,0 +1,36 @@ +import sys +from logging import Logger + +# import team_config +from keepaway.config import team_config +from keepaway.lib.debug.debug_client import DebugClient +from keepaway.lib.debug.os_logger import get_logger +from keepaway.lib.debug.sw_logger import SoccerWindow_Logger +from keepaway.lib.rcsc.game_time import GameTime + +class DebugLogger: + def __init__(self): + self._sw_log: SoccerWindow_Logger = SoccerWindow_Logger('NA', 1, GameTime(0, 0)) + self._os_log: Logger = get_logger(0, False) + self._debug_client: DebugClient = None + + def setup(self, team_name, unum, time): + sys.stderr = open(f'logs/player-{unum}.err', 'w') + self._sw_log = SoccerWindow_Logger(team_name, unum, time) + self._os_log = get_logger(unum, team_config.OUT == team_config.OUT_OPTION.TEXTFILE) + self._debug_client = DebugClient() + + def sw_log(self): + return self._sw_log + + def os_log(self): + return self._os_log + + def debug_client(self): + return self._debug_client + + def update_time(self, t: GameTime): + self._time.assign(t.cycle(), t.stopped_cycle()) + + +log = DebugLogger() diff --git a/keepaway/lib/debug/debug_client.py b/keepaway/lib/debug/debug_client.py new file mode 100644 index 00000000..5b2311b5 --- /dev/null +++ b/keepaway/lib/debug/debug_client.py @@ -0,0 +1,240 @@ +from pyrusgeom.geom_2d import * +import socket + +from keepaway.lib.rcsc.types import Card, SideID, GameModeType, UNUM_UNKNOWN + +# import team_config +from keepaway.config import team_config + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + from keepaway.lib.player.object_player import PlayerObject + + +def player_printer(p: 'PlayerObject', our_side: SideID): + s = ' (' + + if p.side() is SideID.NEUTRAL: + s += 'u' + elif p.side() == our_side: + if p.unum() != UNUM_UNKNOWN: + s += f"t {p.unum()}" + if p.player_type(): + s += f" {p.player_type().id()}" + else: + s += ' -1' + else: + s += 'ut' + else: + if p.unum() != UNUM_UNKNOWN: + s += f"o {p.unum()}" + if p.player_type(): + s += f" {p.player_type().id()}" + else: + s += ' -1' + else: + s += 'uo' + + s += f" {round(p.pos().x(), 2)} {round(p.pos().y(), 2)}" + if p.body_valid(): + s += f" (bd {round(p.body().degree())})" + + if p.pointto_count() < 10: + s += f"(pt {round(float(p.pointto_angle()))})" + + s += ")" + return s + + +class DebugClient: + MAX_LINE = 50 # maximum number of lines in one message. + MAX_TRIANGLE = 50 # maximum number of triangles in one message. + MAX_RECT = 50 # maximum number of rectangles in one message. + MAX_CIRCLE = 50 # maximum number of circles in one message. + + class Line: + def __init__(self, start:Vector2D, end: Vector2D, color: str=''): + self._segment: Segment2D = Segment2D(start, end) + self._color = color + + def to_str(self): + s = f"(line {self._segment.origin().x():.3f} " \ + f"{self._segment.origin().y():.3f} " \ + f"{self._segment.terminal().x():.3f} " \ + f"{self._segment.terminal().y():.3f}" + + if len(self._color) > 0: + s += f' "{self._color}"' + s += ')' + return s + + class Circle: + def __init__(self, center, radius, color): + self._circle = Circle2D(center, radius) + self._color = color + + def to_str(self): + s = f"(circle {self._circle.center().x():.3f} " \ + f"{self._circle.center().y():.3f} " \ + f"{self._circle.radius():.3f}" + + if len(self._color) > 0: + s += f' "{self._color}"' + s+= ')' + return s + + + + def __init__(self): + self._on = True + self._connected = True + self._socket = self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._ip = team_config.HOST + self._port = team_config.DEBUG_CLIENT_PORT + + self._server_log = None + + self._write_mode = None + + self._main_buffer = '' + + self._target_unum = 0 + self._target_point: Vector2D = Vector2D.invalid() + self._message = '' + + self._lines: list[DebugClient.Line] = [] + self._triangles = [] + self._rectangles = [] + self._circles: list[DebugClient.Circle] = [] + + def connect(self, hostname=team_config.HOST, port=team_config.DEBUG_CLIENT_PORT): + pass + + def open(self, log_dir, teamname, unum): + pass + + def write_all(self, world, effector): + self.to_str(world, effector) + self._main_buffer + self.send() + self.clear() + + def close(self): + pass + + def to_str(self, world: 'WorldModel', effector): + if world.game_mode().type() is GameModeType.BeforeKickOff: + ostr = f'((debug (format-version 5)) (time {str(world.time().cycle())},0)' + else: + ostr = f'((debug (format-version 5)) (time {str(world.time().cycle())},{world.time().stopped_cycle()})' + + ostr_player = '' + ostr_ball = '' + if world.self() and world.self().pos().is_valid(): + ostr_player = ' (s ' \ + + ('l ' if world.our_side() == SideID.LEFT else 'r ') \ + + str(world.self().unum()) + ' ' \ + + str(world.self().player_type_id()) + ' ' \ + + str(round(world.self().pos().x(), 2)) + ' ' \ + + str(round(world.self().pos().y(), 2)) + ' ' \ + + str(round(world.self().vel().x(), 2)) + ' ' \ + + str(round(world.self().vel().y(), 2)) + ' ' \ + + str(round(world.self().body().degree(), 1)) + ' ' \ + + str(round(world.self().neck().degree(), 1)) \ + + ' (c "' + str(world.self().pos_count()) + ' ' \ + + str(world.self().vel_count()) + ' ' + str(world.self().face_count()) + if world.self().card() == Card.YELLOW: + ostr_player += 'y' + ostr_player += '"))' + + if world.ball().pos().is_valid(): + ostr_ball = ' (b ' + str(round(world.ball().pos().x(), 2)) \ + + ' ' + str(round(world.ball().pos().y(), 2)) + if world.ball().vel_valid(): + ostr_ball += (' ' + str(round(world.ball().vel().x(), 2)) + + ' ' + str(round(world.ball().vel().y(), 2))) + ostr_ball += (' (c \'g' + str(world.ball().pos_count()) + 'r' + + str(world.ball().rpos_count()) + 'v' + + str(world.ball().vel_count())) + '\'))' + + ostr += ostr_player + ostr += ostr_ball + + for p in world.teammates(): + ostr += player_printer(p, world.our_side()) + + for p in world.opponents(): + ostr += player_printer(p, world.our_side()) + + if self._target_unum != 0: + ostr += (' (target-teammate ' + str(self._target_unum) + ')') + + if self._target_point.is_valid(): + ostr += (' (target-point ' + str(self._target_point.x()) + + ' ' + str(self._target_point.y()) + ')') + + if len(self._message) != 0: + ostr += f' (message "{str(self._message)}")' + + for obj in self._lines: + ostr += obj.to_str() + for obj in self._triangles: # TODO return str + obj.to_str(ostr) + for obj in self._rectangles: # TODO return str + obj.to_str(ostr) + for obj in self._circles: + ostr += obj.to_str() + + ostr += ')' + self._main_buffer = ostr + + def send(self): + if self._main_buffer[-1] != '\0': + self._main_buffer += '\0' + self._sock.sendto(self._main_buffer.encode(), (self._ip, self._port)) + + def write(self, cycle): + pass + + def clear(self): + self._main_buffer = '' + self._target_unum = 0 + self._target_point = Vector2D.invalid() + self._message = '' + self._lines = [] + self._triangles = [] + self._rectangles = [] + self._circles = [] + + def add_message(self, msg: str): + self._message += ('/' + msg.replace('\n', '').replace('"', '')) + + def set_target(self, unum_or_position): + if type(unum_or_position) == int: + self._target_unum = unum_or_position + else: + self._target_point = unum_or_position + + def add_line(self, start, end, color=''): + if len(self._lines) < DebugClient.MAX_LINE: + self._lines.append(DebugClient.Line(start, end,color)) + + def add_triangle(self, v1=None, v2=None, v3=None, tri=None): + if len(self._triangles) < DebugClient.MAX_TRIANGLE: + if tri: + self._triangles.append(tri) + else: + self._triangles.append(Triangle2D(v1, v2, v3)) + + def add_rectangle(self, rect): + if len(self._rectangles) < DebugClient.MAX_RECT: + self._rectangles.append(rect) + + def add_circle(self, center=None, radius=None, circle=None, color=''): + if len(self._circles) < DebugClient.MAX_CIRCLE: + if circle: + self._circles.append(circle) + else: + self._circles.append(DebugClient.Circle(center, radius, color)) diff --git a/keepaway/lib/debug/level.py b/keepaway/lib/debug/level.py new file mode 100644 index 00000000..ae61af33 --- /dev/null +++ b/keepaway/lib/debug/level.py @@ -0,0 +1,43 @@ +from enum import Enum + + +class Level(Enum): + SYSTEM = 0x00000001 + SENSOR = 0x00000002 + WORLD = 0x00000004 + ACTION = 0x00000008 + INTERCEPT = 0x00000010 + KICK = 0x00000020 + HOLD = 0x00000040 + DRIBBLE = 0x00000080 + PASS = 0x00000100 + CROSS = 0x00000200 + SHOOT = 0x00000400 + CLEAR = 0x00000800 + BLOCK = 0x00001000 + MARK = 0x00002000 + POSITIONING = 0x00004000 + ROLE = 0x00008000 + TEAM = 0x00010000 + COMMUNICATION = 0x00020000 + ANALYZER = 0x00040000 + ACTION_CHAIN = 0x00080000 + PLAN = 0x00100000 + + TRAINING = 0x80000000 + + LEVEL_ANY = 0xffffffff + + # unused Levels :\ + LEVEL_00 = 0x00000000 + LEVEL_22 = 0x00200000 + LEVEL_23 = 0x00400000 + LEVEL_24 = 0x00800000 + LEVEL_25 = 0x01000000 + LEVEL_26 = 0x02000000 + LEVEL_27 = 0x04000000 + LEVEL_28 = 0x08000000 + LEVEL_29 = 0x10000000 + LEVEL_30 = 0x20000000 + LEVEL_31 = 0x40000000 + LEVEL_32 = 0x80000000 diff --git a/keepaway/lib/debug/os_logger.py b/keepaway/lib/debug/os_logger.py new file mode 100644 index 00000000..99fd1eb6 --- /dev/null +++ b/keepaway/lib/debug/os_logger.py @@ -0,0 +1,53 @@ +import logging +from typing import Union + +import coloredlogs +import sys + + +def get_logger(unum: Union[int, str] = None, on_file=False): + logging.basicConfig() + logger = logging.getLogger(name='mylogger') + coloredlogs.install(logger=logger) + logger.propagate = False + coloredFormatter = coloredlogs.ColoredFormatter( + datefmt='%H:%M:%S:%s', + fmt=f'%(asctime)s %(filename)s %(lineno)-3d {unum} %(message)s', + level_styles=dict( + debug=dict(color='white'), + info=dict(color='green'), + warning=dict(color='yellow', bright=True), + error=dict(color='red', bold=True, bright=True), + critical=dict(color='black', bold=True, background='red'), + ), + field_styles=dict( + name=dict(color='white'), + asctime=dict(color='white'), + funcName=dict(color='white'), + lineno=dict(color='white'), + ) + ) + if on_file: + if unum == 'coach': + file_name = 'logs/coach.txt' + elif unum > 0: + file_name = f'logs/player-{unum}.txt' + else: + file_name = f'logs/coach-log.txt' + ch = logging.StreamHandler(stream=open(file_name, 'w')) + ch.setFormatter(logging.Formatter('%(asctime)s %(filename)s %(lineno)-3d %(message)s', + '%H:%M:%S:%s')) + else: + ch = logging.StreamHandler(stream=sys.stdout) + ch.setFormatter(fmt=coloredFormatter) + logger.addHandler(hdlr=ch) + logger.setLevel(level=logging.DEBUG) + return logger + +# logger = get_logger() +# logger.setLevel(level=logging.ERROR) +# logger.debug(msg="this is a debug message") +# logger.info(msg="this is an info message") +# logger.warning(msg="this is a warning message") +# logger.error(msg="this is an error message") +# logger.critical(msg="this is a critical message") diff --git a/keepaway/lib/debug/sw_logger.py b/keepaway/lib/debug/sw_logger.py new file mode 100644 index 00000000..6083d1c1 --- /dev/null +++ b/keepaway/lib/debug/sw_logger.py @@ -0,0 +1,203 @@ +from pyrusgeom.circle_2d import Circle2D +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.color import Color +from keepaway.lib.debug.level import Level +from keepaway.lib.rcsc.game_time import GameTime + +import os +# logs_dir = "/logs" +up_one_dir = os.path.dirname(os.getcwd()) +logs_dir = os.path.join(up_one_dir, "logs") + + +class SoccerWindow_Logger: + class LoggerLevel: + def __init__(self, level: Level, game_time: GameTime): + self.level: Level = level + self._time: GameTime = game_time + self._commands = "" + + def add_line(self, + x1: float = None, + y1: float = None, + x2: float = None, + y2: float = None, + start: Vector2D = None, + end: Vector2D = None, + color: Color = Color(string="red")): + if x1 is not None: + self._commands += f"{self._time.cycle()},{self._time.stopped_cycle()} {self.level.value} l {x1} {y1} {x2} {y2} {color}\n" + elif start is not None: + self.add_line(start.x(), start.y(), end.x(), end.y(), color=color) + + def add_text(self, message: str = ""): + self._commands += f"{self._time.cycle()},{self._time.stopped_cycle()} {self.level.value} M {message}\n" # TODO flush if message size is so large like 8192 and bigger + + def add_circle(self, + r: float = None, + cx: float = None, + cy: float = None, + center: Vector2D = None, + circle: Circle2D = None, + fill: bool = False, + color: Color = Color(string='red'), ): + if cx is not None: + self._commands += f"{self._time.cycle()},{self._time.stopped_cycle()} {self.level.value} {'C' if fill else 'c'} {cx} {cy} {r} {color}\n" + elif center is not None: + self.add_circle(r, center.x(), center.y(), color=color, fill=fill) + elif circle is not None: + self.add_circle(circle.radius(), circle.center().x(), circle.center().y(), fill=fill, + color=color) + + def add_point(self, + x: float = None, + y: float = None, + pos: Vector2D = None, + color: Color = Color(string='red')): + if x is not None: + self._commands += f"{self._time.cycle()},{self._time.stopped_cycle()} {self.level.value} p {x} {y} {color}" + elif pos is not None: + self.add_point(pos.x(), pos.y(), color=color) + + def add_message(self, + x, + y, + msg): + self._commands += f"{self._time.cycle()},{self._time.stopped_cycle()} {self.level.value} m {round(x, 4)} {round(y, 4)} {msg}\n" + + def __init__(self, team_name: str, unum: int, time: GameTime): + ## modified to be saved in the current working directory + # self._file = open(f"/tmp/{team_name}-{unum}.log", 'w') - original code + self._file = open(f"{logs_dir}/{team_name}-{unum}.log", 'w') + # self._file = open(f"logs/{team_name}-{unum}.log", 'w') + self._time: GameTime = time + + self._system = SoccerWindow_Logger.LoggerLevel(Level.SYSTEM, self._time) + self._sensor = SoccerWindow_Logger.LoggerLevel(Level.SENSOR, self._time) + self._world = SoccerWindow_Logger.LoggerLevel(Level.WORLD, self._time) + self._action = SoccerWindow_Logger.LoggerLevel(Level.ACTION, self._time) + self._intercept = SoccerWindow_Logger.LoggerLevel(Level.INTERCEPT, self._time) + self._kick = SoccerWindow_Logger.LoggerLevel(Level.KICK, self._time) + self._hold = SoccerWindow_Logger.LoggerLevel(Level.HOLD, self._time) + self._dribble = SoccerWindow_Logger.LoggerLevel(Level.DRIBBLE, self._time) + self._pass = SoccerWindow_Logger.LoggerLevel(Level.PASS, self._time) + self._cross = SoccerWindow_Logger.LoggerLevel(Level.CROSS, self._time) + self._shoot = SoccerWindow_Logger.LoggerLevel(Level.SHOOT, self._time) + self._clear = SoccerWindow_Logger.LoggerLevel(Level.CLEAR, self._time) + self._block = SoccerWindow_Logger.LoggerLevel(Level.BLOCK, self._time) + self._mark = SoccerWindow_Logger.LoggerLevel(Level.MARK, self._time) + self._positioning = SoccerWindow_Logger.LoggerLevel(Level.POSITIONING, self._time) + self._role = SoccerWindow_Logger.LoggerLevel(Level.ROLE, self._time) + self._team = SoccerWindow_Logger.LoggerLevel(Level.TEAM, self._time) + self._communication = SoccerWindow_Logger.LoggerLevel(Level.COMMUNICATION, self._time) + self._analyzer = SoccerWindow_Logger.LoggerLevel(Level.ANALYZER, self._time) + self._action_chain = SoccerWindow_Logger.LoggerLevel(Level.ACTION_CHAIN, self._time) + self._plan = SoccerWindow_Logger.LoggerLevel(Level.PLAN, self._time) + self._training = SoccerWindow_Logger.LoggerLevel(Level.TRAINING, self._time) + self._any = SoccerWindow_Logger.LoggerLevel(Level.LEVEL_ANY, self._time) + + self._levels: list[SoccerWindow_Logger.LoggerLevel] = [ + self._system, + self._sensor, + self._world, + self._action, + self._intercept, + self._kick, + self._hold, + self._dribble, + self._pass, + self._cross, + self._shoot, + self._clear, + self._block, + self._mark, + self._positioning, + self._role, + self._team, + self._communication, + self._analyzer, + self._action_chain, + self._plan, + self._training, + self._any + ] + + def flush(self): + if self._time is None or self._time.cycle() == 0: + return + for l in self._levels: + self._file.write(l._commands) + l._commands = "" + + def update_time(self, t: GameTime): + self._time.assign(t.cycle(), t.stopped_cycle()) + + def system(self): + return self._system + + def sensor(self): + return self._sensor + + def world(self): + return self._world + + def action(self): + return self._action + + def intercept(self): + return self._intercept + + def kick(self): + return self._kick + + def hold(self): + return self._hold + + def dribble(self): + return self._dribble + + def pass_(self): + return self._pass + + def cross(self): + return self._cross + + def shoot(self): + return self._shoot + + def clear(self): + return self._clear + + def block(self): + return self._block + + def mark(self): + return self._mark + + def positioning(self): + return self._positioning + + def role(self): + return self._role + + def team(self): + return self._team + + def communication(self): + return self._communication + + def analyzer(self): + return self._analyzer + + def action_chain(self): + return self._action_chain + + def plan(self): + return self._plan + + def training(self): + return self._training + + def any(self): + return self._any \ No newline at end of file diff --git a/keepaway/lib/debug/timer.py b/keepaway/lib/debug/timer.py new file mode 100644 index 00000000..4c90f771 --- /dev/null +++ b/keepaway/lib/debug/timer.py @@ -0,0 +1,28 @@ +import time + +class ProfileTimer: + main_dict: dict = {} + + @staticmethod + def start(name): + if name not in ProfileTimer.main_dict: + ProfileTimer.main_dict[name] = [0, 0, -1] + ProfileTimer.main_dict[name][2] = time.time() + + @staticmethod + def end(name): + if name not in ProfileTimer.main_dict: + return + ProfileTimer.main_dict[name][0] += time.time() - ProfileTimer.main_dict[name][2] + ProfileTimer.main_dict[name][1] += 1 + + @staticmethod + def get(): + res = '' + for name, value in ProfileTimer.main_dict.items(): + avg = value[0] / value[1] if value[1] > 0 else -1 + res += f'## {name}: {round(avg, 6)}, {value[1]} \n' + return res + +# parse_message: 0.3392317295074463, 410, 0.0008273944622132837 ## flush: 0.4707632064819336, 1096, 0.00042952847306745764 ## synchaction: 5.524951934814453, 79, 0.06993610044068928 ## +#parse_message: 0.09196591377258301, 370, 0.0002485565237096838 ## flush: 0.5167291164398193, 1111, 0.00046510271506734416 ## synchaction: 2.8537697792053223, 92, 0.031019236730492634 ## \ No newline at end of file diff --git a/keepaway/lib/formation/__init__.py b/keepaway/lib/formation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/formation/delaunay_triangulation.py b/keepaway/lib/formation/delaunay_triangulation.py new file mode 100644 index 00000000..4fdb3c0c --- /dev/null +++ b/keepaway/lib/formation/delaunay_triangulation.py @@ -0,0 +1,122 @@ +from scipy.spatial import Delaunay +from pyrusgeom.geom_2d import * +from enum import Enum +from keepaway.lib.rcsc.server_param import ServerParam +from pyrusgeom.soccer_math import min_max + +class FormationType(Enum): + Static = 's' + DelaunayTriangulation2 = 'D' + + +class Formation: + def __init__(self, path): + self._balls = [] + self._players = [] + self._triangles = [] + self._formation_type = FormationType.Static + self._target_players = [] + self._path = path + self.read_file(path) + self.calculate() + + def read_file(self, path): + file = open(path, 'r') + lines = file.readlines() + if lines[0].find('Static') < 0: + self._formation_type = FormationType.DelaunayTriangulation2 + if self._formation_type == FormationType.Static: + self.read_static(lines) + else: + self.read_delaunay(lines) + + def read_static(self, lines): + for i in range(len(lines)): + if i == 0 or lines[i].startswith('#'): + continue + player = lines[i].split() + self._target_players.append(Vector2D(float(player[2]), float(player[3]))) + + def read_delaunay(self, lines): + for i in range(len(lines)): + if lines[i].find('Ball') >= 0: + self.read_sample(i, lines) + i += 11 + + def read_sample(self, i, lines): + ball = lines[i].split(' ') + ball_x = float(ball[1]) + ball_y = float(ball[2]) + self._balls.append([ball_x, ball_y]) + players = [] + for j in range(1, 12): + player = lines[i + j].split(' ') + player_x = float(player[1]) + player_y = float(player[2]) + players.append([player_x, player_y]) + self._players.append(players) + + def calculate(self): + if self._formation_type == FormationType.Static: + return + self._tri = Delaunay(self._balls).simplices + for tri in self._tri: + tmp = [Triangle2D(Vector2D(self._balls[tri[0]][0], self._balls[tri[0]][1]), + Vector2D(self._balls[tri[1]][0], self._balls[tri[1]][1]), + Vector2D(self._balls[tri[2]][0], self._balls[tri[2]][1])), tri[0], tri[1], tri[2]] + self._triangles.append(tmp) + + def update(self, B:Vector2D): + SP = ServerParam.i() + if self._formation_type == FormationType.Static: + return + ids = [] + + point = B.copy() + if point.abs_x() > SP.pitch_half_length(): + point._x = min_max(-SP.pitch_half_length(), point.x(), +SP.pitch_half_length()) + if point.abs_y() > SP.pitch_half_width(): + point._y = min_max(-SP.pitch_half_width(), point.y(), +SP.pitch_half_width()) + + for tri in self._triangles: + if tri[0].contains(point): + ids = [tri[1], tri[2], tri[3]] + break + Pa = Vector2D(self._balls[ids[0]][0], self._balls[ids[0]][1]) + Pb = Vector2D(self._balls[ids[1]][0], self._balls[ids[1]][1]) + Pc = Vector2D(self._balls[ids[2]][0], self._balls[ids[2]][1]) + lineProj = Line2D(p1=Pb, p2=Pc).projection(B) + m1 = Pb.dist(lineProj) + n1 = Pc.dist(lineProj) + m2 = Pa.dist(B) + n2 = lineProj.dist(B) + + self._target_players.clear() + for p in range(11): + OPa = Vector2D(self._players[ids[0]][p][0], self._players[ids[0]][p][1]) + OPb = Vector2D(self._players[ids[1]][p][0], self._players[ids[1]][p][1]) + OPc = Vector2D(self._players[ids[2]][p][0], self._players[ids[2]][p][1]) + OI = (OPc - OPb) + OI *= (m1 / (m1 + n1)) + OI += OPb + OB = (OI - OPa) + OB *= (m2 / (m2 + n2)) + OB += OPa + self._target_players.append(OB) + + def get_pos(self, unum): + return self._target_players[unum - 1] + + def get_poses(self): + return self._target_players + + def __repr__(self): + return self._path + +# f = Formation('keepaway.base/formations-dt/before-kick-off.conf') +# debug_print(len(f._balls)) +# debug_print(len(f._players)) +# debug_print(f._formation_type) +# f.update(Vector2D(20, 16)) +# debug_print(f._formation_type) +# debug_print(f._target_players) diff --git a/keepaway/lib/messenger/__init__.py b/keepaway/lib/messenger/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/messenger/ball_goalie_messenger.py b/keepaway/lib/messenger/ball_goalie_messenger.py new file mode 100644 index 00000000..e133fe79 --- /dev/null +++ b/keepaway/lib/messenger/ball_goalie_messenger.py @@ -0,0 +1,76 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class BallGoalieMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL_GOALIE], [ + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 106), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 69), + (0, 360, 180) + ]) + + def __init__(self, + ball_pos: Vector2D = None, + ball_vel: Vector2D = None, + player_pos: Vector2D = None, + player_body: AngleDeg = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._ball_pos: Vector2D = ball_pos.copy() + self._ball_vel: Vector2D = ball_vel.copy() + self._player_pos: Vector2D = player_pos.copy() + self._player_body: AngleDeg = player_body.copy() + else: + self._ball_pos: Vector2D = None + self._ball_vel: Vector2D = None + self._player_pos: Vector2D = None + self._player_body: AngleDeg = None + self._size = Messenger.SIZES[Messenger.Types.BALL_GOALIE] + self._header = Messenger.Types.BALL_GOALIE.value + + self._message = message + + def encode(self) -> str: + if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ + or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): + return '' + + SP = ServerParam.i() + ep = 0.001 + msg = BallGoalieMessenger.CONVERTER.convert_to_word([ + bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), + bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), + bound(ep, self._player_body.degree() + 180, 360. - ep) + ]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + bpx, bpy, bvx, bvy, ppx, ppy, pb = BallGoalieMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) + messenger_memory.add_opponent_goalie(sender, Vector2D(ppx, ppy), current_time, body=AngleDeg(pb-180)) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/ball_messenger.py b/keepaway/lib/messenger/ball_messenger.py new file mode 100644 index 00000000..d4f3f9bc --- /dev/null +++ b/keepaway/lib/messenger/ball_messenger.py @@ -0,0 +1,64 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class BallMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL], [ + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + ]) + + def __init__(self, + ball_pos: Vector2D = None, + ball_vel: Vector2D = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._ball_pos: Vector2D = ball_pos.copy() + self._ball_vel: Vector2D = ball_vel.copy() + else: + self._ball_pos: Vector2D = None + self._ball_vel: Vector2D = None + + self._size = Messenger.SIZES[Messenger.Types.BALL] + self._header = Messenger.Types.BALL.value + self._message = message + + def encode(self) -> str: + if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ + or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): + return '' + + SP = ServerParam.i() + ep = 0.001 + msg = BallMessenger.CONVERTER.convert_to_word([ + bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), + ]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + log.os_log().debug(self._message) + bpx, bpy, bvx, bvy = BallMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) + + def __repr__(self) -> str: + return "ball msg" diff --git a/keepaway/lib/messenger/ball_player_messenger.py b/keepaway/lib/messenger/ball_player_messenger.py new file mode 100644 index 00000000..718fff83 --- /dev/null +++ b/keepaway/lib/messenger/ball_player_messenger.py @@ -0,0 +1,89 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class BallPlayerMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL_PLAYER], [ + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 106), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 69), + (0, 360, 180) + ]) + + def __init__(self, + ball_pos: Vector2D = None, + ball_vel: Vector2D = None, + unum: int = None, + player_pos: Vector2D = None, + player_body: AngleDeg = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._ball_pos: Vector2D = ball_pos.copy() + self._ball_vel: Vector2D = ball_vel.copy() + self._unum: int = unum + self._player_pos: Vector2D = player_pos.copy() + self._player_body: AngleDeg = player_body.copy() + else: + self._ball_pos: Vector2D = None + self._ball_vel: Vector2D = None + self._unum: int = None + self._player_pos: Vector2D = None + self._player_body: AngleDeg = None + + self._size = Messenger.SIZES[Messenger.Types.BALL_PLAYER] + self._header = Messenger.Types.BALL_PLAYER.value + + self._message = message + + def encode(self) -> str: + if not 1 <= self._unum <= 22: + log.os_log().error(f'(ball player messenger) illegal unum={self._unum}') + log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={self._unum}') + return '' + + if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ + or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): + return '' + + SP = ServerParam.i() + ep = 0.001 + msg = BallPlayerMessenger.CONVERTER.convert_to_word([ + bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), + bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), + self._unum, + bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), + bound(ep, self._player_body.degree() + 180, 360-ep) + ]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + bpx, bpy, bvx, bvy, pu, ppx, ppy, pb = BallPlayerMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) + messenger_memory.add_player(sender, pu, Vector2D(ppx, ppy), current_time, + body=AngleDeg(pb - 180)) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/ball_pos_vel_messenger.py b/keepaway/lib/messenger/ball_pos_vel_messenger.py new file mode 100644 index 00000000..46591b9d --- /dev/null +++ b/keepaway/lib/messenger/ball_pos_vel_messenger.py @@ -0,0 +1,89 @@ +from pyrusgeom.soccer_math import min_max +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +import keepaway.lib.messenger.converters as converters + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +class BallPosVelMessenger(Messenger): + def __init__(self, message:str=None) -> None: + super().__init__() + self._size = Messenger.SIZES[Messenger.Types.BALL_POS_VEL_MESSAGE] + self._header = Messenger.Types.BALL_POS_VEL_MESSAGE.value + + self._message = message + + def encode(self) -> str: + if not wm.ball().pos_valid(): + return + if not wm.ball().vel_valid(): + return + + SP = ServerParam.i() + pos = wm.ball().pos().copy() + vel = wm.ball().vel().copy() + + x:float = min_max(-SP.pitch_half_length(), pos.x(), SP.pitch_half_length()) + SP.pitch_half_length() + y:float = min_max(-SP.pitch_half_width(), pos.y(), SP.pitch_half_width()) + SP.pitch_half_width() + + x= int(x*1024/(SP.pitch_half_length()*2)) + y= int(y*512/(SP.pitch_half_width()*2)) + + max_speed = SP.ball_speed_max() + vx = min_max(-max_speed, vel.x(), max_speed) + max_speed + vy = min_max(-max_speed, vel.y(), max_speed) + max_speed + + vx = int(vx*64/(max_speed*2)) + vy = int(vy*64/(max_speed*2)) + + val = x + val *= 2**9 + val += y + + val*=2**6 + val += vx + val*=2**6 + val += vy + + return self._header + converters.convert_to_words(val, self._size - 1) + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + SP = ServerParam.i() + + val = converters.convert_to_int(self._message[1:]) + if val is None: + return + + vy = val% 64 + val = int(val/64) + + vx = val% 64 + val = int(val/64) + + y = val% 512 + val = int(val/512) + + x = val + + x = x / 1024 * SP.pitch_half_length()*2 - SP.pitch_half_length() + y = y / 512 * SP.pitch_half_width()*2 - SP.pitch_half_width() + + vx = vx /64 *SP.ball_speed_max()*2 - SP.ball_speed_max() + vy = vy /64 *SP.ball_speed_max()*2 - SP.ball_speed_max() + + pos = Vector2D(x, y) + vel = Vector2D(vx, vy) + + messenger_memory.add_ball(sender, pos, vel, current_time) + + def __repr__(self) -> str: + return "ball pos vel msg" + + \ No newline at end of file diff --git a/keepaway/lib/messenger/converters.py b/keepaway/lib/messenger/converters.py new file mode 100644 index 00000000..3da17316 --- /dev/null +++ b/keepaway/lib/messenger/converters.py @@ -0,0 +1,123 @@ +from math import ceil +from typing import Union + +from pyrusgeom.soccer_math import bound + +from keepaway.lib.debug.debug import log + +chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ().+-*/?<>_0123456789" + + +class MessengerConverter: + def __init__(self, size, min_max_sizes: list[tuple[float, float, int]]): + self._min_max_sizes = min_max_sizes + self._size = size + + def convert_to_word(self, values): + s = 0 + for i, (min_v, max_v, size) in enumerate(self._min_max_sizes): + v = values[i] + log.os_log().debug(f'v={v}') + v = bound(min_v, v, max_v) + v -= min_v + v /= max_v - min_v + v *= size + + s *= size + s += int(v) + + n_chars = len(chars) + words = [] + while s != 0: + # print(s) + words.append(s % n_chars) + s = s // n_chars + log.os_log().debug(f's={s}') + # print(s) + # words.append(s) + msg = '' + for word in words: + msg += chars[word] + + while len(msg) < self._size-1: + msg += chars[0] + + return msg + + def convert_to_values(self, msg): + if msg == '': + return None + + msg = msg[::-1] + + n_chars = len(chars) + digit = len(msg) - 1 + + val: int = 0 + + for c in msg: + i = chars.find(c) + val += i * int(n_chars ** digit) + digit -= 1 + + values = [] + for i, (min_v, max_v, size) in enumerate(self._min_max_sizes[::-1]): + v = val % size + v = v / size * (max_v - min_v) + v = v + min_v + + values.append(v) + val = int(val / size) + return values[::-1] + + +def convert_to_bits(values_min_max_sizes: list[tuple[float, float, float, int]]): + s = 0 + for i, (v, min_v, max_v, size) in enumerate(values_min_max_sizes): + v -= min_v + v /= max_v - min_v + v *= size + + s += int(v) + + print(f'{int(v)}, {s}', end=', ') + if i != len(values_min_max_sizes) - 1: + s *= values_min_max_sizes[i + 1][-1] + print(s) + return s + + +# bYl)0L + +def convert_to_words(val: int, size: int) -> str: + n_chars = len(chars) + words = [] + for _ in range(size - 1): + words.append(val % n_chars) + val = val // n_chars + + words.append(val) + msg = '' + for word in words: + msg += chars[word] + + return msg + + +def convert_to_int(msg: str) -> Union[int, None]: + if msg == '': + return None + + msg = msg[::-1] + + n_chars = len(chars) + digit = len(msg) - 1 + + val: int = 0 + + for c in msg: + i = chars.find(c) + val += i * int(n_chars ** digit) + digit -= 1 + + return val diff --git a/keepaway/lib/messenger/free_form_messenger.py b/keepaway/lib/messenger/free_form_messenger.py new file mode 100644 index 00000000..83eca1a5 --- /dev/null +++ b/keepaway/lib/messenger/free_form_messenger.py @@ -0,0 +1,17 @@ +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.coach.gloabl_world_model import GlobalWorldModel + +class FreeFormMessenger(Messenger): + def __init__(self) -> None: + super().__init__() + + def encode(self, wm: 'GlobalWorldModel') -> str: + return super().encode(wm) + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + return super().decode(messenger_memory, sender, current_time) \ No newline at end of file diff --git a/keepaway/lib/messenger/goalie_messenger.py b/keepaway/lib/messenger/goalie_messenger.py new file mode 100644 index 00000000..5e02718a --- /dev/null +++ b/keepaway/lib/messenger/goalie_messenger.py @@ -0,0 +1,66 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class GoalieMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.GOALIE], [ + (53. - 16., 53., 160), + (-20., 20., 400), + (0, 360, 360), + ]) + + def __init__(self, + goalie_unum: int = None, + goalie_pos: Vector2D = None, + goalie_body: AngleDeg = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._goalie_unum: int = goalie_unum + self._goalie_pos: Vector2D = goalie_pos.copy() + self._goalie_body: AngleDeg = goalie_body.copy() + else: + self._goalie_unum: int = None + self._goalie_pos: Vector2D = None + self._goalie_body: AngleDeg = None + + self._size = Messenger.SIZES[Messenger.Types.GOALIE] + self._header = Messenger.Types.GOALIE.value + + self._message = message + + def encode(self) -> str: + if self._goalie_pos.x() < 53. - 16 or self._goalie_pos.x() > 53 or self._goalie_pos.abs_y() > 20: + log.sw_log().communication().add_text(f'(goalie player messenger) goalie pos over poisition range' + f': {self._goalie_pos}') + return '' + + SP = ServerParam.i() + ep = 0.001 + msg = GoalieMessenger.CONVERTER.convert_to_word([ + bound(-SP.pitch_half_length() + ep, self._goalie_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._goalie_pos.y(), SP.pitch_half_width() - ep), + bound(ep, self._goalie_body.degree() + 180, 360-ep), + ]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + gpx, gpy, gb = GoalieMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_opponent_goalie(sender, Vector2D(gpx, gpy), current_time, body=AngleDeg(gb-180)) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/goalie_player_messenger.py b/keepaway/lib/messenger/goalie_player_messenger.py new file mode 100644 index 00000000..f253ff4f --- /dev/null +++ b/keepaway/lib/messenger/goalie_player_messenger.py @@ -0,0 +1,84 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class GoaliePlayerMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.GOALIE_PLAYER], [ + (53. - 16., 53., 160), + (-20., 20., 400), + (0, 360, 360), + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 190), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 124), + ]) + + def __init__(self, + goalie_unum: int = None, + goalie_pos: Vector2D = None, + goalie_body: AngleDeg = None, + player_unum: int = None, + player_pos: Vector2D = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._goalie_unum: int = goalie_unum + self._goalie_pos: Vector2D = goalie_pos.copy() + self._goalie_body: AngleDeg = goalie_body.copy() + self._player_unum: int = player_unum + self._player_pos: Vector2D = player_pos.copy() + else: + self._goalie_unum: int = None + self._goalie_pos: Vector2D = None + self._goalie_body: AngleDeg = None + self._player_unum: int = None + self._player_pos: Vector2D = None + + self._size = Messenger.SIZES[Messenger.Types.GOALIE_PLAYER] + self._header = Messenger.Types.GOALIE_PLAYER.value + + self._message = message + + def encode(self) -> str: + if self._goalie_pos.x() < 53. - 16 or self._goalie_pos.x() > 53 or self._goalie_pos.abs_y() > 20: + log.sw_log().communication().add_text(f'(goalie player messenger) goalie pos over poisition range' + f': {self._goalie_pos}') + return '' + + if not (1<=self._player_unum<=22): + log.sw_log().communication().add_text(f'(goalie player messenger) player unum invalid' + f': {self._player_unum}') + return '' + + SP = ServerParam.i() + ep = 0.001 + msg = GoaliePlayerMessenger.CONVERTER.convert_to_word([ + bound(-SP.pitch_half_length() + ep, self._goalie_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._goalie_pos.y(), SP.pitch_half_width() - ep), + bound(ep, self._goalie_body.degree() + 180, 360-ep), + self._player_unum, + bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), + bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), + ]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + gpx, gpy, gb, pu, ppx, ppy = GoaliePlayerMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_opponent_goalie(sender, Vector2D(gpx, gpy), current_time, body=AngleDeg(gb-180)) # TODO IMP FUNC + messenger_memory.add_player(sender,pu, Vector2D(ppx, ppy), current_time) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/messenger.py b/keepaway/lib/messenger/messenger.py new file mode 100644 index 00000000..499d08b8 --- /dev/null +++ b/keepaway/lib/messenger/messenger.py @@ -0,0 +1,139 @@ +from enum import Enum, unique +from typing import TYPE_CHECKING + +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.messenger.messenger_memory import MessengerMemory + +from keepaway.lib.rcsc.game_time import GameTime + +from keepaway.lib.rcsc.server_param import ServerParam +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class Messenger: + DEBUG = True + + class Types(Enum): + BALL = 'b' + PASS = 'p' + NONE = '' + BALL_PLAYER = 'B' + BALL_GOALIE = 'G' + GOALIE_PLAYER = 'e' + GOALIE = 'g' + THREE_PLAYER = 'R' + TWO_PLAYER = 'Q' + ONE_PLAYER = 'P' + RECOVERY = 'r' + STAMINA = 's' + + + SIZES: dict[Types, int] = { + Types.BALL: 6, + Types.PASS: 10, + Types.BALL_PLAYER: 10, + Types.BALL_GOALIE: 10, + Types.GOALIE_PLAYER: 8, + Types.GOALIE: 5, + Types.THREE_PLAYER: 10, + Types.TWO_PLAYER: 7, + Types.ONE_PLAYER: 4, + Types.RECOVERY: 2, + Types.STAMINA: 2, + + } + + def __init__(self, message: str = None) -> None: + self._type: Messenger.Types = Messenger.Types.NONE + self._size: int = 0 + self._message: str = message + self._header: str = None + + def encode(self) -> str: + pass + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + pass + + def size(self): + return self._size + + def type(self): + return self._type + + @staticmethod + def encode_all(messages: list['Messenger']): + max_message_size = ServerParam.i().player_say_msg_size() + size = 0 + all_messages = "" + log.os_log().debug(f'#'*20) + for i, message in enumerate(messages): + log.os_log().debug(f'msg.t={message._header}') + enc = message.encode() + log.os_log().debug(f'enc: {enc}') + + if not enc: + continue + + if len(enc) + size > max_message_size: + log.os_log().warn("(Messenger encode all) out of limitation. Deny other messages.") + log.os_log().warn("denied messages are:") + for denied in messages[i:]: + log.os_log().warn(denied) + break + + if Messenger.DEBUG: + log.sw_log().action().add_text( f"(encode all messages) a message added, msg={message}, encoded={enc}") + + all_messages += enc + size += len(enc) + return all_messages + + @staticmethod + def decode_all(messenger_memory: MessengerMemory, messages: str, sender: int, current_time: GameTime): + from keepaway.lib.messenger.pass_messenger import PassMessenger + from keepaway.lib.messenger.ball_goalie_messenger import BallGoalieMessenger + from keepaway.lib.messenger.ball_messenger import BallMessenger + from keepaway.lib.messenger.ball_player_messenger import BallPlayerMessenger + from keepaway.lib.messenger.goalie_messenger import GoalieMessenger + from keepaway.lib.messenger.goalie_player_messenger import GoaliePlayerMessenger + from keepaway.lib.messenger.one_player_messenger import OnePlayerMessenger + from keepaway.lib.messenger.recovery_message import RecoveryMessenger + from keepaway.lib.messenger.stamina_messenger import StaminaMessenger + from keepaway.lib.messenger.three_player_messenger import ThreePlayerMessenger + from keepaway.lib.messenger.two_player_messenger import TwoPlayerMessenger + + messenger_classes: dict[Messenger.Types, type['Messenger']] = { + Messenger.Types.BALL: BallMessenger, + Messenger.Types.PASS: PassMessenger, + Messenger.Types.BALL_PLAYER: BallPlayerMessenger, + Messenger.Types.BALL_GOALIE: BallGoalieMessenger, + Messenger.Types.GOALIE_PLAYER: GoaliePlayerMessenger, + Messenger.Types.GOALIE: GoalieMessenger, + Messenger.Types.THREE_PLAYER: ThreePlayerMessenger, + Messenger.Types.TWO_PLAYER: TwoPlayerMessenger, + Messenger.Types.ONE_PLAYER: OnePlayerMessenger, + Messenger.Types.RECOVERY: RecoveryMessenger, + Messenger.Types.STAMINA: StaminaMessenger, + } + + index = 0 + log.os_log().debug('*'*100) + log.os_log().debug(sender) + log.os_log().debug(messages) + while index < len(messages): + message_type = Messenger.Types(messages[index]) + message_size = Messenger.SIZES[message_type] + + message = messages[index+1: index+message_size] + log.os_log().debug(messages[index: index + message_size]) + + messenger_classes[message_type](message=message).decode(messenger_memory, sender, current_time) + index += message_size + + + + diff --git a/keepaway/lib/messenger/messenger_memory.py b/keepaway/lib/messenger/messenger_memory.py new file mode 100644 index 00000000..21552d45 --- /dev/null +++ b/keepaway/lib/messenger/messenger_memory.py @@ -0,0 +1,180 @@ +from typing import Union + +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.rcsc.game_time import GameTime + + +class MessengerMemory: + class Player: + def __init__(self, + sender:int=0, + unum:int=0, + pos:Vector2D=Vector2D(), + body:float=-360, + stamina:float=-1) -> None: + self.sender_: int = sender + self.pos_: Vector2D = pos.copy() + self.unum_: int = unum + self.body_: float = body + self.stamina_: float = stamina + + class Ball: + def __init__(self, sender=0, pos: Vector2D = Vector2D(), vel: Vector2D = None) -> None: + self.sender_: int = sender + self.pos_: Vector2D = pos.copy() + self.vel_: Vector2D = vel.copy() + + class Goalie: + def __init__(self, sender = 0, pos: Vector2D = Vector2D(), body: AngleDeg = None): + self.sender_ = sender + self.pos_ = pos.copy() + self.body_ = body.copy() + + class Pass: + def __init__(self, sender: int, receiver: int, pos: Vector2D): + self._sender = sender + self._receiver = receiver + self._pos = pos.copy() + + class Stamina: + def __init__(self, sender: int, rate: float): + self.rate_ = rate + self.sender_ = sender + + + class Recovery: + def __init__(self, sender: int, rate: float): + self.rate_ = rate + self.sender_ = sender + + def __init__(self) -> None: + self._time: GameTime = GameTime() + + self._players: list[MessengerMemory.Player] = [] + self._player_time: GameTime = GameTime() + + self._balls: list[MessengerMemory.Ball] = [] + self._ball_time: GameTime = GameTime() + + self._pass: list[MessengerMemory.Pass] = [] + self._pass_time: GameTime = GameTime() + + self._goalie: list[MessengerMemory.Goalie] = [] + self._goalie_time: GameTime = GameTime() + + self._player_record: list[tuple[GameTime, MessengerMemory.Player]] = [] + + self._stamina: list[MessengerMemory.Stamina] = [] + self._stamina_time: GameTime = GameTime() + + self._recovery: list[MessengerMemory.Recovery] = [] + self._recovery_time: GameTime = GameTime() + + def add_ball(self, sender: int, pos: Vector2D, vel: Vector2D, current_time: GameTime): + if self._ball_time != current_time: + self._balls.clear() + + self._balls.append(MessengerMemory.Ball(sender, pos, vel)) + self._ball_time = current_time.copy() + self._time = current_time.copy() + + def add_player(self, + sender: int, + unum: int, + pos: Vector2D, + current_time: GameTime, + body: float = -360., + stamina: float = -1.): + if self._player_time != current_time: + self._players.clear() + + self._players.append(MessengerMemory.Player(sender, unum, pos, body, stamina)) + self._player_time = current_time.copy() + + self._player_record.append((current_time, self._players[-1])) + if len(self._player_record) > 30: + self._player_record = self._player_record[1:] + + self._time = current_time.copy() + + def add_pass(self, sender: int, receiver: int, pos: Vector2D, current: GameTime): + if self._pass_time != current: + self._pass.clear() + + self._pass.append(MessengerMemory.Pass(sender, receiver, pos)) + self._pass_time = current.copy() + + self._time = current.copy() + + def add_opponent_goalie(self, sender:int, pos: Vector2D, current_time: GameTime, body: Union[AngleDeg, float]): + if self._goalie_time != current_time: + self._goalie.clear() + + self._goalie.append(MessengerMemory.Goalie(sender, pos, AngleDeg(body))) + self._goalie_time = current_time.copy() + + self._time = current_time.copy() + + def add_stamina(self, sender: int, rate: float, current_time: GameTime): + if self._stamina_time != current_time: + self._stamina.clear() + + self._stamina.append(MessengerMemory.Stamina(sender, rate)) + self._stamina_time = current_time.copy() + + self._time = current_time.copy() + + def add_recovery(self, sender: int, rate: float, current_time: GameTime): + if self._recovery_time != current_time: + self._recovery.clear() + + self._recovery.append(MessengerMemory.Recovery(sender, rate)) + self._recovery_time = current_time.copy() + + self._time = current_time.copy() + + def goalie_time(self): + return self._goalie_time + + def time(self): + return self._time + + def players(self): + return self._players + + def player_time(self): + return self._player_time + + def balls(self): + return self._balls + + def ball_time(self): + return self._ball_time + + def pass_time(self): + return self._pass_time + + def pass_(self): + return self._pass + + def player_record(self): + return self._player_record + + def goalie(self): + return self._goalie + + def recovery(self): + return self._recovery + + def recovery_time(self): + return self._recovery_time + + def stamina(self): + return self._stamina + + def stamina_time(self): + return self._stamina_time + + + diff --git a/keepaway/lib/messenger/one_player_messenger.py b/keepaway/lib/messenger/one_player_messenger.py new file mode 100644 index 00000000..9db10e26 --- /dev/null +++ b/keepaway/lib/messenger/one_player_messenger.py @@ -0,0 +1,69 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class OnePlayerMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.ONE_PLAYER], [ + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 168), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + ]) + + def __init__(self, + u1: int = None, + p1: Vector2D = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._unums: list[int] = [u1] + self._player_poses: list[Vector2D] = [p1.copy()] + else: + self._unums: list[int] = None + self._player_poses: list[Vector2D] = None + + self._size = Messenger.SIZES[Messenger.Types.ONE_PLAYER] + self._header = Messenger.Types.ONE_PLAYER.value + + self._message = message + + def encode(self) -> str: + data = [] + SP = ServerParam.i() + ep = 0.001 + for p, u in zip(self._player_poses, self._unums): + if not 1 <= u <= 22: + log.os_log().error(f'(ball player messenger) illegal unum={u}') + log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') + return '' + data.append(u) + data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) + data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) + + msg = OnePlayerMessenger.CONVERTER.convert_to_word(data) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + data = OnePlayerMessenger.CONVERTER.convert_to_values(self._message) + for i in range(1): + u = data[i * 3] + px = data[i * 3 + 1] + py = data[i * 3 + 2] + + messenger_memory.add_player(sender,u, Vector2D(px, py), current_time) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/pass_messenger.py b/keepaway/lib/messenger/pass_messenger.py new file mode 100644 index 00000000..44b154dd --- /dev/null +++ b/keepaway/lib/messenger/pass_messenger.py @@ -0,0 +1,90 @@ +from pyrusgeom.soccer_math import min_max, bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +import keepaway.lib.messenger.converters as converters + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class PassMessenger(Messenger): + # CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.PASS], [ + # (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), + # (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), + # (1, 12, 11), + # (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), + # (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), + # (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + # (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + # ]) + + ## TODO - keepway custom i dont expect this to change from the orignal pitch size + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.PASS], [ + (-ServerParam.i().keepaway_length()/2, ServerParam.i().keepaway_length()/2, 2 ** 10), + (-ServerParam.i().keepaway_width()/2, ServerParam.i().keepaway_width()/2, 2 ** 9), + (1, 12, 11), + (-ServerParam.i().keepaway_length()/2, ServerParam.i().keepaway_length()/2, 2 ** 10), + (-ServerParam.i().keepaway_width()/2, ServerParam.i().keepaway_width()/2, 2 ** 9), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), + ]) + def __init__(self, + receiver_unum: int = None, + receive_point: Vector2D = None, + ball_pos: Vector2D = None, + ball_vel: Vector2D = None, + message: str = None) -> None: + super().__init__() + self._size = Messenger.SIZES[Messenger.Types.PASS] + self._header = Messenger.Types.PASS.value + + if message: + self._message = message + return + self._receiver_unum = receiver_unum + self._receive_point = receive_point.copy() + self._ball_pos = ball_pos.copy() + self._ball_vel = ball_vel.copy() + + def encode(self) -> str: + SP = ServerParam.i() + ep = 0.001 + # msg = PassMessenger.CONVERTER.convert_to_word([ + # bound(-SP.pitch_half_length() + ep, self._receive_point.x(), SP.pitch_half_length() - ep), + # bound(-SP.pitch_half_width() + ep, self._receive_point.y(), SP.pitch_half_width() - ep), + # self._receiver_unum, + # bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), + # bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), + # bound(ep, self._ball_vel.x(), SP.ball_speed_max() - ep), + # bound(ep, self._ball_vel.y(), SP.ball_speed_max() - ep), + # ]) + + msg = PassMessenger.CONVERTER.convert_to_word([ + bound(-SP.keepaway_length()/2 + ep, self._receive_point.x(), SP.keepaway_length()/2 - ep), + bound(-SP.keepaway_width()/2 + ep, self._receive_point.y(), SP.keepaway_width()/2 - ep), + self._receiver_unum, + bound(-SP.keepaway_length()/2 + ep, self._ball_pos.x(), SP.keepaway_length()/2 - ep), + bound(-SP.keepaway_width()/2 + ep, self._ball_pos.y(), SP.keepaway_width()/2 - ep), + bound(ep, self._ball_vel.x(), SP.ball_speed_max() - ep), + bound(ep, self._ball_vel.y(), SP.ball_speed_max() - ep), + ]) + + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + rpx, rpy, ru, bpx, bpy, bvx, bvy = PassMessenger.CONVERTER.convert_to_values(self._message) + + messenger_memory.add_pass(sender, ru, Vector2D(rpx, rpy), current_time) + messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) + + def __repr__(self) -> str: + return "ball pos vel msg" + diff --git a/keepaway/lib/messenger/player_pos_unum_messenger.py b/keepaway/lib/messenger/player_pos_unum_messenger.py new file mode 100644 index 00000000..e2ad5297 --- /dev/null +++ b/keepaway/lib/messenger/player_pos_unum_messenger.py @@ -0,0 +1,80 @@ +from keepaway.lib.debug.debug import log +from keepaway.lib.debug.level import Level +from pyrusgeom.soccer_math import min_max +from pyrusgeom.vector_2d import Vector2D +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +import keepaway.lib.messenger.converters as converters + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + +class PlayerPosUnumMessenger(Messenger): + def __init__(self, unum: int = None, message:str=None) -> None: + super().__init__() + self._size = Messenger.SIZES[Messenger.Types.PLAYER_POS_VEL_UNUM] + self._header = Messenger.Types.PLAYER_POS_VEL_UNUM.value + + if message: + self._unum: int = None + self._message = message + else: + self._unum: int = unum + self._message = None + + def encode(self) -> str: + if not 1 <= self._unum <= 22: + log.os_log().error(f"(player pos unum messenger encode) unum is out of limit. unum={self._unum}") + return "" + + unum = self._unum % 11 + player = wm.our_player(unum) if self._unum // 11 == 0 else wm.their_player(unum) + + if player is None: + log.os_log().error(f"(player pos unum messenger encode) player is None. unum={self._unum}") + return None + + SP = ServerParam.i() + + pos = player.pos() + x:float = min_max(-SP.pitch_half_length(), pos.x(), SP.pitch_half_length()) + SP.pitch_half_length() + y:float = min_max(-SP.pitch_half_width(), pos.y(), SP.pitch_half_width()) + SP.pitch_half_width() + + val = self._unum - 1 + + val *= 168 + val += int(x/0.63) + + val *= 109 + val += int(y/0.63) + + return self._header + converters.convert_to_words(val, self._size - 1) + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + SP = ServerParam.i() + + val = converters.convert_to_int(self._message[1:]) + if val is None: + return + + y = (val % 109)*0.63 - SP.pitch_half_width() + val = int(val/109) + + x = (val % 168)*0.63 - SP.pitch_half_length() + val = int(val/168) + + unum = (val%22) + 1 + pos = Vector2D(x,y) + + log.sw_log().sensor().add_text( f"(PlayerPosUnumMessenger decode) receive a player. unum={unum}, pos={pos}") + + messenger_memory.add_player(sender, unum, pos, current_time) + + def __repr__(self) -> str: + return "player pos unum msg" + \ No newline at end of file diff --git a/keepaway/lib/messenger/recovery_message.py b/keepaway/lib/messenger/recovery_message.py new file mode 100644 index 00000000..6756b1ee --- /dev/null +++ b/keepaway/lib/messenger/recovery_message.py @@ -0,0 +1,34 @@ +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + + +class RecoveryMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.RECOVERY], [ + (ServerParam.i().recover_min(), ServerParam.i().recover_init()+0.01, 74) + ]) + + def __init__(self, + recovery: float = None, + message: str = None): + super().__init__() + self._recovery: float = recovery + + self._size = Messenger.SIZES[Messenger.Types.RECOVERY] + self._header = Messenger.Types.RECOVERY.value + + self._message = message + + def encode(self) -> str: + msg = RecoveryMessenger.CONVERTER.convert_to_word([self._recovery]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + rate = RecoveryMessenger.CONVERTER.convert_to_values(self._message)[0] + + messenger_memory.add_recovery(sender, rate, current_time) # TODO IMP FUNC + + def __repr__(self): + return 'recovery message' diff --git a/keepaway/lib/messenger/stamina_messenger.py b/keepaway/lib/messenger/stamina_messenger.py new file mode 100644 index 00000000..a813dc4e --- /dev/null +++ b/keepaway/lib/messenger/stamina_messenger.py @@ -0,0 +1,34 @@ +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + + +class StaminaMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.STAMINA], [ + (0, ServerParam.i().stamina_max()+1, 74) + ]) + + def __init__(self, + stamina: float = None, + message: str = None): + super().__init__() + self._stamina: float = stamina + + self._size = Messenger.SIZES[Messenger.Types.STAMINA] + self._header = Messenger.Types.STAMINA.value + + self._message = message + + def encode(self) -> str: + msg = StaminaMessenger.CONVERTER.convert_to_word([self._stamina]) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + rate = StaminaMessenger.CONVERTER.convert_to_values(self._message)[0] + + messenger_memory.add_stamina(sender, rate, current_time) + + def __repr__(self): + return 'recovery message' diff --git a/keepaway/lib/messenger/three_player_messenger.py b/keepaway/lib/messenger/three_player_messenger.py new file mode 100644 index 00000000..1b8d1233 --- /dev/null +++ b/keepaway/lib/messenger/three_player_messenger.py @@ -0,0 +1,78 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class ThreePlayerMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.THREE_PLAYER], [ + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + ]) + + def __init__(self, + u1: int = None, + p1: Vector2D = None, + u2: int = None, + p2: Vector2D = None, + u3: int = None, + p3: Vector2D = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._unums: list[int] = [u1, u2, u3] + self._player_poses: list[Vector2D] = [p1.copy(), p2.copy(), p3.copy()] + else: + self._unums: list[int] = None + self._player_poses: list[Vector2D] = None + + self._size = Messenger.SIZES[Messenger.Types.THREE_PLAYER] + self._header = Messenger.Types.THREE_PLAYER.value + + self._message = message + + def encode(self) -> str: + data = [] + SP = ServerParam.i() + ep = 0.001 + for p, u in zip(self._player_poses,self._unums): + if not 1 <= u <= 22: + log.os_log().error(f'(ball player messenger) illegal unum={u}') + log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') + return '' + data.append(u) + data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) + data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) + + msg = ThreePlayerMessenger.CONVERTER.convert_to_word(data) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + data = ThreePlayerMessenger.CONVERTER.convert_to_values(self._message) + for i in range(3): + u = data[i*3] + px = data[i*3 + 1] + py = data[i*3 + 2] + + messenger_memory.add_player(sender,u, Vector2D(px, py), current_time) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/messenger/two_player_messenger.py b/keepaway/lib/messenger/two_player_messenger.py new file mode 100644 index 00000000..85d7cf53 --- /dev/null +++ b/keepaway/lib/messenger/two_player_messenger.py @@ -0,0 +1,74 @@ +from pyrusgeom.angle_deg import AngleDeg +from pyrusgeom.soccer_math import bound +from pyrusgeom.vector_2d import Vector2D + +from keepaway.lib.debug.debug import log +from keepaway.lib.messenger.converters import MessengerConverter +from keepaway.lib.messenger.messenger import Messenger + +from keepaway.lib.messenger.messenger_memory import MessengerMemory +from keepaway.lib.rcsc.game_time import GameTime +from keepaway.lib.rcsc.server_param import ServerParam + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from keepaway.lib.player.world_model import WorldModel + + +class TwoPlayerMessenger(Messenger): + CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.TWO_PLAYER], [ + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + (1, 23, 22), + (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), + (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), + ]) + + def __init__(self, + u1: int = None, + p1: Vector2D = None, + u2: int = None, + p2: Vector2D = None, + message: str = None) -> None: + super().__init__() + if message is None: + self._unums: list[int] = [u1, u2] + self._player_poses: list[Vector2D] = [p1.copy(), p2.copy()] + else: + self._unums: list[int] = None + self._player_poses: list[Vector2D] = None + + self._size = Messenger.SIZES[Messenger.Types.TWO_PLAYER] + self._header = Messenger.Types.TWO_PLAYER.value + + self._message = message + + def encode(self) -> str: + data = [] + SP = ServerParam.i() + ep = 0.001 + for p, u in zip(self._player_poses, self._unums): + if not 1 <= u <= 22: + log.os_log().error(f'(ball player messenger) illegal unum={u}') + log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') + return '' + data.append(u) + data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) + data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) + + msg = TwoPlayerMessenger.CONVERTER.convert_to_word(data) + return f'{self._header}{msg}' + + def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: + data = TwoPlayerMessenger.CONVERTER.convert_to_values(self._message) + for i in range(2): + u = data[i * 3] + px = data[i * 3 + 1] + py = data[i * 3 + 2] + + messenger_memory.add_player(sender, u, Vector2D(px, py), current_time) # TODO IMP FUNC + + def __repr__(self) -> str: + return "ball player msg" diff --git a/keepaway/lib/network/__init__.py b/keepaway/lib/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/network/udp_socket.py b/keepaway/lib/network/udp_socket.py new file mode 100644 index 00000000..62091682 --- /dev/null +++ b/keepaway/lib/network/udp_socket.py @@ -0,0 +1,51 @@ +import socket + +import team_config + +MAX_BUFF_SIZE = 8192 + + +class IPAddress: + def __init__(self, ip, port): + self._ip = ip + self._port = port + + def tuple(self) -> tuple: + return self.ip(), self.port() + + def __repr__(self): + return self.ip(), self.port() + + def __str__(self): + return f"({self.ip()}:{self.port()}" + + def ip(self): + return self._ip + + def port(self): + return self._port + + +class UDPSocket: + def __init__(self, ip_address: IPAddress): + self._ip: IPAddress = ip_address + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(team_config.SOCKET_INTERVAL) + self._receive_first_message = False + + def send_msg(self, msg: str): + if msg[-1] != '\0': + msg += '\0' + return self._sock.sendto(msg.encode(), self._ip.tuple()) + + def receive_msg(self): + try: + message, server_address = self._sock.recvfrom(MAX_BUFF_SIZE) + if not self._receive_first_message: + self._receive_first_message = True + self._ip._port = server_address[1] + return len(message), message, server_address + except: + message = "" + server_address = 0 + return len(message), message, server_address diff --git a/keepaway/lib/parser/__init__.py b/keepaway/lib/parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keepaway/lib/parser/global_message_parser.py b/keepaway/lib/parser/global_message_parser.py new file mode 100644 index 00000000..3f5ad0c6 --- /dev/null +++ b/keepaway/lib/parser/global_message_parser.py @@ -0,0 +1,129 @@ +from keepaway.lib.parser.parser_message_params import MessageParamsParser + +""" + sample version >= 7.0 + (see_global TIME ((g l) -52.5 0) ((g r) 52.5 0) ((b) ) + ((p "TEAM" UNUM[ goalie]) [ ][ {t|k}][ {y|r}]) + ....) + <-- arm is global + <-- 't' means tackle + <-- 'k' means kick + <-- 'f' means foul charged + <-- 'y' means yellow card + <-- 'r' means red card + (ok look TIME ((g l) -52.5 0) ((g r) 52.5 0) ((b) ) + ((p "TEAM" UNUM[ goalie]) ) <-- no arm & tackle + ....) + +""" + + +class GlobalFullStateWorldMessageParser: + def __init__(self): + self._dic = {} + + def parse(self, message: str): + self._dic['time'] = message.split(" ")[1] + message = message[message.find("(", 1):-1] + + # parsing ball + msg = message[:message.find("((p")] + MessageParamsParser._parse(self._dic, msg) + + # and now parsing players + msg = message[message.find("((p"):] + self._dic.update(PlayerMessageParser().parse(msg)) + self._dic.update({"teams": { + "team_left": PlayerMessageParser._team_l, + "team_right": PlayerMessageParser._team_r + }}) + + def dic(self): + return self._dic + + +class PlayerMessageParser: + _team_l = None + _team_r = None + + def __init__(self): + self._dic = {} + + @staticmethod + def _parser(dic: dict, message: str): + players = [] + seek = 0 + if len(message) < 5: + return + while seek < len(message): + seek = message.find("((p", seek) + next_seek = message.find("((p", seek + 1) + + if next_seek == -1: + next_seek = len(message) + msg = message[seek: next_seek].strip(" ()").split(" ") + k = -1 + kk = 0 + if msg[3] == 'g' or msg[3] == 'goalie)': + k = 0 + player_dic = { + "unum": msg[2].strip("()"), + "pos_x": msg[4 + k], + "pos_y": msg[5 + k], + "vel_x": msg[6 + k], + "vel_y": msg[7 + k], + "body": msg[8 + k], + "neck": msg[9 + k], + } + if PlayerMessageParser._team_l == msg[1]: + player_dic['side_id'] = 'l' + elif PlayerMessageParser._team_r == msg[1]: + player_dic['side_id'] = 'r' + elif PlayerMessageParser._team_l is None: + PlayerMessageParser._team_l = msg[1] + player_dic['side_id'] = 'l' + elif PlayerMessageParser._team_r is None: + PlayerMessageParser._team_r = msg[1] + player_dic['side_id'] = 'r' + + if k == 0: + player_dic['goalie'] = 'g' + ext = [] + if ((k == 0 and len(msg) > 10) + or (k == -1 and len(msg) > 9)): + ext = msg[10 + k:] + if 'k' in ext: + player_dic['kick'] = True + if 't' in ext: + player_dic['tackle'] = True + if 'f' in ext: + player_dic['charged'] = True + if 'y' in ext: + player_dic['card'] = 'y' + if 'r' in ext: + player_dic['card'] = 'r' + + players.append(player_dic) + seek = next_seek + dic["players"] = players + + @staticmethod + def n_inner_dict(message: str): + # dlog.debug(f"message {message}") + n = 0 + for c in message[1:-1]: + if c == '(': + n += 1 + # dlog.debug(f"n {n}") + return n + + def parse(self, message): + PlayerMessageParser._parser(self._dic, message) + return self._dic + +# message = '(fullstate 109 (pmode play_on) (vmode high normal) (count 0 25 82 0 79 0 0 0) (arm (movable 0) (expires 0) (target 0 0) (count 0)) (score 0 0) ((b) 0 0 0 0) ((p r 10 9) 0.00733964 -23.0363 -0.399337 -0.0830174 -164.67 -90 44.2236 1.38729 (stamina 7539.49 0.935966 1 129861)) ((p r 11 10) 3.75961 -2.09864 -0.327071 0.126905 153.836 13 (stamina 7615.44 0.854839 1 129617))) ' +# msg = message[message.find("((p"):] +# a =PlayerMessageParser() +# d = a.parse(msg) +# for p in d['players']: +# debug_print(p['unum'], p['stamina']) diff --git a/keepaway/lib/parser/message_params_parser_see.py b/keepaway/lib/parser/message_params_parser_see.py new file mode 100644 index 00000000..d0c4b384 --- /dev/null +++ b/keepaway/lib/parser/message_params_parser_see.py @@ -0,0 +1,19 @@ +class MessageParamsParserSee: + def __init__(self) -> None: + pass + + def parse(self, string) -> list: + res = [] + objects_start_index = string.find("((") + + if objects_start_index == -1: + return [] + + objects_string = string[objects_start_index: -1] + objects_list_string = objects_string[1:-1].split(") (") + + for object_string in objects_list_string: + key_end_index = object_string.find(")") + key = object_string[1:key_end_index] + res.append((key, object_string[key_end_index+1:].strip(" "))) + return res \ No newline at end of file diff --git a/keepaway/lib/parser/parser_message_fullstate_world.py b/keepaway/lib/parser/parser_message_fullstate_world.py new file mode 100644 index 00000000..7684b47c --- /dev/null +++ b/keepaway/lib/parser/parser_message_fullstate_world.py @@ -0,0 +1,157 @@ +from keepaway.lib.parser.parser_message_params import MessageParamsParser + +"""" + (fullstate