예제 #1
0
    def _update(self) -> None:
        self._leaves_values = []
        self._leaves_elements = []

        for node in self:
            if len(node.children) == 0:
                self._leaves_elements.append(node.element)
                self._leaves_values.append(node.element.value)

        self._leaves_values = uniquify(self._leaves_values)
        self._leaves_elements = uniquify(self._leaves_elements)
예제 #2
0
    def _update(self):
        self._leaves_values = []
        self._leaves_elements = set()

        def _visit(node):
            if len(node.children) == 0:
                self._leaves_elements.add(node.element)
                self._leaves_values.append(node.element.value)

        if self.root is not None:
            self._postorder(self.root, _visit)

        self._leaves_values = uniquify(self._leaves_values)
예제 #3
0
    def get_all_expansions_for_type(self, type: str):
        """
        Get all possible expansions for a given object type.

        Parameters
        ----------
        type :
            Object type.

        Returns
        -------
        names :
            All possible names.
        """
        expansions = self.get_all_expansions_for_tag("#({})#".format(type))
        for room_type in self.grammar["#room_type#"]:
            expansions += self.get_all_expansions_for_tag("#{}_({})#".format(room_type, type))

        return uniquify(expansions)
예제 #4
0
    def get_facts_in_scope(self) -> List[Proposition]:
        facts = []
        facts += [
            fact for exit in self.player_room.exits.values()
            for fact in exit.related_facts
        ]
        facts += [
            fact for door in self.player_room.doors.values()
            for fact in door.related_facts
        ]
        facts += [
            fact for obj in self.get_visible_objects_in(self.player_room)
            for fact in obj.related_facts
        ]
        facts += [
            fact for obj in self.get_objects_in_inventory()
            for fact in obj.related_facts
        ]

        return uniquify(facts)
예제 #5
0
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
예제 #6
0
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