Esempio n. 1
0
def test_all_instantiations():
    state = State([
        Proposition.parse("at(P, kitchen: r)"),
        Proposition.parse("in(key: o, kitchen: r)"),
        Proposition.parse("in(egg: o, kitchen: r)"),
        Proposition.parse("in(book: o, study: r)"),
        Proposition.parse("in(book: o, study: r)"),
        Proposition.parse("in(map: o, I)"),
    ])

    take = Rule.parse("take :: $at(P, r) & in(o, r) -> in(o, I)")
    actions = set(state.all_instantiations(take))
    assert actions == {
        Action.parse("take :: $at(P, kitchen: r) & in(key: o, kitchen: r) -> in(key: o, I)"),
        Action.parse("take :: $at(P, kitchen: r) & in(egg: o, kitchen: r) -> in(egg: o, I)"),
    }

    drop = take.inverse(name="drop")
    actions = set(state.all_instantiations(drop))
    assert actions == {
        Action.parse("drop :: $at(P, kitchen: r) & in(map: o, I) -> in(map: o, kitchen: r)"),
    }

    state.apply(*actions)
    actions = set(state.all_instantiations(drop))
    assert len(actions) == 0

    # The state is no longer aware of the I variable, so there are no solutions
    actions = set(state.all_instantiations(take))
    assert len(actions) == 0
Esempio n. 2
0
    def set_conditions(self, conditions: Iterable[Proposition]) -> Action:
        """
        Set the triggering conditions for this event.

        Args:
            conditions: Set of propositions which need to
                        be all true in order for this event
                        to get triggered.
        Returns:
            Action that can only be applied when all conditions are statisfied.
        """
        if not conditions:
            if len(self.actions) == 0:
                raise UnderspecifiedEventError()

            # The default winning conditions are the postconditions of the
            # last action in the quest.
            conditions = self.actions[-1].postconditions

        variables = sorted(set([v for c in conditions for v in c.arguments]))
        event = Proposition("event", arguments=variables)
        self.condition = Action("trigger",
                                preconditions=conditions,
                                postconditions=list(conditions) + [event])
        return self.condition
Esempio n. 3
0
    def set_winning_conditions(
            self,
            winning_conditions: Optional[Collection[Proposition]]) -> Action:
        """ Sets wining conditions for this quest.

        Args:
            winning_conditions: Set of propositions that need to be true
                                before marking the quest as completed.
                                Default: postconditions of the last action.
        Returns:
            An action that is only applicable when the quest is finished.
        """
        if winning_conditions is None:
            if len(self.actions) == 0:
                raise UnderspecifiedQuestError()

            # The default winning conditions are the postconditions of the
            # last action in the quest.
            winning_conditions = self.actions[-1].postconditions

        # TODO: Make win propositions distinguishable by adding arguments?
        win_fact = Proposition("win")
        self.win_action = Action("win",
                                 preconditions=winning_conditions,
                                 postconditions=list(winning_conditions) +
                                 [win_fact])
        return self.win_action
Esempio n. 4
0
def test_match():
    rule = Rule.parse(
        "go :: at(P, r) & $link(r, d, r') & $free(r, r') & $free(r', r) -> at(P, r')"
    )

    mapping = {
        Placeholder.parse("P"): Variable.parse("P"),
        Placeholder.parse("r"): Variable.parse("r1: r"),
        Placeholder.parse("r'"): Variable.parse("r2: r"),
        Placeholder.parse("d"): Variable.parse("d"),
    }

    action = Action.parse(
        "go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r1: r, r2: r) & $free(r2: r, r1: r) -> at(P, r2: r)"
    )
    assert rule.match(action) == mapping

    # Order shouldn't matter

    action = Action.parse(
        "go :: $link(r1: r, d, r2: r) & $free(r1: r, r2: r) & $free(r2: r, r1: r) & at(P, r1: r) -> at(P, r2: r)"
    )
    assert rule.match(action) == mapping

    action = Action.parse(
        "go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r2: r, r1: r) & $free(r1: r, r2: r) -> at(P, r2: r)"
    )
    assert rule.match(action) == mapping

    # Predicate matches can't conflict
    action = Action.parse(
        "go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r2: r, r1: r) & $free(r1: r, r2: r) -> at(P, r3: r)"
    )
    assert rule.match(action) == None
