예제 #1
0
def visualize(world: Union[Game, State, GlulxGameState, World],
              interactive: bool = False):
    """
    Show the current state of the world.
    :param world: Object representing a game state to be visualized.
    :param interactive: Whether or not to visualize the state in the browser.
    :return: Image object of the visualization.
    """
    try:
        import webbrowser
        from textworld.render.serve import get_html_template
    except ImportError:
        raise ImportError(
            'Visualization dependencies not installed. Try running `pip install textworld[vis]`'
        )

    if isinstance(world, Game):
        game = world
        state = load_state(game.world, game.infos)
        state["objective"] = game.objective
    elif isinstance(world, GlulxGameState):
        state = load_state_from_game_state(game_state=world)
    elif isinstance(world, World):
        state = load_state(world)
    elif isinstance(world, State):
        state = world
        world = World.from_facts(state.facts)
        state = load_state(world)
    else:
        raise ValueError("Don't know how to visualize: {!r}".format(world))

    state["command"] = ""
    state["history"] = ""
    html = get_html_template(game_state=json.dumps(state))
    tmpdir = maybe_mkdir(pjoin(tempfile.gettempdir(), "textworld"))
    fh, filename = tempfile.mkstemp(suffix=".html", dir=tmpdir, text=True)
    url = 'file://' + filename
    with open(filename, 'w') as f:
        f.write(html)

    img_graph = take_screenshot(url, id="world")
    img_inventory = take_screenshot(url, id="inventory")
    image = concat_images(
        img_inventory,
        img_graph,
    )

    if interactive:
        try:
            webbrowser.open(url)
        finally:
            return image

    return image
예제 #2
0
    def test_win_action(self):
        g_rng.set_seed(2018)
        map_ = make_small_map(n_rooms=5, possible_door_states=["open"])
        world = World.from_map(map_)

        for max_depth in range(1, 3):
            for rule in data.get_rules().values():
                options = ChainingOptions()
                options.backward = True
                options.max_depth = max_depth
                options.create_variables = True
                options.rules_per_depth = [[rule]]
                options.restricted_types = {"r"}
                chain = sample_quest(world.state, options)

                # Build the quest by providing the actions.
                actions = chain.actions
                if len(actions) != max_depth:
                    print(chain)
                assert len(actions) == max_depth, rule.name
                quest = Quest(actions)
                tmp_world = World.from_facts(chain.initial_state.facts)

                state = tmp_world.state
                for action in actions:
                    assert not state.is_applicable(quest.win_action)
                    state.apply(action)

                assert state.is_applicable(quest.win_action)

                # Build the quest by only providing the winning conditions.
                quest = Quest(actions=None,
                              winning_conditions=actions[-1].postconditions)
                tmp_world = World.from_facts(chain.initial_state.facts)

                state = tmp_world.state
                for action in actions:
                    assert not state.is_applicable(quest.win_action)
                    state.apply(action)

                assert state.is_applicable(quest.win_action)
예제 #3
0
def test_quest_winning_condition():
    g_rng.set_seed(2018)
    map_ = make_small_map(n_rooms=5, possible_door_states=["open"])
    world = World.from_map(map_)

    for rule in data.get_rules().values():
        chain = sample_quest(world.state,
                             rng=None,
                             max_depth=1,
                             nb_retry=20,
                             allow_partial_match=True,
                             backward=True,
                             rules_per_depth={0: [rule]},
                             exceptions=["r"])
        assert len(chain) > 0, rule.name
        quest = Quest([c.action for c in chain])

        # Set the initial state required for the quest.
        tmp_world = World.from_facts(chain[0].state.facts)
        game = make_game_with(tmp_world, [quest], make_grammar({}))

        if tmp_world.player_room is None:
            # Randomly place the player in the world since
            # the action doesn't care about where the player is.
            tmp_world.set_player_room()

        game_name = "test_quest_winning_condition_" + rule.name.replace(
            "/", "_")
        with make_temp_directory(prefix=game_name) as tmpdir:
            game_file = compile_game(game, game_name, games_folder=tmpdir)

            env = textworld.start(game_file)
            env.reset()
            game_state, _, done = env.step("look")
            assert not done
            assert not game_state.has_won

            game_state, _, done = env.step(quest.commands[0])
            assert done
            assert game_state.has_won
