def _grade_exercise(game_interface: GameInterface, ex: Exercise, seed: int) -> Result: grade = None game_tick_packet = GameTickPacket( ) # We want to do a deep copy for game inputs so people don't mess with em # Run until the Exercise finishes. while grade is None: # Read from game data shared memory game_interface.fresh_live_data_packet(game_tick_packet, 100, 99) try: grade = ex.on_tick(game_tick_packet) ex.render(game_interface.renderer) except Exception as e: return Result( ex, seed, FailDueToExerciseException(e, traceback.format_exc())) return Result(ex, seed, grade)
class PythonHivemind(BotHelperProcess): # Terminology: # hivemind - main process controlling the drones. # drone - a bot under the hivemind's control. # Path to the executable. NOT USED BY PYTHON HIVEMINDS! exec_path = None def __init__(self, agent_metadata_queue, quit_event, options): super().__init__(agent_metadata_queue, quit_event, options) # Sets up the logger. The string is the name of your hivemind. # This is configured inside your config file under hivemind_name. self.logger = get_logger(options['name']) # Give a warning if exec_path was given. if self.exec_path is not None: self.logger.warning( "An exec_path was given, but this is a PythonHivemind, and so it is ignored." ) self.logger.warning( " Try subclassing SubprocessHivemind if you want to use an executable." ) # The game interface is how you get access to things # like ball prediction, the game tick packet, or rendering. self.game_interface = GameInterface(self.logger) # drone_indices is a set of bot indices # which requested this hivemind with the same key. self.drone_indices = set() def try_receive_agent_metadata(self): """Adds all drones with the correct key to our set of running indices.""" while not self.metadata_queue.empty(): # Tries to get the next agent from the queue. single_agent_metadata: AgentMetadata = self.metadata_queue.get( timeout=1.0) # Adds the index to the drone_indices. self.drone_indices.add(single_agent_metadata.index) def start(self): """Starts the BotHelperProcess - Hivemind.""" # Prints an activation message into the console. # This lets you know that the process is up and running. self.logger.debug("Hello, world!") # Loads game interface. self.game_interface.load_interface() # Collect drone indices that requested a helper process with our key. self.logger.info("Collecting drones; give me a moment.") self.try_receive_agent_metadata() self.logger.info("Ready to go!") # Runs the game loop where the hivemind will spend the rest of its time. self.__game_loop() def __game_loop(self): """ The bot hivemind will stay in this loop for the whole game. This is where the initialize_hive and get_outputs functions are called. """ # Creating ball prediction and field info objects to later update in wrapper methods. self._ball_prediction = BallPrediction() self._field_info = FieldInfoPacket() self.game_interface.update_field_info_packet(self._field_info) # Wrapper for renderer. self.renderer: RenderingManager = self.game_interface.renderer # Create packet object. packet = GameTickPacket() # Uses one of the drone indices as a key. key = next(iter(self.drone_indices)) self.game_interface.fresh_live_data_packet(packet, 20, key) # Initialization step for your hivemind. self.initialize_hive(packet) while not self.quit_event.is_set(): try: # Updating the packet. self.game_interface.fresh_live_data_packet(packet, 20, key) # Get outputs from hivemind for each bot. # Outputs are expected to be a Dict[int, PlayerInput] outputs = self.get_outputs(packet) if outputs is None: self.logger.error("No outputs were returned.") self.logger.error( " Try putting `return {i: PlayerInput() for i in self.drone_indices}`" ) self.logger.error(" in `get_outputs()` to troubleshoot.") continue if len(outputs) < len(self.drone_indices): self.logger.error("Not enough outputs were given.") elif len(outputs) > len(self.drone_indices): self.logger.error("Too many outputs were given.") # Send the drone inputs to the drones. for index in outputs: if index not in self.drone_indices: self.logger.error( "Tried to send output to bot index not in drone_indices." ) continue output = outputs[index] self.game_interface.update_player_input(output, index) except Exception: traceback.print_exc() # Override these methods: def initialize_hive(self, packet: GameTickPacket) -> None: """ Override this method if you want to have an initialization step for your hivemind. """ pass def get_outputs(self, packet: GameTickPacket) -> Dict[int, PlayerInput]: """Where all the logic of your hivemind gets its input and returns its outputs for each drone. Use self.drone_indices to access the set of bot indices your hivemind controls. Arguments: packet {GameTickPacket} -- see https://github.com/RLBot/RLBot/wiki/Input-and-Output-Data-(current) Returns: Dict[int, PlayerInput] -- A dictionary with drone indices as keys and corresponding PlayerInputs as values. """ return {index: PlayerInput() for index in self.drone_indices} # Wrapper methods to make them the same as if you were making a normal python bot: def get_ball_prediction_struct(self) -> BallPrediction: self.game_interface.update_ball_prediction(self._ball_prediction) return self._ball_prediction def get_field_info(self) -> FieldInfoPacket: # Field info does not need to be updated. return self._field_info def get_match_settings(self) -> MatchSettings: return self.game_interface.get_match_settings() def set_game_state(self, game_state: GameState) -> None: self.game_interface.set_game_state(game_state)
class BaseScript(RLBotRunnable): """ A convenience class for building scripts on top of. It is NOT required to use this when configuring a script. """ matchcomms_root: Optional[URL] = None def __init__(self, name): super().__init__(name) self.logger = get_logger(name) self.__key = hash("BaseScript:" + name) self.game_tick_packet = GameTickPacket() self.ball_prediction = BallPrediction() self.game_interface = GameInterface(self.logger) self.game_interface.load_interface() fake_index = random.randint( 100, 10000) # a number unlikely to conflict with bots or other scripts self.renderer = self.game_interface.renderer.get_rendering_manager( bot_index=fake_index, bot_team=2) # Get matchcomms root if provided as a command line argument. try: pos = sys.argv.index("--matchcomms-url") potential_url = urlparse(sys.argv[pos + 1]) except (ValueError, IndexError): # Missing the command line argument. pass else: if potential_url.scheme == "ws" and potential_url.netloc: self.matchcomms_root = potential_url else: raise ValueError("The matchcomms url is invalid") def get_game_tick_packet(self): """Gets the latest game tick packet immediately, without blocking.""" return self.game_interface.update_live_data_packet( self.game_tick_packet) def wait_game_tick_packet(self): """A blocking call which waits for the next new game tick packet and returns as soon as it's available. Will wait for a maximum of 30 milliseconds before giving up and returning the packet the framework already has. This is suitable for low-latency update loops.""" return self.game_interface.fresh_live_data_packet( self.game_tick_packet, 30, self.__key) def get_field_info(self): """Gets the information about the field. This does not change during a match so it only needs to be called once after the everything is loaded.""" return self.game_interface.get_field_info() def set_game_state(self, game_state: GameState): self.game_interface.set_game_state(game_state) def get_ball_prediction_struct(self) -> BallPrediction: """Fetches a prediction of where the ball will go during the next few seconds.""" return self.game_interface.update_ball_prediction(self.ball_prediction) def get_match_settings(self) -> MatchSettings: """Gets the current match settings in flatbuffer format. Useful for determining map, game mode, mutator settings, etc.""" return self.game_interface.get_match_settings() # Information about @classmethod: https://docs.python.org/3/library/functions.html#classmethod @classmethod def base_create_agent_configurations(cls) -> ConfigObject: """ This is used when initializing agent config via builder pattern. It also calls `create_agent_configurations` that can be used by BaseAgent subclasses for custom configs. :return: Returns an instance of a ConfigObject object. """ config = super().base_create_agent_configurations() location_config = config.get_header(LOCATIONS_HEADER) location_config.add_value(SCRIPT_FILE_KEY, str, description="Script's python file.") cls.create_agent_configurations(config) return config # Same as in BaseAgent. _matchcomms: Optional[MatchcommsClient] = None @property def matchcomms(self) -> MatchcommsClient: """ Gets a client to send and recieve messages to other participants in the match (e.g. bots, trainer) """ if self.matchcomms_root is None: raise ValueError( "Your bot tried to access matchcomms but matchcomms_root is None! This " "may be due to manually running a bot in standalone mode without passing the " "--matchcomms-url argument. That's a fine thing to do, and if it's safe to " "ignore matchcomms in your case then go ahead and wrap your matchcomms access " "in a try-except, or do a check first for whether matchcomms_root is None." ) if self._matchcomms is None: self._matchcomms = MatchcommsClient(self.matchcomms_root) return self._matchcomms
class BaseScript(RLBotRunnable): """ A convenience class for building scripts on top of. It is NOT required to use this when configuring a script. """ def __init__(self, name): super().__init__(name) self.logger = get_logger(name) self.__key = hash("BaseScript:" + name) self.game_tick_packet = GameTickPacket() self.ball_prediction = BallPrediction() self.game_interface = GameInterface(self.logger) self.game_interface.load_interface() fake_index = random.randint( 100, 10000) # a number unlikely to conflict with bots or other scripts self.renderer = self.game_interface.renderer.get_rendering_manager( bot_index=fake_index, bot_team=2) def get_game_tick_packet(self): """Gets the latest game tick packet immediately, without blocking.""" return self.game_interface.update_live_data_packet( self.game_tick_packet) def wait_game_tick_packet(self): """A blocking call which waits for the next new game tick packet and returns as soon as it's available. Will wait for a maximum of 30 milliseconds before giving up and returning the packet the framework already has. This is suitable for low-latency update loops.""" return self.game_interface.fresh_live_data_packet( self.game_tick_packet, 30, self.__key) def get_field_info(self): """Gets the information about the field. This does not change during a match so it only needs to be called once after the everything is loaded.""" return self.game_interface.get_field_info() def set_game_state(self, game_state: GameState): self.game_interface.set_game_state(game_state) def get_ball_prediction_struct(self) -> BallPrediction: """Fetches a prediction of where the ball will go during the next few seconds.""" return self.game_interface.update_ball_prediction(self.ball_prediction) def get_match_settings(self) -> MatchSettings: """Gets the current match settings in flatbuffer format. Useful for determining map, game mode, mutator settings, etc.""" return self.game_interface.get_match_settings() # Information about @classmethod: https://docs.python.org/3/library/functions.html#classmethod @classmethod def base_create_agent_configurations(cls) -> ConfigObject: """ This is used when initializing agent config via builder pattern. It also calls `create_agent_configurations` that can be used by BaseAgent subclasses for custom configs. :return: Returns an instance of a ConfigObject object. """ config = super().base_create_agent_configurations() location_config = config.get_header(LOCATIONS_HEADER) location_config.add_value(SCRIPT_FILE_KEY, str, description="Script's python file.") cls.create_agent_configurations(config) return config