예제 #1
0
def test_going_through_door():
    P = Variable("P", "P")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = State()
    state.add_facts([
        Proposition("at", [P, room]),
        Proposition("north_of", [kitchen, room]),
        Proposition("free", [kitchen, room]),
        Proposition("free", [room, kitchen]),
        Proposition("south_of", [room, kitchen])
    ])

    options = ChainingOptions()
    options.backward = True
    options.max_depth = 3
    options.subquests = True
    options.create_variables = True
    options.rules_per_depth = [
        [data.get_rules()["take/c"],
         data.get_rules()["take/s"]],
        data.get_rules().get_matching("go.*"),
        [data.get_rules()["open/d"]],
    ]

    chains = list(get_chains(state, options))
    assert len(chains) == 18
예제 #2
0
def test_rules():
    # Make sure the number of basic rules matches the number
    # of rules in rules.txt
    basic_rules = [k for k in data.get_rules().keys() if "-" not in k]
    assert len(basic_rules) == 19

    for rule in data.get_rules().values():
        infos = rule.serialize()
        loaded_rule = Rule.deserialize(infos)
        assert loaded_rule == rule
예제 #3
0
def test_going_through_door():
    P = Variable("P", "P")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = State()
    state.add_facts([
        Proposition("at", [P, room]),
        Proposition("north_of", [kitchen, room]),
        Proposition("free", [kitchen, room]),
        Proposition("free", [room, kitchen]),
        Proposition("south_of", [room, kitchen])
    ])

    # Sample quests.
    chains = []
    rules_per_depth = {
        0: [data.get_rules()["take/c"],
            data.get_rules()["take/s"]],
        1: data.get_rules().get_matching("go.*"),
        2: [data.get_rules()["open/d"]]
    }
    tree_of_possible = chaining.get_chains(state,
                                           max_depth=3,
                                           allow_partial_match=True,
                                           exceptions=[],
                                           rules_per_depth=rules_per_depth,
                                           backward=True)
    chains = list(tree_of_possible.traverse_preorder(subquests=True))
    # chaining.print_chains(chains)
    # 1. take/c(P, room, c_0, o_0, I)
    # 2. take/c(P, room, c_0, o_0, I) -> go/north(P, r_0, room)
    # 3. take/c(P, room, c_0, o_0, I) -> go/north(P, r_0, room) -> open/d(P, r_0, d_0, room)
    # 4. take/c(P, room, c_0, o_0, I) -> go/south(P, kitchen, room)
    # 5. take/c(P, room, c_0, o_0, I) -> go/south(P, kitchen, room) -> open/d(P, kitchen, d_0, room)
    # 6. take/c(P, room, c_0, o_0, I) -> go/east(P, r_0, room)
    # 7. take/c(P, room, c_0, o_0, I) -> go/east(P, r_0, room) -> open/d(P, r_0, d_0, room)
    # 8. take/c(P, room, c_0, o_0, I) -> go/west(P, r_0, room)
    # 9. take/c(P, room, c_0, o_0, I) -> go/west(P, r_0, room) -> open/d(P, r_0, d_0, room)
    # 10. take/s(P, room, s_0, o_0, I)
    # 11. take/s(P, room, s_0, o_0, I) -> go/north(P, r_0, room)
    # 12. take/s(P, room, s_0, o_0, I) -> go/north(P, r_0, room) -> open/d(P, r_0, d_0, room)
    # 13. take/s(P, room, s_0, o_0, I) -> go/south(P, kitchen, room)
    # 14. take/s(P, room, s_0, o_0, I) -> go/south(P, kitchen, room) -> open/d(P, kitchen, d_0, room)
    # 15. take/s(P, room, s_0, o_0, I) -> go/east(P, r_0, room)
    # 16. take/s(P, room, s_0, o_0, I) -> go/east(P, r_0, room) -> open/d(P, r_0, d_0, room)
    # 17. take/s(P, room, s_0, o_0, I) -> go/west(P, r_0, room)
    # 18. take/s(P, room, s_0, o_0, I) -> go/west(P, r_0, room) -> open/d(P, r_0, d_0, room)
    assert len(chains) == 18
예제 #4
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():
                chain = sample_quest(world.state, rng=None, max_depth=max_depth,
                                     nb_retry=30, allow_partial_match=True, backward=True,
                                     rules_per_depth={0: [rule]}, exceptions=["r"])
                assert len(chain) == max_depth, rule.name

                # Build the quest by providing the actions.
                actions = [c.action for c in chain]
                quest = Quest(actions)
                tmp_world = World.from_facts(chain[0].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[0].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)