예제 #4
0
def test_quest_winning_condition():
    g_rng.set_seed(2018)
    map_ = make_small_map(n_rooms=5, possible_door_states=["open"])
    world = World.from_map(map_)

    for rule in KnowledgeBase.default().rules.values():
        options = ChainingOptions()
        options.backward = True
        options.max_depth = 1
        options.create_variables = True
        options.rules_per_depth = [[rule]]
        options.restricted_types = {"r"}
        chain = sample_quest(world.state, options)
        assert len(chain.actions) > 0, rule.name
        event = Event(chain.actions)
        quest = Quest(win_events=[event])

        # Set the initial state required for the quest.
        tmp_world = World.from_facts(chain.initial_state.facts)
        game = make_game_with(tmp_world, [quest], make_grammar({}))

        if tmp_world.player_room is None:
            # Randomly place the player in the world since
            # the action doesn't care about where the player is.
            tmp_world.set_player_room()

        game_name = "test_quest_winning_condition_" + rule.name.replace(
            "/", "_")
        with make_temp_directory(prefix=game_name) as tmpdir:
            game_file = compile_game(game, path=tmpdir)

            env = textworld.start(game_file)
            env.reset()
            game_state, _, done = env.step("look")
            assert not done
            assert not game_state.has_won

            game_state, _, done = env.step(event.commands[0])
            assert done
            assert game_state.has_won
예제 #5
0
def test_get_objects_in_inventory():
    P = Variable("P")
    I = Variable("I")
    room = Variable("room", "r")
    obj = Variable("obj", "o")

    # Closed chest.
    facts = [Proposition("at", [P, room]),
             Proposition("in", [obj, I])]

    world = World.from_facts(facts)
    objects = world.get_objects_in_inventory()
    assert obj in world.objects
    assert obj in objects
예제 #6
0
def test_get_all_objects_in():
    P = Variable("P")
    room = Variable("room", "r")
    chest = Variable("chest", "c")
    obj = Variable("obj", "o")
    facts = [Proposition("at", [P, room]),
             Proposition("at", [chest, room]),
             Proposition("in", [obj, chest]),
             Proposition("closed", [chest])]

    world = World.from_facts(facts)
    objects = world.get_all_objects_in(world.player_room)
    assert obj in world.objects
    assert obj in objects
예제 #7
0
def test_populate_with():
    # setup
    P = Variable('P')
    I = Variable('I')
    room = Variable('room', 'r')
    facts = [Proposition('at', [P, room])]

    world = World.from_facts(facts)

    # test
    obj = Variable('obj', 'o')
    world.populate_with(objects=[obj])

    assert obj in world.objects
    assert (Proposition('at', [obj, room]) in world.facts or Proposition('in', [obj, I]) in world.facts)
예제 #8
0
def load_state_from_game_state(game_state: GlulxGameState, format: str = 'png', limit_player_view: bool = False) -> dict:
    """
    Generates serialization of game state.

    :param game_state: The current game state to visualize.
    :param format: The graph output format (png, svg, pdf, ...)
    :param limit_player_view: Whether to limit the player's view. Default: False.
    :return: The graph generated from this World
    """
    game_infos = game_state.game_infos
    game_infos["objective"] = game_state.objective
    last_action = game_state.action
    # Create a world from the current state's facts.
    world = World.from_facts(game_state.state.facts)
    return load_state(world, game_infos, last_action, format, limit_player_view)
예제 #9
0
def test_cannot_automatically_positioning_rooms():
    P = Variable("P")
    r0 = Variable("Room0", "r")
    r1 = Variable("Room1", "r")
    r2 = Variable("Room2", "r")
    r3 = Variable("Room3", "r")
    r4 = Variable("Room4", "r")
    r5 = Variable("Room5", "r")
    d = Variable("door", "d")

    facts = [Proposition("at", [P, r0])]
    facts.extend(connect(r0, 'north', r1))
    facts.extend(connect(r0, 'east', r2))
    facts.extend(connect(r0, 'south', r3))
    facts.extend(connect(r0, 'west', r4))

    world = World.from_facts(facts)
    npt.assert_raises(NoFreeExitError, world.add_fact, Proposition("link", [r0, d, r5]))
