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_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 test_get_new(): rng = np.random.RandomState(1234) types_counts = {t: rng.randint(2, 10) for t in KnowledgeBase.default().types} orig_types_counts = deepcopy(types_counts) for t in KnowledgeBase.default().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 test_count(self): rng = np.random.RandomState(1234) types_counts = {t: rng.randint(2, 10) for t in self.types.variables} state = State() for t in self.types.variables: v = Variable(get_new(t, types_counts), t) state.add_fact(Proposition("dummy", [v])) counts = self.types.count(state) for t in self.types.variables: assert counts[t] == types_counts[t], (counts[t], types_counts[t])
def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None): types_counts = KnowledgeBase.default().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 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 = self.kb.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 = self.kb.types.sample(parent_type='t', rng=rng, exceptions=["d", "r"], include_parent=False, probs=object_types_probs) if self.kb.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 self.kb.types.is_descendant_of(obj_holder.type, "s"): state.append(Proposition("on", [obj, obj_holder])) elif self.kb.types.is_descendant_of(obj_holder.type, "c"): state.append(Proposition("in", [obj, obj_holder])) elif self.kb.types.is_descendant_of(obj_holder.type, "I"): state.append(Proposition("in", [obj, obj_holder])) elif self.kb.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 self.kb.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 self.kb.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 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. """ kb = KnowledgeBase.default() 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 rngs = options.rngs rng_map = rngs['map'] rng_objects = rngs['objects'] rng_quest = rngs['quest'] rng_grammar = rngs['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 = kb.types.count(world.state) obj_type = kb.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 = kb.types.count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + kb.types.descendants('c') + kb.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 = kb.types.count(world.state) obj_type = kb.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 = [kb.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.initial_state event = Event(chain.actions) quest = Quest( win_events=[event], fail_events=[ Event(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