Skip to content

Commit 6a4d4ae

Browse files
committed
Map enemy robots as well
1 parent fd66e6e commit 6a4d4ae

1 file changed

Lines changed: 103 additions & 36 deletions

File tree

src/software/gameplay_tests/field_test_runner.py

Lines changed: 103 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)