예제 #5
0
def test_serialization_deserialization():
    rule = data.get_rules()["go/east"]
    mapping = {
        Placeholder("r'"): Variable("room1", "r"),
        Placeholder("r"): Variable("room2"),
    }
    mapping.update(data.get_types().constants_mapping)
    action = rule.instantiate(mapping)
    infos = action.serialize()
    action2 = Action.deserialize(infos)
    assert action == action2
예제 #6
0
def is_seq(chain, game_infos):
    """ Check if we have a theoretical chain in actions. """
    seq = MergeAction()

    room_placeholder = Placeholder('r')

    action_mapping = data.get_rules()[chain[0].name].match(chain[0])
    for ph, var in action_mapping.items():
        if ph.type not in ["P", "I"]:
            seq.mapping[ph] = var
            seq.const.append(var)

    for c in chain:
        c_action_mapping = data.get_rules()[c.name].match(c)

        # Update our action name
        seq.name += "_{}".format(c.name.split("/")[0])

        # We break a chain if we move rooms
        if c_action_mapping[room_placeholder] != seq.mapping[room_placeholder]:
            return False, seq

        # Update the mapping
        for ph, var in c_action_mapping.items():
            if ph.type not in ["P", "I"]:
                if ph in seq.mapping and var != seq.mapping[ph]:
                    return False, seq
                else:
                    seq.mapping[ph] = var

        # Remove any objects that we no longer use
        tmp = list(filter(lambda x: x in c_action_mapping.values(), seq.const))

        # If all original objects are gone, the seq is broken
        if len(tmp) == 0:
            return False, seq

        # Update our obj list
        seq.const = tmp

    return True, seq
예제 #7
0
def test_to_networkx():
    # Below copied from test_chaining()
    allowed_rules = data.get_rules().get_matching("take/.*")
    allowed_rules += data.get_rules().get_matching("go.*")
    allowed_rules += data.get_rules().get_matching("insert.*", "put.*")
    allowed_rules += data.get_rules().get_matching("open.*", "close.*")
    allowed_rules += data.get_rules().get_matching("lock.*", "unlock.*")
    allowed_rules += data.get_rules().get_matching("eat.*")

    # No possible action since the wooden door is locked and
    # the player doesn't have the key.
    state = build_state(locked_door=True)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    chains = list(tree.traverse_preorder())
    assert len(chains) == 0

    # The door is now closed instead of locked.
    state = build_state(locked_door=False)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    G, labels = tree.to_networkx()
    assert G is not None
    assert labels is not None
    assert len(labels) > 0
예제 #8
0
def test_print_chains_backwards():
    from textworld.generator import print_chains

    allowed_rules = data.get_rules().get_matching("take/.*")
    allowed_rules += data.get_rules().get_matching("go.*")
    allowed_rules += data.get_rules().get_matching("insert.*", "put.*")
    allowed_rules += data.get_rules().get_matching("open.*", "close.*")
    allowed_rules += data.get_rules().get_matching("lock.*", "unlock.*")
    allowed_rules += data.get_rules().get_matching("eat.*")

    # No possible action since the wooden door is locked and
    # the player doesn't have the key.
    state = build_state(locked_door=True)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    chains = list(tree.traverse_preorder())
    assert len(chains) == 0

    # The door is now closed instead of locked.
    state = build_state(locked_door=False)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    chains = list(tree.traverse_preorder())

    # Only validates that printing chains does not raise exception.
    with testing.capture_stdout() as stdout:
        print_chains(chains, backward=True)
        stdout.seek(0)
        assert len(stdout.read()) > 0
예제 #9
0
def test_chaining():
    # The following test depends on the available rules,
    # so instead of depending on what is in rules.txt,
    # we define the allowed_rules to used.
    allowed_rules = data.get_rules().get_matching("take/.*")
    allowed_rules += data.get_rules().get_matching("go.*")
    allowed_rules += data.get_rules().get_matching("insert.*", "put.*")
    allowed_rules += data.get_rules().get_matching("open.*", "close.*")
    allowed_rules += data.get_rules().get_matching("lock.*", "unlock.*")
    allowed_rules += data.get_rules().get_matching("eat.*")

    class Options(ChainingOptions):
        def get_rules(self, depth):
            return allowed_rules

    options = Options()
    options.max_depth = 5

    # No possible action since the wooden door is locked and
    # the player doesn't have the key.
    state = build_state(locked_door=True)
    chains = list(get_chains(state, options))
    assert len(chains) == 0

    # The door is now closed instead of locked.
    state = build_state(locked_door=False)
    chains = list(get_chains(state, options))
    assert len(chains) == 5

    # With more depth.
    state = build_state(locked_door=False)
    options.max_depth = 20
    chains = list(get_chains(state, options))
    assert len(chains) == 9
