def __init__(self, options: Union[GrammarOptions, Mapping[str, Any]] = {}, rng: Optional[RandomState] = None): """ Arguments: options: For customizing text generation process (see :py:class:`tw_textlabs.generator.GrammarOptions <tw_textlabs.generator.text_grammar.GrammarOptions>` for the list of available options). rng: Random generator used for sampling tag expansions. """ self.options = GrammarOptions(options) self.grammar = OrderedDict() self.rng = g_rng.next() if rng is None else rng self.allowed_variables_numbering = self.options.allowed_variables_numbering self.unique_expansion = self.options.unique_expansion self.all_expansions = defaultdict(list) # The current used symbols self.overflow_dict = OrderedDict() self.used_names = set(self.options.names_to_exclude) # Load the grammar associated to the provided theme. self.theme = self.options.theme # Load the object names file files = glob.glob( pjoin(KnowledgeBase.default().text_grammars_path, glob.escape(self.theme) + "_*.twg")) for filename in files: self._parse(filename) for k, v in self.grammar.items(): self.grammar[k] = tuple(v)
def make_quest(world, quest_length, rng=None, rules_per_depth=(), backward=False): state = world if hasattr(world, "state"): state = world.state rng = g_rng.next() if rng is None else rng # Sample a quest according to quest_length. options = ChainingOptions() options.backward = backward options.max_depth = quest_length options.rng = rng options.rules_per_depth = rules_per_depth chain = sample_quest(state, options) event = Event(chain.actions) return Quest(win_events=[event])
def make_map(n_rooms, size=None, rng=None, possible_door_states=["open", "closed", "locked"]): """ Make a map. Parameters ---------- n_rooms : int Number of rooms in the map. size : tuple of int Size (height, width) of the grid delimiting the map. """ rng = g_rng.next() if rng is None else rng if size is None: edge_size = int(np.ceil(np.sqrt(n_rooms + 1))) size = (edge_size, edge_size) map = create_map(rng, n_rooms, size[0], size[1], possible_door_states) return map
def make_small_map(n_rooms, rng=None, possible_door_states=["open", "closed", "locked"]): """ Make a small map. The map will contains one room that connects to all others. Parameters ---------- n_rooms : int Number of rooms in the map (maximum of 5 rooms). possible_door_states : list of str, optional Possible states doors can have. """ rng = g_rng.next() if rng is None else rng if n_rooms > 5: raise ValueError("Nb. of rooms of a small map must be less than 6 rooms.") map_ = create_small_map(rng, n_rooms, possible_door_states) return map_
def populate_with(self, objects: List[WorldObject], rng: Optional[RandomState] = None) -> List[Proposition]: rng = g_rng.next() if rng is None else rng room_names = [room.id for room in self.rooms] nb_objects_per_room = {room_name: 0 for room_name in room_names} indices = np.arange(len(room_names)) for _ in range(len(objects)): idx = rng.choice(indices) nb_objects_per_room[room_names[idx]] += 1 state = [] for room in self.rooms: state += self.populate_room_with( objects[:nb_objects_per_room[room.id]], room, rng) objects = objects[nb_objects_per_room[room.id]:] self.add_facts(state) return state
def seeds(self, value: Union[int, Mapping[str, int]]) -> None: keys = ['map', 'objects', 'quest', 'grammar'] def _key_missing(seeds): return not set(seeds.keys()).issuperset(keys) seeds = value if type(value) is int: rng = RandomState(value) seeds = {} elif _key_missing(value): rng = g_rng.next() # Check if we need to generate missing seeds. self._seeds = {} for key in keys: if key in seeds: self._seeds[key] = seeds[key] else: self._seeds[key] = rng.randint(65635)
def populate( self, nb_objects: int, rng: Optional[RandomState] = None, object_types_probs: Optional[Dict[str, float]] = None ) -> List[Proposition]: rng = g_rng.next() if rng is None else rng room_names = [room.id for room in self.rooms] nb_objects_per_room = {room_name: 0 for room_name in room_names} indices = np.arange(len(room_names)) for _ in range(nb_objects): idx = rng.choice(indices) nb_objects_per_room[room_names[idx]] += 1 state = [] for room in self.rooms: state += self.populate_room(nb_objects_per_room[room.id], room, rng, object_types_probs) return state
def make_world(world_size, nb_objects=0, rngs=None): """ Make a world (map + objects). Parameters ---------- world_size : int Number of rooms in the world. nb_objects : int Number of objects in the world. """ if rngs is None: rngs = {} rng = g_rng.next() rngs['map'] = RandomState(rng.randint(65635)) rngs['objects'] = RandomState(rng.randint(65635)) map_ = make_map(n_rooms=world_size, rng=rngs['map']) world = World.from_map(map_) world.set_player_room() world.populate(nb_objects=nb_objects, rng=rngs['objects']) return world
def get_seeds_for_game_generation( seeds: Optional[Union[int, Dict[str, int]]] = None) -> Dict[str, int]: """ Get all seeds needed for game generation. Parameters ---------- seeds : optional Seeds for the different generation processes. If None, seeds will be sampled from `tw_textlabs.g_rng`. If a 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: 'map': control the map generation; 'objects': control the type of objects and their location; 'quest': control the quest generation; 'surface': control the text generation; For any key missing, a random number gets assigned (sampled from `tw_textlabs.g_rng`). Returns ------- Seeds that will be used for the game generation. """ keys = ['map', 'objects', 'quest', 'surface'] def _key_missing(seeds): return not set(seeds.keys()).issuperset(keys) if type(seeds) is int: rng = np.random.RandomState(seeds) seeds = {} elif seeds is None or _key_missing(seeds): rng = g_rng.next() # Check if we need to generate missing seeds. for key in keys: if key not in seeds: seeds[key] = rng.randint(65635) return seeds
def make_grammar(options: Mapping = {}, rng: Optional[RandomState] = None) -> Grammar: rng = g_rng.next() if rng is None else rng grammar = Grammar(options, rng) grammar.check() return grammar
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 KnowledgeBase.default().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 KnowledgeBase.default().types.is_descendant_of( obj_holder.type, "s"): state.append(Proposition("on", [obj, obj_holder])) elif KnowledgeBase.default().types.is_descendant_of( obj_holder.type, "c"): state.append(Proposition("in", [obj, obj_holder])) elif KnowledgeBase.default().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 KnowledgeBase.default().types.is_descendant_of(obj_type, "s"): supporter = obj state.append(Proposition("at", [supporter, room])) objects_holder.append(supporter) elif KnowledgeBase.default().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
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 = KnowledgeBase.default().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 = KnowledgeBase.default().types.sample( parent_type='t', rng=rng, exceptions=["d", "r"], include_parent=False, probs=object_types_probs) if KnowledgeBase.default().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 KnowledgeBase.default().types.is_descendant_of( obj_holder.type, "s"): state.append(Proposition("on", [obj, obj_holder])) elif KnowledgeBase.default().types.is_descendant_of( obj_holder.type, "c"): state.append(Proposition("in", [obj, obj_holder])) elif KnowledgeBase.default().types.is_descendant_of( obj_holder.type, "I"): state.append(Proposition("in", [obj, obj_holder])) elif KnowledgeBase.default().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 KnowledgeBase.default().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 KnowledgeBase.default().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