def test_filter_wrapper(): # Make a game for testing purposes. num_nodes = 3 num_items = 10 options = textworld.GameOptions() options.seeds = 1234 options.nb_rooms = num_nodes options.nb_objects = num_items options.quest_length = 3 options.grammar.theme = "house" options.grammar.include_adj = True game = textworld.generator.make_game(options) game_name = "test_filter_wrapper" with make_temp_directory(prefix=game_name) as tmpdir: options.path = tmpdir game_file = compile_game(game, options) env = textworld.start(game_file) env_options = EnvInfos() for attr in env_options.__slots__: if attr == "extras": continue # Skip since it's not a boolean attribute. setattr(env_options, attr, True) assert len(env_options) == len(env_options.__slots__) - 1 assert len(env_options) == len(env_options.basics) env = Filter(env, env_options) _, infos = env.reset() for attr in env_options.basics: assert attr in infos
def _make_env(request_infos, max_episode_steps=None): env = GenericEnvironment(request_infos) if max_episode_steps: env = Limit(env, max_episode_steps=max_episode_steps) env = Filter(env) return env
def reset(self) -> Tuple[str, Dict[str, Any]]: """ Resets the text-based environment. Resetting this environment means starting the next game in the pool. Returns: A tuple (observation, info) where * observation: text observed in the initial state; * infos: additional information as requested. """ if self.textworld_env is not None: self.textworld_env.close() self.current_gamefile = next(self._gamefiles_iterator) env = textworld.start(self.current_gamefile) self.textworld_env = Filter(self.request_infos)(env) self.ob, infos = self.textworld_env.reset() return self.ob, infos
class TextworldGamesEnv(gym.Env): metadata = {'render.modes': ['human', 'ansi', 'text']} def __init__(self, game_files: List[str], request_infos: Optional[EnvInfos] = None, action_space: Optional[gym.Space] = None, observation_space: Optional[gym.Space] = None) -> None: """ Environment for playing text-based games. Each time `TextworldGamesEnv.reset()` is called, a new game from the pool starts. Each game of the pool is guaranteed to be played exactly once before a same game is played for a second time. Arguments: game_files: Paths of every game composing the pool (`*.ulx` + `*.json`, `*.z[1-8]`). request_infos: For customizing the information returned by this environment (see :py:class:`textworld.EnvInfos <textworld.envs.wrappers.filter.EnvInfos>` for the list of available information). .. warning:: This is only supported for `*.ulx` games generated with TextWorld. action_space: The action space of this TextWorld environment. By default, a :py:class:`textworld.gym.spaces.Word <textworld.gym.spaces.text_spaces.Word>` instance is used with a `max_length` of 8 and a vocabulary extracted from the TextWorld game. observation_space: The observation space of this TextWorld environment. By default, a :py:class:`textworld.gym.spaces.Word <textworld.gym.spaces.text_spaces.Word>` instance is used with a `max_length` of 200 and a vocabulary extracted from the TextWorld game. """ self.gamefiles = game_files self.request_infos = request_infos or EnvInfos() self.ob = None self.last_command = None self.textworld_env = None self.current_gamefile = None self.seed(1234) if action_space is None or observation_space is None: # Extract vocabulary from all games. vocab = sorted( textworld.text_utils.extract_vocab_from_gamefiles( self.gamefiles)) self.action_space = action_space or text_spaces.Word(max_length=8, vocab=vocab) self.observation_space = observation_space or text_spaces.Word( max_length=200, vocab=vocab) def seed(self, seed: Optional[int] = None) -> List[int]: """ Set the seed for this environment's random generator(s). This environment use a random generator to shuffle the order in which the games are played. Arguments: seed: Number that will be used to seed the random generators. Returns: All the seeds used to set this environment's random generator(s). """ # We shuffle the order in which the game will be seen. rng = np.random.RandomState(seed) gamefiles = list( self.gamefiles) # Soft copy to avoid shuffling original list. rng.shuffle(gamefiles) # Prepare iterator used for looping through the games. self._gamefiles_iterator = shuffled_cycle(gamefiles, rng=rng) return [seed] def reset(self) -> Tuple[str, Dict[str, Any]]: """ Resets the text-based environment. Resetting this environment means starting the next game in the pool. Returns: A tuple (observation, info) where * observation: text observed in the initial state; * infos: additional information as requested. """ self.current_gamefile = next(self._gamefiles_iterator) if self.textworld_env is None: env = textworld.start(self.current_gamefile, self.request_infos) self.textworld_env = Filter(env) else: self.textworld_env.load(self.current_gamefile) self.ob, infos = self.textworld_env.reset() return self.ob, infos def skip(self, nb_games: int = 1) -> None: """ Skip games. Arguments: nb_games: Number of games to skip. """ for _ in range(nb_games): next(self._gamefiles_iterator) def step(self, command) -> Tuple[str, Dict[str, Any]]: """ Runs a command in the text-based environment. Arguments: command: Text command to send to the game interpreter. Returns: A tuple (observation, score, done, info) where * observation: text observed in the new state; * score: total number of points accumulated so far; * done: whether the game is finished or not; * infos: additional information as requested. """ self.last_command = command self.ob, score, done, infos = self.textworld_env.step( self.last_command) return self.ob, score, done, infos def close(self) -> None: """ Close this environment. """ if self.textworld_env is not None: self.textworld_env.close() self.textworld_env = None def render(self, mode: str = 'human') -> Optional[Union[StringIO, str]]: """ Renders the current state of this environment. The rendering is composed of the previous text command (if there's one) and the text describing the current observation. Arguments: mode: Controls where and how the text is rendered. Supported modes are: * human: Display text to the current display or terminal and return nothing. * ansi: Return a `StringIO` containing a terminal-style text representation. The text can include newlines and ANSI escape sequences (e.g. for colors). * text: Return a string (`str`) containing the text without any ANSI escape sequences. Returns: Depending on the `mode`, this method returns either nothing, a string, or a `StringIO` object. """ outfile = StringIO() if mode in ['ansi', "text"] else sys.stdout msg = self.ob.rstrip() + "\n" if self.last_command is not None: command = "> " + self.last_command if mode in ["ansi", "human"]: command = colorize(command, "yellow", highlight=False) msg = command + "\n" + msg if mode == "human": # Wrap each paragraph at 80 characters. paragraphs = msg.split("\n") paragraphs = [ "\n".join(textwrap.wrap(paragraph, width=80)) for paragraph in paragraphs ] msg = "\n".join(paragraphs) outfile.write(msg + "\n") if mode == "text": outfile.seek(0) return outfile.read() if mode == 'ansi': return outfile
def _make_env(): env = GenericEnvironment(self.request_infos) env = Filter(env) return env