Esempio n. 5
0
    def deserialize(cls, data: Mapping) -> "Event":
        """ Creates an `Event` from serialized data.

        Args:
            data: Serialized data with the needed information to build a
                  `Event` object.
        """
        actions = [Action.deserialize(d) for d in data["actions"]]
        condition = Action.deserialize(data["condition"])
        event = cls(actions, condition.preconditions, data["commands"])
        return event
Esempio n. 6
0
 def get_human_readable_action(self, action: Action) -> Action:
     precondition = list(
         map(self.get_human_readable_fact, action.preconditions))
     postconditions = list(
         map(self.get_human_readable_fact, action.postconditions))
     name = self.kb.inform7_commands[action.name].split("{")[0].strip()
     return Action(name, precondition, postconditions)
Esempio n. 7
0
def test_match_complex():
    rule = Rule.parse(
        "combine/3 :: $at(P, r) & $correct_location(r) & $in(tool, I) & $in(tool', I) & $in(tool'', I) & in(o, I) & in(o', I) & in(o'', I) & $out(o''') & $used(slot) & used(slot') & used(slot'') -> in(o''', I) & free(slot') & free(slot'')"
    )

    mapping = {
        Placeholder.parse("P"): Variable.parse("P"),
        Placeholder.parse("I"): Variable.parse("I"),
        Placeholder.parse("r"): Variable.parse("r"),
        Placeholder.parse("o"): Variable.parse("o1: o"),
        Placeholder.parse("o'"): Variable.parse("o2: o"),
        Placeholder.parse("o''"): Variable.parse("o3: o"),
        Placeholder.parse("o'''"): Variable.parse("o4: o"),
        Placeholder.parse("tool"): Variable.parse("tool1: tool"),
        Placeholder.parse("tool'"): Variable.parse("tool2: tool"),
        Placeholder.parse("tool''"): Variable.parse("tool3: tool"),
        Placeholder.parse("slot"): Variable.parse("slot1: slot"),
        Placeholder.parse("slot'"): Variable.parse("slot2: slot"),
        Placeholder.parse("slot''"): Variable.parse("slot3: slot"),
    }

    action = Action.parse(
        "combine/3 :: $at(P, r) & $correct_location(r) & $in(tool1: tool, I) & $in(tool2: tool, I) & $in(tool3: tool, I) & in(o1: o, I) & in(o2: o, I) & in(o3: o, I) & $out(o4: o) & $used(slot1: slot) & used(slot2: slot) & used(slot3: slot) -> in(o4: o, I) & free(slot2: slot) & free(slot3: slot)"
    )
    for _ in range(10000):
        assert rule.match(action) == mapping
Esempio n. 8
0
    def check_action(self, node: _Node, state: State, action: Action) -> bool:
        # Find the last action before a navigation action
        # TODO: Fold this behaviour into ChainingOptions.check_action()
        nav_parent = node
        while nav_parent.action is not None and self._is_navigation(nav_parent.action):
            # HACK: Going through a door is always considered navigation unless the previous action was to open that door.
            parent = nav_parent.parent
            if parent.action is not None and parent.action.name == "open/d":
                break
            if self.backward and action.name == "close/d":
                break
            nav_parent = parent

        if nav_parent.action is not None and not self._is_navigation(action):
            if self.backward:
                recent = action.inverse()
                pre_navigation = recent
                post_navigation = nav_parent.action.inverse()
            else:
                recent = node.action
                pre_navigation = nav_parent.action
                post_navigation = action

            relevant = set(post_navigation.preconditions)

            if len(recent.added & relevant) == 0 or len(pre_navigation.added & relevant) == 0:
                return False

        return self.options.check_action(state, action)
