def test_get_new(): rng = np.random.RandomState(1234) types_counts = {t: rng.randint(2, 10) for t in data.get_types()} orig_types_counts = deepcopy(types_counts) for t in data.get_types(): name = get_new(t, types_counts) splits = name.split("_") assert splits[0] == t assert int(splits[1]) == orig_types_counts[t] assert types_counts[t] == orig_types_counts[t] + 1
def add(self, *entities: List["WorldEntity"]) -> None: """ Add children to this entity. """ if data.get_types().is_descendant_of(self.type, "r"): name = "at" elif data.get_types().is_descendant_of(self.type, ["c", "I"]): name = "in" elif data.get_types().is_descendant_of(self.type, "s"): name = "on" else: raise ValueError("Unexpected type {}".format(self.type)) for entity in entities: self.add_fact(name, entity, self) self.content.append(entity)
def door(self, door: WorldEntity) -> None: if door is not None and not data.get_types().is_descendant_of( door.type, "d"): msg = "Expecting a WorldEntity of 'door' type." raise TypeError(msg) self._door = door
def new(self, type: str, name: Optional[str] = None, desc: Optional[str] = None) -> Union[WorldEntity, WorldRoom]: """ Creates new entity given its type. Args: type: The type of the entity. name: The name of the entity. desc: The description of the entity. Returns: The newly created entity. * If the `type` is `'r'`, then a `WorldRoom` object is returned. * Otherwise, a `WorldEntity` is returned. """ var_id = type if not data.get_types().is_constant(type): var_id = get_new(type, self._types_counts) var = Variable(var_id, type) if type == "r": entity = WorldRoom(var, name, desc) self.rooms.append(entity) else: entity = WorldEntity(var, name, desc) self._entities[var_id] = entity return entity
def test_serialization_deserialization(): rule = data.get_rules()["go/east"] mapping = { Placeholder("r'"): Variable("room1", "r"), Placeholder("r"): Variable("room2"), } mapping.update(data.get_types().constants_mapping) action = rule.instantiate(mapping) infos = action.serialize() action2 = Action.deserialize(infos) assert action == action2
def __init__(self): self.backward = False self.min_depth = 1 self.max_depth = 1 self.min_breadth = 1 self.max_breadth = 1 self.subquests = False self.create_variables = False self.fixed_mapping = data.get_types().constants_mapping self.rng = None self.logic = data.get_logic() self.rules_per_depth = [] self.restricted_types = frozenset()
def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None): types_counts = data.get_types().count(state) # Instantiate variables if needed try: for ph in rule.placeholders: if mapping.get(ph) is None: name = get_new(ph.type, types_counts, max_types_counts) mapping[ph] = Variable(name, ph.type) except NotEnoughNounsError: return None return rule.instantiate(mapping)
def test_get_reverse_rules(verbose=False): for rule in data.get_rules().values(): r_rule = data.get_reverse_rules(rule) if verbose: print(rule, r_rule) if rule.name.startswith("eat"): assert r_rule is None else: # Check if that when applying the reverse rule we can reobtain # the previous state. action = maybe_instantiate_variables( rule, data.get_types().constants_mapping.copy(), State([])) state = State(action.preconditions) new_state = state.copy() assert new_state.apply(action) assert r_rule is not None actions = list( new_state.all_applicable_actions( [r_rule], data.get_types().constants_mapping)) if len(actions) != 1: print(actions) print(r_rule) print(new_state) print( list( new_state.all_instantiations( r_rule, data.get_types().constants_mapping))) assert len(actions) == 1 r_state = new_state.copy() r_state.apply(actions[0]) assert state == r_state
def _process_objects(self) -> None: for fact in self.facts: if data.get_types().is_descendant_of(fact.arguments[0].type, 'r'): continue # Skip room facts. obj = self._get_entity(fact.arguments[0]) obj.add_related_fact(fact) if fact.name in ["in", "on", "at"]: holder = self._get_entity(fact.arguments[1]) holder.content.append(obj) if fact.arguments[0].type == "P": self._player_room = holder
def expand_clean_replace(symbol, grammar, obj, game_infos): """ Return a cleaned/keyword replaced symbol. """ obj_infos = game_infos[obj.id] phrase = grammar.expand(symbol) phrase = phrase.replace("(obj)", obj_infos.id) phrase = phrase.replace("(name)", obj_infos.name) phrase = phrase.replace("(name-n)", obj_infos.noun if obj_infos.adj is not None else obj_infos.name) phrase = phrase.replace("(name-adj)", obj_infos.adj if obj_infos.adj is not None else grammar.expand("#ordinary_adj#")) if obj.type != "": phrase = phrase.replace("(name-t)", data.get_types().get_description(obj.type)) else: assert False, "Does this even happen?" return fix_determinant(phrase)
def __init__(self) -> None: """ Creates an empty world, with a player and an empty inventory. """ self._entities = {} self._quests = [] self.rooms = [] self.paths = [] self._types_counts = data.get_types().count(State()) self.player = self.new(type='P') self.inventory = self.new(type='I') self.grammar = textworld.generator.make_grammar() self._game = None self._distractors_facts = []
def _get_all_assignments(state, rules, partial=False, constrained_types=None, backward=False): assignments = [] for rule in rules: if backward: rule = rule.inverse() for mapping in state.all_assignments( rule, data.get_types().constants_mapping, partial, constrained_types): assignments.append((rule, mapping)) # Keep everything in a deterministic order return sorted(assignments, key=_assignment_sort_key)
def assign_description_to_object(obj, grammar, game_infos): """ Assign a descripton to an object. """ if game_infos[obj.id].desc is not None: return # Already have a description. # Update the object description desc_tag = "#({})_desc#".format(obj.type) game_infos[obj.id].desc = "" if grammar.has_tag(desc_tag): game_infos[obj.id].desc = expand_clean_replace(desc_tag, grammar, obj, game_infos) # If we have an openable object, append an additional description if data.get_types().is_descendant_of(obj.type, ["c", "d"]): game_infos[obj.id].desc += grammar.expand(" #openable_desc#")
def __init__(self, world: World, grammar: Optional[Grammar] = None, quests: Optional[List[Quest]] = None) -> None: """ Args: world: The world to use for the game. quests: The quests to done in the game. grammar: The grammar to control the text generation. """ self.world = world self.state = world.state.copy() # Current state of the game. self.grammar = grammar self.quests = [] if quests is None else quests self.metadata = {} self._infos = self._build_infos() self._rules = data.get_rules() self._types = data.get_types()
def collect(self, game): self.n_games += 1 # Collect distribution of nb. of commands. update_bincount(self.dist_quest_count, len(game.quests)) # Collect distribution of command's types. for quest in game.quests: actions = quest.actions self.quests.add(quest.desc) update_bincount(self.dist_quest_length_count, len(actions)) for action in actions: action_name = action.name if self.group_actions: action_name = action_name.split("-")[0].split("/")[0] self.dist_cmd_type[action_name] += 1 self.dist_final_cmd_type[action_name] += 1 # Collect distribution of object's types. dist_obj_type = defaultdict(lambda: 0) interactable_objects = game.world.objects inventory = game.world.get_objects_in_inventory() for obj in interactable_objects: self.objects.add(game.infos[obj.id].name) dist_obj_type[obj.type] += 1 nb_objects = 0 for type_ in data.get_types(): if type_ in ["I", "P", "t", "r"]: continue count = dist_obj_type[type_] nb_objects += count self.dist_obj_type[type_] += count update_bincount(self.dist_obj_type_count[type_], count) update_bincount(self.dist_obj_count, nb_objects) update_bincount(self.dist_inventory_size, len(inventory))
def make_game(mode: str, options: GameOptions) -> textworld.Game: """ Make a Treasure Hunter game. Arguments: mode: Mode for the game where * `'easy'`: rooms are all empty except where the two objects are placed. Also, connections between rooms have no door. * `'medium'`: adding closed doors and containers that might need to be open in order to find the object. * `'hard'`: adding locked doors and containers (necessary keys will in the inventory) that might need to be unlocked (and open) in order to find the object. options: For customizing the game generation (see :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Treasure Hunter" metadata["mode"] = mode metadata["seeds"] = options.seeds metadata["world_size"] = options.nb_rooms metadata["quest_length"] = options.quest_length metadata["grammar_flags"] = options.grammar.encode() rngs = options.rngs rng_map = rngs['seed_map'] rng_objects = rngs['seed_objects'] rng_quest = rngs['seed_quest'] rng_grammar = rngs['seed_grammar'] modes = ["easy", "medium", "hard"] if mode == "easy": door_states = None n_distractors = 0 elif mode == "medium": door_states = ["open", "closed"] n_distractors = 10 elif mode == "hard": door_states = ["open", "closed", "locked"] n_distractors = 20 # Generate map. map_ = textworld.generator.make_map(n_rooms=options.nb_rooms, rng=rng_map, possible_door_states=door_states) assert len(map_.nodes()) == options.nb_rooms world = World.from_map(map_) # Randomly place the player. starting_room = None if len(world.rooms) > 1: starting_room = rng_map.choice(world.rooms) world.set_player_room(starting_room) # Add object the player has to pick up. types_counts = data.get_types().count(world.state) obj_type = data.get_types().sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) right_obj = Variable(var_id, obj_type) world.add_fact(Proposition("in", [right_obj, world.inventory])) # Add containers and supporters to the world. types_counts = data.get_types().count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + data.get_types().descendants('c') + data.get_types().descendants('s')) for i in range(n_distractors): obj_type = rng_objects.choice(distractor_types) var_id = get_new(obj_type, types_counts) # This update the types_counts. objects.append(Variable(var_id, obj_type)) world.populate_with(objects, rng=rng_objects) # Add object the player should not pick up. types_counts = data.get_types().count(world.state) obj_type = data.get_types().sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) wrong_obj = Variable(var_id, obj_type) # Place it anywhere in the world. world.populate_with([wrong_obj], rng=rng_objects) # Generate a quest that finishes by taking something (i.e. the right # object since it's the only one in the inventory). options.chaining.rules_per_depth = [data.get_rules().get_matching("take.*")] options.chaining.backward = True options.chaining.rng = rng_quest #options.chaining.restricted_types = exceptions #exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"] chain = textworld.generator.sample_quest(world.state, options.chaining) # Add objects needed for the quest. world.state = chain[0].state quest = Quest([c.action for c in chain]) quest.set_failing_conditions([Proposition("in", [wrong_obj, world.inventory])]) grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar) game = textworld.generator.make_game_with(world, [quest], grammar) game.metadata = metadata mode_choice = modes.index(mode) uuid = "tw-treasure_hunter-{specs}-{grammar}-{seeds}" uuid = uuid.format(specs=encode_seeds((mode_choice, options.nb_rooms, options.quest_length)), grammar=options.grammar.uuid, seeds=encode_seeds([options.seeds[k] for k in sorted(options.seeds)])) game.metadata["uuid"] = uuid return game
def make_game(mode: str, n_rooms: int, quest_length: int, grammar_flags: Mapping = {}, seeds: Optional[Union[int, Dict[str, int]]] = None ) -> textworld.Game: """ Make a Treasure Hunter game. Arguments: mode: Mode for the game where * `'easy'`: rooms are all empty except where the two objects are placed. Also, connections between rooms have no door. * `'medium'`: adding closed doors and containers that might need to be open in order to find the object. * `'hard'`: adding locked doors and containers (necessary keys will in the inventory) that might need to be unlocked (and open) in order to find the object. n_rooms: Number of rooms in the game. quest_length: How far from the player the object to find should ideally be placed. grammar_flags: Options for the grammar controlling the text generation process. seeds: Seeds for the different generation processes. * If `None`, seeds will be sampled from :py:data:`textworld.g_rng <textworld.utils.g_rng>`. * If `int`, it acts as a seed for a random generator that will be used to sample the other seeds. * If dict, the following keys can be set: * `'seed_map'`: control the map generation; * `'seed_objects'`: control the type of objects and their location; * `'seed_quest'`: control the quest generation; * `'seed_grammar'`: control the text generation. For any key missing, a random number gets assigned (sampled from :py:data:`textworld.g_rng <textworld.utils.g_rng>`). Returns: Generated game. """ # Deal with any missing random seeds. seeds = get_seeds_for_game_generation(seeds) metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Treasure Hunter" metadata["mode"] = mode metadata["seeds"] = seeds metadata["world_size"] = n_rooms metadata["quest_length"] = quest_length metadata["grammar_flags"] = grammar_flags rng_map = np.random.RandomState(seeds['seed_map']) rng_objects = np.random.RandomState(seeds['seed_objects']) rng_quest = np.random.RandomState(seeds['seed_quest']) rng_grammar = np.random.RandomState(seeds['seed_grammar']) modes = ["easy", "medium", "hard"] if mode == "easy": door_states = None n_distractors = 0 elif mode == "medium": door_states = ["open", "closed"] n_distractors = 10 elif mode == "hard": door_states = ["open", "closed", "locked"] n_distractors = 20 # Generate map. map_ = textworld.generator.make_map(n_rooms=n_rooms, rng=rng_map, possible_door_states=door_states) assert len(map_.nodes()) == n_rooms world = World.from_map(map_) # Randomly place the player. starting_room = None if len(world.rooms) > 1: starting_room = rng_map.choice(world.rooms) world.set_player_room(starting_room) # Add object the player has to pick up. types_counts = data.get_types().count(world.state) obj_type = data.get_types().sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) right_obj = Variable(var_id, obj_type) world.add_fact(Proposition("in", [right_obj, world.inventory])) # Add containers and supporters to the world. types_counts = data.get_types().count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + data.get_types().descendants('c') + data.get_types().descendants('s')) for i in range(n_distractors): obj_type = rng_objects.choice(distractor_types) var_id = get_new(obj_type, types_counts) # This update the types_counts. objects.append(Variable(var_id, obj_type)) world.populate_with(objects, rng=rng_objects) # Add object the player should not pick up. types_counts = data.get_types().count(world.state) obj_type = data.get_types().sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) wrong_obj = Variable(var_id, obj_type) # Place it anywhere in the world. world.populate_with([wrong_obj], rng=rng_objects) # Generate a quest that finishes by taking something (i.e. the right # object since it's the only one in the inventory). rules_per_depth = {0: data.get_rules().get_matching("take.*")} exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"] chain = textworld.generator.sample_quest(world.state, rng_quest, max_depth=quest_length, allow_partial_match=False, exceptions=exceptions, rules_per_depth=rules_per_depth, nb_retry=5, backward=True) # Add objects needed for the quest. world.state = chain[0].state quest = Quest([c.action for c in chain]) quest.set_failing_conditions([Proposition("in", [wrong_obj, world.inventory])]) grammar = textworld.generator.make_grammar(flags=grammar_flags, rng=rng_grammar) game = textworld.generator.make_game_with(world, [quest], grammar) game.metadata = metadata mode_choice = modes.index(mode) uuid = "tw-treasure_hunter-{specs}-{flags}-{seeds}" uuid = uuid.format(specs=encode_seeds((mode_choice, n_rooms, quest_length)), flags=encode_flags(grammar_flags), seeds=encode_seeds([seeds[k] for k in sorted(seeds)])) game.metadata["uuid"] = uuid return game
def _process_rooms(self) -> None: for fact in self.facts: if not data.get_types().is_descendant_of(fact.arguments[0].type, 'r'): continue # Skip non room facts. room = self._get_room(fact.arguments[0]) room.add_related_fact(fact) if fact.name.endswith("_of"): # Handle room positioning facts. exit = reverse_direction(fact.name.split("_of")[0]) dest = self._get_room(fact.arguments[1]) dest.add_related_fact(fact) assert exit not in room.exits room.exits[exit] = dest # Handle door link facts. for fact in self.facts: if fact.name != "link": continue src = self._get_room(fact.arguments[0]) door = self._get_object(fact.arguments[1]) dest = self._get_room(fact.arguments[2]) door.add_related_fact(fact) src.content.append(door) exit_found = False for exit, room in src.exits.items(): if dest == room: src.doors[exit] = door exit_found = True break if not exit_found: # Need to position both rooms w.r.t. each other. src_free_exits = [ exit for exit in DIRECTIONS if exit not in src.exits ] for exit in src_free_exits: r_exit = reverse_direction(exit) if r_exit not in dest.exits: src.exits[exit] = dest dest.exits[r_exit] = src src.doors[exit] = door exit_found = True break # Relax the Cartesian grid constraint. if not exit_found: # Need to position both rooms w.r.t. each other. src_free_exits = [ exit for exit in DIRECTIONS if exit not in src.exits ] dest_free_exits = [ exit for exit in DIRECTIONS if exit not in dest.exits ] if len(src_free_exits) > 0 and len(dest_free_exits) > 0: exit = src_free_exits[0] r_exit = dest_free_exits[0] src.exits[exit] = dest dest.exits[r_exit] = src src.doors[exit] = door exit_found = True if not exit_found: # If there is still no exit found. raise NoFreeExitError("Cannot connect {} and {}.".format( src, dest))
def load_state(world: World, game_infos: Optional[Dict[str, EntityInfo]] = None, action: Optional[Action] = None, format: str = 'png', limit_player_view: bool = False) -> dict: """ Generates serialization of game state. :param world: The current state of the world to visualize. :param game_infos: The mapping needed to get objects names. :param action: If provided, highlight the world changes made by that action. :param format: The graph output format (gv, svg, png...) :param limit_player_view: Whether to limit the player's view (defaults to false) :return: The graph generated from this World """ if world.player_room is None: room = world.rooms[0] else: room = world.player_room edges = [] nodes = sorted([room.name for room in world.rooms]) pos = {room.name: (0, 0)} def used_pos(): pos_along_edges = [] for e in edges: A, B = pos[e[0]], pos[e[1]] if A[0] == B[0]: # Y-axis edge. for i in range(A[1], B[1], np.sign(B[1] - A[1])): pos_along_edges.append((A[0], i)) else: # X-axis edge. for i in range(A[0], B[0], np.sign(B[0] - A[0])): pos_along_edges.append((i, A[1])) return list(pos.values()) + pos_along_edges openset = [room] closedset = set() # temp_viz(nodes, edges, pos, color=[world.player_room.name]) while len(openset) > 0: room = openset.pop(0) closedset.add(room) for exit, target in room.exits.items(): if target in openset or target in closedset: continue openset.append(target) src_pos = np.array(pos[room.name]) if exit == "north": target_pos = tuple(src_pos + (0, 1)) if target_pos in used_pos(): for n, p in pos.items(): if p[1] <= src_pos[1]: pos[n] = (p[0], p[1] - 1) pos[target.name] = (pos[room.name][0], pos[room.name][1] + 1) elif exit == "south": target_pos = tuple(src_pos + (0, -1)) if target_pos in used_pos(): for n, p in pos.items(): if p[1] >= src_pos[1]: pos[n] = (p[0], p[1] + 1) pos[target.name] = (pos[room.name][0], pos[room.name][1] - 1) elif exit == "east": target_pos = tuple(src_pos + (1, 0)) if target_pos in used_pos(): for n, p in pos.items(): if p[0] <= src_pos[0]: pos[n] = (p[0] - 1, p[1]) pos[target.name] = (pos[room.name][0] + 1, pos[room.name][1]) elif exit == "west": target_pos = tuple(src_pos + (-1, 0)) if target_pos in used_pos(): for n, p in pos.items(): if p[0] >= src_pos[0]: pos[n] = (p[0] + 1, p[1]) pos[target.name] = (pos[room.name][0] - 1, pos[room.name][1]) edges.append((room.name, target.name, room.doors.get(exit))) # temp_viz(nodes, edges, pos, color=[world.player_room.name]) pos = {game_infos[k].name: v for k, v in pos.items()} rooms = {} player_room = world.player_room if game_infos is None: new_game = Game(world, []) game_infos = new_game.infos game_infos["objective"] = new_game.quests[0].desc for k, v in game_infos.items(): if v.name is None: v.name = k for room in world.rooms: rooms[room.id] = GraphRoom(game_infos[room.id].name, room) result = {} # Objective if "objective" in game_infos: result["objective"] = game_infos["objective"] # Objects all_items = {} inventory_items = [] objects = world.objects # if limit_player_view: # objects = world.get_visible_objects_in(world.player_room) # objects += world.get_objects_in_inventory() # add all items first, in case properties are "out of order" for obj in objects: cur_item = GraphItem(obj.type, game_infos[obj.id].name) cur_item.portable = data.get_types().is_descendant_of( cur_item.type, "o") all_items[obj.id] = cur_item for obj in sorted(objects, key=lambda obj: obj.name): cur_item = all_items[obj.id] for attribute in obj.get_attributes(): if action and attribute in action.added: cur_item.highlight = True if attribute.name == 'in': # add object to inventory if attribute.arguments[-1].type == 'I': inventory_items.append(cur_item) elif attribute.arguments[0].name == obj.id: # add object to containers if same object all_items[attribute.arguments[1].name].add_content( cur_item) else: print('DEBUG: Skipping attribute %s for object %s' % (attribute, obj.id)) elif attribute.name == 'at': # add object to room if attribute.arguments[-1].type == 'r': rooms[attribute.arguments[1].name].add_item(cur_item) elif attribute.name == 'on': # add object to supporters all_items[attribute.arguments[1].name].add_content(cur_item) elif attribute.name == 'open': cur_item.set_open_closed_locked('open') elif attribute.name == 'closed': cur_item.set_open_closed_locked('closed') elif attribute.name == 'locked': cur_item.set_open_closed_locked('locked') if not limit_player_view: cur_item.infos = " (locked)" elif attribute.name == 'match': if not limit_player_view: cur_item.infos = " (for {})".format( game_infos[attribute.arguments[-1].name].name) else: cur_item.add_unknown_predicate(attribute) for room in rooms.values(): room.position = pos[room.name] result["rooms"] = [] for room in rooms.values(): room.items = [item.to_dict() for item in room.items] temp = room.base_room.serialize() temp["attributes"] = [ a.serialize() for a in room.base_room.get_attributes() ] room.base_room = temp result["rooms"].append(room.__dict__) def _get_door(door): if door is None: return None return all_items[door.name].__dict__ result["connections"] = [{ "src": game_infos[e[0]].name, "dest": game_infos[e[1]].name, 'door': _get_door(e[2]) } for e in edges] result["inventory"] = [inv.__dict__ for inv in inventory_items] return result
def generate_instruction(action, grammar, game_infos, world, counts): """ Generate text instruction for a specific action. """ # Get the more precise command tag. cmd_tag = "#{}#".format(action.name) if not grammar.has_tag(cmd_tag): cmd_tag = "#{}#".format(action.name.split("-")[0]) if not grammar.has_tag(cmd_tag): cmd_tag = "#{}#".format(action.name.split("-")[0].split("/")[0]) separator_tag = "#action_separator_{}#".format(action.name) if not grammar.has_tag(separator_tag): separator_tag = "#action_separator_{}#".format(action.name.split("-")[0]) if not grammar.has_tag(separator_tag): separator_tag = "#action_separator_{}#".format(action.name.split("-")[0].split("/")[0]) if not grammar.has_tag(separator_tag): separator_tag = "#action_separator#" if not grammar.has_tag(separator_tag): separator = "" else: separator = grammar.expand(separator_tag) desc = grammar.expand(cmd_tag) # We generate a custom mapping. mapping = OrderedDict() if isinstance(action, MergeAction): action_mapping = action.mapping else: action_mapping = data.get_rules()[action.name].match(action) for ph, var in action_mapping.items(): if var.type == "r": # We can use a simple description for the room r = world.find_room_by_id(var.name) # Match on 'name' if r is None: mapping[ph.name] = '' else: mapping[ph.name] = game_infos[r.id].name elif var.type in ["P", "I"]: continue else: # We want a more complex description for the objects obj = world.find_object_by_id(var.name) obj_infos = game_infos[obj.id] if grammar.flags.ambiguous_instructions: assert False, "not tested" choices = [] for t in ["adj", "noun", "type"]: if counts[t][getattr(obj_infos, t)] <= 1: if t == "noun": choices.append(getattr(obj_infos, t)) elif t == "type": choices.append(data.get_types().get_description(getattr(obj_infos, t))) else: # For adj, we pick an abstraction on the type atype = data.get_types().get_description(grammar.rng.choice(data.get_types().get_ancestors(obj.type))) choices.append("{} {}".format(getattr(obj_infos, t), atype)) # If we have no possibilities, use the name (ie. prioritize abstractions) if len(choices) == 0: choices.append(obj_infos.name) mapping[ph.name] = grammar.rng.choice(choices) else: mapping[ph.name] = obj_infos.name # Replace the keyword with one of the possibilities for keyword in re.findall(r'[(]\S*[)]', desc + separator): for key in keyword[1:-1].split("|"): if key in mapping: desc = desc.replace(keyword, mapping[key]) separator = separator.replace(keyword, mapping[key]) return desc, separator
def assign_description_to_room(room, game, grammar): """ Assign a descripton to a room. """ # Add the decorative text room_desc = expand_clean_replace("#dec#\n\n", grammar, room, game.infos) # Convert the objects into groupings based on adj/noun/type objs = [o for o in room.content if data.get_types().is_descendant_of(o.type, data.get_types().CLASS_HOLDER)] groups = OrderedDict() groups["adj"] = OrderedDict() groups["noun"] = OrderedDict() for obj in objs: obj_infos = game.infos[obj.id] adj, noun = obj_infos.adj, obj_infos.noun # get all grouped adjectives and nouns groups['adj'][adj] = list(filter(lambda x: game.infos[x.id].adj == adj, objs)) groups['noun'][noun] = list(filter(lambda x: game.infos[x.id].noun == noun, objs)) # Generate the room description, prioritizing group descriptions where possible ignore = [] for obj in objs: if obj.id in ignore: continue # Skip that object. obj_infos = game.infos[obj.id] adj, noun = obj_infos.adj, obj_infos.noun if grammar.flags.blend_descriptions: found = False for type in ["noun", "adj"]: group_filt = [] if getattr(obj_infos, type) != "": group_filt = list(filter(lambda x: x.id not in ignore, groups[type][getattr(obj_infos, type)])) if len(group_filt) > 1: found = True desc = replace_num(grammar.expand("#room_desc_group#"), len(group_filt)) if type == "noun": desc = desc.replace("(val)", "{}s".format(getattr(obj_infos, type))) desc = desc.replace("(name)", obj_list_to_prop_string(group_filt, "adj", game.infos, det_type="one")) elif type == "adj": _adj = getattr(obj_infos, type) if getattr(obj_infos, type) is not None else "" desc = desc.replace("(val)", "{}things".format(_adj)) desc = desc.replace("(name)", obj_list_to_prop_string(group_filt, "noun", game.infos)) for o2 in group_filt: ignore.append(o2.id) if data.get_types().is_descendant_of(o2.type, data.get_types().CLASS_HOLDER): for vtype in [o2.type] + data.get_types().get_ancestors(o2.type): tag = "#room_desc_({})_multi_{}#".format(vtype, "adj" if type == "noun" else "noun") if grammar.has_tag(tag): desc += expand_clean_replace(" " + tag, grammar, o2, game.infos) break room_desc += " {}".format(fix_determinant(desc)) break if found: continue if obj.type not in ["P", "I", "d"]: for vtype in [obj.type] + data.get_types().get_ancestors(obj.type): tag = "#room_desc_({})#".format(vtype) if grammar.has_tag(tag): room_desc += expand_clean_replace(" " + tag, grammar, obj, game.infos) break room_desc += "\n\n" # Look for potential exit directions. exits_with_open_door = [] exits_with_closed_door = [] exits_without_door = [] for dir_ in sorted(room.exits.keys()): if dir_ in room.doors: door_obj = room.doors[dir_] attributes_names = [attr.name for attr in door_obj.get_attributes()] if "open" in attributes_names: exits_with_open_door.append((dir_, door_obj)) else: exits_with_closed_door.append((dir_, door_obj)) else: exits_without_door.append(dir_) exits_desc = [] # Describing exits with door. if grammar.flags.blend_descriptions and len(exits_with_closed_door) > 1: dirs, door_objs = zip(*exits_with_closed_door) e_desc = grammar.expand("#room_desc_doors_closed#") e_desc = replace_num(e_desc, len(door_objs)) e_desc = e_desc.replace("(dir)", list_to_string(dirs, False)) e_desc = clean_replace_objs(grammar, e_desc, door_objs, game.infos) e_desc = repl_sing_plur(e_desc, len(door_objs)) exits_desc.append(e_desc) else: for dir_, door_obj in exits_with_closed_door: d_desc = expand_clean_replace(" #room_desc_(d)#", grammar, door_obj, game.infos) d_desc = d_desc.replace("(dir)", dir_) exits_desc.append(d_desc) if grammar.flags.blend_descriptions and len(exits_with_open_door) > 1: dirs, door_objs = zip(*exits_with_open_door) e_desc = grammar.expand("#room_desc_doors_open#") e_desc = replace_num(e_desc, len(door_objs)) e_desc = e_desc.replace("(dir)", list_to_string(dirs, False)) e_desc = clean_replace_objs(grammar, e_desc, door_objs, game.infos) e_desc = repl_sing_plur(e_desc, len(door_objs)) exits_desc.append(e_desc) else: for dir_, door_obj in exits_with_open_door: d_desc = expand_clean_replace(" #room_desc_(d)#", grammar, door_obj, game.infos) d_desc = d_desc.replace("(dir)", dir_) exits_desc.append(d_desc) # Describing exits without door. if grammar.flags.blend_descriptions and len(exits_without_door) > 1: e_desc = grammar.expand("#room_desc_exits#").replace("(dir)", list_to_string(exits_without_door, False)) e_desc = repl_sing_plur(e_desc, len(exits_without_door)) exits_desc.append(e_desc) else: for dir_ in exits_without_door: e_desc = grammar.expand("#room_desc_(dir)#").replace("(dir)", dir_) exits_desc.append(e_desc) room_desc += " ".join(exits_desc) # Finally, set the description return fix_determinant(room_desc)
def generate_inform7_source(game, seed=1234, use_i7_description=False): var_infos = game.infos world = game.world quests = game.quests source = "" source += "When play begins, seed the random-number generator with {}.\n\n".format( seed) source += define_inform7_kinds() # Mention that rooms have a special text attribute called 'internal name'. source += "A room has a text called internal name.\n\n" # Define custom addons. source += data.INFORM7_ADDONS_CODE + "\n" # Declare all rooms. room_names = [room.id for room in world.rooms] source += "The " + " and the ".join(room_names) + " are rooms.\n\n" # Process the rooms. for room in world.rooms: room_infos = var_infos[room.id] room_name = room_infos.name if not use_i7_description: # Describe the room. room_desc = room_infos.desc source += "The internal name of {} is \"{}\".\n".format( room.id, room_name) source += "The printed name of {} is \"-= {} =-\".\n".format( room.id, str.title(room_name)) parts = [] splits = re.split("\[end if\]", room_desc) for split in splits: part_name = "{} part {}".format(room_name, len(parts)) text = "The {name} is some text that varies. The {name} is \"{desc}\".\n" if split != splits[-1]: split += "[end if]" source += text.format(name=part_name, desc=split) parts.append(part_name) source += "The description of {} is \"{}\".\n".format( room.id, "".join("[{}]".format(part) for part in parts)) source += "\n" # List the room's attributes. source += gen_source_for_map(room) # Declare all objects for vtype in data.get_types(): if vtype in ["P", "I"]: continue # Skip player and inventory. entities = world.get_entities_per_type(vtype) if len(entities) == 0: continue # No entity of that specific type. kind = data.INFORM7_VARIABLES[vtype] names = [entity.id for entity in entities] source += "The " + " and the ".join(names) + " are {}s.\n".format(kind) # All objects are privately-named and we manually define all "Understand as" phrases needed. source += "The " + " and the ".join(names) + " are privately-named.\n" # Process the objects. source += "\n" source += gen_source_for_objects(world.objects, var_infos, use_i7_description=use_i7_description) source += "\n\n" # Place the player. source += "The player is in {}.\n\n".format( var_infos[world.player_room.id].id) objective = game.objective maximum_score = 0 for quest_id, quest in enumerate(quests): commands = gen_commands_from_actions(quest.actions, var_infos) quest.commands = commands maximum_score += quest.reward quest_completed = textwrap.dedent("""\ The quest{quest_id} completed is a truth state that varies. The quest{quest_id} completed is usually false. """) source += quest_completed.format(quest_id=quest_id) walkthrough = '\nTest quest{} with "{}"\n\n'.format( quest_id, " / ".join(commands)) source += walkthrough # Add winning and losing conditions for quest. quest_ending_condition = """\ Every turn: if {losing_tests}: end the story; [Lost] else if quest{quest_id} completed is false and {winning_tests}: increase the score by {reward}; [Quest completed] Now the quest{quest_id} completed is true. """ winning_tests = gen_source_for_conditions( quest.win_action.preconditions) losing_tests = "1 is 0 [always false]" if quest.fail_action is not None: losing_tests = gen_source_for_conditions( quest.fail_action.preconditions) quest_ending_condition = quest_ending_condition.format( losing_tests=losing_tests, winning_tests=winning_tests, reward=quest.reward, quest_id=quest_id) source += textwrap.dedent(quest_ending_condition) # Enable scoring is at least one quest has nonzero reward. if maximum_score != 0: source += "Use scoring. The maximum score is {}.\n".format( maximum_score) # Build test condition for winning the game. game_winning_test = "1 is 0 [always false]" if len(quests) > 0: test_template = "quest{} completed is true" game_winning_test = " and ".join( test_template.format(i) for i in range(len(quests))) # Remove square bracket when printing score increases. Square brackets are conflicting with # Inform7's events parser in git_glulx_ml.py. # And add winning conditions for the game. source += textwrap.dedent("""\ This is the simpler notify score changes rule: If the score is not the last notified score: let V be the score - the last notified score; say "Your score has just gone up by [V in words] "; if V > 1: say "points."; else: say "point."; Now the last notified score is the score; if {game_winning_test}: end the story finally; [Win] The simpler notify score changes rule substitutes for the notify score changes rule. """.format(game_winning_test=game_winning_test)) if not use_i7_description: # Remove Inform7 listing of nondescript items. source += textwrap.dedent("""\ Rule for listing nondescript items: stop. """) else: # List exits in room description source += textwrap.dedent("""\ [Ref: http://dhayton.haverford.edu/wp-content/uploads/Inform-manuals/Rex434.html#e434] The initial appearance of a door is usually "Nearby [an item described] leads [if the other side of the item described is visited][direction of the item described from the location] to [the other side][otherwise][direction of the item described from the location][end if]. It is [if open]open[else if closed]closed[otherwise]closed[end if]." Direction-relevance relates a door (called X) to a direction (called Y) when the direction of X from the location is Y. The verb to be directionally-relevant to means the direction-relevance relation. Understand "[something related by direction-relevance] door" as a door. Rule for printing a parser error when the player's command includes "[non-door direction] door": say "There is no door in that direction." instead. Definition: a direction (called direction D) is non-door: let the target be the room-or-door direction D from the location; if the target is a door: no; yes; """) source += textwrap.dedent("""\ Definition: a direction (called thataway) is viable if the room thataway from the location is a room and the room-or-door thataway from the location is a room. After looking: if list of viable directions is not empty: say "You can also go [list of viable directions] from here.". """) # Replace default banner with a greeting message and the quest description. source += textwrap.dedent("""\ Rule for printing the banner text: say "{objective}[line break]". """.format(objective=objective)) # Simply display *** The End *** when game ends. source += textwrap.dedent("""\ Include Basic Screen Effects by Emily Short. Rule for printing the player's obituary: if story has ended finally: center "*** The End ***"; else: center "*** You lost! ***"; say paragraph break; let X be the turn count; if restrict commands option is true: let X be the turn count minus one; say "You scored [score] out of a possible [maximum score], in [X] turn(s)."; [wait for any key; stop game abruptly;] rule succeeds. """) # Disable implicitly taking something. source += textwrap.dedent("""\ Rule for implicitly taking something (called target): if target is fixed in place: say "The [target] is fixed in place."; otherwise: say "You need to take the [target] first."; stop. """) # Referring to an object by its whole name shouldn't be ambiguous. source += textwrap.dedent("""\ Does the player mean doing something: if the noun is not nothing and the second noun is nothing and the player's command matches the text printed name of the noun: it is likely; if the noun is nothing and the second noun is not nothing and the player's command matches the text printed name of the second noun: it is likely; if the noun is not nothing and the second noun is not nothing and the player's command matches the text printed name of the noun and the player's command matches the text printed name of the second noun: it is very likely. [Handle action with two arguments.] """) # Useful for listing room contents with their properties. source += textwrap.dedent("""\ Printing the content of the room is an activity. Rule for printing the content of the room: let R be the location of the player; say "Room contents:[line break]"; list the contents of R, with newlines, indented, including all contents, with extra indentation. """) # Useful for listing world contents with their properties. source += textwrap.dedent("""\ Printing the content of the world is an activity. Rule for printing the content of the world: let L be the list of the rooms; say "World: [line break]"; repeat with R running through L: say " [the internal name of R][line break]"; repeat with R running through L: say "[the internal name of R]:[line break]"; if the list of things in R is empty: say " nothing[line break]"; otherwise: list the contents of R, with newlines, indented, including all contents, with extra indentation. """) # Useful for listing inventory contents with their properties. source += textwrap.dedent("""\ Printing the content of the inventory is an activity. Rule for printing the content of the inventory: say "Inventory:[line break]"; list the contents of the player, with newlines, indented, giving inventory information, including all contents, with extra indentation. """) # Useful for listing off-stage contents with their properties. source += textwrap.dedent("""\ Printing the content of nowhere is an activity. Rule for printing the content of nowhere: say "Nowhere:[line break]"; let L be the list of the off-stage things; repeat with thing running through L: say " [thing][line break]"; """) # Useful for listing things laying on the floor. source += textwrap.dedent("""\ Printing the things on the floor is an activity. Rule for printing the things on the floor: let R be the location of the player; let L be the list of things in R; remove yourself from L; remove the list of containers from L; remove the list of supporters from L; remove the list of doors from L; if the number of entries in L is greater than 0: say "There is [L with indefinite articles] on the floor."; """) # Print properties of objects when listing the inventory contents and the room contents. source += textwrap.dedent("""\ After printing the name of something (called target) while printing the content of the room or printing the content of the world or printing the content of the inventory or printing the content of nowhere: follow the property-aggregation rules for the target. The property-aggregation rules are an object-based rulebook. The property-aggregation rulebook has a list of text called the tagline. [At the moment, we only support "open/unlocked", "closed/unlocked" and "closed/locked" for doors and containers.] [A first property-aggregation rule for an openable open thing (this is the mention open openables rule): add "open" to the tagline. A property-aggregation rule for an openable closed thing (this is the mention closed openables rule): add "closed" to the tagline. A property-aggregation rule for an lockable unlocked thing (this is the mention unlocked lockable rule): add "unlocked" to the tagline. A property-aggregation rule for an lockable locked thing (this is the mention locked lockable rule): add "locked" to the tagline.] A first property-aggregation rule for an openable lockable open unlocked thing (this is the mention open openables rule): add "open" to the tagline. A property-aggregation rule for an openable lockable closed unlocked thing (this is the mention closed openables rule): add "closed" to the tagline. A property-aggregation rule for an openable lockable closed locked thing (this is the mention locked openables rule): add "locked" to the tagline. A property-aggregation rule for a lockable thing (called the lockable thing) (this is the mention matching key of lockable rule): let X be the matching key of the lockable thing; if X is not nothing: add "match [X]" to the tagline. A property-aggregation rule for an edible off-stage thing (this is the mention eaten edible rule): add "eaten" to the tagline. The last property-aggregation rule (this is the print aggregated properties rule): if the number of entries in the tagline is greater than 0: say " ([tagline])"; rule succeeds; rule fails; """) source += textwrap.dedent("""\ An objective is some text that varies. The objective is "{objective}". """.format(objective=objective)) # Special command to print the objective of the game, if any. source += textwrap.dedent("""\ Printing the objective is an action applying to nothing. Carry out printing the objective: say "[objective]". Understand "goal" as printing the objective. """) # Customize reporting of the "take" action. # Ref: http://inform7.com/learn/man/RB_6_8.html source += textwrap.dedent("""\ The taking action has an object called previous locale (matched as "from"). Setting action variables for taking: now previous locale is the holder of the noun. Report taking something from the location: say "You pick up [the noun] from the ground." instead. Report taking something: say "You take [the noun] from [the previous locale]." instead. Report dropping something: say "You drop [the noun] on the ground." instead. """) # Special command to print game state. source += textwrap.dedent("""\ The print state option is a truth state that varies. The print state option is usually false. Turning on the print state option is an action applying to nothing. Carry out turning on the print state option: Now the print state option is true. Turning off the print state option is an action applying to nothing. Carry out turning off the print state option: Now the print state option is false. Printing the state is an activity. Rule for printing the state: let R be the location of the player; say "Room: [line break] [the internal name of R][line break]"; [say "[line break]"; carry out the printing the content of the room activity;] say "[line break]"; carry out the printing the content of the world activity; say "[line break]"; carry out the printing the content of the inventory activity; say "[line break]"; carry out the printing the content of nowhere activity; say "[line break]". Printing the entire state is an action applying to nothing. Carry out printing the entire state: say "-=STATE START=-[line break]"; carry out the printing the state activity; say "[line break]Score:[line break] [score]/[maximum score][line break]"; say "[line break]Objective:[line break] [objective][line break]"; say "[line break]Inventory description:[line break]"; say " You are carrying: [a list of things carried by the player].[line break]"; say "[line break]Room description:[line break]"; try looking; say "[line break]-=STATE STOP=-"; When play begins: if print state option is true: try printing the entire state; Every turn: if print state option is true: try printing the entire state; When play ends: if print state option is true: try printing the entire state; After looking: carry out the printing the things on the floor activity. Understand "print_state" as printing the entire state. Understand "enable print state option" as turning on the print state option. Understand "disable print state option" as turning off the print state option. """) # Disable implicitly opening/unlocking door. source += textwrap.dedent("""\ Before going through a closed door (called the blocking door): say "You have to open the [blocking door] first."; stop. Before opening a locked door (called the locked door): let X be the matching key of the locked door; if X is nothing: say "The [locked door] is welded shut."; otherwise: say "You have to unlock the [locked door] with the [X] first."; stop. Before opening a locked container (called the locked container): let X be the matching key of the locked container; if X is nothing: say "The [locked container] is welded shut."; otherwise: say "You have to unlock the [locked container] with the [X] first."; stop. """) # Add new actions. source += textwrap.dedent("""\ Displaying help message is an action applying to nothing. Carry out displaying help message: say "[fixed letter spacing]Available commands:[line break]"; say " look: describe the current room[line break]"; say " goal: print the goal of this game[line break]"; say " inventory: print player's inventory[line break]"; say " go <dir>: move the player north, east, south or west[line break]"; say " examine <something>: examine something more closely[line break]"; say " eat <something>: eat something edible[line break]"; say " open <something>: open a door or a container[line break]"; say " close <something>: close a door or a container[line break]"; say " drop <something>: drop an object on the floor[line break]"; say " take <something>: take an object that is on the floor[line break]"; say " put <something> on <something>: place an object on a supporter[line break]"; say " take <something> from <something>: take an object from a container or a supporter[line break]"; say " insert <something> into <something>: place an object into a container[line break]"; say " lock <something> with <something>: lock a door or a container with a key[line break]"; say " unlock <something> with <something>: unlock a door or a container with a key[line break]"; Understand "help" as displaying help message. """) # Disable take/get all. source += textwrap.dedent("""\ Taking all is an action applying to nothing. Carry out taking all: say "You have to be more specific!". Understand "take all" as taking all. Understand "get all" as taking all. Understand "pick up all" as taking all. Understand "take each" as taking all. Understand "get each" as taking all. Understand "pick up each" as taking all. Understand "take everything" as taking all. Understand "get everything" as taking all. Understand "pick up everything" as taking all. """) # Special command to restrict possible actions. source += textwrap.dedent("""\ The restrict commands option is a truth state that varies. The restrict commands option is usually false. Turning on the restrict commands option is an action applying to nothing. Carry out turning on the restrict commands option: Decrease turn count by 1; Now the restrict commands option is true. Understand "restrict commands" as turning on the restrict commands option. """) # If "restrict commands" mode is on, force the player to mention where to # take the object from. source += textwrap.dedent("""\ The taking allowed flag is a truth state that varies. The taking allowed flag is usually false. Before removing something from something: now the taking allowed flag is true. After removing something from something: now the taking allowed flag is false. Before taking a thing (called the object) when the object is on a supporter (called the supporter): if the restrict commands option is true and taking allowed flag is false: say "Can't see any [object] on the floor! Try taking the [object] from the [supporter] instead."; rule fails. Before of taking a thing (called the object) when the object is in a container (called the container): if the restrict commands option is true and taking allowed flag is false: say "Can't see any [object] on the floor! Try taking the [object] from the [container] instead."; rule fails. """) # Add dummy object to detect end of the objects tree. source += textwrap.dedent("""\ There is a EndOfObject. """) # Indent using \t instead of spaces because of Inform6. while True: last = source source = re.sub("(^ *) ", r"\1\t", source, flags=re.MULTILINE) if source == last: break return source
def populate_room( self, nb_objects: int, room: Variable, rng: Optional[RandomState] = None, object_types_probs: Optional[Dict[str, float]] = None ) -> List[Proposition]: rng = g_rng.next() if rng is None else rng state = [] types_counts = data.get_types().count(self.state) inventory = Variable("I", "I") objects_holder = [inventory, room] locked_or_closed_objects = [] lockable_objects = [] for s in self.facts: # Look for containers and supporters to put stuff in/on them. if s.name == "at" and s.arguments[0].type in [ "c", "s" ] and s.arguments[1].name == room.name: objects_holder.append(s.arguments[0]) # Look for containers and doors without a matching key. if s.name == "at" and s.arguments[0].type in [ "c", "d" ] and s.arguments[1].name == room.name: obj_propositions = [ p.name for p in self.facts if s.arguments[0].name in p.names ] if "match" not in obj_propositions and s.arguments[ 0] not in lockable_objects: lockable_objects.append(s.arguments[0]) if "locked" in obj_propositions or "closed" in obj_propositions: locked_or_closed_objects.append(s.arguments[0]) object_id = 0 while object_id < nb_objects: if len(locked_or_closed_objects) > 0: # Prioritize adding key if there are locked or closed things in the room. obj_type = "k" else: obj_type = data.get_types().sample(parent_type='t', rng=rng, exceptions=["d", "r"], include_parent=False, probs=object_types_probs) if data.get_types().is_descendant_of(obj_type, "o"): obj_name = get_new(obj_type, types_counts) obj = Variable(obj_name, obj_type) allowed_objects_holder = list(objects_holder) if obj_type == "k": if len(locked_or_closed_objects) > 0: # Look for a *locked* container or a door. rng.shuffle(locked_or_closed_objects) locked_or_closed_obj = locked_or_closed_objects.pop() state.append( Proposition("match", [obj, locked_or_closed_obj])) lockable_objects.remove(locked_or_closed_obj) # Do not place the key in its own matching container. if locked_or_closed_obj in allowed_objects_holder: allowed_objects_holder.remove(locked_or_closed_obj) elif len(lockable_objects) > 0: # Look for a container or a door. rng.shuffle(lockable_objects) lockable_obj = lockable_objects.pop() state.append(Proposition("match", [obj, lockable_obj])) else: continue # Unuseful key is not allowed. elif obj_type == "f": # HACK: manually add the edible property to food items. state.append(Proposition("edible", [obj])) # Place the object somewhere. obj_holder = rng.choice(allowed_objects_holder) if data.get_types().is_descendant_of(obj_holder.type, "s"): state.append(Proposition("on", [obj, obj_holder])) elif data.get_types().is_descendant_of(obj_holder.type, "c"): state.append(Proposition("in", [obj, obj_holder])) elif data.get_types().is_descendant_of(obj_holder.type, "I"): state.append(Proposition("in", [obj, obj_holder])) elif data.get_types().is_descendant_of(obj_holder.type, "r"): state.append(Proposition("at", [obj, obj_holder])) else: raise ValueError( "Unknown type for object holder: {}".format( obj_holder)) elif data.get_types().is_descendant_of(obj_type, "s"): supporter_name = get_new(obj_type, types_counts) supporter = Variable(supporter_name, obj_type) state.append(Proposition("at", [supporter, room])) objects_holder.append(supporter) elif data.get_types().is_descendant_of(obj_type, "c"): container_name = get_new(obj_type, types_counts) container = Variable(container_name, obj_type) state.append(Proposition("at", [container, room])) objects_holder.append(container) container_state = rng.choice(["open", "closed", "locked"]) state.append(Proposition(container_state, [container])) lockable_objects.append(container) if container_state in ["locked", "closed"]: locked_or_closed_objects.append(container) else: raise ValueError("Unknown object type: {}".format(obj_type)) object_id += 1 self.add_facts(state) return state
def test_objects_types(self): expected_types = set(data.get_types().types) assert set(self.game.objects_types) == expected_types
def populate_room_with( self, objects: WorldObject, room: WorldRoom, rng: Optional[RandomState] = None) -> List[Proposition]: rng = g_rng.next() if rng is None else rng state = [] objects_holder = [room] locked_or_closed_objects = [] lockable_objects = [] for s in self.facts: # Look for containers and supporters to put stuff in/on them. if s.name == "at" and s.arguments[0].type in [ "c", "s" ] and s.arguments[1].name == room.name: objects_holder.append(s.arguments[0]) # Look for containers and doors without a matching key. if s.name == "at" and s.arguments[0].type in [ "c", "d" ] and s.arguments[1].name == room.name: obj_propositions = [ p.name for p in self.facts if s.arguments[0].name in p.names ] if "match" not in obj_propositions and s.arguments[ 0] not in lockable_objects: lockable_objects.append(s.arguments[0]) if "locked" in obj_propositions or "closed" in obj_propositions: locked_or_closed_objects.append(s.arguments[0]) remaining_objects_id = list(range(len(objects))) rng.shuffle(remaining_objects_id) for idx in remaining_objects_id: obj = objects[idx] obj_type = obj.type if data.get_types().is_descendant_of(obj_type, "o"): allowed_objects_holder = list(objects_holder) # Place the object somewhere. obj_holder = rng.choice(allowed_objects_holder) if data.get_types().is_descendant_of(obj_holder.type, "s"): state.append(Proposition("on", [obj, obj_holder])) elif data.get_types().is_descendant_of(obj_holder.type, "c"): state.append(Proposition("in", [obj, obj_holder])) elif data.get_types().is_descendant_of(obj_holder.type, "r"): state.append(Proposition("at", [obj, obj_holder])) else: raise ValueError( "Unknown type for object holder: {}".format( obj_holder)) elif data.get_types().is_descendant_of(obj_type, "s"): supporter = obj state.append(Proposition("at", [supporter, room])) objects_holder.append(supporter) elif data.get_types().is_descendant_of(obj_type, "c"): container = obj state.append(Proposition("at", [container, room])) objects_holder.append(container) container_state = rng.choice(["open", "closed", "locked"]) state.append(Proposition(container_state, [container])) lockable_objects.append(container) if container_state in ["locked", "closed"]: locked_or_closed_objects.append(container) else: raise ValueError("Unknown object type: {}".format(obj_type)) self.add_facts(state) return state