def create_builder(test_subject_id=None): world_size = [24, 28] bg_color = "#ebebeb" wall_color = "#adadad" tdp = "tdp_decision_support_explained" timestamp = setTimestamp() config_path = os.path.join(os.path.realpath("mhc"), 'cases', 'experiment_3_tdp_dss.json') config = json.load(open(config_path)) print("Loaded config file:", config_path) np.random.seed(config['random_seed']) print("Set random seed:", config['random_seed']) # Create our builder instance builder = WorldBuilder(shape=world_size, run_matrx_api=True, run_matrx_visualizer=False, visualization_bg_clr=bg_color, visualization_bg_img="", tick_duration=config['world']['tick_duration'], simulation_goal=AllPatientsTriaged( config['patients']['max_patients'])) ################################################################# # Rooms ################################################################ add_mhc_rooms(builder, config, world_size, wall_color) ################################################################# # Beds ################################################################ add_mhc_chairs_beds(builder) ################################################################# # Other objects ################################################################ add_mhc_extras(builder, config) # add settings object builder.add_object(location=[0, 0], is_traversable=True, is_movable=False, name="Settings", visualize_size=0, tdp=tdp, visualize_your_vs_patient_robots=True, start_timestamp=timestamp, show_agent_predictions=True, trial_completed=False, config=config, end_message=config['world']['trial_end_message'], customizable_properties=['trial_completed']) ################################################################# # Loggers ################################################################ log_folder = tdp + "_" + timestamp if test_subject_id is not None: log_folder = f"test_subject_{test_subject_id}_{log_folder}" builder.add_logger( LogPatientStatus, save_path=os.path.join('Results', log_folder), file_name_prefix="patient_status", ) builder.add_logger(LogNewPatients, save_path=os.path.join('Results', log_folder), file_name_prefix="new_patients") builder.add_logger(LogTriageDecision, save_path=os.path.join('Results', log_folder), file_name_prefix="triage_decisions") ################################################################# # Actors ################################################################ # create the patient planner (god agent) that spawns patients over time, as described in the config builder.add_agent(location=[0, 0], is_traversable=True, is_movable=False, agent_brain=PatientPlanner(config=config, tdp=tdp), name="Patient planner", visualize_size=0) # add the test subject: the human doctor builder.add_human_agent(location=config['human_doctor']['location'], is_traversable=True, is_movable=False, agent=HumanDoctor(), name="human_doctor", visualize_size=0) # add the hospital manager (god agent) that takes care of removing deceased patients builder.add_agent(location=[0, 0], is_traversable=True, is_movable=False, agent_brain=HospitalManager(), name="hospital_manager", visualize_size=0) # Return the builder return builder
class BW4TWorld: ''' Creates a single GridWorld to be run. The idea was that this extends GridWorld, however it seems we need to create these through the WorldBuilder and therefore this approach seems not possible. Instead, this only supports the 'run' function and internally creates the gridworld using WorldBuilder. ''' def __init__(self, agents: List[dict], worldsettings: dict = DEFAULT_WORLDSETTINGS): ''' @param agents a list like [ {'name':'agent1', 'botclass':PatrollingAgent, 'settings':{'slowdown':3}}, {'name':'human1', 'botclass':Human, 'settings':{'slowdown':1}} ] Names must all be unique. Check BW4TBrain for more on the agents specification. ''' self._worldsettings = worldsettings self._agents = agents np.random.seed(worldsettings['random_seed']) world_size = self.world_size() # Create the goal goal = CollectionGoal(worldsettings['deadline']) # Create our world builder self._builder = WorldBuilder( shape=world_size, tick_duration=worldsettings['tick_duration'], random_seed=worldsettings['random_seed'], run_matrx_api=worldsettings['run_matrx_api'], run_matrx_visualizer=worldsettings['run_matrx_visualizer'], verbose=worldsettings['verbose'], simulation_goal=goal) # Hacky? But this is apparently the way to do this. # Also note the extra underscore, maybe that's a buig in matrx? self._builder.api_info['_matrx_paused'] = worldsettings['matrx_paused'] # Add the world bounds (not needed, as agents cannot 'walk off' the grid, but for visual effects) self._builder.add_room(top_left_location=(0, 0), width=world_size[0], height=world_size[1], name="world_bounds") room_locations = self._addRooms() self._addBlocks(room_locations) self._addDropOffZones(world_size) # Add the agents and human agents to the top row of the world self._addAgents() media_folder = os.path.dirname( os.path.join(os.path.realpath(__file__), "media")) self._builder.startup(media_folder=media_folder) self._builder.add_logger(BW4TLogger, save_path='.') self._gridworld = self._builder.worlds(nr_of_worlds=1).__next__() def run(self): ''' run the world till termination @return this ''' self._gridworld.run(self._builder.api_info) return self def getLogger(self) -> BW4TLogger: ''' @return the logger. We assume there is only 1: BW4TLogger ''' return self._gridworld._GridWorld__loggers[0] def world_size(self): ''' returns (width,height) (number of tiles) ''' worldsettings = self._worldsettings nr_room_rows = np.ceil(worldsettings['nr_rooms'] / worldsettings['rooms_per_row']) # calculate the total width world_width = max( worldsettings['rooms_per_row'] * worldsettings['room_size'][0] + 2 * worldsettings['hallway_space'], (worldsettings['nr_drop_zones'] + 1) * worldsettings['hallway_space'] + worldsettings['nr_drop_zones']) + 2 # calculate the total height world_height = nr_room_rows * worldsettings['room_size'][1] + ( nr_room_rows + 1) * worldsettings['hallway_space'] + worldsettings[ 'nr_blocks_needed'] + 2 return int(world_width), int(world_height) def _addBlocks(self, room_locations): ''' Add blocks to all given room locations ''' for room_name, locations in room_locations.items(): for loc in locations: # Get the block's name name = f"Block in {room_name}" # Get the probability for adding a block so we get the on average the requested number of blocks per room prob = min( 1.0, self._worldsettings['average_blocks_per_room'] / len(locations)) # Create a MATRX random property of shape and color so each block varies per created world. # These random property objects are used to obtain a certain value each time a new world is # created from this builder. colour_property = RandomProperty( values=self._worldsettings['block_colors']) shape_property = RandomProperty( values=self._worldsettings['block_shapes']) # Add the block; a regular SquareBlock as denoted by the given 'callable_class' which the # builder will use to create the object. In addition to setting MATRX properties, we also # provide a `is_block` boolean as custom property so we can identify this as a collectible # block. self._builder.add_object_prospect( loc, name, callable_class=CollectableBlock, probability=prob, visualize_shape=shape_property, visualize_colour=colour_property, block_size=self._worldsettings['block_size']) def _addAgents(self): ''' Add bots as specified, starting top left corner. All bots have the same sense_capability. ''' sense_capability = SenseCapability({ AgentBody: self._worldsettings['agent_sense_range'], CollectableBlock: self._worldsettings['block_sense_range'], None: self._worldsettings['other_sense_range'] }) loc = (0, 1) # agents start in horizontal row at top left corner. team_name = "Team 1" # currently this supports 1 team for agent in self._agents: brain = agent['botclass'](agent['settings']) loc = (loc[0] + 1, loc[1]) if agent['botclass'] == Human: self._builder.add_human_agent( loc, brain, team=team_name, name=agent['name'], key_action_map=self._worldsettings['key_action_map'], sense_capability=sense_capability) else: self._builder.add_agent(loc, brain, team=team_name, name=agent['name'], sense_capability=sense_capability) def _addRooms(self): ''' @return room locations ''' room_locations = {} for room_nr in range(self._worldsettings['nr_rooms']): room_top_left, door_loc = self.get_room_loc(room_nr) # We assign a simple random color to each room. Not for any particular reason except to brighting up the place. room_color = random.choice(self._worldsettings['room_colors']) # Add the room room_name = f"room_{room_nr}" self._builder.add_room( top_left_location=room_top_left, width=self._worldsettings['room_size'][0], height=self._worldsettings['room_size'][1], name=room_name, door_locations=[door_loc], wall_visualize_colour=self._worldsettings['wall_color'], with_area_tiles=True, area_visualize_colour=room_color, area_visualize_opacity=0.1) # Find all inner room locations where we allow objects (making sure that the location behind to door is free) room_locations[room_name] = self._builder.get_room_locations( room_top_left, self._worldsettings['room_size'][0], self._worldsettings['room_size'][1]) return room_locations def get_room_loc(self, room_nr): ''' @return room location (room_x, room_y), (door_x, door_y) for given room nr ''' row = np.floor(room_nr / self._worldsettings['rooms_per_row']) column = room_nr % self._worldsettings['rooms_per_row'] # x is: +1 for the edge, +edge hallway, +room width * column nr, +1 off by one room_x = int(1 + self._worldsettings['hallway_space'] + (self._worldsettings['room_size'][0] * column)) # y is: +1 for the edge, +hallway space * (nr row + 1 for the top hallway), +row * room height, +1 off by one room_y = int(1 + self._worldsettings['hallway_space'] * (row + 1) + row * self._worldsettings['room_size'][1] + 1) # door location is always center bottom door_x = room_x + int(np.ceil(self._worldsettings['room_size'][0] / 2)) door_y = room_y + self._worldsettings['room_size'][1] - 1 return (room_x, room_y), (door_x, door_y) def _addDropOffZones(self, world_size): x = int(np.ceil(world_size[0] / 2)) - \ (int(np.floor(self._worldsettings['nr_drop_zones'] / 2)) * \ (self._worldsettings['hallway_space'] + 1)) y = world_size[ 1] - 1 - 1 # once for off by one, another for world bound for nr_zone in range(self._worldsettings['nr_drop_zones']): # Add the zone's tiles. Area tiles are special types of objects in MATRX that simply function as # a kind of floor. They are always traversable and cannot be picked up. self._builder.add_area( (x, y - self._worldsettings['nr_blocks_needed'] + 1), width=1, height=self._worldsettings['nr_blocks_needed'], name=f"Drop off {nr_zone}", visualize_colour=self._worldsettings['drop_off_color'], drop_zone_nr=nr_zone, is_drop_zone=True, is_goal_block=False, is_collectable=False) # Go through all needed blocks for nr_block in range(self._worldsettings['nr_blocks_needed']): # Create a MATRX random property of shape and color so each world contains different blocks to collect colour_property = RandomProperty( values=self._worldsettings['block_colors']) shape_property = RandomProperty( values=self._worldsettings['block_shapes']) # Add a 'ghost image' of the block that should be collected. This can be seen by both humans and agents to # know what should be collected in what order. loc = (x, y - nr_block) self._builder.add_object( loc, name="Collect Block", callable_class=GhostBlock, visualize_colour=colour_property, visualize_shape=shape_property, drop_zone_nr=nr_zone, block_size=self._worldsettings['block_size']) # Change the x to the next zone x = x + self._worldsettings['hallway_space'] + 1