Esempio n. 9
0
    def apply(self, node: _Node, action: Action) -> Optional[State]:
        """Attempt to apply an action to the given state."""

        new_state = node.state.copy()
        for prop in action.preconditions:
            new_state.add_fact(prop)

        # Make sure new_state still respects the constraints
        if not self.check_state(new_state):
            return None

        new_state.apply(action)

        if not self.check_state(new_state):
            return None

        # Detect cycles
        state = new_state.copy()
        state.apply(action.inverse())
        while node.action:
            state.apply(node.action.inverse())
            if new_state == state:
                return None
            node = node.parent

        return new_state
Esempio n. 10
0
    def test_flatten(self):
        action_lock = Action.parse(
            "lock/c :: $at(P, r) & $at(c, r) & $match(k, c) & $in(k, I) & closed(c) -> locked(c)"
        )
        action_close = Action.parse(
            "close/c :: $at(P, r) & $at(c, r) & open(c) -> closed(c)")
        action_insert1 = Action.parse(
            "insert :: $at(P, r) & $at(c, r) & $open(c) & in(o1: o, I) -> in(o1: o, c)"
        )
        action_insert2 = Action.parse(
            "insert :: $at(P, r) & $at(c, r) & $open(c) & in(o2: o, I) -> in(o2: o, c)"
        )
        action_take1 = Action.parse(
            "take :: $at(P, r) & at(o1: o, r) -> in(o1: o, I)")
        action_take2 = Action.parse(
            "take :: $at(P, r) & at(o2: o, r) -> in(o2: o, I)")
        action_win = Action.parse(
            "win :: $in(o1: o, c) & $in(o2: o, c) & $locked(c) -> win(o1: o, o2: o, c)"
        )

        tree = ActionDependencyTree(element_type=ActionDependencyTreeElement)
        tree.push(action_win)
        tree.push(action_lock)
        tree.push(action_close)
        tree.push(action_insert1)
        tree.push(action_insert2)
        tree.push(action_take1)
        tree.push(action_take2)
        actions = list(a.name for a in tree.flatten())
        assert actions == [
            'take', 'insert', 'take', 'insert', 'close/c', 'lock/c', 'win'
        ], actions
Esempio n. 11
0
    def setUpClass(cls):
        action_lock = Action.parse(
            "lock/c :: $at(P, r) & $at(c, r) & $match(k, c) & $in(k, I) & closed(c) -> locked(c)"
        )
        action_close = Action.parse(
            "close/c :: $at(P, r) & $at(c, r) & open(c) -> closed(c)")
        action_insert1 = Action.parse(
            "insert :: $at(P, r) & $at(c, r) & $open(c) & in(o1: o, I) -> in(o1: o, c)"
        )
        action_insert2 = Action.parse(
            "insert :: $at(P, r) & $at(c, r) & $open(c) & in(o2: o, I) -> in(o2: o, c)"
        )
        action_take1 = Action.parse(
            "take :: $at(P, r) & at(o1: o, r) -> in(o1: o, I)")
        action_take2 = Action.parse(
            "take :: $at(P, r) & at(o2: o, r) -> in(o2: o, I)")
        action_win = Action.parse(
            "win :: $in(o1: o, c) & $in(o2: o, c) & $locked(c) -> win(o1: o, o2: o, c)"
        )

        tree = ActionDependencyTree(element_type=ActionDependencyTreeElement)
        tree.push(action_win)
        tree.push(action_lock)
        tree.push(action_close)
        tree.push(action_insert1)
        tree.push(action_insert2)
        tree.push(action_take1)
        tree.push(action_take2)
        cls.tree = tree
