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