예제 #10
0
def test_automatically_positioning_rooms():
    P = Variable("P")
    r1 = Variable("Room1", "r")
    r2 = Variable("Room2", "r")
    d = Variable("door", "d")
    facts = [Proposition("at", [P, r1])]
    world = World.from_facts(facts)
    assert len(world.rooms) == 1
    assert len(world.find_room_by_id(r1.name).exits) == 0

    world.add_fact(Proposition("link", [r1, d, r2]))
    assert len(world.rooms) == 2
    r1_entity = world.find_room_by_id(r1.name)
    r2_entity = world.find_room_by_id(r2.name)
    assert len(r1_entity.exits) == 1
    assert len(r2_entity.exits) == 1

    assert list(r1_entity.exits.keys())[0] == reverse_direction(list(r2_entity.exits.keys())[0])
예제 #11
0
파일: render.py 프로젝트: taodav/TextWorld
def visualize(world: Union[Game, State, GlulxGameState, World],
              interactive: bool = False):
    """
    Show the current state of the world.
    :param world: Object representing a game state to be visualized.
    :param interactive: Whether or not to visualize the state in the browser.
    :return: Image object of the visualization.
    """
    if isinstance(world, Game):
        game = world
        state = load_state(game.world, game.infos)
        state["objective"] = ""
        if len(game.quests) > 0:
            state["objective"] = game.quests[0].desc
    elif isinstance(world, GlulxGameState):
        state = load_state_from_game_state(game_state=world)
    elif isinstance(world, World):
        state = load_state(world)
    elif isinstance(world, State):
        state = world
        world = World.from_facts(state.facts)
        state = load_state(world)
    else:
        raise ValueError("Don't know how to visualize: {!r}".format(world))

    state["command"] = ""
    state["history"] = ""
    html = textworld.render.serve.get_html_template(
        game_state=json.dumps(state))
    tmpdir = maybe_mkdir(pjoin(tempfile.gettempdir(), "textworld"))
    fh, filename = tempfile.mkstemp(suffix=".html", dir=tmpdir, text=True)
    url = 'file://' + filename
    with open(filename, 'w') as f:
        f.write(html)

    image = take_screenshot(url)
    if interactive:
        try:
            webbrowser.open(url)
        finally:
            return image

    return image
예제 #12
0
def test_making_a_game(play_the_game=False):
    rng_map = np.random.RandomState(1234)
    map_ = textworld.generator.make_small_map(1, rng_map)
    world = World.from_map(map_)
    world.set_player_room()  # First room generated (i.e. the only one).

    rng_objects = np.random.RandomState(123)
    nb_objects = 10
    world.populate(nb_objects, rng=rng_objects)

    options = textworld.GameOptions()
    options.chaining.max_depth = 5
    options.chaining.max_breadth = 2
    options.chaining.rng = np.random.RandomState(124)
    quests = make_quest(world, options)

    # Define the grammar we'll use.
    rng_grammar = np.random.RandomState(1234)
    grammar_flags = {
        "theme": "house",
        "include_adj": False,
        "only_last_action": True,
        "blend_instructions": True,
        "blend_descriptions": True,
        "refer_by_name_only": True,
        "instruction_extension": [],
    }
    grammar = textworld.generator.make_grammar(grammar_flags, rng=rng_grammar)

    # Generate the world representation.
    game = textworld.generator.make_game_with(world, quests, grammar)

    with make_temp_directory(prefix="test_render_wrapper") as tmpdir:
        options = textworld.GameOptions()
        options.path = tmpdir
        game_file = compile_game(game, options)

        if play_the_game:
            textworld.play(game_file)
예제 #13
0
def generate_never_ending_game_old(args):
    g_rng.set_seed(args.seed)
    msg = "--max-steps {} --nb-objects {} --nb-rooms {} --seed {}"
    print(
        msg.format(args.max_steps, args.nb_objects, args.nb_rooms, g_rng.seed))
    print("Generating game...")

    map_ = textworld.generator.make_map(n_rooms=args.nb_rooms)
    world = World.from_map(map_)
    world.set_player_room()
    world.populate(nb_objects=args.nb_objects)
    grammar = textworld.generator.make_grammar(flags={"theme": "house"})

    quests = []  # No quest
    game = textworld.generator.make_game_with(world, quests, grammar)

    game_name = "neverending"
    game_file = textworld.generator.compile_game(game,
                                                 game_name,
                                                 force_recompile=True,
                                                 games_folder=args.output)
    return game_file