Esempio n. 12
0
def test_logic_parsing():
    P = Variable("P", "P")
    kitchen = Variable("kitchen", "r")
    egg = Variable("egg", "f")

    assert Variable.parse("P") == P
    assert Variable.parse("kitchen: r") == kitchen

    at_kitchen = Proposition("at", [P, kitchen])
    in_kitchen = Proposition("in", [egg, kitchen])
    raw_egg = Proposition("raw", [egg])
    cooked_egg = Proposition("cooked", [egg])

    assert Proposition.parse("at(P, kitchen: r)") == at_kitchen

    assert Signature.parse("at(P, r)") == at_kitchen.signature

    cook_egg = Action("cook", [at_kitchen, in_kitchen, raw_egg],
                      [at_kitchen, in_kitchen, cooked_egg])
    assert Action.parse(
        "cook :: $at(P, kitchen: r) & $in(egg: f, kitchen: r) & raw(egg: f) -> cooked(egg: f)"
    ) == cook_egg

    P = Placeholder("P", "P")
    r = Placeholder("r", "r")
    d = Placeholder("d", "d")
    rp = Placeholder("r'", "r")
    assert Placeholder.parse("P") == P
    assert Placeholder.parse("r") == r
    assert Placeholder.parse("d") == d
    assert Placeholder.parse("r'") == rp

    at_r = Predicate("at", [P, r])
    link = Predicate("link", [r, d, rp])
    unlocked = Predicate("unlocked", [d])
    at_rp = Predicate("at", [P, rp])
    assert Predicate.parse("link(r, d, r')") == link

    go = Rule("go", [at_r, link, unlocked], [link, unlocked, at_rp])
    assert Rule.parse(
        "go :: at(P, r) & $link(r, d, r') & $unlocked(d) -> at(P, r')") == go

    # Make sure the types match in the whole expression
    assert_raises(ValueError, Rule.parse,
                  "take :: $at(P, r) & $in(c, r) & in(o: k, c) -> in(o, I)")
Esempio n. 13
0
    def deserialize(cls, data: Mapping) -> "Quest":
        """ Creates a `Quest` from serialized data.

        Args:
            data: Serialized data with the needed information to build a
                  `Quest` object.
        """
        actions = [Action.deserialize(d) for d in data["actions"]]
        win_action = Action.deserialize(data["win_action"])
        failing_conditions = None
        if data["fail_action"] is not None:
            fail_action = Action.deserialize(data["fail_action"])
            failing_conditions = fail_action.preconditions

        desc = data["desc"]
        quest = cls(actions, win_action.preconditions, failing_conditions, desc=desc)
        quest.commands = data["commands"]
        quest.reward = data.get("reward", 1)
        return quest
Esempio n. 14
0
    def set_failing_conditions(
        self, failing_conditions: Optional[Collection[Proposition]]
    ) -> Optional[Action]:
        """ Sets the failing conditions of this quest.

        Args:
            failing_conditions: Set of propositions that if are all true
                                means the quest is failed.
                                Default: can't fail the quest.
        Returns:
            An action that is only applicable when the quest has failed or `None`
            if the quest can be failed.
        """
        self.fail_action = None
        if failing_conditions is not None:
            self.fail_action = Action("fail", failing_conditions,
                                      [Proposition("fail")])

        return self.fail_action
Esempio n. 15
0
    def set_failing_conditions(self, failing_conditions: Optional[Collection[Proposition]]) -> Optional[Action]:
        """ Sets the failing conditions of this quest.

        Args:
            failing_conditions: Set of propositions that if are all true
                                means the quest is failed.
                                Default: can't fail the quest.
        Returns:
            An action that is only applicable when the quest has failed or `None`
            if the quest can be failed.
        """
        self.fail_action = None
        if failing_conditions is not None:
            # TODO: Make fail propositions distinguishable by adding arguments?
            fail_fact = Proposition("fail")
            self.fail_action = Action("fail", preconditions=failing_conditions,
                                              postconditions=list(failing_conditions) + [fail_fact])

        return self.fail_action
Esempio n. 16
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
Esempio n. 17
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
Esempio n. 18
0
def test_is_sequence_applicable():
    state = State([
        Proposition.parse("at(P, r_1: r)"),
        Proposition.parse("empty(r_2: r)"),
        Proposition.parse("empty(r_3: r)"),
    ])

    assert state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"
        ),
        Action.parse(
            "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"
        ),
    ])

    assert not state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"
        ),
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_1: r)"
        ),
    ])

    assert not state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"
        ),
        Action.parse(
            "go :: at(P, r_3: r) & empty(r_1: r) -> at(P, r_1: r) & empty(r_3: r)"
        ),
    ])
