def main(): args = parse_args() game = Game.load(args.game) # Sample quests. rng = np.random.RandomState(args.seed) chains = [] rules_per_depth = {} for i in range(args.nb_quests): chain = sample_quest(game.world.state, rng, max_depth=args.quest_length, allow_partial_match=False, exceptions=[], rules_per_depth=rules_per_depth, backward=False) chains.append(chain) print_chains(chains, verbose=args.verbose) actions_tree = build_tree_from_chains(chains) # Convert tree to networkx graph/tree filename = "sample_tree.svg" G, labels = actions_tree.to_networkx() if len(G) > 0: tree = nx.bfs_tree(G, actions_tree.no) save_graph_to_svg(tree, labels, filename, backward=False) else: try: os.remove(filename) except: pass
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 make_game(options: GameOptions) -> Game: """ Make a game (map + objects + quest). Arguments: options: For customizing the game generation (see :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ rngs = options.rngs # Generate only the map for now (i.e. without any objects) world = make_world(options.nb_rooms, nb_objects=0, rngs=rngs) # Sample a quest. chaining_options = options.chaining.copy() # Go, examine, look and inventory shouldn't be used for chaining. exclude = ["go.*", "examine.*", "look.*", "inventory.*"] chaining_options.rules_per_depth = [ options.kb.rules.get_matching(".*", exclude=exclude) ] chaining_options.backward = True chaining_options.create_variables = True chaining_options.rng = rngs['quest'] chaining_options.restricted_types = {"r", "d"} chain = sample_quest(world.state, chaining_options) subquests = [] for i in range(1, len(chain.nodes)): if chain.nodes[i].breadth != chain.nodes[i - 1].breadth: event = Event(chain.actions[:i]) subquests.append(Quest(win_events=[event])) event = Event(chain.actions) subquests.append(Quest(win_events=[event])) # 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(options.nb_objects, rng=rngs['objects']) grammar = make_grammar(options.grammar, rng=rngs['grammar']) game = make_game_with(world, subquests, grammar) game.change_grammar(grammar) game.metadata["uuid"] = options.uuid return game
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. chain = sample_quest(world.state, rngs['rng_quest'], max_depth=quest_length, nb_retry=20, allow_partial_match=True, backward=True, exceptions=["r"]) quest = Quest([c.action for c in chain]) # Set the initial state required for the quest. world.state = chain[0].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 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 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. chain = sample_quest(state, rng, max_depth=quest_length, nb_retry=20, rules_per_depth=rules_per_depth, backward=backward) quest = [c.action for c in chain] return Quest(quest)
def make_quest(world: Union[World, State], options: Optional[GameOptions] = None): state = getattr(world, "state", world) if options is None: options = GameOptions() # By default, exclude quests finishing with: go, examine, look and inventory. exclude = ["go.*", "examine.*", "look.*", "inventory.*"] options.chaining.rules_per_depth = [ options.kb.rules.get_matching(".*", exclude=exclude) ] options.chaining.rng = options.rngs['quest'] chains = [] for _ in range(options.nb_parallel_quests): chain = sample_quest(state, options.chaining) if chain is None: msg = "No quest can be generated with the provided options." raise NoSuchQuestExistError(msg) chains.append(chain) state = chain.initial_state # State might have changed, i.e. options.create_variable is True. if options.chaining.backward and hasattr(world, "state"): world.state = state quests = [] actions = [] for chain in reversed(chains): for i in range(1, len(chain.nodes)): actions.append(chain.actions[i - 1]) if chain.nodes[i].breadth != chain.nodes[i - 1].breadth: event = Event(actions) quests.append(Quest(win_events=[event])) actions.append(chain.actions[-1]) event = Event(actions) quests.append(Quest(win_events=[event])) return quests
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 make_game(world_size: int, nb_objects: int, quest_length: int, quest_breadth: 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. quest_breadth: How many branches the quest can have. 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. class Options(ChainingOptions): def get_rules(self, depth): if depth == 0: # Last action should not be "go <dir>". return data.get_rules().get_matching("^(?!go.*).*") else: return super().get_rules(depth) options = Options() options.backward = True options.min_depth = 1 options.max_depth = quest_length options.min_breadth = 1 options.max_breadth = quest_breadth options.create_variables = True options.rng = rngs['rng_quest'] options.restricted_types = {"r", "d"} chain = sample_quest(world.state, options) subquests = [] for i in range(1, len(chain.nodes)): if chain.nodes[i].breadth != chain.nodes[i - 1].breadth: quest = Quest(chain.actions[:i]) subquests.append(quest) quest = Quest(chain.actions) subquests.append(quest) # 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, subquests, grammar) game.change_grammar(grammar) return game