예제 #10
0
def make_game(options: GameOptions) -> Game:
    """
    Make a game (map + objects + quest).

    Arguments:
        options:
            For customizing the game generation (see
            :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>`
            for the list of available options).

    Returns:
        Generated game.
    """
    rngs = options.rngs

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

    # Sample a quest.
    chaining_options = options.chaining.copy()
    chaining_options.rules_per_depth = [
        get_rules().get_matching("^(?!go.*).*")
    ]
    chaining_options.backward = True
    chaining_options.create_variables = True
    chaining_options.rng = rngs['quest']
    chaining_options.restricted_types = {"r", "d"}
    chain = sample_quest(world.state, chaining_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(options.nb_objects, rng=rngs['objects'])

    grammar = make_grammar(options.grammar, rng=rngs['grammar'])
    game = make_game_with(world, subquests, grammar)
    game.change_grammar(grammar)
    game.metadata["uuid"] = options.uuid

    return game
예제 #11
0
def main():
    args = parse_args()

    P = Variable("P")
    # I = Variable("I")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = [
        Proposition("at", [P, room]),
        Proposition("north_of", [kitchen, room]),
        Proposition("free", [kitchen, room]),
        Proposition("free", [room, kitchen]),
        Proposition("south_of", [room, kitchen])
    ]

    # Sample quests.
    rng = np.random.RandomState(args.seed)
    chains = []
    # rules_per_depth = {0: [data.get_rules()["take/c"], data.get_rules()["take/s"]],
    #                    1: [data.get_rules()["open/c"]],
    #                    }
    rules_per_depth = {
        0: [data.get_rules()["eat"]],
        1: data.get_rules().get_matching("take/s.*"),
        2: data.get_rules().get_matching("go.*"),
        3: [data.get_rules()["open/d"]],
        4: [data.get_rules()["unlock/d"]],
        5: data.get_rules().get_matching("take/s.*", "take/c.*")
    }

    for i in range(args.nb_quests):
        chain = textworld.logic.sample_quest(state,
                                             rng,
                                             max_depth=args.quest_length,
                                             allow_partial_match=True,
                                             exceptions=[],
                                             rules_per_depth=rules_per_depth,
                                             backward=True)
        chains.append(chain[::-1])

    print_chains(chains, verbose=args.verbose)
    actions_tree = build_tree_from_chains(chains)

    # Convert tree to networkx graph/tree
    filename = "sample_tree.svg"
    G, labels = actions_tree.to_networkx()
    if len(G) > 0:
        tree = nx.bfs_tree(G, actions_tree.no)
        save_graph_to_svg(tree, labels, filename, backward=True)
    else:
        try:
            os.remove(filename)
        except:
            pass
예제 #12
0
파일: game.py 프로젝트: zp312/TextWorld
 def __init__(self,
              world: World,
              grammar: Optional[Grammar] = None,
              quests: Optional[List[Quest]] = None) -> None:
     """
     Args:
         world: The world to use for the game.
         quests: The quests to done in the game.
         grammar: The grammar to control the text generation.
     """
     self.world = world
     self.state = world.state.copy()  # Current state of the game.
     self.grammar = grammar
     self.quests = [] if quests is None else quests
     self.metadata = {}
     self._infos = self._build_infos()
     self._rules = data.get_rules()
     self._types = data.get_types()
예제 #13
0
def test_backward_chaining():
    P = Variable("P", "P")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = State([
        Proposition("at", [P, room]),
        Proposition("north_of", [kitchen, room]),
        Proposition("south_of", [room, kitchen]),
    ])

    rules_per_depth = {
        0: [data.get_rules()["take/c"],
            data.get_rules()["take/s"]],
        1: [data.get_rules()["open/c"]]
    }

    tree_of_possible = chaining.get_chains(state,
                                           max_depth=2,
                                           allow_partial_match=True,
                                           exceptions=['d'],
                                           rules_per_depth=rules_per_depth,
                                           backward=True)

    chains = list(tree_of_possible.traverse_preorder(subquests=True))
    assert len(chains) == 3
    for chain in chains:
        for depth, action in enumerate(chain):
            assert action.action.name in [
                rule.name for rule in rules_per_depth[depth]
            ]

    rules_per_depth = {
        0: [data.get_rules()["put"]],
        1: [data.get_rules()["go/north"]],
        2: [data.get_rules()["take/c"]]
    }

    tree_of_possible = chaining.get_chains(state,
                                           max_depth=3,
                                           allow_partial_match=True,
                                           exceptions=['d'],
                                           rules_per_depth=rules_per_depth,
                                           backward=True)

    chains = list(tree_of_possible.traverse_preorder(subquests=True))
    assert len(chains) == 3
    for chain in chains:
        for depth, action in enumerate(chain):
            assert action.action.name in [
                rule.name for rule in rules_per_depth[depth]
            ]
