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(): chain = sample_quest(world.state, rng=None, max_depth=max_depth, nb_retry=30, allow_partial_match=True, backward=True, rules_per_depth={0: [rule]}, exceptions=["r"]) assert len(chain) == max_depth, rule.name # Build the quest by providing the actions. actions = [c.action for c in chain] quest = Quest(actions) tmp_world = World.from_facts(chain[0].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[0].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_game_with_infinite_max_score(self): M = textworld.GameMaker() museum = M.new_room("Museum") statue = M.new(type="o", name="golden statue") pedestal = M.new(type="s", name="pedestal") pedestal.add(statue) museum.add(pedestal) M.set_player(museum) M.quests = [ Quest(win_events=[ Event(conditions=[M.new_fact('in', statue, M.inventory)]), ], reward=10, optional=True, repeatable=True), Quest(win_events=[ Event(conditions=[M.new_fact('at', statue, museum)]), ], reward=0) ] M.set_walkthrough( ["take golden statue from pedestal", "look", "drop statue"]) game = M.build() assert game.max_score == np.inf inform7 = Inform7Game(game) game_progress = GameProgression(game) assert len(game_progress.quest_progressions) == len(game.quests) # Following the actions associated to the last quest actually corresponds # to solving the whole game. for action in game_progress.winning_policy: assert not game_progress.done game_progress.update(action) assert game_progress.done assert not game_progress.quest_progressions[ 0].completed # Repeatable quests can never be completed. assert game_progress.quest_progressions[ 1].completed # Mandatory quest. # Solve the game while completing the optional quests. game_progress = GameProgression(game) for command in [ "take golden statue from pedestal", "look", "look", "drop golden statue" ]: _apply_command(command, game_progress, inform7) progressions = game_progress.quest_progressions assert not progressions[0].done # Repeatable quests can never be done. assert progressions[ 0].nb_completions == 3 # They could have been completed a number of times, though. assert progressions[1].done assert game_progress.score == 30
def setUpClass(cls): M = GameMaker() # Create a 'bedroom' room. R1 = M.new_room("bedroom") R2 = M.new_room("kitchen") M.set_player(R2) path = M.connect(R1.east, R2.west) path.door = M.new(type='d', name='wooden door') path.door.add_property("closed") carrot = M.new(type='f', name='carrot') lettuce = M.new(type='f', name='lettuce') R1.add(carrot) R1.add(lettuce) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) # The goals commands = [ "open wooden door", "go west", "take carrot", "go east", "drop carrot" ] cls.eventA = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "go east", "insert lettuce into chest" ] cls.eventB = M.new_event_using_commands(commands) cls.losing_eventA = Event(conditions={M.new_fact("eaten", carrot)}) cls.losing_eventB = Event(conditions={M.new_fact("eaten", lettuce)}) cls.questA = Quest(win_events=[cls.eventA], fail_events=[cls.losing_eventA]) cls.questB = Quest(win_events=[cls.eventB], fail_events=[cls.losing_eventB]) cls.questC = Quest(win_events=[], fail_events=[cls.losing_eventA, cls.losing_eventB]) commands = ["open wooden door", "go west", "take carrot", "eat carrot"] cls.eating_carrot = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "eat lettuce" ] cls.eating_lettuce = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "go east", "insert lettuce into chest" ] M.quests = [cls.questA, cls.questB, cls.questC] cls.game = M.build()
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 test_init(self): npt.assert_raises(UnderspecifiedQuestError, Quest) quest = Quest(win_events=[self.eventA, self.eventB]) assert len(quest.fail_events) == 0 quest = Quest(fail_events=[self.eventC, self.eventD]) assert len(quest.win_events) == 0 quest = Quest(win_events=[self.eventA], fail_events=[self.eventC, self.eventD]) assert len(quest.win_events) > 0 assert len(quest.fail_events) > 0
def build_test_game(): M = GameMaker() # Create a 'bedroom' room. R1 = M.new_room("bedroom") R2 = M.new_room("kitchen") M.set_player(R1) path = M.connect(R1.east, R2.west) path.door = M.new(type='d', name='wooden door') path.door.add_property("open") carrot = M.new(type='f', name='carrot') M.inventory.add(carrot) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) commands = ["go east", "insert carrot into chest"] quest1 = M.new_quest_using_commands(commands) quest1.reward = 2 commands = ["go east", "insert carrot into chest", "close chest"] event = M.new_event_using_commands(commands) quest2 = Quest(win_events=[event]) M.quests = [quest1, quest2] game = M.build() return game
def record_quest(self, ask_for_state: bool = False) -> Quest: """ Defines the game's quest by recording the commands. This launches a `textworld.play` session. Args: ask_for_state: If true, the user will be asked to specify which set of facts of the final state are should be true in order to consider the quest as completed. Returns: The resulting quest. """ with make_temp_directory() as tmpdir: game_file = self.compile(pjoin(tmpdir, "record_quest")) recorder = Recorder() textworld.play(game_file, wrapper=recorder) # Skip "None" actions. actions = [action for action in recorder.actions if action is not None] # Ask the user which quests have important state, if this is set # (if not, we assume the last action contains all the relevant facts) winning_facts = None if ask_for_state and recorder.last_game_state is not None: winning_facts = user_query.query_for_important_facts(actions=recorder.actions, facts=recorder.last_game_state.state.facts, varinfos=self._working_game.infos) self._quests = [Quest(actions=actions, winning_conditions=winning_facts)] # Calling build will generate the description for the quest. self.build() return self._quests[0]
def new_quest_using_commands(self, commands: List[str]) -> Quest: """ Creates a new quest using predefined text commands. This launches a `textworld.play` session to execute provided commands. Args: commands: Text commands. Returns: The resulting quest. """ with make_temp_directory() as tmpdir: try: game_file = self.compile(pjoin(tmpdir, "record_quest.ulx")) recorder = Recorder() agent = textworld.agents.WalkthroughAgent(commands) textworld.play(game_file, agent=agent, wrapper=recorder, silent=True) except textworld.agents.WalkthroughDone: pass # Quest is done. # Skip "None" actions. actions = [action for action in recorder.actions if action is not None] return Quest(actions=actions)
def test_quest_creation(self): quest = Quest(self.quest.actions) assert quest.actions == self.quest.actions assert quest.win_action == self.quest.win_action assert quest.win_action.preconditions == self.quest.actions[-1].postconditions assert quest.fail_action is None quest = Quest(actions=None, winning_conditions=self.quest.actions[-1].postconditions) assert quest.actions is None assert quest.win_action == self.quest.win_action assert quest.fail_action is None npt.assert_raises(UnderspecifiedQuestError, Quest, actions=None, winning_conditions=None) quest = Quest(self.quest.actions, failing_conditions=self.failing_conditions) assert quest.fail_action == self.quest.fail_action assert quest.fail_action.preconditions == self.failing_conditions
def generate_quest(self, obj): quests = [] locations = self.find_correct_locations(obj) assert len(locations) > 0 conditions = [self.maker.new_fact(preposition_of(location), obj, location) for location in locations] events = [Event(conditions={c}) for c in conditions] place_quest = Quest(win_events=events, reward=self.config.reward) quests.append(place_quest) if self.config.intermediate_reward > 0: current_location = obj.parent if current_location == self.maker.inventory: return quests take_cond = self.maker.new_fact('in', obj, self.maker.inventory) events = [Event(conditions={take_cond})] take_quest = Quest(win_events=events, reward=int(self.config.intermediate_reward)) quests.append(take_quest) return quests
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 generate_quest(maker, obj): locations = find_correct_locations(maker, obj) assert len(locations) > 0 conditions = [ maker.new_fact(preposition_of(location), obj, location) for location in locations ] events = [Event(conditions={c}) for c in conditions] return Quest(win_events=events)
def set_quest_from_commands(self, commands: List[str], ask_for_state: bool = False) -> Quest: """ Defines the game's quest using predefined text commands. This launches a `textworld.play` session. Args: commands: Text commands. ask_for_state: If true, the user will be asked to specify which set of facts of the final state are should be true in order to consider the quest as completed. Returns: The resulting quest. """ with make_temp_directory() as tmpdir: try: game_file = self.compile(pjoin(tmpdir, "record_quest.ulx")) recorder = Recorder() agent = textworld.agents.WalkthroughAgent(commands) textworld.play(game_file, agent=agent, wrappers=[recorder], silent=True) except textworld.agents.WalkthroughDone: pass # Quest is done. # Skip "None" actions. actions = [action for action in recorder.actions if action is not None] # Ask the user which quests have important state, if this is set # (if not, we assume the last action contains all the relevant facts) winning_facts = None if ask_for_state and recorder.last_game_state is not None: winning_facts = [ user_query.query_for_important_facts( actions=recorder.actions, facts=recorder.last_game_state.state.facts, varinfos=self._working_game.infos) ] if len(commands) != len(actions): unrecognized_commands = [ c for c, a in zip(commands, recorder.actions) if a is None ] raise QuestError( "Some of the actions were unrecognized: {}".format( unrecognized_commands)) event = Event(actions=actions, conditions=winning_facts) self.quests = [Quest(win_events=[event])] # Calling build will generate the description for the quest. self.build() return self.quests[-1]
def build_and_compile_game(options: GameOptions): M = textworld.GameMaker() # Create a 'bedroom' room. R1 = M.new_room("bedroom") R2 = M.new_room("kitchen") M.set_player(R1) path = M.connect(R1.east, R2.west) path.door = M.new(type='d', name='wooden door') path.door.add_property("open") carrot = M.new(type='f', name='carrot') M.inventory.add(carrot) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) quest1_cmds = ["go east", "insert carrot into chest"] carrot_in_chest = M.new_event_using_commands(quest1_cmds) eating_carrot = Event(conditions={M.new_fact("eaten", carrot)}) quest1 = Quest(win_events=[carrot_in_chest], fail_events=[eating_carrot], reward=2) quest2_cmds = quest1_cmds + ["close chest"] quest2_actions = M.new_event_using_commands(quest2_cmds).actions chest_closed_with_carrot = Event(conditions={ M.new_fact("in", carrot, chest), M.new_fact("closed", chest) }, actions=quest2_actions) quest2 = Quest(win_events=[chest_closed_with_carrot], fail_events=[eating_carrot]) M.quests = [quest1, quest2] M.set_walkthrough(quest2_cmds) game = M.build() game_file = _compile_test_game(game, options) return game, game_file
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 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 new_quest_using_commands(self, commands: List[str]) -> Quest: """ Creates a new quest using predefined text commands. This launches a `textworld.play` session to execute provided commands. Args: commands: Text commands. Returns: The resulting quest. """ event = self.new_event_using_commands(commands) return Quest(win_events=[event], commands=event.commands)
def generate_text_from_grammar(game, grammar: Grammar): # Assign a specific room type and name to our rooms for room in game.world.rooms: # First, generate a unique roomtype and name from the grammar if game.infos[room.id].room_type is None and grammar.has_tag( "#room_type#"): game.infos[room.id].room_type = grammar.expand("#room_type#") assign_name_to_object(room, grammar, game.infos) # Next, assure objects contained in a room must have the same room type for obj in game.world.get_all_objects_in(room): if game.infos[obj.id].room_type is None: game.infos[obj.id].room_type = game.infos[room.id].room_type # Objects in inventory can be of any room type. for obj in game.world.get_objects_in_inventory(): if game.infos[obj.id].room_type is None and grammar.has_tag( "#room_type#"): game.infos[obj.id].room_type = grammar.expand("#room_type#") # Assign name and description to objects. for obj in game.world.objects: if obj.type in ["I", "P"]: continue assign_name_to_object(obj, grammar, game.infos) assign_description_to_object(obj, grammar, game.infos) # Generate the room descriptions. for room in game.world.rooms: if game.infos[ room. id].desc is None: # Skip rooms which already have a description. game.infos[room.id].desc = assign_description_to_room( room, game, grammar) # Generate the instructions. for quest in game.quests: if quest.desc is None: # Skip quests which already have a description. quest.desc = assign_description_to_quest(quest, game, grammar) if grammar.options.only_last_action and len(game.quests) > 1: main_quest = Quest( actions=[quest.actions[-1] for quest in game.quests]) only_last_action_bkp = grammar.options.only_last_action grammar.options.only_last_action = False game.objective = assign_description_to_quest(main_quest, game, grammar) grammar.options.only_last_action = only_last_action_bkp 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. 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_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 build_and_compile_playground_game(): M = GameMaker() room_dr = M.new_room("Locker Room") room_mr = M.new_room("Meeting Room") room_fl = M.new_room("Field") team_mates = M.new(type='tm', name = "teammates") captain = M.new(type='tm3', name = "captain") room_dr.add(team_mates) room_mr.add(captain) corridor = M.connect(room_dr.east, room_mr.west) corridor1 = M.connect(room_mr.east, room_fl.west) #corridor2 = M.connect(room_fl.east, new_room.west) M.set_player(room_dr) team_mates_2 = M.new(type='tm2', name = "someone") room_dr.add(team_mates_2) game_gear = M.new(type='ap', name = "game gears") room_dr.add(game_gear) M.add_fact("not_equiped", game_gear) M.add_fact("not_participated", captain) M.add_fact("not_helped", team_mates) M.add_fact("not_disturbed", team_mates) M.add_fact("not_bully", team_mates) room_dr.infos.desc = "You are in the Locker room with your team mates. You will find your game gears here. Collect the game gears and wear them before the meeting. If you need help, ask help from others and be helpful to others as well. \n After your are done here, go to the meeting room." room_mr.infos.desc = "You are in the meeting room. A meeting is going to start to finalize the game plan. Join the meeting and try to share your thought." captain.infos.desc = "The meeting is leading by the captain. Please, Cooperate with him." failed_event_1 = Event(conditions={ M.new_fact("not_equiped", game_gear), M.new_fact("at", M._entities['P'], room_mr)}) failed_quest_1 = Quest(win_events=[], fail_events=[failed_event_1]) #M.quests = [failed_quest_1] game = M.build() game_file = _compile_test_game(game) return game, game_file
def set_quest_from_final_state(self, final_state: Collection[Proposition]) -> Quest: """ Defines the game's quest using a collection of facts. The quest will be considered as finished as soon as all the provided facts are true. Args: final_state: List of all facts that will have to be true for the quest to be considered as completed. Returns: The resulting quest. """ quest = Quest(actions=None, winning_conditions=final_state) self._quests = [quest] # Calling build will generate the description for the quest self.build() return self._quests[0]
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 setUpClass(cls): M = GameMaker() room = M.new_room("room") M.set_player(room) carrot = M.new(type='f', name='carrot') lettuce = M.new(type='f', name='lettuce') room.add(carrot) room.add(lettuce) chest = M.new(type='c', name='chest') chest.add_property("open") room.add(chest) # The goals commands = ["take carrot", "insert carrot into chest"] cls.eventA = M.new_event_using_commands(commands) commands = ["take lettuce", "insert lettuce into chest", "close chest"] event = M.new_event_using_commands(commands) cls.eventB = Event(actions=event.actions, conditions={ M.new_fact("in", lettuce, chest), M.new_fact("closed", chest) }) cls.fail_eventA = Event(conditions={M.new_fact("eaten", carrot)}) cls.fail_eventB = Event(conditions={M.new_fact("eaten", lettuce)}) cls.quest = Quest(win_events=[cls.eventA, cls.eventB], fail_events=[cls.fail_eventA, cls.fail_eventB]) commands = ["take carrot", "eat carrot"] cls.eating_carrot = M.new_event_using_commands(commands) commands = ["take lettuce", "eat lettuce"] cls.eating_lettuce = M.new_event_using_commands(commands) commands = ["take lettuce", "insert lettuce into chest"] M.quests = [cls.quest] cls.game = M.build()
def setUpClass(cls): M = GameMaker() # The goal commands = ["go east", "insert carrot into chest"] # Create a 'bedroom' room. R1 = M.new_room("bedroom") R2 = M.new_room("kitchen") M.set_player(R1) path = M.connect(R1.east, R2.west) path.door = M.new(type='d', name='wooden door') path.door.add_property("open") carrot = M.new(type='f', name='carrot') M.inventory.add(carrot) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) cls.eventA = M.new_event_using_commands(commands) cls.eventB = Event(conditions={ M.new_fact("at", carrot, R1), M.new_fact("closed", path.door) }) cls.eventC = Event(conditions={M.new_fact("eaten", carrot)}) cls.eventD = Event(conditions={ M.new_fact("closed", chest), M.new_fact("closed", path.door) }) cls.quest = Quest(win_events=[cls.eventA, cls.eventB], fail_events=[cls.eventC, cls.eventD], reward=2) M.quests = [cls.quest] cls.game = M.build() cls.inform7 = Inform7Game(cls.game)
def set_quest_from_commands(self, commands: List[str]) -> Quest: """ Defines the game's quest using predefined text commands. This launches a `textworld.play` session. Args: commands: Text commands. Returns: The resulting quest. """ with make_temp_directory() as tmpdir: try: game_file = self.compile(pjoin(tmpdir, "record_quest.ulx")) recorder = Recorder() agent = textworld.agents.WalkthroughAgent(commands) textworld.play(game_file, agent=agent, wrappers=[recorder], silent=True) except textworld.agents.WalkthroughDone: pass # Quest is done. # Skip "None" actions. actions = [action for action in recorder.actions if action is not None] if len(commands) != len(actions): unrecognized_commands = [ c for c, a in zip(commands, recorder.actions) if a is None ] raise QuestError( "Some of the actions were unrecognized: {}".format( unrecognized_commands)) event = Event(actions=actions) self.quests = [Quest(win_events=[event])] # Calling build will generate the description for the quest. self.build() return self.quests[-1]
def record_quest(self) -> Quest: """ Defines the game's quest by recording the commands. This launches a `textworld.play` session. Returns: The resulting quest. """ with make_temp_directory() as tmpdir: game_file = self.compile(pjoin(tmpdir, "record_quest.ulx")) recorder = Recorder() agent = textworld.agents.HumanAgent(autocompletion=True) textworld.play(game_file, agent=agent, wrappers=[recorder]) # Skip "None" actions. actions = [action for action in recorder.actions if action is not None] # Assume the last action contains all the relevant facts about the winning condition. event = Event(actions=actions) self.quests.append(Quest(win_events=[event])) # Calling build will generate the description for the quest. self.build() return self.quests[-1]
def make_game(settings: Mapping[str, str], options: Optional[GameOptions] = None) -> textworld.Game: """ Make a simple game. Arguments: settings: Difficulty settings (see notes). options: For customizing the game generation (see :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>` for the list of available options). Returns: Generated game. Notes: The settings that can be provided are: * rewards : The reward frequency: dense, balanced, or sparse. * goal : The description of the game's objective shown at the beginning of the game: detailed, bried, or none. * test : Whether this game should be drawn from the test distributions of games. """ metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Simple game" metadata["seeds"] = options.seeds metadata["world_size"] = 6 metadata["quest_length"] = None # TBD rngs = options.rngs rng_map = rngs['map'] rng_objects = rngs['objects'] rng_grammar = rngs['grammar'] rng_quest = rngs['quest'] # Make the generation process reproducible. textworld.g_rng.set_seed(2018) M = textworld.GameMaker() M.grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar) # Start by building the layout of the world. bedroom = M.new_room("bedroom") kitchen = M.new_room("kitchen") livingroom = M.new_room("living room") bathroom = M.new_room("bathroom") backyard = M.new_room("backyard") garden = M.new_room("garden") # Connect rooms together. bedroom_kitchen = M.connect(bedroom.east, kitchen.west) kitchen_bathroom = M.connect(kitchen.north, bathroom.south) kitchen_livingroom = M.connect(kitchen.south, livingroom.north) kitchen_backyard = M.connect(kitchen.east, backyard.west) backyard_garden = M.connect(backyard.south, garden.north) # Add doors. bedroom_kitchen.door = M.new(type='d', name='wooden door') kitchen_backyard.door = M.new(type='d', name='screen door') kitchen_backyard.door.add_property("closed") # Design the bedroom. drawer = M.new(type='c', name='chest drawer') trunk = M.new(type='c', name='antique trunk') bed = M.new(type='s', name='king-size bed') bedroom.add(drawer, trunk, bed) # Close the trunk and drawer. trunk.add_property("closed") drawer.add_property("closed") # - The bedroom's door is locked bedroom_kitchen.door.add_property("locked") # Design the kitchen. counter = M.new(type='s', name='counter') stove = M.new(type='s', name='stove') kitchen_island = M.new(type='s', name='kitchen island') refrigerator = M.new(type='c', name='refrigerator') kitchen.add(counter, stove, kitchen_island, refrigerator) # - Add some food in the refrigerator. apple = M.new(type='f', name='apple') milk = M.new(type='f', name='milk') refrigerator.add(apple, milk) # Design the bathroom. toilet = M.new(type='c', name='toilet') sink = M.new(type='s', name='sink') bath = M.new(type='c', name='bath') bathroom.add(toilet, sink, bath) toothbrush = M.new(type='o', name='toothbrush') sink.add(toothbrush) soap_bar = M.new(type='o', name='soap bar') bath.add(soap_bar) # Design the living room. couch = M.new(type='s', name='couch') low_table = M.new(type='s', name='low table') tv = M.new(type='s', name='tv') livingroom.add(couch, low_table, tv) remote = M.new(type='o', name='remote') low_table.add(remote) bag_of_chips = M.new(type='f', name='half of a bag of chips') couch.add(bag_of_chips) # Design backyard. bbq = M.new(type='s', name='bbq') patio_table = M.new(type='s', name='patio table') chairs = M.new(type='s', name='set of chairs') backyard.add(bbq, patio_table, chairs) # Design garden. shovel = M.new(type='o', name='shovel') tomato = M.new(type='f', name='tomato plant') pepper = M.new(type='f', name='bell pepper') lettuce = M.new(type='f', name='lettuce') garden.add(shovel, tomato, pepper, lettuce) # Close all containers for container in M.findall(type='c'): container.add_property("closed") # Set uncooked property for to all food items. foods = M.findall(type='f') for food in foods: food.add_property("edible") food_names = [food.name for food in foods] # Shuffle the position of the food items. rng_quest.shuffle(food_names) for food, name in zip(foods, food_names): food.orig_name = food.name food.name = name # The player starts in the bedroom. M.set_player(bedroom) # Quest walkthrough = [] # Part I - Escaping the room. # Generate the key that unlocks the bedroom door. bedroom_key = M.new(type='k', name='old key') M.add_fact("match", bedroom_key, bedroom_kitchen.door) # Decide where to hide the key. if rng_quest.rand() > 0.5: drawer.add(bedroom_key) walkthrough.append("open chest drawer") walkthrough.append("take old key from chest drawer") bedroom_key_holder = drawer else: trunk.add(bedroom_key) walkthrough.append("open antique trunk") walkthrough.append("take old key from antique trunk") bedroom_key_holder = trunk # Unlock the door, open it and leave the room. walkthrough.append("unlock wooden door with old key") walkthrough.append("open wooden door") walkthrough.append("go east") # Part II - Find food item. # 1. Randomly pick a food item to cook. food = rng_quest.choice(foods) if settings["test"]: TEST_FOODS = ["garlic", "kiwi", "carrot"] food.name = rng_quest.choice(TEST_FOODS) # Retrieve the food item and get back in the kitchen. # HACK: handcrafting the walkthrough. if food.orig_name in ["apple", "milk"]: rooms_to_visit = [] doors_to_open = [] walkthrough.append("open refrigerator") walkthrough.append("take {} from refrigerator".format(food.name)) elif food.orig_name == "half of a bag of chips": rooms_to_visit = [livingroom] doors_to_open = [] walkthrough.append("go south") walkthrough.append("take {} from couch".format(food.name)) walkthrough.append("go north") elif food.orig_name in ["bell pepper", "lettuce", "tomato plant"]: rooms_to_visit = [backyard, garden] doors_to_open = [kitchen_backyard.door] walkthrough.append("open screen door") walkthrough.append("go east") walkthrough.append("go south") walkthrough.append("take {}".format(food.name)) walkthrough.append("go north") walkthrough.append("go west") # Part II - Cooking the food item. walkthrough.append("put {} on stove".format(food.name)) # walkthrough.append("cook {}".format(food.name)) # walkthrough.append("eat {}".format(food.name)) # 2. Determine the winning condition(s) of the subgoals. quests = [] bedroom_key_holder if settings["rewards"] == "dense": # Finding the bedroom key and opening the bedroom door. # 1. Opening the container. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("open", bedroom_key_holder)}) ])) # 2. Getting the key. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("in", bedroom_key, M.inventory)}) ])) # 3. Unlocking the bedroom door. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("closed", bedroom_kitchen.door)}) ])) # 4. Opening the bedroom door. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("open", bedroom_kitchen.door)}) ])) if settings["rewards"] in ["dense", "balanced"]: # Escaping out of the bedroom. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("at", M.player, kitchen)}) ])) if settings["rewards"] in ["dense", "balanced"]: # Opening doors. for door in doors_to_open: quests.append( Quest( win_events=[Event(conditions={M.new_fact("open", door)})])) if settings["rewards"] == "dense": # Moving through places. for room in rooms_to_visit: quests.append( Quest(win_events=[ Event(conditions={M.new_fact("at", M.player, room)}) ])) if settings["rewards"] in ["dense", "balanced"]: # Retrieving the food item. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("in", food, M.inventory)}) ])) if settings["rewards"] in ["dense", "balanced"]: # Retrieving the food item. quests.append( Quest(win_events=[ Event(conditions={M.new_fact("in", food, M.inventory)}) ])) if settings["rewards"] in ["dense", "balanced", "sparse"]: # Putting the food on the stove. quests.append( Quest( win_events=[Event( conditions={M.new_fact("on", food, stove)})])) # 3. Determine the losing condition(s) of the game. quests.append( Quest(fail_events=[Event(conditions={M.new_fact("eaten", food)})])) # Set the subquest(s). M.quests = quests # - Add a hint of what needs to be done in this game. objective = "The dinner is almost ready! It's only missing a grilled {}." objective = objective.format(food.name) note = M.new(type='o', name='note', desc=objective) kitchen_island.add(note) game = M.build() game.main_quest = M.new_quest_using_commands(walkthrough) game.change_grammar(game.grammar) if settings["goal"] == "detailed": # Use the detailed version of the objective. pass elif settings["goal"] == "brief": # Use a very high-level description of the objective. game.objective = objective elif settings["goal"] == "none": # No description of the objective. game.objective = "" game.metadata = metadata uuid = "tw-simple-r{rewards}+g{goal}+{dataset}-{flags}-{seeds}" uuid = uuid.format(rewards=str.title(settings["rewards"]), goal=str.title(settings["goal"]), dataset="test" if settings["test"] else "train", flags=options.grammar.uuid, seeds=encode_seeds( [options.seeds[k] for k in sorted(options.seeds)])) game.metadata["uuid"] = uuid return game
def make_game(mode: str, options: GameOptions) -> textworld.Game: """ Make a Treasure Hunter game. Arguments: mode: Mode for the game where * `'easy'`: rooms are all empty except where the two objects are placed. Also, connections between rooms have no door. * `'medium'`: adding closed doors and containers that might need to be open in order to find the object. * `'hard'`: adding locked doors and containers (necessary keys will in the inventory) that might need to be unlocked (and open) in order to find the object. options: For customizing the game generation (see :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ kb = KnowledgeBase.default() metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Treasure Hunter" metadata["mode"] = mode metadata["seeds"] = options.seeds metadata["world_size"] = options.nb_rooms metadata["quest_length"] = options.quest_length rngs = options.rngs rng_map = rngs['map'] rng_objects = rngs['objects'] rng_quest = rngs['quest'] rng_grammar = rngs['grammar'] modes = ["easy", "medium", "hard"] if mode == "easy": door_states = None n_distractors = 0 elif mode == "medium": door_states = ["open", "closed"] n_distractors = 10 elif mode == "hard": door_states = ["open", "closed", "locked"] n_distractors = 20 # Generate map. map_ = textworld.generator.make_map(n_rooms=options.nb_rooms, rng=rng_map, possible_door_states=door_states) assert len(map_.nodes()) == options.nb_rooms world = World.from_map(map_) # Randomly place the player. starting_room = None if len(world.rooms) > 1: starting_room = rng_map.choice(world.rooms) world.set_player_room(starting_room) # Add object the player has to pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) right_obj = Variable(var_id, obj_type) world.add_fact(Proposition("in", [right_obj, world.inventory])) # Add containers and supporters to the world. types_counts = kb.types.count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + kb.types.descendants('c') + kb.types.descendants('s')) for i in range(n_distractors): obj_type = rng_objects.choice(distractor_types) var_id = get_new(obj_type, types_counts) # This update the types_counts. objects.append(Variable(var_id, obj_type)) world.populate_with(objects, rng=rng_objects) # Add object the player should not pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) wrong_obj = Variable(var_id, obj_type) # Place it anywhere in the world. world.populate_with([wrong_obj], rng=rng_objects) # Generate a quest that finishes by taking something (i.e. the right # object since it's the only one in the inventory). options.chaining.rules_per_depth = [kb.rules.get_matching("take.*")] options.chaining.backward = True options.chaining.rng = rng_quest #options.chaining.restricted_types = exceptions #exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"] chain = textworld.generator.sample_quest(world.state, options.chaining) # Add objects needed for the quest. world.state = chain.initial_state event = Event(chain.actions) quest = Quest( win_events=[event], fail_events=[ Event(conditions={Proposition("in", [wrong_obj, world.inventory])}) ]) grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar) game = textworld.generator.make_game_with(world, [quest], grammar) game.metadata = metadata mode_choice = modes.index(mode) uuid = "tw-treasure_hunter-{specs}-{grammar}-{seeds}" uuid = uuid.format(specs=encode_seeds( (mode_choice, options.nb_rooms, options.quest_length)), grammar=options.grammar.uuid, seeds=encode_seeds( [options.seeds[k] for k in sorted(options.seeds)])) game.metadata["uuid"] = uuid return game