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
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 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 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
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 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)
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
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)
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
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
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
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)")
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 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
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 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_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
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)" ), ])
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
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
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
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)
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())
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)