class Observer():
    def __init__(self):
        self.game_interface = GameInterface(get_logger("observer"))
        self.game_interface.load_interface()
        self.game_interface.wait_until_loaded()
        self.game_interface.set_game_state(GameState(console_commands=[f'Set WorldInfo WorldGravityZ {WORLD_GRAVITY}']))
        self.main()

    def main(self):
        # Create packet
        packet = GameTickPacket()
        last_game_time = 0.0

        while True:
            # Update packet
            self.game_interface.update_live_data_packet(packet)
            game_time = packet.game_info.seconds_elapsed

            # Sleep until a new packet is received.
            if last_game_time == game_time:
                time.sleep(0.001)

            else:
                if packet.game_info.is_round_active:

                    # Renders ball prediction.
                    ball_prediction = BallPrediction()
                    self.game_interface.update_ball_prediction(ball_prediction)
                    self.game_interface.renderer.begin_rendering()
                    self.game_interface.renderer.draw_polyline_3d([step.physics.location for step in ball_prediction.slices[::10]], self.game_interface.renderer.cyan())
                    self.game_interface.renderer.end_rendering()

                    car_states = {}

                    for i in range(packet.num_cars):
                        car = packet.game_cars[i]

                        if STICK != 0 and car.has_wheel_contact:
                            # Makes cars stick by adding a velocity downwards.

                            pitch = car.physics.rotation.pitch
                            yaw = car.physics.rotation.yaw
                            roll = car.physics.rotation.roll

                            CP = cos(pitch) 
                            SP = sin(pitch)
                            CY = cos(yaw)
                            SY = sin(yaw)
                            CR = cos(roll)
                            SR = sin(roll)

                            x = car.physics.velocity.x - STICK*(-CR * CY * SP - SR * SY)
                            y = car.physics.velocity.y - STICK*(-CR * SY * SP + SR * CY)
                            z = car.physics.velocity.z - STICK*(CP * CR)

                            car_states.update({i: CarState(physics=Physics(velocity=Vector3(x,y,z)))})

                    
                    if packet.game_info.is_kickoff_pause and round(packet.game_ball.physics.location.z) != KICKOFF_BALL_HEIGHT:
                        # Places the ball in the air on kickoff.
                        ball_state = BallState(Physics(location=Vector3(z=KICKOFF_BALL_HEIGHT), velocity=Vector3(0,0,0)))

                        if len(car_states) > 0:
                            game_state = GameState(ball=ball_state, cars=car_states)
                        else:
                            game_state = GameState(ball=ball_state)

                    else:
                        if len(car_states) > 0:
                            game_state = GameState(cars=car_states)
                        else:
                            game_state = GameState()

                    # Uses state setting to set the game state.
                    self.game_interface.set_game_state(game_state)
