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())
def test_parallel_quests(): logic = GameLogic.parse(""" type foo { rules { do_a :: not_a(foo) & $not_c(foo) -> a(foo); do_b :: not_b(foo) & $not_c(foo) -> b(foo); do_c :: $a(foo) & $b(foo) & not_c(foo) -> c(foo); } constraints { a_or_not_a :: a(foo) & not_a(foo) -> fail(); b_or_not_b :: b(foo) & not_b(foo) -> fail(); c_or_not_c :: c(foo) & not_c(foo) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State(kb.logic, [ Proposition.parse("a(foo)"), Proposition.parse("b(foo)"), Proposition.parse("c(foo)"), ]) options = ChainingOptions() options.backward = True options.kb = kb options.max_depth = 3 options.max_breadth = 1 options.max_length = 3 chains = list(get_chains(state, options)) assert len(chains) == 2 options.max_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 3 options.min_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 1 assert len(chains[0].actions) == 3 assert chains[0].nodes[0].depth == 2 assert chains[0].nodes[0].breadth == 2 assert chains[0].nodes[0].parent == chains[0].nodes[2] assert chains[0].nodes[1].depth == 2 assert chains[0].nodes[1].breadth == 1 assert chains[0].nodes[1].parent == chains[0].nodes[2] assert chains[0].nodes[2].depth == 1 assert chains[0].nodes[2].breadth == 1 assert chains[0].nodes[2].parent is None options.min_breadth = 1 options.create_variables = True state = State(kb.logic) chains = list(get_chains(state, options)) assert len(chains) == 5
def load(cls, target_dir: Optional[str] = None): if target_dir is None: if os.path.isdir("./textworld_data"): target_dir = "./textworld_data" else: target_dir = BUILTIN_DATA_PATH # Load knowledge base related files. paths = glob.glob(pjoin(target_dir, "logic", "*")) logic = GameLogic.load(paths) # Load text generation related files. text_grammars_path = pjoin(target_dir, "text_grammars") return cls(logic, text_grammars_path)
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
def load(cls, target_dir: Optional[str] = None, logic_path: Optional[str] = None, grammar_path: Optional[str] = None) -> "KnowledgeBase": """ Build a KnowledgeBase from several files (logic and text grammar). Args: target_dir: Folder containing two subfolders: `logic` and `text_grammars`. If provided, both `logic_path` and `grammar_path` are ignored. logic_path: Folder containing `*.twl` files that describe the logic of a game. grammar_path: Folder containing `*.twg` files that describe the grammar used for text generation. Returns: KnowledgeBase object. """ if target_dir: logic_path = pjoin(target_dir, "logic") grammar_path = pjoin(target_dir, "text_grammars") if logic_path is None: logic_path = pjoin(".", "textworld_data", "logic") # Check within working dir. if not os.path.isdir(logic_path): logic_path = LOGIC_DATA_PATH # Default to built-in data. # Load knowledge base related files. paths = glob.glob(pjoin(logic_path, "*.twl")) logic = GameLogic.load(paths) if grammar_path is None: grammar_path = pjoin(".", "textworld_data", "text_grammars") # Check within working dir. if not os.path.isdir(grammar_path): grammar_path = TEXT_GRAMMARS_PATH # Default to built-in data. # Load text generation related files. kb = cls(logic, grammar_path) kb.logic_path = logic_path return kb
def load_logic(target_dir: str): global _LOGIC if _LOGIC: return paths = [pjoin(target_dir, path) for path in os.listdir(target_dir)] logic = GameLogic.load(paths) global _TYPES _TYPES = _to_type_tree(logic.types) global _RULES _RULES = _to_regex_dict(logic.rules.values()) global _REVERSE_RULES _REVERSE_RULES = _to_reverse_mapper(_RULES, logic.reverse_rules) global _CONSTRAINTS _CONSTRAINTS = _to_regex_dict(logic.constraints.values()) global INFORM7_COMMANDS INFORM7_COMMANDS = {i7cmd.rule: i7cmd.command for i7cmd in logic.inform7.commands.values()} global INFORM7_EVENTS INFORM7_EVENTS = {i7cmd.rule: i7cmd.event for i7cmd in logic.inform7.commands.values()} global INFORM7_PREDICATES INFORM7_PREDICATES = {i7pred.predicate.signature: (i7pred.predicate, i7pred.source) for i7pred in logic.inform7.predicates.values()} global INFORM7_VARIABLES INFORM7_VARIABLES = {i7type.name: i7type.kind for i7type in logic.inform7.types.values()} global INFORM7_VARIABLES_DESCRIPTION INFORM7_VARIABLES_DESCRIPTION = {i7type.name: i7type.definition for i7type in logic.inform7.types.values()} global INFORM7_ADDONS_CODE INFORM7_ADDONS_CODE = logic.inform7.code _LOGIC = logic
def deserialize(cls, data: Mapping) -> "KnowledgeBase": logic = GameLogic.deserialize(data["logic"]) text_grammars_path = data["text_grammars_path"] return cls(logic, text_grammars_path)
def test_parallel_quests_navigation(): logic = GameLogic.parse(""" type P { } type I { } type r { rules { move :: at(P, r) & $free(r, r') -> at(P, r'); } constraints { atat :: at(P, r) & at(P, r') -> fail(); } } type o { rules { take :: $at(P, r) & at(o, r) -> in(o, I); } constraints { inat :: in(o, I) & at(o, r) -> fail(); } } type flour : o { } type eggs : o { } type cake { rules { bake :: in(flour, I) & in(eggs, I) -> in(cake, I) & in(flour, cake) & in(eggs, cake); } constraints { inincake :: in(o, I) & in(o, cake) -> fail(); atincake :: at(o, r) & in(o, cake) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State(kb.logic, [ Proposition.parse("at(P, r3: r)"), Proposition.parse("free(r2: r, r3: r)"), Proposition.parse("free(r1: r, r2: r)"), ]) bake = [kb.logic.rules["bake"]] non_bake = [r for r in kb.logic.rules.values() if r.name != "bake"] options = ChainingOptions() options.backward = True options.create_variables = True options.min_depth = 3 options.max_depth = 3 options.min_breadth = 2 options.max_breadth = 2 options.max_length = 6 options.kb = kb options.rules_per_depth = [bake, non_bake, non_bake] options.restricted_types = {"P", "r"} chains = list(get_chains(state, options)) assert len(chains) == 2