Example #1
0
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)
Example #2
0
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)
Example #3
0
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
Example #4
0
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