Beispiel #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)
Beispiel #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
Beispiel #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
Beispiel #5
0
class Hivemind(BotHelperProcess):
    # TODO Maybe use __slots__ for better performance?

    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)
        self.logger = get_logger('Hivemind')
        self.game_interface = GameInterface(self.logger)
        self.running_indices = set()

    def try_receive_agent_metadata(self):
        while True:  # will exit on queue.Empty
            try:
                single_agent_metadata: AgentMetadata = self.metadata_queue.get(
                    timeout=0.1)
                self.running_indices.add(single_agent_metadata.index)
            except queue.Empty:
                return
            except Exception as ex:
                self.logger.error(ex)

    def start(self):
        """Runs once, sets up the hivemind and its agents."""
        # Prints stuff into the console.
        self.logger.info("Hivemind A C T I V A T E D")
        message = random.choice([
            "Breaking the meta",
            "Welcoming r0bbi3",
            "Annoying chip by reinventing the wheel",
            "Actually texting her",
            "Banning anime",
            "Killing that guy",
            "Trying to pronounce jeroen",
            "Getting banned by Redox",
            "Becomind a mod",
        ])
        self.logger.info(message)

        # Loads game interface.
        self.game_interface.load_interface()

        # Wait a moment for all agents to have a chance to start up and send metadata.
        time.sleep(1)
        self.try_receive_agent_metadata()

        # Runs the game loop where the hivemind will spend the rest of its time.
        self.game_loop()

    def game_loop(self):
        """The main game loop. This is where your hivemind code goes."""

        # Setting up rate limiter.
        rate_limit = rate_limiter.RateLimiter(120)

        # Setting up data.
        field_info = FieldInfoPacket()
        self.game_interface.update_field_info_packet(field_info)
        packet = GameTickPacket()
        self.game_interface.update_live_data_packet(packet)

        data.setup(self, packet, field_info, self.running_indices)

        self.ball.predict = BallPrediction()
        # https://github.com/RLBot/RLBotPythonExample/wiki/Ball-Path-Prediction

        # MAIN LOOP:
        while True:
            # Updating the game packet from the game.
            self.game_interface.update_live_data_packet(packet)

            # Processing packet.
            data.process(self, packet)

            # Ball prediction.
            self.game_interface.update_ball_prediction(self.ball.predict)

            # Planning.
            brain.plan(self)

            # Rendering.
            self.render_debug(self.game_interface.renderer)

            # For each drone under the hivemind's control, do something.
            for drone in self.drones:

                # The controls are reset each frame.
                drone.ctrl = PlayerInput(
                )  # Basically the same as SimpleControllerState().

                # Role execution.
                if drone.role is not None:
                    drone.role.execute(self, drone)

                    self.render_role(self.game_interface.renderer, drone)

                # Send the controls to the bots.
                self.game_interface.update_player_input(
                    drone.ctrl, drone.index)

            # Rate limit sleep.
            rate_limit.acquire()

    def render_debug(hive, rndr):
        """Debug rendering for all manner of things.
        
        Arguments:
            hive {Hivemind} -- The hivemind.
            rndr {?} -- The renderer.
        """
        # Rendering Ball prediction.
        locations = [
            step.physics.location for step in hive.ball.predict.slices
        ]
        rndr.begin_rendering('ball prediction')
        rndr.draw_polyline_3d(locations, rndr.pink())
        rndr.end_rendering()

    def render_role(hive, rndr, drone):
        """Renders roles above the drones.
        
        Arguments:
            hive {Hivemind} -- The hivemind.
            rndr {?} -- The renderer.
            drone {Drone} -- The drone who's role is being rendered.
        """
        # Rendering role names above drones.
        above = drone.pos + a3l([0, 0, 100])
        rndr.begin_rendering(f'role_{hive.team}_{drone.index}')
        rndr.draw_string_3d(above, 1, 1, drone.role.name, rndr.cyan())
        rndr.end_rendering()
