Beispiel #1
0
    def test_winning_policy(self):
        kb = KnowledgeBase.default()
        quest = QuestProgression(self.quest, kb)
        quest = self._apply_actions_to_quest(quest.winning_policy, quest)
        assert quest.completed
        assert not quest.failed
        assert quest.winning_policy is None

        # Winning policy should be the shortest one leading to a winning event.
        state = self.game.world.state.copy()
        quest = QuestProgression(self.quest, KnowledgeBase.default())
        for i, action in enumerate(self.eventB.actions):
            if i < 2:
                assert quest.winning_policy == self.eventA.actions
            else:
                # After taking the lettuce and putting it in the chest,
                # QuestB becomes the shortest one to complete.
                assert quest.winning_policy == self.eventB.actions[i:]
            assert not quest.done
            state.apply(action)
            quest.update(action, state)

        assert quest.done
        assert quest.completed
        assert not quest.failed
        assert quest.winning_policy is None
Beispiel #2
0
def test_going_through_door():
    P = Variable("P", "P")
    room = Variable("room", "r")
    kitchen = Variable("kitchen", "r")
    state = State(KnowledgeBase.default().logic)
    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.max_length = 3
    options.subquests = True
    options.create_variables = True
    options.rules_per_depth = [
        [KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"]],
        KnowledgeBase.default().rules.get_matching("go.*"),
        [KnowledgeBase.default().rules["open/d"]],
    ]

    chains = list(get_chains(state, options))
    assert len(chains) == 18
Beispiel #3
0
def test_get_new():
    rng = np.random.RandomState(1234)
    types_counts = {t: rng.randint(2, 10) for t in KnowledgeBase.default().types}
    orig_types_counts = deepcopy(types_counts)
    for t in KnowledgeBase.default().types:
        name = get_new(t, types_counts)
        splits = name.split("_")
        assert splits[0] == t
        assert int(splits[1]) == orig_types_counts[t]
        assert types_counts[t] == orig_types_counts[t] + 1
Beispiel #4
0
def test_serialization_deserialization():
    rule = KnowledgeBase.default().rules["go/east"]
    mapping = {
        Placeholder("r'"): Variable("room1", "r"),
        Placeholder("r"): Variable("room2"),
    }
    mapping.update(KnowledgeBase.default().types.constants_mapping)
    action = rule.instantiate(mapping)
    infos = action.serialize()
    action2 = Action.deserialize(infos)
    assert action == action2
    def test_failed(self):
        quest = QuestProgression(self.quest, KnowledgeBase.default())
        quest = self._apply_actions_to_quest(self.eating_carrot.actions, quest)
        assert not quest.completed
        assert quest.failed
        assert quest.winning_policy is None

        quest = QuestProgression(self.quest, KnowledgeBase.default())
        quest = self._apply_actions_to_quest(self.eating_lettuce.actions, quest)
        assert not quest.completed
        assert quest.failed
        assert quest.winning_policy is None
Beispiel #6
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 KnowledgeBase.default().rules.keys() if "-" not in k
    ]
    assert len(basic_rules) == 19

    for rule in KnowledgeBase.default().rules.values():
        infos = rule.serialize()
        loaded_rule = Rule.deserialize(infos)
        assert loaded_rule == rule
Beispiel #7
0
    def test_completed(self):
        quest = QuestProgression(self.quest, KnowledgeBase.default())
        quest = self._apply_actions_to_quest(self.eventA.actions, quest)
        assert quest.completed
        assert not quest.failed
        assert quest.winning_policy is None

        # Alternative winning strategy.
        quest = QuestProgression(self.quest, KnowledgeBase.default())
        quest = self._apply_actions_to_quest(self.eventB.actions, quest)
        assert quest.completed
        assert not quest.failed
        assert quest.winning_policy is None
Beispiel #8
0
 def __init__(self):
     self.backward = False
     self.min_depth = 1
     self.max_depth = 1
     self.min_breadth = 1
     self.max_breadth = 1
     self.subquests = False
     self.independent_chains = False
     self.create_variables = False
     self.fixed_mapping = KnowledgeBase.default().types.constants_mapping
     self.rng = None
     self.logic = KnowledgeBase.default().logic
     self.rules_per_depth = []
     self.restricted_types = frozenset()