def test_is_sequence_applicable():
    state = State(KnowledgeBase.default().logic, [
        Proposition.parse("at(P, r_1: r)"),
        Proposition.parse("empty(r_2: r)"),
        Proposition.parse("empty(r_3: r)"),
    ])

    assert state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"
        ),
        Action.parse(
            "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"
        ),
    ])

    assert not state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"
        ),
        Action.parse(
            "go :: at(P, r_1: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_1: r)"
        ),
    ])

    assert not state.is_sequence_applicable([
        Action.parse(
            "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"
        ),
        Action.parse(
            "go :: at(P, r_3: r) & empty(r_1: r) -> at(P, r_1: r) & empty(r_3: r)"
        ),
    ])
Esempio n. 20
0
    def set_winning_conditions(
            self,
            winning_conditions: Optional[Collection[Proposition]]) -> Action:
        """ Sets wining conditions for this quest.

        Args:
            winning_conditions: Set of propositions that need to be true
                                before marking the quest as completed.
                                Default: postconditions of the last action.
        Returns:
            An action that is only applicable when the quest is finished.
        """
        if winning_conditions is None:
            if self.actions is None:
                raise UnderspecifiedQuestError()

            # The default winning conditions are the postconditions of the
            # last action in the quest.
            winning_conditions = self.actions[-1].postconditions

        self.win_action = Action("win", winning_conditions,
                                 [Proposition("win")])
        return self.win_action
Esempio n. 21
0
    def remove(self, action: Action) -> Tuple[bool, Optional[Action]]:
        changed = super().remove(action)

        if self.empty:
            return changed, None

        # The last action might have impacted one of the subquests.
        reverse_action = self._kb.get_reverse_action(action)
        if reverse_action is not None:
            changed = self.push(reverse_action)
        elif self.push(action.inverse()):
            # The last action did impact one of the subquests
            # but there's no reverse action to recover from it.
            changed = True

        return changed, reverse_action
Esempio n. 22
0
def test_reverse_rule_and_action():
    logic = GameLogic.parse("""
        type container {
            predicates {
                open(container);
                closed(container);
            }

            rules {
                open  :: closed(container) -> open(container);
                close :: open(container) -> closed(container);
            }

            reverse_rules {
                open :: close;
            }

            inform7 {
                commands {
                    open :: "open {container}" :: "opening the {container}";
                    close :: "close {container}" :: "closing the {container}";
                }
            }

        }
    """)

    open_rule = logic.rules["open"]
    close_rule = logic.rules["close"]
    assert open_rule.reverse_rule == close_rule
    assert close_rule.reverse_rule == open_rule

    open_action = open_rule.instantiate(
        {Placeholder("container", "container"): Variable("c_0", "container")})

    mapping = {"c_0": "chest"}
    assert open_action.format_command(mapping) == "open chest"
    r_open_action = open_action.inverse()
    assert r_open_action.name == "close"
    assert r_open_action.format_command(mapping) == "close chest"

    # Action's command template should persist through serialization.
    open_action2 = Action.deserialize(open_action.serialize())
    open_action2.format_command(mapping) == "open chest"

    assert open_action2.inverse() == r_open_action