Beispiel #6
0
class ExampleHivemind(BotHelperProcess):

    # Some terminology:
    # hivemind = the process which controls the drones.
    # drone = a bot under the hivemind's control.

    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.
        # Call this something unique so people can differentiate between hiveminds.
        self.logger = get_logger('Example Hivemind')

        # 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)

        # Running indices is a set of bot indices
        # which requested this hivemind with the same key.
        self.running_indices = set()

    def try_receive_agent_metadata(self):
        """Adds all drones with the correct key to our set of running indices."""
        while True:  # will exit on queue.Empty
            try:
                # Adds drone indices to running_indices.
                single_agent_metadata: AgentMetadata = self.metadata_queue.get(
                    timeout=0.1)
                self.running_indices.add(single_agent_metadata.index)
            except queue.Empty:
                return
            except Exception as ex:
                self.logger.error(ex)

    def start(self):
        """Runs once, sets up the hivemind and its agents."""
        # Prints an activation message into the console.
        # This let's you know that the process is up and running.
        self.logger.info("Hello World!")

        # Loads game interface.
        self.game_interface.load_interface()

        # Wait a moment for all agents to have a chance to start up and send metadata.
        self.logger.info("Snoozing for 3 seconds; give me a moment.")
        time.sleep(3)
        self.try_receive_agent_metadata()

        # This is how you access field info.
        # First create the initialise the object...
        field_info = FieldInfoPacket()
        # Then update it.
        self.game_interface.update_field_info_packet(field_info)

        # Same goes for the packet, but that is
        # also updated in the main loop every tick.
        packet = GameTickPacket()
        self.game_interface.update_live_data_packet(packet)
        # Ball prediction works the same. Check the main loop.

        # Create a Ball object for the ball that holds its information.
        self.ball = Ball()

        # Create a Drone object for every drone that holds its information.
        self.drones = []
        for index in range(packet.num_cars):
            if index in self.running_indices:
                self.drones.append(Drone(index, packet.game_cars[index].team))

        # Other attribute initialisation.
        self.state = State.SETUP
        self.pinch_target = None

        # Runs the game loop where the hivemind will spend the rest of its time.
        self.game_loop()

    def game_loop(self):
        """The main game loop. This is where your hivemind code goes."""

        # Creating packet and ball prediction objects which will be updated every tick.
        packet = GameTickPacket()
        ball_prediction = BallPrediction()

        # Nicknames the renderer to shorten code.
        draw = self.game_interface.renderer

        # MAIN LOOP:
        while True:

            previous_packet = packet

            # Updating the game tick packet.
            self.game_interface.update_live_data_packet(packet)

            # Checking if packet is new, otherwise sleep.
            if previous_packet.game_info.seconds_elapsed == packet.game_info.seconds_elapsed:
                time.sleep(0.001)

            else:
                # Begins rendering at the start of the loop; makes life easier.
                # https://discordapp.com/channels/348658686962696195/446761380654219264/610879527089864737
                draw.begin_rendering(f'Hivemind{self.drones[0].team}')

                # PRE-PROCESSING:

                # Updates the ball prediction.
                self.game_interface.update_ball_prediction(ball_prediction)

                # Processing ball data.
                self.ball.pos = a3v(packet.game_ball.physics.location)

                # Processing drone data.
                for drone in self.drones:
                    drone.pos = a3v(packet.game_cars[drone.index].physics.location)
                    drone.rot = a3r(packet.game_cars[drone.index].physics.rotation)
                    drone.vel = a3v(packet.game_cars[drone.index].physics.velocity)
                    drone.boost = packet.game_cars[drone.index].boost
                    drone.orient_m = orient_matrix(drone.rot)

                    # Reset ctrl every tick.
                    # PlayerInput is practically identical to SimpleControllerState.
                    drone.ctrl = PlayerInput()

                # Game time.
                game_time = packet.game_info.seconds_elapsed

                # Example Team Pinches (2 bots only)
                # There's nothing stopping you from doing it with more ;) Give it a shot!
                if len(self.drones) == 2:

                    # Sorts the drones left to right. (More understandble code below)
                    #right_to_left_drones = sorted(self.drones, key=lambda drone: drone.pos[0]*team_sign(drone.team))

                    # Finds the right and left drones.
                    sign = team_sign(self.drones[0].team)
                    if self.drones[0].pos[0]*sign <= self.drones[1].pos[0]*sign:
                        right = self.drones[0]
                        left = self.drones[1]
                    else:
                        right = self.drones[1]
                        left = self.drones[0]

                    # Bots get boost and go to wait positions.
                    if self.state == State.SETUP:

                        # Some guide positions.
                        right_boost = a3l([-3072.0, -4096.0, 71.1])*sign
                        right_wait = a3l([-1792.0, -4184.0, 71.1])*sign
                        # Making use of symmetry
                        left_boost = right_boost * a3l([-1, 1, 1])
                        left_wait = right_wait * a3l([-1, 1, 1])

                        # First get boost and then go to wait position.
                        if right.boost < 100:
                            slow_to_pos(right, right_boost)
                        else:
                            slow_to_pos(right, right_wait)

                        if left.boost < 100:
                            slow_to_pos(left, left_boost)
                        else:
                            slow_to_pos(left, left_wait)

                        # If both bots are in wait position, switch to WAIT state.
                        if np.linalg.norm(right.pos-right_wait) + np.linalg.norm(left.pos-left_wait) < 200:
                            self.state = State.WAIT

                    # Bots try to face the ball, waiting for perfect moment to team pinch.
                    elif self.state == State.WAIT:

                        # Each drone should try to face the ball.
                        for drone in self.drones:
                            turn_to_pos(drone, self.ball.pos, game_time)

                        # Filters out all the predictions where the ball is too far off the ground.
                        # Result is a list of tuples of positions and time.
                        filtered_prediction = [(a3v(step.physics.location), step.game_seconds)
                                            for step in ball_prediction.slices if step.physics.location.z < 100]

                        if len(filtered_prediction) > 0:
                            # Turns the predition into a numpy array for fast vectorized calculations.
                            filtered_prediction = np.array(filtered_prediction)

                            # Gets the vectors from the drones to the ball prediction.
                            positions = np.vstack(filtered_prediction[:, 0])
                            right_to_prediction = positions - right.pos
                            left_to_prediction = positions - left.pos

                            # Calculates the distances.
                            # Cool blog post about einsum: http://ajcr.net/Basic-guide-to-einsum/
                            right_distances = np.sqrt(
                                np.einsum('ij,ij->i', right_to_prediction, right_to_prediction))
                            left_distances = np.sqrt(
                                np.einsum('ij,ij->i', left_to_prediction, left_to_prediction))

                            # Filters out the predictions which are too close or too far.
                            good_distances = (CLOSEST <= right_distances) & (FARTHEST >= right_distances) & (
                                CLOSEST <= left_distances) & (FARTHEST >= left_distances)
                            valid_targets = filtered_prediction[good_distances]

                            if len(valid_targets) > 0:
                                # Getting the remaining distances after filter.
                                right_distances = right_distances[good_distances]
                                left_distances = left_distances[good_distances]

                                # Getting time estimates to go that distance. (Assuming boosting, and going in a straight line.)
                                # https://www.geogebra.org/m/nnsat4pj
                                right_times = right_distances**0.55 / 41.53
                                right_times[right_distances > 2177.25] = 1/2300 * \
                                    right_distances[right_distances > 2177.25] + 0.70337
                                right_times += game_time + TIME_BUFFER

                                left_times = left_distances**0.55 / 41.53
                                left_times[left_distances > 2177.25] = 1/2300 * \
                                    left_distances[left_distances > 2177.25] + 0.70337
                                left_times += game_time + TIME_BUFFER

                                # Filters out the predictions which we can't get to.
                                good_times = (valid_targets[:, 1] > right_times) & (
                                    valid_targets[:, 1] > left_times)
                                valid_targets = valid_targets[good_times]

                                # To avoid flukes or anomalies, check that the ball is valid for at least 10 steps.
                                # Not exact because there could be more bounce spots but good enough to avoid flukes.
                                if len(valid_targets) > 10:
                                    # Select first valid target.
                                    self.pinch_target = valid_targets[0]
                                    # Reset drone's going attribute.
                                    right.going = False
                                    left.going = False
                                    # Set the state to PINCH.
                                    self.state = State.PINCH

                        # Rendering number of positions viable after each condition.
                        draw.draw_string_2d(
                            10, 70, 2, 2, f'Good height: {len(filtered_prediction)}', draw.white())
                        draw.draw_string_2d(
                            10, 100, 2, 2, f'Good distance: {len(valid_targets)}', draw.white())
                        # Render circles to show distances.
                        draw.draw_polyline_3d(make_circle(
                            CLOSEST, right.pos, 20), draw.cyan())
                        draw.draw_polyline_3d(make_circle(
                            CLOSEST, left.pos, 20), draw.cyan())
                        draw.draw_polyline_3d(make_circle(
                            FARTHEST, right.pos, 30), draw.pink())
                        draw.draw_polyline_3d(make_circle(
                            FARTHEST, left.pos, 30), draw.pink())

                    elif self.state == State.PINCH:

                        # Checks if the ball has been hit recently.
                        if packet.game_ball.latest_touch.time_seconds + 0.1 > game_time:
                            self.pinch_target = None
                            self.state = State.SETUP

                        elif self.pinch_target is not None:
                            if not right.going:
                                # Get the distance to the target.
                                right_distance = np.linalg.norm(
                                    self.pinch_target[0] - right.pos)
                                # Get a time estimate
                                right_time = right_distance**0.55 / \
                                    41.53 if right_distance <= 2177.25 else 1/2300 * right_distance + 0.70337

                                # Waits until time is right to go. Otherwise turns to face the target position.
                                if game_time + right_time + TIME_ERROR >= self.pinch_target[1]:
                                    right.going = True
                                else:
                                    turn_to_pos(
                                        right, self.pinch_target[0], game_time)

                            else:
                                fast_to_pos(right, self.pinch_target[0])

                            # Same for left.
                            if not left.going:
                                left_distance = np.linalg.norm(
                                    self.pinch_target[0] - left.pos)
                                left_time = left_distance**0.55 / \
                                    41.53 if left_distance <= 2177.25 else 1/2300 * left_distance + 0.70337
                                if game_time + left_time + TIME_ERROR >= self.pinch_target[1]:
                                    left.going = True
                                else:
                                    turn_to_pos(
                                        left, self.pinch_target[0], game_time)
                            else:
                                fast_to_pos(left, self.pinch_target[0])

                            # Some rendering.
                            draw.draw_string_2d(
                                10, 70, 2, 2, f'Right going: {right.going}', draw.white())
                            draw.draw_string_2d(
                                10, 100, 2, 2, f'Left going: {left.going}', draw.white())

                else:
                    draw.draw_string_2d(
                        10, 10, 2, 2, 'This example version has only been coded for 2 HiveBots.', draw.red())

                # Use this to send the drone inputs to the drones.
                for drone in self.drones:
                    self.game_interface.update_player_input(
                        drone.ctrl, drone.index)

                # Some example rendering:
                draw.draw_string_2d(10, 10, 3, 3, f'{self.state}', draw.pink())
                # Renders ball prediction
                path = [step.physics.location for step in ball_prediction.slices[::10]]
                draw.draw_polyline_3d(path, draw.pink())

                # Renders drone indices.
                for drone in self.drones:
                    draw.draw_string_3d(drone.pos, 1, 1, str(
                        drone.index), draw.white())

                # Team pinch info.
                if self.pinch_target is not None:
                    draw.draw_rect_3d(
                        self.pinch_target[0], 10, 10, True, draw.red())

                # Ending rendering.
                draw.end_rendering()