Beispiel #9
0
    def add(self, *entities: List["WorldEntity"]) -> None:
        """ Add children to this entity. """
        if KnowledgeBase.default().types.is_descendant_of(self.type, "r"):
            name = "at"
        elif KnowledgeBase.default().types.is_descendant_of(self.type, ["c", "I"]):
            name = "in"
        elif KnowledgeBase.default().types.is_descendant_of(self.type, "s"):
            name = "on"
        else:
            raise ValueError("Unexpected type {}".format(self.type))

        for entity in entities:
            self.add_fact(name, entity, self)
            self.content.append(entity)
Beispiel #10
0
    def remove(self, *entities):
        if KnowledgeBase.default().types.is_descendant_of(self.type, "r"):
            name = "at"
        elif KnowledgeBase.default().types.is_descendant_of(
                self.type, ["c", "I"]):
            name = "in"
        elif KnowledgeBase.default().types.is_descendant_of(self.type, "s"):
            name = "on"
        else:
            raise ValueError("Unexpected type {}".format(self.type))

        for entity in entities:
            self.remove_fact(name, entity, self)
            self.content.remove(entity)
            entity.parent = None
Beispiel #11
0
    def door(self, door: WorldEntity) -> None:
        if door is not None and not KnowledgeBase.default(
        ).types.is_descendant_of(door.type, "d"):
            msg = "Expecting a WorldEntity of 'door' type."
            raise TypeError(msg)

        self._door = door
Beispiel #12
0
 def __init__(self) -> None:
     self._state = State(KnowledgeBase.default().logic)
     self._entities = OrderedDict()
     self._rooms = []
     self._objects = []
     self._update()
     self._player_room = None
Beispiel #13
0
def test_reloading_game_with_custom_kb():
    twl = KnowledgeBase.default().logic._document
    twl += """
        type customobj : o {
            inform7 {
                type {
                    kind :: "custom-obj-like";
                }
            }
        }
    """

    logic = GameLogic.parse(twl)
    options = GameOptions()
    options.kb = KnowledgeBase(logic, "")
    M = GameMaker(options)

    room = M.new_room("room")
    M.set_player(room)

    custom_obj = M.new(type='customobj', name='customized object')
    M.inventory.add(custom_obj)

    commands = ["drop customized object"]
    quest = M.set_quest_from_commands(commands)
    assert quest.commands == tuple(commands)
    game = M.build()
    assert game == Game.deserialize(game.serialize())
Beispiel #14
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)
Beispiel #15
0
def build_state(locked_door=False):
    # Set up a world with two rooms and a few objecs.
    P = Variable("P")
    I = Variable("I")
    bedroom = Variable("bedroom", "r")
    kitchen = Variable("kitchen", "r")
    rusty_key = Variable("rusty key", "k")
    small_key = Variable("small key", "k")
    wooden_door = Variable("wooden door", "d")
    chest = Variable("chest", "c")
    cabinet = Variable("cabinet", "c")
    robe = Variable("robe", "o")

    state = State(KnowledgeBase.default().logic, [
        Proposition("at", [P, bedroom]),
        Proposition("south_of", [kitchen, bedroom]),
        Proposition("north_of", [bedroom, kitchen]),
        Proposition("link", [bedroom, wooden_door, kitchen]),
        Proposition("link", [kitchen, wooden_door, bedroom]),

        Proposition("locked" if locked_door else "closed", [wooden_door]),

        Proposition("in", [rusty_key, I]),
        Proposition("match", [rusty_key, chest]),
        Proposition("locked", [chest]),
        Proposition("at", [chest, kitchen]),
        Proposition("in", [small_key, chest]),

        Proposition("match", [small_key, cabinet]),
        Proposition("locked", [cabinet]),
        Proposition("at", [cabinet, bedroom]),
        Proposition("in", [robe, cabinet]),
    ])

    return state
Beispiel #16
0
    def __init__(self, world: World, grammar: Optional[Grammar] = None,
                 quests: Iterable[Quest] = (),
                 kb: Optional[KnowledgeBase] = None) -> None:
        """
        Args:
            world: The world to use for the game.
            quests: The quests to be done in the game.
            grammar: The grammar to control the text generation.
        """
        self.world = world
        self.quests = tuple(quests)
        self.metadata = {}
        self._objective = None
        self._infos = self._build_infos()
        self.kb = kb or KnowledgeBase.default()
        self.extras = {}

        # Check if we can derive a global winning policy from the quests.
        self.main_quest = None
        policy = GameProgression(self).winning_policy
        if policy:
            win_event = Event(actions=policy)
            self.main_quest = Quest(win_events=[win_event])

        self.change_grammar(grammar)