예제 #14
0
def _get_chains(state,
                root=None,
                max_depth=1,
                allow_parallel_chains=False,
                allow_partial_match=False,
                rng=None,
                exceptions=[],
                max_types_counts=None,
                rules_per_depth={},
                backward=False):

    root = ActionTree(state) if root is None else root

    openset = [root]
    while len(openset) > 0:
        parent = openset.pop()

        rules = rules_per_depth.get(parent.depth, data.get_rules().values())
        assignments = _get_all_assignments(parent.state,
                                           rules=rules,
                                           partial=allow_partial_match,
                                           constrained_types=exceptions,
                                           backward=backward)
        if rng is not None:
            rng.shuffle(assignments)

        for rule, mapping in assignments:
            child = _try_instantiation(rule, mapping, parent,
                                       allow_parallel_chains, max_types_counts,
                                       backward)
            if child:
                child.set_parent(parent)
                parent.children.append(child)

        if len(parent.children) == 0:
            yield parent.get_path()

        if parent.depth + 1 < max_depth:
            openset += parent.children[::-1]
        else:
            for child in parent.children:
                yield child.get_path()
예제 #15
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)
예제 #16
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
예제 #17
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():
        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
        quest = Quest(chain.actions)

        # 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, 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
예제 #18
0
def test_get_reverse_rules(verbose=False):
    for rule in data.get_rules().values():
        r_rule = data.get_reverse_rules(rule)

        if verbose:
            print(rule, r_rule)

        if rule.name.startswith("eat"):
            assert r_rule is None
        else:
            # Check if that when applying the reverse rule we can reobtain
            # the previous state.
            action = maybe_instantiate_variables(
                rule,
                data.get_types().constants_mapping.copy(), State([]))
            state = State(action.preconditions)

            new_state = state.copy()
            assert new_state.apply(action)

            assert r_rule is not None
            actions = list(
                new_state.all_applicable_actions(
                    [r_rule],
                    data.get_types().constants_mapping))
            if len(actions) != 1:
                print(actions)
                print(r_rule)
                print(new_state)
                print(
                    list(
                        new_state.all_instantiations(
                            r_rule,
                            data.get_types().constants_mapping)))
                assert len(actions) == 1
            r_state = new_state.copy()
            r_state.apply(actions[0])
            assert state == r_state
예제 #19
0
def test_chaining():
    # The following test depends on the available rules,
    # so instead of depending on what is in rules.txt,
    # we define the allowed_rules to used.
    allowed_rules = data.get_rules().get_matching("take/.*")
    allowed_rules += data.get_rules().get_matching("go.*")
    allowed_rules += data.get_rules().get_matching("insert.*", "put.*")
    allowed_rules += data.get_rules().get_matching("open.*", "close.*")
    allowed_rules += data.get_rules().get_matching("lock.*", "unlock.*")
    allowed_rules += data.get_rules().get_matching("eat.*")

    # No possible action since the wooden door is locked and
    # the player doesn't have the key.
    state = build_state(locked_door=True)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    chains = list(tree.traverse_preorder())
    assert len(chains) == 0

    # The door is now closed instead of locked.
    state = build_state(locked_door=False)
    tree = chaining.get_chains(
        state,
        max_depth=5,
        rules_per_depth={i: allowed_rules
                         for i in range(5)})
    chains = list(tree.traverse_preorder())
    # chaining.print_chains(chains)
    assert len(chains) == 5

    # With more depth.
    state = build_state(locked_door=False)
    tree = chaining.get_chains(
        state,
        max_depth=20,
        rules_per_depth={i: allowed_rules
                         for i in range(20)})
    chains = list(tree.traverse_preorder())
    assert len(chains) == 9