Beispiel #7
0
class Hivemind(BotHelperProcess):
    def __init__(self, agent_metadata_queue, quit_event, options):
        super().__init__(agent_metadata_queue, quit_event, options)
        self.logger = get_logger('Hivemind')
        self.game_interface = GameInterface(self.logger)
        self.running_indices = set()

    def try_receive_agent_metadata(self):
        while True:  # will exit on queue.Empty
            try:
                single_agent_metadata: AgentMetadata = self.metadata_queue.get(
                    timeout=0.1)
                self.running_indices.add(single_agent_metadata.index)
            except queue.Empty:
                return
            except Exception as ex:
                self.logger.error(ex)

    def start(self):
        """Runs once, sets up the hivemind and its agents."""
        # Prints stuff into the console.
        self.logger.info("Hivemind A C T I V A T E D")
        self.logger.info("Breaking the meta")
        self.logger.info("Welcoming @r0bbi3#0269")

        # Loads game interface.
        self.game_interface.load_interface()

        # Wait a moment for all agents to have a chance to start up and send metadata.
        time.sleep(1)
        self.try_receive_agent_metadata()

        # Runs the game loop where the hivemind will spend the rest of its time.
        self.game_loop()

    def game_loop(self):
        """The main game loop. This is where your hivemind code goes."""

        # Setting up rate limiter.
        rate_limit = rate_limiter.RateLimiter(120)

        # Setting up data.
        field_info = FieldInfoPacket()
        self.game_interface.update_field_info_packet(field_info)
        packet = GameTickPacket()
        self.game_interface.update_live_data_packet(packet)

        data.setup(self, packet, field_info, self.running_indices)

        self.ball.predict = BallPrediction()

        # MAIN LOOP:
        while True:
            # Updating the game packet from the game.
            self.game_interface.update_live_data_packet(packet)

            # Processing packet.
            data.process(self, packet)

            # Ball prediction.
            self.game_interface.update_ball_prediction(self.ball.predict)

            brain.think(self)

            for drone in self.drones:
                drone.ctrl = PlayerInput()
                if drone.role is not None:
                    drone.role.execute(self, drone)
                self.game_interface.update_player_input(
                    drone.ctrl, drone.index)

            self.draw_debug()

            # Rate limit sleep.
            rate_limit.acquire()

    def draw_debug(self):
        self.game_interface.renderer.begin_rendering()
        path = [
            a3v(step.physics.location) for step in self.ball.predict.slices
        ]
        self.game_interface.renderer.draw_polyline_3d(
            path, self.game_interface.renderer.pink())

        for drone in self.drones:
            if drone.role is not None:
                self.game_interface.renderer.draw_string_3d(
                    drone.pos, 1, 1, drone.role.name,
                    self.game_interface.renderer.white())
        self.game_interface.renderer.end_rendering()