Esempio n. 23
0
class Quest:
    """ Quest presentation in TextWorld.

    A quest is a sequence of :py:class:`Action <textworld.logic.Action>`
    undertaken with a goal.
    """

    def __init__(self, actions: Optional[Iterable[Action]] = None,
                 winning_conditions: Optional[Collection[Proposition]] = None,
                 failing_conditions: Optional[Collection[Proposition]] = None,
                 desc: Optional[str] = None) -> None:
        """
        Args:
            actions: The actions to be performed to complete the quest.
                     If `None` or an empty list, then `winning_conditions`
                     must be provided.
            winning_conditions: Set of propositions that need to be true
                                before marking the quest as completed.
                                Default: postconditions of the last action.
            failing_conditions: Set of propositions that if are all true
                                means the quest is failed.
                                Default: can't fail the quest.
            desc: A text description of the quest.
        """
        self.actions = tuple(actions) if actions else ()
        self.desc = desc
        self.commands = gen_commands_from_actions(self.actions)
        self.reward = 1
        self.win_action = self.set_winning_conditions(winning_conditions)
        self.fail_action = self.set_failing_conditions(failing_conditions)

    def set_winning_conditions(self, winning_conditions: Optional[Collection[Proposition]]) -> Action:
        """ Sets wining conditions for this quest.

        Args:
            winning_conditions: Set of propositions that need to be true
                                before marking the quest as completed.
                                Default: postconditions of the last action.
        Returns:
            An action that is only applicable when the quest is finished.
        """
        if winning_conditions is None:
            if len(self.actions) == 0:
                raise UnderspecifiedQuestError()

            # The default winning conditions are the postconditions of the
            # last action in the quest.
            winning_conditions = self.actions[-1].postconditions

        # TODO: Make win propositions distinguishable by adding arguments?
        win_fact = Proposition("win")
        self.win_action = Action("win", preconditions=winning_conditions,
                                        postconditions=list(winning_conditions) + [win_fact])
        return self.win_action

    def set_failing_conditions(self, failing_conditions: Optional[Collection[Proposition]]) -> Optional[Action]:
        """ Sets the failing conditions of this quest.

        Args:
            failing_conditions: Set of propositions that if are all true
                                means the quest is failed.
                                Default: can't fail the quest.
        Returns:
            An action that is only applicable when the quest has failed or `None`
            if the quest can be failed.
        """
        self.fail_action = None
        if failing_conditions is not None:
            # TODO: Make fail propositions distinguishable by adding arguments?
            fail_fact = Proposition("fail")
            self.fail_action = Action("fail", preconditions=failing_conditions,
                                              postconditions=list(failing_conditions) + [fail_fact])

        return self.fail_action

    def __hash__(self) -> int:
        return hash((self.actions,
                     self.win_action,
                     self.fail_action,
                     self.desc,
                     tuple(self.commands)))

    def __eq__(self, other: Any) -> bool:
        return (isinstance(other, Quest) and
                self.actions == other.actions and
                self.win_action == other.win_action and
                self.fail_action == other.fail_action and
                self.desc == other.desc and
                self.reward == other.reward and
                self.commands == other.commands)

    @classmethod
    def deserialize(cls, data: Mapping) -> "Quest":
        """ Creates a `Quest` from serialized data.

        Args:
            data: Serialized data with the needed information to build a
                  `Quest` object.
        """
        actions = [Action.deserialize(d) for d in data["actions"]]
        win_action = Action.deserialize(data["win_action"])
        failing_conditions = None
        if data["fail_action"] is not None:
            fail_action = Action.deserialize(data["fail_action"])
            failing_conditions = fail_action.preconditions

        desc = data["desc"]
        quest = cls(actions, win_action.preconditions, failing_conditions, desc=desc)
        quest.commands = data["commands"]
        quest.reward = data.get("reward", 1)
        return quest

    def serialize(self) -> Mapping:
        """ Serialize this quest.

        Results:
            Quest's data serialized to be JSON compatible
        """
        data = {}
        data["desc"] = self.desc
        data["reward"] = self.reward
        data["commands"] = self.commands
        data["actions"] = [action.serialize() for action in self.actions]
        data["win_action"] = self.win_action.serialize()
        data["fail_action"] = self.fail_action
        if self.fail_action is not None:
            data["fail_action"] = self.fail_action.serialize()

        return data

    def copy(self) -> "Quest":
        """ Copy this quest. """
        return self.deserialize(self.serialize())

    def __str__(self) -> str:
        return " -> ".join(map(str, self.actions))

    def __repr__(self) -> str:
        txt = "Quest({!r}, winning_conditions={!r}, failing_conditions={!r} desc={!r})"
        failing_conditions = None
        if self.fail_action is not None:
            failing_conditions = self.fail_action.preconditions

        return txt.format(self.actions, self.win_action.preconditions,
                          failing_conditions, self.desc)
