11from __future__ import annotations
2+ import itertools
23
34import queue
45import random
910from typing import Any
1011
1112from proto .import_all_protos import *
13+ from proto .message_translation .tbots_protobuf import create_default_world_state
1214from proto .ssl_gc_common_pb2 import Team as SslTeam
1315from software .networking .ssl_proto_communication import *
1416import software .python_bindings as tbots_cpp
2123 ThreadSafeCircularBuffer ,
2224)
2325from software .thunderscope .util import is_current_platform_macos
26+ from software .py_constants import DIV_B_NUM_ROBOTS
2427
2528logger = logging .getLogger (__name__ )
26- import itertools
2729
2830
2931class Gamecontroller :
@@ -68,6 +70,7 @@ def __init__(
6870 self .latest_world = None
6971 self .blue_removed_robot_ids = queue .Queue ()
7072 self .yellow_removed_robot_ids = queue .Queue ()
73+ self .processed_event_ids = set ()
7174
7275 def get_referee_port (self ) -> int :
7376 """Sometimes, the port that we are using changes depending on context.
@@ -178,6 +181,31 @@ def __update_robot_count(
178181 except queue .Empty :
179182 return
180183
184+ def __automate_referee (self , referee : Referee ) -> None :
185+ """Automate referee events by handling possible goals, ball placement failures,
186+ game stage changes, and starting new game.
187+
188+ :param referee: the referee protobuf message
189+ """
190+ if referee .stage_time_left < 0 :
191+ self .__handle_game_stage_change (referee .stage )
192+
193+ possible_goal_events = self .__find_unprocessed_events (
194+ referee , GameEvent .Type .POSSIBLE_GOAL
195+ )
196+ placement_failed_events = self .__find_unprocessed_events (
197+ referee , GameEvent .Type .PLACEMENT_FAILED
198+ )
199+
200+ if len (possible_goal_events ) >= 1 :
201+ self .__handle_possible_goal (possible_goal_events [0 ].possible_goal .by_team )
202+ self .processed_event_ids .add (possible_goal_events [0 ].id )
203+
204+ if len (placement_failed_events ) >= 2 :
205+ self .__handle_placement_failed ()
206+ self .processed_event_ids .add (placement_failed_events [0 ].id )
207+ self .processed_event_ids .add (placement_failed_events [1 ].id )
208+
181209 def handle_referee (self , referee : Referee ) -> None :
182210 """Updates the world state based on the referee message
183211 :param referee: the referee protobuf message
@@ -192,6 +220,9 @@ def handle_referee(self, referee: Referee) -> None:
192220 block = False , return_cached = True
193221 )
194222
223+ # TODO (#3633): only automate referee events in record_stats mode
224+ self .__automate_referee (referee )
225+
195226 max_allowed_bots_yellow : int = referee .yellow .max_allowed_bots
196227 max_allowed_bots_blue : int = referee .blue .max_allowed_bots
197228 # Ignore if nothing needs to be updated
@@ -235,6 +266,87 @@ def handle_referee(self, referee: Referee) -> None:
235266 # Send out updated world state
236267 self .simulator_proto_unix_io .send_proto (WorldState , world_state )
237268
269+ def __handle_game_stage_change (self , game_stage : Referee .Stage ) -> None :
270+ """Handle game stage change by advancing to the next half once first half is over
271+ and starting a new game once the second half is over.
272+
273+ :param game_stage: The current game stage from the referee message
274+ """
275+ if game_stage == Referee .Stage .NORMAL_FIRST_HALF :
276+ # skip to pre second half
277+ new_stage = Referee .Stage .NORMAL_SECOND_HALF_PRE
278+ else :
279+ # reset game
280+ new_stage = Referee .Stage .NORMAL_FIRST_HALF_PRE
281+
282+ # reset game state
283+ self .simulator_proto_unix_io .send_proto (
284+ WorldState , create_default_world_state (num_robots = DIV_B_NUM_ROBOTS )
285+ )
286+
287+ ci_input = CiInput (timestamp = int (time .time_ns ()))
288+ api_input = Input ()
289+ change = Change ()
290+ change .change_stage_change .new_stage = new_stage
291+ api_input .change .CopyFrom (change )
292+ ci_input .api_inputs .append (api_input )
293+
294+ self .send_ci_input (ci_input )
295+
296+ self .send_gc_command (gc_command = Command .Type .STOP , team = SslTeam .UNKNOWN )
297+
298+ def __find_unprocessed_events (
299+ self , referee : Referee , event_type : GameEvent .Type
300+ ) -> list [GameEvent ]:
301+ """Find unprocessed game events of the specified type.
302+
303+ :param referee: the referee protobuf message
304+ :param event_type: the type of game event to search for
305+ :return: list of unprocessed events of the given type
306+ """
307+ return [
308+ event
309+ for event in referee .game_events
310+ if event .type == event_type and event .id not in self .processed_event_ids
311+ ]
312+
313+ def __handle_possible_goal (self , scoring_team : SslTeam ) -> None :
314+ """Handle a possible goal event by approving the goal and starting ball placement.
315+
316+ :param scoring_team: the team that scored the goal
317+ """
318+ ci_input = CiInput (timestamp = int (time .time_ns ()))
319+ api_input = Input ()
320+ change = Change ()
321+
322+ # Send goal game event to resolve the possible goal
323+ game_event = GameEvent (
324+ type = GameEvent .Type .GOAL ,
325+ origin = [
326+ "Majority"
327+ ], # Required or else ssl-gamecontroller will convert game event to proposal
328+ goal = GameEvent .Goal (by_team = scoring_team ),
329+ )
330+
331+ change .add_game_event_change .game_event .CopyFrom (game_event )
332+ api_input .change .CopyFrom (change )
333+ ci_input .api_inputs .append (api_input )
334+ self .send_ci_input (ci_input )
335+
336+ # reset the robot and ball positions
337+ self .simulator_proto_unix_io .send_proto (
338+ WorldState , create_default_world_state (num_robots = DIV_B_NUM_ROBOTS )
339+ )
340+ self .send_gc_command (Command .Type .STOP , SslTeam .UNKNOWN )
341+
342+ def __handle_placement_failed (self ) -> None :
343+ """Handle a placement failed event by moving the ball to the placement point."""
344+ world_state = WorldState ()
345+ world_state .ball_state .global_position .CopyFrom (
346+ self .latest_world .game_state .ball_placement_point
347+ )
348+ self .simulator_proto_unix_io .send_proto (WorldState , world_state )
349+
238350 def is_valid_port (self , port ):
239351 """Determine whether or not a given port is valid
240352
0 commit comments