예제 #14
0
def test_used_names_is_updated(verbose=False):
    # Make generation throughout the framework reproducible.
    g_rng.set_seed(1234)

    # Generate a map that's shape in a cross with room0 in the middle.
    P = Variable('P')
    r = Variable('r_0', 'r')
    k1 = Variable('k_1', 'k')
    k2 = Variable('k_2', 'k')
    c1 = Variable('c_1', 'c')
    c2 = Variable('c_2', 'c')
    facts = [
        Proposition('at', [P, r]),
        Proposition('at', [k1, r]),
        Proposition('at', [k2, r]),
        Proposition('at', [c1, r]),
        Proposition('at', [c2, r]),
        Proposition('match', [k1, c1]),
        Proposition('match', [k2, c2])
    ]
    world = World.from_facts(facts)
    world.set_player_room()  # Set start room to the middle one.
    world.populate_room(10,
                        world.player_room)  # Add objects to the starting room.

    # Generate the world representation.
    grammar = textworld.generator.make_grammar(flags={},
                                               rng=np.random.RandomState(42))
    for k, v in grammar.grammar.items():
        grammar.grammar[k] = v[:2]  # Force reusing variables.

    game = textworld.generator.make_game_with(world, [], grammar)
    for entity_infos in game.infos.values():
        if entity_infos.name is None:
            continue

        assert entity_infos.name in grammar.used_names
예제 #15
0
def filter_observables(world_facts: List[Proposition],
                       verbose=False,
                       game=None):
    fixups = defaultdict(set)
    if not world_facts:
        return None
    world_facts = reconstitute_facts(world_facts)

    # print("WORLD FACTS:")
    for fact in world_facts:
        # print('\t', fact)
        for v in fact.arguments:
            #     print('\t\t{}:{}'.format(v.name, v.type))
            if not v.name:
                v_count = len(fixups[v.type])
                assert v not in fixups[v.type]
                if v.type == 'P' or v.type == 'I' or v.type == 'RECIPE' or v.type == 'MEAL':
                    v.name = v.type
                    if v_count == 0:
                        fixups[v.type].add(v)
                else:
                    v.name = '~{}_{}'.format(v.type, v_count)
                    fixups[v.type].add(v)

    world = World.from_facts(world_facts)
    world_state = world.state

    if 'P' in world_state._vars_by_type:
        players = world_state.variables_of_type('P')
        assert len(players) == 1
    #     for p in players:
    #         player = p
    # else:
    #     player = None

    where_sig = Signature('at', ('P', 'r'))
    where_am_i = world_state.facts_with_signature(where_sig)
    assert len(where_am_i) == 1
    where_fact = list(where_am_i)[0]
    the_player = where_fact.arguments[0]
    player_location = where_fact.arguments[1]
    # if verbose:
    #     print("WORLD FACTS:")
    #     for fact in world_facts:
    #         print('\t', fact)
    #         # print_fact(game, fact)

    # if verbose:
    #     print("VARIABLES:")
    #     for v in world_state.variables:
    #         print('\t\t{}:{}'.format(v.name, v.type))

    print(where_fact, world.player_room)
    facts_in_scope = world.get_facts_in_scope()
    observable_facts = []
    for fact in facts_in_scope:
        # print("***\t\t", fact)
        if is_observable_relation(fact.name):
            if fact != where_fact:
                observable_facts.append(fact)
            else:  # consider the player's current location to be directly observable
                observable_facts.append(fact)
        else:
            pass

    for e in world.player_room.exits:
        if world.state.is_fact(
                Proposition(
                    'free',
                    (world.player_room.exits[e], where_fact.arguments[1]))):
            observable_facts.append(
                Proposition(
                    "{}_of".format(e),
                    (
                        Variable("exit_{}".format(e), 'e'),
                        world.player_room  # the_player
                    )))
    # REFACTORING: the following is now handled within add_extra_door_facts()
    #     else:  # probably a closed door
    #         door_facts = world.state.facts_with_signature(Signature('link', ('r', 'd', 'r')))
    #         for df in door_facts:
    #             if df.arguments[0] == player_location:
    #                 the_door = df.arguments[1]
    #                 observable_facts.append(Proposition("{}_of".format(e), (
    #                                                                 the_player,
    #                                                                 the_door
    #                                                                 )))
    #                 if world.state.is_fact(Proposition('closed', [the_door])):
    #                     observable_facts.append(Proposition('closed', [the_door]))

    add_extra_door_facts(world,
                         world_facts,
                         local_facts=observable_facts,
                         where_fact=where_fact)

    if verbose:
        print("++WORLD FACTS++:")
        for fact in world_facts:
            prettyprint_fact(fact, game=game)
    return observable_facts, player_location
예제 #16
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
예제 #17
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