├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── check_requirements.py ├── generate.sh ├── idl └── service.proto ├── requirements.txt ├── scripts ├── create_binary.sh ├── download-proxy.sh ├── download-rcssserver.sh ├── start └── startAll ├── server.py ├── src ├── behaviors │ ├── bhv_block.py │ ├── bhv_kick_planner.py │ ├── bhv_penalty.py │ ├── bhv_setplay.py │ ├── bhv_shoot.py │ ├── bhv_tackle.py │ └── starter │ │ ├── bhv_starter_clearball.py │ │ ├── bhv_starter_dribble.py │ │ ├── bhv_starter_go_to_placed_ball.py │ │ ├── bhv_starter_kick_planner.py │ │ ├── bhv_starter_pass.py │ │ ├── bhv_starter_penalty.py │ │ ├── bhv_starter_setplay.py │ │ ├── bhv_starter_shoot.py │ │ ├── bhv_starter_tackle.py │ │ └── setplay │ │ ├── bhv_starter_intention_wait_after_setplay_kick.py │ │ ├── bhv_starter_prepare_setplay_kick.py │ │ ├── bhv_starter_setplay_freekick.py │ │ ├── bhv_starter_setplay_goal_kick.py │ │ ├── bhv_starter_setplay_indirect_freekick.py │ │ ├── bhv_starter_setplay_kickin.py │ │ ├── bhv_starter_setplay_kickoff.py │ │ └── bhv_starter_their_goal_kick_move.py ├── decision_makers │ ├── decision_maker.py │ ├── goalie_decision_maker.py │ ├── kick_decision_maker.py │ ├── move_decision_maker.py │ ├── penalty_decision_maker.py │ ├── play_on_decision_maker.py │ └── set_play_decision_maker.py ├── formations │ ├── 4-3-3-cyrus-base │ │ ├── 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 │ ├── 4-3-3-helios-base │ │ ├── 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 │ └── 4-3-3 │ │ ├── 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 ├── interfaces │ ├── IAgent.py │ ├── IBehavior.py │ ├── IDecisionMaker.py │ └── IPositionStrategy.py ├── sample_coach_agent.py ├── sample_player_agent.py ├── sample_trainer_agent.py ├── strategy │ ├── formation.py │ ├── formation_file.py │ ├── formation_file_reader.py │ ├── formation_strategy.py │ ├── player_role.py │ └── starter_strategy.py └── utils │ ├── memory.py │ └── tools.py ├── start.py ├── start.sh └── utils ├── __init__.py └── logger_utils.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Update Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'README.md' 9 | 10 | jobs: 11 | update-docs: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout the py2d repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Git 19 | run: | 20 | git config --global user.name 'GitHub Actions Bot' 21 | git config --global user.email 'actions@github.com' 22 | 23 | - name: Check if README was updated 24 | run: | 25 | if git diff --name-only HEAD^ HEAD | grep 'README.md'; then 26 | echo "README.md has changed" 27 | else 28 | echo "README.md has not changed" && exit 0 29 | fi 30 | 31 | - name: Generate random number for branch name 32 | id: random 33 | run: echo "::set-output name=random_number::$(shuf -i 1000-9999 -n 1)" 34 | 35 | - name: Clone the CLSFramework.github.io repository 36 | run: | 37 | git clone https://github.com/CLSFramework/CLSFramework.github.io.git cls-repo 38 | cd cls-repo 39 | 40 | # Copy updated README to target directory in the CLSFramework.github.io repository 41 | cp ../README.md docs/6-basecode/py2d/index.md 42 | 43 | # Create a new branch with a random number appended 44 | git checkout -b update-README-py2d-${{ steps.random.outputs.random_number }} 45 | 46 | - name: Add front matter to index.md before committing 47 | run: | 48 | cd cls-repo 49 | # Add the custom_edit_url to the index.md file 50 | sed -i '1s/^/---\ncustom_edit_url: '\''https:\/\/github.com\/CLSFramework\/py2d\/blob\/main\/README.md'\''\n---\n\n/' docs/6-basecode/py2d/index.md 51 | 52 | - name: Set up authentication using PAT for CLSFramework.github.io 53 | run: | 54 | cd cls-repo 55 | git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/CLSFramework/CLSFramework.github.io.git 56 | 57 | - name: Commit and Push Changes to CLSFramework.github.io 58 | run: | 59 | cd cls-repo 60 | git add docs/6-basecode/py2d/index.md 61 | git commit -m "Update py2d documentation from README" 62 | git push origin update-README-py2d-${{ steps.random.outputs.random_number }} 63 | 64 | - name: Create Pull Request in CLSFramework.github.io using GitHub API 65 | run: | 66 | PR_RESPONSE=$(curl -X POST -H "Authorization: token ${{ secrets.GH_TOKEN }}" \ 67 | -H "Accept: application/vnd.github.v3+json" \ 68 | https://api.github.com/repos/CLSFramework/CLSFramework.github.io/pulls \ 69 | -d '{"title":"Update py2d documentation","head":"update-README-py2d-${{ steps.random.outputs.random_number }}","base":"main","body":"This PR updates the py2d documentation based on changes made in README.md."}') 70 | echo "Pull request created: $PR_RESPONSE" 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | scripts/proxy 3 | scripts/rcssserver 4 | logs/ 5 | __pycache__/ 6 | utils/__pycache__/ 7 | scripts/binary/ 8 | service_pb2_grpc.py 9 | service_pb2.py 10 | service_pb2.pyi 11 | -------------------------------------------------------------------------------- /check_requirements.py: -------------------------------------------------------------------------------- 1 | 2 | import pkg_resources 3 | import sys 4 | 5 | def check_requirements(requirements_file='requirements.txt'): 6 | with open(requirements_file, 'r') as file: 7 | requirements = file.readlines() 8 | 9 | for requirement in requirements: 10 | requirement = requirement.strip() 11 | try: 12 | pkg_resources.require(requirement) 13 | except pkg_resources.VersionConflict as e: 14 | print(f"WARNING: {str(e)}") 15 | except pkg_resources.DistributionNotFound as e: 16 | print(f"ERROR: {str(e)}") 17 | sys.exit(1) 18 | 19 | if __name__ == "__main__": 20 | check_requirements() 21 | -------------------------------------------------------------------------------- /generate.sh: -------------------------------------------------------------------------------- 1 | python3 -m grpc_tools.protoc -I./idl --python_out=./ --pyi_out=./ --grpc_python_out=./ ./idl/*.proto -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio==1.65.4 2 | grpcio-tools==1.65.4 3 | scipy==1.14.1 4 | pyrusgeom==0.1.2 5 | Nuitka==2.5 6 | psutil==5.8.0 -------------------------------------------------------------------------------- /scripts/create_binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # show error if binary directory already exists 5 | 6 | if [ -d "binary" ]; then 7 | echo "binary directory already exists. Please remove it before running this script." 8 | exit 1 9 | fi 10 | 11 | # create binary directory 12 | 13 | mkdir binary 14 | 15 | # copy scripts/proxy to binary directory 16 | 17 | mkdir binary/scripts 18 | cp -r proxy binary/scripts/proxy 19 | 20 | # copy formations directory to binary directory 21 | 22 | mkdir binary/src 23 | cp -r ../src/formations binary/src/formations 24 | 25 | # create binary 26 | 27 | nuitka --standalone --onefile --output-dir=binary ../start.py 28 | 29 | # remove build directory 30 | 31 | rm -rf binary/start.build 32 | rm -rf binary/start.dist 33 | rm -rf binary/start.onefile-build 34 | 35 | # copy start to binary directory 36 | 37 | cp start binary/start 38 | cp startAll binary/startAll -------------------------------------------------------------------------------- /scripts/download-proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # check proxy directory exists, if exists, remove it 4 | if [ -d proxy ]; then 5 | echo "proxy directory exists, remove it" 6 | rm -rf proxy 7 | fi 8 | 9 | mkdir proxy 10 | 11 | cd proxy 12 | 13 | # Check if curl exists 14 | if command -v curl >/dev/null 2>&1; then 15 | echo "curl is installed." 16 | else 17 | echo "curl is not installed. Please install it." 18 | exit 1 19 | fi 20 | 21 | # Check if get exists 22 | if command -v wget >/dev/null 2>&1; then 23 | echo "wget is installed." 24 | else 25 | echo "wget is not installed. Please install it." 26 | exit 1 27 | fi 28 | 29 | # download soccer simulation proxy 30 | wget $(curl -s "https://api.github.com/repos/clsframework/soccer-simulation-proxy/releases/latest" | grep -oP '"browser_download_url": "\K[^"]*' | grep "soccer-simulation-proxy.tar.gz") 31 | 32 | tar -xvf soccer-simulation-proxy.tar.gz 33 | 34 | mv soccer-simulation-proxy/* . 35 | 36 | echo "Inserting code to start.sh..." 37 | 38 | sed -i 's/rpc_type="thrift"/rpc_type="grpc"/' start.sh 39 | 40 | rm -rf soccer-simulation-proxy 41 | 42 | rm soccer-simulation-proxy.tar.gz 43 | -------------------------------------------------------------------------------- /scripts/download-rcssserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # check rcssserver directory exists, if exists, remove it 4 | if [ -d rcssserver ]; then 5 | echo "rcssserver directory exists, remove it" 6 | rm -rf rcssserver 7 | fi 8 | 9 | mkdir rcssserver 10 | 11 | cd rcssserver 12 | 13 | # Check if curl exists 14 | if command -v curl >/dev/null 2>&1; then 15 | echo "curl is installed." 16 | else 17 | echo "curl is not installed. Please install it." 18 | exit 1 19 | fi 20 | 21 | # Check if get exists 22 | if command -v wget >/dev/null 2>&1; then 23 | echo "wget is installed." 24 | else 25 | echo "wget is not installed. Please install it." 26 | exit 1 27 | fi 28 | 29 | # download soccer simulation server App Image 30 | wget $(curl -s https://api.github.com/repos/clsframework/rcssserver/releases/latest | grep -oP '"browser_download_url": "\K(.*rcssserver-x86_64-.*\.AppImage)' | head -n 1) 31 | 32 | # check download is successful 33 | if [ ! -f *.AppImage ]; then 34 | echo "Download failed" 35 | exit 1 36 | fi 37 | 38 | mv rcssserver-x86_64-*.AppImage rcssserver 39 | 40 | chmod +x rcssserver -------------------------------------------------------------------------------- /scripts/start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HOST=$1 4 | BASEDIR=$2 5 | NUM=$3 6 | 7 | teamname="CLSF" 8 | 9 | cd $BASEDIR 10 | 11 | options="--team_name $teamname --server-host $HOST --use-random-rpc-port --auto-close-rpc-server --disable-log-file" 12 | 13 | case $NUM in 14 | 1) 15 | ./start.bin $options --goalie 16 | ;; 17 | 12) 18 | ./start.bin $options --coach 19 | ;; 20 | *) 21 | ./start.bin $options --player 22 | ;; 23 | esac -------------------------------------------------------------------------------- /scripts/startAll: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./start 127.0.0.1 . 1 & 4 | sleep 1 5 | ./start 127.0.0.1 . 2 & 6 | ./start 127.0.0.1 . 3 & 7 | ./start 127.0.0.1 . 4 & 8 | ./start 127.0.0.1 . 5 & 9 | ./start 127.0.0.1 . 6 & 10 | ./start 127.0.0.1 . 7 & 11 | ./start 127.0.0.1 . 8 & 12 | ./start 127.0.0.1 . 9 & 13 | ./start 127.0.0.1 . 10 & 14 | ./start 127.0.0.1 . 11 & 15 | ./start 127.0.0.1 . 12 & -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures 2 | from time import sleep 3 | import service_pb2_grpc as pb2_grpc 4 | import service_pb2 as pb2 5 | from typing import Union 6 | from multiprocessing import Manager, Lock 7 | from utils.logger_utils import setup_logger 8 | import logging 9 | import grpc 10 | import argparse 11 | import datetime 12 | from src.interfaces.IAgent import IAgent 13 | from src.sample_coach_agent import SampleCoachAgent 14 | from src.sample_player_agent import SamplePlayerAgent 15 | from src.sample_trainer_agent import SampleTrainerAgent 16 | import traceback 17 | 18 | 19 | console_logging_level = logging.INFO 20 | file_logging_level = logging.INFO 21 | player_console_logging_level = logging.INFO 22 | player_file_logging_level = logging.DEBUG 23 | 24 | main_logger = None 25 | log_dir = None 26 | 27 | 28 | class GrpcAgent: 29 | def __init__(self, agent_type, uniform_number, logger, debug) -> None: 30 | self.agent_type: pb2.AgentType = agent_type 31 | self.uniform_number: int = uniform_number 32 | self.agent: IAgent = None 33 | self.logger: logging.Logger = logger 34 | if self.agent_type == pb2.AgentType.PlayerT: 35 | self.agent = SamplePlayerAgent(self.logger) 36 | elif self.agent_type == pb2.AgentType.CoachT: 37 | self.agent = SampleCoachAgent(self.logger) 38 | elif self.agent_type == pb2.AgentType.TrainerT: 39 | self.agent = SampleTrainerAgent(self.logger) 40 | self.agent.set_debug_mode(debug) 41 | self.debug_mode: bool = False 42 | 43 | def GetAction(self, state: pb2.State): 44 | self.logger.debug(f"================================= cycle={state.world_model.cycle}.{state.world_model.stoped_cycle} =================================") 45 | # self.logger.debug(f"State: {state}") 46 | try: 47 | if self.agent_type == pb2.AgentType.PlayerT: 48 | return self.GetPlayerActions(state) 49 | elif self.agent_type == pb2.AgentType.CoachT: 50 | return self.GetCoachActions(state) 51 | elif self.agent_type == pb2.AgentType.TrainerT: 52 | return self.GetTrainerActions(state) 53 | except Exception as e: 54 | self.logger.error(f"Error in GetAction: {e}") 55 | self.logger.error(traceback.format_exc()) 56 | return pb2.PlayerActions() 57 | 58 | def GetPlayerActions(self, state: pb2.State): 59 | self.agent.update_actions(state.world_model) 60 | return self.agent.get_actions() 61 | 62 | def GetBestPlannerAction(self, request: pb2.BestPlannerActionRequest) -> int: 63 | self.logger.debug(f"GetBestPlannerAction cycle:{request.state.world_model.cycle} pairs:{len(request.pairs)} unum:{request.state.register_response.uniform_number}") 64 | best_index = max(request.pairs.items(), key=lambda x: x[1].evaluation)[0] 65 | best_action = request.pairs[best_index].action 66 | 67 | while best_action.parent_index and best_action.parent_index > 0: 68 | best_action = request.pairs[best_action.parent_index].action 69 | 70 | res = pb2.BestPlannerActionResponse(index=best_action.index) 71 | return res 72 | 73 | def GetCoachActions(self, state: pb2.State): 74 | self.agent.update_actions(state.world_model) 75 | return self.agent.get_actions() 76 | 77 | def GetTrainerActions(self, state: pb2.State): 78 | self.agent.update_actions(state.world_model) 79 | return self.agent.get_actions() 80 | 81 | def SetServerParams(self, server_params: pb2.ServerParam): 82 | try: 83 | self.logger.debug(f"Server params received unum {server_params.register_response.uniform_number}") 84 | self.agent.set_server_params(server_params) 85 | except Exception as e: 86 | self.logger.error(f"Error in GetAction: {e}") 87 | self.logger.error(traceback.format_exc()) 88 | return pb2.PlayerActions() 89 | 90 | def SetPlayerParams(self, player_params: pb2.PlayerParam): 91 | try: 92 | self.logger.debug(f"Player params received unum {player_params.register_response.uniform_number}") 93 | self.agent.set_player_params(player_params) 94 | except Exception as e: 95 | self.logger.error(f"Error in GetAction: {e}") 96 | self.logger.error(traceback.format_exc()) 97 | return pb2.PlayerActions() 98 | 99 | def SetPlayerType(self, player_type: pb2.PlayerType): 100 | try: 101 | self.logger.debug(f"Player type received unum {player_type.register_response.uniform_number}") 102 | self.agent.set_player_types(player_type) 103 | except Exception as e: 104 | self.logger.error(f"Error in GetAction: {e}") 105 | self.logger.error(traceback.format_exc()) 106 | return pb2.PlayerActions() 107 | 108 | class GameHandler(pb2_grpc.GameServicer): 109 | def __init__(self, shared_lock, shared_number_of_connections, debug) -> None: 110 | self.agents: dict[int, GrpcAgent] = {} 111 | self.shared_lock = shared_lock 112 | self.shared_number_of_connections = shared_number_of_connections 113 | self.debug = debug 114 | 115 | def GetPlayerActions(self, state: pb2.State, context): 116 | main_logger.debug(f"GetPlayerActions unum {state.register_response.uniform_number} at {state.world_model.cycle}") 117 | res = self.agents[state.register_response.client_id].GetAction(state) 118 | return res 119 | 120 | def GetCoachActions(self, state: pb2.State, context): 121 | main_logger.debug(f"GetCoachActions coach at {state.world_model.cycle}") 122 | res = self.agents[state.register_response.client_id].GetAction(state) 123 | return res 124 | 125 | def GetTrainerActions(self, state: pb2.State, context): 126 | main_logger.debug(f"GetTrainerActions trainer at {state.world_model.cycle}") 127 | res = self.agents[state.register_response.client_id].GetAction(state) 128 | return res 129 | 130 | def SendServerParams(self, serverParams: pb2.ServerParam, context): 131 | main_logger.debug(f"Server params received unum {serverParams.register_response.uniform_number}") 132 | self.agents[serverParams.register_response.client_id].SetServerParams(serverParams) 133 | res = pb2.Empty() 134 | return res 135 | 136 | def SendPlayerParams(self, playerParams: pb2.PlayerParam, context): 137 | main_logger.debug(f"Player params received unum {playerParams.register_response.uniform_number}") 138 | self.agents[playerParams.register_response.client_id].SetPlayerParams(playerParams) 139 | res = pb2.Empty() 140 | return res 141 | 142 | def SendPlayerType(self, playerType: pb2.PlayerType, context): 143 | main_logger.debug(f"Player type received unum {playerType.register_response.uniform_number}") 144 | self.agents[playerType.register_response.client_id].SetPlayerType(playerType) 145 | res = pb2.Empty() 146 | return res 147 | 148 | def SendInitMessage(self, initMessage: pb2.InitMessage, context): 149 | main_logger.debug(f"Init message received unum {initMessage.register_response.uniform_number}") 150 | self.agents[initMessage.register_response.client_id].debug_mode = initMessage.debug_mode 151 | res = pb2.Empty() 152 | return res 153 | 154 | def Register(self, register_request: pb2.RegisterRequest, context): 155 | try: 156 | with self.shared_lock: 157 | main_logger.info(f"received register request from team_name: {register_request.team_name} " 158 | f"unum: {register_request.uniform_number} " 159 | f"agent_type: {register_request.agent_type}") 160 | self.shared_number_of_connections.value += 1 161 | main_logger.info(f"Number of connections {self.shared_number_of_connections.value}") 162 | team_name = register_request.team_name 163 | uniform_number = register_request.uniform_number 164 | agent_type = register_request.agent_type 165 | register_response = pb2.RegisterResponse(client_id=self.shared_number_of_connections.value, 166 | team_name=team_name, 167 | uniform_number=uniform_number, 168 | agent_type=agent_type) 169 | logger = setup_logger(f"agent{register_response.uniform_number}_{register_response.client_id}", log_dir, 170 | console_level=player_console_logging_level, file_level=player_file_logging_level) 171 | self.agents[self.shared_number_of_connections.value] = GrpcAgent(agent_type, uniform_number, logger, self.debug) 172 | return register_response 173 | except Exception as e: 174 | main_logger.error(f"Error in Register: {e}") 175 | main_logger.error(traceback.format_exc()) 176 | return pb2.RegisterResponse() 177 | 178 | def SendByeCommand(self, register_response: pb2.RegisterResponse, context): 179 | main_logger.debug(f"Bye command received unum {register_response.uniform_number}") 180 | # with shared_lock: 181 | self.agents.pop(register_response.client_id) 182 | 183 | res = pb2.Empty() 184 | return res 185 | 186 | def GetBestPlannerAction(self, pairs: pb2.BestPlannerActionRequest, context): 187 | main_logger.debug(f"GetBestPlannerAction cycle:{pairs.state.world_model.cycle} pairs:{len(pairs.pairs)} unum:{pairs.register_response.uniform_number}") 188 | res = self.agents[pairs.register_response.client_id].GetBestPlannerAction(pairs) 189 | return res 190 | 191 | 192 | def serve(port, shared_lock, shared_number_of_connections, debug): 193 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=22)) 194 | game_service = GameHandler(shared_lock, shared_number_of_connections, debug) 195 | pb2_grpc.add_GameServicer_to_server(game_service, server) 196 | server.add_insecure_port(f'[::]:{port}') 197 | server.start() 198 | main_logger.info(f"Starting server on port {port}") 199 | 200 | server.wait_for_termination() 201 | 202 | 203 | def main(): 204 | global main_logger, log_dir, file_logging_level, player_file_logging_level 205 | parser = argparse.ArgumentParser(description='Run play maker server') 206 | parser.add_argument('-p', '--rpc-port', required=False, help='The port of the server', default=50051) 207 | parser.add_argument('-l', '--log-dir', required=False, help='The directory of the log file', 208 | default=f'logs/{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}') 209 | parser.add_argument('--disable-log-file', required=False, help='Disable logging to a file', default=False, action='store_true') 210 | parser.add_argument('-d', '--debug', required=False, help='Enable debug mode for agents', default=False, action='store_true') 211 | 212 | args = parser.parse_args() 213 | 214 | log_dir = args.log_dir 215 | if args.disable_log_file: 216 | file_logging_level = None 217 | player_file_logging_level = None 218 | 219 | main_logger = setup_logger("pmservice", log_dir, console_level=console_logging_level, file_level=file_logging_level) 220 | main_logger.info("Starting server") 221 | manager = Manager() 222 | shared_lock = Lock() # Create a Lock for synchronization 223 | shared_number_of_connections = manager.Value('i', 0) 224 | 225 | serve(args.rpc_port, shared_lock, shared_number_of_connections, args.debug) 226 | 227 | if __name__ == '__main__': 228 | main() 229 | 230 | -------------------------------------------------------------------------------- /src/behaviors/bhv_block.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class Bhv_Block(IBehavior): 13 | """ 14 | Bhv_Block is a behavior class that determines whether an agent should block the ball based on the predicted future positions of the ball and the players. 15 | Methods 16 | ------- 17 | __init__(): 18 | Initializes the Bhv_Block instance. 19 | execute(agent): 20 | Executes the block behavior for the agent. Predicts the future position of the ball and checks if the agent or any teammate can block it within a certain number of cycles. 21 | _get_final_target(agent): 22 | Returns the final target position for the agent. 23 | _get_average_dribble_speed(agent): 24 | Returns the average dribble speed of the agent. 25 | _calculate_block_cycles(future_ball_pos, player): 26 | Calculates the number of cycles required for a player to block the ball at a future position. 27 | """ 28 | def __init__(self): 29 | pass 30 | 31 | def execute(self, agent: "SamplePlayerAgent") -> bool: 32 | """ 33 | Executes the block behavior for the agent. Predicts the future position of the ball and checks if the agent or any teammate can block it within a certain number of cycles. 34 | Parameters: 35 | agent (IAgent): The agent executing the behavior. 36 | Returns: 37 | bool: True if the agent or a teammate can block the ball, False otherwise. 38 | Actions: 39 | - Find the predicted future position of the ball based on the current ball position, velocity, and opponent reach steps. 40 | - Calculate the first cycle that the agent or a teammate can block the ball. 41 | - If the agent can block the ball, add a Body_GoToPoint action to the agent. 42 | """ 43 | agent.logger.debug(f'------ Bhv_Block ------') 44 | wm = agent.wm 45 | sp = agent.server_params 46 | 47 | opp_min = wm.intercept_table.first_opponent_reach_steps 48 | current_ball_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 49 | current_ball_vel = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.velocity) 50 | intercept_pos: Vector2D = inertia_n_step_point(current_ball_pos, current_ball_vel, opp_min, sp.ball_decay) 51 | target_pos = self._get_final_target(agent) 52 | dribble_vel = Vector2D.from_polar(self._get_average_dribble_speed(agent), (target_pos - intercept_pos).th()) 53 | home_pos_offside_line_x = agent.strategy.get_offside_line() 54 | 55 | future_ball_pos = intercept_pos 56 | 57 | for cycle in range(opp_min + 1, opp_min + 40): 58 | future_ball_pos += dribble_vel 59 | 60 | if future_ball_pos.abs_x() > sp.pitch_half_length: 61 | agent.logger.debug(f'Bhv_Block: False: future_ball_pos.abs_x() > sp.pitch_half_length') 62 | return False 63 | 64 | if future_ball_pos.abs_y() > sp.pitch_half_width: 65 | agent.logger.debug(f'Bhv_Block: False: future_ball_pos.abs_y() > sp.pitch_half_width') 66 | return False 67 | 68 | if wm.self.uniform_number <= 5: 69 | # Defender should not block the ball if it is too far from the offside line 70 | if future_ball_pos.x() > home_pos_offside_line_x + 10.0: 71 | continue 72 | 73 | for our_player in wm.our_players_dict.values(): 74 | if our_player.is_goalie: 75 | continue 76 | block_cycles = self._calculate_block_cycles(future_ball_pos, our_player) 77 | if block_cycles <= cycle: 78 | if wm.self.uniform_number == our_player.uniform_number: 79 | agent.logger.debug(f'Bhv_Block: True: I can block in {cycle} in {future_ball_pos=}') 80 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(future_ball_pos), 81 | max_dash_power=100.0, 82 | distance_threshold=0.5))) 83 | agent.add_action(PlayerAction(neck_turn_to_ball_or_scan=Neck_TurnToBallOrScan(count_threshold=0))) 84 | agent.add_log_circle(LoggerLevel.BLOCK, future_ball_pos.x(), future_ball_pos.y(), 0.5, 'red', True) 85 | return True 86 | else: 87 | agent.logger.debug(f'Bhv_Block: False: tm {our_player.uniform_number} can block in {block_cycles} in {future_ball_pos=}') 88 | return False 89 | 90 | return False 91 | 92 | def _get_final_target(self, agent: IAgent) -> Vector2D: 93 | """ 94 | Returns the final target position for the agent. 95 | Parameters: 96 | agent (IAgent): The agent executing the behavior. 97 | Returns: 98 | Vector2D: The final target position. 99 | """ 100 | return Vector2D(-agent.server_params.pitch_half_length, 0) 101 | 102 | def _get_average_dribble_speed(self, agent: IAgent) -> float: 103 | """ 104 | Returns the average dribble speed of the agent. 105 | Parameters: 106 | agent (IAgent): The agent executing the behavior. 107 | Returns: 108 | float: The average dribble speed. 109 | """ 110 | return 0.7 111 | 112 | def _calculate_block_cycles(self, future_ball_pos: Vector2D, player: Player) -> int: 113 | """ 114 | Calculates the number of cycles required for a player to block the ball at a future position. 115 | Parameters: 116 | future_ball_pos (Vector2D): The predicted future position of the ball. 117 | player (Player): The player attempting to block the ball. 118 | Returns: 119 | int: The number of cycles required for the player to block the ball. 120 | """ 121 | player_pos = Tools.convert_rpc_vector2d_to_vector2d(player.position) 122 | distance = future_ball_pos.dist(player_pos) 123 | return int(distance) -------------------------------------------------------------------------------- /src/behaviors/bhv_kick_planner.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from src.interfaces.IAgent import IAgent 4 | from pyrusgeom.soccer_math import * 5 | from pyrusgeom.geom_2d import * 6 | from service_pb2 import * 7 | from src.behaviors.bhv_shoot import BhvShoot 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class BhvKickPlanner(IBehavior): 13 | """ 14 | Decision maker class for an agent with the ball. 15 | 16 | Methods 17 | ------- 18 | __init__(): 19 | Initializes the WithBallDecisionMaker instance. 20 | 21 | execute(agent: IAgent): 22 | Makes a decision for the agent when it has the ball. 23 | 24 | _get_helios_offensive_planner(): 25 | Returns an instance of HeliosOffensivePlanner. 26 | 27 | _get_planner_evaluation(agent: IAgent): 28 | Returns an instance of PlannerEvaluation. 29 | 30 | _get_planner_evaluation_effector(agent: IAgent): 31 | Returns an instance of PlannerEvaluationEffector. 32 | 33 | _get_opponent_effector(agent: IAgent): 34 | Determines the opponent effector based on the agent's world model. 35 | """ 36 | 37 | def __init__(self): 38 | pass 39 | 40 | def execute(self, agent: "SamplePlayerAgent"): 41 | agent.logger.debug("--- WithBallDecisionMaker ---") 42 | 43 | BhvShoot().execute(agent) 44 | 45 | agent.add_action( 46 | PlayerAction(helios_offensive_planner=self._get_helios_offensive_planner(agent)) 47 | ) 48 | 49 | def _get_helios_offensive_planner(self, agent): 50 | """ Summary 51 | In this function you can create an instance of HeliosOffensivePlanner and set its attributes. 52 | The HeliosOffensivePlanner is a message that ask proxy to create a tree and find the best chain of actions and execute the first action of the chain. 53 | 54 | Returns: 55 | _type_: HeliosOffensivePlanner 56 | """ 57 | res = HeliosOffensivePlanner(evaluation=self._get_planner_evaluation(agent)) 58 | res.lead_pass = True 59 | res.direct_pass = True 60 | res.through_pass = True 61 | res.simple_pass = True 62 | res.short_dribble = True 63 | res.long_dribble = True 64 | res.simple_shoot = True 65 | res.simple_dribble = True 66 | res.cross = True 67 | 68 | return res 69 | 70 | def _get_planner_evaluation(self, agent: IAgent): 71 | return PlannerEvaluation( 72 | effectors=self._get_planner_evaluation_effector(agent), 73 | ) 74 | 75 | def _get_planner_evaluation_effector(self, agent: IAgent): 76 | return PlannerEvaluationEffector( 77 | opponent_effector=self._get_opponent_effector(agent), 78 | ) 79 | 80 | def _get_opponent_effector(self, agent: IAgent): 81 | wm = agent.wm 82 | 83 | if wm.ball.position.x > 30: 84 | negetive_effect_by_distance = [-5, -4, -3, -2, -1] 85 | else: 86 | negetive_effect_by_distance = [-30, -25, -20, -15, -10, -4, -3, -2, -1] 87 | 88 | return OpponentEffector( 89 | negetive_effect_by_distance=negetive_effect_by_distance, 90 | negetive_effect_by_distance_based_on_first_layer=False, 91 | ) 92 | -------------------------------------------------------------------------------- /src/behaviors/bhv_penalty.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class BhvPenalty(IBehavior): 13 | def __init__(self): 14 | pass 15 | 16 | def execute(self, agent: "SamplePlayerAgent"): 17 | agent.logger.debug("BhvPenalty.execute") 18 | agent.add_action(PlayerAction(helios_penalty=HeliosPenalty())) -------------------------------------------------------------------------------- /src/behaviors/bhv_setplay.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class BhvSetPlay(IBehavior): 13 | def __init__(self): 14 | pass 15 | 16 | def execute(self, agent: "SamplePlayerAgent"): 17 | agent.logger.debug("BhvSetPlay.execute") 18 | agent.add_action(PlayerAction(helios_set_play=HeliosSetPlay())) -------------------------------------------------------------------------------- /src/behaviors/bhv_shoot.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class BhvShoot(IBehavior): 13 | def __init__(self): 14 | pass 15 | 16 | def execute(self, agent: "SamplePlayerAgent"): 17 | agent.logger.debug("BhvShoot.execute") 18 | # To enable this behavior, you need to set ignore_shootInPreprocess to True in the sample_player_agent.py 19 | # Otherwise, the proxy will execute shoot action automatically. 20 | 21 | agent.add_action(PlayerAction(helios_shoot=HeliosShoot())) -------------------------------------------------------------------------------- /src/behaviors/bhv_tackle.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class BhvTackle(IBehavior): 13 | def __init__(self): 14 | pass 15 | 16 | def execute(self, agent: "SamplePlayerAgent"): 17 | agent.logger.debug("BhvTackle.execute") 18 | agent.add_action(PlayerAction(helios_basic_tackle=HeliosBasicTackle(min_prob=0.8, body_thr=100.0))) -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_clearball.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from src.utils.tools import Tools 6 | from service_pb2 import * 7 | 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | 13 | class BhvStarterClearBall(IBehavior): 14 | 15 | def __init__(self): 16 | pass 17 | 18 | def execute(self, agent: "SamplePlayerAgent") -> bool: 19 | wm = agent.wm # Get the world model from the agent 20 | ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) # Get the ball position 21 | target = Vector2D(agent.server_params.pitch_half_length, 0.0) # Default target position 22 | 23 | # Determine the target position based on the ball's position 24 | if ball_pos.x() > -25.0: 25 | if ball_pos.dist(Vector2D(0.0, -agent.server_params.pitch_half_width)) < ball_pos.dist(Vector2D(0.0, agent.server_params.pitch_half_width)): 26 | target = Vector2D(0.0, -34.0) 27 | else: 28 | target = Vector2D(0.0, 34.0) 29 | else: 30 | if abs(ball_pos.y()) < 10 and ball_pos.x() < -10.0: 31 | if ball_pos.y() > 0.0: 32 | target = Vector2D(-agent.server_params.pitch_half_length, 20.0) 33 | else: 34 | target = Vector2D(-agent.server_params.pitch_half_length, -20.0) 35 | else: 36 | if ball_pos.y() > 0.0: 37 | target = Vector2D(ball_pos.x(), 34.0) 38 | else: 39 | target = Vector2D(ball_pos.x(), -34.0) 40 | 41 | # Log the clearing action 42 | agent.add_log_message( 43 | LoggerLevel.CLEAR, 44 | f": Clearing to {target}", 45 | agent.wm.self.position.x, 46 | agent.wm.self.position.y - 2, 47 | "\033[31m", 48 | ) 49 | agent.add_log_text(LoggerLevel.CLEAR, f": Clearing to {target}") 50 | agent.logger.debug(f"Clearing to {target}") 51 | 52 | # Add the clearing action to the agent's action list 53 | agent.add_action( 54 | PlayerAction( 55 | body_smart_kick=Body_SmartKick( 56 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target), 57 | first_speed=2.7, 58 | first_speed_threshold=2.7, 59 | max_steps=3, 60 | ) 61 | ) 62 | ) 63 | return True 64 | -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_dribble.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from src.utils.tools import Tools 4 | from pyrusgeom.vector_2d import Vector2D 5 | from pyrusgeom.sector_2d import Sector2D 6 | from service_pb2 import * 7 | 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | 13 | class BhvStarterDribble(IBehavior): 14 | # Initialize the behavior 15 | def __init__(self): 16 | pass 17 | 18 | # Execute the dribbling behavior 19 | def execute(self, agent: "SamplePlayerAgent") -> bool: 20 | ''' 21 | Execute the dribbling behavior. 22 | Args: 23 | agent (SamplePlayerAgent): The agent that will execute the 24 | behavior. 25 | Returns: 26 | bool: True if the action was added to the agent's action list,False otherwise. 27 | ''' 28 | 29 | agent.logger.debug("BhvStarterDribble.execute") 30 | wm = agent.wm 31 | ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) # Get ball position 32 | dribble_angle = (Vector2D(52.5, 0) - ball_pos).th().degree() # Calculate dribble angle 33 | dribble_speed = 0.8 # Set dribble speed 34 | dribble_threshold = 0.7 # Set dribble speed threshold 35 | dribble_radius = 3 # Set dribble radius 36 | dribble_sector = Sector2D( 37 | ball_pos, 0, dribble_radius, dribble_angle - 15, dribble_angle + 15 # Define dribble sector 38 | ) 39 | # Define dribble angles 40 | dribble_angles = [dribble_angle , dribble_angle - 30, dribble_angle + 30] 41 | # Calculate dribble targets base on the dribble angles 42 | targets = list(map(lambda angle: Vector2D.polar2vector(dribble_radius, angle) + ball_pos, dribble_angles)) 43 | for target in targets: 44 | # Check if there are no opponents in the dribble sector 45 | if Tools.exist_opponent_in(agent, dribble_sector): 46 | targets.remove(target) 47 | continue 48 | 49 | if len(targets) > 0: 50 | # Get the best target to dribble based on the nearest target to the opponent's goal 51 | best_target = min(targets, key=lambda target: target.dist(Vector2D(52.5, 0))) 52 | agent.add_log_message( 53 | LoggerLevel.DRIBBLE, 54 | f": Dribbling to {best_target}", 55 | agent.wm.self.position.x, 56 | agent.wm.self.position.y - 2, 57 | "\033[31m", 58 | ) 59 | agent.add_log_text(LoggerLevel.DRIBBLE, f": Dribbling to {best_target}") 60 | agent.logger.debug(f"Dribbling to {best_target}") 61 | agent.add_action( 62 | PlayerAction( 63 | body_smart_kick=Body_SmartKick( 64 | target_point=Tools.convert_vector2d_to_rpc_vector2d(best_target), 65 | first_speed=dribble_speed, 66 | first_speed_threshold=dribble_threshold, 67 | max_steps=2, 68 | ) 69 | ) 70 | ) 71 | return True 72 | return False 73 | -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_go_to_placed_ball.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from service_pb2 import * 3 | from pyrusgeom.vector_2d import Vector2D 4 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 5 | from src.utils.tools import Tools 6 | 7 | if TYPE_CHECKING: 8 | from src.sample_player_agent import SamplePlayerAgent 9 | 10 | 11 | class BhvStarterGoToPlacedBall: 12 | 13 | def __init__(self, angle: float): 14 | self.M_ball_place_angle = angle 15 | 16 | 17 | def execute(self, agent: "SamplePlayerAgent") -> bool: 18 | ''' 19 | Execute the movment behavior of the kicker in setplay which is finding the place that agent should be. 20 | Args: 21 | agent (SamplePlayerAgent): The agent that will execute the 22 | behavior. 23 | Returns: 24 | bool: True if the action was added to the agent's action list,False otherwise. 25 | ''' 26 | 27 | setplay = BhvStarterSetPlay() 28 | 29 | dir_margin = 15.0 30 | sp = agent.server_params 31 | wm = agent.wm 32 | #angle between self and ball 33 | angle_diff = wm.ball.angle_from_self - self.M_ball_place_angle 34 | 35 | if abs(angle_diff) < dir_margin and wm.ball.dist_from_self < (agent.player_types[wm.self.id].player_size + sp.ball_size + 0.08): 36 | # already reach 37 | return False 38 | 39 | # decide sub-target point 40 | ball_position = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 41 | sub_target = ball_position + Vector2D.polar2vector(2.0, self.M_ball_place_angle + 180.0) 42 | 43 | # calculate dash power base on the distance to the ball 44 | dash_power = 20.0 45 | dash_speed = -1.0 46 | if wm.ball.dist_from_self > 2.0: 47 | dash_power = setplay.get_set_play_dash_power(agent) 48 | else: 49 | dash_speed = agent.player_types[wm.self.id].player_size 50 | dash_power = Tools.get_dash_power_to_keep_speed(agent, dash_speed, wm.self.effort) #DEBUG NEEDED 51 | # it is necessary to go to sub target point 52 | if abs(angle_diff) > dir_margin: 53 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(sub_target), distance_threshold=0.1, max_dash_power=50))) 54 | # dir diff is small. go to ball 55 | else: 56 | # body dir is not right 57 | if abs(wm.ball.angle_from_self - wm.self.body_direction) > 1.5: 58 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 59 | # dash to ball 60 | else: 61 | agent.add_action(PlayerAction(dash=Dash(power=dash_power, relative_direction=0))) 62 | 63 | return True -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_kick_planner.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from service_pb2 import * 6 | from src.behaviors.starter.bhv_starter_clearball import BhvStarterClearBall 7 | from src.behaviors.starter.bhv_starter_pass import BhvStarterPass 8 | from src.behaviors.starter.bhv_starter_dribble import BhvStarterDribble 9 | from src.behaviors.starter.bhv_starter_shoot import BhvStarterShoot 10 | from src.utils.tools import Tools 11 | 12 | 13 | if TYPE_CHECKING: 14 | from src.sample_player_agent import SamplePlayerAgent 15 | 16 | class BhvStarterKickPlanner(IBehavior): 17 | def __init__(self): 18 | self.starter_shoot = BhvStarterShoot() 19 | self.starter_clear_ball = BhvStarterClearBall() 20 | self.starter_dribble = BhvStarterDribble() 21 | self.starter_pass = BhvStarterPass() 22 | 23 | def execute(self, agent: "SamplePlayerAgent"): 24 | ''' 25 | Execute the kick planner behavior which decides whether to shoot, pass, dribble, hold the ball, or clear the ball. 26 | Args: 27 | agent (SamplePlayerAgent): The agent that will execute the 28 | behavior. 29 | Returns: 30 | bool: True if the action was added to the agent's action list,False otherwise. 31 | ''' 32 | agent.logger.debug("BhvStarterKickPlanner.execute") 33 | 34 | # Checking if shoot is possible 35 | if self.starter_shoot.execute(agent): 36 | agent.logger.debug("Shooting") 37 | 38 | # Find nearest opponent distance 39 | opps = Tools.get_opponents_from_self(agent) 40 | nearest_opp_dist = min((opp.dist_from_self for opp in opps), default=1000.0) 41 | 42 | # If nearest opponent is close, pass 43 | if nearest_opp_dist < 10: 44 | if self.starter_pass.execute(agent): 45 | agent.logger.debug("Passing") 46 | 47 | # If nearest opponent is far, dribble 48 | if self.starter_dribble.execute(agent): 49 | agent.logger.debug("Dribbling") 50 | 51 | # If dribble is not possible, hold the ball 52 | if nearest_opp_dist > 2.5: 53 | agent.add_action(PlayerAction(body_hold_ball=Body_HoldBall())) 54 | agent.logger.debug("Holding ball") 55 | 56 | # If holding the ball is not possible, clear the ball 57 | if self.starter_clear_ball.execute(agent): 58 | agent.logger.debug("Clearing ball") 59 | 60 | return True 61 | -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_pass.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from src.utils.tools import Tools 6 | from service_pb2 import * 7 | 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | 13 | class BhvStarterPass(IBehavior): 14 | def __init__(self): 15 | pass 16 | 17 | def execute(self, agent: "SamplePlayerAgent") -> bool: 18 | ''' 19 | Execute the passing behavior which is finding the best pass target and executing the pass action. 20 | Args: 21 | agent (SamplePlayerAgent): The agent that will execute the 22 | behavior. 23 | Returns: 24 | bool: True if the action was added to the agent's action list,False otherwise. 25 | 26 | ''' 27 | # Log the execution of the behavior 28 | agent.logger.debug("BhvStarterPass.execute") 29 | 30 | # Get the world model from the agent 31 | wm = agent.wm 32 | 33 | # Initialize the list of potential pass targets 34 | targets: list[Vector2D] = self.get_candidates(agent) 35 | 36 | # Get the best pass target 37 | best_target = self.get_best_candidate(agent, targets) 38 | 39 | # If there is a valid pass target, execute the pass action 40 | if best_target is not None: 41 | first_speed = 2.5 if wm.game_mode_type == GameModeType.PlayOn else 2.7 42 | first_speed_threshold = ( 43 | 2.5 if wm.game_mode_type == GameModeType.PlayOn else 0.0 44 | ) 45 | max_steps = 3 if wm.game_mode_type == GameModeType.PlayOn else 1 46 | 47 | # Log the pass action 48 | agent.add_log_message( 49 | LoggerLevel.PASS, 50 | f": Passing to {best_target}", 51 | agent.wm.self.position.x, 52 | agent.wm.self.position.y - 2, 53 | "\033[31m", 54 | ) 55 | agent.add_log_text(LoggerLevel.PASS, f": Passing to {best_target}") 56 | agent.logger.debug(f"Passing to {best_target}") 57 | 58 | # Add the pass action to the agent's action list 59 | agent.add_action( 60 | PlayerAction( 61 | body_smart_kick=Body_SmartKick( 62 | target_point=Tools.convert_vector2d_to_rpc_vector2d( 63 | best_target 64 | ), 65 | first_speed=first_speed, 66 | first_speed_threshold=first_speed_threshold, 67 | max_steps=max_steps, 68 | ) 69 | ) 70 | ) 71 | return True 72 | 73 | # Return False if no valid pass targets are found 74 | return False 75 | 76 | def get_candidates(self, agent: "SamplePlayerAgent") -> list[Vector2D]: 77 | """ 78 | Identify potential pass targets for the agent. 79 | This function evaluates the positions of the agent's teammates and determines 80 | which ones are valid pass targets based on several criteria, such as distance 81 | from the ball, distance from the agent, and offside position. It also checks 82 | for the presence of opponents in the passing path. 83 | Args: 84 | agent (SamplePlayerAgent): The agent for which to find pass targets. 85 | Returns: 86 | list[Vector2D]: A list of valid pass target positions as Vector2D objects. 87 | """ 88 | # Get the world model from the agent 89 | wm = agent.wm 90 | 91 | # Initialize the list of potential pass targets 92 | targets: list[Vector2D] = [] 93 | 94 | # Get the positions of the ball and the agent 95 | ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) 96 | self_pos = Vector2D(wm.self.position.x, wm.self.position.y) 97 | 98 | # Iterate over teammates position and teammates around position to find valid pass targets 99 | for teammate in wm.teammates: 100 | if ( 101 | teammate == None 102 | or teammate.uniform_number == wm.self.uniform_number 103 | or teammate.uniform_number < 0 104 | ): 105 | continue 106 | #teammate positon to pass 107 | tm_pos = Vector2D(teammate.position.x, teammate.position.y) 108 | 109 | if self.target_point_validation(agent, tm_pos): 110 | targets.append(tm_pos) 111 | 112 | 113 | # points around the teammate 114 | raduis = 2.0 115 | for angle in range(0, 360, 30): 116 | pass_poss = Vector2D(teammate.position.x, teammate.position.y) + Vector2D.polar2vector(raduis, angle) 117 | 118 | if self.target_point_validation(agent, pass_poss): 119 | targets.append(pass_poss) 120 | 121 | 122 | # Return the list of valid pass targets 123 | return targets 124 | 125 | def get_best_candidate(self, agent: "SamplePlayerAgent", targets: list[Vector2D]) -> Union[Vector2D, None]: 126 | """ 127 | Determine the best candidate target from a list of targets for the given agent. 128 | Args: 129 | agent (SamplePlayerAgent): The agent that will execute the 130 | behavior. 131 | targets (list[Vector2D]): A list of potential target positions represented as Vector2D objects. 132 | Returns: 133 | Union[Vector2D, None]: The best candidate target based on the highest x-coordinate value, 134 | or None if the targets list is empty. 135 | """ 136 | # Get the world model from the agent 137 | wm = agent.wm 138 | 139 | if len(targets) == 0: 140 | return None 141 | 142 | # Initialize the best pass target 143 | best_target: Vector2D = max(targets, key=lambda target: target.x()) 144 | 145 | # Return the best pass target 146 | return best_target 147 | 148 | def target_point_validation(self, agent: "SamplePlayerAgent", target : Vector2D) -> bool: 149 | """ 150 | Check if the pass point is valid for the agent to pass the ball to the target. 151 | Args: 152 | agent (SamplePlayerAgent): The agent that will execute the 153 | behavior. 154 | target (Vector2D): The target position to pass the ball to. 155 | Returns: 156 | bool: True if the pass point is valid, False otherwise. 157 | """ 158 | wm = agent.wm 159 | 160 | # Get the positions of the ball and the agent 161 | ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) 162 | self_pos = Vector2D(wm.self.position.x, wm.self.position.y) 163 | 164 | # Check if the teammate is too far from the ball 165 | if target.dist(ball_pos) > 30.0: 166 | return False 167 | 168 | # Check if the teammate is too close to the agent 169 | if self_pos.dist(target) < 2.0: 170 | return False 171 | 172 | # Check if the teammate is offside 173 | if target.x() > wm.offside_line_x - 0.5: 174 | return False 175 | 176 | # Define a sector to check for opponents 177 | check_root = Sector2D( 178 | ball_pos, 179 | 1.0, 180 | target.dist(ball_pos) + 3.0, 181 | (target - ball_pos).th().degree() - 15.0, 182 | (target - ball_pos).th().degree() + 15.0, 183 | ) 184 | 185 | # Check if there are no opponents in the sector 186 | if Tools.exist_opponent_in(agent, check_root): 187 | return False 188 | 189 | 190 | return True -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_shoot.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IBehavior import IBehavior 3 | from pyrusgeom.vector_2d import Vector2D 4 | from service_pb2 import * 5 | from src.utils.tools import Tools 6 | 7 | 8 | if TYPE_CHECKING: 9 | from src.sample_player_agent import SamplePlayerAgent 10 | 11 | 12 | class BhvStarterShoot(IBehavior): 13 | def __init__(self): 14 | pass 15 | 16 | def execute(self, agent: "SamplePlayerAgent") -> bool: 17 | """Executes the shooting behavior for the agent. 18 | Args: 19 | agent (SamplePlayerAgent): The agent that will execute the 20 | behavior. 21 | Returns: 22 | bool: True if the action was added to the agent's action list,False otherwise. 23 | """ 24 | 25 | agent.logger.debug("BhvStarterShoot.execute") 26 | wm = agent.wm 27 | ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) 28 | ball_max_velocity = agent.server_params.ball_speed_max 29 | 30 | # Define goal positions 31 | center_goal = Vector2D(agent.server_params.pitch_half_length, 0.0) 32 | right_goal = Vector2D( 33 | agent.server_params.pitch_half_length, agent.server_params.goal_width / 2.0 - 1.0 34 | ) # Lower Pole 35 | left_goal = Vector2D( 36 | agent.server_params.pitch_half_length, -agent.server_params.goal_width / 2.0 + 1.0 37 | ) # Upper Pole 38 | 39 | # Determine which goal post to shoot at 40 | target = left_goal if ball_pos.dist(right_goal) < ball_pos.dist(left_goal) else right_goal 41 | 42 | # Check if the ball is within shooting range 43 | if ball_pos.dist(center_goal) <= 25.0: 44 | 45 | # Log and perform the action to shoot at the left goal 46 | agent.add_log_message( 47 | LoggerLevel.SHOOT, 48 | f": Shooting to {target}", 49 | agent.wm.self.position.x, 50 | agent.wm.self.position.y - 2, 51 | "\033[31m", 52 | ) 53 | agent.add_log_text(LoggerLevel.SHOOT, f": Shooting to {target}") 54 | agent.logger.debug(f"Shooting to {target}") 55 | agent.add_action( 56 | PlayerAction( 57 | body_smart_kick=Body_SmartKick( 58 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target), 59 | first_speed=ball_max_velocity, 60 | first_speed_threshold=0.1, 61 | max_steps=3, 62 | ) 63 | ) 64 | ) 65 | return True 66 | return False 67 | -------------------------------------------------------------------------------- /src/behaviors/starter/bhv_starter_tackle.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | from src.utils.tools import Tools 4 | from pyrusgeom.geom_2d import * 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | from src.interfaces.IBehavior import IBehavior 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | 13 | class BhvStarterTackle(IBehavior): 14 | def __init__(self, min_prob: float, body_thr: float): 15 | self.min_prob = min_prob 16 | self.body_thr = body_thr 17 | 18 | def execute(self, agent: "SamplePlayerAgent"): 19 | ''' 20 | Executes the tackle behavior for the agent base on the card type and the tackle probability. 21 | Args: 22 | agent (SamplePlayerAgent): The agent that will execute the behavior. 23 | Returns: 24 | ''' 25 | 26 | agent.logger.debug("BhvStarterTackle.execute") 27 | wm = agent.wm 28 | use_foul = False 29 | tackle_prob = wm.self.tackle_probability 30 | if ( 31 | wm.self.card == CardType.NO_CARD 32 | and ( 33 | wm.ball.position.x > agent.server_params.our_penalty_area_line_x + 0.5 34 | or abs(wm.ball.position.y) 35 | > agent.server_params.penalty_area_half_width + 0.5 36 | ) 37 | and tackle_prob < wm.self.foul_probability 38 | ): 39 | tackle_prob = wm.self.foul_probability 40 | use_foul = True 41 | 42 | if tackle_prob < self.min_prob: 43 | return 44 | 45 | self_min = wm.intercept_table.self_reach_steps 46 | mate_min = wm.intercept_table.first_teammate_reach_steps 47 | opp_min = wm.intercept_table.first_opponent_reach_steps 48 | 49 | self_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.self.position) 50 | ball_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 51 | ball_velocity = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.velocity) 52 | 53 | self_reach_point = inertia_n_step_point( 54 | ball_pos, ball_velocity, self_min, agent.server_params.ball_decay 55 | ) 56 | 57 | ball_will_be_in_our_goal = False 58 | 59 | if self_reach_point.x() < -agent.server_params.pitch_half_length: 60 | 61 | ball_ray = Ray2D(ball_pos, ball_velocity.th()) 62 | goal_line = Line2D( 63 | Vector2D(-agent.server_params.pitch_half_length, 10.0), 64 | Vector2D(-agent.server_params.pitch_half_length, -10.0), 65 | ) 66 | 67 | intersect = ball_ray.intersection(goal_line) 68 | 69 | if ( 70 | intersect.is_valid() 71 | and intersect.abs_y() < (agent.server_params.goal_width / 2) + 1 72 | ): 73 | ball_will_be_in_our_goal = True 74 | 75 | if ( 76 | opp_min < 2 77 | or ball_will_be_in_our_goal 78 | or (opp_min < self_min - 3 and opp_min < mate_min - 3) 79 | or ( 80 | self_min >= 5 81 | and ball_pos.dist2(Vector2D(agent.server_params.pitch_half_length, 0)) 82 | < 100 83 | ) 84 | and ( 85 | (Vector2D(agent.server_params.pitch_half_length, 0) - self_pos).th() 86 | - wm.self.body_direction 87 | ).abs() 88 | < 45 89 | ): 90 | # Try tackle 91 | pass 92 | else: 93 | return 94 | 95 | BhvStarterTackle.ExecuteOldVersion(self, agent, use_foul) 96 | 97 | def ExecuteOldVersion(self, agent: IAgent, use_foul: bool): 98 | 99 | wm = agent.wm 100 | tackle_power = agent.server_params.max_tackle_power 101 | 102 | if abs(wm.self.body_direction) < self.body_thr: 103 | agent.add_action( 104 | PlayerAction(tackle=Tackle(power_or_dir=tackle_power, foul=use_foul)) 105 | ) 106 | 107 | tackle_power = -agent.server_params.max_back_tackle_power 108 | 109 | if tackle_power < 0.0 and abs(wm.self.body_direction) > 180 - self.body_thr: 110 | agent.add_action( 111 | PlayerAction(tackle=Tackle(power_or_dir=tackle_power, foul=False)) 112 | ) 113 | 114 | return 115 | 116 | def ExecuteV12(self, agent: IAgent, use_foul: bool): 117 | 118 | s_last_execute_time = agent.wm.cycle 119 | s_result = False 120 | s_best_angle = AngleDeg(0, 0) 121 | 122 | wm = agent.wm 123 | 124 | if s_last_execute_time == wm.time(): 125 | agent.add_log_text(LoggerLevel.TEAM, f": called several times") 126 | if s_result: 127 | agent.add_log_text( 128 | LoggerLevel.TEAM, 129 | f"{__file__}: executeV12() tackle angle={s_best_angle.degree()}", 130 | ) 131 | agent.add_log_message( 132 | LoggerLevel.TEAM, 133 | f"BasicTackle{s_best_angle.degree()}", 134 | agent.wm.self.position.x, 135 | agent.wm.self.position.y - 2, 136 | "\033[31m", 137 | ) 138 | 139 | tackle_dir = (s_best_angle - wm.self.body_direction).degree() 140 | agent.add_action( 141 | PlayerAction(tackle=Tackle(power_or_dir=tackle_dir, foul=use_foul)) 142 | ) 143 | 144 | s_last_execute_time = wm.cycle 145 | s_result = False 146 | 147 | SP = agent.server_params 148 | 149 | opp_goal = Vector2D(SP.pitch_half_length, 0.0) 150 | our_goal = Vector2D(-SP.pitch_half_length, 0.0) 151 | kickable_opponent = True 152 | if wm.intercept_table.first_opponent_reach_steps > 1: 153 | kickable_opponent = False 154 | virtual_accel = ( 155 | kickable_opponent 156 | and Vector2D(our_goal - wm.ball.position).set_length(0.5) 157 | or Vector2D(0.0, 0.0) 158 | ) 159 | shoot_chance = wm.ball.position.dist(opp_goal) < 20.0 160 | 161 | ball_rel_angle = wm.ball.angleFromSelf() - wm.self.body_direction 162 | tackle_rate = SP.tackle_power_rate * (1.0 - 0.5 * abs(ball_rel_angle) / 180.0) 163 | 164 | best_angle = AngleDeg(0.0) 165 | max_speed = -1.0 166 | 167 | for a in range(-180.0, 180.0, 10.0): 168 | rel_angle = AngleDeg(a - wm.self.body_direction) 169 | 170 | eff_power = SP.max_tackle_power + ( 171 | (SP.max_tackle_power - SP.max_back_tackle_power) 172 | * (1.0 - rel_angle.abs() / 180.0) 173 | ) 174 | eff_power *= tackle_rate 175 | 176 | vel = Vector2D( 177 | wm.ball.velocity + Vector2D.polar2vector(eff_power, AngleDeg(a)) 178 | ) 179 | vel += virtual_accel 180 | 181 | speed = vel.r() 182 | if speed > SP.ball_speed_max: 183 | vel *= SP.ball_speed_max / speed 184 | speed = SP.ball_speed_max 185 | 186 | if abs(vel.th()) > 90.0: 187 | continue 188 | 189 | ball_next = wm.ball.position + vel 190 | 191 | maybe_opponent_get_ball = False 192 | 193 | for o in wm.opponents: 194 | if o.pos_count > 10: 195 | continue 196 | if o.ghost_count > 0: 197 | continue 198 | if o.is_tackling: 199 | continue 200 | if o.dist_from_ball > 6.0: 201 | break 202 | 203 | opp_pos = Vector2D(o.position + o.velocity) 204 | if opp_pos.dist(ball_next) < SP.kickable_area + 0.1: 205 | maybe_opponent_get_ball = True 206 | break 207 | 208 | if maybe_opponent_get_ball: 209 | continue 210 | 211 | if shoot_chance: 212 | ball_ray = Ray2D(wm.ball.position, vel.th()) 213 | goal_line = Line2D( 214 | Vector2D(SP.pitch_half_length, 10.0), 215 | Vector2D(SP.pitch_half_length, -10.0), 216 | ) 217 | intersect = Vector2D(ball_ray.intersection(goal_line)) 218 | if ( 219 | intersect._is_valid 220 | and intersect.abs_y() < (SP.goal_width / 2.0) - 3.0 221 | ): 222 | speed += 10.0 223 | 224 | if speed > max_speed: 225 | max_speed = speed 226 | best_angle = AngleDeg(a) 227 | 228 | if max_speed < 0.0: 229 | s_result = False 230 | agent.add_log_text( 231 | LoggerLevel.TEAM, 232 | f"{__file__}: failed executeV12. max_speed={max_speed}", 233 | ) 234 | return False 235 | 236 | s_result = True 237 | s_best_angle = best_angle 238 | 239 | agent.add_log_text( 240 | LoggerLevel.TEAM, f"{__file__}: executeV12() angle={best_angle.degree()}" 241 | ) 242 | agent.add_log_message( 243 | LoggerLevel.TEAM, 244 | f"BasicTackle{best_angle.degree()}", 245 | agent.wm.self.position.x, 246 | agent.wm.self.position.y - 2, 247 | "\033[31m", 248 | ) 249 | 250 | tackle_dir = (best_angle - wm.self.body_direction).degree() 251 | agent.add_action( 252 | PlayerAction(tackle=Tackle(power_or_dir=tackle_dir, foul=use_foul)) 253 | ) 254 | -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_intention_wait_after_setplay_kick.py: -------------------------------------------------------------------------------- 1 | from src.interfaces.IAgent import IAgent 2 | from service_pb2 import * 3 | 4 | class BhvStarterIntentionWaitAfterSetPlayKick: 5 | 6 | def __init__(self): 7 | pass 8 | 9 | def finished(self, agent: IAgent) -> bool: 10 | wm = agent.wm 11 | 12 | if wm.kickable_opponent_existance: 13 | return True 14 | 15 | if not wm.self.is_kickable: 16 | return True 17 | 18 | return False 19 | 20 | def execute(self, agent: IAgent) -> bool: 21 | return [PlayerAction(bhv_body_neck_to_ball=Bhv_BodyNeckToBall())] 22 | -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_prepare_setplay_kick.py: -------------------------------------------------------------------------------- 1 | from src.interfaces.IAgent import IAgent 2 | from service_pb2 import * 3 | from pyrusgeom.angle_deg import AngleDeg 4 | class BhvStarterPrepareSetPlayKick: 5 | s_rest_wait_cycle = -1 6 | 7 | def __init__(self, ball_place_angle: float, wait_cycle: float): 8 | self.M_ball_place_angle = ball_place_angle 9 | self.M_wait_cycle = wait_cycle 10 | 11 | def execute(self, agent: IAgent) -> bool: 12 | 13 | from src.behaviors.starter.bhv_starter_go_to_placed_ball import BhvStarterGoToPlacedBall 14 | go_to_placed_ball = BhvStarterGoToPlacedBall(self.M_ball_place_angle) 15 | 16 | actions = [] 17 | # Not reach the ball side 18 | actions += go_to_placed_ball.execute(agent) 19 | 20 | # Reach to ball side 21 | if self.s_rest_wait_cycle < 0: 22 | self.s_rest_wait_cycle = self.M_wait_cycle 23 | 24 | if self.s_rest_wait_cycle == 0: 25 | if (agent.wm.self.stamina < agent.server_params.stamina_max * 0.9 or agent.wm.self.seetime != agent.wm.cycle): #TODO 26 | self.s_rest_wait_cycle = 1 27 | 28 | if self.s_rest_wait_cycle > 0: 29 | if agent.wm.game_mode_type == GameModeType.KickOff_: 30 | moment = AngleDeg(agent.server_params.visible_angle) 31 | actions.append(PlayerAction(turn=Turn(relative_direction=moment))) 32 | else: 33 | actions.append(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 34 | 35 | self.s_rest_wait_cycle -= 1 36 | 37 | return actions 38 | 39 | self.s_rest_wait_cycle = -1 40 | return [] -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_setplay_freekick.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | import math 3 | from service_pb2 import * 4 | from pyrusgeom.soccer_math import calc_length_geom_series 5 | from src.behaviors.starter.bhv_starter_pass import BhvStarterPass 6 | from src.utils.tools import Tools 7 | import math 8 | from src.behaviors.starter.bhv_starter_clearball import BhvStarterClearBall 9 | 10 | 11 | if TYPE_CHECKING: 12 | from src.sample_player_agent import SamplePlayerAgent 13 | 14 | class BhvStarterSetPlayFreeKick: 15 | def __init__(self): 16 | pass 17 | 18 | def execute(self, agent: "SamplePlayerAgent"): 19 | ''' 20 | Executes the free kick behavior for the agent if the free kick is on the agent's side. 21 | Args: 22 | agent (SamplePlayerAgent): The agent that will execute the behavior. 23 | Returns: 24 | bool: True if the action was added to the agent's action list,False otherwise. 25 | ''' 26 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 27 | selfplay = BhvStarterSetPlay() 28 | if selfplay.is_kicker(agent): 29 | #Kicker action 30 | return self.doKick(agent) 31 | else: 32 | #Non-kicker action 33 | return self.do_move(agent) 34 | 35 | def doKick(self, agent: "SamplePlayerAgent"): 36 | ''' 37 | Executes the kick behavior for the agent in a free kick situation. 38 | Args: 39 | agent (SamplePlayerAgent): The agent that will execute the 40 | behavior. 41 | Returns: 42 | bool: True if the action was added to the agent's action list,False otherwise. 43 | ''' 44 | from src.behaviors.starter.bhv_starter_go_to_placed_ball import BhvStarterGoToPlacedBall 45 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 46 | go_to_placed_ball = BhvStarterGoToPlacedBall(0.0) 47 | 48 | # go to the ball position 49 | go_to_placed_ball.execute(agent) 50 | 51 | # wait before kicking 52 | setplay = BhvStarterSetPlay() 53 | if setplay.do_kick_wait(agent): 54 | return True 55 | 56 | # kick 57 | wm = agent.wm 58 | 59 | # pass 60 | passer = BhvStarterPass() 61 | passer.execute(agent) 62 | 63 | # kick to the nearest teammate 64 | if self.kick_to_nearest_teammate(agent): 65 | return True 66 | 67 | # clear 68 | if abs(wm.ball.angle_from_self - wm.self.body_direction) > 1.5: 69 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 70 | return True 71 | clear_ball = BhvStarterClearBall() 72 | clear_ball.execute(agent) 73 | return True 74 | 75 | def kick_to_nearest_teammate(self, agent: "SamplePlayerAgent"): 76 | ''' 77 | Kicks the ball to the nearest teammate. 78 | Args: 79 | agent (SamplePlayerAgent): The agent that will execute the 80 | behavior. 81 | Returns: 82 | bool: True if the action was added to the agent's action list,False otherwise. 83 | ''' 84 | wm = agent.wm 85 | max_ball_speed = wm.self.kick_rate * agent.server_params.max_power 86 | nearest_teammate: Player = Tools.get_teammate_nearest_to_self(agent, False) 87 | if nearest_teammate and nearest_teammate.dist_from_self < 20.0 and (nearest_teammate.position.x > -30.0 or nearest_teammate.dist_from_self < 10.0): 88 | nearest_teammate_pos = Tools.convert_rpc_vector2d_to_vector2d(nearest_teammate.position) 89 | nearest_teammate_vel = Tools.convert_rpc_vector2d_to_vector2d(nearest_teammate.velocity) 90 | target_point = Tools.convert_rpc_vector2d_to_vector2d(nearest_teammate.inertia_final_point) 91 | target_point.set_x(target_point.x() + 0.5) 92 | ball_position = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 93 | 94 | ball_move_dist = ball_position.dist(target_point) 95 | ball_reach_step = math.ceil(calc_length_geom_series(max_ball_speed, ball_move_dist, agent.server_params.ball_decay)) 96 | ball_speed = 2.3 97 | '''if ball_reach_step > 3: 98 | ball_speed = calc_first_term_geom_series(ball_move_dist, agent.server_params.ball_decay, ball_reach_step) 99 | else: 100 | ball_speed = Tools.calc_first_term_geom_series_last(1.4, ball_move_dist, agent.server_params.ball_decay) 101 | ball_reach_step = math.ceil(calc_length_geom_series(ball_speed, ball_move_dist, agent.server_params.ball_decay))''' 102 | 103 | ball_speed = min(ball_speed, max_ball_speed) 104 | agent.add_action(PlayerAction(body_kick_one_step=Body_KickOneStep(target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), first_speed=ball_speed, force_mode=False))) 105 | return True 106 | return False 107 | 108 | def do_move(self, agent: "SamplePlayerAgent"): 109 | ''' 110 | Executes the move behavior for the agent in a free kick situation. 111 | Args: 112 | agent (SamplePlayerAgent): The agent that will execute the 113 | behavior. 114 | Returns: 115 | bool: True if the action was added to the agent's action list,False otherwise. 116 | ''' 117 | wm = agent.wm 118 | target_point_rpc = Tools.convert_vector2d_to_rpc_vector2d(agent.strategy.get_position(wm.self.uniform_number, agent)) 119 | target_point = Tools.convert_rpc_vector2d_to_vector2d(target_point_rpc) 120 | ball_positions = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 121 | self_positions = Tools.convert_rpc_vector2d_to_vector2d(wm.self.position) 122 | 123 | #Try to find the base position base on the nearest opponent's position 124 | if wm.set_play_count > 0 and wm.self.stamina > agent.server_params.stamina_max * 0.9: 125 | nearest_opps = Tools.get_opponents_from_self(agent) 126 | if len(nearest_opps) > 0: 127 | nearest_opp = nearest_opps[0] 128 | 129 | if nearest_opp and nearest_opp.dist_from_self < 3.0: 130 | add_vec = ball_positions - target_point 131 | add_vec.set_length(3.0) 132 | 133 | time_val = wm.cycle % 60 134 | if time_val < 20: 135 | pass 136 | elif time_val < 40: 137 | target_point += add_vec.rotated_vector(90.0) 138 | else: 139 | target_point += add_vec.rotated_vector(-90.0) 140 | 141 | target_point.set_x(min(max(-agent.server_params.pitch_half_length, target_point.x()), agent.server_params.pitch_half_length)) 142 | target_point.set_y(min(max(-agent.server_params.pitch_half_width, target_point.y()), agent.server_params.pitch_half_width)) 143 | 144 | target_point.set_x(min(target_point.x(), wm.offside_line_x - 0.5)) 145 | 146 | dash_power = 50 # you can choose to be selfplay.get_set_play_dash_power(agent) 147 | 148 | dist_thr = wm.ball.dist_from_self * 0.07 149 | if dist_thr < 1.0: 150 | dist_thr = 1.0 151 | 152 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), distance_threshold=dist_thr, max_dash_power=dash_power))) 153 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 154 | 155 | if self_positions.dist(target_point) > max(ball_positions.dist(target_point) * 0.2, dist_thr) + 6.0 or wm.self.stamina < agent.server_params.stamina_max * 0.7: 156 | if not wm.self.stamina_capacity == 0: #TODO stamina model 157 | #TODO actions.append(Say(wait_request_message=WaitRequestMessage())) 158 | pass 159 | 160 | return True -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_setplay_goal_kick.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from service_pb2 import * 3 | from pyrusgeom.vector_2d import Vector2D 4 | from src.behaviors.starter.bhv_starter_clearball import BhvStarterClearBall 5 | from src.utils.tools import Tools 6 | from src.behaviors.starter.bhv_starter_pass import BhvStarterPass 7 | from src.behaviors.starter.bhv_starter_clearball import BhvStarterClearBall 8 | from src.utils.tools import Tools 9 | 10 | 11 | if TYPE_CHECKING: 12 | from src.sample_player_agent import SamplePlayerAgent 13 | 14 | 15 | class BhvStarterSetPlayGoalKick: 16 | def __init__(self): 17 | pass 18 | 19 | def execute(self, agent: "SamplePlayerAgent"): 20 | """ 21 | Execute the set play move for the agent in goal kick game mode. 22 | Args: 23 | agent (SamplePlayerAgent): The agent that will execute the behavior. 24 | """ 25 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 26 | 27 | setplay = BhvStarterSetPlay() 28 | if setplay.is_kicker(agent): 29 | return self.do_kick(agent) 30 | else: 31 | return self.do_move(agent) 32 | 33 | def do_kick(self, agent: "SamplePlayerAgent"): 34 | ''' 35 | Execute the decision for the kicker agent in the goal kick set play. 36 | Args: 37 | agent (SamplePlayerAgent): The agent that will execute the behavior. 38 | Returns: 39 | bool: True if the action was added to the agent's action list, False otherwise. 40 | ''' 41 | from src.behaviors.starter.bhv_starter_go_to_placed_ball import ( 42 | BhvStarterGoToPlacedBall, 43 | ) 44 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 45 | 46 | go_to_placed_ball = BhvStarterGoToPlacedBall(0.0) 47 | 48 | self.do_second_kick(agent) 49 | 50 | # Go to the ball position and wait before kicking 51 | if go_to_placed_ball.execute(agent): 52 | return True 53 | 54 | set_play = BhvStarterSetPlay() 55 | if set_play.do_kick_wait(agent): 56 | return True 57 | 58 | # Pass the ball if its possible 59 | self.do_pass(agent) 60 | 61 | self.do_kick_to_far_side(agent) 62 | 63 | wm = agent.wm 64 | # Find the time left for the set play 65 | real_set_play_count = wm.cycle - agent.wm.last_set_play_start_time 66 | if real_set_play_count <= agent.server_params.drop_ball_time - 10: 67 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 68 | return True 69 | clear_ball = BhvStarterClearBall() 70 | clear_ball.execute(agent) 71 | return True 72 | 73 | def do_second_kick(self, agent: "SamplePlayerAgent"): 74 | ''' 75 | Perform the real kick action for the kicker agent in the goal kick set play. 76 | Args: 77 | agent (SamplePlayerAgent): The agent that will execute the behavior. 78 | Returns: 79 | bool: True if the action was added to the agent's action list, False otherwise. 80 | ''' 81 | wm = agent.wm 82 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 83 | ball_velocity = Vector2D(wm.ball.velocity.x, wm.ball.velocity.y) 84 | 85 | # Check if the ball is inside the goal area; if so, return False 86 | if ( 87 | wm.ball.position.x 88 | < -agent.server_params.pitch_half_length 89 | + agent.server_params.goal_area_length 90 | + 1.0 91 | and abs(wm.ball.position.y) < agent.server_params.goal_width * 0.5 + 1.0 92 | ): 93 | return False 94 | 95 | # If the ball is kickable by the agent, perform a pass and clear ball action 96 | if wm.self.is_kickable: 97 | self.do_pass(agent) 98 | clear_ball = BhvStarterClearBall() 99 | clear_ball.execute(agent) 100 | 101 | self.do_intercept(agent) 102 | 103 | # Calculate the final position of the ball using inertia 104 | ball_final = Tools.calculate_ball_inertia_final_point( 105 | ball_position, ball_velocity, agent.server_params.ball_decay 106 | ) 107 | 108 | agent.add_action( 109 | PlayerAction( 110 | body_go_to_point=Body_GoToPoint( 111 | target_point=Tools.convert_vector2d_to_rpc_vector2d(ball_final), 112 | distance_threshold=2.0, 113 | max_dash_power=agent.server_params.max_dash_power, 114 | ) 115 | ) 116 | ) 117 | 118 | agent.add_action( 119 | PlayerAction( 120 | body_turn_to_point=Body_TurnToPoint( 121 | target_point=RpcVector2D(x=0, y=0), cycle=2 122 | ) 123 | ) 124 | ) 125 | 126 | return True 127 | 128 | def do_pass(self, agent: "SamplePlayerAgent"): 129 | ''' 130 | Perform a pass action for the kicker agent in the goal kick set play. 131 | Args: 132 | agent (SamplePlayerAgent): The agent that will execute 133 | the behavior. 134 | ''' 135 | passer = BhvStarterPass() 136 | passer.execute(agent) 137 | return True 138 | 139 | def do_intercept(self, agent: "SamplePlayerAgent"): 140 | ''' 141 | Perform the intercept action for the kicker agent in the goal kick set play. 142 | Args: 143 | agent (SamplePlayerAgent): The agent that will execute the behavior. 144 | Returns: 145 | bool: True if the action was added to the agent's action list, False otherwise. 146 | ''' 147 | wm = agent.wm 148 | 149 | if ( 150 | wm.ball.position.x 151 | < -agent.server_params.pitch_half_length 152 | + agent.server_params.goal_area_length 153 | + 1.0 154 | and abs(wm.ball.position.y) 155 | < agent.server_params.goal_area_width * 0.5 + 1.0 156 | ): 157 | return False 158 | 159 | if wm.self.is_kickable: 160 | return False 161 | 162 | self_min = wm.intercept_table.self_reach_steps 163 | mate_min = wm.intercept_table.first_teammate_reach_steps 164 | if self_min > mate_min: 165 | return False 166 | 167 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 168 | ball_velocity = Vector2D(wm.ball.velocity.x, wm.ball.velocity.y) 169 | trap_pos = Tools.inertia_point( 170 | ball_position, ball_velocity, self_min, agent.server_params.ball_decay 171 | ) 172 | if ( 173 | trap_pos.x() > agent.server_params.our_penalty_area_line_x - 8.0 174 | and abs(trap_pos.y()) > agent.server_params.penalty_area_half_width - 5.0 175 | ) or ball_velocity.r2() < 0.25: 176 | agent.add_action(PlayerAction(body_intercept=Body_Intercept())) 177 | return True 178 | 179 | return False 180 | 181 | def do_move(self, agent: "SamplePlayerAgent"): 182 | ''' 183 | Execute the move action for the non-kicker agent in the kick-in set play. 184 | Args: 185 | agent (SamplePlayerAgent): The agent that will execute the behavior. 186 | Returns: 187 | bool: True if the action was added to the agent's action list, False otherwise. 188 | ''' 189 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 190 | 191 | setplay = BhvStarterSetPlay() 192 | self.do_intercept(agent) 193 | 194 | wm = agent.wm 195 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 196 | dash_power = setplay.get_set_play_dash_power(agent) 197 | dist_thr = max(wm.ball.dist_from_self * 0.07, 1.0) 198 | 199 | # Get target position based on strategy and adjust the y-coordinate 200 | target_rpc = Tools.convert_vector2d_to_rpc_vector2d( 201 | agent.strategy.get_position(wm.self.uniform_number, agent) 202 | ) 203 | target_point = Vector2D(target_rpc.x, target_rpc.y) 204 | target_point.set_y(target_point.y() + wm.ball.position.y * 0.5) 205 | 206 | # Ensure the target point is within pitch boundaries 207 | if abs(target_point.y()) > agent.server_params.pitch_half_width - 1.0: 208 | target_point.set_y( 209 | (target_point.y() / abs(target_point.y())) 210 | * (agent.server_params.pitch_half_width - 1.0) 211 | ) 212 | 213 | # Check if the agent has sufficient stamina 214 | if wm.self.stamina > agent.server_params.stamina_max * 0.9: 215 | nearest_opp = Tools.get_opponent_nearest_to_self(agent) 216 | if nearest_opp and nearest_opp.dist_from_self < 3.0: 217 | add_vec = ball_position - target_point 218 | add_vec.set_length(3.0) 219 | 220 | time_val = wm.cycle % 60 221 | if time_val < 20: 222 | pass 223 | elif time_val < 40: 224 | target_point += add_vec.rotated_vector(90.0) 225 | else: 226 | target_point += add_vec.rotated_vector(-90.0) 227 | 228 | # Ensure target point is within pitch boundaries 229 | target_point.set_x( 230 | min( 231 | max(-agent.server_params.pitch_half_length, target_point.x()), 232 | agent.server_params.pitch_half_length, 233 | ) 234 | ) 235 | target_point.set_y( 236 | min( 237 | max(-agent.server_params.pitch_half_width, target_point.y()), 238 | agent.server_params.pitch_half_width, 239 | ) 240 | ) 241 | 242 | agent.add_action( 243 | PlayerAction( 244 | body_go_to_point=Body_GoToPoint( 245 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 246 | distance_threshold=dist_thr, 247 | max_dash_power=dash_power, 248 | ) 249 | ) 250 | ) 251 | 252 | # Add action to turn towards the ball 253 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 254 | 255 | self_position = Vector2D(wm.self.position.x, wm.self.position.y) # Get agent's current position 256 | if ( 257 | self_position.dist(target_point) 258 | > ball_position.dist(target_point) * 0.2 + 6.0 259 | or wm.self.stamina < agent.server_params.stamina_max * 0.7 260 | ): 261 | if not wm.self.stamina_capacity == 0: # TODO: Add wait request action 262 | pass 263 | 264 | return True 265 | 266 | def do_kick_to_far_side(self, agent: "SamplePlayerAgent"): 267 | ''' 268 | Execute the kick action to the far side of the field. 269 | Args: 270 | agent (SamplePlayerAgent): The agent that will execute the behavior. 271 | Returns: 272 | bool: True if the action was added to the agent's action list, False otherwise. 273 | ''' 274 | wm = agent.wm 275 | 276 | # Define the target point near the opponent's penalty area 277 | target_point = Vector2D( 278 | agent.server_params.our_penalty_area_line_x - 5.0, 279 | agent.server_params.penalty_area_half_width, 280 | ) 281 | 282 | # Adjust the target point if the ball is above the center line 283 | if wm.ball.position.y > 0.0: 284 | target_point.set_y(target_point.y() * -1.0) 285 | 286 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 287 | ball_move_dist = ball_position.dist(target_point) 288 | 289 | ball_first_speed = Tools.calc_first_term_geom_series_last( 290 | 0.7, ball_move_dist, agent.server_params.ball_decay 291 | ) 292 | ball_first_speed = min(agent.server_params.ball_speed_max, ball_first_speed) 293 | ball_first_speed = min( 294 | wm.self.kick_rate * agent.server_params.max_power, ball_first_speed 295 | ) 296 | 297 | accel = target_point - ball_position 298 | accel.set_length(ball_first_speed) 299 | 300 | # Calculate the kick power based on the acceleration and the agent's kick rate 301 | kick_power = min(agent.server_params.max_power, accel.r() / wm.self.kick_rate) 302 | kick_angle = accel.th() 303 | 304 | agent.add_action( 305 | PlayerAction( 306 | kick=Kick( 307 | power=kick_power, 308 | relative_direction=kick_angle.degree() - wm.self.body_direction, 309 | ) 310 | ) 311 | ) 312 | return True 313 | 314 | -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_setplay_kickin.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | import math 3 | from src.interfaces.IAgent import IAgent 4 | from service_pb2 import * 5 | from pyrusgeom.vector_2d import Vector2D 6 | 7 | # from src.setplay.BhvGoToPlacedBall import BhvGoToPlacedBall 8 | from src.behaviors.starter.bhv_starter_pass import BhvStarterPass 9 | from src.utils.tools import Tools 10 | import math 11 | from pyrusgeom.soccer_math import * 12 | 13 | # from src.setplay.BhvSetPlay import BhvSetPlay 14 | from src.strategy.starter_strategy import StarterStrategy 15 | from src.utils.tools import Tools 16 | 17 | 18 | if TYPE_CHECKING: 19 | from src.sample_player_agent import SamplePlayerAgent 20 | 21 | 22 | class BhvStarterSetPlayKickIn: 23 | 24 | def __init__(self): 25 | pass 26 | 27 | def execute(self, agent: "SamplePlayerAgent"): 28 | """ 29 | Execute the agent behavior for the kickin set play. 30 | Args: 31 | agent (SamplePlayerAgent): The agent that will execute the behavior. 32 | Returns: 33 | bool: True if the action was added to the agent's action list, False otherwise. 34 | """ 35 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 36 | 37 | setplay = BhvStarterSetPlay() 38 | 39 | if setplay.is_kicker(agent): 40 | return self.do_kick(agent) 41 | else: 42 | return self.do_move(agent) 43 | 44 | def do_kick(self, agent: "SamplePlayerAgent"): 45 | """ 46 | Execute the kick action for the kicker agent in the kickin set play. 47 | Args: 48 | agent (SamplePlayerAgent): The agent that will execute the behavior. 49 | Returns: 50 | bool: True if the action was added to the agent's action list, False otherwise. 51 | """ 52 | from src.behaviors.starter.bhv_starter_go_to_placed_ball import ( 53 | BhvStarterGoToPlacedBall, 54 | ) 55 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 56 | 57 | wm = agent.wm 58 | # Go to the kick position 59 | ball_place_angle = -90.0 if wm.ball.position.y > 0.0 else 90.0 60 | go_to_placed_ball = BhvStarterGoToPlacedBall(ball_place_angle) 61 | go_to_placed_ball.execute(agent) 62 | 63 | # Wait before kicking 64 | setplay = BhvStarterSetPlay() 65 | if setplay.do_kick_wait(agent): 66 | agent.logger.debug("BhvStarterSetPlayKickOff.do_kick: wait") 67 | return True 68 | 69 | # Kick 70 | # Pass 71 | passer = BhvStarterPass() 72 | passer.execute(agent) 73 | 74 | # Kick to the nearest teammate 75 | if self.kick_to_nearest_teammate(agent): 76 | return True 77 | 78 | # Clear 79 | # Turn to ball 80 | if abs(wm.ball.angle_from_self - wm.self.body_direction) > 1.5: 81 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 82 | return True 83 | 84 | # Advance ball 85 | if wm.self.position.x < 20.0: 86 | agent.add_action(PlayerAction(body_advance_ball=Body_AdvanceBall())) 87 | return True 88 | 89 | # Kick to the opponent side corner 90 | if self.kick_to_opponent_side_corner(agent): 91 | return True 92 | 93 | return True 94 | 95 | def kick_to_nearest_teammate(self, agent: "SamplePlayerAgent"): 96 | """ 97 | Perform the kick action to the nearest teammate. 98 | Args: 99 | agent (SamplePlayerAgent): The agent that will execute the behavior. 100 | Returns: 101 | bool: True if the action was added to the agent's action list, False otherwise. 102 | """ 103 | wm = agent.wm 104 | max_ball_speed = wm.self.kick_rate * agent.server_params.max_power 105 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 106 | receiver: Player = Tools.get_teammate_nearest_to(agent, ball_position) 107 | if ( 108 | receiver 109 | and receiver.dist_from_ball < 10.0 110 | and abs(receiver.position.x) < agent.server_params.pitch_half_length 111 | and abs(receiver.position.y) < agent.server_params.pitch_half_width 112 | ): 113 | 114 | target_point = Vector2D( 115 | receiver.inertia_final_point.x, receiver.inertia_final_point.y 116 | ) 117 | target_point.set_x(target_point.x() + 0.5) 118 | ball_move_dist = ball_position.dist(target_point) 119 | ball_reach_step = math.ceil( 120 | calc_length_geom_series( 121 | max_ball_speed, ball_move_dist, agent.server_params.ball_decay 122 | ) 123 | ) 124 | ball_speed = 0.0 125 | 126 | if ball_reach_step > 3: 127 | ball_speed = calc_first_term_geom_series( 128 | ball_move_dist, agent.server_params.ball_decay, ball_reach_step 129 | ) 130 | else: 131 | ball_speed = Tools.calc_first_term_geom_series_last( 132 | 1.4, ball_move_dist, agent.server_params.ball_decay 133 | ) 134 | ball_reach_step = math.ceil( 135 | calc_length_geom_series( 136 | ball_speed, ball_move_dist, agent.server_params.ball_decay 137 | ) 138 | ) 139 | 140 | ball_speed = min(ball_speed, max_ball_speed) 141 | agent.add_action( 142 | PlayerAction( 143 | body_kick_one_step=Body_KickOneStep( 144 | target_point=Tools.convert_vector2d_to_rpc_vector2d( 145 | target_point 146 | ), 147 | first_speed=ball_speed, 148 | force_mode=False, 149 | ) 150 | ) 151 | ) 152 | return True 153 | return False 154 | 155 | def kick_to_opponent_side_corner(self, agent: "SamplePlayerAgent"): 156 | """ 157 | Perform the kick action to the opponent side corner. 158 | Args: 159 | agent (SamplePlayerAgent): The agent that will execute the behavior. 160 | Returns: 161 | bool: True if the action was added to the agent's action list, False otherwise. 162 | """ 163 | wm = agent.wm 164 | target_point = Vector2D( 165 | agent.server_params.pitch_half_length - 2.0, 166 | (agent.server_params.pitch_half_width - 5.0) 167 | * (1.0 - (wm.self.position.x / agent.server_params.pitch_half_length)), 168 | ) 169 | 170 | if wm.self.position.y < 0.0: 171 | target_point.set_y(target_point.y() * -1.0) 172 | 173 | # Enforce one step kick 174 | agent.add_action( 175 | PlayerAction( 176 | body_kick_one_step=Body_KickOneStep( 177 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 178 | first_speed=agent.server_params.ball_speed_max, 179 | force_mode=False, 180 | ) 181 | ) 182 | ) 183 | return True 184 | 185 | def do_move(self, agent: "SamplePlayerAgent"): 186 | """ 187 | Execute the move action for the non-kicker agent in the kickin set play. 188 | Args: 189 | agent (SamplePlayerAgent): The agent that will execute the behavior. 190 | Returns: 191 | bool: True if the action was added to the agent's action list, False otherwise. 192 | """ 193 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 194 | 195 | setplay = BhvStarterSetPlay() 196 | wm = agent.wm 197 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 198 | target_point = agent.strategy.get_position(wm.self.uniform_number, agent) 199 | avoid_opponent = False 200 | 201 | # Check if the agent has sufficient stamina and the nearest opponent is close to the target point then move away from the opponent 202 | if wm.self.stamina > agent.server_params.stamina_max * 0.9: 203 | nearest_opp = Tools.get_opponent_nearest_to_self(agent) 204 | 205 | if nearest_opp: 206 | nearest_opp_pos = Vector2D(nearest_opp.position.x, nearest_opp.position.y) 207 | if nearest_opp_pos.dist(target_point) < 3.0: 208 | add_vec = ball_position - target_point 209 | add_vec.set_length(3.0) 210 | 211 | time_val = wm.cycle % 60 212 | if time_val < 20: 213 | pass 214 | elif time_val < 40: 215 | target_point += add_vec.rotated_vector(90.0) 216 | else: 217 | target_point += add_vec.rotated_vector(-90.0) 218 | 219 | target_point.set_x( 220 | min( 221 | max(-agent.server_params.pitch_half_length, target_point.x()), 222 | agent.server_params.pitch_half_length, 223 | ) 224 | ) 225 | target_point.set_y( 226 | min( 227 | max(-agent.server_params.pitch_half_width, target_point.y()), 228 | agent.server_params.pitch_half_width, 229 | ) 230 | ) 231 | avoid_opponent = True 232 | 233 | dash_power = setplay.get_set_play_dash_power(agent) 234 | dist_thr = wm.ball.dist_from_self * 0.07 235 | dist_thr = max(dist_thr, 1.0) 236 | tm = Tools.get_teammates_from_ball(agent) 237 | if tm: 238 | kicker_ball_dist = tm[0].dist_from_ball 239 | else: 240 | 1000 241 | 242 | agent.add_action( 243 | PlayerAction( 244 | body_go_to_point=Body_GoToPoint( 245 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 246 | distance_threshold=dist_thr, 247 | max_dash_power=dash_power, 248 | ) 249 | ) 250 | ) 251 | 252 | # Already there 253 | if kicker_ball_dist > 1.0: 254 | agent.add_action(PlayerAction(turn=Turn(relative_direction=120))) 255 | else: 256 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 257 | self_position = Vector2D(wm.self.position.x, wm.self.position.y) 258 | self_velocity = Vector2D(wm.self.velocity.x, wm.self.velocity.y) 259 | my_inertia = Tools.inertia_final_point( 260 | agent.player_types[wm.self.id], self_position, self_velocity 261 | ) 262 | wait_dist_buf = ( 263 | 10.0 if avoid_opponent else ball_position.dist(target_point) * 0.2 + 6.0 264 | ) 265 | 266 | # Check if agent needs to wait before taking action 267 | if ( 268 | my_inertia.dist(target_point) > wait_dist_buf 269 | or wm.self.stamina < agent.server_params.stamina_max * 0.7 270 | ): 271 | if not wm.self.stamina_capacity == 0: 272 | # TODO actions.append(Say(wait_request_message=WaitRequestMessage())) 273 | pass 274 | 275 | """'if kicker_ball_dist > 3.0: 276 | agent.setViewAction(ViewWide()) 277 | agent.setNeckAction(NeckScanField()) 278 | elif wm.ball().distFromSelf() > 10.0 or kicker_ball_dist > 1.0: 279 | agent.setNeckAction(NeckTurnToBallOrScan(0)) 280 | else: 281 | agent.setNeckAction(NeckTurnToBall())""" # TODO 282 | 283 | return True 284 | -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_setplay_kickoff.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from service_pb2 import * 3 | from pyrusgeom.vector_2d import Vector2D 4 | from src.utils.tools import Tools 5 | 6 | 7 | if TYPE_CHECKING: 8 | from src.sample_player_agent import SamplePlayerAgent 9 | 10 | class BhvStarterSetPlayKickOff: 11 | def __init__(self): 12 | pass 13 | 14 | def execute(self, agent: "SamplePlayerAgent"): 15 | """ 16 | Execute the kickoff behavior. 17 | Args: 18 | agent (SamplePlayerAgent): The agent that will execute the behavior. 19 | Returns: 20 | bool: True if the action was added to the agent's action list, False otherwise. 21 | """ 22 | agent.logger.debug("BhvStarterSetPlayKickOff.execute") 23 | wm = agent.wm 24 | teammates = Tools.get_teammates_from_ball(agent) 25 | 26 | # Decide whether to kick or move based on teammates' positions 27 | if not teammates or teammates[0].dist_from_self > wm.self.dist_from_ball: 28 | return self.do_kick(agent) 29 | else: 30 | return self.do_move(agent) 31 | 32 | def do_kick(self, agent: "SamplePlayerAgent"): 33 | """ 34 | Perform the kick action. 35 | Args: 36 | agent (SamplePlayerAgent): The agent that will execute the 37 | behavior. 38 | Returns: 39 | bool: True if the action was added to the agent's action list, False otherwise. 40 | """ 41 | from src.behaviors.starter.bhv_starter_go_to_placed_ball import ( 42 | BhvStarterGoToPlacedBall, 43 | ) 44 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 45 | agent.logger.debug("BhvStarterSetPlayKickOff.do_kick") 46 | 47 | go_to_placed_ball = BhvStarterGoToPlacedBall(0.0) 48 | 49 | # Go to the ball position 50 | if go_to_placed_ball.execute(agent): 51 | agent.logger.debug("BhvStarterSetPlayKickOff.do_kick: go_to_placed_ball") 52 | 53 | # Wait before kicking 54 | setplay = BhvStarterSetPlay() 55 | if setplay.do_kick_wait(agent): 56 | agent.logger.debug("BhvStarterSetPlayKickOff.do_kick: wait") 57 | return True 58 | 59 | # Perform the kick 60 | from src.behaviors.starter.bhv_starter_pass import BhvStarterPass 61 | bhv_starter_pass = BhvStarterPass() 62 | if bhv_starter_pass.execute(agent): 63 | agent.logger.debug("BhvStarterSetPlayKickOff.do_kick: pass") 64 | return True 65 | 66 | wm = agent.wm 67 | 68 | # Define target point and ball speed for the kick 69 | target_point = Vector2D(-20.0, 0.0) 70 | ball_speed = agent.server_params.max_power * wm.self.kick_rate 71 | 72 | # Enforce one step kick 73 | agent.add_action( 74 | PlayerAction( 75 | body_smart_kick=Body_SmartKick( 76 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 77 | first_speed=ball_speed, 78 | first_speed_threshold=ball_speed * 0.96, 79 | max_steps=1, 80 | ) 81 | ) 82 | ) 83 | agent.logger.debug(f"BhvStarterSetPlayKickOff.do_kick: kick to {target_point}") 84 | 85 | return True 86 | 87 | def do_move(self, agent: "SamplePlayerAgent"): 88 | """ 89 | Perform the move action in the kickoff gamemode. 90 | Args: 91 | agent (SamplePlayerAgent): The agent that will execute the 92 | behavior. 93 | Returns: 94 | bool: True if the action was added to the agent's action list, False otherwise 95 | """ 96 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 97 | 98 | setplay = BhvStarterSetPlay() 99 | wm = agent.wm 100 | 101 | # Get target position and dash power 102 | target_point = agent.strategy.get_position(wm.self.uniform_number, agent) 103 | target_point.set_x(min(-0.5, target_point.x())) 104 | dash_power = setplay.get_set_play_dash_power(agent) 105 | dist_thr = wm.ball.dist_from_self * 0.07 106 | if dist_thr < 1.0: 107 | dist_thr = 1.0 108 | 109 | # Move to the target point 110 | agent.add_action( 111 | PlayerAction( 112 | body_go_to_point=Body_GoToPoint( 113 | target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 114 | distance_threshold=dist_thr, 115 | max_dash_power=dash_power, 116 | ) 117 | ) 118 | ) 119 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=1))) 120 | 121 | return True 122 | -------------------------------------------------------------------------------- /src/behaviors/starter/setplay/bhv_starter_their_goal_kick_move.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IAgent import IAgent 3 | #from src.setplay.BhvSetPlay import BhvSetPlay 4 | from service_pb2 import * 5 | from src.strategy.starter_strategy import StarterStrategy 6 | from pyrusgeom.vector_2d import Vector2D 7 | from src.utils.tools import Tools 8 | from pyrusgeom.soccer_math import inertia_n_step_point 9 | from pyrusgeom.ray_2d import Ray2D 10 | from pyrusgeom.size_2d import Size2D 11 | from pyrusgeom.rect_2d import Rect2D 12 | from pyrusgeom.angle_deg import AngleDeg 13 | from src.utils.tools import Tools 14 | 15 | if TYPE_CHECKING: 16 | from src.sample_player_agent import SamplePlayerAgent 17 | class BhvStarterTheirGoalKickMove: 18 | def __init__(self): 19 | pass 20 | 21 | def execute(self, agent: "SamplePlayerAgent"): 22 | ''' 23 | Execute the set play move for the agent in goal kick game mode. 24 | Args: 25 | agent (SamplePlayerAgent): The agent that will execute the behavior. 26 | ''' 27 | # Define the expanded penalty area 28 | expand_their_penalty = Rect2D( 29 | Vector2D(agent.server_params.their_penalty_area_line_x - 0.75, 30 | -agent.server_params.penalty_area_half_width - 0.75), 31 | Size2D(agent.server_params.penalty_area_length + 0.75, 32 | (agent.server_params.penalty_area_half_width * 2) + 1.5) 33 | ) 34 | 35 | wm = agent.wm 36 | 37 | # agent to chase the ball 38 | self.do_chase_ball(agent) 39 | 40 | self_position = Vector2D(wm.self.position.x, wm.self.position.y) 41 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 42 | ball_velocity = Vector2D(wm.ball.velocity.x, wm.ball.velocity.y) 43 | 44 | # Determine intersection points of the ball's trajectory with the expanded penalty area 45 | intersection_list = expand_their_penalty.intersection(Ray2D(ball_position, ball_velocity.th())) 46 | intersection = intersection_list[0] 47 | 48 | # Check if the ball's velocity is significant do normal movement 49 | if ball_velocity.r() > 0.2: 50 | if not expand_their_penalty.contains(ball_position) or len(intersection_list) != 1: # TODO Check 51 | return self.do_normal(agent) 52 | else: 53 | if (wm.ball.position.x > agent.server_params.their_penalty_area_line_x + 7.0 and 54 | abs(wm.ball.position.y) < (agent.server_params.goal_width / 2.0) + 2.0): 55 | return self.do_normal(agent) 56 | 57 | # Adjust intersection point if the ball's velocity is not significant 58 | intersection.set_x(agent.server_params.their_penalty_area_line_x - 0.76) 59 | intersection.set_y(wm.ball.position.y) 60 | 61 | min_dist = 100.0 62 | nearest_tm = Tools.get_teammate_nearest_to(agent, intersection) 63 | nearest_tm_pos = Vector2D(nearest_tm.position.x, nearest_tm.position.y) 64 | min_dist = nearest_tm_pos.dist(intersection) 65 | 66 | # If the nearest teammate is closer to the intersection, perform a normal action 67 | if min_dist < self_position.dist(intersection): 68 | return self.do_normal(agent) 69 | 70 | dash_power = wm.self.get_safety_dash_power # TODO: Determine safe dash power 71 | 72 | # Adjust intersection point based on agent's position and penalty area constraints 73 | if intersection.x() < agent.server_params.their_penalty_area_line_x and wm.self.position.x > agent.server_params.their_penalty_area_line_x - 0.5: 74 | intersection.set_y(agent.server_params.penalty_area_half_width - 0.5) 75 | if wm.self.position.y < 0.0: 76 | intersection.set_y(intersection.y() * -1.0) 77 | elif intersection.y() > agent.server_params.penalty_area_half_width and abs(wm.self.position.y) < agent.server_params.penalty_area_half_width + 0.5: 78 | intersection.set_y(agent.server_params.penalty_area_half_width + 0.5) 79 | if wm.self.position.y < 0.0: 80 | intersection.set_y(intersection.y() * -1.0) 81 | 82 | dist_thr = max(wm.ball.dist_from_self * 0.07, 1.0) # Calculate distance threshold 83 | 84 | # Add actions to the agent: go to the intersection point and turn to the ball 85 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(intersection), distance_threshold=dist_thr, max_dash_power=dash_power))) 86 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=0))) 87 | 88 | return True 89 | 90 | 91 | def do_normal(self, agent: "SamplePlayerAgent"): 92 | ''' 93 | Perform normal movement for the agen in goal kick game mode. 94 | Args: 95 | agent (SamplePlayerAgent): The agent that will execute the behavior. 96 | Returns: 97 | bool: True if the action was added to the agent's action list,False otherwise. 98 | ''' 99 | wm = agent.wm 100 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 101 | setplay = BhvStarterSetPlay() 102 | dash_power = setplay.get_set_play_dash_power(agent) 103 | targ = Tools.convert_vector2d_to_rpc_vector2d(agent.strategy.get_position(wm.self.uniform_number, agent)) 104 | target_point = Vector2D(targ.x, targ.y) 105 | 106 | # Attract to ball 107 | if target_point.x() > 25.0 and (target_point.y() * wm.ball.position.y < 0.0 or target_point.abs_y() < 10.0): 108 | y_diff = wm.ball.position.y - target_point.y() 109 | target_point.set_y(target_point.y() + (y_diff * 0.4)) 110 | 111 | # Check penalty area 112 | if wm.self.position.x > agent.server_params.their_penalty_area_line_x and target_point.abs_y() < agent.server_params.penalty_area_half_width: 113 | target_point.set_y(agent.server_params.penalty_area_half_width + 0.5) 114 | if wm.self.position.y < 0.0: 115 | target_point.set_y(target_point.y() * -1) 116 | 117 | dist_thr = max(wm.ball.dist_from_self * 0.07, 1.0) 118 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), distance_threshold=dist_thr, max_dash_power=dash_power))) 119 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall(cycle=0))) 120 | 121 | return True 122 | 123 | def do_chase_ball(self, agent: "SamplePlayerAgent"): 124 | ''' 125 | Determine if the agent should chase the ball. 126 | Args: 127 | agent (SamplePlayerAgent): The agent that will execute the behavior. 128 | Returns: 129 | bool: True if the agent should chase the ball, False otherwise. 130 | ''' 131 | wm = agent.wm 132 | 133 | ball_position = Vector2D(wm.ball.position.x, wm.ball.position.y) 134 | ball_velocity = Vector2D(wm.ball.velocity.x, wm.ball.velocity.y) 135 | 136 | if ball_velocity.r() < 0.2: 137 | return False 138 | 139 | self_min = wm.intercept_table.self_reach_steps 140 | 141 | # Check if the minimum steps required to reach the ball is too high 142 | if self_min > 10: 143 | return False 144 | 145 | # Calculate the predicted position of the ball using its inertia 146 | get_pos = Tools.inertia_point(ball_position, ball_velocity, self_min, agent.server_params.ball_decay) 147 | 148 | # Define the expanded penalty area 149 | pen_x = agent.server_params.their_penalty_area_line_x - 1.0 150 | pen_y = agent.server_params.penalty_area_half_width + 1.0 151 | their_penalty = Rect2D( 152 | Vector2D(pen_x, -pen_y), 153 | Size2D(agent.server_params.penalty_area_length + 1.0, 154 | (agent.server_params.penalty_area_half_width * 2) - 2.0) 155 | ) 156 | 157 | # Check if the predicted ball position is within the penalty area 158 | if their_penalty.contains(get_pos): 159 | return False 160 | 161 | # Check if the agent's position relative to the penalty area allows chasing the ball 162 | if (get_pos.x() > pen_x and wm.self.position.x < pen_x and abs(wm.self.position.y) < pen_y - 0.5): 163 | return False 164 | 165 | # The agent can chase the ball 166 | agent.add_action(PlayerAction(body_intercept=Body_Intercept())) 167 | return True 168 | 169 | -------------------------------------------------------------------------------- /src/decision_makers/decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from .play_on_decision_maker import PlayOnDecisionMaker 4 | from .set_play_decision_maker import SetPlayDecisionMaker 5 | from .penalty_decision_maker import PenaltyDecisionMaker 6 | from .goalie_decision_maker import GoalieDecisionMaker 7 | from service_pb2 import * 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class DecisionMaker(IDecisionMaker): 13 | """ 14 | DecisionMaker is responsible for making decisions for an agent based on the current game state. 15 | Attributes: 16 | play_on_decision_maker (PlayOnDecisionMaker): Handles decisions during the 'PlayOn' game mode. 17 | set_play_decision_maker (SetPlayDecisionMaker): Handles decisions during set plays. 18 | penalty_decision_maker (PenaltyDecisionMaker): Handles decisions during penalty kicks. 19 | goalie_decision_maker (GoalieDecisionMaker): Handles decisions for goalies. 20 | Methods: 21 | __init__(): 22 | Initializes the DecisionMaker with specific decision makers for different game modes. 23 | make_decision(agent: IAgent): 24 | Makes a decision for the given agent based on its role and the current game mode. 25 | If the agent is a goalie, it adds a goalie action using goalie_decision_maker. 26 | If the game mode is 'PlayOn', it delegates the decision to play_on_decision_maker. 27 | If the game mode is a penalty kick, it adds a penalty action using penalty_decision_maker. 28 | Otherwise, it adds a set play action using set_play_decision_maker. 29 | """ 30 | def __init__(self, agent: "SamplePlayerAgent"): 31 | self.play_on_decision_maker = PlayOnDecisionMaker(agent) 32 | self.set_play_decision_maker = SetPlayDecisionMaker(agent) 33 | self.penalty_decision_maker = PenaltyDecisionMaker(agent) 34 | self.goalie_decision_maker = GoalieDecisionMaker(agent) 35 | 36 | def make_decision(self, agent: "SamplePlayerAgent"): 37 | if agent.wm.self.is_goalie: 38 | self.goalie_decision_maker.make_decision(agent) 39 | elif agent.wm.game_mode_type == GameModeType.PlayOn: 40 | self.play_on_decision_maker.make_decision(agent) 41 | elif agent.wm.is_penalty_kick_mode: 42 | self.penalty_decision_maker.make_decision(agent) 43 | else: 44 | self.set_play_decision_maker.make_decision(agent) -------------------------------------------------------------------------------- /src/decision_makers/goalie_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from service_pb2 import * 6 | 7 | 8 | if TYPE_CHECKING: 9 | from src.sample_player_agent import SamplePlayerAgent 10 | 11 | class GoalieDecisionMaker(IDecisionMaker): 12 | def __init__(self, agent: "SamplePlayerAgent"): 13 | pass 14 | 15 | def make_decision(self, agent: "SamplePlayerAgent"): 16 | agent.logger.debug("--- GoalieDecisionMaker ---") 17 | agent.add_action(PlayerAction(helios_goalie=HeliosGoalie())) -------------------------------------------------------------------------------- /src/decision_makers/kick_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from service_pb2 import * 6 | from src.behaviors.bhv_kick_planner import BhvKickPlanner 7 | from src.behaviors.starter.bhv_starter_kick_planner import BhvStarterKickPlanner 8 | 9 | if TYPE_CHECKING: 10 | from src.sample_player_agent import SamplePlayerAgent 11 | 12 | class KickDecisionMaker(IDecisionMaker): 13 | def __init__(self, agent: "SamplePlayerAgent"): 14 | self.bhv_kick_planner = BhvKickPlanner() if not agent.use_starter_code else BhvStarterKickPlanner() 15 | 16 | def make_decision(self, agent: "SamplePlayerAgent"): 17 | agent.logger.debug("--- WithBallDecisionMaker ---") 18 | self.bhv_kick_planner.execute(agent) -------------------------------------------------------------------------------- /src/decision_makers/move_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from pyrusgeom.soccer_math import * 4 | from pyrusgeom.geom_2d import * 5 | from service_pb2 import * 6 | from src.utils.tools import Tools 7 | from src.behaviors.bhv_block import Bhv_Block 8 | from src.behaviors.bhv_tackle import BhvTackle 9 | from src.behaviors.starter.bhv_starter_tackle import BhvStarterTackle 10 | 11 | 12 | if TYPE_CHECKING: 13 | from src.sample_player_agent import SamplePlayerAgent 14 | 15 | class MoveDecisionMaker(IDecisionMaker): 16 | """ 17 | A decision maker class for an agent when it does not have the ball. 18 | Methods 19 | ------- 20 | __init__(): 21 | Initializes the NoBallDecisionMaker instance. 22 | make_decision(agent): 23 | Makes a decision for the agent based on the current world model state. 24 | The decision includes actions like intercepting the ball, blocking opponents, 25 | moving to a strategic position, and turning towards the ball. 26 | Parameters: 27 | agent (SamplePlayerAgent): The agent for which the decision is being made. 28 | """ 29 | def __init__(self, agent: "SamplePlayerAgent"): 30 | self.bhv_tackle = BhvTackle() if not agent.use_starter_code else BhvStarterTackle(0.8, 80) 31 | self.bhv_block = Bhv_Block() 32 | 33 | def make_decision(self, agent: "SamplePlayerAgent"): 34 | """ 35 | Make a decision for the agent when it does not have the ball. 36 | This method determines the best course of action for the agent based on the current state of the game world. 37 | It considers the positions and reachability of the ball by the agent, teammates, and opponents. 38 | Args: 39 | agent (SamplePlayerAgent): The agent making the decision. Must be an instance of SamplePlayerAgent. 40 | Raises: 41 | AssertionError: If the agent is not an instance of SamplePlayerAgent. 42 | Actions: 43 | - If can tackle the ball, the player will execute the helios basic tackle action and ignore other actions. 44 | - If no teammate can kick the ball and the agent can reach the ball within 3 steps or before any opponent: 45 | - Adds Body_Intercept and Neck_TurnToBallOrScan actions to the agent. 46 | - If an opponent can reach the ball before the agent and teammates: 47 | - Executes Bhv_Block behavior. 48 | - Otherwise: 49 | - Adds Body_GoToPoint and Body_TurnToBall actions to the agent. 50 | - If an opponent can kick the ball and is within 18 units of distance: 51 | - Adds Neck_TurnToBall action. 52 | - Otherwise: 53 | - Adds Neck_TurnToBallOrScan action with a count threshold of 0. 54 | Logging: 55 | - Logs debug information about the decisions made. 56 | """ 57 | agent.logger.debug(f'------ NoBallDecisionMaker ------') 58 | wm: WorldModel = agent.wm 59 | 60 | self.bhv_tackle.execute(agent) 61 | 62 | self_min = wm.intercept_table.self_reach_steps 63 | opp_min = wm.intercept_table.first_opponent_reach_steps 64 | tm_min = wm.intercept_table.first_teammate_reach_steps 65 | 66 | if not wm.kickable_teammate_existance and (self_min <= 3 or (self_min <= tm_min and self_min < opp_min + 3)): 67 | agent.add_action(PlayerAction(body_intercept=Body_Intercept())) 68 | agent.add_action(PlayerAction(neck_offensive_intercept_neck=Neck_OffensiveInterceptNeck())) 69 | 70 | agent.logger.debug(f'NoBallDecisionMaker: Body_Intercept') 71 | return 72 | 73 | if opp_min < min(self_min, tm_min): 74 | if self.bhv_block.execute(agent): 75 | return 76 | 77 | target_point = agent.strategy.get_position(wm.self.uniform_number, agent) 78 | dash_power = MoveDecisionMaker.get_normal_dash_power(agent) 79 | 80 | ball_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 81 | self_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.self.position) 82 | 83 | ball_dist_from_self = ball_pos.dist(self_pos) 84 | dist_thr = ball_dist_from_self * 0.1 85 | if dist_thr < 1.0: 86 | dist_thr = 1.0 87 | 88 | agent.add_action(PlayerAction(body_go_to_point=Body_GoToPoint(target_point=Tools.convert_vector2d_to_rpc_vector2d(target_point), 89 | max_dash_power=dash_power, 90 | distance_threshold=dist_thr))) 91 | agent.add_action(PlayerAction(body_turn_to_ball=Body_TurnToBall())) 92 | 93 | if wm.kickable_opponent_existance and ball_dist_from_self < 18.0: 94 | agent.add_action(PlayerAction(neck_turn_to_ball=Neck_TurnToBall())) 95 | else: 96 | agent.add_action(PlayerAction(neck_turn_to_ball_or_scan=Neck_TurnToBallOrScan(count_threshold=0))) 97 | 98 | agent.logger.debug(f'NoBallDecisionMaker: Body_GoToPoint {target_point} {dash_power} {dist_thr} or Body_TurnToBall') 99 | 100 | s_recover_mode = False 101 | 102 | @staticmethod 103 | def get_normal_dash_power(agent: "SamplePlayerAgent") -> float: 104 | """ 105 | Get the normal dash power for the agent based on the current world model state. 106 | This method calculates the normal dash power for the agent based on the stamina level, ball position, and other factors. 107 | Args: 108 | agent (SamplePlayerAgent): The agent for which the dash power is being calculated 109 | Returns: 110 | float: The normal dash power for the agent. 111 | """ 112 | wm = agent.wm 113 | 114 | player_type: PlayerType = agent.player_types[wm.self.type_id] 115 | sp: ServerParam = agent.server_params 116 | 117 | if wm.self.stamina_capacity < 0.01: 118 | return min(sp.max_dash_power, wm.self.stamina + player_type.extra_stamina) 119 | 120 | self_min = wm.intercept_table.self_reach_steps 121 | mate_min = wm.intercept_table.first_teammate_reach_steps 122 | opp_min = wm.intercept_table.first_opponent_reach_steps 123 | 124 | if wm.self.stamina_capacity < 0.01: 125 | MoveDecisionMaker.s_recover_mode = False 126 | elif wm.self.stamina < sp.stamina_max * 0.5: 127 | MoveDecisionMaker.s_recover_mode = True 128 | elif wm.self.stamina > sp.stamina_max * 0.7: 129 | MoveDecisionMaker.s_recover_mode = False 130 | 131 | dash_power = sp.max_dash_power 132 | my_inc = player_type.stamina_inc_max * wm.self.recovery 133 | 134 | if wm.our_defense_line_x > wm.self.position.x and wm.ball.position.x < wm.our_defense_line_x + 20.0: 135 | agent.logger.debug(f'NoBallDecisionMaker: correct DF line. keep max power') 136 | dash_power = sp.max_dash_power 137 | elif MoveDecisionMaker.s_recover_mode: 138 | dash_power = my_inc - 25.0 139 | if dash_power < 0.0: 140 | dash_power = 0.0 141 | agent.logger.debug(f'NoBallDecisionMaker: recovering') 142 | elif wm.kickable_teammate_existance and wm.ball.dist_from_self < 20.0: 143 | dash_power = min(my_inc * 1.1, sp.max_dash_power) 144 | agent.logger.debug(f'NoBallDecisionMaker: exist kickable teammate. dash_power={dash_power}') 145 | elif wm.self.position.x > wm.offside_line_x: 146 | dash_power = sp.max_dash_power 147 | agent.logger.debug(f'NoBallDecisionMaker: in offside area. dash_power={dash_power}') 148 | elif wm.ball.position.x > 25.0 and wm.ball.position.x > wm.self.position.x + 10.0 and self_min < opp_min - 6 and mate_min < opp_min - 6: 149 | dash_power = bound(sp.max_dash_power * 0.1, my_inc * 0.5, sp.max_dash_power) 150 | agent.logger.debug(f'NoBallDecisionMaker: opponent ball dash_power={dash_power}') 151 | else: 152 | dash_power = min(my_inc * 1.7, sp.max_dash_power) 153 | agent.logger.debug(f'NoBallDecisionMaker: normal mode dash_power={dash_power}') 154 | 155 | return dash_power 156 | -------------------------------------------------------------------------------- /src/decision_makers/penalty_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from service_pb2 import * 4 | from src.behaviors.bhv_penalty import BhvPenalty 5 | from src.behaviors.starter.bhv_starter_penalty import BhvStarterPenalty 6 | 7 | if TYPE_CHECKING: 8 | from src.sample_player_agent import SamplePlayerAgent 9 | 10 | class PenaltyDecisionMaker(IDecisionMaker): 11 | def __init__(self, agent: "SamplePlayerAgent"): 12 | self.bhv_penalty = BhvPenalty() if not agent.use_starter_code else BhvStarterPenalty() 13 | 14 | def make_decision(self, agent: "SamplePlayerAgent"): 15 | agent.logger.debug("PenaltyDecisionMaker.make_decision") 16 | self.bhv_penalty.execute(agent) -------------------------------------------------------------------------------- /src/decision_makers/play_on_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from src.decision_makers.kick_decision_maker import KickDecisionMaker 4 | from src.decision_makers.move_decision_maker import MoveDecisionMaker 5 | 6 | if TYPE_CHECKING: 7 | from src.sample_player_agent import SamplePlayerAgent 8 | 9 | class PlayOnDecisionMaker(IDecisionMaker): 10 | """ 11 | A decision maker that delegates decisions to either a KickDecisionMaker or a MoveDecisionMaker 12 | based on whether the agent is in a position to kick the ball. 13 | Attributes: 14 | kick_decision_maker (KickDecisionMaker): The decision maker used when the agent can kick the ball. 15 | move_decision_maker (MoveDecisionMaker): The decision maker used when the agent cannot kick the ball. 16 | Methods: 17 | make_decision(agent: IAgent): 18 | Makes a decision based on the agent's ability to kick the ball. 19 | Delegates the decision to either kick_decision_maker or move_decision_maker. 20 | """ 21 | def __init__(self, agent: "SamplePlayerAgent"): 22 | self.kick_decision_maker = KickDecisionMaker(agent) 23 | self.move_decision_maker = MoveDecisionMaker(agent) 24 | pass 25 | 26 | def make_decision(self, agent: "SamplePlayerAgent"): 27 | if agent.wm.self.is_kickable: 28 | self.kick_decision_maker.make_decision(agent) 29 | else: 30 | self.move_decision_maker.make_decision(agent) -------------------------------------------------------------------------------- /src/decision_makers/set_play_decision_maker.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IDecisionMaker import IDecisionMaker 3 | from service_pb2 import * 4 | from src.behaviors.bhv_setplay import BhvSetPlay 5 | from src.behaviors.starter.bhv_starter_setplay import BhvStarterSetPlay 6 | 7 | if TYPE_CHECKING: 8 | from src.sample_player_agent import SamplePlayerAgent 9 | 10 | class SetPlayDecisionMaker(IDecisionMaker): 11 | def __init__(self, agent: "SamplePlayerAgent"): 12 | self.bhv_setplay = BhvSetPlay() if not agent.use_starter_code else BhvStarterSetPlay() 13 | 14 | def make_decision(self, agent: "SamplePlayerAgent"): 15 | agent.logger.debug("SetPlayDecisionMaker.make_decision") 16 | self.bhv_setplay.execute(agent) -------------------------------------------------------------------------------- /src/formations/4-3-3-cyrus-base/before-kick-off.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "20220408-135243", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "OffensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "DefensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "DefensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideHalf", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideHalf", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : -12.50, "y" : -5.00 }, 89 | "3" : { "x" : -12.50, "y" : 5.00 }, 90 | "4" : { "x" : -11.60, "y" : -15.80 }, 91 | "5" : { "x" : -11.60, "y" : 15.80 }, 92 | "6" : { "x" : -6.70, "y" : -1.90 }, 93 | "7" : { "x" : -10.00, "y" : -10.80 }, 94 | "8" : { "x" : -10.00, "y" : 10.80 }, 95 | "9" : { "x" : -0.50, "y" : -23.80 }, 96 | "10" : { "x" : -0.50, "y" : 23.80 }, 97 | "11" : { "x" : -0.39, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3-cyrus-base/goalie-kick-opp-formation.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "20220408-143912", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "DefensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "OffensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "OffensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideForward", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideForward", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : 0.04, "y" : -10.00 }, 89 | "3" : { "x" : 0.04, "y" : 10.00 }, 90 | "4" : { "x" : 0.04, "y" : -20.00 }, 91 | "5" : { "x" : 0.04, "y" : 20.00 }, 92 | "6" : { "x" : 10.00, "y" : 0.00 }, 93 | "7" : { "x" : 15.00, "y" : -12.00 }, 94 | "8" : { "x" : 15.00, "y" : 12.00 }, 95 | "9" : { "x" : 31.00, "y" : -17.50 }, 96 | "10" : { "x" : 31.00, "y" : 17.50 }, 97 | "11" : { "x" : 31.00, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3-cyrus-base/goalie-kick-our-formation.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "20220408-143921", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "DefensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "OffensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "OffensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideForward", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideForward", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : -44.50, "y" : -4.00 }, 89 | "3" : { "x" : -44.50, "y" : 4.00 }, 90 | "4" : { "x" : -42.00, "y" : -24.00 }, 91 | "5" : { "x" : -42.00, "y" : 24.00 }, 92 | "6" : { "x" : -32.00, "y" : 0.00 }, 93 | "7" : { "x" : -32.50, "y" : -13.00 }, 94 | "8" : { "x" : -32.50, "y" : 13.00 }, 95 | "9" : { "x" : -13.00, "y" : -27.00 }, 96 | "10" : { "x" : -13.00, "y" : 27.00 }, 97 | "11" : { "x" : -13.00, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3-helios-base/before-kick-off.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "DefensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "OffensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "OffensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideForward", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideForward", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : -25.00, "y" : -5.00 }, 89 | "3" : { "x" : -25.00, "y" : 5.00 }, 90 | "4" : { "x" : -25.00, "y" : -10.00 }, 91 | "5" : { "x" : -25.00, "y" : 10.00 }, 92 | "6" : { "x" : -25.00, "y" : 0.00 }, 93 | "7" : { "x" : -15.00, "y" : -5.00 }, 94 | "8" : { "x" : -15.00, "y" : 5.00 }, 95 | "9" : { "x" : -15.00, "y" : -10.00 }, 96 | "10" : { "x" : -15.00, "y" : 10.00 }, 97 | "11" : { "x" : -15.00, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3-helios-base/goalie-kick-opp-formation.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "DefensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "OffensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "OffensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideForward", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideForward", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : 0.00, "y" : -5.00 }, 89 | "3" : { "x" : 0.00, "y" : 5.00 }, 90 | "4" : { "x" : 0.00, "y" : -12.00 }, 91 | "5" : { "x" : 0.00, "y" : 12.00 }, 92 | "6" : { "x" : 10.00, "y" : 0.00 }, 93 | "7" : { "x" : 15.00, "y" : -12.00 }, 94 | "8" : { "x" : 15.00, "y" : 12.00 }, 95 | "9" : { "x" : 31.00, "y" : -17.50 }, 96 | "10" : { "x" : 31.00, "y" : 17.50 }, 97 | "11" : { "x" : 31.00, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3-helios-base/goalie-kick-our-formation.conf: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "", 3 | "method" : "Static", 4 | "role" : [ 5 | { 6 | "number" : 1, 7 | "name" : "Goalie", 8 | "type" : "Unknown", 9 | "side" : "C", 10 | "pair" : 0 11 | }, 12 | { 13 | "number" : 2, 14 | "name" : "CenterBack", 15 | "type" : "Unknown", 16 | "side" : "C", 17 | "pair" : 0 18 | }, 19 | { 20 | "number" : 3, 21 | "name" : "CenterBack", 22 | "type" : "Unknown", 23 | "side" : "C", 24 | "pair" : 0 25 | }, 26 | { 27 | "number" : 4, 28 | "name" : "SideBack", 29 | "type" : "Unknown", 30 | "side" : "C", 31 | "pair" : 0 32 | }, 33 | { 34 | "number" : 5, 35 | "name" : "SideBack", 36 | "type" : "Unknown", 37 | "side" : "C", 38 | "pair" : 0 39 | }, 40 | { 41 | "number" : 6, 42 | "name" : "DefensiveHalf", 43 | "type" : "Unknown", 44 | "side" : "C", 45 | "pair" : 0 46 | }, 47 | { 48 | "number" : 7, 49 | "name" : "OffensiveHalf", 50 | "type" : "Unknown", 51 | "side" : "C", 52 | "pair" : 0 53 | }, 54 | { 55 | "number" : 8, 56 | "name" : "OffensiveHalf", 57 | "type" : "Unknown", 58 | "side" : "C", 59 | "pair" : 0 60 | }, 61 | { 62 | "number" : 9, 63 | "name" : "SideForward", 64 | "type" : "Unknown", 65 | "side" : "C", 66 | "pair" : 0 67 | }, 68 | { 69 | "number" : 10, 70 | "name" : "SideForward", 71 | "type" : "Unknown", 72 | "side" : "C", 73 | "pair" : 0 74 | }, 75 | { 76 | "number" : 11, 77 | "name" : "CenterForward", 78 | "type" : "Unknown", 79 | "side" : "C", 80 | "pair" : 0 81 | } 82 | ], 83 | "data" : [ 84 | { 85 | "index" : 0, 86 | "ball" : { "x" : 0.00, "y" : 0.00 }, 87 | "1" : { "x" : -49.00, "y" : 0.00 }, 88 | "2" : { "x" : -44.50, "y" : -4.00 }, 89 | "3" : { "x" : -44.50, "y" : 4.00 }, 90 | "4" : { "x" : -42.00, "y" : -24.00 }, 91 | "5" : { "x" : -42.00, "y" : 24.00 }, 92 | "6" : { "x" : -32.00, "y" : 0.00 }, 93 | "7" : { "x" : -32.50, "y" : -13.00 }, 94 | "8" : { "x" : -32.50, "y" : 13.00 }, 95 | "9" : { "x" : -13.00, "y" : -27.00 }, 96 | "10" : { "x" : -13.00, "y" : 27.00 }, 97 | "11" : { "x" : -13.00, "y" : 0.00 } } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/formations/4-3-3/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 | -------------------------------------------------------------------------------- /src/formations/4-3-3/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 | -------------------------------------------------------------------------------- /src/formations/4-3-3/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 | -------------------------------------------------------------------------------- /src/formations/4-3-3/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 | -------------------------------------------------------------------------------- /src/formations/4-3-3/setplay-opp-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 45 16 | ----- 0 ----- 17 | Ball 0 0 18 | 1 -50 0 19 | 2 -11.63 -4.6 20 | 3 -11.9 4.06 21 | 4 -10.09 -16.13 22 | 5 -9.91 14.51 23 | 6 -11.18 -0.36 24 | 7 -6.58 -8.2 25 | 8 -7.57 8.29 26 | 9 -1.26 -11.99 27 | 10 -1.8 12.17 28 | 11 11.72 0 29 | ----- 1 ----- 30 | Ball -54.44 -20.73 31 | 1 -50 0 32 | 2 -47.41 -10.72 33 | 3 -45.24 -5.14 34 | 4 -50.02 -17.21 35 | 5 -45.6 3.88 36 | 6 -39.73 -9.8 37 | 7 -40.83 -15.77 38 | 8 -30.82 6.85 39 | 9 -24.78 -29.47 40 | 10 -14.69 21.98 41 | 11 -14.9 -5.27 42 | ----- 2 ----- 43 | Ball -54.44 20.73 44 | 1 -50 0 45 | 2 -45.24 5.14 46 | 3 -47.41 10.72 47 | 4 -45.6 -3.88 48 | 5 -50.02 17.21 49 | 6 -39.73 9.8 50 | 7 -30.82 -6.85 51 | 8 -40.83 15.77 52 | 9 -14.69 -21.98 53 | 10 -24.78 29.47 54 | 11 -14.9 5.27 55 | ----- 3 ----- 56 | Ball 45.24 0 57 | 1 -50 0 58 | 2 -0.18 -6.92 59 | 3 -0.18 6.92 60 | 4 6.47 -17.34 61 | 5 6.47 17.34 62 | 6 15.5 -0 63 | 7 31.36 -6.2 64 | 8 31.36 6.2 65 | 9 36.39 -9.79 66 | 10 36.39 9.79 67 | 11 35.58 0.54 68 | ----- 4 ----- 69 | Ball -31.36 0 70 | 1 -50 0 71 | 2 -41.28 -3.97 72 | 3 -41.19 3.97 73 | 4 -40.38 -8.74 74 | 5 -40.02 8.47 75 | 6 -41.46 -0.27 76 | 7 -34.88 -9.1 77 | 8 -34.34 9.19 78 | 9 -10.86 -21.61 79 | 10 -10.75 21.75 80 | 11 -19.2 0.27 81 | ----- 5 ----- 82 | Ball 22.08 0 83 | 1 -50 0 84 | 2 -0.09 -9.37 85 | 3 0.18 7.75 86 | 4 0.09 -19.47 87 | 5 0.27 19.02 88 | 6 1.35 0 89 | 7 10.63 -6.13 90 | 8 10.45 5.5 91 | 9 13.16 -10.72 92 | 10 13.43 11.36 93 | 11 10.63 -0.27 94 | ----- 6 ----- 95 | Ball 11.72 0 96 | 1 -50 0 97 | 2 -2.25 -6.67 98 | 3 -2.61 4.6 99 | 4 -1.53 -14.15 100 | 5 -1.44 14.78 101 | 6 -1.44 -0.18 102 | 7 1.98 -8.92 103 | 8 1.71 8.56 104 | 9 7.84 -13.16 105 | 10 8.65 12.89 106 | 11 0.99 0 107 | ----- 7 ----- 108 | Ball -15.95 -22.98 109 | 1 -50 0 110 | 2 -28.75 -17.21 111 | 3 -28.57 -6.02 112 | 4 -28.66 -24.78 113 | 5 -28.3 4.67 114 | 6 -25.34 -11.68 115 | 7 -25.88 -20.3 116 | 8 -21.02 -7.1 117 | 9 0.09 -24.26 118 | 10 2.02 15.41 119 | 11 -12.76 -6.65 120 | ----- 8 ----- 121 | Ball -15.95 22.98 122 | 1 -50 0 123 | 2 -28.57 6.02 124 | 3 -28.75 17.21 125 | 4 -28.3 -4.67 126 | 5 -28.66 24.78 127 | 6 -25.34 11.68 128 | 7 -21.02 7.1 129 | 8 -25.88 20.3 130 | 9 2.02 -15.41 131 | 10 0.09 24.26 132 | 11 -12.76 6.65 133 | ----- 9 ----- 134 | Ball 30.73 -36 135 | 1 -50 0 136 | 2 0.18 -11.95 137 | 3 0.09 0.63 138 | 4 0.45 -25.43 139 | 5 3.05 8.45 140 | 6 8.27 -18.15 141 | 7 18.6 -28.03 142 | 8 22.55 -14.64 143 | 9 20.84 -33.15 144 | 10 29.56 -5.66 145 | 11 23.09 -22.91 146 | ----- 10 ----- 147 | Ball 30.73 36 148 | 1 -50 0 149 | 2 0.09 -0.63 150 | 3 0.18 11.95 151 | 4 3.05 -8.45 152 | 5 0.45 25.43 153 | 6 8.27 18.15 154 | 7 22.55 14.64 155 | 8 18.6 28.03 156 | 9 29.56 5.66 157 | 10 20.84 33.15 158 | 11 23.09 22.91 159 | ----- 11 ----- 160 | Ball -54.5 -36 161 | 1 -50 0 162 | 2 -45.64 -14.73 163 | 3 -45.24 -5.14 164 | 4 -49.75 -24.6 165 | 5 -45.6 3.88 166 | 6 -39.84 -15.59 167 | 7 -41.33 -23.81 168 | 8 -34.88 -0.09 169 | 9 -34.41 -31.45 170 | 10 -13.66 14.2 171 | 11 -15.54 -11.23 172 | ----- 12 ----- 173 | Ball -54.5 36 174 | 1 -50 0 175 | 2 -45.24 5.14 176 | 3 -45.64 14.73 177 | 4 -45.6 -3.88 178 | 5 -49.75 24.6 179 | 6 -39.84 15.59 180 | 7 -34.88 0.09 181 | 8 -41.33 23.81 182 | 9 -13.66 -14.2 183 | 10 -34.41 31.45 184 | 11 -15.54 11.23 185 | ----- 13 ----- 186 | Ball -35.51 -20.1 187 | 1 -50 0 188 | 2 -35.85 -6.47 189 | 3 -35.94 -0.54 190 | 4 -35.51 -10.18 191 | 5 -35.85 6.83 192 | 6 -31.54 -9.91 193 | 7 -26.05 -14.96 194 | 8 -28.84 -0.72 195 | 9 -21.72 -27.22 196 | 10 -4.96 16.58 197 | 11 -12.35 -9.19 198 | ----- 14 ----- 199 | Ball -35.51 20.1 200 | 1 -50 0 201 | 2 -35.94 0.54 202 | 3 -35.85 6.47 203 | 4 -35.85 -6.83 204 | 5 -35.51 10.18 205 | 6 -31.54 9.91 206 | 7 -28.84 0.72 207 | 8 -26.05 14.96 208 | 9 -4.96 -16.58 209 | 10 -21.72 27.22 210 | 11 -12.35 9.19 211 | ----- 15 ----- 212 | Ball 40.11 -36 213 | 1 -50 0 214 | 2 -0.63 -13.93 215 | 3 0.45 -1.08 216 | 4 9.7 -29.65 217 | 5 4.94 5.48 218 | 6 11.18 -14.06 219 | 7 26.5 -27.94 220 | 8 29.92 -16.62 221 | 9 31.09 -34.7 222 | 10 32.43 -4.67 223 | 11 31.98 -25.52 224 | ----- 16 ----- 225 | Ball 40.11 36 226 | 1 -50 0 227 | 2 0.45 1.08 228 | 3 -0.63 13.93 229 | 4 4.94 -5.48 230 | 5 9.7 29.65 231 | 6 11.18 14.06 232 | 7 29.92 16.62 233 | 8 26.5 27.94 234 | 9 32.43 4.67 235 | 10 31.09 34.7 236 | 11 31.98 25.52 237 | ----- 17 ----- 238 | Ball 54.5 -36 239 | 1 -50 0 240 | 2 -0.54 -11.68 241 | 3 0.09 0.09 242 | 4 12.58 -26.14 243 | 5 4.94 9.6 244 | 6 20.39 -13.39 245 | 7 35.49 -27.13 246 | 8 35.49 -9.7 247 | 9 39.53 -32.88 248 | 10 46 -11.32 249 | 11 37.2 -20.21 250 | ----- 18 ----- 251 | Ball 54.5 36 252 | 1 -50 0 253 | 2 0.09 -0.09 254 | 3 -0.54 11.68 255 | 4 4.94 -9.6 256 | 5 12.58 26.14 257 | 6 20.39 13.39 258 | 7 35.49 9.7 259 | 8 35.49 27.13 260 | 9 46 11.32 261 | 10 39.53 32.88 262 | 11 37.2 20.21 263 | ----- 19 ----- 264 | Ball 19.65 -36 265 | 1 -50 0 266 | 2 -1.44 -23.27 267 | 3 -0.36 -0.63 268 | 4 -0.45 -32.99 269 | 5 -0.09 10.51 270 | 6 1.17 -17.16 271 | 7 2.34 -27.67 272 | 8 8.36 -11.86 273 | 9 10.27 -29.92 274 | 10 15.18 -1.26 275 | 11 12.85 -22.73 276 | ----- 20 ----- 277 | Ball 19.65 36 278 | 1 -50 0 279 | 2 -0.36 0.63 280 | 3 -1.44 23.27 281 | 4 -0.09 -10.51 282 | 5 -0.45 32.99 283 | 6 1.17 17.16 284 | 7 8.36 11.86 285 | 8 2.34 27.67 286 | 9 15.18 1.26 287 | 10 10.27 29.92 288 | 11 12.85 22.73 289 | ----- 21 ----- 290 | Ball 0.27 -36 291 | 1 -50 0 292 | 2 -18.57 -23.88 293 | 3 -18.93 -8.47 294 | 4 -15.68 -32.72 295 | 5 -15.77 4.15 296 | 6 -12.98 -19.2 297 | 7 -12.08 -27.58 298 | 8 -6.67 -7.48 299 | 9 -6.58 -22.8 300 | 10 8.56 9.37 301 | 11 0.09 -18.75 302 | ----- 22 ----- 303 | Ball 0.27 36 304 | 1 -50 0 305 | 2 -18.93 8.47 306 | 3 -18.57 23.88 307 | 4 -15.77 -4.15 308 | 5 -15.68 32.72 309 | 6 -12.98 19.2 310 | 7 -6.67 7.48 311 | 8 -12.08 27.58 312 | 9 8.56 -9.37 313 | 10 -6.58 22.8 314 | 11 0.09 18.75 315 | ----- 23 ----- 316 | Ball 5.59 -11.36 317 | 1 -50 0 318 | 2 -8.36 -12.85 319 | 3 -7.1 -4.4 320 | 4 -6.92 -21.11 321 | 5 -5.59 6.22 322 | 6 -5.12 -10.24 323 | 7 -5.57 -17.61 324 | 8 -1.98 -1.26 325 | 9 -3.05 -19.68 326 | 10 13.16 16.76 327 | 11 6.94 -1.17 328 | ----- 24 ----- 329 | Ball 5.59 11.36 330 | 1 -50 0 331 | 2 -7.1 4.4 332 | 3 -8.36 12.85 333 | 4 -5.59 -6.22 334 | 5 -6.92 21.11 335 | 6 -5.12 10.24 336 | 7 -1.98 1.26 337 | 8 -5.57 17.61 338 | 9 13.16 -16.76 339 | 10 -3.05 19.68 340 | 11 6.94 1.17 341 | ----- 25 ----- 342 | Ball 5.32 -20.37 343 | 1 -50 0 344 | 2 -9.61 -19.5 345 | 3 -7.66 -7.93 346 | 4 -8.45 -27.04 347 | 5 -7.12 5.05 348 | 6 -6.94 -13.52 349 | 7 -6.65 -23.36 350 | 8 -0.72 -4.15 351 | 9 -5.93 -18.42 352 | 10 11.43 14.05 353 | 11 5.59 -8.29 354 | ----- 26 ----- 355 | Ball 5.32 20.37 356 | 1 -50 0 357 | 2 -7.66 7.93 358 | 3 -9.61 19.5 359 | 4 -7.12 -5.05 360 | 5 -8.45 27.04 361 | 6 -6.94 13.52 362 | 7 -0.72 4.15 363 | 8 -6.65 23.36 364 | 9 11.43 -14.05 365 | 10 -5.93 18.42 366 | 11 5.59 8.29 367 | ----- 27 ----- 368 | Ball 6.04 -27.85 369 | 1 -50 0 370 | 2 -6.02 -21.65 371 | 3 -5.77 -10.36 372 | 4 -6.65 -29.47 373 | 5 -4.69 1.98 374 | 6 -2.34 -16.53 375 | 7 -4.04 -25.43 376 | 8 0.81 -3.7 377 | 9 -1.35 -21.02 378 | 10 11.65 12.19 379 | 11 8.47 -14.06 380 | ----- 28 ----- 381 | Ball 6.04 27.85 382 | 1 -50 0 383 | 2 -5.77 10.36 384 | 3 -6.02 21.65 385 | 4 -4.69 -1.98 386 | 5 -6.65 29.47 387 | 6 -2.34 16.53 388 | 7 0.81 3.7 389 | 8 -4.04 25.43 390 | 9 11.65 -12.19 391 | 10 -1.35 21.02 392 | 11 8.47 14.06 393 | ----- 29 ----- 394 | Ball 43.71 -26.77 395 | 1 -50 0 396 | 2 -0.54 -11.68 397 | 3 0.09 0.09 398 | 4 3.15 -22.59 399 | 5 4.94 9.6 400 | 6 18.69 -12.31 401 | 7 27.58 -23.72 402 | 8 32.7 -10.78 403 | 9 31.36 -28.03 404 | 10 40.79 -5.75 405 | 11 31.98 -19.5 406 | ----- 30 ----- 407 | Ball 43.71 26.77 408 | 1 -50 0 409 | 2 0.09 -0.09 410 | 3 -0.54 11.68 411 | 4 4.94 -9.6 412 | 5 3.15 22.59 413 | 6 18.69 12.31 414 | 7 32.7 10.78 415 | 8 27.58 23.72 416 | 9 40.79 5.75 417 | 10 31.36 28.03 418 | 11 31.98 19.5 419 | ----- 31 ----- 420 | Ball 21.9 -12.8 421 | 1 -50 0 422 | 2 0.08 -11.13 423 | 3 0.03 2.01 424 | 4 0.18 -23.54 425 | 5 0.45 13.08 426 | 6 7.55 -6.2 427 | 7 7.28 -16.26 428 | 8 11.5 -1.71 429 | 9 11.86 -21.47 430 | 10 21.65 15 431 | 11 10.42 -11.05 432 | ----- 32 ----- 433 | Ball 21.9 12.8 434 | 1 -50 0 435 | 2 0.03 -2.01 436 | 3 0.08 11.13 437 | 4 0.45 -13.08 438 | 5 0.18 23.54 439 | 6 7.55 6.2 440 | 7 11.5 1.71 441 | 8 7.28 16.26 442 | 9 21.65 -15 443 | 10 11.86 21.47 444 | 11 10.42 11.05 445 | ----- 33 ----- 446 | Ball 21.27 -21.99 447 | 1 -50 0 448 | 2 0.18 -13.61 449 | 3 0.27 -3.24 450 | 4 -0.18 -27.58 451 | 5 0.54 8.56 452 | 6 9.55 -10.09 453 | 7 7.75 -20.55 454 | 8 12.71 -7.39 455 | 9 9.91 -25.05 456 | 10 20.64 -0.99 457 | 11 9.64 -16.49 458 | ----- 34 ----- 459 | Ball 21.27 21.99 460 | 1 -50 0 461 | 2 0.27 3.24 462 | 3 0.18 13.61 463 | 4 0.54 -8.56 464 | 5 -0.18 27.58 465 | 6 9.55 10.09 466 | 7 12.71 7.39 467 | 8 7.75 20.55 468 | 9 20.64 0.99 469 | 10 9.91 25.05 470 | 11 9.64 16.49 471 | ----- 35 ----- 472 | Ball 35.69 -8.56 473 | 1 -50 0 474 | 2 -0.45 -10.42 475 | 3 0.63 3.5 476 | 4 3.45 -20.89 477 | 5 5.09 13.48 478 | 6 9.25 -3.23 479 | 7 22.01 -14.46 480 | 8 23.09 -3.41 481 | 9 25.79 -17.88 482 | 10 26.32 3.68 483 | 11 24.51 -8.92 484 | ----- 36 ----- 485 | Ball 35.69 8.56 486 | 1 -50 0 487 | 2 0.63 -3.5 488 | 3 -0.45 10.42 489 | 4 5.09 -13.48 490 | 5 3.45 20.89 491 | 6 9.25 3.23 492 | 7 23.09 3.41 493 | 8 22.01 14.46 494 | 9 26.32 -3.68 495 | 10 25.79 17.88 496 | 11 24.51 8.92 497 | ----- 37 ----- 498 | Ball 31 -28.39 499 | 1 -50 0 500 | 2 0.18 -10.36 501 | 3 2.88 0.27 502 | 4 0.36 -22.89 503 | 5 9.88 9.61 504 | 6 10.42 -9.34 505 | 7 17.66 -23.88 506 | 8 22.64 -15.18 507 | 9 18.39 -28.75 508 | 10 25.34 -2.16 509 | 11 19.56 -19.47 510 | ----- 38 ----- 511 | Ball 31 28.39 512 | 1 -50 0 513 | 2 2.88 -0.27 514 | 3 0.18 10.36 515 | 4 9.88 -9.61 516 | 5 0.36 22.89 517 | 6 10.42 9.34 518 | 7 22.64 15.18 519 | 8 17.66 23.88 520 | 9 25.34 2.16 521 | 10 18.39 28.75 522 | 11 19.56 19.47 523 | ----- 39 ----- 524 | Ball -25.96 -36 525 | 1 -50 0 526 | 2 -38.99 -16.71 527 | 3 -37.91 -6.56 528 | 4 -39.53 -27.31 529 | 5 -36.57 4.94 530 | 6 -32.43 -17.43 531 | 7 -34.23 -24.89 532 | 8 -19.65 -5.68 533 | 9 -25.25 -22.37 534 | 10 0.05 16.07 535 | 11 -2.05 -11.45 536 | ----- 40 ----- 537 | Ball -25.96 36 538 | 1 -50 0 539 | 2 -37.91 6.56 540 | 3 -38.99 16.71 541 | 4 -36.57 -4.94 542 | 5 -39.53 27.31 543 | 6 -32.43 17.43 544 | 7 -19.65 5.68 545 | 8 -34.23 24.89 546 | 9 0.05 -16.07 547 | 10 -25.25 22.37 548 | 11 -2.05 11.45 549 | ----- 41 ----- 550 | Ball -34.7 -36 551 | 1 -50 -0 552 | 2 -45.42 -16.67 553 | 3 -41.17 -6.38 554 | 4 -43.38 -26.2 555 | 5 -42.27 5.02 556 | 6 -40.74 -16.42 557 | 7 -38.7 -22.37 558 | 8 -26.68 -5.3 559 | 9 -30.37 -23.56 560 | 10 -6.21 10.04 561 | 11 -15.91 -13.86 562 | ----- 42 ----- 563 | Ball -34.7 36 564 | 1 -50 0 565 | 2 -41.17 6.38 566 | 3 -45.42 16.67 567 | 4 -42.27 -5.02 568 | 5 -43.38 26.2 569 | 6 -40.74 16.42 570 | 7 -26.68 5.3 571 | 8 -38.7 22.37 572 | 9 -6.21 -10.04 573 | 10 -30.37 23.56 574 | 11 -15.91 13.86 575 | ----- 43 ----- 576 | Ball -16.13 -36 577 | 1 -50 0 578 | 2 -30.37 -20.37 579 | 3 -29.74 -7.57 580 | 4 -30.19 -31.27 581 | 5 -25.42 2.88 582 | 6 -24.53 -18.78 583 | 7 -26.86 -26.5 584 | 8 -14.15 -4.87 585 | 9 -17.07 -21.11 586 | 10 3.1 15.08 587 | 11 0.51 -11.31 588 | ----- 44 ----- 589 | Ball -16.13 36 590 | 1 -50 0 591 | 2 -29.74 7.57 592 | 3 -30.37 20.37 593 | 4 -25.42 -2.88 594 | 5 -30.19 31.27 595 | 6 -24.53 18.78 596 | 7 -14.15 4.87 597 | 8 -26.86 26.5 598 | 9 3.1 -15.08 599 | 10 -17.07 21.11 600 | 11 0.51 11.31 601 | End Samples 602 | End 603 | -------------------------------------------------------------------------------- /src/formations/4-3-3/setplay-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 46 16 | ----- 0 ----- 17 | Ball 0 0 18 | 1 -50 0 19 | 2 -15.22 -4.84 20 | 3 -15.33 3.66 21 | 4 -9.29 -15.12 22 | 5 -10.84 13.69 23 | 6 -0.71 -0.36 24 | 7 0 -6.97 25 | 8 0.48 6.73 26 | 9 13.69 -20.13 27 | 10 10.73 24 28 | 11 16.08 0 29 | ----- 1 ----- 30 | Ball -54 0 31 | 1 -50 -0 32 | 2 -46.15 -1.92 33 | 3 -46.03 2.88 34 | 4 -45.07 -7.57 35 | 5 -45.55 7.93 36 | 6 -39.78 0 37 | 7 -36.3 -15.98 38 | 8 -37.02 11.78 39 | 9 -22 -28 40 | 10 -19.29 26.44 41 | 11 -16.47 -0.84 42 | ----- 2 ----- 43 | Ball 36.26 0 44 | 1 -50 0 45 | 2 0 -7.38 46 | 3 0 7.38 47 | 4 4 -17.7 48 | 5 4 17.7 49 | 6 15.24 0.48 50 | 7 27.75 -8.1 51 | 8 28.1 9.76 52 | 9 35.96 -16.2 53 | 10 36.56 16.91 54 | 11 35.61 0 55 | ----- 3 ----- 56 | Ball -41.78 0 57 | 1 -50 0 58 | 2 -42.99 -0.71 59 | 3 -42.91 4.21 60 | 4 -42.31 -7.69 61 | 5 -42.91 9.01 62 | 6 -36.66 0.36 63 | 7 -33.41 -10.82 64 | 8 -31.61 10.94 65 | 9 -15.48 -29.41 66 | 10 -17.51 27.15 67 | 11 -18.27 -0.36 68 | ----- 4 ----- 69 | Ball -26.95 0 70 | 1 -50 0 71 | 2 -30.96 -1.91 72 | 3 -30.25 7.74 73 | 4 -29.9 -11.32 74 | 5 -28.25 16.41 75 | 6 -15.27 -7.63 76 | 7 -17.43 -23.41 77 | 8 -15.52 9.16 78 | 9 -4.76 -27.75 79 | 10 -5.34 26.47 80 | 11 -0.51 0.51 81 | ----- 5 ----- 82 | Ball -17.5 0 83 | 1 -50 0 84 | 2 -23.22 -1.67 85 | 3 -23.2 3.73 86 | 4 -22.84 -8.89 87 | 5 -22.96 12.26 88 | 6 -16.1 1.2 89 | 7 -11.9 -4.69 90 | 8 -10.94 6.37 91 | 9 1.2 -15.38 92 | 10 0.12 18.51 93 | 11 2.4 0 94 | ----- 6 ----- 95 | Ball 7.67 0 96 | 1 -50 0 97 | 2 -5.9 -5.31 98 | 3 -6.02 5.19 99 | 4 -3.07 -16.04 100 | 5 -2.95 16.39 101 | 6 6.25 -0.36 102 | 7 11.66 -5.77 103 | 8 11.78 6.13 104 | 9 21.76 -22.52 105 | 10 22.78 23.54 106 | 11 17.43 0 107 | ----- 7 ----- 108 | Ball 49.5 -20.51 109 | 1 -50 -0 110 | 2 3 -8.85 111 | 3 0 5 112 | 4 5 -20 113 | 5 12 15.5 114 | 6 21.04 -6.95 115 | 7 32.8 -18.96 116 | 8 35.1 0.46 117 | 9 45.46 -24.17 118 | 10 44.84 10.61 119 | 11 45.68 -10.85 120 | ----- 8 ----- 121 | Ball 49.5 20.51 122 | 1 -50 0 123 | 2 0 -5 124 | 3 3 8.85 125 | 4 12 -15.5 126 | 5 5 20 127 | 6 21.04 6.95 128 | 7 35.1 -0.46 129 | 8 32.8 18.96 130 | 9 44.84 -10.61 131 | 10 45.46 24.17 132 | 11 45.68 10.85 133 | ----- 9 ----- 134 | Ball -54 -10 135 | 1 -50.57 -6.44 136 | 2 -48.18 -5.96 137 | 3 -48.06 -1.07 138 | 4 -49.5 -9.06 139 | 5 -47.94 4.06 140 | 6 -44.37 -2.62 141 | 7 -43.73 -9.47 142 | 8 -31.39 8.23 143 | 9 -24.81 -29.58 144 | 10 -18.82 25.56 145 | 11 -20.87 -8.59 146 | ----- 10 ----- 147 | Ball -54 10 148 | 1 -50.57 6.44 149 | 2 -48.06 1.07 150 | 3 -48.18 5.96 151 | 4 -47.94 -4.06 152 | 5 -49.5 9.06 153 | 6 -44.37 2.62 154 | 7 -31.39 -8.23 155 | 8 -43.73 9.47 156 | 9 -18.82 -25.56 157 | 10 -24.81 29.58 158 | 11 -20.87 8.59 159 | ----- 11 ----- 160 | Ball -6 0 161 | 1 -50 0 162 | 2 -9.67 -0.25 163 | 3 -14 4.58 164 | 4 -13.23 -12.34 165 | 5 -8.91 12.34 166 | 6 -5.09 -9.92 167 | 7 -0.51 -21.38 168 | 8 1.65 11.71 169 | 9 10.26 -24 170 | 10 10.73 24 171 | 11 4.84 0 172 | ----- 12 ----- 173 | Ball -12 0 174 | 1 -50 0 175 | 2 -19.35 -3.12 176 | 3 -19.11 3 177 | 4 -17.91 -11.18 178 | 5 -17.55 11.06 179 | 6 -13.22 0.12 180 | 7 -6.97 -6.85 181 | 8 -7.57 5.29 182 | 9 1.92 -21.51 183 | 10 0.84 22.23 184 | 11 5.65 0.36 185 | ----- 13 ----- 186 | Ball 35.78 -9.54 187 | 1 -50 0 188 | 2 1.68 -7.93 189 | 3 4.93 6.85 190 | 4 5 -20 191 | 5 12.26 16.47 192 | 6 16.55 -2.26 193 | 7 21.79 -15.36 194 | 8 26.44 1.07 195 | 9 35.73 -17.03 196 | 10 35.13 8.57 197 | 11 33.94 -8.57 198 | ----- 14 ----- 199 | Ball 35.78 9.54 200 | 1 -50 -0 201 | 2 4.93 -7.33 202 | 3 2.16 8.65 203 | 4 12 -15.5 204 | 5 5 20 205 | 6 16.55 2.26 206 | 7 26.44 -1.07 207 | 8 21.79 15.36 208 | 9 35.13 -8.57 209 | 10 35.73 17.03 210 | 11 33.94 8.57 211 | ----- 15 ----- 212 | Ball 54 -35 213 | 1 -50 0 214 | 2 -0.89 -12.98 215 | 3 6.36 2.29 216 | 4 8.3 -27.3 217 | 5 18.32 10.56 218 | 6 20.16 -10.85 219 | 7 38.11 -27.75 220 | 8 38.3 -4.2 221 | 9 47.63 -30.13 222 | 10 47.28 -2.38 223 | 11 48.47 -19.77 224 | ----- 16 ----- 225 | Ball 54 35 226 | 1 -50 -0 227 | 2 6.36 -2.29 228 | 3 -0.89 12.98 229 | 4 18.32 -10.56 230 | 5 8.3 27.3 231 | 6 20.16 10.85 232 | 7 38.3 4.2 233 | 8 38.11 27.75 234 | 9 47.28 2.38 235 | 10 47.63 30.13 236 | 11 48.47 19.77 237 | ----- 17 ----- 238 | Ball -12 -35 239 | 1 -50 0 240 | 2 -17.51 -22.27 241 | 3 -18.01 -8.59 242 | 4 -13.46 -32.39 243 | 5 -18.72 4.06 244 | 6 -7.5 -15.36 245 | 7 -8.65 -28.96 246 | 8 -5.96 0.12 247 | 9 4.17 -31.84 248 | 10 0.95 18.25 249 | 11 5.48 -7.15 250 | ----- 18 ----- 251 | Ball -12 35 252 | 1 -50 -0 253 | 2 -18.01 8.59 254 | 3 -17.51 22.27 255 | 4 -18.72 -4.06 256 | 5 -13.46 32.39 257 | 6 -7.5 15.36 258 | 7 -5.96 -0.12 259 | 8 -7.57 30.17 260 | 9 0.95 -18.25 261 | 10 4.17 31.84 262 | 11 5.48 7.15 263 | ----- 19 ----- 264 | Ball -36.02 -35 265 | 1 -50 -0.01 266 | 2 -37.79 -14.51 267 | 3 -38.05 -7.25 268 | 4 -36.32 -30.49 269 | 5 -37.92 1.27 270 | 6 -30.54 -19.34 271 | 7 -26.08 -24.69 272 | 8 -20.16 0.6 273 | 9 -12.72 -32.32 274 | 10 -7.44 19.44 275 | 11 -7.62 -11.95 276 | ----- 20 ----- 277 | Ball -36.02 35 278 | 1 -50 0.01 279 | 2 -38.05 7.25 280 | 3 -37.79 14.51 281 | 4 -37.92 -1.27 282 | 5 -36.32 30.49 283 | 6 -30.54 19.34 284 | 7 -20.16 -0.6 285 | 8 -26.08 24.69 286 | 9 -7.44 -19.44 287 | 10 -12.72 32.32 288 | 11 -7.62 11.95 289 | ----- 21 ----- 290 | Ball -54 -35 291 | 1 -50 0 292 | 2 -46.83 -11.96 293 | 3 -46.51 -4.65 294 | 4 -50.73 -32.15 295 | 5 -45.56 4.77 296 | 6 -41.99 -15.01 297 | 7 -35.76 -22.52 298 | 8 -23.79 0.76 299 | 9 -22.39 -31.81 300 | 10 -12.98 19.47 301 | 11 -16.92 -13.11 302 | ----- 22 ----- 303 | Ball -54 35 304 | 1 -50 -0 305 | 2 -46.51 4.65 306 | 3 -46.83 11.96 307 | 4 -45.56 -4.77 308 | 5 -50.73 32.15 309 | 6 -41.99 15.01 310 | 7 -23.79 -0.76 311 | 8 -35.76 22.52 312 | 9 -12.98 -19.47 313 | 10 -22.39 31.81 314 | 11 -16.92 13.11 315 | ----- 23 ----- 316 | Ball -17.5 -11 317 | 1 -50 0 318 | 2 -26.59 -10.18 319 | 3 -26.47 -3.94 320 | 4 -26.72 -14.63 321 | 5 -26.21 2.54 322 | 6 -19.98 -11.07 323 | 7 -17.43 -18.96 324 | 8 -15.27 -1.4 325 | 9 -4.07 -30.92 326 | 10 -1.02 17.69 327 | 11 -3.82 -10.69 328 | ----- 24 ----- 329 | Ball -17.5 11 330 | 1 -50 -0 331 | 2 -26.47 3.94 332 | 3 -26.59 10.18 333 | 4 -26.21 -2.54 334 | 5 -26.72 14.63 335 | 6 -19.98 11.07 336 | 7 -15.27 1.4 337 | 8 -17.43 18.96 338 | 9 -1.02 -17.69 339 | 10 -4.07 30.92 340 | 11 -3.82 10.69 341 | ----- 25 ----- 342 | Ball 36.08 -20.6 343 | 1 -50 0 344 | 2 2.57 -11.68 345 | 3 0 2.08 346 | 4 4.57 -22.24 347 | 5 9.42 13.34 348 | 6 17.57 -12.21 349 | 7 32.96 -16.03 350 | 8 28.91 -1.62 351 | 9 38.94 -20.74 352 | 10 39.06 2.16 353 | 11 37.92 -10.18 354 | ----- 26 ----- 355 | Ball 36.08 20.6 356 | 1 -50 -0 357 | 2 0 -2.08 358 | 3 2.57 11.68 359 | 4 9.42 -13.34 360 | 5 4.57 22.24 361 | 6 17.57 12.21 362 | 7 28.91 1.62 363 | 8 32.96 16.03 364 | 9 39.06 -2.16 365 | 10 38.94 20.74 366 | 11 37.92 10.18 367 | ----- 27 ----- 368 | Ball -26.95 -11 369 | 1 -50 0 370 | 2 -30.25 -11.07 371 | 3 -29.69 -2.28 372 | 4 -29.81 -18.03 373 | 5 -26.92 7.69 374 | 6 -21.75 -7.69 375 | 7 -18.87 -29.81 376 | 8 -12.5 5.05 377 | 9 -4.64 -30.01 378 | 10 -3.73 23.56 379 | 11 -6.61 -10.94 380 | ----- 28 ----- 381 | Ball -26.95 11 382 | 1 -50 -0 383 | 2 -29.69 2.28 384 | 3 -30.25 11.07 385 | 4 -26.92 -7.69 386 | 5 -29.81 18.03 387 | 6 -21.75 7.69 388 | 7 -12.5 -5.05 389 | 8 -18.87 29.81 390 | 9 -3.73 -23.56 391 | 10 -4.64 30.01 392 | 11 -6.61 10.94 393 | ----- 29 ----- 394 | Ball -48.97 -17.73 395 | 1 -50 -7 396 | 2 -47.24 -6.8 397 | 3 -46.76 -0.19 398 | 4 -46.68 -16.79 399 | 5 -46.57 4.31 400 | 6 -40.62 -8.41 401 | 7 -37.14 -28.12 402 | 8 -35.33 4.21 403 | 9 -18.7 -31.91 404 | 10 -20.48 23.1 405 | 11 -17.62 -9.17 406 | ----- 30 ----- 407 | Ball -48.97 17.73 408 | 1 -50 7 409 | 2 -46.76 0.19 410 | 3 -47.24 6.8 411 | 4 -46.57 -4.31 412 | 5 -46.68 16.79 413 | 6 -40.62 8.41 414 | 7 -35.33 -4.21 415 | 8 -37.14 28.12 416 | 9 -20.48 -23.1 417 | 10 -18.7 31.91 418 | 11 -17.62 9.17 419 | ----- 31 ----- 420 | Ball -26.95 -31.61 421 | 1 -50 -0.02 422 | 2 -30.65 -16.82 423 | 3 -30.77 -6.44 424 | 4 -30.06 -29.1 425 | 5 -30.29 3.7 426 | 6 -21.03 -15.74 427 | 7 -24.04 -26.08 428 | 8 -15.24 -1.07 429 | 9 -4.96 -32.07 430 | 10 -7.86 21.08 431 | 11 -0.24 -11.31 432 | ----- 32 ----- 433 | Ball -26.95 31.61 434 | 1 -50 0.02 435 | 2 -30.77 6.44 436 | 3 -30.65 16.82 437 | 4 -30.29 -3.7 438 | 5 -30.06 29.1 439 | 6 -21.03 15.74 440 | 7 -15.24 1.07 441 | 8 -24.04 26.08 442 | 9 -7.86 -21.08 443 | 10 -4.96 32.07 444 | 11 -0.24 11.31 445 | ----- 33 ----- 446 | Ball 15.14 0 447 | 1 -50 0 448 | 2 0.13 -4.07 449 | 3 0.25 4.45 450 | 4 0 -16.13 451 | 5 0 16.13 452 | 6 13.34 0.24 453 | 7 17.07 -7.45 454 | 8 18.15 6.85 455 | 9 22.63 -28.94 456 | 10 24.41 28.34 457 | 11 22.59 0.12 458 | ----- 34 ----- 459 | Ball 23.26 -21.95 460 | 1 -50 0 461 | 2 2 -15.44 462 | 3 4.45 -1.2 463 | 4 4 -25.21 464 | 5 7.81 9.98 465 | 6 13.81 -12.62 466 | 7 20.31 -21.63 467 | 8 20.24 1.31 468 | 9 28.58 -31.08 469 | 10 27.63 19.29 470 | 11 28.22 -12.15 471 | ----- 35 ----- 472 | Ball 23.26 21.95 473 | 1 -50 -0 474 | 2 4.45 1.2 475 | 3 2 15.44 476 | 4 7.81 -9.98 477 | 5 4 25.21 478 | 6 13.81 12.62 479 | 7 20.24 -1.31 480 | 8 20.31 21.63 481 | 9 27.63 -19.29 482 | 10 28.58 31.08 483 | 11 28.22 12.15 484 | ----- 36 ----- 485 | Ball 0 -11 486 | 1 -50 0 487 | 2 -10.84 -11.19 488 | 3 -10.46 -3.85 489 | 4 -9.65 -21.67 490 | 5 -9.54 9.16 491 | 6 -1.32 -11.18 492 | 7 1.44 -16.35 493 | 8 2.16 0.96 494 | 9 14.12 -27.99 495 | 10 14.51 16.92 496 | 11 12.72 -8.91 497 | ----- 37 ----- 498 | Ball 0 11 499 | 1 -50 -0 500 | 2 -10.46 3.85 501 | 3 -10.84 11.19 502 | 4 -9.54 -9.16 503 | 5 -9.65 21.67 504 | 6 -1.32 11.18 505 | 7 2.16 -0.96 506 | 8 1.44 16.35 507 | 9 14.51 -16.92 508 | 10 14.12 27.99 509 | 11 12.72 8.91 510 | ----- 38 ----- 511 | Ball 0 -35 512 | 1 -50 0 513 | 2 -7.81 -20.31 514 | 3 -8.65 -10.46 515 | 4 -2.16 -33.05 516 | 5 -5.37 4.29 517 | 6 -0.48 -16.47 518 | 7 2.88 -28.72 519 | 8 5.49 -0.12 520 | 9 15.14 -32.93 521 | 10 14.89 18.34 522 | 11 14.89 -12.5 523 | ----- 39 ----- 524 | Ball 0 35 525 | 1 -50 -0 526 | 2 -8.65 10.46 527 | 3 -7.81 20.31 528 | 4 -5.37 -4.29 529 | 5 -2.16 33.05 530 | 6 -0.48 16.47 531 | 7 5.49 0.12 532 | 8 2.88 28.72 533 | 9 14.89 -18.34 534 | 10 15.14 32.93 535 | 11 14.89 12.5 536 | ----- 40 ----- 537 | Ball 22.74 -30.01 538 | 1 -50 0 539 | 2 -0.96 -16.59 540 | 3 1.68 -2.28 541 | 4 1.85 -27.71 542 | 5 4.21 8.37 543 | 6 12.38 -17.15 544 | 7 21.03 -29.57 545 | 8 18.62 0.38 546 | 9 26.08 -24.88 547 | 10 27 16.09 548 | 11 27.75 -9.17 549 | ----- 41 ----- 550 | Ball 22.74 30.01 551 | 1 -50 -0 552 | 2 1.68 2.28 553 | 3 -0.96 16.59 554 | 4 4.21 -8.37 555 | 5 1.85 27.71 556 | 6 12.38 17.15 557 | 7 18.62 -0.38 558 | 8 21.03 29.57 559 | 9 27 -16.09 560 | 10 26.08 24.88 561 | 11 27.75 9.17 562 | ----- 42 ----- 563 | Ball -12 -19 564 | 1 -50 0 565 | 2 -14.06 -18.51 566 | 3 -18.1 -7.15 567 | 4 -16.95 -24.28 568 | 5 -17.43 3.61 569 | 6 -8.29 -12.74 570 | 7 -5.89 -20.67 571 | 8 -1.53 3.94 572 | 9 3.45 -30.49 573 | 10 3.33 25.13 574 | 11 2.38 -8.1 575 | ----- 43 ----- 576 | Ball -12 19 577 | 1 -50 -0 578 | 2 -18.1 7.15 579 | 3 -14.06 18.51 580 | 4 -17.43 -3.61 581 | 5 -16.95 24.28 582 | 6 -8.29 12.74 583 | 7 -1.53 -3.94 584 | 8 -5.89 20.67 585 | 9 3.33 -25.13 586 | 10 3.45 30.49 587 | 11 2.38 8.1 588 | ----- 44 ----- 589 | Ball -42.1 -28.03 590 | 1 -50 -0.04 591 | 2 -42.16 -14.65 592 | 3 -41.86 -6.87 593 | 4 -42.99 -26.08 594 | 5 -40.72 1.53 595 | 6 -35.21 -15.5 596 | 7 -37.26 -22.84 597 | 8 -22.03 1.19 598 | 9 -18.99 -32.33 599 | 10 -12.76 21.71 600 | 11 -10.36 -13.93 601 | ----- 45 ----- 602 | Ball -42.1 28.03 603 | 1 -50 0.04 604 | 2 -41.86 6.87 605 | 3 -42.16 14.65 606 | 4 -40.72 -1.53 607 | 5 -42.99 26.08 608 | 6 -35.21 15.5 609 | 7 -22.03 -1.19 610 | 8 -37.26 22.84 611 | 9 -12.76 -21.71 612 | 10 -18.99 32.33 613 | 11 -10.36 13.93 614 | End Samples 615 | End 616 | -------------------------------------------------------------------------------- /src/interfaces/IAgent.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from typing import Union 3 | from abc import ABC, abstractmethod 4 | from typing import Union 5 | from service_pb2 import * 6 | import logging 7 | from src.utils.memory import Memory 8 | 9 | 10 | if TYPE_CHECKING: 11 | from src.utils.tools import Tools 12 | 13 | class IAgent(ABC): 14 | def __init__(self, logger) -> None: 15 | super().__init__() 16 | self.wm: Union[WorldModel, None] = None 17 | self.actions: list[Union[PlayerAction, CoachAction, TrainerAction]] = [] 18 | self.server_params: Union[ServerParam, None] = None 19 | self.player_params: Union[PlayerParam, None] = None 20 | self.player_types: dict[PlayerType] = {} 21 | self.debug_mode: bool = False 22 | self.memory: Memory = Memory() 23 | self.logger: logging.Logger = logger 24 | 25 | def set_server_params(self, server_param: ServerParam): 26 | self.server_params = server_param 27 | 28 | def set_player_params(self, player_param: PlayerParam): 29 | self.player_params = player_param 30 | 31 | def set_player_types(self, player_type: PlayerType): 32 | self.player_types[player_type.id] = player_type 33 | from src.utils.tools import Tools 34 | Tools.update_dash_distance_table(player_type, self) 35 | 36 | def get_player_type(self, id: int) -> PlayerType: 37 | if id < 0: 38 | id = 0 39 | return self.player_types[id] 40 | 41 | @abstractmethod 42 | def update_actions(self, wm: WorldModel): 43 | pass 44 | 45 | # @abstractmethod 46 | # def get_strategy(self) -> IPositionStrategy: 47 | # pass 48 | 49 | def set_debug_mode(self, debug_mode: bool): 50 | self.debug_mode = debug_mode 51 | 52 | def add_log_text(self, level: LoggerLevel, message: str): 53 | if not self.debug_mode: 54 | return 55 | self.add_action(PlayerAction( 56 | log=Log( 57 | add_text=AddText( 58 | level=level, 59 | message=message 60 | ) 61 | ) 62 | )) 63 | 64 | def add_log_message(self, level: LoggerLevel, message: str, x, y, color): 65 | if not self.debug_mode: 66 | return 67 | self.add_action(PlayerAction( 68 | log=Log( 69 | add_message=AddMessage( 70 | level=level, 71 | message=message, 72 | position=RpcVector2D(x=x, y=y), 73 | color=color, 74 | ) 75 | ) 76 | )) 77 | 78 | def add_log_circle(self, level: LoggerLevel, center_x: float, center_y: float, radius: float, color: str, 79 | fill: bool): 80 | if not self.debug_mode: 81 | return 82 | self.add_action(PlayerAction( 83 | log=Log( 84 | add_circle=AddCircle( 85 | level=level, 86 | center=RpcVector2D(x=center_x, y=center_y), 87 | radius=radius, 88 | color=color, 89 | fill=fill 90 | ) 91 | ) 92 | )) 93 | 94 | def add_log_line(self, level: LoggerLevel, start_x: float, start_y: float, end_x: float, end_y: float, color: str): 95 | if not self.debug_mode: 96 | return 97 | self.add_action(PlayerAction( 98 | log=Log( 99 | add_line=AddLine( 100 | level=level, 101 | start=RpcVector2D(x=start_x, y=start_y), 102 | end=RpcVector2D(x=end_x, y=end_y), 103 | color=color, 104 | ) 105 | ) 106 | ) 107 | ) 108 | 109 | def add_action(self, action: Union[PlayerAction, CoachAction, TrainerAction]): 110 | self.actions.append(action) 111 | 112 | @abstractmethod 113 | def get_actions(self) -> Union[PlayerActions, CoachActions, TrainerActions]: 114 | pass -------------------------------------------------------------------------------- /src/interfaces/IBehavior.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from src.interfaces.IAgent import IAgent 3 | 4 | 5 | class IBehavior(ABC): 6 | def execute(self, agent: IAgent): 7 | pass -------------------------------------------------------------------------------- /src/interfaces/IDecisionMaker.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from src.interfaces.IAgent import IAgent 3 | 4 | 5 | class IDecisionMaker(ABC): 6 | @abstractmethod 7 | def make_decision(self, agent: IAgent): 8 | pass 9 | -------------------------------------------------------------------------------- /src/interfaces/IPositionStrategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from pyrusgeom.soccer_math import * 3 | from pyrusgeom.geom_2d import * 4 | from service_pb2 import * 5 | from src.interfaces.IAgent import IAgent 6 | 7 | 8 | class IPositionStrategy(ABC): 9 | @abstractmethod 10 | def get_position(self, uniform_number, agent: IAgent) -> Vector2D: 11 | """ 12 | Get the position for a given uniform number. 13 | 14 | Args: 15 | uniform_number (int): The uniform number of the player. 16 | agent (IAgent): The agent instance. 17 | 18 | Returns: 19 | Vector2D: The position of the player. 20 | """ 21 | pass 22 | 23 | @abstractmethod 24 | def update(self, agent: IAgent): 25 | """ 26 | Update the strategy based on the agent's world model. 27 | 28 | Args: 29 | agent (IAgent): The agent instance. 30 | """ 31 | pass 32 | -------------------------------------------------------------------------------- /src/sample_coach_agent.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from src.interfaces.IAgent import IAgent 3 | from service_pb2 import * 4 | 5 | 6 | class SampleCoachAgent(IAgent, ABC): 7 | """ 8 | A sample coach agent implementation that handles team substitutions and tactics. 9 | Inherits from IAgent and implements required abstract methods. 10 | """ 11 | def __init__(self, logger) -> None: 12 | """ 13 | Initialize the coach agent. 14 | 15 | Args: 16 | logger: Logger instance for debug/info messages 17 | """ 18 | super().__init__(logger) 19 | self.logger.info('SampleCoachAgent created') 20 | self.wm: WorldModel = None 21 | self.first_substitution = True 22 | 23 | def update_actions(self, wm:WorldModel) -> CoachActions: 24 | """ 25 | Update coach actions based on the current world model state. 26 | Currently implements basic Helios substitution strategy. 27 | 28 | Args: 29 | wm (WorldModel): Current world model containing game state 30 | 31 | Returns: 32 | CoachActions: List of coach actions to be executed 33 | """ 34 | self.logger.debug(f'update_actions: {wm.cycle}') 35 | self.wm = wm 36 | 37 | self.actions.clear() 38 | self.add_action(CoachAction( 39 | do_helios_substitute=DoHeliosSubstitute() 40 | )) 41 | # actions = CoachActions() 42 | # actions.actions = [] 43 | # if (wm.cycle == 0 44 | # and self.first_substitution 45 | # and self.playerParams is not None 46 | # and len(self.playerTypes.keys()) == self.playerParams.player_types): 47 | 48 | # self.first_substitution = False 49 | # for i in range(11): 50 | # actions.actions.append( 51 | # CoachAction( 52 | # change_player_types=ChangePlayerType( 53 | # uniform_number=i+1, 54 | # type=i 55 | # ) 56 | # ) 57 | # ) 58 | 59 | # actions.append( 60 | # CoachAction( 61 | # do_helios_substitute=DoHeliosSubstitute() 62 | # ) 63 | # ) 64 | # self.add_action(CoachAction( 65 | # do_helios_substitute=DoHeliosSubstitute() 66 | # )) 67 | 68 | self.logger.debug(f'actions: {self.actions}') 69 | 70 | def get_actions(self) -> CoachActions: 71 | """ 72 | Get the list of coach actions to be executed 73 | """ 74 | 75 | return CoachActions(actions=self.actions) -------------------------------------------------------------------------------- /src/sample_player_agent.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from src.decision_makers.decision_maker import DecisionMaker 3 | from src.interfaces.IAgent import IAgent 4 | from src.strategy.formation_strategy import FormationStrategy 5 | from src.strategy.starter_strategy import StarterStrategy 6 | from service_pb2 import * 7 | 8 | 9 | class SamplePlayerAgent(IAgent, ABC): 10 | """ 11 | A sample player agent implementation that handles decision making and strategy execution. 12 | Inherits from IAgent and implements required abstract methods. 13 | """ 14 | def __init__(self, logger) -> None: 15 | """ 16 | Initialize the player agent with required components. 17 | 18 | Args: 19 | logger: Logger instance for debug/info messages 20 | """ 21 | super().__init__(logger) 22 | self.logger.info('SamplePlayerAgent created') 23 | 24 | # Flag to switch between starter mode and major mode 25 | self.use_starter_code = False 26 | 27 | # Initialize core components 28 | self.decision_maker = DecisionMaker(self) 29 | self.strategy = self._initialize_strategy() 30 | self.wm: WorldModel = None 31 | 32 | def _initialize_strategy(self): 33 | """ 34 | Initialize the strategy based on the use_starter_code flag. 35 | 36 | Returns: 37 | Strategy: Initialized strategy instance 38 | """ 39 | if self.use_starter_code: 40 | return StarterStrategy(self.logger) 41 | return FormationStrategy(self.logger) 42 | 43 | def update_actions(self, wm: WorldModel): 44 | """ 45 | Update agent actions based on the current world model state. 46 | 47 | Args: 48 | wm (WorldModel): Current world model containing game state 49 | """ 50 | self.logger.debug(f'update_actions: {wm.cycle}') 51 | self.wm = wm 52 | self.actions.clear() 53 | self.strategy.update(self) 54 | self.decision_maker.make_decision(self) 55 | self.logger.debug(f'actions: {self.actions}') 56 | 57 | def get_strategy(self): 58 | """ 59 | Get the current strategy instance being used by the agent. 60 | 61 | Returns: 62 | Strategy: Current strategy instance (FormationStrategy or StarterStrategy) 63 | """ 64 | return self.strategy 65 | 66 | def get_actions(self) -> PlayerActions: 67 | """ 68 | Get the list of player actions to be executed 69 | """ 70 | 71 | res = PlayerActions() 72 | res.actions.extend(self.actions) 73 | 74 | if self.use_starter_code: 75 | res.ignore_doHeardPassRecieve = True 76 | res.ignore_doIntention = True 77 | res.ignore_shootInPreprocess = True 78 | else: 79 | pass 80 | # res.ignore_shootInPreprocess = True 81 | return res -------------------------------------------------------------------------------- /src/sample_trainer_agent.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from src.interfaces.IAgent import IAgent 3 | from service_pb2 import * 4 | 5 | 6 | class SampleTrainerAgent(IAgent, ABC): 7 | """ 8 | A sample trainer agent implementation that handles training scenarios and ball control. 9 | Inherits from IAgent and implements required abstract methods. 10 | """ 11 | def __init__(self, logger) -> None: 12 | """ 13 | Initialize the trainer agent. 14 | 15 | Args: 16 | logger: Logger instance for debug/info messages 17 | """ 18 | super().__init__(logger) 19 | self.logger.info('SampleTrainerAgent created') 20 | self.wm: WorldModel = None 21 | self.first_substitution = True 22 | 23 | def update_actions(self, wm:WorldModel): 24 | """ 25 | Update trainer actions based on the current world model state. 26 | Periodically moves ball to center position every 100 cycles. 27 | 28 | Args: 29 | wm (WorldModel): Current world model containing game state 30 | """ 31 | self.logger.debug(f'update_actions: {wm.cycle}') 32 | self.wm = wm 33 | 34 | actions = TrainerActions() 35 | actions.actions = [] 36 | 37 | if self.wm.cycle % 100 == 0: 38 | actions.actions.append( 39 | TrainerAction( 40 | do_move_ball=DoMoveBall( 41 | position=RpcVector2D( 42 | x=0, 43 | y=0 44 | ), 45 | velocity=RpcVector2D( 46 | x=0, 47 | y=0 48 | ), 49 | ) 50 | ) 51 | ) 52 | self.logger.debug(f'actions: {self.actions}') 53 | 54 | def set_params(self, params): 55 | """ 56 | Set various game parameters for the trainer. 57 | 58 | Args: 59 | params: Can be ServerParam, PlayerParam, or PlayerType 60 | 61 | Raises: 62 | Exception: If parameter type is unknown 63 | """ 64 | if isinstance(params, ServerParam): 65 | self.serverParams = params 66 | elif isinstance(params, PlayerParam): 67 | self.playerParams = params 68 | elif isinstance(params, PlayerType): 69 | self.playerTypes[params.id] = params 70 | else: 71 | raise Exception("Unknown params type") 72 | 73 | def get_actions(self) -> TrainerActions: 74 | """ 75 | Get the list of coach actions to be executed 76 | """ 77 | 78 | return TrainerActions(actions=self.actions) -------------------------------------------------------------------------------- /src/strategy/formation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from src.strategy.formation_file import FormationFile 3 | 4 | class Formation: 5 | """ 6 | A class to manage different soccer formations for various game situations. 7 | 8 | Attributes: 9 | before_kick_off_formation (FormationFile): Formation used before the kick-off. 10 | defense_formation (FormationFile): Formation used during defense. 11 | offense_formation (FormationFile): Formation used during offense. 12 | goalie_kick_opp_formation (FormationFile): Formation used when the opponent's goalie kicks. 13 | goalie_kick_our_formation (FormationFile): Formation used when our goalie kicks. 14 | kickin_our_formation (FormationFile): Formation used during our team's kick-in. 15 | setplay_opp_formation (FormationFile): Formation used during the opponent's set play. 16 | setplay_our_formation (FormationFile): Formation used during our team's set play. 17 | 18 | Args: 19 | path (str): The path to the directory containing the formation configuration files. 20 | logger (logging.Logger): Logger instance for logging formation-related information. 21 | """ 22 | def __init__(self, path, logger: logging.Logger): 23 | self.logger = logger 24 | self.path = path 25 | self._initialize_formations() 26 | 27 | def _initialize_formations(self): 28 | """ 29 | Initialize formation files for different game situations. 30 | """ 31 | self.before_kick_off_formation = self._create_formation_file('before-kick-off.conf') 32 | self.defense_formation = self._create_formation_file('defense-formation.conf') 33 | self.offense_formation = self._create_formation_file('offense-formation.conf') 34 | self.goalie_kick_opp_formation = self._create_formation_file('goalie-kick-opp-formation.conf') 35 | self.goalie_kick_our_formation = self._create_formation_file('goalie-kick-our-formation.conf') 36 | self.kickin_our_formation = self._create_formation_file('kickin-our-formation.conf') 37 | self.setplay_opp_formation = self._create_formation_file('setplay-opp-formation.conf') 38 | self.setplay_our_formation = self._create_formation_file('setplay-our-formation.conf') 39 | 40 | def _create_formation_file(self, filename: str) -> FormationFile: 41 | """ 42 | Create a FormationFile instance for a given filename. 43 | 44 | Args: 45 | filename (str): The name of the formation configuration file. 46 | 47 | Returns: 48 | FormationFile: The created FormationFile instance. 49 | """ 50 | return FormationFile(f'{self.path}/{filename}', self.logger) 51 | -------------------------------------------------------------------------------- /src/strategy/formation_file.py: -------------------------------------------------------------------------------- 1 | from scipy.spatial import Delaunay 2 | from pyrusgeom.geom_2d import * 3 | from pyrusgeom.soccer_math import min_max 4 | import logging 5 | from src.strategy.formation_file_reader import FormationFileReaderFactory, FormationType 6 | from src.strategy.player_role import PlayerRole 7 | 8 | class FormationFile: 9 | def __init__(self, path, logger: logging.Logger): 10 | self._logger = logger 11 | self._balls = [] 12 | self._players = [] 13 | self._triangles = [] 14 | self._formation_type = FormationType.Static 15 | self._target_players = {} 16 | self._path = path 17 | self._roles: dict[int, PlayerRole] = {} 18 | self.read_file(path) 19 | self.calculate() 20 | 21 | def read_file(self, path): 22 | """Reads the formation file and initializes the formation data.""" 23 | indexes, self._roles, self._formation_type = FormationFileReaderFactory().read_file(path) 24 | 25 | if self._formation_type == FormationType.Static: 26 | data = indexes[0] 27 | players = data.players() 28 | for i in range(1, 12): 29 | self._target_players[i] = Vector2D(float(players[i][0]), float(players[i][1])) 30 | else: 31 | for index in indexes: 32 | self._balls.append(index.ball()) 33 | self._players.append(index.players()) 34 | 35 | def calculate(self): 36 | """Calculates the Delaunay triangulation for dynamic formations.""" 37 | if self._formation_type == FormationType.Static: 38 | return 39 | self._tri = Delaunay(self._balls).simplices 40 | for tri in self._tri: 41 | tmp = [Triangle2D(Vector2D(self._balls[tri[0]][0], self._balls[tri[0]][1]), 42 | Vector2D(self._balls[tri[1]][0], self._balls[tri[1]][1]), 43 | Vector2D(self._balls[tri[2]][0], self._balls[tri[2]][1])), tri[0], tri[1], tri[2]] 44 | self._triangles.append(tmp) 45 | 46 | def update(self, B: Vector2D): 47 | """Updates the target player positions based on the ball position B.""" 48 | if self._formation_type == FormationType.Static: 49 | return 50 | ids = [] 51 | 52 | point = B.copy() 53 | if point.abs_x() > 52.5: # Ensure the point is within pitch boundaries 54 | point._x = min_max(-52.5, point.x(), 52.5) 55 | if point.abs_y() > 34.0: 56 | point._y = min_max(-34.0, point.y(), 34.0) 57 | 58 | for tri in self._triangles: 59 | if tri[0].contains(point): 60 | ids = [tri[1], tri[2], tri[3]] 61 | break 62 | 63 | Pa = Vector2D(self._balls[ids[0]][0], self._balls[ids[0]][1]) 64 | Pb = Vector2D(self._balls[ids[1]][0], self._balls[ids[1]][1]) 65 | Pc = Vector2D(self._balls[ids[2]][0], self._balls[ids[2]][1]) 66 | lineProj = Line2D(p1=Pb, p2=Pc).projection(B) 67 | m1 = Pb.dist(lineProj) 68 | n1 = Pc.dist(lineProj) 69 | m2 = Pa.dist(B) 70 | n2 = lineProj.dist(B) 71 | 72 | self._target_players.clear() 73 | for p in range(1, 12): 74 | OPa = Vector2D(self._players[ids[0]][p][0], self._players[ids[0]][p][1]) 75 | OPb = Vector2D(self._players[ids[1]][p][0], self._players[ids[1]][p][1]) 76 | OPc = Vector2D(self._players[ids[2]][p][0], self._players[ids[2]][p][1]) 77 | OI = (OPc - OPb) 78 | OI *= (m1 / (m1 + n1)) 79 | OI += OPb 80 | OB = (OI - OPa) 81 | OB *= (m2 / (m2 + n2)) 82 | OB += OPa 83 | self._target_players[p] = OB 84 | 85 | def get_pos(self, unum): 86 | """Returns the position of the player with the given uniform number.""" 87 | return self._target_players[unum] 88 | 89 | def get_poses(self): 90 | """Returns the positions of all target players.""" 91 | return self._target_players 92 | 93 | def get_role(self, unum) -> PlayerRole: 94 | """Returns the role of the player with the given uniform number.""" 95 | return self._roles[unum] 96 | 97 | def __repr__(self): 98 | return self._path 99 | 100 | # Example usage: 101 | # f = Formation('base/formations-dt/before-kick-off.conf') 102 | # debug_print(len(f._balls)) 103 | # debug_print(len(f._players)) 104 | # debug_print(f._formation_type) 105 | # f.update(Vector2D(20, 16)) 106 | # debug_print(f._formation_type) 107 | # debug_print(f._target_players) 108 | -------------------------------------------------------------------------------- /src/strategy/formation_file_reader.py: -------------------------------------------------------------------------------- 1 | from pyrusgeom.geom_2d import * 2 | from enum import Enum 3 | from abc import ABC, abstractmethod 4 | import json 5 | from src.strategy.player_role import PlayerRole 6 | 7 | # Enum for formation types 8 | class FormationType(Enum): 9 | Static = 's' 10 | DelaunayTriangulation2 = 'D' 11 | 12 | # Class to store formation index data 13 | class FormationIndexData: 14 | def __init__(self, ball, players): 15 | self._ball: list[float] = ball 16 | self._players: list[list[float]] = players 17 | 18 | def ball(self) -> list[float]: 19 | """Returns the ball position.""" 20 | return self._ball 21 | 22 | def players(self) -> list[list[float]]: 23 | """Returns the players' positions.""" 24 | return self._players 25 | 26 | # Abstract base class for formation file readers 27 | class IFormationFileReader(ABC): 28 | @abstractmethod 29 | def read_file(self, lines: list[str]) -> tuple[list[FormationIndexData], dict[int, PlayerRole]]: 30 | """Reads the formation file and returns formation index data and player roles.""" 31 | pass 32 | 33 | # Class to read old static formation files 34 | class OldStaticFormationFileReader(IFormationFileReader): 35 | def read_file(self, lines: list[str]) -> tuple[list[FormationIndexData], dict[int, PlayerRole]]: 36 | """Reads old static formation files and returns formation index data and player roles.""" 37 | players = {} 38 | roles = {} 39 | for i in range(len(lines)): 40 | if i == 0 or lines[i].startswith('#'): 41 | continue 42 | player = lines[i].split() 43 | players[int(player[0])] = [float(player[2]), float(player[3])] 44 | roles[int(player[0])] = PlayerRole(player[1], None, None, None) 45 | 46 | return [FormationIndexData(None, players)], roles 47 | 48 | # Class to read old Delaunay triangulation formation files 49 | class OldDelaunayFormationFileReader(IFormationFileReader): 50 | def read_file(self, lines: list[str]) -> tuple[list[FormationIndexData], dict[int, PlayerRole]]: 51 | """Reads old Delaunay triangulation formation files and returns formation index data and player roles.""" 52 | roles = {} 53 | begin_roles = False 54 | for i in range(len(lines)): 55 | if lines[i].startswith('Begin Roles'): 56 | begin_roles = True 57 | continue 58 | if lines[i].startswith('End Roles'): 59 | break 60 | if begin_roles: 61 | player = lines[i].split() 62 | roles[int(player[0])] = PlayerRole(player[1], None, None, int(player[2])) 63 | indexes = [] 64 | for i in range(len(lines)): 65 | if 'Ball' in lines[i]: 66 | indexes.append(self.read_index(i, lines)) 67 | i += 11 68 | return indexes, roles 69 | 70 | def read_index(self, i: int, lines: list[str]) -> FormationIndexData: 71 | """Reads a single formation index from the lines.""" 72 | ball = lines[i].split(' ') 73 | ball_x = float(ball[1]) 74 | ball_y = float(ball[2]) 75 | ball = [ball_x, ball_y] 76 | players = {} 77 | for j in range(1, 12): 78 | player = lines[i + j].split(' ') 79 | player_x = float(player[1]) 80 | player_y = float(player[2]) 81 | players[j] = [player_x, player_y] 82 | return FormationIndexData(ball, players) 83 | 84 | # Class to read JSON formation files 85 | class JsonFormationFileReader(IFormationFileReader): 86 | def read_file(self, lines: list[str]) -> tuple[list[FormationIndexData], dict[int, PlayerRole]]: 87 | """Reads JSON formation files and returns formation index data and player roles.""" 88 | text = ''.join(lines) 89 | data = json.loads(text) 90 | roles = {} 91 | for role in data['role']: 92 | roles[role['number']] = PlayerRole(role['name'], role['type'], role['side'], role['pair']) 93 | indexes = [] 94 | for index in data['data']: 95 | ball = [index['ball']['x'], index['ball']['y']] 96 | players = {} 97 | for i in range(1, 12): 98 | players[i] = [index[str(i)]['x'], index[str(i)]['y']] 99 | indexes.append(FormationIndexData(ball, players)) 100 | return indexes, roles 101 | 102 | @staticmethod 103 | def is_json(lines: list[str]) -> bool: 104 | """Checks if the lines represent a JSON file.""" 105 | return '{' in lines[0] 106 | 107 | @staticmethod 108 | def get_method(lines: list[str]) -> FormationType: 109 | """Gets the formation method from the JSON data.""" 110 | text = ''.join(lines) 111 | data = json.loads(text) 112 | if data['method'] == 'Static': 113 | return FormationType.Static 114 | return FormationType.DelaunayTriangulation2 115 | 116 | # Factory class to get the appropriate formation file reader 117 | class FormationFileReaderFactory: 118 | def get_reader(self, lines: list[str]) -> tuple[IFormationFileReader, FormationType]: 119 | """Returns the appropriate formation file reader based on the file content.""" 120 | if JsonFormationFileReader.is_json(lines): 121 | return JsonFormationFileReader(), JsonFormationFileReader.get_method(lines) 122 | if 'Static' in lines[0]: 123 | return OldStaticFormationFileReader(), FormationType.Static 124 | return OldDelaunayFormationFileReader(), FormationType.DelaunayTriangulation2 125 | 126 | def read_file(self, path: str) -> tuple[list[FormationIndexData], dict[int, PlayerRole], FormationType]: 127 | """Reads the formation file and returns formation index data, player roles, and formation type.""" 128 | with open(path, 'r') as file: 129 | lines = file.readlines() 130 | reader, formation_type = self.get_reader(lines) 131 | indexes, roles = reader.read_file(lines) 132 | return indexes, roles, formation_type 133 | -------------------------------------------------------------------------------- /src/strategy/formation_strategy.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | from src.interfaces.IPositionStrategy import IPositionStrategy 3 | from src.strategy.formation_file import * 4 | from src.interfaces.IAgent import IAgent 5 | from src.strategy.player_role import PlayerRole, RoleName, RoleType, RoleSide 6 | from enum import Enum 7 | from pyrusgeom.soccer_math import * 8 | from service_pb2 import * 9 | import logging 10 | from src.strategy.formation import Formation 11 | 12 | if TYPE_CHECKING: 13 | from src.sample_player_agent import SamplePlayerAgent 14 | 15 | class Situation(Enum): 16 | """ 17 | Enum class representing different game situations in a 2D soccer simulation. 18 | """ 19 | OurSetPlay_Situation = 0 20 | OppSetPlay_Situation = 1 21 | Defense_Situation = 2 22 | Offense_Situation = 3 23 | PenaltyKick_Situation = 4 24 | 25 | class FormationStrategy(IPositionStrategy): 26 | def __init__(self, logger: logging.Logger): 27 | self.logger = logger 28 | self.formations: dict[str, Formation] = {} 29 | self._poses: dict[int, Vector2D] = {i: Vector2D(0, 0) for i in range(11)} 30 | self.current_situation = Situation.Offense_Situation 31 | self.selected_formation_name = '4-3-3-cyrus-base' 32 | 33 | self._read_formations() 34 | self.current_formation_file: FormationFile = self._get_current_formation().offense_formation 35 | 36 | def _read_formations(self): 37 | """ 38 | Read and initialize formations from configuration files. 39 | """ 40 | self.formations['4-3-3'] = Formation('src/formations/4-3-3', self.logger) 41 | self.formations['4-3-3-cyrus-base'] = Formation('src/formations/4-3-3-cyrus-base', self.logger) 42 | self.formations['4-3-3-helios-base'] = Formation('src/formations/4-3-3-helios-base', self.logger) 43 | 44 | def _get_current_formation(self) -> Formation: 45 | """ 46 | Get the current formation based on the selected formation name. 47 | 48 | Returns: 49 | Formation: The current formation instance. 50 | """ 51 | return self.formations[self.selected_formation_name] 52 | 53 | def _set_formation(self, wm: WorldModel): 54 | """ 55 | Set the formation based on the world model state. 56 | 57 | Args: 58 | wm (WorldModel): The current world model. 59 | """ 60 | self.selected_formation_name = '4-3-3-cyrus-base' # Example: '4-3-3', '4-3-3-cyrus-base', '4-3-3-helios-base' 61 | 62 | def update(self, agent: 'SamplePlayerAgent'): 63 | """ 64 | Update the strategy based on the agent's world model. 65 | 66 | Args: 67 | agent (SamplePlayerAgent): The agent instance. 68 | """ 69 | logger = agent.logger 70 | logger.debug('---- update strategy ----') 71 | 72 | wm: WorldModel = agent.wm 73 | self._set_formation(wm) 74 | 75 | tm_min = wm.intercept_table.first_teammate_reach_steps 76 | opp_min = wm.intercept_table.first_opponent_reach_steps 77 | self_min = wm.intercept_table.self_reach_steps 78 | all_min = min(tm_min, opp_min, self_min) 79 | current_ball_pos = Vector2D(wm.ball.position.x, wm.ball.position.y) 80 | current_ball_vel = Vector2D(wm.ball.velocity.x, wm.ball.velocity.y) 81 | ball_pos = inertia_n_step_point(current_ball_pos, current_ball_vel, all_min, 0.96) # todo: use server param ball decay 82 | 83 | self._determine_situation(wm, ball_pos, tm_min, opp_min, self_min) 84 | self._update_formation_file(ball_pos) 85 | self._adjust_positions(wm) 86 | 87 | logger.debug(f'{self._poses=}') 88 | 89 | def _determine_situation(self, wm: WorldModel, ball_pos: Vector2D, tm_min: int, opp_min: int, self_min: int): 90 | """ 91 | Determine the current game situation based on the world model. 92 | 93 | Args: 94 | wm (WorldModel): The current world model. 95 | ball_pos (Vector2D): The current ball position. 96 | tm_min (int): First teammate reach steps. 97 | opp_min (int): First opponent reach steps. 98 | self_min (int): Self reach steps. 99 | """ 100 | if wm.game_mode_type is GameModeType.PlayOn: 101 | thr = 0 102 | if ball_pos.x() > 0: 103 | thr += 1 104 | if wm.self.uniform_number > 6: 105 | thr += 1 106 | if min(tm_min, self_min) < opp_min + thr: 107 | self.current_situation = Situation.Offense_Situation 108 | else: 109 | self.current_situation = Situation.Defense_Situation 110 | elif wm.game_mode_type is GameModeType.PenaltyKick_: 111 | self.current_situation = Situation.PenaltyKick_Situation 112 | elif wm.game_mode_type is not GameModeType.PlayOn and wm.game_mode_side is wm.our_side: 113 | self.current_situation = Situation.OurSetPlay_Situation 114 | else: 115 | self.current_situation = Situation.OppSetPlay_Situation 116 | 117 | def _update_formation_file(self, ball_pos: Vector2D): 118 | """ 119 | Update the current formation file based on the current situation. 120 | 121 | Args: 122 | ball_pos (Vector2D): The current ball position. 123 | """ 124 | if self.current_situation is Situation.Offense_Situation: 125 | self.current_formation_file = self._get_current_formation().offense_formation 126 | elif self.current_situation is Situation.Defense_Situation: 127 | self.current_formation_file = self._get_current_formation().defense_formation 128 | elif self.current_situation is Situation.OurSetPlay_Situation: 129 | self.current_formation_file = self._get_current_formation().setplay_our_formation 130 | elif self.current_situation is Situation.OppSetPlay_Situation: 131 | self.current_formation_file = self._get_current_formation().setplay_opp_formation 132 | elif self.current_situation is Situation.PenaltyKick_Situation: 133 | self.current_formation_file = self._get_current_formation().before_kick_off_formation 134 | else: 135 | self.current_formation_file = self._get_current_formation().before_kick_off_formation 136 | 137 | self.current_formation_file.update(ball_pos) 138 | self._poses = self.current_formation_file.get_poses() 139 | 140 | def _adjust_positions(self, wm: WorldModel): 141 | """ 142 | Adjust player positions based on the game mode and offside line. 143 | 144 | Args: 145 | wm (WorldModel): The current world model. 146 | """ 147 | if wm.game_mode_type in [GameModeType.BeforeKickOff, GameModeType.AfterGoal_]: 148 | for pos in self._poses.values(): 149 | pos._x = min(pos.x(), -0.5) 150 | else: 151 | offside_line = wm.offside_line_x 152 | for pos in self._poses.values(): 153 | pos._x = min(pos.x(), offside_line - 0.5) 154 | 155 | def get_position(self, uniform_number, agent: IAgent = None) -> Vector2D: 156 | """ 157 | Get the position for a given uniform number. 158 | 159 | Args: 160 | uniform_number (int): The uniform number of the player. 161 | agent (IAgent, optional): The agent instance. 162 | 163 | Returns: 164 | Vector2D: The position of the player. 165 | """ 166 | return self._poses[uniform_number] 167 | 168 | def get_role_name(self, uniform_number) -> RoleName: 169 | """ 170 | Get the role name for a given uniform number. 171 | 172 | Args: 173 | uniform_number (int): The uniform number of the player. 174 | 175 | Returns: 176 | RoleName: The role name of the player. 177 | """ 178 | return self.current_formation_file.get_role(uniform_number).name 179 | 180 | def get_role_type(self, uniform_number) -> RoleType: 181 | """ 182 | Get the role type for a given uniform number. 183 | 184 | Args: 185 | uniform_number (int): The uniform number of the player. 186 | 187 | Returns: 188 | RoleType: The role type of the player. 189 | """ 190 | return self.current_formation_file.get_role(uniform_number).type 191 | 192 | def get_role_side(self, uniform_number) -> RoleSide: 193 | """ 194 | Get the role side for a given uniform number. 195 | 196 | Args: 197 | uniform_number (int): The uniform number of the player. 198 | 199 | Returns: 200 | RoleSide: The role side of the player. 201 | """ 202 | return self.current_formation_file.get_role(uniform_number).side 203 | 204 | def get_role_pair(self, uniform_number) -> int: 205 | """ 206 | Get the role pair for a given uniform number. 207 | 208 | Args: 209 | uniform_number (int): The uniform number of the player. 210 | 211 | Returns: 212 | int: The role pair of the player. 213 | """ 214 | return self.current_formation_file.get_role(uniform_number).pair 215 | 216 | def get_role(self, uniform_number) -> PlayerRole: 217 | """ 218 | Get the role for a given uniform number. 219 | 220 | Args: 221 | uniform_number (int): The uniform number of the player. 222 | 223 | Returns: 224 | PlayerRole: The role of the player. 225 | """ 226 | return self.current_formation_file.get_role(uniform_number) 227 | 228 | def get_offside_line(self) -> float: 229 | """ 230 | Get the offside line position. 231 | 232 | Returns: 233 | float: The offside line position. 234 | """ 235 | home_poses_x = [pos.x() for pos in self._poses.values()] 236 | home_poses_x.sort() 237 | if len(home_poses_x) > 1: 238 | return home_poses_x[1] 239 | elif len(home_poses_x) == 1: 240 | return home_poses_x[0] 241 | else: 242 | return 0.0 -------------------------------------------------------------------------------- /src/strategy/player_role.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class RoleName(Enum): 4 | """ 5 | Enum class representing different role names for players. 6 | """ 7 | Goalie = "Goalie" 8 | CenterBack = "CenterBack" 9 | SideBack = "SideBack" 10 | DefensiveHalf = "DefensiveHalf" 11 | OffensiveHalf = "OffensiveHalf" 12 | SideForward = "SideForward" 13 | CenterForward = "CenterForward" 14 | Unknown = "Unknown" 15 | 16 | class RoleType(Enum): 17 | """ 18 | Enum class representing different role types for players. 19 | """ 20 | G = "G" 21 | DF = "DF" 22 | MF = "MF" 23 | FW = "FW" 24 | Unknown = "Unknown" 25 | 26 | class RoleSide(Enum): 27 | """ 28 | Enum class representing different role sides for players. 29 | """ 30 | L = "L" 31 | R = "R" 32 | C = "C" 33 | Unknown = "Unknown" 34 | 35 | class PlayerRole: 36 | """ 37 | Class representing a player's role in the game. 38 | 39 | Attributes: 40 | _name (RoleName): The name of the role. 41 | _type (RoleType): The type of the role. 42 | _side (RoleSide): The side of the role. 43 | _pair (int): The pair number of the role. 44 | """ 45 | def __init__(self, name: str, type: str, side: str, pair: int): 46 | """ 47 | Initialize a PlayerRole instance. 48 | 49 | Args: 50 | name (str): The name of the role. 51 | type (str): The type of the role. 52 | side (str): The side of the role. 53 | pair (int): The pair number of the role. 54 | """ 55 | self._name: RoleName = RoleName(name) if name in RoleName.__members__ else RoleName.Unknown 56 | self._type: RoleType = RoleType(type) if type in RoleType.__members__ else RoleType.Unknown 57 | self._side: RoleSide = RoleSide(side) if side in RoleSide.__members__ else RoleSide.Unknown 58 | self._pair: int = pair 59 | 60 | @property 61 | def name(self) -> RoleName: 62 | """ 63 | Get the name of the role. 64 | 65 | Returns: 66 | RoleName: The name of the role. 67 | """ 68 | return self._name 69 | 70 | @property 71 | def type(self) -> RoleType: 72 | """ 73 | Get the type of the role. 74 | 75 | Returns: 76 | RoleType: The type of the role. 77 | """ 78 | return self._type 79 | 80 | @property 81 | def side(self) -> RoleSide: 82 | """ 83 | Get the side of the role. 84 | 85 | Returns: 86 | RoleSide: The side of the role. 87 | """ 88 | return self._side 89 | 90 | @property 91 | def pair(self) -> int: 92 | """ 93 | Get the pair number of the role. 94 | 95 | Returns: 96 | int: The pair number of the role. 97 | """ 98 | return self._pair -------------------------------------------------------------------------------- /src/strategy/starter_strategy.py: -------------------------------------------------------------------------------- 1 | from src.interfaces.IPositionStrategy import IPositionStrategy 2 | from src.strategy.formation_file import * 3 | from src.interfaces.IAgent import IAgent 4 | from src.strategy.player_role import PlayerRole, RoleName, RoleType, RoleSide 5 | from pyrusgeom.soccer_math import * 6 | from service_pb2 import * 7 | import logging 8 | from src.utils.tools import Tools 9 | 10 | 11 | class StarterStrategy(IPositionStrategy): 12 | def __init__(self, logger: logging.Logger): 13 | self.logger = logger 14 | self._poses: dict[int, Vector2D] = {i: Vector2D(0, 0) for i in range(12)} 15 | 16 | def update(self, agent: IAgent): 17 | """ 18 | Update the strategy based on the agent's world model. 19 | 20 | Args: 21 | agent (IAgent): The agent instance. 22 | """ 23 | wm = agent.wm 24 | if wm.game_mode_type in [GameModeType.BeforeKickOff, GameModeType.AfterGoal_]: 25 | self._set_kickoff_positions() 26 | else: 27 | ball_pos = self._predict_ball_position(agent) 28 | self._set_dynamic_positions(ball_pos, agent) 29 | self._apply_offside_rule(agent) 30 | 31 | def get_position(self, uniform_number: int, agent: IAgent) -> Vector2D: 32 | """ 33 | Get the position for a given uniform number. 34 | 35 | Args: 36 | uniform_number (int): The uniform number of the player. 37 | agent (IAgent): The agent instance. 38 | 39 | Returns: 40 | Vector2D: The position of the player. 41 | """ 42 | return self._poses[uniform_number] 43 | 44 | def _set_kickoff_positions(self): 45 | """ 46 | Set static positions for players during kickoff. 47 | """ 48 | self._poses = { 49 | 1: Vector2D(-52, 0), 50 | 2: Vector2D(-48, -2), 51 | 3: Vector2D(-48, 2), 52 | 4: Vector2D(-48, -10), 53 | 5: Vector2D(-48, 10), 54 | 6: Vector2D(-48, -20), 55 | 7: Vector2D(-48, 20), 56 | 8: Vector2D(-30, -20), 57 | 9: Vector2D(-30, 0), 58 | 10: Vector2D(-30, 20), 59 | 11: Vector2D(-17, 0), 60 | } 61 | 62 | def _predict_ball_position(self, agent: IAgent) -> Vector2D: 63 | """ 64 | Predict the ball position based on the agent's world model. 65 | 66 | Args: 67 | agent (IAgent): The agent instance. 68 | 69 | Returns: 70 | Vector2D: The predicted ball position. 71 | """ 72 | wm = agent.wm 73 | ball_step = 0 74 | if wm.game_mode_type == GameModeType.PlayOn or wm.game_mode_type == GameModeType.GoalKick_: 75 | ball_step = min(1000, wm.intercept_table.first_teammate_reach_steps) 76 | ball_step = min(ball_step, wm.intercept_table.first_opponent_reach_steps) 77 | ball_step = min(ball_step, wm.intercept_table.self_reach_steps) 78 | 79 | real_ball_pos = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.position) 80 | real_ball_vel = Tools.convert_rpc_vector2d_to_vector2d(wm.ball.velocity) 81 | return inertia_n_step_point(real_ball_pos, real_ball_vel, ball_step, agent.server_params.ball_decay) 82 | 83 | def _set_dynamic_positions(self, ball_pos: Vector2D, agent: IAgent): 84 | """ 85 | Set dynamic positions for players based on the ball position. 86 | 87 | Args: 88 | ball_pos (Vector2D): The predicted ball position. 89 | agent (IAgent): The agent instance. 90 | """ 91 | min_x_rectangle = [0, -52, -52, -52, -52, -52, -30, -30, -30, 0, 0, 0] 92 | max_x_rectangle = [0, -48, -10, -10, -10, -10, 15, 15, 15, 50, 50, 50] 93 | min_y_rectangle = [0, -2, -20, -10, -30, 10, -20, -30, 0, -30, 0, -20] 94 | max_y_rectangle = [0, 2, 10, 20, -10, 30, 20, 0, 30, 0, 30, 20] 95 | 96 | for i in range(1, 12): 97 | xx_rectangle = max_x_rectangle[i] - min_x_rectangle[i] 98 | yy_rectangle = max_y_rectangle[i] - min_y_rectangle[i] 99 | x_ball = ball_pos.x() + agent.server_params.pitch_half_length 100 | x_ball /= agent.server_params.pitch_half_length * 2 101 | y_ball = ball_pos.y() + 34 102 | y_ball /= agent.server_params.pitch_half_width * 2 103 | x_pos = xx_rectangle * x_ball + min_x_rectangle[i] 104 | y_pos = yy_rectangle * y_ball + min_y_rectangle[i] 105 | self._poses[i] = Vector2D(x_pos, y_pos) 106 | 107 | def _apply_offside_rule(self, agent: IAgent): 108 | """ 109 | Apply the offside rule to adjust player positions. 110 | 111 | Args: 112 | agent (IAgent): The agent instance. 113 | wm: The world model instance. 114 | """ 115 | wm = agent.wm 116 | if agent.server_params.use_offside: 117 | max_x = wm.offside_line_x 118 | if agent.server_params.kickoff_offside and wm.game_mode_type in [GameModeType.BeforeKickOff, GameModeType.AfterGoal_]: 119 | max_x = 0 120 | else: 121 | mate_step = wm.intercept_table.first_teammate_reach_steps 122 | if mate_step < 50: 123 | trap_pos = inertia_n_step_point(Vector2D(wm.ball.position.x, wm.ball.position.y), Vector2D(wm.ball.velocity.x, wm.ball.velocity.y), mate_step, agent.server_params.ball_decay) 124 | max_x = max(max_x, trap_pos.x()) 125 | max_x -= 1.0 126 | 127 | for unum in range(1, 12): 128 | self._poses[unum].set_x(min(self._poses[unum].x(), max_x)) 129 | 130 | s_recover_mode = False 131 | 132 | @staticmethod 133 | def get_normal_dash_power(agent: IAgent) -> float: 134 | """ 135 | Get the normal dash power for the agent. 136 | 137 | Args: 138 | agent (IAgent): The agent instance. 139 | 140 | Returns: 141 | float: The normal dash power. 142 | """ 143 | wm = agent.wm 144 | if wm.self.stamina_capacity == 0: 145 | return min(agent.server_params.max_dash_power, wm.self.stamina + agent.player_types[agent.wm.self.id].extra_stamina) 146 | self_min = wm.intercept_table.self_reach_steps 147 | mate_min = wm.intercept_table.first_teammate_reach_steps 148 | opp_min = wm.intercept_table.first_opponent_reach_steps 149 | # Check recover mode 150 | if wm.self.stamina_capacity == 0: 151 | StarterStrategy.s_recover_mode = False 152 | elif wm.self.stamina < agent.server_params.stamina_max * 0.5: 153 | StarterStrategy.s_recover_mode = True 154 | elif wm.self.stamina > agent.server_params.stamina_max * 0.7: 155 | StarterStrategy.s_recover_mode = False 156 | # Initialize dash_power with max_dash_power 157 | dash_power = agent.server_params.max_dash_power 158 | my_inc = (agent.player_types[agent.wm.self.id].stamina_inc_max * wm.self.recovery) 159 | if wm.our_defense_line_x > wm.self.position.x and wm.ball.position.x < wm.our_defense_line_x + 20.0: 160 | dash_power = agent.server_params.max_dash_power 161 | elif StarterStrategy.s_recover_mode: 162 | dash_power = my_inc - 25.0 # Preferred recovery value 163 | dash_power = max(dash_power, 0) 164 | elif mate_min <= 1 and wm.ball.dist_from_self < 20.0: 165 | dash_power = min(my_inc * 1.1, agent.server_params.max_dash_power) 166 | elif wm.self.position.x > wm.offside_line_x: 167 | dash_power = agent.server_params.max_dash_power 168 | elif wm.ball.position.x > 25.0 and wm.ball.position.x > wm.self.position.x + 10.0 and self_min < opp_min - 6 and mate_min < opp_min - 6: 169 | dash_power = bound(agent.server_params.max_dash_power * 0.1, my_inc * 0.5, agent.server_params.max_dash_power) 170 | else: 171 | dash_power = min(my_inc * 1.7, agent.server_params.max_dash_power) 172 | return dash_power 173 | 174 | def get_offside_line(self): 175 | """ 176 | Get the offside line position. 177 | 178 | Returns: 179 | float: The offside line position. 180 | """ 181 | home_poses_x = sorted(pos.x() for pos in self._poses.values() if pos is not None) 182 | return home_poses_x[1] if len(home_poses_x) > 1 else (home_poses_x[0] if home_poses_x else 0.0) 183 | -------------------------------------------------------------------------------- /src/utils/memory.py: -------------------------------------------------------------------------------- 1 | 2 | class Memory: 3 | def __init__(self): 4 | self.dash_distance_tables: dict[int, list[float]] = {} -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python_env=.venv/bin/activate 4 | source $python_env 5 | 6 | # Set the options, all the options are passed as environment variables 7 | options=$@ 8 | 9 | echo "Options: $options" 10 | 11 | # Function to handle termination 12 | cleanup() { 13 | echo "Terminating all start_agent.py processes..." 14 | sleep 2 15 | # kill $pid 16 | exit 0 17 | } 18 | 19 | # Trap SIGTERM and SIGINT signals 20 | trap cleanup SIGTERM SIGINT 21 | 22 | run_command="python3 start.py" 23 | 24 | $run_command $options & 25 | pid=$! 26 | 27 | # Wait for the process to finish 28 | wait $pid -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CLSFramework/py2d/70993cbc4355e6b9e2dfb96e25f3af8a74994ce3/utils/__init__.py -------------------------------------------------------------------------------- /utils/logger_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | 5 | def setup_logger(name, log_dir, console_level=logging.INFO, file_level=logging.DEBUG, console_format_str=None, file_format_str=None): 6 | """ 7 | Set up a logger that writes to both a file and the console, with different formats and levels. 8 | 9 | :param name: Name of the logger. 10 | :param log_file: Path to the log file. 11 | :param console_level: Logging level for the console output. 12 | :param file_level: Logging level for the file output. 13 | :return: Configured logger. 14 | """ 15 | have_console_handler = console_level is not None 16 | have_file_handler = file_level is not None 17 | 18 | if have_file_handler and not os.path.exists(log_dir): 19 | os.makedirs(log_dir) 20 | 21 | log_file = os.path.join(log_dir, f'{name}.log') 22 | 23 | # Create a custom logger 24 | logger = logging.getLogger(name) 25 | 26 | if not logger.hasHandlers(): 27 | logger.setLevel(logging.DEBUG) # Set the overall logger level to the lowest level you want to capture 28 | # Console handler 29 | if have_console_handler: 30 | console_handler = logging.StreamHandler() # For console output 31 | console_handler.setLevel(console_level) 32 | if not console_format_str: 33 | console_format_str = '%(name)s - %(levelname)s - %(message)s' 34 | console_format = logging.Formatter(console_format_str) 35 | console_handler.setFormatter(console_format) 36 | logger.addHandler(console_handler) 37 | 38 | # File handler 39 | if have_file_handler: 40 | file_handler = logging.FileHandler(log_file) # For file output 41 | file_handler.setLevel(file_level) 42 | if not file_format_str: 43 | file_format_str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 44 | file_format = logging.Formatter(file_format_str) 45 | file_handler.setFormatter(file_format) 46 | logger.addHandler(file_handler) 47 | 48 | return logger 49 | --------------------------------------------------------------------------------