├── .gitignore ├── LICENSE ├── README.md ├── base ├── basic_tackle.py ├── bhv_block.py ├── bhv_kick.py ├── bhv_move.py ├── decision.py ├── formation_dt │ ├── before_kick_off.conf │ ├── defense_formation.conf │ ├── goalie_kick_opp_formation.conf │ ├── goalie_kick_our_formation.conf │ ├── kickin_our_formation.conf │ ├── offense_formation.conf │ ├── setplay_opp_formation.conf │ └── setplay_our_formation.conf ├── generator_action.py ├── generator_clear.py ├── generator_dribble.py ├── generator_pass.py ├── generator_shoot.py ├── goalie_decision.py ├── sample_coach.py ├── sample_communication.py ├── sample_player.py ├── sample_trainer.py ├── set_play │ ├── bhv_goalie_set_play.py │ ├── bhv_set_play.py │ └── bhv_set_play_before_kick_off.py ├── stamina_manager.py ├── strategy.py ├── strategy_formation.py ├── tackle_generator.py ├── tools.py └── view_tactical.py ├── docs └── algorithms ├── lib ├── action │ ├── basic_actions.py │ ├── go_to_point.py │ ├── hold_ball.py │ ├── intercept.py │ ├── intercept_info.py │ ├── intercept_player.py │ ├── intercept_self.py │ ├── intercept_table.py │ ├── kick_table.py │ ├── neck_body_to_ball.py │ ├── neck_body_to_point.py │ ├── neck_scan_field.py │ ├── neck_scan_players.py │ ├── neck_turn_to_ball.py │ ├── neck_turn_to_ball_or_scan.py │ ├── neck_turn_to_point.py │ ├── neck_turn_to_relative.py │ ├── scan_field.py │ ├── smart_kick.py │ ├── stop_ball.py │ ├── turn_to_ball.py │ ├── turn_to_point.py │ └── view_wide.py ├── coach │ ├── coach_agent.py │ ├── gloabl_world_model.py │ └── global_object.py ├── debug │ ├── color.py │ ├── debug.py │ ├── debug_client.py │ ├── level.py │ ├── os_logger.py │ ├── sw_logger.py │ └── timer.py ├── formation │ └── delaunay_triangulation.py ├── messenger │ ├── ball_goalie_messenger.py │ ├── ball_messenger.py │ ├── ball_player_messenger.py │ ├── ball_pos_vel_messenger.py │ ├── converters.py │ ├── free_form_messenger.py │ ├── goalie_messenger.py │ ├── goalie_player_messenger.py │ ├── messenger.py │ ├── messenger_memory.py │ ├── one_player_messenger.py │ ├── pass_messenger.py │ ├── player_pos_unum_messenger.py │ ├── recovery_message.py │ ├── stamina_messenger.py │ ├── three_player_messenger.py │ └── two_player_messenger.py ├── network │ └── udp_socket.py ├── parser │ ├── global_message_parser.py │ ├── message_params_parser_see.py │ ├── parser_message_fullstate_world.py │ └── parser_message_params.py ├── player │ ├── action_effector.py │ ├── basic_client.py │ ├── localizer.py │ ├── object.py │ ├── object_ball.py │ ├── object_player.py │ ├── object_self.py │ ├── object_table.py │ ├── player_agent.py │ ├── sensor │ │ ├── body_sensor.py │ │ ├── say_message_builder.py │ │ ├── see_state.py │ │ └── visual_sensor.py │ ├── soccer_action.py │ ├── soccer_agent.py │ ├── stamina_model.py │ ├── trainer_agent.py │ ├── view_area.py │ └── world_model.py ├── player_command │ ├── coach_command.py │ ├── player_command.py │ ├── player_command_body.py │ ├── player_command_sender.py │ ├── player_command_support.py │ └── trainer_command.py └── rcsc │ ├── game_mode.py │ ├── game_time.py │ ├── player_type.py │ ├── server_param.py │ └── types.py ├── main.py ├── requirements.txt ├── scripts ├── create_binary.sh ├── start └── startAll ├── start.sh ├── team_config.py ├── tests ├── test_messages.py └── test_visual_sensor.py └── train.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | */__pycache__/ 3 | */__pycache__/* 4 | *.pyc 5 | venv/ 6 | lib/network/__pycache__/udp_socket.cpython-36.pyc 7 | lib/Player/__pycache__/ 8 | lib/math/__pycache__/ 9 | variables 10 | accessors 11 | *.log 12 | main_test.py 13 | test.sh 14 | todos.py 15 | *.so 16 | *.c 17 | build/ 18 | binary/ 19 | 20 | test.py 21 | 22 | player-*.txt 23 | player-*.err 24 | log 25 | .gitignore 26 | test-log 27 | main_tmp.py 28 | logs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cyrus Soccer Simulation 2D Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PYRUS2D 2 | 3 | ## Robocup Soccer Simmulation 2D Python Base Code 4 | 5 | 6 | PYRUS2D is the first Python base code (sample team) for RoboCup Soccer 2D Simulator. 7 | This project is implemented by members of CYRUS soccer simulation 2D team. 8 | By using this project, a team includes 11 players and one coach can connect to RoboCup Soccer Server and play a game. 9 | Also, researchers can use the trainer to control the server for training proposes. 10 | 11 | --- 12 | ## Dependencies 13 | 14 | 15 | 16 | ### RoboCup Soccer Simulation Server and Monitor 17 | 18 | Install rcssserver and rcssmonitor (soccer window for debugging proposes) 19 | 20 | - rcssserver: [https://github.com/rcsoccersim/rcssserver](https://github.com/rcsoccersim/rcssserver) 21 | - rcssmonitor: [https://github.com/rcsoccersim/rcssmonitor](https://github.com/rcsoccersim/rcssmonitor) 22 | - soccer window: [https://github.com/helios-base/soccerwindow2](https://github.com/helios-base/soccerwindow2) 23 | 24 | ### Python requirements 25 | 26 | - Python v3.9 27 | - coloredlogs==15.0.1 28 | - pyrusgeom==0.1.2 29 | - scipy==1.10.1 30 | 31 | ```bash 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | --- 36 | 37 | ## Quick Start 38 | 39 | ### Run team (11 players and 1 coach) 40 | 41 | To run the team there are some options, 42 | 43 | - running agents(players and coach execute separately) 44 | 45 | ```bash 46 | cd Pyrus2D 47 | ./start.sh 48 | ``` 49 | 50 | - running the agents by Python (or running them separately in PyCharm) 51 | 52 | ```bash 53 | cd Pyrus2D 54 | python team/main_player.py (11 times) 55 | python team/main_coach.py 56 | ``` 57 | 58 | - running the agents by using one Python main process 59 | 60 | ```bash 61 | cd Pyrus2D 62 | python main.py 63 | ``` 64 | 65 | 66 | ## Start team by arguments 67 | 68 | 69 | To modify team configuration, you can pass the arguments or update ```team_config.py```. 70 | 71 | Configurations are listed bellow: 72 | 73 | ```bash 74 | # To change the teamname (default is PYRUS): 75 | -t|--teamname TeamName 76 | 77 | # Determines if the connecting player is goalie or not. (take no parameters) 78 | -g|--goalie 79 | 80 | # To change Output of loggers(Default is std): 81 | # - std: loggers print data on the terminal -> std output 82 | # - textfile: loggers write on the files based on players unum. (player-{unum}.txt, player-{unum}.err) 83 | -o|--out [std|textfile] 84 | 85 | # change the host(serve) ip adderess. (defualt is localhost) 86 | -H|--host new_ip_address 87 | 88 | # change the player port connection. (default is 6000) 89 | -p|--player-port new_port 90 | 91 | # change the coach port connection. (default is 6002) 92 | -P|--coach-port new_port 93 | 94 | # change the trainer port connection. (default is 6001) 95 | --trainer-port new_port 96 | 97 | ``` 98 | 99 | --- 100 | 101 | ## Useful links 102 | 103 | - CYRUS team: [https://cyrus2d.com/](https://cyrus2d.com/) 104 | - RoboCup: [https://www.robocup.org/](https://www.robocup.org/) 105 | - Soccer Simulation 2D League: [https://rcsoccersim.github.io/](https://rcsoccersim.github.io/) 106 | - Server documentation: [https://rcsoccersim.readthedocs.io/](https://rcsoccersim.readthedocs.io/) 107 | 108 | ## Related Papers 109 | 110 | - Zare N, Amini O, Sayareh A, Sarvmaili M, Firouzkouhi A, Rad SR, Matwin S, Soares A. Cyrus2D Base: Source Code 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) 111 | - 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) 112 | - 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) 113 | - Akiyama, H., Nakashima, T.: Helios 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. 114 | 115 | 116 | ## Cite and Support 117 | 118 | Please don't forget to cite our papers and star our GitHub repo if you haven't already! 119 | ## Contributing 120 | 121 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 122 | -------------------------------------------------------------------------------- /base/basic_tackle.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from pyrusgeom.line_2d import Line2D 4 | from pyrusgeom.ray_2d import Ray2D 5 | from pyrusgeom.vector_2d import Vector2D 6 | 7 | from base.tackle_generator import TackleGenerator 8 | from lib.action.neck_turn_to_point import NeckTurnToPoint 9 | from lib.debug.debug import log 10 | from lib.rcsc.server_param import ServerParam 11 | from lib.rcsc.types import Card 12 | 13 | if TYPE_CHECKING: 14 | from lib.player.player_agent import PlayerAgent 15 | 16 | 17 | class BasicTackle: 18 | def __init__(self, min_prob: float, body_thr: float): 19 | self._min_prob: float = min_prob 20 | self._body_thr: float = body_thr 21 | 22 | def execute(self, agent: 'PlayerAgent'): 23 | SP = ServerParam.i() 24 | wm = agent.world() 25 | 26 | use_foul = False 27 | tackle_prob = wm.self().tackle_probability() 28 | 29 | if wm.self().card() == Card.NO_CARD \ 30 | and (wm.ball().pos().x() > SP.our_penalty_area_line_x() + 0.5 31 | or wm.ball().pos().abs_y() > SP.penalty_area_half_width() + 0.5) \ 32 | and tackle_prob < wm.self().foul_probability(): 33 | tackle_prob = wm.self().foul_probability() 34 | use_foul = True 35 | 36 | if tackle_prob < self._min_prob: 37 | return False 38 | 39 | self_min = wm.intercept_table().self_reach_cycle() 40 | mate_min = wm.intercept_table().teammate_reach_cycle() 41 | opp_min = wm.intercept_table().opponent_reach_cycle() 42 | 43 | self_reach_point = wm.ball().inertia_point(self_min) 44 | 45 | self_goal = False 46 | if self_reach_point.x() < - SP.pitch_half_length(): 47 | ball_ray = Ray2D(wm.ball().pos(), wm.ball().vel().th()) 48 | goal_line = Line2D(Vector2D(-SP.pitch_half_length(), +10), 49 | Vector2D(-SP.pitch_half_length(), -10)) 50 | 51 | intersect = ball_ray.intersection(goal_line) 52 | if intersect and intersect.is_valid() \ 53 | and intersect.abs_y() < SP.goal_half_width() + 1.: 54 | self_goal = True 55 | 56 | if not (wm.kickable_opponent() 57 | or self_goal 58 | or (opp_min < self_min - 3 and opp_min < mate_min - 3) 59 | or (self_min >= 5 60 | and wm.ball().pos().dist2(SP.their_team_goal_pos()) < 10 **2 61 | and ((SP.their_team_goal_pos() - wm.self().pos()).th() - wm.self().body()).abs() < 45.)): 62 | 63 | return False 64 | 65 | return self.executeV14(agent, use_foul) 66 | 67 | def executeV14(self, agent: 'PlayerAgent', use_foul: bool): 68 | wm = agent.world() 69 | 70 | result = TackleGenerator.instance().best_result(wm) 71 | 72 | ball_next = wm.ball().pos() + result._ball_vel 73 | 74 | log.debug_client().add_message(f"Basic{'Foul' if use_foul else 'Tackle'}{result._tackle_angle.degree()}") 75 | tackle_dir = (result._tackle_angle - wm.self().body()).degree() 76 | 77 | agent.do_tackle(tackle_dir, use_foul) 78 | agent.set_neck_action(NeckTurnToPoint(ball_next)) 79 | return True 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /base/bhv_block.py: -------------------------------------------------------------------------------- 1 | from lib.action.go_to_point import GoToPoint 2 | from base.strategy_formation import StrategyFormation 3 | from lib.action.intercept import Intercept 4 | from base.tools import Tools 5 | from base.stamina_manager import get_normal_dash_power 6 | from pyrusgeom.soccer_math import * 7 | 8 | from typing import TYPE_CHECKING 9 | 10 | from lib.debug.debug import log 11 | 12 | if TYPE_CHECKING: 13 | from lib.player.player_agent import PlayerAgent 14 | 15 | class Bhv_Block: 16 | def execute(self, agent: 'PlayerAgent'): 17 | wm = agent.world() 18 | opp_min = wm.intercept_table().opponent_reach_cycle() 19 | ball_pos = wm.ball().inertia_point(opp_min) 20 | dribble_speed_etimate = 0.7 21 | dribble_angle_estimate = (Vector2D(-52.0, 0) - ball_pos).th() 22 | blocker = 0 23 | block_cycle = 1000 24 | block_pos = Vector2D(0, 0) 25 | for unum in range(1, 12): 26 | tm = wm.our_player(unum) 27 | if tm is None: 28 | continue 29 | if tm.unum() < 1: 30 | continue 31 | for c in range(1, 40): 32 | dribble_pos = ball_pos + Vector2D.polar2vector(c * dribble_speed_etimate, dribble_angle_estimate) 33 | 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) 34 | tm_cycle = tm.player_type().cycles_to_reach_distance(tm.inertia_point(opp_min).dist(dribble_pos)) + turn_cycle 35 | if tm_cycle <= opp_min + c: 36 | if tm_cycle < block_cycle: 37 | block_cycle = tm_cycle 38 | blocker = unum 39 | block_pos = dribble_pos 40 | break 41 | if blocker == wm.self_unum(): 42 | GoToPoint(block_pos, 0.1, 100).execute(agent) 43 | log.debug_client().add_message(f'block in ({round(block_pos.x(), 2)}, {round(block_pos.y(), 2)})') 44 | log.debug_client().set_target(block_pos) 45 | return True 46 | return False 47 | -------------------------------------------------------------------------------- /base/bhv_kick.py: -------------------------------------------------------------------------------- 1 | from lib.action.hold_ball import HoldBall 2 | from lib.action.neck_scan_players import NeckScanPlayers 3 | from lib.action.smart_kick import SmartKick 4 | from typing import List 5 | from base.generator_action import KickAction, ShootAction, KickActionType 6 | from base.generator_dribble import BhvDribbleGen 7 | from base.generator_pass import BhvPassGen 8 | from base.generator_shoot import BhvShhotGen 9 | from base.generator_clear import BhvClearGen 10 | 11 | from typing import TYPE_CHECKING 12 | 13 | from lib.debug.debug import log 14 | from lib.messenger.pass_messenger import PassMessenger 15 | 16 | if TYPE_CHECKING: 17 | from lib.player.world_model import WorldModel 18 | from lib.player.player_agent import PlayerAgent 19 | 20 | 21 | class BhvKick: 22 | def __init__(self): 23 | pass 24 | 25 | def execute(self, agent: 'PlayerAgent'): 26 | wm: 'WorldModel' = agent.world() 27 | shoot_candidate: ShootAction = BhvShhotGen().generator(wm) 28 | if shoot_candidate: 29 | log.debug_client().set_target(shoot_candidate.target_point) 30 | log.debug_client().add_message( 31 | 'shoot' + 'to ' + shoot_candidate.target_point.__str__() + ' ' + str(shoot_candidate.first_ball_speed)) 32 | SmartKick(shoot_candidate.target_point, shoot_candidate.first_ball_speed, 33 | shoot_candidate.first_ball_speed - 1, 3).execute(agent) 34 | agent.set_neck_action(NeckScanPlayers()) 35 | return True 36 | else: 37 | action_candidates: List[KickAction] = [] 38 | action_candidates += BhvPassGen().generator(wm) 39 | action_candidates += BhvDribbleGen().generator(wm) 40 | 41 | if len(action_candidates) == 0: 42 | return self.no_candidate_action(agent) 43 | 44 | best_action: KickAction = max(action_candidates) 45 | 46 | target = best_action.target_ball_pos 47 | log.debug_client().set_target(target) 48 | log.debug_client().add_message(best_action.type.value + 'to ' + best_action.target_ball_pos.__str__() + ' ' + str(best_action.start_ball_speed)) 49 | SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3).execute(agent) 50 | 51 | if best_action.type is KickActionType.Pass: 52 | agent.add_say_message(PassMessenger(best_action.target_unum, 53 | best_action.target_ball_pos, 54 | agent.effector().queued_next_ball_pos(), 55 | agent.effector().queued_next_ball_vel())) 56 | 57 | agent.set_neck_action(NeckScanPlayers()) 58 | return True 59 | 60 | def no_candidate_action(self, agent: 'PlayerAgent'): 61 | wm = agent.world() 62 | opp_min = wm.intercept_table().opponent_reach_cycle() 63 | if opp_min <= 3: 64 | action_candidates = BhvClearGen().generator(wm) 65 | if len(action_candidates) > 0: 66 | best_action: KickAction = max(action_candidates) 67 | target = best_action.target_ball_pos 68 | log.debug_client().set_target(target) 69 | log.debug_client().add_message(best_action.type.value + 'to ' + best_action.target_ball_pos.__str__() + ' ' + str(best_action.start_ball_speed)) 70 | SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 2.0, 2).execute(agent) 71 | 72 | agent.set_neck_action(NeckScanPlayers()) 73 | return HoldBall().execute(agent) -------------------------------------------------------------------------------- /base/bhv_move.py: -------------------------------------------------------------------------------- 1 | from base.basic_tackle import BasicTackle 2 | from lib.action.go_to_point import GoToPoint 3 | from base.strategy_formation import StrategyFormation 4 | from lib.action.intercept import Intercept 5 | from lib.action.neck_turn_to_ball import NeckTurnToBall 6 | from lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan 7 | from lib.action.turn_to_ball import TurnToBall 8 | from lib.action.turn_to_point import TurnToPoint 9 | from base.tools import Tools 10 | from base.stamina_manager import get_normal_dash_power 11 | from base.bhv_block import Bhv_Block 12 | from pyrusgeom.vector_2d import Vector2D 13 | 14 | from typing import TYPE_CHECKING 15 | 16 | from lib.debug.debug import log 17 | 18 | if TYPE_CHECKING: 19 | from lib.player.player_agent import PlayerAgent 20 | from lib.player.world_model import WorldModel 21 | 22 | 23 | class BhvMove: 24 | def __init__(self): 25 | self._in_recovery_mode = False 26 | pass 27 | 28 | def execute(self, agent: 'PlayerAgent'): 29 | wm: 'WorldModel' = agent.world() 30 | 31 | if BasicTackle(0.8, 80).execute(agent): 32 | return True 33 | 34 | # intercept 35 | self_min = wm.intercept_table().self_reach_cycle() 36 | tm_min = wm.intercept_table().teammate_reach_cycle() 37 | opp_min = wm.intercept_table().opponent_reach_cycle() 38 | log.sw_log().block().add_text( 39 | f"self_min={self_min}") 40 | log.sw_log().block().add_text( 41 | f"tm_min={tm_min}") 42 | log.sw_log().block().add_text( 43 | f"opp_min={opp_min}") 44 | 45 | if (not wm.exist_kickable_teammates() 46 | and (self_min <= 2 47 | or (self_min <= tm_min 48 | and self_min < opp_min + 5))): 49 | log.sw_log().block().add_text( "INTERCEPTING") 50 | log.debug_client().add_message('intercept') 51 | if Intercept().execute(agent): 52 | agent.set_neck_action(NeckTurnToBall()) 53 | return True 54 | 55 | if opp_min < min(tm_min, self_min): 56 | if Bhv_Block().execute(agent): 57 | agent.set_neck_action(NeckTurnToBall()) 58 | return True 59 | st = StrategyFormation().i() 60 | target = st.get_pos(agent.world().self().unum()) 61 | 62 | log.debug_client().set_target(target) 63 | log.debug_client().add_message('bhv_move') 64 | 65 | dash_power, self._in_recovery_mode = get_normal_dash_power(wm, self._in_recovery_mode) 66 | dist_thr = wm.ball().dist_from_self() * 0.1 67 | 68 | if dist_thr < 1.0: 69 | dist_thr = 1.0 70 | 71 | if GoToPoint(target, dist_thr, dash_power).execute(agent): 72 | agent.set_neck_action(NeckTurnToBallOrScan()) 73 | return True 74 | return False 75 | 76 | -------------------------------------------------------------------------------- /base/decision.py: -------------------------------------------------------------------------------- 1 | from base import goalie_decision 2 | from base.strategy_formation import StrategyFormation 3 | from base.set_play.bhv_set_play import Bhv_SetPlay 4 | from base.bhv_kick import BhvKick 5 | from base.bhv_move import BhvMove 6 | from lib.action.neck_scan_field import NeckScanField 7 | from lib.action.neck_scan_players import NeckScanPlayers 8 | from lib.action.neck_turn_to_ball import NeckTurnToBall 9 | from lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan 10 | from lib.action.scan_field import ScanField 11 | from lib.debug.debug import log 12 | from lib.messenger.ball_pos_vel_messenger import BallPosVelMessenger 13 | from lib.messenger.player_pos_unum_messenger import PlayerPosUnumMessenger 14 | from lib.rcsc.types import GameModeType, ViewWidth, UNUM_UNKNOWN 15 | 16 | from typing import TYPE_CHECKING 17 | if TYPE_CHECKING: 18 | from lib.player.world_model import WorldModel 19 | from lib.player.player_agent import PlayerAgent 20 | 21 | 22 | # TODO TACKLE GEN 23 | # TODO GOAL KICK L/R 24 | # TODO GOAL L/R 25 | def get_decision(agent: 'PlayerAgent'): 26 | wm: 'WorldModel' = agent.world() 27 | st = StrategyFormation().i() 28 | st.update(wm) 29 | 30 | if wm.self().goalie(): 31 | if goalie_decision.decision(agent): 32 | return True 33 | 34 | if wm.game_mode().type() != GameModeType.PlayOn: 35 | if Bhv_SetPlay().execute(agent): 36 | return True 37 | 38 | log.sw_log().team().add_text(f'is kickable? dist {wm.ball().dist_from_self()} ' 39 | f'ka {wm.self().player_type().kickable_area()} ' 40 | f'seen pos count {wm.ball().seen_pos_count()} ' 41 | f'is? {wm.self()._kickable}') 42 | if wm.self().is_kickable(): 43 | return BhvKick().execute(agent) 44 | if BhvMove().execute(agent): 45 | return True 46 | log.os_log().warn("NO ACTION, ScanFIELD") 47 | return ScanField().execute(agent) -------------------------------------------------------------------------------- /base/formation_dt/before_kick_off.conf: -------------------------------------------------------------------------------- 1 | Formation Static 2 | # --------------------------------------------------------- 3 | # move positions when playmode is BeforeKickOff or AfterGoal. 4 | 1 Goalie -49.0 0.0 5 | 2 CenterBack -25.0 -5.0 6 | 3 CenterBack -25.0 5.0 7 | 4 SideBack -25.0 -10.0 8 | 5 SideBack -25.0 10.0 9 | 6 DefensiveHalf -25.0 0.0 10 | 7 OffensiveHalf -15.0 -5.0 11 | 8 OffensiveHalf -15.0 5.0 12 | 9 SideForward -15.0 -10.0 13 | 10 SideForward -15.0 10.0 14 | 11 CenterForward -15.0 0.0 15 | # --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /base/formation_dt/goalie_kick_opp_formation.conf: -------------------------------------------------------------------------------- 1 | Formation Static 2 | # --------------------------------------------------------- 3 | # for opponent goal kick 4 | 1 Goalie -49.0 0.0 5 | 2 CenterBack 0.0 -5.0 6 | 3 CenterBack 0.0 5.0 7 | 4 SideBack 0.0 -12.0 8 | 5 SideBack 0.0 12.0 9 | 6 DefensiveHalf 10.0 0.0 10 | 7 OffensiveHalf 15.0 -12.0 11 | 8 OffensiveHalf 15.0 12.0 12 | 9 SideForward 31.0 -17.5 13 | 10 SideForward 31.0 17.5 14 | 11 CenterForward 31.0 0.0 15 | # --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /base/formation_dt/goalie_kick_our_formation.conf: -------------------------------------------------------------------------------- 1 | Formation Static 2 | # --------------------------------------------------------- 3 | # for our goalie catch 4 | 1 Goalie -49.0 0.0 5 | 2 CenterBack -33.5 -4.0 6 | 3 CenterBack -33.5 4.0 7 | 4 SideBack -33.0 -24.0 8 | 5 SideBack -33.0 24.0 9 | 6 DefensiveHalf -20.0 0.0 10 | 7 OffensiveHalf -20.5 -13.0 11 | 8 OffensiveHalf -20.5 13.0 12 | 9 SideForward -10.0 -27.0 13 | 10 SideForward -10.0 27.0 14 | 11 CenterForward -10.0 0.0 15 | # --------------------------------------------------------- 16 | -------------------------------------------------------------------------------- /base/formation_dt/kickin_our_formation.conf: -------------------------------------------------------------------------------- 1 | Formation DelaunayTriangulation 2 2 | Begin Roles 3 | 1 Goalie 0 4 | 2 CenterBack -1 5 | 3 CenterBack 2 6 | 4 SideBack -1 7 | 5 SideBack 4 8 | 6 DefensiveHalf 0 9 | 7 OffensiveHalf -1 10 | 8 OffensiveHalf 7 11 | 9 SideForward -1 12 | 10 SideForward 9 13 | 11 CenterForward 0 14 | End Roles 15 | Begin Samples 2 21 16 | ----- 0 ----- 17 | Ball 54 0 18 | 1 -50 0 19 | 2 0 -9 20 | 3 0 9 21 | 4 7 -19 22 | 5 7 19 23 | 6 21 0 24 | 7 35 -6 25 | 8 35 6 26 | 9 46 -9.5 27 | 10 46 9.5 28 | 11 46 0 29 | ----- 1 ----- 30 | Ball -54 0 31 | 1 -50 -0 32 | 2 -47 -2.5 33 | 3 -47 2.5 34 | 4 -47 -7 35 | 5 -47 7 36 | 6 -43 0 37 | 7 -35 -13 38 | 8 -35 13 39 | 9 -22 -28 40 | 10 -22 28 41 | 11 -18.49 0 42 | ----- 2 ----- 43 | Ball 0 0 44 | 1 -50 0 45 | 2 -15.06 -4.84 46 | 3 -15.18 3.68 47 | 4 -12.58 -14.88 48 | 5 -13.39 14.07 49 | 6 -5.61 0 50 | 7 0.11 -11.99 51 | 8 0.11 11.99 52 | 9 10.37 -23.99 53 | 10 10.84 23.99 54 | 11 10.84 0 55 | ----- 3 ----- 56 | Ball -54 -35 57 | 1 -50 0 58 | 2 -47.35 -11.81 59 | 3 -46.51 -4.65 60 | 4 -47.81 -26.33 61 | 5 -45.56 4.77 62 | 6 -41.23 -11.92 63 | 7 -37.38 -21.36 64 | 8 -27.94 1.74 65 | 9 -22.23 -31.17 66 | 10 -17.01 19.99 67 | 11 -17.51 -11.55 68 | ----- 4 ----- 69 | Ball -54 35 70 | 1 -50 -0 71 | 2 -46.51 4.65 72 | 3 -47.35 11.81 73 | 4 -45.56 -4.77 74 | 5 -47.81 26.33 75 | 6 -41.23 11.92 76 | 7 -27.94 -1.74 77 | 8 -37.38 21.36 78 | 9 -17.01 -19.99 79 | 10 -22.23 31.17 80 | 11 -17.51 11.55 81 | ----- 5 ----- 82 | Ball -36.02 -35 83 | 1 -50 -0.01 84 | 2 -39.12 -16.02 85 | 3 -38.87 -6.58 86 | 4 -36.39 -27.94 87 | 5 -36.76 3.85 88 | 6 -28.32 -15.28 89 | 7 -22.23 -24.59 90 | 8 -20.16 0.6 91 | 9 -10.43 -32.54 92 | 10 -7.44 19.44 93 | 11 -7.2 -14.16 94 | ----- 6 ----- 95 | Ball -36.02 35 96 | 1 -50 0.01 97 | 2 -38.87 6.58 98 | 3 -39.12 16.02 99 | 4 -36.76 -3.85 100 | 5 -36.39 27.94 101 | 6 -28.32 15.28 102 | 7 -20.16 -0.6 103 | 8 -22.23 24.59 104 | 9 -7.44 -19.44 105 | 10 -10.43 32.54 106 | 11 -7.2 14.16 107 | ----- 7 ----- 108 | Ball -12 -35 109 | 1 -50 0 110 | 2 -18.5 -21.61 111 | 3 -18.5 -8.94 112 | 4 -12.42 -34.65 113 | 5 -18.38 4.72 114 | 6 -9.07 -14.9 115 | 7 -0.5 -22.48 116 | 8 -5.96 0.12 117 | 9 11.67 -32.29 118 | 10 10.8 14.03 119 | 11 8.2 -15.15 120 | ----- 8 ----- 121 | Ball -12 35 122 | 1 -50 -0 123 | 2 -18.5 8.94 124 | 3 -18.5 21.61 125 | 4 -18.38 -4.72 126 | 5 -12.42 34.65 127 | 6 -9.07 14.9 128 | 7 -5.96 -0.12 129 | 8 -0.5 22.48 130 | 9 10.8 -14.03 131 | 10 11.67 32.29 132 | 11 8.2 15.15 133 | ----- 9 ----- 134 | Ball 38.13 -35 135 | 1 -50 0 136 | 2 -0.14 -16.53 137 | 3 6.25 -1.8 138 | 4 7.93 -28 139 | 5 17.31 8.77 140 | 6 24.88 -17.67 141 | 7 36.3 -31.49 142 | 8 32.09 -0.36 143 | 9 46.75 -24.64 144 | 10 44.23 -0.72 145 | 11 44.59 -13.82 146 | ----- 10 ----- 147 | Ball 38.13 35 148 | 1 -50 -0 149 | 2 6.25 1.8 150 | 3 -0.14 16.53 151 | 4 17.31 -8.77 152 | 5 7.93 28 153 | 6 24.88 17.67 154 | 7 32.09 0.36 155 | 8 36.3 31.49 156 | 9 44.23 0.72 157 | 10 46.75 24.64 158 | 11 44.59 13.82 159 | ----- 11 ----- 160 | Ball 35 -35 161 | 1 -50 0 162 | 2 1.68 -14.54 163 | 3 6.49 -0.12 164 | 4 6.37 -27.76 165 | 5 15.86 8.65 166 | 6 22.73 -17.39 167 | 7 33.41 -32.69 168 | 8 29.81 0 169 | 9 43.03 -29.81 170 | 10 41.7 -1.08 171 | 11 42.31 -15.38 172 | ----- 12 ----- 173 | Ball 35 35 174 | 1 -50 -0 175 | 2 6.49 0.12 176 | 3 1.68 14.54 177 | 4 15.86 -8.65 178 | 5 6.37 27.76 179 | 6 22.73 17.39 180 | 7 29.81 -0 181 | 8 33.41 32.69 182 | 9 41.7 1.08 183 | 10 43.03 29.81 184 | 11 42.31 15.38 185 | ----- 13 ----- 186 | Ball 24.88 -35 187 | 1 -50 0 188 | 2 -0.84 -21.03 189 | 3 2.88 -5.53 190 | 4 20.67 -32.93 191 | 5 11.42 7.69 192 | 6 14.54 -13.46 193 | 7 26.08 -19.11 194 | 8 31.01 -6.01 195 | 9 44.23 -29.93 196 | 10 39.54 -1.08 197 | 11 41.34 -17.43 198 | ----- 14 ----- 199 | Ball 24.88 35 200 | 1 -50 -0 201 | 2 2.88 5.53 202 | 3 -0.84 21.03 203 | 4 11.42 -7.69 204 | 5 20.67 32.93 205 | 6 14.54 13.46 206 | 7 31.01 6.01 207 | 8 26.08 19.11 208 | 9 39.54 1.08 209 | 10 44.23 29.93 210 | 11 41.34 17.43 211 | ----- 15 ----- 212 | Ball 12.98 -35 213 | 1 -50 0 214 | 2 -3.61 -21.51 215 | 3 -0.12 -4.33 216 | 4 9.86 -31.97 217 | 5 8.29 8.17 218 | 6 8.51 -16.2 219 | 7 20.91 -20.67 220 | 8 18.15 -1.2 221 | 9 37.02 -31.25 222 | 10 31.49 -1.08 223 | 11 34.97 -15.38 224 | ----- 16 ----- 225 | Ball 12.98 35 226 | 1 -50 -0 227 | 2 -0.12 4.33 228 | 3 -3.61 21.51 229 | 4 8.29 -8.17 230 | 5 9.86 31.97 231 | 6 8.51 16.2 232 | 7 18.15 1.2 233 | 8 20.91 20.67 234 | 9 31.49 1.08 235 | 10 37.02 31.25 236 | 11 34.97 15.38 237 | ----- 17 ----- 238 | Ball 0 -35 239 | 1 -50 0 240 | 2 -7.58 -23.22 241 | 3 -9.06 -10.97 242 | 4 -1.56 -32.69 243 | 5 -5.37 4.29 244 | 6 0 -16.95 245 | 7 7.57 -22.71 246 | 8 5.49 -0.12 247 | 9 24.47 -30.18 248 | 10 23.68 3.97 249 | 11 20.91 -14.66 250 | ----- 18 ----- 251 | Ball 0 35 252 | 1 -50 -0 253 | 2 -9.06 10.97 254 | 3 -7.58 23.22 255 | 4 -5.37 -4.29 256 | 5 -1.56 32.69 257 | 6 0 16.95 258 | 7 5.49 0.12 259 | 8 7.57 22.71 260 | 9 23.68 -3.97 261 | 10 24.47 30.18 262 | 11 20.91 14.66 263 | ----- 19 ----- 264 | Ball 54 -35 265 | 1 -50 0 266 | 2 -0.24 -14.54 267 | 3 7.21 -0.48 268 | 4 8.3 -27.3 269 | 5 17.19 10.22 270 | 6 24.76 -14.66 271 | 7 39.78 -28.6 272 | 8 38.65 -11.04 273 | 9 51.54 -34.65 274 | 10 46.27 -8.05 275 | 11 48.07 -22.35 276 | ----- 20 ----- 277 | Ball 54 35 278 | 1 -50 -0 279 | 2 7.21 0.48 280 | 3 -0.24 14.54 281 | 4 17.19 -10.22 282 | 5 8.3 27.3 283 | 6 24.76 14.66 284 | 7 38.65 11.04 285 | 8 39.78 28.6 286 | 9 46.27 8.05 287 | 10 51.54 34.65 288 | 11 48.07 22.35 289 | End Samples 290 | End 291 | -------------------------------------------------------------------------------- /base/generator_action.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pyrusgeom.geom_2d import * 3 | 4 | from typing import TYPE_CHECKING 5 | if TYPE_CHECKING: 6 | from lib.player.world_model import WorldModel 7 | from lib.player.object_player import PlayerObject 8 | 9 | 10 | class KickActionType(Enum): 11 | No = '0' 12 | Pass = 'Pass' 13 | Dribble = 'Dribble' 14 | Clear = 'Clear' 15 | 16 | 17 | class KickAction: 18 | def __init__(self): 19 | self.target_ball_pos = Vector2D.invalid() 20 | self.start_ball_pos = Vector2D.invalid() 21 | self.target_unum = 0 22 | self.start_unum = 0 23 | self.start_ball_speed = 0 24 | self.type = KickActionType.No 25 | self.eval = -1000 26 | self.index = 0 27 | self.min_opp_dist = 0.0 28 | 29 | def calculate_min_opp_dist(self, wm: 'WorldModel' = None): 30 | if wm is None or wm.opponents() is None or len(wm.opponents()) == 0: 31 | return 0.0 32 | return min([opp.pos().dist(self.target_ball_pos) for opp in wm.opponents() if opp is not None and opp.unum() > 0]) 33 | 34 | def evaluate(self, wm: 'WorldModel' = None): 35 | self.min_opp_dist = self.calculate_min_opp_dist(wm) 36 | self.eval = self.target_ball_pos.x() + max(0.0, 40.0 - self.target_ball_pos.dist(Vector2D(52, 0))) 37 | if self.min_opp_dist < 5.0: 38 | self.eval -= list([30, 20, 10, 5, 2])[int(self.min_opp_dist)] 39 | 40 | def __gt__(self, other): 41 | return self.eval > other.eval 42 | 43 | def __repr__(self): 44 | return '{} Action {} to {} in ({}, {}) eval:{}'.format(self.type.value, 45 | self.start_unum, 46 | self.target_unum, 47 | round(self.target_ball_pos.x(), 2), 48 | round(self.target_ball_pos.y(), 2), 49 | self.eval) 50 | 51 | def __str__(self): 52 | return self.__repr__() 53 | 54 | 55 | class TackleAction: 56 | def __init__(self, angle: AngleDeg = None, vel: Vector2D = None): 57 | if angle is not None and vel is not None: 58 | self._tackle_angle: AngleDeg = AngleDeg(angle) 59 | self._ball_vel: Vector2D = vel.copy() 60 | self._ball_speed: float = vel.r() 61 | self._ball_move_angle: AngleDeg = vel.th() 62 | self._score: float = 0. 63 | return 64 | self._tackle_angle: AngleDeg = AngleDeg(0) 65 | self._ball_vel: Vector2D = Vector2D(0,0) 66 | self._ball_speed: float = 0. 67 | self._ball_move_angle: AngleDeg = AngleDeg(0) 68 | self._score: float = -float('inf') 69 | 70 | def clear(self): 71 | self._tackle_angle: AngleDeg = AngleDeg(0) 72 | self._ball_vel: Vector2D = Vector2D(0,0) 73 | self._ball_speed: float = 0. 74 | self._ball_move_angle: AngleDeg = AngleDeg(0) 75 | self._score: float = -float('inf') 76 | 77 | 78 | class ShootAction: 79 | def __init__(self, index, target_point, first_ball_speed, ball_move_angle, ball_move_dist, ball_reach_step): 80 | self.index = index 81 | self.target_point: Vector2D = target_point 82 | self.first_ball_vel: Vector2D = Vector2D.polar2vector(first_ball_speed, ball_move_angle) 83 | self.first_ball_speed = first_ball_speed 84 | self.ball_move_angle: AngleDeg = ball_move_angle 85 | self.ball_move_dist = ball_move_dist 86 | self.ball_reach_step = ball_reach_step 87 | self.goalie_never_reach = True 88 | self.opponent_never_reach = True 89 | self.kick_step = 3 90 | self.score = 0 91 | 92 | def __repr__(self): 93 | return '{} target {} first_vel {}'.format(self.index, self.target_point, self.first_ball_vel) 94 | 95 | def __str__(self): 96 | return self.__repr__() 97 | 98 | 99 | class BhvKickGen: 100 | def __init__(self): 101 | self.candidates: list = [] 102 | self.index = 0 103 | self.debug_list = [] 104 | 105 | def can_opponent_cut_ball(self, wm: 'WorldModel', ball_pos, cycle): 106 | for unum in range(1, 12): 107 | opp: 'PlayerObject' = wm.their_player(unum) 108 | if opp.unum() == 0: 109 | continue 110 | opp_cycle = opp.pos().dist(ball_pos) - opp.player_type().kickable_area() 111 | opp_cycle /= opp.player_type().real_speed_max() 112 | if opp_cycle < cycle: 113 | return True 114 | return False 115 | -------------------------------------------------------------------------------- /base/generator_clear.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.geom_2d import * 2 | from lib.debug.color import Color 3 | from lib.debug.debug import log 4 | from lib.rcsc.server_param import ServerParam as SP 5 | from base.generator_action import KickAction, KickActionType, BhvKickGen 6 | from typing import TYPE_CHECKING 7 | if TYPE_CHECKING: 8 | from lib.player.world_model import WorldModel 9 | 10 | debug_clear_ball = True 11 | 12 | 13 | class BhvClearGen(BhvKickGen): 14 | def generator(self, wm: 'WorldModel'): 15 | log.sw_log().clear().add_text("=========start clear ball generator") 16 | self.generate_clear_ball(wm) 17 | log.sw_log().clear().add_text("=========generated clear ball actions:") 18 | for candid in self.candidates: 19 | log.sw_log().clear().add_text(f'{candid.index} {candid.target_ball_pos} {candid.eval}') 20 | if debug_clear_ball: 21 | for candid in self.debug_list: 22 | if candid[2]: 23 | log.sw_log().clear().add_message(candid[1].x(), candid[1].y(), '{}'.format(candid[0])) 24 | log.sw_log().clear().add_circle(circle=Circle2D(candid[1], 0.2), color=Color(string='green')) 25 | else: 26 | log.sw_log().clear().add_message(candid[1].x(), candid[1].y(), '{}'.format(candid[0])) 27 | log.sw_log().clear().add_circle(circle=Circle2D(candid[1], 0.2), color=Color(string='red')) 28 | return self.candidates 29 | 30 | def add_to_candidate(self, wm: 'WorldModel', ball_pos: Vector2D): 31 | action = KickAction() 32 | action.target_ball_pos = ball_pos 33 | action.start_ball_pos = wm.ball().pos() 34 | action.start_ball_speed = 2.5 35 | action.type = KickActionType.Clear 36 | action.index = self.index 37 | 38 | action.eval = 0 39 | if ball_pos.x() > SP.i().pitch_half_length(): 40 | action.eval = 200.0 41 | elif ball_pos.x() < -SP.i().pitch_half_length(): 42 | if ball_pos.abs_y() < 10: 43 | action.eval = -200.0 44 | else: 45 | action.eval = -80.0 46 | elif ball_pos.abs_y() > SP.i().pitch_half_width(): 47 | action.eval = ball_pos.x() 48 | elif ball_pos.x() < -30 and ball_pos.abs_y() < 20: 49 | action.eval = -100.0 50 | else: 51 | action.eval = ball_pos.dist(Vector2D(-SP.i().pitch_half_length(), 0.0)) 52 | self.candidates.append(action) 53 | self.debug_list.append((self.index, ball_pos, True)) 54 | self.index += 1 55 | 56 | def generate_clear_ball(self, wm: 'WorldModel'): 57 | angle_div = 16 58 | angle_step = 360.0 / angle_div 59 | 60 | for a in range(angle_div): 61 | ball_pos = wm.ball().pos() 62 | angle = AngleDeg(a * angle_step) 63 | speed = 2.5 64 | log.sw_log().clear().add_text(f'========= a:{a} speed:{speed} angle:{angle} ball:{ball_pos}') 65 | for c in range(30): 66 | ball_pos += Vector2D.polar2vector(speed, angle) 67 | log.sw_log().clear().add_text(f'--->>>{ball_pos}') 68 | speed *= SP.i().ball_decay() 69 | if ball_pos.x() > SP.i().pitch_half_length(): 70 | break 71 | if ball_pos.x() < -SP.i().pitch_half_length(): 72 | break 73 | if ball_pos.abs_y() > SP.i().pitch_half_width(): 74 | break 75 | receiver_opp = 0 76 | for opp in wm.opponents(): 77 | if not opp: 78 | continue 79 | if opp.unum() <= 0: 80 | continue 81 | opp_cycle = opp.pos().dist(ball_pos) / opp.player_type().real_speed_max() - opp.player_type().kickable_area() 82 | opp_cycle -= min(0, opp.pos_count()) 83 | if opp_cycle <= c: 84 | receiver_opp = opp.unum() 85 | break 86 | if receiver_opp != 0: 87 | break 88 | self.add_to_candidate(wm, ball_pos) 89 | 90 | -------------------------------------------------------------------------------- /base/goalie_decision.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import TYPE_CHECKING 3 | 4 | from pyrusgeom.line_2d import Line2D 5 | from pyrusgeom.rect_2d import Rect2D 6 | from pyrusgeom.size_2d import Size2D 7 | from pyrusgeom.soccer_math import bound 8 | from pyrusgeom.vector_2d import Vector2D 9 | 10 | from base.basic_tackle import BasicTackle 11 | from base.generator_action import KickAction 12 | from base.generator_pass import BhvPassGen 13 | from base.set_play.bhv_goalie_set_play import Bhv_GoalieSetPlay 14 | from lib.action.go_to_point import GoToPoint 15 | from lib.action.hold_ball import HoldBall 16 | from lib.action.intercept import Intercept 17 | from lib.action.neck_scan_players import NeckScanPlayers 18 | from lib.action.neck_turn_to_ball import NeckTurnToBall 19 | from lib.action.smart_kick import SmartKick 20 | from lib.debug.color import Color 21 | from lib.debug.debug import log 22 | from lib.debug.level import Level 23 | from lib.rcsc.server_param import ServerParam 24 | from lib.rcsc.types import GameModeType 25 | 26 | if TYPE_CHECKING: 27 | from lib.player.player_agent import PlayerAgent 28 | 29 | 30 | DEBUG = True 31 | 32 | def decision(agent: 'PlayerAgent'): 33 | SP = ServerParam.i() 34 | wm = agent.world() 35 | 36 | our_penalty = Rect2D(Vector2D(-SP.pitch_half_length(), -SP.penalty_area_half_width() + 1), 37 | Size2D(SP.penalty_area_length() - 1, SP.penalty_area_width() - 2)) 38 | 39 | log.os_log().debug(f'########## gdc={wm.time().cycle()}') 40 | log.os_log().debug(f'########## gd gmt={wm.game_mode().type()}') 41 | if wm.game_mode().type() != GameModeType.PlayOn: 42 | if Bhv_GoalieSetPlay().execute(agent): 43 | return True 44 | return False 45 | 46 | if (wm.time().cycle() > wm.self().catch_time().cycle() + SP.catch_ban_cycle() 47 | and wm.ball().dist_from_self() < SP.catchable_area() - 0.05 48 | and our_penalty.contains(wm.ball().pos())): 49 | 50 | agent.do_catch() 51 | agent.set_neck_action(NeckTurnToBall()) 52 | elif wm.self().is_kickable(): 53 | do_kick(agent) 54 | else: 55 | do_move(agent) 56 | 57 | return True 58 | 59 | def do_kick(agent: 'PlayerAgent'): 60 | wm = agent.world() 61 | 62 | action_candidates = BhvPassGen().generator(wm) 63 | 64 | if len(action_candidates) == 0: 65 | agent.set_neck_action(NeckScanPlayers()) 66 | return HoldBall().execute(agent) 67 | 68 | best_action: KickAction = max(action_candidates) 69 | target = best_action.target_ball_pos 70 | log.debug_client().set_target(target) 71 | log.debug_client().add_message(best_action.type.value + 'to ' + best_action.target_ball_pos.__str__() + ' ' + str( 72 | best_action.start_ball_speed)) 73 | SmartKick(target, best_action.start_ball_speed, best_action.start_ball_speed - 1, 3).execute(agent) 74 | agent.set_neck_action(NeckScanPlayers()) 75 | return True 76 | 77 | 78 | def do_move(agent: 'PlayerAgent'): 79 | SP = ServerParam.i() 80 | wm = agent.world() 81 | 82 | if BasicTackle(0.8, 90.).execute(agent): 83 | return True 84 | 85 | self_min = wm.intercept_table().self_reach_cycle() 86 | tm_min = wm.intercept_table().teammate_reach_cycle() 87 | opp_min = wm.intercept_table().opponent_reach_cycle() 88 | 89 | if self_min < tm_min and self_min < opp_min - 10: 90 | Intercept(False).execute(agent) 91 | agent.set_neck_action(NeckTurnToBall()) 92 | return True 93 | 94 | ball_pos = wm.ball().inertia_point(min(tm_min, opp_min)) 95 | 96 | post1 = Vector2D(-SP.pitch_half_length(), + SP.goal_half_width()) 97 | post2 = Vector2D(-SP.pitch_half_length(), - SP.goal_half_width()) 98 | 99 | ball_to_post1 = (post1 - ball_pos).th() 100 | ball_to_post2 = (post2 - ball_pos).th() 101 | ball_dir = (ball_to_post1 + ball_to_post2).degree()/ 2 102 | 103 | ball_move_line = Line2D(ball_pos, ball_dir) 104 | 105 | margin = min(-SP.pitch_half_length() + 3, ball_pos.x() - 0.1) 106 | goalie_move_line = Line2D(Vector2D(margin, -SP.pitch_half_width()), 107 | Vector2D(margin, +SP.pitch_half_width())) 108 | 109 | target = goalie_move_line.intersection(ball_move_line) 110 | target.set_y(bound(-SP.goal_half_width(), target.y(), SP.goal_half_width())) 111 | 112 | if DEBUG: 113 | log.sw_log().positioning().add_line( 114 | start=Vector2D(ball_pos.x(), ball_move_line.get_y(ball_pos.x())), 115 | end=Vector2D(-SP.pitch_half_length(), ball_move_line.get_y(-SP.pitch_half_length())), 116 | color=Color(string='red')) 117 | log.sw_log().positioning().add_line( 118 | start=Vector2D(goalie_move_line.get_x(-30), -30), 119 | end=Vector2D(goalie_move_line.get_x(+30), +30), 120 | color=Color(string='red')) 121 | 122 | if target: 123 | GoToPoint(target, 0.2, 100).execute(agent) 124 | agent.set_neck_action(NeckTurnToBall()) 125 | return True 126 | 127 | return False 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /base/sample_coach.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from lib.coach.coach_agent import CoachAgent 3 | from lib.debug.debug import log 4 | from lib.rcsc.player_type import PlayerType 5 | from lib.rcsc.types import HETERO_DEFAULT, HETERO_UNKNOWN 6 | 7 | import team_config 8 | 9 | def real_speed_max(lhs: PlayerType, rhs: PlayerType) -> bool: 10 | if abs(lhs.real_speed_max() - rhs.real_speed_max()) < 0.005: 11 | return lhs.cycles_to_reach_max_speed() < rhs.cycles_to_reach_max_speed() 12 | return lhs.real_speed_max() > rhs.real_speed_max() 13 | 14 | class SampleCoach(CoachAgent): 15 | def __init__(self): 16 | super().__init__() 17 | 18 | self._first_sub = False 19 | 20 | def action_impl(self): 21 | # TODO Send Team Graphic 22 | 23 | self.do_substitute() 24 | 25 | def do_substitute(self): 26 | if (not self._first_sub 27 | and self.world().time().cycle() == 0 28 | and self.world().time().stopped_cycle() > 10): 29 | 30 | self.do_first_subsititute() 31 | self._first_sub = True 32 | return 33 | 34 | def do_first_subsititute(self): 35 | candidates: list[PlayerType] = [] 36 | 37 | for i, pt in enumerate(self.world().player_types()): 38 | if pt is None: 39 | log.os_log().error(f"(sample coach first sub) pt is None! index={i}") 40 | continue 41 | 42 | for i in range(1): # TODO PLAYER PARAM 43 | candidates.append(pt) 44 | 45 | unum_order = [11,2,3,10,9,6,4,5,7,8] 46 | 47 | self.subsititute_to(1, HETERO_DEFAULT) 48 | for pt in candidates: 49 | if pt.id() == HETERO_DEFAULT: 50 | candidates.remove(pt) 51 | break 52 | 53 | for unum in unum_order: 54 | p = self.world().our_player(unum) 55 | if p is None: 56 | log.os_log().error(f"(sample coach sub to) player is None! unum={unum}") 57 | continue 58 | type = self.get_fastest_type(candidates) 59 | if type != HETERO_UNKNOWN: 60 | self.subsititute_to(unum, type) 61 | 62 | def subsititute_to(self, unum, type): 63 | if self.world().time().cycle() > 0 and self.world().our_subsititute_count() >= 3: # TODO PLAYER PARAM 64 | log.os_log().error(f"(sample coach subsititute to) WARNING: {team_config.TEAM_NAME} coach: over the substitution max." 65 | f"cannot change player({unum}) to {type}") 66 | return 67 | 68 | if type not in self.world().available_player_type_id(): # IMP FUNC 69 | log.os_log().error(f"(sample coach subsititute to) type is not available. type={type}") 70 | return 71 | 72 | self.do_change_player_type(unum, type) 73 | log.os_log().error(f"(sample coach subsititute to) player({unum})'s type changed to {type}") 74 | 75 | def get_fastest_type(self, candidates: list[PlayerType]): 76 | if len(candidates) == 0: 77 | return HETERO_UNKNOWN 78 | 79 | candidates.sort(key=functools.cmp_to_key(real_speed_max)) 80 | 81 | best_type: PlayerType = None 82 | max_speed = 0 83 | min_cycle = 100 84 | 85 | for candidate in candidates: 86 | if candidate.real_speed_max() < max_speed - 0.01: 87 | break 88 | 89 | if candidate.cycles_to_reach_max_speed() < min_cycle: 90 | best_type = candidate 91 | max_speed = best_type.real_speed_max() 92 | min_cycle = best_type.cycles_to_reach_max_speed() 93 | continue 94 | 95 | if candidate.cycles_to_reach_max_speed() == min_cycle: 96 | if candidate.get_one_step_stamina_consumption() < best_type.get_one_step_stamina_consumption(): 97 | best_type = candidate 98 | max_speed = best_type.real_speed_max() 99 | 100 | if best_type is not None: 101 | id = best_type.id() 102 | candidates.remove(best_type) 103 | return id 104 | return HETERO_UNKNOWN -------------------------------------------------------------------------------- /base/sample_player.py: -------------------------------------------------------------------------------- 1 | from base.decision import get_decision 2 | from base.sample_communication import SampleCommunication 3 | from base.view_tactical import ViewTactical 4 | from lib.action.go_to_point import GoToPoint 5 | from lib.action.intercept import Intercept 6 | from lib.action.neck_body_to_ball import NeckBodyToBall 7 | from lib.action.neck_turn_to_ball import NeckTurnToBall 8 | from lib.action.neck_turn_to_ball_or_scan import NeckTurnToBallOrScan 9 | from lib.action.scan_field import ScanField 10 | from lib.debug.debug import log 11 | from lib.debug.level import Level 12 | from lib.player.player_agent import PlayerAgent 13 | from lib.rcsc.server_param import ServerParam 14 | from lib.rcsc.types import GameModeType 15 | 16 | 17 | class SamplePlayer(PlayerAgent): 18 | def __init__(self, goalie=False): 19 | super().__init__(goalie) 20 | 21 | self._communication = SampleCommunication() 22 | 23 | def action_impl(self): 24 | wm = self.world() 25 | if self.do_preprocess(): 26 | return 27 | 28 | get_decision(self) 29 | 30 | def do_preprocess(self): 31 | wm = self.world() 32 | 33 | if wm.self().is_frozen(): 34 | self.set_view_action(ViewTactical()) 35 | self.set_neck_action(NeckTurnToBallOrScan()) 36 | return True 37 | 38 | if not wm.self().pos_valid(): 39 | self.set_view_action(ViewTactical()) 40 | ScanField().execute(self) 41 | return True 42 | 43 | count_thr = 10 if wm.self().goalie() else 5 44 | 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): 45 | self.set_view_action(ViewTactical()) 46 | NeckBodyToBall().execute(self) 47 | return True 48 | 49 | self.set_view_action(ViewTactical()) 50 | 51 | if self.do_heard_pass_receive(): 52 | return True 53 | 54 | return False 55 | 56 | def do_heard_pass_receive(self): 57 | wm = self.world() 58 | 59 | if wm.messenger_memory().pass_time() != wm.time() \ 60 | or len(wm.messenger_memory().pass_()) == 0 \ 61 | or wm.messenger_memory().pass_()[0]._receiver != wm.self().unum(): 62 | 63 | return False 64 | 65 | self_min = wm.intercept_table().self_reach_cycle() 66 | intercept_pos = wm.ball().inertia_point(self_min) 67 | heard_pos = wm.messenger_memory().pass_()[0]._pos 68 | 69 | log.sw_log().team().add_text( f"(sample palyer do heard pass) heard_pos={heard_pos}, intercept_pos={intercept_pos}") 70 | 71 | if not wm.kickable_teammate() \ 72 | and wm.ball().pos_count() <= 1 \ 73 | and wm.ball().vel_count() <= 1 \ 74 | and self_min < 20: 75 | log.sw_log().team().add_text( f"(sample palyer do heard pass) intercepting!, self_min={self_min}") 76 | log.debug_client().add_message("Comm:Receive:Intercept") 77 | Intercept().execute(self) 78 | self.set_neck_action(NeckTurnToBall()) 79 | else: 80 | log.sw_log().team().add_text( f"(sample palyer do heard pass) go to point!, cycle={self_min}") 81 | log.debug_client().set_target(heard_pos) 82 | log.debug_client().add_message("Comm:Receive:GoTo") 83 | 84 | GoToPoint(heard_pos, 0.5, ServerParam.i().max_dash_power()).execute(self) 85 | self.set_neck_action(NeckTurnToBall()) 86 | 87 | # TODO INTENTION?!? 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /base/sample_trainer.py: -------------------------------------------------------------------------------- 1 | from lib.debug.debug import log 2 | from lib.debug.level import Level 3 | from pyrusgeom.vector_2d import Vector2D 4 | from lib.player.trainer_agent import TrainerAgent 5 | from lib.rcsc.types import GameModeType 6 | 7 | 8 | class SampleTrainer(TrainerAgent): 9 | def __init__(self): 10 | super().__init__() 11 | 12 | def action_impl(self): 13 | if self.world().team_name_left() == "": # TODO left team name... # TODO is empty... 14 | self.do_teamname() 15 | return 16 | self.sample_action() 17 | 18 | def sample_action(self): 19 | log.sw_log().block().add_text( "Sample Action") 20 | 21 | wm = self.world() 22 | ballpos = wm.ball().pos() 23 | if ballpos.abs_x() > 10 or ballpos.abs_y() > 10: 24 | for i in range(1, 12): 25 | self.do_move_player(wm.team_name_l(), i, Vector2D(-40, i * 5 - 30)) 26 | self.do_move_ball(Vector2D(0, 0), Vector2D(0, 0)) 27 | self.do_change_mode(GameModeType.PlayOn) 28 | -------------------------------------------------------------------------------- /base/set_play/bhv_set_play_before_kick_off.py: -------------------------------------------------------------------------------- 1 | from lib.action.neck_scan_field import NeckScanField 2 | from lib.action.scan_field import ScanField 3 | from lib.debug.level import Level 4 | from pyrusgeom.angle_deg import AngleDeg 5 | from base.strategy_formation import StrategyFormation 6 | 7 | from typing import TYPE_CHECKING 8 | if TYPE_CHECKING: 9 | from lib.player.player_agent import PlayerAgent 10 | class Bhv_BeforeKickOff: 11 | def __init__(self): 12 | pass 13 | 14 | def execute(self, agent: 'PlayerAgent'): 15 | unum = agent.world().self().unum() 16 | st = StrategyFormation.i() 17 | target = st.get_pos(unum) 18 | if target.dist(agent.world().self().pos()) > 1.: 19 | agent.do_move(target.x(), target.y()) 20 | agent.set_neck_action(NeckScanField()) 21 | return True 22 | ScanField().execute(agent) 23 | return True 24 | -------------------------------------------------------------------------------- /base/stamina_manager.py: -------------------------------------------------------------------------------- 1 | from lib.rcsc.server_param import ServerParam as SP 2 | import pyrusgeom.soccer_math as smath 3 | 4 | from typing import TYPE_CHECKING 5 | if TYPE_CHECKING: 6 | from lib.player.world_model import WorldModel 7 | 8 | 9 | def get_normal_dash_power(wm: 'WorldModel', s_recover_mode: bool): 10 | if wm.self().stamina_model().capacity_is_empty(): 11 | return min(SP.i().max_dash_power(), wm.self().stamina() + wm.self().player_type().extra_stamina()) 12 | 13 | self_min = wm.intercept_table().self_reach_cycle() 14 | mate_min = wm.intercept_table().teammate_reach_cycle() 15 | opp_min = wm.intercept_table().opponent_reach_cycle() 16 | ball_min_reach_cycle = min(self_min, mate_min, opp_min) 17 | 18 | if wm.self().stamina_model().capacity_is_empty(): 19 | s_recover_mode = False 20 | elif wm.self().stamina() < SP.i().stamina_max() * 0.5: 21 | s_recover_mode = True 22 | elif wm.self().stamina() > SP.i().stamina_max() * 0.7: 23 | s_recover_mode = False 24 | 25 | dash_power = SP.i().max_dash_power() 26 | my_inc = wm.self().player_type().stamina_inc_max() * wm.self().recovery() 27 | 28 | # TODO wm.ourDefenseLineX() > wm.self().pos().x 29 | # TODO wm.ball().pos().x() < wm.ourDefenseLineX() + 20.0 30 | if wm.self().unum() <= 5 and wm.ball().inertia_point(ball_min_reach_cycle).x() < -20.0: 31 | dash_power = SP.i().max_dash_power() 32 | elif s_recover_mode: 33 | dash_power = my_inc - 25.0 34 | if dash_power < 0.0: 35 | dash_power = 0.0 36 | elif wm.exist_kickable_teammates() and wm.ball().dist_from_self() < 20.0: 37 | dash_power = min(my_inc * 1.1, SP.i().max_dash_power()) 38 | elif wm.self().pos().x() > wm.offside_line_x(): 39 | dash_power = SP.i().max_dash_power() 40 | 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: 41 | dash_power = smath.bound(SP.i().max_dash_power() * 0.1, my_inc * 0.5, SP.i().max_dash_power()) 42 | else: 43 | dash_power = min(my_inc * 1.7, SP.i().max_dash_power()) 44 | return dash_power, s_recover_mode 45 | -------------------------------------------------------------------------------- /base/strategy.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.geom_2d import * 2 | 3 | 4 | class _Strategy: 5 | def __init__(self): 6 | self._base_poses = [] 7 | self._base_poses.append([Vector2D(-45, 0), 7, 10]) 8 | self._base_poses.append([Vector2D(-30, -10), 7, 10]) 9 | self._base_poses.append([Vector2D(-30, 10), 7, 10]) 10 | self._base_poses.append([Vector2D(-30, -20), 7, 10]) 11 | self._base_poses.append([Vector2D(-30, 20), 7, 10]) 12 | self._base_poses.append([Vector2D(0, -15), 15, 15]) 13 | self._base_poses.append([Vector2D(0, 15), 15, 15]) 14 | self._base_poses.append([Vector2D(0, 0), 15, 15]) 15 | self._base_poses.append([Vector2D(30, -15), 15, 15]) 16 | self._base_poses.append([Vector2D(30, 15), 15, 15]) 17 | self._base_poses.append([Vector2D(30, 0), 15, 15]) 18 | self._poses = [Vector2D(0, 0) for i in range(11)] 19 | 20 | def update(self, wm): 21 | ball_pos = wm.ball().pos() 22 | for p in range(len(self._poses)): 23 | x = ball_pos.x() / 52.5 * self._base_poses[p - 1][1] + self._base_poses[p - 1][0].x() 24 | y = ball_pos.y() / 52.5 * self._base_poses[p - 1][1] + self._base_poses[p - 1][0].y() 25 | self._poses[p - 1] = Vector2D(x, y) 26 | 27 | def get_pos(self, unum): 28 | return self._poses[unum - 1] 29 | 30 | 31 | class Strategy: 32 | _i: _Strategy = _Strategy() 33 | 34 | @staticmethod 35 | def i() -> _Strategy: 36 | return Strategy._i 37 | -------------------------------------------------------------------------------- /base/strategy_formation.py: -------------------------------------------------------------------------------- 1 | from lib.formation.delaunay_triangulation import * 2 | import os 3 | from enum import Enum 4 | from lib.debug.debug import log 5 | from lib.rcsc.types import GameModeType 6 | 7 | from typing import TYPE_CHECKING 8 | if TYPE_CHECKING: 9 | from lib.player.world_model import WorldModel 10 | 11 | class Situation(Enum): 12 | OurSetPlay_Situation = 0, 13 | OppSetPlay_Situation = 1, 14 | Defense_Situation = 2, 15 | Offense_Situation = 3, 16 | PenaltyKick_Situation = 4 17 | 18 | 19 | class _StrategyFormation: 20 | def __init__(self): 21 | pwd = '.' 22 | if "base" in os.listdir('.'): 23 | pwd = 'base' 24 | self.before_kick_off_formation: Formation = Formation(f'{pwd}/formation_dt/before_kick_off.conf') 25 | self.defense_formation: Formation = Formation(f'{pwd}/formation_dt/defense_formation.conf') 26 | self.offense_formation: Formation = Formation(f'{pwd}/formation_dt/offense_formation.conf') 27 | self.goalie_kick_opp_formation: Formation = Formation(f'{pwd}/formation_dt/goalie_kick_opp_formation.conf') 28 | self.goalie_kick_our_formation: Formation = Formation(f'{pwd}/formation_dt/goalie_kick_our_formation.conf') 29 | self.kickin_our_formation: Formation = Formation(f'{pwd}/formation_dt/kickin_our_formation.conf') 30 | self.setplay_opp_formation: Formation = Formation(f'{pwd}/formation_dt/setplay_opp_formation.conf') 31 | self.setplay_our_formation: Formation = Formation(f'{pwd}/formation_dt/setplay_our_formation.conf') 32 | self._poses = [Vector2D(0, 0) for i in range(11)] 33 | self.current_situation = Situation.Offense_Situation 34 | self.current_formation = self.offense_formation 35 | 36 | def update(self, wm: 'WorldModel'): 37 | 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())}') 38 | tm_min = wm.intercept_table().teammate_reach_cycle() 39 | opp_min = wm.intercept_table().opponent_reach_cycle() 40 | self_min = wm.intercept_table().self_reach_cycle() 41 | all_min = min(tm_min, opp_min, self_min) 42 | ball_pos = wm.ball().inertia_point(all_min) 43 | 44 | if wm.game_mode().type() is GameModeType.PlayOn: 45 | thr = 0 46 | if wm.ball().inertia_point(min(self_min, tm_min, opp_min)).x() > 0: 47 | thr += 1 48 | if wm.self().unum() > 6: 49 | thr += 1 50 | if min(tm_min, self_min) < opp_min + thr: 51 | self.current_situation = Situation.Offense_Situation 52 | else: 53 | self.current_situation = Situation.Defense_Situation 54 | else: 55 | if wm.game_mode().is_penalty_kick_mode(): 56 | self.current_situation = Situation.PenaltyKick_Situation 57 | elif wm.game_mode().is_our_set_play(wm.our_side()): 58 | self.current_situation = Situation.OurSetPlay_Situation 59 | else: 60 | self.current_situation = Situation.OppSetPlay_Situation 61 | 62 | if wm.game_mode().type() is GameModeType.PlayOn: 63 | if self.current_situation is Situation.Offense_Situation: 64 | self.current_formation = self.offense_formation 65 | else: 66 | self.current_formation = self.defense_formation 67 | 68 | elif wm.game_mode().type() in [GameModeType.BeforeKickOff, GameModeType.AfterGoal_Left, 69 | GameModeType.AfterGoal_Right]: 70 | self.current_formation = self.before_kick_off_formation 71 | 72 | elif wm.game_mode().type() in [GameModeType.GoalKick_Left, GameModeType.GoalKick_Right, GameModeType.GoalieCatchBall_Left, GameModeType.GoalieCatchBall_Right]: # Todo add Goal Catch!! 73 | if wm.game_mode().is_our_set_play(wm.our_side()): 74 | self.current_formation = self.goalie_kick_our_formation 75 | else: 76 | self.current_formation = self.goalie_kick_opp_formation 77 | 78 | else: 79 | if wm.game_mode().is_our_set_play(wm.our_side()): 80 | if wm.game_mode().type() in [GameModeType.KickIn_Right, GameModeType.KickIn_Left, 81 | GameModeType.CornerKick_Right, GameModeType.CornerKick_Left]: 82 | self.current_formation = self.kickin_our_formation 83 | else: 84 | self.current_formation = self.setplay_our_formation 85 | else: 86 | self.current_formation = self.setplay_opp_formation 87 | 88 | self.current_formation.update(ball_pos) 89 | self._poses = self.current_formation.get_poses() 90 | 91 | if self.current_formation is self.before_kick_off_formation or wm.game_mode().type() in \ 92 | [GameModeType.KickOff_Left, GameModeType.KickOff_Right]: 93 | for pos in self._poses: 94 | pos._x = min(pos.x(), -0.5) 95 | else: 96 | pass # Todo add offside line 97 | # for pos in self._poses: 98 | # pos._x = math.min(pos.x(), ) 99 | 100 | def get_pos(self, unum): 101 | return self._poses[unum - 1] 102 | 103 | 104 | class StrategyFormation: 105 | _i: _StrategyFormation = _StrategyFormation() 106 | 107 | @staticmethod 108 | def i() -> _StrategyFormation: 109 | return StrategyFormation._i 110 | -------------------------------------------------------------------------------- /base/tools.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from pyrusgeom.geom_2d import * 4 | from lib.rcsc.server_param import ServerParam 5 | from lib.rcsc.player_type import PlayerType 6 | from lib.rcsc.game_mode import GameModeType 7 | from lib.action.kick_table import calc_max_velocity 8 | 9 | from typing import TYPE_CHECKING 10 | if TYPE_CHECKING: 11 | from lib.player.world_model import WorldModel 12 | from lib.player.object_player import PlayerObject 13 | 14 | class Tools: 15 | @staticmethod 16 | def predict_player_turn_cycle(ptype: PlayerType, player_body: AngleDeg, player_speed, target_dist, 17 | target_angle: AngleDeg, dist_thr, use_back_dash): 18 | sp = ServerParam.i() 19 | n_turn = 0 20 | angle_diff = (target_angle - player_body).abs() 21 | 22 | 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: 23 | angle_diff = abs( angle_diff - 180.0 ) 24 | 25 | turn_margin = 180.0 26 | if dist_thr < target_dist: 27 | turn_margin = max( 15.0, AngleDeg.asin_deg( dist_thr / target_dist ) ) 28 | 29 | speed = float(player_speed) 30 | while angle_diff > turn_margin: 31 | angle_diff -= ptype.effective_turn( sp.max_moment(), speed ) 32 | speed *= ptype.player_decay() 33 | n_turn += 1 34 | 35 | return n_turn 36 | 37 | @staticmethod 38 | def predict_kick_count(wm: 'WorldModel', kicker, first_ball_speed, ball_move_angle: AngleDeg): 39 | if wm.game_mode().type() != GameModeType.PlayOn and not wm.game_mode().is_penalty_kick_mode(): 40 | return 1 41 | 42 | if kicker == wm.self().unum() and wm.self().is_kickable(): 43 | max_vel = calc_max_velocity(ball_move_angle, wm.self().kick_rate(), wm.ball().vel()) 44 | if max_vel.r2() >= pow( first_ball_speed, 2): 45 | return 1 46 | if first_ball_speed > 2.5: 47 | return 3 48 | elif first_ball_speed > 1.5: 49 | return 2 50 | return 1 51 | 52 | @staticmethod 53 | def estimate_min_reach_cycle(player_pos: Vector2D, player_speed_max, target_first_point: Vector2D, target_move_angle: AngleDeg): 54 | target_to_player: Vector2D = (player_pos - target_first_point).rotated_vector(-target_move_angle) 55 | if target_to_player.x() < -1.0: 56 | return -1 57 | else: 58 | return max( 1, int(target_to_player.abs_y() / player_speed_max)) 59 | 60 | @staticmethod 61 | def get_nearest_teammate(wm: 'WorldModel', position: Vector2D, players: list['PlayerObject'] =None): 62 | if players is None: 63 | players = wm.teammates() 64 | best_player: 'PlayerObject' = None 65 | min_dist2 = 1000 66 | for player in players: 67 | d2 = player.pos().dist2( position ) 68 | if d2 < min_dist2: 69 | min_dist2 = d2 70 | best_player = player 71 | 72 | return best_player 73 | 74 | @staticmethod 75 | def estimate_virtual_dash_distance(player: 'PlayerObject'): 76 | pos_count = min(10, player.pos_count(), player.seen_pos_count()) 77 | max_speed = player.player_type().real_speed_max() * 0.8 78 | 79 | d = 0. 80 | for i in range(pos_count): 81 | d += max_speed * math.exp(-(i**2)/15) 82 | 83 | return d 84 | -------------------------------------------------------------------------------- /base/view_tactical.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import TYPE_CHECKING 3 | 4 | from pyrusgeom.vector_2d import Vector2D 5 | 6 | from lib.player.soccer_action import ViewAction 7 | from lib.rcsc.server_param import ServerParam 8 | from lib.rcsc.types import GameModeType, ViewWidth 9 | 10 | if TYPE_CHECKING: 11 | from lib.player.player_agent import PlayerAgent 12 | 13 | 14 | class ViewTactical(ViewAction): 15 | PREV1 = None 16 | PREV2 = None 17 | 18 | def __init__(self): 19 | pass 20 | 21 | def execute(self, agent: 'PlayerAgent'): 22 | ViewTactical.PREV2 = ViewTactical.PREV1 23 | ViewTactical.PREV1 = agent.world().self().view_width().width() 24 | 25 | best_width = None 26 | gm = agent.world().game_mode().type() 27 | if (gm is GameModeType.BeforeKickOff 28 | or gm.is_after_goal() 29 | or gm.is_penalty_setup() 30 | or gm.is_penalty_ready() 31 | or gm.is_penalty_miss() 32 | or gm.is_penalty_score()): 33 | 34 | best_width = ViewWidth.WIDE 35 | elif gm.is_penalty_taken(): 36 | best_width = ViewWidth.NARROW 37 | 38 | elif gm.is_goalie_catch_ball() and gm.side() == agent.world().our_side(): 39 | best_width = self.get_our_goalie_free_kick_view_width(agent) 40 | else: 41 | best_width = self.get_default_view_width(agent) 42 | 43 | if ViewTactical.PREV1 == 180.0 and ViewTactical.PREV2 == 180.0 and agent.world().see_time().cycle() == agent.world().time().cycle() - 2: 44 | best_width = ViewWidth.WIDE 45 | elif ViewTactical.PREV1 == 120.0 and best_width.width() == 60.0 and agent.world().see_time().cycle() == agent.world().time().cycle() - 1: 46 | best_width = ViewWidth.NORMAL 47 | 48 | return agent.do_change_view(best_width) 49 | 50 | def get_default_view_width(self, agent: 'PlayerAgent'): 51 | wm = agent.world() 52 | ef = agent.effector() 53 | SP = ServerParam.i() 54 | 55 | if not wm.ball().pos_valid(): 56 | return ViewWidth.WIDE 57 | 58 | self_min = wm.intercept_table().self_reach_cycle() 59 | mate_min = wm.intercept_table().teammate_reach_cycle() 60 | opp_min = wm.intercept_table().opponent_reach_cycle() 61 | ball_reach_cycle = min(self_min, mate_min, opp_min) 62 | 63 | ball_pos = wm.ball().inertia_point(ball_reach_cycle) 64 | ball_dist = ef.queued_next_self_pos().dist(ball_pos) 65 | 66 | if wm.self().goalie() and not wm.self().is_kickable(): 67 | goal_pos = Vector2D(- SP.pitch_half_length(), 0) 68 | if ball_dist > 10 or ball_pos.x() > SP.our_penalty_area_line_x() or ball_pos.dist(goal_pos) > 20: 69 | ball_angle = ef.queued_next_angle_from_body(ef.queued_next_ball_pos()) # TODO IMP FUNC 70 | angle_diff = ball_angle.abs() - SP.max_neck_angle() 71 | if angle_diff > ViewWidth.NORMAL.width()/2 -3: 72 | return ViewWidth.WIDE 73 | if angle_diff > ViewWidth.NARROW.width()/2 -3: 74 | return ViewWidth.NORMAL 75 | 76 | if ball_dist > 40: 77 | return ViewWidth.WIDE 78 | 79 | if ball_dist > 20: 80 | return ViewWidth.NORMAL 81 | 82 | if ball_dist > 10: 83 | ball_angle = ef.queued_next_angle_from_body(ef.queued_next_ball_pos()) 84 | if ball_angle.abs() > 120: 85 | return ViewWidth.WIDE 86 | 87 | teammate_ball_dist = 1000. 88 | opponent_ball_dist = 1000. 89 | 90 | if len(wm.teammates_from_ball()) > 0: 91 | teammate_ball_dist = wm.teammates_from_ball()[0].dist_from_ball() 92 | if len(wm.opponents_from_ball()) > 0: 93 | opponent_ball_dist = wm.opponents_from_ball()[0].dist_from_ball() 94 | 95 | if (not wm.self().goalie() 96 | and teammate_ball_dist > 5 97 | and opponent_ball_dist > 5 98 | and ball_dist > 10 99 | and wm.ball().dist_from_self() > 10): 100 | 101 | return ViewWidth.NORMAL 102 | return ViewWidth.NARROW 103 | 104 | def get_our_goalie_free_kick_view_width(self, agent: 'PlayerAgent'): 105 | if agent.world().self().goalie(): 106 | return ViewWidth.WIDE 107 | return self.get_default_view_width(agent) 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/algorithms: -------------------------------------------------------------------------------- 1 | BasicClient::run() 2 | BasicClient::run_online() 3 | PlayerAgent::run() 4 | PlayerAgent::parse_message() 5 | PlayerAgent::parse_sense_body_message() 6 | PlayerAgent::parse_cycle_info 7 | PlayerAgent::update_current_time 8 | PlayerAgent::sensor_body_parser.parse 9 | PlayerAgent::see_state.update 10 | PlayerAgent::parse_see_message 11 | PlayerAgent::parse_cycle_info 12 | PlayerAgent::update_current_time 13 | PlayerAgent::see_parser.parse 14 | PlayerAgent::see_state.update 15 | PlayerAgent::parse_full_state_message 16 | PlayerAgent::parse_cycle_info 17 | PlayerAgent::update_current_time 18 | PlayerAgent::full_state_parser.parse 19 | PlayerAgent::hear_parser 20 | PlayerAgent::parse_cycle_info 21 | PlayerAgent::update_current_time 22 | PlayerAgent::hear_player_parser 23 | PlayerAgent::hear_referee_parser 24 | PlayerAgent::action() 25 | PlayerAgent::update_before_decision 26 | PlayerAgent::update_real_world_before_decision 27 | ActionEffector::check_command_count 28 | WorldModel::update_by_last_cycle 29 | SelfObject::update_by_last_cycle 30 | BallObject::update_by_last_cycle 31 | PlayerObject::update_by_last_cycle 32 | real_world.update_world_after_sense_body 33 | SelfObject::update_self_after_sense_body 34 | real_world.update_world_after_see 35 | WorldModel::localize_self 36 | WorldModel::localize_ball 37 | WorldModel::localize_players 38 | WorldModel::check_ghosts 39 | WorldModel::update_dir_count 40 | PlayerAgent::real_world.update_just_before_decision 41 | # LOTS OF UPDATES 42 | PlayerAgent::update_full_world_before_decision 43 | // ActionEffector::check_command_count_by_full_state_parser 44 | //WorldModel::update_by_last_cycle 45 | // SelfObject::update_by_last_cycle 46 | // BallObject::update_by_last_cycle 47 | // PlayerObject::update_by_last_cycle 48 | //real_world.update_world_after_sense_body 49 | // SelfObject::update_self_after_sense_body 50 | PlayerAgent::full_world.update_by_full_state_message 51 | CreatePlayers 52 | PlayerAgent::full_world.update_just_before_decision 53 | # LOTS OF UPDATES 54 | KickTable::create_table 55 | PlayerAgent::action_impl 56 | real/full_world::update_just_after_decision 57 | decision_time -> update 58 | change_view/pointto/attention -> updates 59 | 60 | 61 | 62 | fix bugs in full state 63 | kick table and update it 64 | intercept table 65 | neck 66 | check hear 67 | player type after change sub -------------------------------------------------------------------------------- /lib/action/intercept_info.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pyrusgeom.angle_deg import AngleDeg 4 | from pyrusgeom.vector_2d import Vector2D 5 | 6 | 7 | class InterceptInfo: 8 | class Mode(Enum): 9 | NORMAL = 0 10 | EXHAUST = 100 11 | 12 | def __init__(self, mode: Mode = Mode.EXHAUST, turn_cycle: int = 10000, dash_cycle: int = 10000, 13 | dash_power: float = 100000, dash_angle: float = 0, 14 | self_pos: Vector2D = Vector2D(-10000, 0), ball_dist: float = 100000, stamina: float = 0): 15 | self._valid: bool = True 16 | self._mode: InterceptInfo.Mode = mode 17 | self._turn_cycle: int = turn_cycle 18 | self._dash_cycle: int = dash_cycle 19 | self._dash_power: float = dash_power 20 | self._dash_angle: AngleDeg = AngleDeg(dash_angle) 21 | self._self_pos: Vector2D = self_pos.copy() # object in object then copy 22 | self._ball_dist: float = ball_dist 23 | self._stamina: float = stamina 24 | if self._mode == InterceptInfo.Mode.EXHAUST and \ 25 | self._turn_cycle == 10000 and \ 26 | self._dash_cycle == 10000 and \ 27 | self._dash_power == 100000.0 and \ 28 | self._dash_angle == AngleDeg(0.0) and \ 29 | self._self_pos == Vector2D(-10000.0, 0.0) and \ 30 | self._ball_dist == 10000000.0 and \ 31 | self._stamina == 0.0: 32 | self._valid = False 33 | 34 | def init(self, mode: Mode = Mode.EXHAUST, turn_cycle: int = 10000, dash_cycle: int = 10000, 35 | dash_power: float = 100000, dash_angle: float = 0, 36 | self_pos: Vector2D = Vector2D(-10000, 0), ball_dist: float = 100000, stamina: float = 0): 37 | self.__init__(mode, turn_cycle, dash_cycle, dash_power, dash_angle, self_pos, ball_dist, stamina) 38 | 39 | def is_valid(self): 40 | return self._valid 41 | 42 | def mode(self): 43 | return self._mode 44 | 45 | def turn_cycle(self): 46 | return self._turn_cycle 47 | 48 | def dash_cycle(self): 49 | return self._dash_cycle 50 | 51 | def reach_cycle(self): 52 | return self._dash_cycle + self._turn_cycle 53 | 54 | def dash_power(self): 55 | return self._dash_power 56 | 57 | def dash_angle(self): 58 | return self._dash_angle 59 | 60 | def self_pos(self): 61 | return self._self_pos 62 | 63 | def ball_dist(self): 64 | return self._ball_dist 65 | 66 | def stamina(self): 67 | return self._stamina 68 | 69 | @staticmethod 70 | def compare(lhs, rhs): 71 | return True if lhs.reach_cycle() < rhs.reach_cycle() else ( 72 | lhs.reach_cycle() == rhs.reach_cycle() and lhs.turn_cycle() < rhs.turn_cycle()) 73 | 74 | def __lt__(self, other): 75 | if self.reach_cycle() < other.reach_cycle(): 76 | return True 77 | return self.reach_cycle() == other.reach_cycle() and self.turn_cycle() < other.turn_cycle() 78 | 79 | def __repr__(self): 80 | return (f"(mode: {self._mode}," 81 | f"each_cycle: {self.reach_cycle()}," 82 | f"turn_cycle: {self._turn_cycle}," 83 | f"dash_cycle: {self._dash_cycle}," 84 | f"dash_power: {self._dash_power}," 85 | f"dash_angle: {self._dash_angle.degree()}," 86 | f"self_pos: {self._self_pos}," 87 | f"ball_dist: {self._ball_dist}," 88 | f"stamina: {self._stamina})") 89 | -------------------------------------------------------------------------------- /lib/action/neck_body_to_ball.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | 3 | from lib.action.neck_body_to_point import NeckBodyToPoint 4 | from lib.action.scan_field import ScanField 5 | from lib.debug.debug import log 6 | from lib.player.soccer_action import NeckAction 7 | 8 | from typing import TYPE_CHECKING, Union 9 | 10 | if TYPE_CHECKING: 11 | from lib.player.player_agent import PlayerAgent 12 | 13 | 14 | class NeckBodyToBall(NeckAction): 15 | def __init__(self, angle_buf: Union[AngleDeg,float] = 5.): 16 | self._angle_buf = float(angle_buf) 17 | 18 | def execute(self, agent: 'PlayerAgent'): 19 | log.debug_client().add_message('BodyToBall/') 20 | wm = agent.world() 21 | if wm.ball().pos_valid(): 22 | ball_next = wm.ball().pos() + wm.ball().vel() 23 | 24 | return NeckBodyToPoint(ball_next, self._angle_buf).execute(agent) 25 | return ScanField().execute(agent) 26 | -------------------------------------------------------------------------------- /lib/action/neck_body_to_point.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.action.neck_turn_to_relative import NeckTurnToRelative 6 | from lib.debug.debug import log 7 | from lib.player.soccer_action import NeckAction 8 | 9 | from typing import TYPE_CHECKING, Union 10 | 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | if TYPE_CHECKING: 14 | from lib.player.player_agent import PlayerAgent 15 | 16 | 17 | class NeckBodyToPoint(NeckAction): 18 | def __init__(self, point: Vector2D, angle_buf: Union[AngleDeg, float] = 5.): 19 | super().__init__() 20 | self._point = point.copy() 21 | self._angle_buf = float(angle_buf) 22 | 23 | def execute(self, agent: 'PlayerAgent'): 24 | log.debug_client().add_message('BodyToPoint/') 25 | SP = ServerParam.i() 26 | wm = agent.world() 27 | 28 | angle_buf = bound(0., self._angle_buf, 180.) 29 | 30 | my_next = wm.self().pos() + wm.self().vel() 31 | target_rel_angle = (self._point - my_next).th() - wm.self().body() 32 | 33 | if SP.min_neck_angle() + angle_buf < target_rel_angle.degree() < SP.max_neck_angle() - angle_buf: 34 | agent.do_turn(0.) 35 | agent.set_neck_action(NeckTurnToRelative(target_rel_angle)) 36 | return True 37 | 38 | max_turn = wm.self().player_type().effective_turn(SP.max_moment(),wm.self().vel().r()) 39 | if target_rel_angle.abs() < max_turn: 40 | agent.do_turn(target_rel_angle) 41 | agent.set_neck_action(NeckTurnToRelative(0.)) 42 | return True 43 | 44 | agent.do_turn(target_rel_angle) 45 | if target_rel_angle.degree() > 0.: 46 | target_rel_angle -= max_turn 47 | else: 48 | target_rel_angle += max_turn 49 | 50 | agent.set_neck_action(NeckTurnToRelative(target_rel_angle)) 51 | return True 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /lib/action/neck_turn_to_ball.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | 4 | from lib.action.basic_actions import NeckAction 5 | 6 | from typing import TYPE_CHECKING 7 | 8 | from lib.action.neck_scan_field import NeckScanField 9 | from lib.action.neck_scan_players import NeckScanPlayers 10 | from lib.debug.debug import log 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | if TYPE_CHECKING: 14 | from lib.player.player_agent import PlayerAgent 15 | 16 | class NeckTurnToBall(NeckAction): 17 | def __init__(self): 18 | super().__init__() 19 | 20 | def execute(self, agent: 'PlayerAgent'): 21 | log.debug_client().add_message('TurnToBall/') 22 | SP = ServerParam.i() 23 | wm = agent.world() 24 | 25 | if not wm.ball().pos_valid(): 26 | NeckScanField().execute(agent) 27 | return True 28 | 29 | my_next = agent.effector().queued_next_self_pos() 30 | my_body_next = agent.effector().queued_next_self_body() 31 | 32 | ball_next = agent.effector().queued_next_ball_pos() # TODO IMP FUNC 33 | ball_angle_next = (ball_next - my_next).th() 34 | ball_rel_angle_next = ball_angle_next - my_body_next 35 | 36 | next_view_width = agent.effector().queued_next_view_width().width() 37 | 38 | if ball_rel_angle_next.abs() > SP.max_neck_angle() + next_view_width*0.5: 39 | NeckScanField().execute(agent) 40 | return True 41 | 42 | if wm.intercept_table().opponent_reach_cycle() <= 1: 43 | neck_moment = ball_rel_angle_next - wm.self().neck() 44 | agent.do_turn_neck(neck_moment) 45 | return True 46 | 47 | view_half = max(0, next_view_width * 0.5 - 10.) 48 | 49 | opp = wm.get_opponent_nearest_to_ball(10) 50 | mate = wm.get_teammate_nearest_to_ball(10) 51 | 52 | ball_dist = my_next.dist(ball_next) 53 | 54 | if (SP.visible_distance() * 0.7 < ball_dist < 15 55 | and (wm.kickable_teammate() 56 | or wm.kickable_opponent() 57 | or (opp and opp.dist_from_ball() < opp.player_type().kickable_area()+0.3) 58 | or (mate and mate.dist_from_ball() < mate.player_type().kickable_area() + 0.3) 59 | ) 60 | ): 61 | view_half = max(0, next_view_width*0.5 - 20) 62 | 63 | if (len(wm.opponents_from_self()) >= 11 64 | and (wm.ball().pos().x() > 0 65 | or wm.ball().pos().abs_y() > SP.pitch_half_width() - 8 66 | or not opp 67 | or opp.dist_from_ball() > 3)): 68 | best_angle = NeckScanPlayers.INVALID_ANGLE 69 | 70 | if ball_dist > SP.visible_distance() - 0.3 or wm.ball().seen_pos_count() > 0: 71 | min_neck_angle = bound(SP.min_neck_angle(), 72 | ball_rel_angle_next.degree() - view_half, 73 | SP.max_neck_angle()) 74 | 75 | max_neck_angle = bound(SP.min_neck_angle(), 76 | ball_rel_angle_next.degree() + view_half, 77 | SP.max_neck_angle()) 78 | 79 | best_angle = NeckScanPlayers.get_best_angle(agent, min_neck_angle, max_neck_angle) 80 | 81 | else: 82 | best_angle = NeckScanPlayers.get_best_angle(agent) 83 | 84 | if best_angle != NeckScanPlayers.INVALID_ANGLE: 85 | target_angle = best_angle 86 | neck_moment = AngleDeg(target_angle - my_body_next.degree() - wm.self().neck().degree()) 87 | 88 | agent.do_turn_neck(neck_moment) 89 | return True 90 | 91 | left_rel_angle = bound(SP.min_neck_angle(), ball_rel_angle_next.degree() - view_half, SP.max_neck_angle()) 92 | right_rel_angle = bound(SP.min_neck_angle(), ball_rel_angle_next.degree() + view_half, SP.max_neck_angle()) 93 | 94 | left_sum_count = 0 95 | right_sum_count = 0 96 | 97 | _, left_sum_count, _ = wm.dir_range_count(my_body_next + left_rel_angle, next_view_width) 98 | _, right_sum_count, _ = wm.dir_range_count(my_body_next + right_rel_angle, next_view_width) 99 | 100 | if left_sum_count > right_sum_count: 101 | agent.do_turn_neck(left_rel_angle - wm.self().neck().degree()) 102 | else: 103 | agent.do_turn_neck(right_rel_angle - wm.self().neck().degree()) 104 | 105 | return True 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /lib/action/neck_turn_to_ball_or_scan.py: -------------------------------------------------------------------------------- 1 | from lib.action.neck_scan_field import NeckScanField 2 | from lib.action.neck_turn_to_ball import NeckTurnToBall 3 | from lib.debug.debug import log 4 | from lib.player.soccer_action import NeckAction 5 | 6 | from typing import TYPE_CHECKING 7 | 8 | from lib.rcsc.server_param import ServerParam 9 | 10 | if TYPE_CHECKING: 11 | from lib.player.player_agent import PlayerAgent 12 | 13 | 14 | class NeckTurnToBallOrScan(NeckAction): 15 | def __init__(self, count_thr: int = 5): 16 | super().__init__() 17 | self._count_thr = count_thr 18 | 19 | def execute(self, agent: 'PlayerAgent'): 20 | log.debug_client().add_message('TurnToBallOrScan/') 21 | wm = agent.world() 22 | ef = agent.effector() 23 | SP = ServerParam.i() 24 | 25 | if wm.ball().pos_count() <= self._count_thr: 26 | return NeckScanField().execute(agent) 27 | 28 | ball_next = ef.queued_next_ball_pos() 29 | my_next = ef.queued_next_self_pos() 30 | 31 | if (wm.ball().pos_count() <= 0 32 | and not wm.kickable_opponent() 33 | and not wm.kickable_teammate() 34 | and my_next.dist(ball_next) < SP.visible_distance() - 0.2): 35 | return NeckScanField().execute(agent) 36 | 37 | my_next_body = ef.queued_next_self_body() 38 | next_view_width = ef.queued_next_view_width().width() 39 | 40 | if ((ball_next - my_next).th() - my_next_body).abs() > SP.max_neck_angle() + next_view_width * 0.5 + 2: 41 | return NeckScanField().execute(agent) 42 | 43 | return NeckTurnToBall().execute(agent) 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /lib/action/neck_turn_to_point.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.vector_2d import Vector2D 2 | 3 | from lib.action.neck_scan_field import NeckScanField 4 | from lib.debug.debug import log 5 | from lib.player.soccer_action import NeckAction 6 | 7 | from typing import TYPE_CHECKING 8 | 9 | from lib.rcsc.server_param import ServerParam 10 | 11 | if TYPE_CHECKING: 12 | from lib.player.player_agent import PlayerAgent 13 | 14 | 15 | class NeckTurnToPoint(NeckAction): 16 | def __init__(self, point: Vector2D = None, points: list[Vector2D] = None): 17 | self._points: list[Vector2D] = [] 18 | if point: 19 | self._points.append(point) 20 | else: 21 | self._points = list(points) 22 | 23 | def execute(self, agent: 'PlayerAgent'): 24 | log.debug_client().add_message('TurnToPoint/') 25 | ef = agent.effector() 26 | wm = agent.world() 27 | SP = ServerParam.i() 28 | 29 | next_pos = ef.queued_next_self_pos() 30 | next_body = ef.queued_next_self_body() 31 | next_view_width = ef.queued_next_view_width().width()/2 32 | 33 | for p in self._points: 34 | rel_pos = p - next_pos 35 | rel_angle = rel_pos.th() - next_body 36 | 37 | if rel_angle.abs() < SP.max_neck_angle() + next_view_width - 5.: 38 | return agent.do_turn_neck(rel_angle - agent.world().self().neck()) 39 | 40 | NeckScanField().execute(agent) 41 | return True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/action/neck_turn_to_relative.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | 3 | from lib.player.soccer_action import NeckAction 4 | 5 | from typing import TYPE_CHECKING, Union 6 | 7 | if TYPE_CHECKING: 8 | from lib.player.player_agent import PlayerAgent 9 | 10 | 11 | class NeckTurnToRelative(NeckAction): 12 | def __init__(self, rel_angle: Union[AngleDeg, float]): 13 | self._angle_rel_to_body: AngleDeg = AngleDeg(rel_angle) 14 | 15 | def execute(self, agent: 'PlayerAgent'): 16 | return agent.do_turn_neck(self._angle_rel_to_body - agent.world().self().neck()) -------------------------------------------------------------------------------- /lib/action/scan_field.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.vector_2d import Vector2D 2 | 3 | from lib.action.neck_body_to_point import NeckBodyToPoint 4 | from lib.action.neck_turn_to_relative import NeckTurnToRelative 5 | from lib.action.view_wide import ViewWide 6 | from lib.debug.debug import log 7 | from lib.player.soccer_action import BodyAction 8 | 9 | from typing import TYPE_CHECKING 10 | 11 | from lib.rcsc.types import ViewWidth 12 | 13 | if TYPE_CHECKING: 14 | from lib.player.player_agent import PlayerAgent 15 | 16 | 17 | class ScanField(BodyAction): 18 | def __init__(self): 19 | pass 20 | 21 | def execute(self, agent: 'PlayerAgent'): 22 | log.debug_client().add_message('ScanField/') 23 | wm = agent.world() 24 | if not wm.self().pos_valid(): 25 | agent.do_turn(60.) 26 | agent.set_neck_action(NeckTurnToRelative(0)) 27 | return True 28 | 29 | if wm.ball().pos_valid(): 30 | self.scan_all_field(agent) 31 | return True 32 | self.find_ball(agent) 33 | return True 34 | 35 | def find_ball(self, agent: 'PlayerAgent'): 36 | wm = agent.world() 37 | 38 | if agent.effector().queued_next_view_width() is not ViewWidth.WIDE: 39 | agent.set_view_action(ViewWide()) 40 | 41 | my_next = wm.self().pos() + wm.self().vel() 42 | face_angle = (wm.ball().seen_pos() - my_next).th() if wm.ball().seen_pos().is_valid() else (my_next*-1).th() 43 | 44 | search_flag = wm.ball().lost_count() //3 45 | if search_flag%2==1: 46 | face_angle += 180. 47 | 48 | face_point = my_next + Vector2D(r=10, a=face_angle) 49 | NeckBodyToPoint(face_point).execute(agent) 50 | 51 | def scan_all_field(self, agent: 'PlayerAgent'): 52 | wm = agent.world() 53 | if agent.effector().queued_next_view_width() is not ViewWidth.WIDE: 54 | agent.set_view_action(ViewWide()) 55 | 56 | turn_moment=wm.self().view_width().width() + agent.effector().queued_next_view_width().width() 57 | turn_moment /= 2 58 | agent.do_turn(turn_moment) 59 | agent.set_neck_action(NeckTurnToRelative(wm.self().neck())) 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /lib/action/smart_kick.py: -------------------------------------------------------------------------------- 1 | """ 2 | \ file smart_kick.py 3 | \ brief smart kick action class file. 4 | """ 5 | from lib.debug.debug import log 6 | from lib.player.soccer_action import * 7 | from lib.action.kick_table import KickTable, Sequence 8 | from lib.action.stop_ball import StopBall 9 | from lib.action.hold_ball import HoldBall 10 | from lib.debug.level import Level 11 | from lib.rcsc.server_param import ServerParam 12 | from pyrusgeom.soccer_math import * 13 | 14 | 15 | from typing import TYPE_CHECKING 16 | if TYPE_CHECKING: 17 | from lib.player.player_agent import PlayerAgent 18 | 19 | # from lib.player.player_agent import * 20 | # from pyrusgeom.soccer_math import * 21 | 22 | 23 | class SmartKick(BodyAction): 24 | debug_print_DEBUG: bool = True # debug_prints IN SMARTKICK 25 | 26 | def __init__(self, target_point: Vector2D, first_speed, first_speed_thr, max_step): 27 | super().__init__() 28 | # target point where the ball should move to 29 | self._target_point = target_point 30 | # desired ball first speed 31 | self._first_speed: float = first_speed 32 | # threshold value for the ball first speed 33 | self._first_speed_thr: float = first_speed_thr 34 | # maximum number of kick steps 35 | self._max_step: int = max_step 36 | # result kick sequence holder 37 | self._sequence = Sequence() 38 | 39 | def execute(self, agent: 'PlayerAgent'): 40 | log.sw_log().kick().add_text( "Body_SmartKick") 41 | log.os_log().debug(f'c{agent.world().time().cycle()}kick{self._target_point} {self._first_speed}') 42 | log.sw_log().kick().add_text(f'c{agent.world().time().cycle()}kick{self._target_point} {self._first_speed}') 43 | wm = agent.world() 44 | if not wm.self().is_kickable(): 45 | if SmartKick.debug_print_DEBUG: 46 | log.os_log().info("----- NotKickable -----") 47 | log.sw_log().kick().add_text("not kickable") 48 | return False 49 | if not wm.ball().vel_valid(): 50 | if SmartKick.debug_print_DEBUG: 51 | log.os_log().info("-- NonValidBall -> StopBall --") 52 | log.sw_log().kick().add_text("unknown ball vel") 53 | return StopBall().execute(agent) 54 | first_speed = min(self._first_speed, ServerParam.i().ball_speed_max()) 55 | first_speed_thr = max(0.0, self._first_speed_thr) 56 | max_step = max(1, self._max_step) 57 | ans = KickTable.instance().simulate(wm, 58 | self._target_point, 59 | first_speed, 60 | first_speed_thr, 61 | max_step, 62 | self._sequence) 63 | if ans[0] and SmartKick.debug_print_DEBUG: 64 | 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_}") 65 | 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_}") 66 | 67 | if ans[0]: 68 | self._sequence = ans[1] 69 | if self._sequence.speed_ >= first_speed_thr: # double check 70 | vel = self._sequence.pos_list_[0] - wm.ball().pos() 71 | kick_accel = vel - wm.ball().vel() 72 | if SmartKick.debug_print_DEBUG: 73 | 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()}") 74 | 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()}") 75 | 76 | agent.do_kick(kick_accel.r() / wm.self().kick_rate(), 77 | kick_accel.th() - wm.self().body()) 78 | if SmartKick.debug_print_DEBUG: 79 | log.os_log().debug(f"----------------#### Player Number {wm.self().unum()} 'DO_KICK'ed in SmartKick at Time: {wm.time().cycle()} ####----------------") 80 | log.sw_log().kick().add_text(f"----------------#### Player Number {wm.self().unum()} 'DO_KICK'ed in SmartKick at Time: {wm.time().cycle()} ####----------------") 81 | return True 82 | 83 | # failed to search the kick sequence 84 | log.sw_log().kick().add_text("----->>>>>Hold Ball") 85 | HoldBall(False, self._target_point, self._target_point).execute(agent) 86 | return False 87 | 88 | def sequence(self): 89 | return self._sequence 90 | -------------------------------------------------------------------------------- /lib/action/stop_ball.py: -------------------------------------------------------------------------------- 1 | """ 2 | \ file body_stop_ball.py 3 | \ brief kick the ball to keep a current positional relation. 4 | """ 5 | 6 | from lib.player.soccer_action import * 7 | from pyrusgeom.soccer_math import * 8 | from pyrusgeom.angle_deg import AngleDeg 9 | from lib.rcsc.server_param import ServerParam 10 | 11 | from typing import TYPE_CHECKING 12 | if TYPE_CHECKING: 13 | from lib.player.world_model import WorldModel 14 | from lib.player.player_agent import PlayerAgent 15 | """ 16 | \ class Body_StopBall 17 | \ brief stop the ball, possible as. 18 | """ 19 | 20 | 21 | class StopBall(BodyAction): 22 | """ 23 | \ brief accessible from global. 24 | """ 25 | 26 | def __init__(self): 27 | super().__init__() 28 | self._accel_radius = 0.0 29 | self._accel_angle = AngleDeg() 30 | 31 | """ 32 | \ brief execute action 33 | \ param agent the agent itself 34 | \ return True if action is performed 35 | """ 36 | 37 | def execute(self, agent: 'PlayerAgent'): 38 | wm: 'WorldModel' = agent.world() 39 | if not wm.self().is_kickable(): 40 | return False 41 | if not wm.ball().vel_valid(): # Always true until NFS nice :) 42 | required_accel = wm.self().vel() - (wm.self().pos() - wm.ball().pos()) 43 | kick_power = required_accel.r() / wm.self().kick_rate() 44 | kick_power *= 0.5 45 | agent.do_kick(min(kick_power, ServerParam.i().max_power()), 46 | required_accel.th() - wm.self().body()) 47 | return True 48 | 49 | self._accel_radius = 0.0 50 | self._accel_angle = AngleDeg() 51 | 52 | self.calcAccel(agent) 53 | 54 | if self._accel_radius < 0.02: 55 | agent.do_turn(0.0) 56 | return False 57 | kick_power = 0.0 58 | # kick_power = self._accel_radius / wm.self().kickRate() 59 | # kick_power = min(kick_power, i.maxPower()) 60 | 61 | return agent.do_kick(kick_power, 62 | self._accel_angle - wm.self().body()) 63 | 64 | def calcAccel(self, agent): 65 | 66 | wm: 'WorldModel' = agent.world() 67 | 68 | safety_dist = wm.self().player_type().player_size() + ServerParam.i().ball_size() + 0.1 69 | 70 | target_dist = wm.ball().dist_from_self() 71 | if target_dist < safety_dist: 72 | target_dist = safety_dist 73 | 74 | if target_dist > wm.self().player_type().kickable_area() - 0.1: 75 | target_dist = wm.self().player_type().kickable_area() - 0.1 76 | 77 | target_rel = wm.self().pos() - wm.ball().pos() 78 | target_rel.set_length(target_dist) 79 | 80 | required_accel = wm.self().vel() 81 | required_accel += target_rel # target relative to current 82 | required_accel -= wm.self().pos() - wm.ball().pos() # vel = pos diff 83 | required_accel -= wm.ball().vel() # required accel 84 | 85 | self._accel_radius = required_accel.r() 86 | 87 | if self._accel_radius < 0.01: 88 | return None 89 | 90 | # check max accel with player's kick rate 91 | 92 | max_accel = ServerParam.i().max_power() * wm.self().kick_rate() 93 | if max_accel > self._accel_radius: 94 | # can accelerate -. can stop ball successfully 95 | self._accel_angle = required_accel.th() 96 | return None 97 | 98 | ################################## 99 | # keep the ball as much as possible near the best point 100 | 101 | next_ball_to_self = wm.self().vel() 102 | next_ball_to_self -= wm.self().pos() - wm.ball().pos() 103 | next_ball_to_self -= wm.ball().vel() 104 | 105 | keep_dist = wm.self().player_type().player_size() + wm.self().player_type().kickable_margin() * 0.4 106 | 107 | self._accel_radius = min(max_accel, next_ball_to_self.r() - keep_dist) 108 | self._accel_angle = next_ball_to_self.th() 109 | 110 | if self._accel_radius < 0.0: # == next_ball_dist < keep_dist 111 | # next ball dist will be closer than keep dist. 112 | # -. kick angle must be reversed. 113 | self._accel_radius *= -1.0 114 | self._accel_radius = min(self._accel_radius, max_accel) 115 | self._accel_angle -= 180.0 116 | -------------------------------------------------------------------------------- /lib/action/turn_to_ball.py: -------------------------------------------------------------------------------- 1 | from lib.action.turn_to_point import TurnToPoint 2 | 3 | from typing import TYPE_CHECKING 4 | if TYPE_CHECKING: 5 | from lib.player.player_agent import PlayerAgent 6 | 7 | class TurnToBall: 8 | def __init__(self, cycle: int = 1) -> None: 9 | self._cycle: int = cycle 10 | 11 | def execute(self, agent: 'PlayerAgent'): 12 | if not agent.world().ball().pos_valid(): 13 | return False 14 | 15 | ball_point = agent.world().ball().inertia_point(self._cycle) 16 | return TurnToPoint(ball_point, self._cycle).execute(agent) -------------------------------------------------------------------------------- /lib/action/turn_to_point.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.vector_2d import Vector2D 2 | 3 | from typing import TYPE_CHECKING 4 | if TYPE_CHECKING: 5 | from lib.player.player_agent import PlayerAgent 6 | 7 | class TurnToPoint: 8 | def __init__(self, point: Vector2D, cycle: int = 1) -> None: 9 | self._point: Vector2D = point 10 | self._cycle: int = cycle 11 | 12 | def execute(self, agent: 'PlayerAgent'): 13 | self_player = agent.world().self() 14 | if not self_player.pos_valid(): 15 | return agent.do_turn(60) 16 | 17 | my_point = self_player.inertia_point(self._cycle) 18 | target_rel_angle = (self._point - my_point).th() - self_player.body() 19 | 20 | agent.do_turn(target_rel_angle) 21 | if target_rel_angle.abs() < 1: 22 | return False 23 | return True -------------------------------------------------------------------------------- /lib/action/view_wide.py: -------------------------------------------------------------------------------- 1 | from lib.player.soccer_action import ViewAction 2 | from lib.rcsc.types import ViewWidth 3 | 4 | from typing import TYPE_CHECKING 5 | if TYPE_CHECKING: 6 | from lib.player.player_agent import PlayerAgent 7 | 8 | 9 | class ViewWide(ViewAction): 10 | def execute(self, agent: 'PlayerAgent'): 11 | return agent.do_change_view(ViewWidth.WIDE) -------------------------------------------------------------------------------- /lib/debug/color.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self, red: int = 0, green: int = 0, blue: int = 0, string: str = None): 3 | self._hex: str = "" 4 | if string is None: 5 | red = ('0' if red < 16 else "") + hex(red).split("x")[1] 6 | green = ('0' if green < 16 else "") + hex(green).split("x")[1] 7 | blue = ('0' if blue < 16 else "") + hex(blue).split("x")[1] 8 | self._hex = f"#{red}{green}{blue}" 9 | else: 10 | if string[0] == '#': 11 | self._hex = string 12 | else: 13 | string = string.lower() 14 | if string in ['white', 'w']: 15 | self.__init__(255, 255, 255) 16 | elif string in ['black', 'b']: 17 | self.__init__(0, 0, 0) 18 | elif string in ['red', 'r']: 19 | self.__init__(255, 0, 0) 20 | elif string in ['green', 'g']: 21 | self.__init__(0, 255, 0) 22 | elif string in ['blue', 'b']: 23 | self.__init__(0, 0, 255) 24 | elif string in ['yellow', 'y']: 25 | self.__init__(255, 248, 27) 26 | elif string in ['ping', 'p']: 27 | self.__init__(241, 27, 255) 28 | elif string in ['gray']: 29 | self.__init__(151, 151, 151) 30 | 31 | def hex(self): 32 | return self._hex 33 | 34 | def color(self): 35 | return self._hex 36 | 37 | def __repr__(self): 38 | return self._hex 39 | -------------------------------------------------------------------------------- /lib/debug/debug.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from logging import Logger 3 | import os 4 | import team_config 5 | from lib.debug.debug_client import DebugClient 6 | from lib.debug.os_logger import get_logger 7 | from lib.debug.sw_logger import SoccerWindow_Logger 8 | from lib.rcsc.game_time import GameTime 9 | 10 | 11 | class DebugLogger: 12 | def __init__(self): 13 | if not team_config.DISABLE_FILE_LOG and not os.path.exists(team_config.LOG_PATH): 14 | os.makedirs(team_config.LOG_PATH) 15 | self._sw_log: SoccerWindow_Logger = SoccerWindow_Logger('NA', 1, GameTime(0, 0)) 16 | self._os_log: Logger = get_logger(0) 17 | self._debug_client: DebugClient = None 18 | 19 | def setup(self, team_name, unum, time): 20 | if not team_config.DISABLE_FILE_LOG and not os.path.exists(team_config.LOG_PATH): 21 | os.makedirs(team_config.LOG_PATH) 22 | self.set_stderr(unum) 23 | self._sw_log = SoccerWindow_Logger(team_name, unum, time) 24 | self._os_log = get_logger(unum) 25 | self._debug_client = DebugClient() 26 | 27 | def sw_log(self): 28 | return self._sw_log 29 | 30 | def os_log(self): 31 | return self._os_log 32 | 33 | def debug_client(self): 34 | return self._debug_client 35 | 36 | def update_time(self, t: GameTime): 37 | self._time.assign(t.cycle(), t.stopped_cycle()) 38 | 39 | def set_stderr(self, unum): 40 | if team_config.DISABLE_FILE_LOG: 41 | return 42 | if unum == 'coach': 43 | file_name = 'coach.err' 44 | elif unum > 0: 45 | file_name = f'player-{unum}.err' 46 | else: 47 | file_name = f'coach-log.err' 48 | sys.stderr = open(os.path.join(team_config.LOG_PATH, file_name), 'w') 49 | 50 | 51 | log = DebugLogger() 52 | -------------------------------------------------------------------------------- /lib/debug/level.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Level(Enum): 5 | SYSTEM = 0x00000001 6 | SENSOR = 0x00000002 7 | WORLD = 0x00000004 8 | ACTION = 0x00000008 9 | INTERCEPT = 0x00000010 10 | KICK = 0x00000020 11 | HOLD = 0x00000040 12 | DRIBBLE = 0x00000080 13 | PASS = 0x00000100 14 | CROSS = 0x00000200 15 | SHOOT = 0x00000400 16 | CLEAR = 0x00000800 17 | BLOCK = 0x00001000 18 | MARK = 0x00002000 19 | POSITIONING = 0x00004000 20 | ROLE = 0x00008000 21 | TEAM = 0x00010000 22 | COMMUNICATION = 0x00020000 23 | ANALYZER = 0x00040000 24 | ACTION_CHAIN = 0x00080000 25 | PLAN = 0x00100000 26 | 27 | TRAINING = 0x80000000 28 | 29 | LEVEL_ANY = 0xffffffff 30 | 31 | # unused Levels :\ 32 | LEVEL_00 = 0x00000000 33 | LEVEL_22 = 0x00200000 34 | LEVEL_23 = 0x00400000 35 | LEVEL_24 = 0x00800000 36 | LEVEL_25 = 0x01000000 37 | LEVEL_26 = 0x02000000 38 | LEVEL_27 = 0x04000000 39 | LEVEL_28 = 0x08000000 40 | LEVEL_29 = 0x10000000 41 | LEVEL_30 = 0x20000000 42 | LEVEL_31 = 0x40000000 43 | LEVEL_32 = 0x80000000 44 | -------------------------------------------------------------------------------- /lib/debug/os_logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Union 3 | import datetime 4 | import coloredlogs 5 | import sys 6 | import os 7 | import team_config 8 | 9 | 10 | def get_logger(unum: Union[int, str] = None): 11 | logging.basicConfig() 12 | logger = logging.getLogger(name='mylogger') 13 | coloredlogs.install(logger=logger) 14 | logger.propagate = False 15 | coloredFormatter = coloredlogs.ColoredFormatter( 16 | datefmt='%H:%M:%S:%s', 17 | fmt=f'%(asctime)s %(filename)s u{unum} %(lineno)-3d %(levelname)s %(message)s', 18 | level_styles=dict( 19 | debug=dict(color='white'), 20 | info=dict(color='green'), 21 | warning=dict(color='yellow', bright=True), 22 | error=dict(color='red', bold=True, bright=True), 23 | critical=dict(color='black', bold=True, background='red'), 24 | ), 25 | field_styles=dict( 26 | name=dict(color='white'), 27 | asctime=dict(color='white'), 28 | funcName=dict(color='white'), 29 | lineno=dict(color='white'), 30 | ) 31 | ) 32 | # remove all handlers 33 | for handler in logger.handlers: 34 | logger.removeHandler(handler) 35 | 36 | if not team_config.DISABLE_FILE_LOG: 37 | if unum == 'coach': 38 | file_name = 'coach.txt' 39 | elif unum > 0: 40 | file_name = f'player-{unum}.txt' 41 | else: 42 | file_name = f'before-set-log.txt' 43 | path = team_config.LOG_PATH 44 | 45 | file_ch = logging.FileHandler(filename=f'{path}/{file_name}', mode='w') 46 | file_ch.setFormatter(logging.Formatter('%(asctime)s %(filename)s %(lineno)-3d %(message)s', 47 | '%H:%M:%S:%s')) 48 | file_ch.setLevel(level=team_config.FILE_LOG_LEVEL) 49 | logger.addHandler(hdlr=file_ch) 50 | 51 | console_ch = logging.StreamHandler(stream=sys.stdout) 52 | console_ch.setFormatter(fmt=coloredFormatter) 53 | console_ch.setLevel(level=team_config.CONSOLE_LOG_LEVEL) 54 | logger.addHandler(hdlr=console_ch) 55 | 56 | logger.setLevel(logging.DEBUG) # Set the logger level to DEBUG to capture all levels 57 | 58 | return logger 59 | 60 | # logger = get_logger() 61 | # logger.setLevel(level=logging.ERROR) 62 | # logger.debug(msg="this is a debug message") 63 | # logger.info(msg="this is an info message") 64 | # logger.warning(msg="this is a warning message") 65 | # logger.error(msg="this is an error message") 66 | # logger.critical(msg="this is a critical message") 67 | -------------------------------------------------------------------------------- /lib/debug/timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class ProfileTimer: 4 | main_dict: dict = {} 5 | 6 | @staticmethod 7 | def start(name): 8 | if name not in ProfileTimer.main_dict: 9 | ProfileTimer.main_dict[name] = [0, 0, -1] 10 | ProfileTimer.main_dict[name][2] = time.time() 11 | 12 | @staticmethod 13 | def end(name): 14 | if name not in ProfileTimer.main_dict: 15 | return 16 | ProfileTimer.main_dict[name][0] += time.time() - ProfileTimer.main_dict[name][2] 17 | ProfileTimer.main_dict[name][1] += 1 18 | 19 | @staticmethod 20 | def get(): 21 | res = '' 22 | for name, value in ProfileTimer.main_dict.items(): 23 | avg = value[0] / value[1] if value[1] > 0 else -1 24 | res += f'## {name}: {round(avg, 6)}, {value[1]} \n' 25 | return res 26 | 27 | # parse_message: 0.3392317295074463, 410, 0.0008273944622132837 ## flush: 0.4707632064819336, 1096, 0.00042952847306745764 ## synchaction: 5.524951934814453, 79, 0.06993610044068928 ## 28 | #parse_message: 0.09196591377258301, 370, 0.0002485565237096838 ## flush: 0.5167291164398193, 1111, 0.00046510271506734416 ## synchaction: 2.8537697792053223, 92, 0.031019236730492634 ## -------------------------------------------------------------------------------- /lib/formation/delaunay_triangulation.py: -------------------------------------------------------------------------------- 1 | from scipy.spatial import Delaunay 2 | from pyrusgeom.geom_2d import * 3 | from enum import Enum 4 | from lib.rcsc.server_param import ServerParam 5 | from pyrusgeom.soccer_math import min_max 6 | 7 | class FormationType(Enum): 8 | Static = 's' 9 | DelaunayTriangulation2 = 'D' 10 | 11 | 12 | class Formation: 13 | def __init__(self, path): 14 | self._balls = [] 15 | self._players = [] 16 | self._triangles = [] 17 | self._formation_type = FormationType.Static 18 | self._target_players = [] 19 | self._path = path 20 | self.read_file(path) 21 | self.calculate() 22 | 23 | def read_file(self, path): 24 | file = open(path, 'r') 25 | lines = file.readlines() 26 | if lines[0].find('Static') < 0: 27 | self._formation_type = FormationType.DelaunayTriangulation2 28 | if self._formation_type == FormationType.Static: 29 | self.read_static(lines) 30 | else: 31 | self.read_delaunay(lines) 32 | 33 | def read_static(self, lines): 34 | for i in range(len(lines)): 35 | if i == 0 or lines[i].startswith('#'): 36 | continue 37 | player = lines[i].split() 38 | self._target_players.append(Vector2D(float(player[2]), float(player[3]))) 39 | 40 | def read_delaunay(self, lines): 41 | for i in range(len(lines)): 42 | if lines[i].find('Ball') >= 0: 43 | self.read_sample(i, lines) 44 | i += 11 45 | 46 | def read_sample(self, i, lines): 47 | ball = lines[i].split(' ') 48 | ball_x = float(ball[1]) 49 | ball_y = float(ball[2]) 50 | self._balls.append([ball_x, ball_y]) 51 | players = [] 52 | for j in range(1, 12): 53 | player = lines[i + j].split(' ') 54 | player_x = float(player[1]) 55 | player_y = float(player[2]) 56 | players.append([player_x, player_y]) 57 | self._players.append(players) 58 | 59 | def calculate(self): 60 | if self._formation_type == FormationType.Static: 61 | return 62 | self._tri = Delaunay(self._balls).simplices 63 | for tri in self._tri: 64 | tmp = [Triangle2D(Vector2D(self._balls[tri[0]][0], self._balls[tri[0]][1]), 65 | Vector2D(self._balls[tri[1]][0], self._balls[tri[1]][1]), 66 | Vector2D(self._balls[tri[2]][0], self._balls[tri[2]][1])), tri[0], tri[1], tri[2]] 67 | self._triangles.append(tmp) 68 | 69 | def update(self, B:Vector2D): 70 | SP = ServerParam.i() 71 | if self._formation_type == FormationType.Static: 72 | return 73 | ids = [] 74 | 75 | point = B.copy() 76 | if point.abs_x() > SP.pitch_half_length(): 77 | point._x = min_max(-SP.pitch_half_length(), point.x(), +SP.pitch_half_length()) 78 | if point.abs_y() > SP.pitch_half_width(): 79 | point._y = min_max(-SP.pitch_half_width(), point.y(), +SP.pitch_half_width()) 80 | 81 | for tri in self._triangles: 82 | if tri[0].contains(point): 83 | ids = [tri[1], tri[2], tri[3]] 84 | break 85 | Pa = Vector2D(self._balls[ids[0]][0], self._balls[ids[0]][1]) 86 | Pb = Vector2D(self._balls[ids[1]][0], self._balls[ids[1]][1]) 87 | Pc = Vector2D(self._balls[ids[2]][0], self._balls[ids[2]][1]) 88 | lineProj = Line2D(p1=Pb, p2=Pc).projection(B) 89 | m1 = Pb.dist(lineProj) 90 | n1 = Pc.dist(lineProj) 91 | m2 = Pa.dist(B) 92 | n2 = lineProj.dist(B) 93 | 94 | self._target_players.clear() 95 | for p in range(11): 96 | OPa = Vector2D(self._players[ids[0]][p][0], self._players[ids[0]][p][1]) 97 | OPb = Vector2D(self._players[ids[1]][p][0], self._players[ids[1]][p][1]) 98 | OPc = Vector2D(self._players[ids[2]][p][0], self._players[ids[2]][p][1]) 99 | OI = (OPc - OPb) 100 | OI *= (m1 / (m1 + n1)) 101 | OI += OPb 102 | OB = (OI - OPa) 103 | OB *= (m2 / (m2 + n2)) 104 | OB += OPa 105 | self._target_players.append(OB) 106 | 107 | def get_pos(self, unum): 108 | return self._target_players[unum - 1] 109 | 110 | def get_poses(self): 111 | return self._target_players 112 | 113 | def __repr__(self): 114 | return self._path 115 | 116 | # f = Formation('base/formations-dt/before-kick-off.conf') 117 | # debug_print(len(f._balls)) 118 | # debug_print(len(f._players)) 119 | # debug_print(f._formation_type) 120 | # f.update(Vector2D(20, 16)) 121 | # debug_print(f._formation_type) 122 | # debug_print(f._target_players) 123 | -------------------------------------------------------------------------------- /lib/messenger/ball_goalie_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class BallGoalieMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL_GOALIE], [ 20 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), 21 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), 22 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 23 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 24 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 106), 25 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 69), 26 | (0, 360, 180) 27 | ]) 28 | 29 | def __init__(self, 30 | ball_pos: Vector2D = None, 31 | ball_vel: Vector2D = None, 32 | player_pos: Vector2D = None, 33 | player_body: AngleDeg = None, 34 | message: str = None) -> None: 35 | super().__init__() 36 | if message is None: 37 | self._ball_pos: Vector2D = ball_pos.copy() 38 | self._ball_vel: Vector2D = ball_vel.copy() 39 | self._player_pos: Vector2D = player_pos.copy() 40 | self._player_body: AngleDeg = player_body.copy() 41 | else: 42 | self._ball_pos: Vector2D = None 43 | self._ball_vel: Vector2D = None 44 | self._player_pos: Vector2D = None 45 | self._player_body: AngleDeg = None 46 | self._size = Messenger.SIZES[Messenger.Types.BALL_GOALIE] 47 | self._header = Messenger.Types.BALL_GOALIE.value 48 | 49 | self._message = message 50 | 51 | def encode(self) -> str: 52 | if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ 53 | or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): 54 | return '' 55 | 56 | SP = ServerParam.i() 57 | ep = 0.001 58 | msg = BallGoalieMessenger.CONVERTER.convert_to_word([ 59 | bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), 60 | bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), 61 | bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), 62 | bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), 63 | bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), 64 | bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), 65 | bound(ep, self._player_body.degree() + 180, 360. - ep) 66 | ]) 67 | return f'{self._header}{msg}' 68 | 69 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 70 | bpx, bpy, bvx, bvy, ppx, ppy, pb = BallGoalieMessenger.CONVERTER.convert_to_values(self._message) 71 | 72 | messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) 73 | messenger_memory.add_opponent_goalie(sender, Vector2D(ppx, ppy), current_time, body=AngleDeg(pb-180)) # TODO IMP FUNC 74 | 75 | def __repr__(self) -> str: 76 | return "ball player msg" 77 | -------------------------------------------------------------------------------- /lib/messenger/ball_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class BallMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL], [ 20 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), 21 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), 22 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 23 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 24 | ]) 25 | 26 | def __init__(self, 27 | ball_pos: Vector2D = None, 28 | ball_vel: Vector2D = None, 29 | message: str = None) -> None: 30 | super().__init__() 31 | if message is None: 32 | self._ball_pos: Vector2D = ball_pos.copy() 33 | self._ball_vel: Vector2D = ball_vel.copy() 34 | else: 35 | self._ball_pos: Vector2D = None 36 | self._ball_vel: Vector2D = None 37 | 38 | self._size = Messenger.SIZES[Messenger.Types.BALL] 39 | self._header = Messenger.Types.BALL.value 40 | self._message = message 41 | 42 | def encode(self) -> str: 43 | if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ 44 | or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): 45 | return '' 46 | 47 | SP = ServerParam.i() 48 | ep = 0.001 49 | msg = BallMessenger.CONVERTER.convert_to_word([ 50 | bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), 51 | bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), 52 | bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), 53 | bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), 54 | ]) 55 | return f'{self._header}{msg}' 56 | 57 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 58 | log.os_log().debug(self._message) 59 | bpx, bpy, bvx, bvy = BallMessenger.CONVERTER.convert_to_values(self._message) 60 | 61 | messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) 62 | 63 | def __repr__(self) -> str: 64 | return "ball msg" 65 | -------------------------------------------------------------------------------- /lib/messenger/ball_player_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | 15 | if TYPE_CHECKING: 16 | from lib.player.world_model import WorldModel 17 | 18 | 19 | class BallPlayerMessenger(Messenger): 20 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.BALL_PLAYER], [ 21 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), 22 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), 23 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 24 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 25 | (1, 23, 22), 26 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 106), 27 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 69), 28 | (0, 360, 180) 29 | ]) 30 | 31 | def __init__(self, 32 | ball_pos: Vector2D = None, 33 | ball_vel: Vector2D = None, 34 | unum: int = None, 35 | player_pos: Vector2D = None, 36 | player_body: AngleDeg = None, 37 | message: str = None) -> None: 38 | super().__init__() 39 | if message is None: 40 | self._ball_pos: Vector2D = ball_pos.copy() 41 | self._ball_vel: Vector2D = ball_vel.copy() 42 | self._unum: int = unum 43 | self._player_pos: Vector2D = player_pos.copy() 44 | self._player_body: AngleDeg = player_body.copy() 45 | else: 46 | self._ball_pos: Vector2D = None 47 | self._ball_vel: Vector2D = None 48 | self._unum: int = None 49 | self._player_pos: Vector2D = None 50 | self._player_body: AngleDeg = None 51 | 52 | self._size = Messenger.SIZES[Messenger.Types.BALL_PLAYER] 53 | self._header = Messenger.Types.BALL_PLAYER.value 54 | 55 | self._message = message 56 | 57 | def encode(self) -> str: 58 | if not 1 <= self._unum <= 22: 59 | log.os_log().error(f'(ball player messenger) illegal unum={self._unum}') 60 | log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={self._unum}') 61 | return '' 62 | 63 | if self._ball_pos.abs_x() > ServerParam.i().pitch_half_length() \ 64 | or self._ball_pos.abs_y() > ServerParam.i().pitch_half_width(): 65 | return '' 66 | 67 | SP = ServerParam.i() 68 | ep = 0.001 69 | msg = BallPlayerMessenger.CONVERTER.convert_to_word([ 70 | bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), 71 | bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), 72 | bound(-SP.ball_speed_max() + ep, self._ball_vel.x(), SP.ball_speed_max() - ep), 73 | bound(-SP.ball_speed_max() + ep, self._ball_vel.y(), SP.ball_speed_max() - ep), 74 | self._unum, 75 | bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), 76 | bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), 77 | bound(ep, self._player_body.degree() + 180, 360-ep) 78 | ]) 79 | return f'{self._header}{msg}' 80 | 81 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 82 | bpx, bpy, bvx, bvy, pu, ppx, ppy, pb = BallPlayerMessenger.CONVERTER.convert_to_values(self._message) 83 | 84 | messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) 85 | messenger_memory.add_player(sender, pu, Vector2D(ppx, ppy), current_time, 86 | body=AngleDeg(pb - 180)) # TODO IMP FUNC 87 | 88 | def __repr__(self) -> str: 89 | return "ball player msg" 90 | -------------------------------------------------------------------------------- /lib/messenger/ball_pos_vel_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.soccer_math import min_max 2 | from pyrusgeom.vector_2d import Vector2D 3 | from lib.messenger.messenger import Messenger 4 | from lib.messenger.messenger_memory import MessengerMemory 5 | from lib.rcsc.game_time import GameTime 6 | from lib.rcsc.server_param import ServerParam 7 | 8 | import lib.messenger.converters as converters 9 | 10 | from typing import TYPE_CHECKING 11 | 12 | if TYPE_CHECKING: 13 | from lib.player.world_model import WorldModel 14 | 15 | class BallPosVelMessenger(Messenger): 16 | def __init__(self, message:str=None) -> None: 17 | super().__init__() 18 | self._size = Messenger.SIZES[Messenger.Types.BALL_POS_VEL_MESSAGE] 19 | self._header = Messenger.Types.BALL_POS_VEL_MESSAGE.value 20 | 21 | self._message = message 22 | 23 | def encode(self) -> str: 24 | if not wm.ball().pos_valid(): 25 | return 26 | if not wm.ball().vel_valid(): 27 | return 28 | 29 | SP = ServerParam.i() 30 | pos = wm.ball().pos().copy() 31 | vel = wm.ball().vel().copy() 32 | 33 | x:float = min_max(-SP.pitch_half_length(), pos.x(), SP.pitch_half_length()) + SP.pitch_half_length() 34 | y:float = min_max(-SP.pitch_half_width(), pos.y(), SP.pitch_half_width()) + SP.pitch_half_width() 35 | 36 | x= int(x*1024/(SP.pitch_half_length()*2)) 37 | y= int(y*512/(SP.pitch_half_width()*2)) 38 | 39 | max_speed = SP.ball_speed_max() 40 | vx = min_max(-max_speed, vel.x(), max_speed) + max_speed 41 | vy = min_max(-max_speed, vel.y(), max_speed) + max_speed 42 | 43 | vx = int(vx*64/(max_speed*2)) 44 | vy = int(vy*64/(max_speed*2)) 45 | 46 | val = x 47 | val *= 2**9 48 | val += y 49 | 50 | val*=2**6 51 | val += vx 52 | val*=2**6 53 | val += vy 54 | 55 | return self._header + converters.convert_to_words(val, self._size - 1) 56 | 57 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 58 | SP = ServerParam.i() 59 | 60 | val = converters.convert_to_int(self._message[1:]) 61 | if val is None: 62 | return 63 | 64 | vy = val% 64 65 | val = int(val/64) 66 | 67 | vx = val% 64 68 | val = int(val/64) 69 | 70 | y = val% 512 71 | val = int(val/512) 72 | 73 | x = val 74 | 75 | x = x / 1024 * SP.pitch_half_length()*2 - SP.pitch_half_length() 76 | y = y / 512 * SP.pitch_half_width()*2 - SP.pitch_half_width() 77 | 78 | vx = vx /64 *SP.ball_speed_max()*2 - SP.ball_speed_max() 79 | vy = vy /64 *SP.ball_speed_max()*2 - SP.ball_speed_max() 80 | 81 | pos = Vector2D(x, y) 82 | vel = Vector2D(vx, vy) 83 | 84 | messenger_memory.add_ball(sender, pos, vel, current_time) 85 | 86 | def __repr__(self) -> str: 87 | return "ball pos vel msg" 88 | 89 | -------------------------------------------------------------------------------- /lib/messenger/converters.py: -------------------------------------------------------------------------------- 1 | from math import ceil 2 | from typing import Union 3 | 4 | from pyrusgeom.soccer_math import bound 5 | 6 | from lib.debug.debug import log 7 | 8 | chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ().+-*/?<>_0123456789" 9 | 10 | 11 | class MessengerConverter: 12 | def __init__(self, size, min_max_sizes: list[tuple[float, float, int]]): 13 | self._min_max_sizes = min_max_sizes 14 | self._size = size 15 | 16 | def convert_to_word(self, values): 17 | s = 0 18 | for i, (min_v, max_v, size) in enumerate(self._min_max_sizes): 19 | v = values[i] 20 | log.os_log().debug(f'v={v}') 21 | v = bound(min_v, v, max_v) 22 | v -= min_v 23 | v /= max_v - min_v 24 | v *= size 25 | 26 | s *= size 27 | s += int(v) 28 | 29 | n_chars = len(chars) 30 | words = [] 31 | while s != 0: 32 | words.append(s % n_chars) 33 | s = s // n_chars 34 | log.os_log().debug(f's={s}') 35 | # words.append(s) 36 | msg = '' 37 | for word in words: 38 | msg += chars[word] 39 | 40 | while len(msg) < self._size-1: 41 | msg += chars[0] 42 | 43 | return msg 44 | 45 | def convert_to_values(self, msg): 46 | if msg == '': 47 | return None 48 | 49 | msg = msg[::-1] 50 | 51 | n_chars = len(chars) 52 | digit = len(msg) - 1 53 | 54 | val: int = 0 55 | 56 | for c in msg: 57 | i = chars.find(c) 58 | val += i * int(n_chars ** digit) 59 | digit -= 1 60 | 61 | values = [] 62 | for i, (min_v, max_v, size) in enumerate(self._min_max_sizes[::-1]): 63 | v = val % size 64 | v = v / size * (max_v - min_v) 65 | v = v + min_v 66 | 67 | values.append(v) 68 | val = int(val / size) 69 | return values[::-1] 70 | 71 | 72 | def convert_to_bits(values_min_max_sizes: list[tuple[float, float, float, int]]): 73 | s = 0 74 | for i, (v, min_v, max_v, size) in enumerate(values_min_max_sizes): 75 | v -= min_v 76 | v /= max_v - min_v 77 | v *= size 78 | 79 | s += int(v) 80 | 81 | if i != len(values_min_max_sizes) - 1: 82 | s *= values_min_max_sizes[i + 1][-1] 83 | return s 84 | 85 | 86 | # bYl)0L 87 | 88 | def convert_to_words(val: int, size: int) -> str: 89 | n_chars = len(chars) 90 | words = [] 91 | for _ in range(size - 1): 92 | words.append(val % n_chars) 93 | val = val // n_chars 94 | 95 | words.append(val) 96 | msg = '' 97 | for word in words: 98 | msg += chars[word] 99 | 100 | return msg 101 | 102 | 103 | def convert_to_int(msg: str) -> Union[int, None]: 104 | if msg == '': 105 | return None 106 | 107 | msg = msg[::-1] 108 | 109 | n_chars = len(chars) 110 | digit = len(msg) - 1 111 | 112 | val: int = 0 113 | 114 | for c in msg: 115 | i = chars.find(c) 116 | val += i * int(n_chars ** digit) 117 | digit -= 1 118 | 119 | return val 120 | -------------------------------------------------------------------------------- /lib/messenger/free_form_messenger.py: -------------------------------------------------------------------------------- 1 | from lib.messenger.messenger import Messenger 2 | from lib.messenger.messenger_memory import MessengerMemory 3 | from lib.rcsc.game_time import GameTime 4 | 5 | from typing import TYPE_CHECKING 6 | if TYPE_CHECKING: 7 | from lib.coach.gloabl_world_model import GlobalWorldModel 8 | 9 | class FreeFormMessenger(Messenger): 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | def encode(self, wm: 'GlobalWorldModel') -> str: 14 | return super().encode(wm) 15 | 16 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 17 | return super().decode(messenger_memory, sender, current_time) -------------------------------------------------------------------------------- /lib/messenger/goalie_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class GoalieMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.GOALIE], [ 20 | (53. - 16., 53., 160), 21 | (-20., 20., 400), 22 | (0, 360, 360), 23 | ]) 24 | 25 | def __init__(self, 26 | goalie_unum: int = None, 27 | goalie_pos: Vector2D = None, 28 | goalie_body: AngleDeg = None, 29 | message: str = None) -> None: 30 | super().__init__() 31 | if message is None: 32 | self._goalie_unum: int = goalie_unum 33 | self._goalie_pos: Vector2D = goalie_pos.copy() 34 | self._goalie_body: AngleDeg = goalie_body.copy() 35 | else: 36 | self._goalie_unum: int = None 37 | self._goalie_pos: Vector2D = None 38 | self._goalie_body: AngleDeg = None 39 | 40 | self._size = Messenger.SIZES[Messenger.Types.GOALIE] 41 | self._header = Messenger.Types.GOALIE.value 42 | 43 | self._message = message 44 | 45 | def encode(self) -> str: 46 | if self._goalie_pos.x() < 53. - 16 or self._goalie_pos.x() > 53 or self._goalie_pos.abs_y() > 20: 47 | log.sw_log().communication().add_text(f'(goalie player messenger) goalie pos over poisition range' 48 | f': {self._goalie_pos}') 49 | return '' 50 | 51 | SP = ServerParam.i() 52 | ep = 0.001 53 | msg = GoalieMessenger.CONVERTER.convert_to_word([ 54 | bound(-SP.pitch_half_length() + ep, self._goalie_pos.x(), SP.pitch_half_length() - ep), 55 | bound(-SP.pitch_half_width() + ep, self._goalie_pos.y(), SP.pitch_half_width() - ep), 56 | bound(ep, self._goalie_body.degree() + 180, 360-ep), 57 | ]) 58 | return f'{self._header}{msg}' 59 | 60 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 61 | gpx, gpy, gb = GoalieMessenger.CONVERTER.convert_to_values(self._message) 62 | 63 | messenger_memory.add_opponent_goalie(sender, Vector2D(gpx, gpy), current_time, body=AngleDeg(gb-180)) # TODO IMP FUNC 64 | 65 | def __repr__(self) -> str: 66 | return "ball player msg" 67 | -------------------------------------------------------------------------------- /lib/messenger/goalie_player_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class GoaliePlayerMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.GOALIE_PLAYER], [ 20 | (53. - 16., 53., 160), 21 | (-20., 20., 400), 22 | (0, 360, 360), 23 | (1, 23, 22), 24 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 190), 25 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 124), 26 | ]) 27 | 28 | def __init__(self, 29 | goalie_unum: int = None, 30 | goalie_pos: Vector2D = None, 31 | goalie_body: AngleDeg = None, 32 | player_unum: int = None, 33 | player_pos: Vector2D = None, 34 | message: str = None) -> None: 35 | super().__init__() 36 | if message is None: 37 | self._goalie_unum: int = goalie_unum 38 | self._goalie_pos: Vector2D = goalie_pos.copy() 39 | self._goalie_body: AngleDeg = goalie_body.copy() 40 | self._player_unum: int = player_unum 41 | self._player_pos: Vector2D = player_pos.copy() 42 | else: 43 | self._goalie_unum: int = None 44 | self._goalie_pos: Vector2D = None 45 | self._goalie_body: AngleDeg = None 46 | self._player_unum: int = None 47 | self._player_pos: Vector2D = None 48 | 49 | self._size = Messenger.SIZES[Messenger.Types.GOALIE_PLAYER] 50 | self._header = Messenger.Types.GOALIE_PLAYER.value 51 | 52 | self._message = message 53 | 54 | def encode(self) -> str: 55 | if self._goalie_pos.x() < 53. - 16 or self._goalie_pos.x() > 53 or self._goalie_pos.abs_y() > 20: 56 | log.sw_log().communication().add_text(f'(goalie player messenger) goalie pos over poisition range' 57 | f': {self._goalie_pos}') 58 | return '' 59 | 60 | if not (1<=self._player_unum<=22): 61 | log.sw_log().communication().add_text(f'(goalie player messenger) player unum invalid' 62 | f': {self._player_unum}') 63 | return '' 64 | 65 | SP = ServerParam.i() 66 | ep = 0.001 67 | msg = GoaliePlayerMessenger.CONVERTER.convert_to_word([ 68 | bound(-SP.pitch_half_length() + ep, self._goalie_pos.x(), SP.pitch_half_length() - ep), 69 | bound(-SP.pitch_half_width() + ep, self._goalie_pos.y(), SP.pitch_half_width() - ep), 70 | bound(ep, self._goalie_body.degree() + 180, 360-ep), 71 | self._player_unum, 72 | bound(-SP.pitch_half_length() + ep, self._player_pos.x(), SP.pitch_half_length() - ep), 73 | bound(-SP.pitch_half_width() + ep, self._player_pos.y(), SP.pitch_half_width() - ep), 74 | ]) 75 | return f'{self._header}{msg}' 76 | 77 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 78 | gpx, gpy, gb, pu, ppx, ppy = GoaliePlayerMessenger.CONVERTER.convert_to_values(self._message) 79 | 80 | messenger_memory.add_opponent_goalie(sender, Vector2D(gpx, gpy), current_time, body=AngleDeg(gb-180)) # TODO IMP FUNC 81 | messenger_memory.add_player(sender,pu, Vector2D(ppx, ppy), current_time) # TODO IMP FUNC 82 | 83 | def __repr__(self) -> str: 84 | return "ball player msg" 85 | -------------------------------------------------------------------------------- /lib/messenger/messenger.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | from typing import TYPE_CHECKING 3 | 4 | from lib.debug.debug import log 5 | from lib.debug.level import Level 6 | from pyrusgeom.vector_2d import Vector2D 7 | from lib.messenger.messenger_memory import MessengerMemory 8 | 9 | from lib.rcsc.game_time import GameTime 10 | 11 | from lib.rcsc.server_param import ServerParam 12 | if TYPE_CHECKING: 13 | from lib.player.world_model import WorldModel 14 | 15 | 16 | class Messenger: 17 | DEBUG = True 18 | 19 | class Types(Enum): 20 | BALL = 'b' 21 | PASS = 'p' 22 | NONE = '' 23 | BALL_PLAYER = 'B' 24 | BALL_GOALIE = 'G' 25 | GOALIE_PLAYER = 'e' 26 | GOALIE = 'g' 27 | THREE_PLAYER = 'R' 28 | TWO_PLAYER = 'Q' 29 | ONE_PLAYER = 'P' 30 | RECOVERY = 'r' 31 | STAMINA = 's' 32 | 33 | 34 | SIZES: dict[Types, int] = { 35 | Types.BALL: 6, 36 | Types.PASS: 10, 37 | Types.BALL_PLAYER: 10, 38 | Types.BALL_GOALIE: 10, 39 | Types.GOALIE_PLAYER: 8, 40 | Types.GOALIE: 5, 41 | Types.THREE_PLAYER: 10, 42 | Types.TWO_PLAYER: 7, 43 | Types.ONE_PLAYER: 4, 44 | Types.RECOVERY: 2, 45 | Types.STAMINA: 2, 46 | 47 | } 48 | 49 | def __init__(self, message: str = None) -> None: 50 | self._type: Messenger.Types = Messenger.Types.NONE 51 | self._size: int = 0 52 | self._message: str = message 53 | self._header: str = None 54 | 55 | def encode(self) -> str: 56 | pass 57 | 58 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 59 | pass 60 | 61 | def size(self): 62 | return self._size 63 | 64 | def type(self): 65 | return self._type 66 | 67 | @staticmethod 68 | def encode_all(messages: list['Messenger']): 69 | max_message_size = ServerParam.i().player_say_msg_size() 70 | size = 0 71 | all_messages = "" 72 | log.os_log().debug(f'#'*20) 73 | for i, message in enumerate(messages): 74 | log.os_log().debug(f'msg.t={message._header}') 75 | enc = message.encode() 76 | log.os_log().debug(f'enc: {enc}') 77 | 78 | if not enc: 79 | continue 80 | 81 | if len(enc) + size > max_message_size: 82 | log.os_log().warn("(Messenger encode all) out of limitation. Deny other messages.") 83 | log.os_log().warn("denied messages are:") 84 | for denied in messages[i:]: 85 | log.os_log().warn(denied) 86 | break 87 | 88 | if Messenger.DEBUG: 89 | log.sw_log().action().add_text( f"(encode all messages) a message added, msg={message}, encoded={enc}") 90 | 91 | all_messages += enc 92 | size += len(enc) 93 | return all_messages 94 | 95 | @staticmethod 96 | def decode_all(messenger_memory: MessengerMemory, messages: str, sender: int, current_time: GameTime): 97 | from lib.messenger.pass_messenger import PassMessenger 98 | from lib.messenger.ball_goalie_messenger import BallGoalieMessenger 99 | from lib.messenger.ball_messenger import BallMessenger 100 | from lib.messenger.ball_player_messenger import BallPlayerMessenger 101 | from lib.messenger.goalie_messenger import GoalieMessenger 102 | from lib.messenger.goalie_player_messenger import GoaliePlayerMessenger 103 | from lib.messenger.one_player_messenger import OnePlayerMessenger 104 | from lib.messenger.recovery_message import RecoveryMessenger 105 | from lib.messenger.stamina_messenger import StaminaMessenger 106 | from lib.messenger.three_player_messenger import ThreePlayerMessenger 107 | from lib.messenger.two_player_messenger import TwoPlayerMessenger 108 | 109 | messenger_classes: dict[Messenger.Types, type['Messenger']] = { 110 | Messenger.Types.BALL: BallMessenger, 111 | Messenger.Types.PASS: PassMessenger, 112 | Messenger.Types.BALL_PLAYER: BallPlayerMessenger, 113 | Messenger.Types.BALL_GOALIE: BallGoalieMessenger, 114 | Messenger.Types.GOALIE_PLAYER: GoaliePlayerMessenger, 115 | Messenger.Types.GOALIE: GoalieMessenger, 116 | Messenger.Types.THREE_PLAYER: ThreePlayerMessenger, 117 | Messenger.Types.TWO_PLAYER: TwoPlayerMessenger, 118 | Messenger.Types.ONE_PLAYER: OnePlayerMessenger, 119 | Messenger.Types.RECOVERY: RecoveryMessenger, 120 | Messenger.Types.STAMINA: StaminaMessenger, 121 | } 122 | 123 | index = 0 124 | log.os_log().debug('*'*100) 125 | log.os_log().debug(sender) 126 | log.os_log().debug(messages) 127 | while index < len(messages): 128 | message_type = Messenger.Types(messages[index]) 129 | message_size = Messenger.SIZES[message_type] 130 | 131 | message = messages[index+1: index+message_size] 132 | log.os_log().debug(messages[index: index + message_size]) 133 | 134 | messenger_classes[message_type](message=message).decode(messenger_memory, sender, current_time) 135 | index += message_size 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /lib/messenger/one_player_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | 15 | if TYPE_CHECKING: 16 | from lib.player.world_model import WorldModel 17 | 18 | 19 | class OnePlayerMessenger(Messenger): 20 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.ONE_PLAYER], [ 21 | (1, 23, 22), 22 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 168), 23 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 24 | ]) 25 | 26 | def __init__(self, 27 | u1: int = None, 28 | p1: Vector2D = None, 29 | message: str = None) -> None: 30 | super().__init__() 31 | if message is None: 32 | self._unums: list[int] = [u1] 33 | self._player_poses: list[Vector2D] = [p1.copy()] 34 | else: 35 | self._unums: list[int] = None 36 | self._player_poses: list[Vector2D] = None 37 | 38 | self._size = Messenger.SIZES[Messenger.Types.ONE_PLAYER] 39 | self._header = Messenger.Types.ONE_PLAYER.value 40 | 41 | self._message = message 42 | 43 | def encode(self) -> str: 44 | data = [] 45 | SP = ServerParam.i() 46 | ep = 0.001 47 | for p, u in zip(self._player_poses, self._unums): 48 | if not 1 <= u <= 22: 49 | log.os_log().error(f'(ball player messenger) illegal unum={u}') 50 | log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') 51 | return '' 52 | data.append(u) 53 | data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) 54 | data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) 55 | 56 | msg = OnePlayerMessenger.CONVERTER.convert_to_word(data) 57 | return f'{self._header}{msg}' 58 | 59 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 60 | data = OnePlayerMessenger.CONVERTER.convert_to_values(self._message) 61 | for i in range(1): 62 | u = data[i * 3] 63 | px = data[i * 3 + 1] 64 | py = data[i * 3 + 2] 65 | 66 | messenger_memory.add_player(sender,u, Vector2D(px, py), current_time) # TODO IMP FUNC 67 | 68 | def __repr__(self) -> str: 69 | return "ball player msg" 70 | -------------------------------------------------------------------------------- /lib/messenger/pass_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.soccer_math import min_max, bound 2 | from pyrusgeom.vector_2d import Vector2D 3 | 4 | from lib.messenger.converters import MessengerConverter 5 | from lib.messenger.messenger import Messenger 6 | from lib.messenger.messenger_memory import MessengerMemory 7 | from lib.rcsc.game_time import GameTime 8 | from lib.rcsc.server_param import ServerParam 9 | 10 | import lib.messenger.converters as converters 11 | 12 | from typing import TYPE_CHECKING 13 | 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class PassMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.PASS], [ 20 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), 21 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), 22 | (1, 12, 11), 23 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 2 ** 10), 24 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 2 ** 9), 25 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 26 | (-ServerParam.i().ball_speed_max(), ServerParam.i().ball_speed_max(), 2 ** 6), 27 | ]) 28 | def __init__(self, 29 | receiver_unum: int = None, 30 | receive_point: Vector2D = None, 31 | ball_pos: Vector2D = None, 32 | ball_vel: Vector2D = None, 33 | message: str = None) -> None: 34 | super().__init__() 35 | self._size = Messenger.SIZES[Messenger.Types.PASS] 36 | self._header = Messenger.Types.PASS.value 37 | 38 | if message: 39 | self._message = message 40 | return 41 | self._receiver_unum = receiver_unum 42 | self._receive_point = receive_point.copy() 43 | self._ball_pos = ball_pos.copy() 44 | self._ball_vel = ball_vel.copy() 45 | 46 | def encode(self) -> str: 47 | SP = ServerParam.i() 48 | ep = 0.001 49 | msg = PassMessenger.CONVERTER.convert_to_word([ 50 | bound(-SP.pitch_half_length() + ep, self._receive_point.x(), SP.pitch_half_length() - ep), 51 | bound(-SP.pitch_half_width() + ep, self._receive_point.y(), SP.pitch_half_width() - ep), 52 | self._receiver_unum, 53 | bound(-SP.pitch_half_length() + ep, self._ball_pos.x(), SP.pitch_half_length() - ep), 54 | bound(-SP.pitch_half_width() + ep, self._ball_pos.y(), SP.pitch_half_width() - ep), 55 | bound(ep, self._ball_vel.x(), SP.ball_speed_max() - ep), 56 | bound(ep, self._ball_vel.y(), SP.ball_speed_max() - ep), 57 | ]) 58 | 59 | return f'{self._header}{msg}' 60 | 61 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 62 | rpx, rpy, ru, bpx, bpy, bvx, bvy = PassMessenger.CONVERTER.convert_to_values(self._message) 63 | 64 | messenger_memory.add_pass(sender, ru, Vector2D(rpx, rpy), current_time) 65 | messenger_memory.add_ball(sender, Vector2D(bpx, bpy), Vector2D(bvx, bvy), current_time) 66 | 67 | def __repr__(self) -> str: 68 | return "ball pos vel msg" 69 | 70 | -------------------------------------------------------------------------------- /lib/messenger/player_pos_unum_messenger.py: -------------------------------------------------------------------------------- 1 | from lib.debug.debug import log 2 | from lib.debug.level import Level 3 | from pyrusgeom.soccer_math import min_max 4 | from pyrusgeom.vector_2d import Vector2D 5 | from lib.messenger.messenger import Messenger 6 | from lib.messenger.messenger_memory import MessengerMemory 7 | from lib.rcsc.game_time import GameTime 8 | from lib.rcsc.server_param import ServerParam 9 | 10 | import lib.messenger.converters as converters 11 | 12 | from typing import TYPE_CHECKING 13 | 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | class PlayerPosUnumMessenger(Messenger): 18 | def __init__(self, unum: int = None, message:str=None) -> None: 19 | super().__init__() 20 | self._size = Messenger.SIZES[Messenger.Types.PLAYER_POS_VEL_UNUM] 21 | self._header = Messenger.Types.PLAYER_POS_VEL_UNUM.value 22 | 23 | if message: 24 | self._unum: int = None 25 | self._message = message 26 | else: 27 | self._unum: int = unum 28 | self._message = None 29 | 30 | def encode(self) -> str: 31 | if not 1 <= self._unum <= 22: 32 | log.os_log().error(f"(player pos unum messenger encode) unum is out of limit. unum={self._unum}") 33 | return "" 34 | 35 | unum = self._unum % 11 36 | player = wm.our_player(unum) if self._unum // 11 == 0 else wm.their_player(unum) 37 | 38 | if player is None: 39 | log.os_log().error(f"(player pos unum messenger encode) player is None. unum={self._unum}") 40 | return None 41 | 42 | SP = ServerParam.i() 43 | 44 | pos = player.pos() 45 | x:float = min_max(-SP.pitch_half_length(), pos.x(), SP.pitch_half_length()) + SP.pitch_half_length() 46 | y:float = min_max(-SP.pitch_half_width(), pos.y(), SP.pitch_half_width()) + SP.pitch_half_width() 47 | 48 | val = self._unum - 1 49 | 50 | val *= 168 51 | val += int(x/0.63) 52 | 53 | val *= 109 54 | val += int(y/0.63) 55 | 56 | return self._header + converters.convert_to_words(val, self._size - 1) 57 | 58 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 59 | SP = ServerParam.i() 60 | 61 | val = converters.convert_to_int(self._message[1:]) 62 | if val is None: 63 | return 64 | 65 | y = (val % 109)*0.63 - SP.pitch_half_width() 66 | val = int(val/109) 67 | 68 | x = (val % 168)*0.63 - SP.pitch_half_length() 69 | val = int(val/168) 70 | 71 | unum = (val%22) + 1 72 | pos = Vector2D(x,y) 73 | 74 | log.sw_log().sensor().add_text( f"(PlayerPosUnumMessenger decode) receive a player. unum={unum}, pos={pos}") 75 | 76 | messenger_memory.add_player(sender, unum, pos, current_time) 77 | 78 | def __repr__(self) -> str: 79 | return "player pos unum msg" 80 | -------------------------------------------------------------------------------- /lib/messenger/recovery_message.py: -------------------------------------------------------------------------------- 1 | from lib.messenger.converters import MessengerConverter 2 | from lib.messenger.messenger import Messenger 3 | from lib.messenger.messenger_memory import MessengerMemory 4 | from lib.rcsc.game_time import GameTime 5 | from lib.rcsc.server_param import ServerParam 6 | 7 | 8 | class RecoveryMessenger(Messenger): 9 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.RECOVERY], [ 10 | (ServerParam.i().recover_min(), ServerParam.i().recover_init()+0.01, 74) 11 | ]) 12 | 13 | def __init__(self, 14 | recovery: float = None, 15 | message: str = None): 16 | super().__init__() 17 | self._recovery: float = recovery 18 | 19 | self._size = Messenger.SIZES[Messenger.Types.RECOVERY] 20 | self._header = Messenger.Types.RECOVERY.value 21 | 22 | self._message = message 23 | 24 | def encode(self) -> str: 25 | msg = RecoveryMessenger.CONVERTER.convert_to_word([self._recovery]) 26 | return f'{self._header}{msg}' 27 | 28 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 29 | rate = RecoveryMessenger.CONVERTER.convert_to_values(self._message)[0] 30 | 31 | messenger_memory.add_recovery(sender, rate, current_time) # TODO IMP FUNC 32 | 33 | def __repr__(self): 34 | return 'recovery message' 35 | -------------------------------------------------------------------------------- /lib/messenger/stamina_messenger.py: -------------------------------------------------------------------------------- 1 | from lib.messenger.converters import MessengerConverter 2 | from lib.messenger.messenger import Messenger 3 | from lib.messenger.messenger_memory import MessengerMemory 4 | from lib.rcsc.game_time import GameTime 5 | from lib.rcsc.server_param import ServerParam 6 | 7 | 8 | class StaminaMessenger(Messenger): 9 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.STAMINA], [ 10 | (0, ServerParam.i().stamina_max()+1, 74) 11 | ]) 12 | 13 | def __init__(self, 14 | stamina: float = None, 15 | message: str = None): 16 | super().__init__() 17 | self._stamina: float = stamina 18 | 19 | self._size = Messenger.SIZES[Messenger.Types.STAMINA] 20 | self._header = Messenger.Types.STAMINA.value 21 | 22 | self._message = message 23 | 24 | def encode(self) -> str: 25 | msg = StaminaMessenger.CONVERTER.convert_to_word([self._stamina]) 26 | return f'{self._header}{msg}' 27 | 28 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 29 | rate = StaminaMessenger.CONVERTER.convert_to_values(self._message)[0] 30 | 31 | messenger_memory.add_stamina(sender, rate, current_time) 32 | 33 | def __repr__(self): 34 | return 'recovery message' 35 | -------------------------------------------------------------------------------- /lib/messenger/three_player_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | if TYPE_CHECKING: 15 | from lib.player.world_model import WorldModel 16 | 17 | 18 | class ThreePlayerMessenger(Messenger): 19 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.THREE_PLAYER], [ 20 | (1, 23, 22), 21 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), 22 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 23 | (1, 23, 22), 24 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), 25 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 26 | (1, 23, 22), 27 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), 28 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 29 | ]) 30 | 31 | def __init__(self, 32 | u1: int = None, 33 | p1: Vector2D = None, 34 | u2: int = None, 35 | p2: Vector2D = None, 36 | u3: int = None, 37 | p3: Vector2D = None, 38 | message: str = None) -> None: 39 | super().__init__() 40 | if message is None: 41 | self._unums: list[int] = [u1, u2, u3] 42 | self._player_poses: list[Vector2D] = [p1.copy(), p2.copy(), p3.copy()] 43 | else: 44 | self._unums: list[int] = None 45 | self._player_poses: list[Vector2D] = None 46 | 47 | self._size = Messenger.SIZES[Messenger.Types.THREE_PLAYER] 48 | self._header = Messenger.Types.THREE_PLAYER.value 49 | 50 | self._message = message 51 | 52 | def encode(self) -> str: 53 | data = [] 54 | SP = ServerParam.i() 55 | ep = 0.001 56 | for p, u in zip(self._player_poses,self._unums): 57 | if not 1 <= u <= 22: 58 | log.os_log().error(f'(ball player messenger) illegal unum={u}') 59 | log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') 60 | return '' 61 | data.append(u) 62 | data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) 63 | data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) 64 | 65 | msg = ThreePlayerMessenger.CONVERTER.convert_to_word(data) 66 | return f'{self._header}{msg}' 67 | 68 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 69 | data = ThreePlayerMessenger.CONVERTER.convert_to_values(self._message) 70 | for i in range(3): 71 | u = data[i*3] 72 | px = data[i*3 + 1] 73 | py = data[i*3 + 2] 74 | 75 | messenger_memory.add_player(sender,u, Vector2D(px, py), current_time) # TODO IMP FUNC 76 | 77 | def __repr__(self) -> str: 78 | return "ball player msg" 79 | -------------------------------------------------------------------------------- /lib/messenger/two_player_messenger.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.angle_deg import AngleDeg 2 | from pyrusgeom.soccer_math import bound 3 | from pyrusgeom.vector_2d import Vector2D 4 | 5 | from lib.debug.debug import log 6 | from lib.messenger.converters import MessengerConverter 7 | from lib.messenger.messenger import Messenger 8 | 9 | from lib.messenger.messenger_memory import MessengerMemory 10 | from lib.rcsc.game_time import GameTime 11 | from lib.rcsc.server_param import ServerParam 12 | 13 | from typing import TYPE_CHECKING 14 | 15 | if TYPE_CHECKING: 16 | from lib.player.world_model import WorldModel 17 | 18 | 19 | class TwoPlayerMessenger(Messenger): 20 | CONVERTER = MessengerConverter(Messenger.SIZES[Messenger.Types.TWO_PLAYER], [ 21 | (1, 23, 22), 22 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), 23 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 24 | (1, 23, 22), 25 | (-ServerParam.i().pitch_half_length(), ServerParam.i().pitch_half_length(), 167), 26 | (-ServerParam.i().pitch_half_width(), ServerParam.i().pitch_half_width(), 108), 27 | ]) 28 | 29 | def __init__(self, 30 | u1: int = None, 31 | p1: Vector2D = None, 32 | u2: int = None, 33 | p2: Vector2D = None, 34 | message: str = None) -> None: 35 | super().__init__() 36 | if message is None: 37 | self._unums: list[int] = [u1, u2] 38 | self._player_poses: list[Vector2D] = [p1.copy(), p2.copy()] 39 | else: 40 | self._unums: list[int] = None 41 | self._player_poses: list[Vector2D] = None 42 | 43 | self._size = Messenger.SIZES[Messenger.Types.TWO_PLAYER] 44 | self._header = Messenger.Types.TWO_PLAYER.value 45 | 46 | self._message = message 47 | 48 | def encode(self) -> str: 49 | data = [] 50 | SP = ServerParam.i() 51 | ep = 0.001 52 | for p, u in zip(self._player_poses, self._unums): 53 | if not 1 <= u <= 22: 54 | log.os_log().error(f'(ball player messenger) illegal unum={u}') 55 | log.sw_log().sensor().add_text(f'(ball player messenger) illegal unum={u}') 56 | return '' 57 | data.append(u) 58 | data.append(bound(-SP.pitch_half_length() + ep, p.x(), SP.pitch_half_length() - ep)) 59 | data.append(bound(-SP.pitch_half_width() + ep, p.y(), SP.pitch_half_width() - ep)) 60 | 61 | msg = TwoPlayerMessenger.CONVERTER.convert_to_word(data) 62 | return f'{self._header}{msg}' 63 | 64 | def decode(self, messenger_memory: MessengerMemory, sender: int, current_time: GameTime) -> None: 65 | data = TwoPlayerMessenger.CONVERTER.convert_to_values(self._message) 66 | for i in range(2): 67 | u = data[i * 3] 68 | px = data[i * 3 + 1] 69 | py = data[i * 3 + 2] 70 | 71 | messenger_memory.add_player(sender, u, Vector2D(px, py), current_time) # TODO IMP FUNC 72 | 73 | def __repr__(self) -> str: 74 | return "ball player msg" 75 | -------------------------------------------------------------------------------- /lib/network/udp_socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | import team_config 4 | 5 | MAX_BUFF_SIZE = 8192 6 | 7 | 8 | class IPAddress: 9 | def __init__(self, ip, port): 10 | self._ip = ip 11 | self._port = port 12 | 13 | def tuple(self) -> tuple: 14 | return self.ip(), self.port() 15 | 16 | def __repr__(self): 17 | return self.ip(), self.port() 18 | 19 | def __str__(self): 20 | return f"({self.ip()}:{self.port()}" 21 | 22 | def ip(self): 23 | return self._ip 24 | 25 | def port(self): 26 | return self._port 27 | 28 | 29 | class UDPSocket: 30 | def __init__(self, ip_address: IPAddress): 31 | self._ip: IPAddress = ip_address 32 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 33 | self._sock.settimeout(team_config.SOCKET_INTERVAL) 34 | self._receive_first_message = False 35 | 36 | def send_msg(self, msg: str): 37 | if msg[-1] != '\0': 38 | msg += '\0' 39 | return self._sock.sendto(msg.encode(), self._ip.tuple()) 40 | 41 | def receive_msg(self): 42 | try: 43 | message, server_address = self._sock.recvfrom(MAX_BUFF_SIZE) 44 | if not self._receive_first_message: 45 | self._receive_first_message = True 46 | self._ip._port = server_address[1] 47 | return len(message), message, server_address 48 | except: 49 | message = "" 50 | server_address = 0 51 | return len(message), message, server_address 52 | -------------------------------------------------------------------------------- /lib/parser/global_message_parser.py: -------------------------------------------------------------------------------- 1 | from lib.parser.parser_message_params import MessageParamsParser 2 | 3 | """ 4 | sample version >= 7.0 5 | (see_global TIME ((g l) -52.5 0) ((g r) 52.5 0) ((b) ) 6 | ((p "TEAM" UNUM[ goalie]) [ ][ {t|k}][ {y|r}]) 7 | ....) 8 | <-- arm is global 9 | <-- 't' means tackle 10 | <-- 'k' means kick 11 | <-- 'f' means foul charged 12 | <-- 'y' means yellow card 13 | <-- 'r' means red card 14 | (ok look TIME ((g l) -52.5 0) ((g r) 52.5 0) ((b) ) 15 | ((p "TEAM" UNUM[ goalie]) ) <-- no arm & tackle 16 | ....) 17 | 18 | """ 19 | 20 | 21 | class GlobalFullStateWorldMessageParser: 22 | def __init__(self): 23 | self._dic = {} 24 | 25 | def parse(self, message: str): 26 | self._dic['time'] = message.split(" ")[1] 27 | message = message[message.find("(", 1):-1] 28 | 29 | # parsing ball 30 | msg = message[:message.find("((p")] 31 | MessageParamsParser._parse(self._dic, msg) 32 | 33 | # and now parsing players 34 | msg = message[message.find("((p"):] 35 | self._dic.update(PlayerMessageParser().parse(msg)) 36 | self._dic.update({"teams": { 37 | "team_left": PlayerMessageParser._team_l, 38 | "team_right": PlayerMessageParser._team_r 39 | }}) 40 | 41 | def dic(self): 42 | return self._dic 43 | 44 | 45 | class PlayerMessageParser: 46 | _team_l = None 47 | _team_r = None 48 | 49 | def __init__(self): 50 | self._dic = {} 51 | 52 | @staticmethod 53 | def _parser(dic: dict, message: str): 54 | players = [] 55 | seek = 0 56 | if len(message) < 5: 57 | return 58 | while seek < len(message): 59 | seek = message.find("((p", seek) 60 | next_seek = message.find("((p", seek + 1) 61 | 62 | if next_seek == -1: 63 | next_seek = len(message) 64 | msg = message[seek: next_seek].strip(" ()").split(" ") 65 | k = -1 66 | kk = 0 67 | if msg[3] == 'g' or msg[3] == 'goalie)': 68 | k = 0 69 | player_dic = { 70 | "unum": msg[2].strip("()"), 71 | "pos_x": msg[4 + k], 72 | "pos_y": msg[5 + k], 73 | "vel_x": msg[6 + k], 74 | "vel_y": msg[7 + k], 75 | "body": msg[8 + k], 76 | "neck": msg[9 + k], 77 | } 78 | if PlayerMessageParser._team_l == msg[1]: 79 | player_dic['side_id'] = 'l' 80 | elif PlayerMessageParser._team_r == msg[1]: 81 | player_dic['side_id'] = 'r' 82 | elif PlayerMessageParser._team_l is None: 83 | PlayerMessageParser._team_l = msg[1] 84 | player_dic['side_id'] = 'l' 85 | elif PlayerMessageParser._team_r is None: 86 | PlayerMessageParser._team_r = msg[1] 87 | player_dic['side_id'] = 'r' 88 | 89 | if k == 0: 90 | player_dic['goalie'] = 'g' 91 | ext = [] 92 | if ((k == 0 and len(msg) > 10) 93 | or (k == -1 and len(msg) > 9)): 94 | ext = msg[10 + k:] 95 | if 'k' in ext: 96 | player_dic['kick'] = True 97 | if 't' in ext: 98 | player_dic['tackle'] = True 99 | if 'f' in ext: 100 | player_dic['charged'] = True 101 | if 'y' in ext: 102 | player_dic['card'] = 'y' 103 | if 'r' in ext: 104 | player_dic['card'] = 'r' 105 | 106 | players.append(player_dic) 107 | seek = next_seek 108 | dic["players"] = players 109 | 110 | @staticmethod 111 | def n_inner_dict(message: str): 112 | # dlog.debug(f"message {message}") 113 | n = 0 114 | for c in message[1:-1]: 115 | if c == '(': 116 | n += 1 117 | # dlog.debug(f"n {n}") 118 | return n 119 | 120 | def parse(self, message): 121 | PlayerMessageParser._parser(self._dic, message) 122 | return self._dic 123 | 124 | # 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))) ' 125 | # msg = message[message.find("((p"):] 126 | # a =PlayerMessageParser() 127 | # d = a.parse(msg) 128 | # for p in d['players']: 129 | # debug_print(p['unum'], p['stamina']) 130 | -------------------------------------------------------------------------------- /lib/parser/message_params_parser_see.py: -------------------------------------------------------------------------------- 1 | class MessageParamsParserSee: 2 | def __init__(self) -> None: 3 | pass 4 | 5 | def parse(self, string) -> list: 6 | res = [] 7 | objects_start_index = string.find("((") 8 | 9 | if objects_start_index == -1: 10 | return [] 11 | 12 | objects_string = string[objects_start_index: -1] 13 | objects_list_string = objects_string[1:-1].split(") (") 14 | 15 | for object_string in objects_list_string: 16 | key_end_index = object_string.find(")") 17 | key = object_string[1:key_end_index] 18 | res.append((key, object_string[key_end_index+1:].strip(" "))) 19 | return res -------------------------------------------------------------------------------- /lib/parser/parser_message_fullstate_world.py: -------------------------------------------------------------------------------- 1 | from lib.parser.parser_message_params import MessageParamsParser 2 | 3 | """" 4 | (fullstate