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
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 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
def test_backward_chaining(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State(KnowledgeBase.default().logic, [ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("south_of", [room, kitchen]), ]) options = ChainingOptions() options.backward = True options.max_depth = 2 options.max_length = 2 options.subquests = True options.create_variables = True options.rules_per_depth = [ [ KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"] ], [KnowledgeBase.default().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.max_length = 3 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["put"]], [KnowledgeBase.default().rules["go/north"]], [KnowledgeBase.default().rules["take/c"]], ] options.restricted_types = {"d"} chains = list(get_chains(state, options)) assert len(chains) == 3
def test_applying_actions(): state = build_state(locked_door=False) options = ChainingOptions() options.backward = True options.max_depth = 5 chains = list(get_chains(state, options)) expected_state = state for chain in chains: state = chain.initial_state.copy() for action in chain.actions: assert state.apply(action) assert expected_state == state
def make_quest(world, quest_length, rng=None, rules_per_depth=(), backward=False): state = world if hasattr(world, "state"): state = world.state rng = g_rng.next() if rng is None else rng # Sample a quest according to quest_length. options = ChainingOptions() options.backward = backward options.max_depth = quest_length options.rng = rng options.rules_per_depth = rules_per_depth chain = sample_quest(state, options) return Quest(chain.actions)
def make_game(world_size: int, nb_objects: int, quest_length: int, grammar_flags: Mapping = {}, rngs: Optional[Dict[str, RandomState]] = None) -> Game: """ Make a game (map + objects + quest). Arguments: world_size: Number of rooms in the world. nb_objects: Number of objects in the world. quest_length: Minimum nb. of actions the quest requires to be completed. grammar_flags: Options for the grammar. Returns: Generated game. """ if rngs is None: rngs = {} rng = g_rng.next() rngs['rng_map'] = RandomState(rng.randint(65635)) rngs['rng_objects'] = RandomState(rng.randint(65635)) rngs['rng_quest'] = RandomState(rng.randint(65635)) rngs['rng_grammar'] = RandomState(rng.randint(65635)) # Generate only the map for now (i.e. without any objects) world = make_world(world_size, nb_objects=0, rngs=rngs) # Sample a quest according to quest_length. options = ChainingOptions() options.backward = True options.max_depth = quest_length options.create_variables = True options.rng = rngs['rng_quest'] options.restricted_types = {"r", "d"} chain = sample_quest(world.state, options) quest = Quest(chain.actions) # 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(nb_objects, rng=rngs['rng_objects']) grammar = make_grammar(grammar_flags, rng=rngs['rng_grammar']) game = make_game_with(world, [quest], grammar) return game
def main(): args = parse_args() # Load game for which to sample quests for. game = Game.load(args.game.replace(".ulx", ".json")) options = ChainingOptions() options.backward = False options.max_depth = args.quest_length options.max_breadth = args.quest_breadth options.rules_per_depth = {} options.create_variables = False options.rng = np.random.RandomState(args.seed) # Sample quests. chains = [] for i in range(args.nb_quests): chain = sample_quest(game.world.state, options) chains.append(chain) inform7 = Inform7Game(game) print_chains(chains, inform7) # Convert chains to networkx graph/tree filename_world = pjoin(args.output, "sample_world.png") filename_tree = pjoin(args.output, "sample_tree.svg") filename_graph = pjoin(args.output, "sample_graph.svg") G, labels = build_tree_from_chains(chains, inform7) if len(G) > 0: image = visualize(game) image.save(filename_world) tree = nx.bfs_tree(G, "root") save_graph_to_svg(tree, labels, filename_tree) save_graph_to_svg(G, labels, filename_graph) else: try: os.remove(filename_world) os.remove(filename_tree) os.remove(filename_graph) except: pass
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)
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
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