Beispiel #17
0
    def new(self,
            type: str,
            name: Optional[str] = None,
            desc: Optional[str] = None) -> Union[WorldEntity, WorldRoom]:
        """ Creates new entity given its type.

        Args:
            type: The type of the entity.
            name: The name of the entity.
            desc: The description of the entity.

        Returns:
            The newly created entity.

            * If the `type` is `'r'`, then a `WorldRoom` object is returned.
            * Otherwise, a `WorldEntity` is returned.
        """
        var_id = type
        if not KnowledgeBase.default().types.is_constant(type):
            var_id = get_new(type, self._types_counts)

        var = Variable(var_id, type)
        if type == "r":
            entity = WorldRoom(var, name, desc)
            self.rooms.append(entity)
        else:
            entity = WorldEntity(var, name, desc)

        self._entities[var_id] = entity
        return entity
Beispiel #18
0
 def __init__(self, kb: Optional[KnowledgeBase] = None) -> None:
     self.kb = kb or KnowledgeBase.default()
     self._state = State(self.kb.logic)
     self._entities = OrderedDict()
     self._rooms = []
     self._objects = []
     self._update()
     self._player_room = None
Beispiel #19
0
    def test_generating_quests(self):
        g_rng.set_seed(2018)
        map_ = make_small_map(n_rooms=5, possible_door_states=["open"])
        world = World.from_map(map_)

        def _rule_to_skip(rule):
            # Examine, look and inventory shouldn't be used for chaining.
            if rule.name.startswith("look"):
                return True

            if rule.name.startswith("inventory"):
                return True

            if rule.name.startswith("examine"):
                return True

            return False

        for max_depth in range(1, 3):
            for rule in KnowledgeBase.default().rules.values():
                if _rule_to_skip(rule):
                    continue

                options = ChainingOptions()
                options.backward = True
                options.max_depth = max_depth
                options.max_length = 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
                assert len(actions) == max_depth, rule.name
                quest = Quest(win_events=[Event(actions)])
                tmp_world = World.from_facts(chain.initial_state.facts)

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

                assert quest.is_winning(state)

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

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

                assert quest.is_winning(state)
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_)

    def _rule_to_skip(rule):
        # Examine, look and inventory shouldn't be used for chaining.
        if rule.name.startswith("look"):
            return True

        if rule.name.startswith("inventory"):
            return True

        if rule.name.startswith("examine"):
            return True

        return False

    for rule in KnowledgeBase.default().rules.values():
        if _rule_to_skip(rule):
            continue

        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.won

            game_state, _, done = env.step(event.commands[0])
            assert done
            assert game_state.won
Beispiel #21
0
def is_seq(chain, game_infos):
    """ Check if we have a theoretical chain in actions. """
    seq = MergeAction()

    room_placeholder = Placeholder('r')

    action_mapping = KnowledgeBase.default().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 = KnowledgeBase.default().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
Beispiel #22
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(KnowledgeBase.default().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(KnowledgeBase.default().text_grammars_path,
                          filename)) as f:
                grammar_contents.extend(f.readlines())

        self._parse(grammar_contents)
Beispiel #23
0
def test_room_connections():
    kb = KnowledgeBase.default()

    room0 = Variable("room0", "r")
    room1 = Variable("room1", "r")
    room2 = Variable("room2", "r")

    # Only one connection can exist between two rooms.
    # r1
    # |
    # r0 - r1
    state = State(kb.logic, [
        Proposition("north_of", [room1, room0]),
        Proposition("south_of", [room0, room1]),
        Proposition("east_of", [room1, room0]),
        Proposition("west_of", [room0, room1])
    ])

    assert not check_state(state)

    # Non Cartesian layout are allowed.
    # r1
    # |
    # r0 - r2 - r1
    state = State(kb.logic, [
        Proposition("north_of", [room1, room0]),
        Proposition("south_of", [room0, room1]),
        Proposition("east_of", [room2, room0]),
        Proposition("west_of", [room0, room2]),
        Proposition("east_of", [room1, room2]),
        Proposition("west_of", [room2, room1])
    ])

    assert check_state(state)

    # A room cannot have more than 4 'link' propositions.
    room3 = Variable("room3", "r")
    room4 = Variable("room4", "r")
    room5 = Variable("room5", "r")
    door1 = Variable("door1", "d")
    door2 = Variable("door2", "d")
    door3 = Variable("door3", "d")
    door4 = Variable("door4", "d")
    door5 = Variable("door5", "d")

    state = State(kb.logic, [
        Proposition("link", [room0, door1, room1]),
        Proposition("link", [room0, door2, room2]),
        Proposition("link", [room0, door3, room3]),
        Proposition("link", [room0, door4, room4]),
        Proposition("link", [room0, door5, room5])
    ])

    assert not check_state(state)