Esempio n. 24
0
class Event:
    """
    Event happening in TextWorld.

    An event gets triggered when its set of conditions become all statisfied.

    Attributes:
        actions: Actions to be performed to trigger this event
        commands: Human readable version of the actions.
        condition: :py:class:`textworld.logic.Action` that can only be applied
                    when all conditions are statisfied.
    """
    def __init__(
        self,
        actions: Iterable[Action] = (),
        conditions: Iterable[Proposition] = (),
        commands: Iterable[str] = ()
    ) -> None:
        """
        Args:
            actions: The actions to be performed to trigger this event.
                     If an empty list, then `conditions` must be provided.
            conditions: Set of propositions which need to
                        be all true in order for this event
                        to get triggered.
            commands: Human readable version of the actions.
        """
        self.actions = actions
        self.commands = commands
        self.condition = self.set_conditions(conditions)

    @property
    def actions(self) -> Iterable[Action]:
        return self._actions

    @actions.setter
    def actions(self, actions: Iterable[Action]) -> None:
        self._actions = tuple(actions)

    @property
    def commands(self) -> Iterable[str]:
        return self._commands

    @commands.setter
    def commands(self, commands: Iterable[str]) -> None:
        self._commands = tuple(commands)

    def is_triggering(self, state: State) -> bool:
        """ Check if this event would be triggered in a given state. """
        return state.is_applicable(self.condition)

    def set_conditions(self, conditions: Iterable[Proposition]) -> Action:
        """
        Set the triggering conditions for this event.

        Args:
            conditions: Set of propositions which need to
                        be all true in order for this event
                        to get triggered.
        Returns:
            Action that can only be applied when all conditions are statisfied.
        """
        if not conditions:
            if len(self.actions) == 0:
                raise UnderspecifiedEventError()

            # The default winning conditions are the postconditions of the
            # last action in the quest.
            conditions = self.actions[-1].postconditions

        variables = sorted(set([v for c in conditions for v in c.arguments]))
        event = Proposition("event", arguments=variables)
        self.condition = Action("trigger",
                                preconditions=conditions,
                                postconditions=list(conditions) + [event])
        return self.condition

    def __hash__(self) -> int:
        return hash((self.actions, self.commands, self.condition))

    def __eq__(self, other: Any) -> bool:
        return (isinstance(other, Event) and self.actions == other.actions
                and self.commands == other.commands
                and self.condition == other.condition)

    @classmethod
    def deserialize(cls, data: Mapping) -> "Event":
        """ Creates an `Event` from serialized data.

        Args:
            data: Serialized data with the needed information to build a
                  `Event` object.
        """
        actions = [Action.deserialize(d) for d in data["actions"]]
        condition = Action.deserialize(data["condition"])
        event = cls(actions, condition.preconditions, data["commands"])
        return event

    def serialize(self) -> Mapping:
        """ Serialize this event.

        Results:
            `Event`'s data serialized to be JSON compatible.
        """
        data = {}
        data["commands"] = self.commands
        data["actions"] = [action.serialize() for action in self.actions]
        data["condition"] = self.condition.serialize()
        return data

    def copy(self) -> "Event":
        """ Copy this event. """
        return self.deserialize(self.serialize())
Esempio n. 25
0
from typing import List

from textworld.logic import Action, Proposition, State
from textworld.generator.user_query import query_for_important_facts


# noinspection PyAbstractClass
class FakeState(State):
    def __init__(self, parrot_facts: List[Proposition]):
        super().__init__()
        self._facts = parrot_facts

    @property
    def facts(self):
        return self._facts


# generate fake propositions
propositions = []
for i in range(3):
    new_prop = Proposition(name='thing %d' % (i, ))
    propositions.append(new_prop)
fake_state = FakeState(propositions)

# run the test
action = Action(name='Test action',
                preconditions=[],
                postconditions=propositions)
facts = query_for_important_facts(actions=[action], last_game_state=fake_state)
print(facts)