Ejemplo n.º 1
0
    def __init__(self, flags: Mapping = {}, rng: Optional[RandomState] = None):
        """
        Create a grammar.

        :param flags:
            Flags guiding the text generation process.
            TODO: describe expected flags.
        :param rng:
            Random generator used for sampling tag expansions.
        """
        self.flags = flags
        self.grammar = OrderedDict()
        self.rng = g_rng.next() if rng is None else rng
        self.allowed_variables_numbering = self.flags.get("allowed_variables_numbering", False)
        self.unique_expansion = self.flags.get("unique_expansion", False)
        self.all_expansions = defaultdict(list)

        # The current used symbols
        self.overflow_dict = OrderedDict()
        self.used_names = set(self.flags.get("names_to_exclude", []))

        # Load the grammar associated to the provided theme.
        self.theme = self.flags.get("theme", "house")
        grammar_contents = []

        # Load the object names file
        files = os.listdir(data.get_text_grammars_path())
        files = [f for f in files if f.startswith(self.theme + "_") and f.endswith(".twg")]
        for filename in files:
            with open(pjoin(data.get_text_grammars_path(), filename)) as f:
                grammar_contents.extend(f.readlines())

        self._parse(grammar_contents)
Ejemplo n.º 2
0
    def __init__(self, options: Union[GrammarOptions, Mapping] = {}, rng: Optional[RandomState] = None):
        """
        Create a grammar.

        Arguments:
        options:
            For customizing text generation process (see
            :py:class:`textworld.generator.GrammarOptions <textworld.generator.text_grammar.GrammarOptions>`
            for the list of available options).
        :param 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
        grammar_contents = []

        # Load the object names file
        files = os.listdir(data.get_text_grammars_path())
        files = [f for f in files if f.startswith(self.theme + "_") and f.endswith(".twg")]
        for filename in files:
            with open(pjoin(data.get_text_grammars_path(), filename)) as f:
                grammar_contents.extend(f.readlines())

        self._parse(grammar_contents)
Ejemplo n.º 3
0
    def __init__(self, options: Union[GrammarOptions, Mapping[str, Any]] = {}, rng: Optional[RandomState] = None):
        """
        Arguments:
            options:
                For customizing text generation process (see
                :py:class:`textworld.generator.GrammarOptions <textworld.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)
Ejemplo n.º 4
0
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)
    return Quest(chain.actions)
Ejemplo n.º 5
0
def make_game(world_size: int,
              nb_objects: int,
              quest_length: int,
              grammar_flags: Mapping = {},
              rngs: Optional[Dict[str, RandomState]] = None) -> Game:
    """
    Make a game (map + objects + quest).

    Arguments:
        world_size: Number of rooms in the world.
        nb_objects: Number of objects in the world.
        quest_length: Minimum nb. of actions the quest requires to be completed.
        grammar_flags: Options for the grammar.

    Returns:
        Generated game.
    """
    if rngs is None:
        rngs = {}
        rng = g_rng.next()
        rngs['rng_map'] = RandomState(rng.randint(65635))
        rngs['rng_objects'] = RandomState(rng.randint(65635))
        rngs['rng_quest'] = RandomState(rng.randint(65635))
        rngs['rng_grammar'] = RandomState(rng.randint(65635))

    # Generate only the map for now (i.e. without any objects)
    world = make_world(world_size, nb_objects=0, rngs=rngs)

    # Sample a quest according to quest_length.
    chain = sample_quest(world.state,
                         rngs['rng_quest'],
                         max_depth=quest_length,
                         nb_retry=20,
                         allow_partial_match=True,
                         backward=True,
                         exceptions=["r"])
    quest = Quest([c.action for c in chain])

    # Set the initial state required for the quest.
    world.state = chain[0].state

    # Add distractors objects (i.e. not related to the quest)
    world.populate(nb_objects, rng=rngs['rng_objects'])

    grammar = make_grammar(grammar_flags, rng=rngs['rng_grammar'])
    game = make_game_with(world, [quest], grammar)
    return game
Ejemplo n.º 6
0
def make_game(world_size: int,
              nb_objects: int,
              quest_length: int,
              grammar_flags: Mapping = {},
              rngs: Optional[Dict[str, RandomState]] = None) -> Game:
    """
    Make a game (map + objects + quest).

    Arguments:
        world_size: Number of rooms in the world.
        nb_objects: Number of objects in the world.
        quest_length: Minimum nb. of actions the quest requires to be completed.
        grammar_flags: Options for the grammar.

    Returns:
        Generated game.
    """
    if rngs is None:
        rngs = {}
        rng = g_rng.next()
        rngs['rng_map'] = RandomState(rng.randint(65635))
        rngs['rng_objects'] = RandomState(rng.randint(65635))
        rngs['rng_quest'] = RandomState(rng.randint(65635))
        rngs['rng_grammar'] = RandomState(rng.randint(65635))

    # Generate only the map for now (i.e. without any objects)
    world = make_world(world_size, nb_objects=0, rngs=rngs)

    # Sample a quest according to quest_length.
    options = ChainingOptions()
    options.backward = True
    options.max_depth = quest_length
    options.create_variables = True
    options.rng = rngs['rng_quest']
    options.restricted_types = {"r", "d"}
    chain = sample_quest(world.state, options)
    quest = Quest(chain.actions)

    # Set the initial state required for the quest.
    world.state = chain.initial_state

    # Add distractors objects (i.e. not related to the quest)
    world.populate(nb_objects, rng=rngs['rng_objects'])

    grammar = make_grammar(grammar_flags, rng=rngs['rng_grammar'])
    game = make_game_with(world, [quest], grammar)
    return game
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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_
Ejemplo n.º 10
0
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.
    chain = sample_quest(state,
                         rng,
                         max_depth=quest_length,
                         nb_retry=20,
                         rules_per_depth=rules_per_depth,
                         backward=backward)
    quest = [c.action for c in chain]
    return Quest(quest)
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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 `textworld.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:
                '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 `textworld.g_rng`).

    Returns
    -------
        Seeds that will be used for the game generation.
    """
    keys = ['seed_map', 'seed_objects', 'seed_quest', 'seed_grammar']

    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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
    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 self.kb.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 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, "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 = obj
                state.append(Proposition("at", [supporter, room]))
                objects_holder.append(supporter)

            elif self.kb.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
Ejemplo n.º 17
0
    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
Ejemplo n.º 18
0
def make_game(world_size: int, nb_objects: int, quest_length: int, quest_breadth: int,
              grammar_flags: Mapping = {},
              rngs: Optional[Dict[str, RandomState]] = None
              ) -> Game:
    """
    Make a game (map + objects + quest).

    Arguments:
        world_size: Number of rooms in the world.
        nb_objects: Number of objects in the world.
        quest_length: Minimum nb. of actions the quest requires to be completed.
        quest_breadth: How many branches the quest can have.
        grammar_flags: Options for the grammar.

    Returns:
        Generated game.
    """
    if rngs is None:
        rngs = {}
        rng = g_rng.next()
        rngs['rng_map'] = RandomState(rng.randint(65635))
        rngs['rng_objects'] = RandomState(rng.randint(65635))
        rngs['rng_quest'] = RandomState(rng.randint(65635))
        rngs['rng_grammar'] = RandomState(rng.randint(65635))

    # Generate only the map for now (i.e. without any objects)
    world = make_world(world_size, nb_objects=0, rngs=rngs)

    # Sample a quest according to quest_length.
    class Options(ChainingOptions):

        def get_rules(self, depth):
            if depth == 0:
		        # Last action should not be "go <dir>".
                return data.get_rules().get_matching("^(?!go.*).*")
            else:
                return super().get_rules(depth)

    options = Options()
    options.backward = True
    options.min_depth = 1
    options.max_depth = quest_length
    options.min_breadth = 1
    options.max_breadth = quest_breadth
    options.create_variables = True
    options.rng = rngs['rng_quest']
    options.restricted_types = {"r", "d"}
    chain = sample_quest(world.state, options)

    subquests = []
    for i in range(1, len(chain.nodes)):
        if chain.nodes[i].breadth != chain.nodes[i - 1].breadth:
            quest = Quest(chain.actions[:i])
            subquests.append(quest)

    quest = Quest(chain.actions)
    subquests.append(quest)

    # Set the initial state required for the quest.
    world.state = chain.initial_state

    # Add distractors objects (i.e. not related to the quest)
    world.populate(nb_objects, rng=rngs['rng_objects'])

    grammar = make_grammar(grammar_flags, rng=rngs['rng_grammar'])
    game = make_game_with(world, subquests, grammar)
    game.change_grammar(grammar)

    return game