@@ -85,13 +85,22 @@ def set_world_state(self, world_state: WorldState):
8585
8686 @override
8787 def set_tactics (self , blue_tactics = {}, yellow_tactics = {}):
88- """Override AI tactics, remapping the friendly team's simulated robot ids
89- to the robot ids that are actually available on the field.
88+ """Override AI tactics, remapping each team's simulated robot ids to the
89+ robot ids that are actually available on the field.
9090 """
9191 if self .is_yellow_friendly :
92- yellow_tactics = self ._map_tactics_to_field_ids (yellow_tactics )
92+ blue_sim_to_field_id = self .enemy_sim_to_field_id
93+ yellow_sim_to_field_id = self .friendly_sim_to_field_id
9394 else :
94- blue_tactics = self ._map_tactics_to_field_ids (blue_tactics )
95+ blue_sim_to_field_id = self .friendly_sim_to_field_id
96+ yellow_sim_to_field_id = self .enemy_sim_to_field_id
97+
98+ blue_tactics = self ._map_tactics_to_field_ids (
99+ blue_tactics , blue_sim_to_field_id , "blue"
100+ )
101+ yellow_tactics = self ._map_tactics_to_field_ids (
102+ yellow_tactics , yellow_sim_to_field_id , "yellow"
103+ )
95104
96105 super ().set_tactics (blue_tactics = blue_tactics , yellow_tactics = yellow_tactics )
97106
@@ -205,16 +214,21 @@ def _survey_field_robots(self):
205214 # survey field for available robot ids
206215 survey_start_time = time .time ()
207216 self .friendly_robot_ids_field = []
217+ self .enemy_robot_ids_field = []
208218 while time .time () - survey_start_time < WORLD_BUFFER_TIMEOUT :
209219 try :
210220 world = self .world_buffer .get (block = True , timeout = 0.1 )
211221 self .initial_world = world
212222 self .friendly_robot_ids_field = [
213223 robot .id for robot in world .friendly_team .team_robots
214224 ]
225+ self .enemy_robot_ids_field = [
226+ robot .id for robot in world .enemy_team .team_robots
227+ ]
215228
216229 if len (self .friendly_robot_ids_field ) > 0 :
217230 logger .info (f"friendly team ids { self .friendly_robot_ids_field } " )
231+ logger .info (f"enemy team ids { self .enemy_robot_ids_field } " )
218232 break
219233 except queue .Empty :
220234 continue
@@ -225,20 +239,41 @@ def _survey_field_robots(self):
225239 # Simulated tests create robots with contiguous ids (0, 1, 2, ...), but
226240 # the robots physically on the field may have arbitrary ids and there may
227241 # be fewer of them. Map each simulated id to an available field id, in
228- # ascending order, so tactics written against simulated ids are sent to
229- # the robots that are actually present. e.g. field robots [4, 5] map
230- # simulated id 0 -> 4 and 1 -> 5; field robots [0, 1, 5] map 0 -> 0,
231- # 1 -> 1 and 2 -> 5.
232- self .sim_to_field_robot_id = {
233- sim_id : field_id
234- for sim_id , field_id in enumerate (sorted (self .friendly_robot_ids_field ))
235- }
236- # Inverse mapping, used to relabel the world's field ids back to the
242+ # ascending order and per team, so tactics and validations written against
243+ # simulated ids refer to the robots that are actually present. e.g. field
244+ # robots [4, 5] map simulated id 0 -> 4 and 1 -> 5; field robots [0, 1, 5]
245+ # map 0 -> 0, 1 -> 1 and 2 -> 5.
246+ self .friendly_sim_to_field_id = self ._build_sim_to_field_mapping (
247+ self .friendly_robot_ids_field
248+ )
249+ self .enemy_sim_to_field_id = self ._build_sim_to_field_mapping (
250+ self .enemy_robot_ids_field
251+ )
252+ # Inverse mappings, used to relabel the world's field ids back to the
237253 # simulated ids that validations are written against.
238- self .field_to_sim_robot_id = {
239- field_id : sim_id for sim_id , field_id in self .sim_to_field_robot_id .items ()
254+ self .friendly_field_to_sim_id = {
255+ field_id : sim_id
256+ for sim_id , field_id in self .friendly_sim_to_field_id .items ()
257+ }
258+ self .enemy_field_to_sim_id = {
259+ field_id : sim_id for sim_id , field_id in self .enemy_sim_to_field_id .items ()
240260 }
241- logger .info (f"simulated id to field id mapping { self .sim_to_field_robot_id } " )
261+ logger .info (
262+ f"friendly simulated id to field id mapping { self .friendly_sim_to_field_id } "
263+ )
264+ logger .info (
265+ f"enemy simulated id to field id mapping { self .enemy_sim_to_field_id } "
266+ )
267+
268+ @staticmethod
269+ def _build_sim_to_field_mapping (field_ids ):
270+ """Map contiguous simulated robot ids (0, 1, 2, ...) onto the available
271+ field ids for a team, in ascending order.
272+
273+ :param field_ids: the robot ids present on the field for a team
274+ :return: dict of simulated_robot_id -> field_robot_id
275+ """
276+ return {sim_id : field_id for sim_id , field_id in enumerate (sorted (field_ids ))}
242277
243278 def _create_move_tactics (self , robot_states ):
244279 """Create a MoveTactic for each robot to drive it to its world state.
@@ -259,58 +294,89 @@ def _create_move_tactics(self, robot_states):
259294 for robot_id , robot_state in robot_states .items ()
260295 }
261296
262- def _map_tactics_to_field_ids (self , tactics ):
297+ def _map_tactics_to_field_ids (self , tactics , sim_to_field_id , team_name ):
263298 """Remap a tactics dict keyed by simulated robot ids to the robot ids
264299 available on the field. Tactics for simulated ids with no available
265300 field robot are dropped with a warning.
266301
267302 :param tactics: None or dict of simulated_robot_id -> tactic
303+ :param sim_to_field_id: the team's simulated-id -> field-id mapping
304+ :param team_name: the team colour, used in warnings
268305 :return: None if tactics is None, else dict of field_robot_id -> tactic
269306 """
270307 if tactics is None :
271308 return None
272309
273310 mapped_tactics = {}
274311 for sim_id , tactic in tactics .items ():
275- if sim_id not in self . sim_to_field_robot_id :
312+ if sim_id not in sim_to_field_id :
276313 logger .warning (
277- f"no field robot available for simulated robot id { sim_id } ; "
278- " skipping its tactic"
314+ f"no { team_name } field robot available for simulated robot id "
315+ f" { sim_id } ; skipping its tactic"
279316 )
280317 continue
281- mapped_tactics [self . sim_to_field_robot_id [sim_id ]] = tactic
318+ mapped_tactics [sim_to_field_id [sim_id ]] = tactic
282319
283320 return mapped_tactics
284321
322+ @staticmethod
323+ def _remap_robot_state_ids (robot_states , sim_to_field_id ):
324+ """Remap a world-state robot map keyed by simulated ids to field ids,
325+ dropping any simulated id with no available field robot.
326+
327+ :param robot_states: map of simulated_robot_id -> RobotState
328+ :param sim_to_field_id: the team's simulated-id -> field-id mapping
329+ :return: dict of field_robot_id -> RobotState
330+ """
331+ return {
332+ sim_to_field_id [sim_id ]: robot_state
333+ for sim_id , robot_state in robot_states .items ()
334+ if sim_id in sim_to_field_id
335+ }
336+
285337 def _relabel_world_to_sim_ids (self , world : World ) -> World :
286- """Return a copy of the world with friendly robot ids translated from
338+ """Return a copy of the world with both teams' robot ids translated from
287339 field ids back to the simulated ids the test uses.
288340
289341 Validations match robots by the contiguous simulated ids (0, 1, 2, ...),
290342 but the world reports the robots' actual field ids. Relabeling the world
291343 here lets every id-based validation find the correct robot without each
292- validation needing to know about the mapping. Friendly robots with no
293- mapping (e.g. extras not part of the test) keep their field id.
344+ validation needing to know about the mapping. Robots with no mapping
345+ (e.g. extras not part of the test) keep their field id.
294346
295347 :param world: The World proto reported by the field full system
296- :return: a copy of the world with friendly robot ids in simulated-id space
348+ :return: a copy of the world with robot ids in simulated-id space
297349 """
298350 relabeled_world = World ()
299351 relabeled_world .CopyFrom (world )
300- for robot in relabeled_world .friendly_team .team_robots :
301- if robot .id in self .field_to_sim_robot_id :
302- robot .id = self .field_to_sim_robot_id [robot .id ]
352+ self ._relabel_team_to_sim_ids (
353+ relabeled_world .friendly_team , self .friendly_field_to_sim_id
354+ )
355+ self ._relabel_team_to_sim_ids (
356+ relabeled_world .enemy_team , self .enemy_field_to_sim_id
357+ )
303358 return relabeled_world
304359
360+ @staticmethod
361+ def _relabel_team_to_sim_ids (team , field_to_sim_id ):
362+ """Relabel a team's robot ids in place from field ids to simulated ids.
363+
364+ :param team: a Team proto whose robots' ids to relabel
365+ :param field_to_sim_id: the team's field-id -> simulated-id mapping
366+ """
367+ for robot in team .team_robots :
368+ if robot .id in field_to_sim_id :
369+ robot .id = field_to_sim_id [robot .id ]
370+
305371 def _wait_for_robots_at_world_state (self , world_state : WorldState ):
306372 """Block until every robot in the world state is within
307373 ROBOT_SETUP_TOLERANCE_M of its target position.
308374
309375 Robot positions are read from the friendly full system's World in the
310376 same fashion as _survey_field_robots. The friendly team maps to the
311377 friendly-coloured robots in the world state, and the enemy team to the
312- other colour. The friendly targets are remapped from simulated ids to
313- the field ids they were assigned to in set_tactics.
378+ other colour. Each team's targets are remapped from simulated ids to the
379+ field ids they were assigned to in set_tactics.
314380
315381 :param world_state: The WorldState proto with the desired robot states
316382 :raises Exception: if the robots do not reach their targets within
@@ -323,13 +389,14 @@ def _wait_for_robots_at_world_state(self, world_state: WorldState):
323389 friendly_targets = world_state .blue_robots
324390 enemy_targets = world_state .yellow_robots
325391
326- # Remap the friendly targets onto the field ids they were commanded to,
392+ # Remap each team's targets onto the field ids they were commanded to,
327393 # dropping any simulated id that has no available field robot
328- friendly_targets = {
329- self .sim_to_field_robot_id [sim_id ]: robot_state
330- for sim_id , robot_state in friendly_targets .items ()
331- if sim_id in self .sim_to_field_robot_id
332- }
394+ friendly_targets = self ._remap_robot_state_ids (
395+ friendly_targets , self .friendly_sim_to_field_id
396+ )
397+ enemy_targets = self ._remap_robot_state_ids (
398+ enemy_targets , self .enemy_sim_to_field_id
399+ )
333400
334401 logger .info ("waiting for robots to reach their target positions" )
335402 wait_start_time = time .time ()
0 commit comments