예제 #20
0
def test_backward_chaining():
    P = Variable("P", "P")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = State([
        Proposition("at", [P, room]),
        Proposition("north_of", [kitchen, room]),
        Proposition("south_of", [room, kitchen]),
    ])

    options = ChainingOptions()
    options.backward = True
    options.max_depth = 2
    options.subquests = True
    options.create_variables = True
    options.rules_per_depth = [
        [data.get_rules()["take/c"],
         data.get_rules()["take/s"]],
        [data.get_rules()["open/c"]],
    ]
    options.restricted_types = {"d"}

    chains = list(get_chains(state, options))
    assert len(chains) == 3

    options = ChainingOptions()
    options.backward = True
    options.max_depth = 3
    options.subquests = True
    options.create_variables = True
    options.rules_per_depth = [
        [data.get_rules()["put"]],
        [data.get_rules()["go/north"]],
        [data.get_rules()["take/c"]],
    ]
    options.restricted_types = {"d"}

    chains = list(get_chains(state, options))
    assert len(chains) == 3
예제 #21
0
def generate_instruction(action, grammar, game_infos, world, counts):
    """
    Generate text instruction for a specific action.
    """
    # Get the more precise command tag.
    cmd_tag = "#{}#".format(action.name)
    if not grammar.has_tag(cmd_tag):
        cmd_tag = "#{}#".format(action.name.split("-")[0])

        if not grammar.has_tag(cmd_tag):
            cmd_tag = "#{}#".format(action.name.split("-")[0].split("/")[0])

    separator_tag = "#action_separator_{}#".format(action.name)
    if not grammar.has_tag(separator_tag):
        separator_tag = "#action_separator_{}#".format(action.name.split("-")[0])

        if not grammar.has_tag(separator_tag):
            separator_tag = "#action_separator_{}#".format(action.name.split("-")[0].split("/")[0])

    if not grammar.has_tag(separator_tag):
        separator_tag = "#action_separator#"

    if not grammar.has_tag(separator_tag):
        separator = ""
    else:
        separator = grammar.expand(separator_tag)

    desc = grammar.expand(cmd_tag)

    # We generate a custom mapping.
    mapping = OrderedDict()
    if isinstance(action, MergeAction):
        action_mapping = action.mapping
    else:
        action_mapping = data.get_rules()[action.name].match(action)

    for ph, var in action_mapping.items():
        if var.type == "r":

            # We can use a simple description for the room
            r = world.find_room_by_id(var.name)  # Match on 'name'
            if r is None:
                mapping[ph.name] = ''
            else:
                mapping[ph.name] = game_infos[r.id].name
        elif var.type in ["P", "I"]:
            continue
        else:
            # We want a more complex description for the objects
            obj = world.find_object_by_id(var.name)
            obj_infos = game_infos[obj.id]

            if grammar.flags.ambiguous_instructions:
                assert False, "not tested"
                choices = []

                for t in ["adj", "noun", "type"]:
                    if counts[t][getattr(obj_infos, t)] <= 1:
                        if t == "noun":
                            choices.append(getattr(obj_infos, t))
                        elif t == "type":
                            choices.append(data.get_types().get_description(getattr(obj_infos, t)))
                        else:
                            # For adj, we pick an abstraction on the type
                            atype = data.get_types().get_description(grammar.rng.choice(data.get_types().get_ancestors(obj.type)))
                            choices.append("{} {}".format(getattr(obj_infos, t), atype))

                # If we have no possibilities, use the name (ie. prioritize abstractions)
                if len(choices) == 0:
                    choices.append(obj_infos.name)

                mapping[ph.name] = grammar.rng.choice(choices)
            else:
                mapping[ph.name] = obj_infos.name

    # Replace the keyword with one of the possibilities
    for keyword in re.findall(r'[(]\S*[)]', desc + separator):
        for key in keyword[1:-1].split("|"):
            if key in mapping:
                desc = desc.replace(keyword, mapping[key])
                separator = separator.replace(keyword, mapping[key])

    return desc, separator
예제 #22
0
 def _get_name_mapping(action):
     mapping = data.get_rules()[action.name].match(action)
     return {
         ph.name: var_infos[var.name].name
         for ph, var in mapping.items()
     }
예제 #23
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.
    """
    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
    metadata["grammar_flags"] = options.grammar.encode()

    rngs = options.rngs
    rng_map = rngs['seed_map']
    rng_objects = rngs['seed_objects']
    rng_quest = rngs['seed_quest']
    rng_grammar = rngs['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=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 = 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).
    options.chaining.rules_per_depth = [data.get_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[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(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
예제 #24
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