Beispiel #24
0
    def test_count(self):
        rng = np.random.RandomState(1234)
        types_counts = {t: rng.randint(2, 10) for t in self.types.variables}

        state = State(KnowledgeBase.default().logic)
        for t in self.types.variables:
            v = Variable(get_new(t, types_counts), t)
            state.add_fact(Proposition("dummy", [v]))

        counts = self.types.count(state)
        for t in self.types.variables:
            assert counts[t] == types_counts[t], (counts[t], types_counts[t])
Beispiel #25
0
def test_get_reverse_action():
    kb = KnowledgeBase.default()
    for rule in kb.rules.values():
        empty_state = State(KnowledgeBase.default().logic)
        action = maybe_instantiate_variables(rule, kb.types.constants_mapping.copy(), empty_state)
        r_action = kb.get_reverse_action(action)

        if rule.name.startswith("eat"):
            assert r_action is None
        else:
            assert r_action is not None

            # Check if that when applying the reverse rule we can re-obtain
            # the previous state.
            state = State(KnowledgeBase.default().logic, action.preconditions)

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

            r_state = new_state.copy()
            r_state.apply(r_action)
            assert state == r_state
Beispiel #26
0
def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None):
    types_counts = KnowledgeBase.default().types.count(state)

    # Instantiate variables if needed
    try:
        for ph in rule.placeholders:
            if mapping.get(ph) is None:
                name = get_new(ph.type, types_counts, max_types_counts)
                mapping[ph] = Variable(name, ph.type)
    except NotEnoughNounsError:
        return None

    return rule.instantiate(mapping)
Beispiel #27
0
 def __init__(self):
     self.backward = False
     self.min_depth = 1
     self.max_depth = 1
     self.min_breadth = 1
     self.max_breadth = 1
     self.subquests = False
     self.independent_chains = False
     self.create_variables = False
     self.kb = KnowledgeBase.default()
     self.rng = None
     self.rules_per_depth = []
     self.restricted_types = frozenset()
def check_state(state):
    fail = Proposition("fail", [])

    constraints = state.all_applicable_actions(KnowledgeBase.default().constraints.values())
    for constraint in constraints:
        if state.is_applicable(constraint):
            # Optimistically delay copying the state
            copy = state.copy()
            copy.apply(constraint)

            if copy.is_fact(fail):
                return False

    return True
Beispiel #29
0
def expand_clean_replace(symbol, grammar, obj, game_infos):
    """ Return a cleaned/keyword replaced symbol. """
    obj_infos = game_infos[obj.id]
    phrase = grammar.expand(symbol)
    phrase = phrase.replace("(obj)", obj_infos.id)
    phrase = phrase.replace("(name)", obj_infos.name)
    phrase = phrase.replace("(name-n)", obj_infos.noun if obj_infos.adj is not None else obj_infos.name)
    phrase = phrase.replace("(name-adj)", obj_infos.adj if obj_infos.adj is not None else grammar.expand("#ordinary_adj#"))
    if obj.type != "":
        phrase = phrase.replace("(name-t)", KnowledgeBase.default().types.get_description(obj.type))
    else:
        assert False, "Does this even happen?"

    return fix_determinant(phrase)
Beispiel #30
0
 def __init__(self) -> None:
     """
     Creates an empty world, with a player and an empty inventory.
     """
     self._entities = {}
     self.quests = []
     self.rooms = []
     self.paths = []
     self._types_counts = KnowledgeBase.default().types.count(State())
     self.player = self.new(type='P')
     self.inventory = self.new(type='I')
     self.grammar = textworld.generator.make_grammar()
     self._game = None
     self._distractors_facts = []