def bench_loading_speed(): REPEAT = 1000 start = time.time() rom = pjoin(DATA_PATH, "905.z5") for _ in range(REPEAT): env = jericho.FrotzEnv(rom) env.reset() env.close() duration1 = time.time() - start print("Calling FrotzEnv({}) {} times in {:6.2f} secs.".format( rom, REPEAT, duration1)) del env start = time.time() env = jericho.FrotzEnv(rom) for _ in range(REPEAT - 1): env.load(rom) env.reset() env.close() duration2 = time.time() - start print("Calling env.load({}) {} times in {:6.2f} secs.".format( rom, REPEAT, duration2)) print("Speedup: {}x.".format(duration1 / duration2))
def test_saving_opcode_in_state(): # At some point in yomomma.z8, the player is asked to hit [enter] to continue. # Restoring to that particular state without setting the opcode appropriately # would result in the emulated halting. COMMANDS = [ 'south', 'southeast', 'sit', 'wait', 'yes', 'west', 'north', # Actions needed to reach the corner case. 'get in stage', # -> opcode == 246 (z_read_char) '', # Pressing [enter] -> opcode == 246 (z_read_char) '[save]', '', # Pressing [enter] -> opcode == 228 (z_read_line) '[restore]', # If not resetted properly, opcode == 228 (z_read_line) instaead of 246 (z_read_char) 'examine something', # Causes an illegal opcode 'look' # Emulator has halted. ] rom = pjoin(DATA_PATH, "roms", "yomomma.z8") if not os.path.exists(rom): raise unittest.SkipTest("Missing data: {}".format(rom)) env = jericho.FrotzEnv(rom) env.reset() state = None for cmd in COMMANDS: if cmd == "[save]": state = env.get_state() elif cmd == "[restore]": env.set_state(state) else: obs, rew, done, info = env.step(cmd) assert not env._emulator_halted()
def test_loading_unsupported_game(): with warnings.catch_warnings(record=True) as w: env = jericho.FrotzEnv(TEST_GAME) warnings.simplefilter("always") assert len(w) == 1 assert issubclass(w[-1].category, UnsupportedGameWarning) assert TEST_GAME in str(w[-1].message) state = env.reset() assert env.get_score() == 0 assert env.get_max_score() == 0 # Instead of 3. state, score, done, _ = env.step("go east") state, score, done, _ = env.step("insert carrot into chest") assert score == 0 # Instead of 2. state, score, done, _ = env.step("close chest") assert score == 0 # Instead of 3. assert "The End" in state assert not done # Instead of True assert not env.victory() # Instead of True state = env.reset() state, score, done, _ = env.step("eat carrot") assert "You lost" in state assert not done # Instead of True assert not env.game_over() # Instead of True
def test_loading_a_textworld_game(): env = jericho.FrotzEnv(TEST_GAME) state = env.reset() assert not env.victory() assert not env.game_over() assert env.get_score() == 0 assert env.get_max_score() == 3 state, score, done, _ = env.step("go east") assert not done assert score == 0 state, score, done, _ = env.step("insert carrot into chest") assert not done assert score == 2 state, score, done, _ = env.step("close chest") assert done assert env.victory() assert score == 3 state = env.reset() state, score, done, _ = env.step("eat carrot") assert done assert env.game_over() # Lost assert score == 0
def test_loading_unsupported_game(): # Note: dummy.z8 is the same as tw-game.z8. gamefile = "dummy.z8" with warnings.catch_warnings(record=True) as w: env = jericho.FrotzEnv(pjoin(DATA_PATH, gamefile)) warnings.simplefilter("always") assert len(w) == 1 assert issubclass(w[-1].category, UnsupportedGameWarning) assert gamefile in str(w[-1].message) state, info = env.reset() assert env.get_score() == 0 assert env.get_max_score() == 0 # Instead of 3. state, score, done, _ = env.step("go east") state, score, done, _ = env.step("insert carrot into chest") assert score == 0 # Instead of 2. state, score, done, _ = env.step("close chest") assert score == 0 # Instead of 3. assert "The End" in state assert not done # Instead of True assert not env.victory() # Instead of True state, info = env.reset() state, score, done, _ = env.step("eat carrot") assert "You lost" in state assert not done # Instead of True assert not env.game_over() # Instead of True
def test_loading_a_textworld_game(): env = jericho.FrotzEnv(pjoin(DATA_PATH, "tw-game.z8")) state, info = env.reset() assert not env.victory() assert not env.game_over() assert env.get_score() == 0 assert env.get_max_score() == 3 state, reward, done, info = env.step("go east") assert not done assert reward == 0 state, reward, done, info = env.step("insert carrot into chest") assert not done assert reward == 2 state, reward, done, info = env.step("close chest") assert done assert env.victory() assert reward == 1 assert info['score'] == 3 state = env.reset() state, reward, done, info = env.step("eat carrot") assert done assert env.game_over() # Lost assert info['score'] == 0
def create(self): ''' Create the Jericho environment and connect to redis. ''' self.env = jericho.FrotzEnv(self.rom_path, self.seed) self.bindings = jericho.load_bindings(self.rom_path) self.act_gen = TemplateActionGenerator(self.bindings) self.max_word_len = self.bindings['max_word_length'] self.vocab, self.vocab_rev = load_vocab(self.env) self.conn_valid = redis.Redis(host='localhost', port=6379, db=0) self.conn_openie = redis.Redis(host='localhost', port=6379, db=1)
def test_valid_action_identification(): rom_path = pjoin(DATA_PATH, '905.z5') env = jericho.FrotzEnv(rom_path) obs, info = env.reset() valid = env.get_valid_actions() assert 'take wallet' in valid assert 'open wallet' in valid assert 'take keys' in valid assert 'get up' in valid assert 'take phone' in valid
def reset(self): self.close() # In case, it is running. # Start the game using Jericho. self._jericho = jericho.FrotzEnv(self.gamefile, self._seed) self.state = GameState() self.state.raw, _ = self._jericho.reset() self._gather_infos() return self.state
def __init__(self, rom_path, seed): self.jericho_env = jericho.FrotzEnv(rom_path, seed) self.action_space = NaturalLanguage(max_words=4) self.observation_space = NaturalLanguage() self.high = None self.low = None self.seed(seed) self.vocabulary = self.jericho_env.get_dictionary()
def test_for_memory_leaks(): # Make sure we don't have severe memory leaks. def _get_mem(): import resource return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss unit = 1024 # Denominator to get memory usage in MB. if sys.platform == 'darwin': # According to https://github.com/apple/darwin-xnu/blob/master/bsd/man/man2/getrusage.2#L95 # OSX outputs ru_maxrss in bytes rather then kilobytes. unit = 1024 * 1024 gamefile1 = pjoin(DATA_PATH, "905.z5") env1 = jericho.FrotzEnv(gamefile1) env1.reset() del env1 mem_start = _get_mem() print('Memory usage: {:.1f}MB'.format(mem_start / unit)) for _ in range(1000): # Make sure we don't have memory leak. env1 = jericho.FrotzEnv(gamefile1) env1.reset() del env1 mem_mid = _get_mem() print('Memory usage: {:.1f}MB ({:+.1f}MB)'.format( mem_mid / unit, (mem_mid-mem_start) / unit )) for _ in range(1000): env1 = jericho.FrotzEnv(gamefile1) env1.reset() del env1 mem_end = _get_mem() print('Memory usage: {:.1f}MB ({:+.1f}MB)'.format( mem_end / unit, (mem_end-mem_mid) / unit)) # Less than 1MB memory leak per 1000 load/unload cycles. assert (mem_mid - mem_start) < 1024 * 1024 assert (mem_end - mem_mid) < 1024 * 1024
def test_multiple_instances(): gamefile1 = pjoin(DATA_PATH, "905.z5") gamefile2 = pjoin(DATA_PATH, "tw-game.z8") # Make sure both frotz_lib have different handles. env1 = jericho.FrotzEnv(gamefile1) env2 = jericho.FrotzEnv(gamefile2) assert env1.frotz_lib._handle != env2.frotz_lib._handle # Test we can play two different games in parallel. state1, _ = env1.reset() state2, _ = env2.reset() assert "9:05 by Adam Cadre" in state1 assert "TextWorld" in state2 state1, _, _, _ = env1.step("examine me") env1.close() assert "You're covered with mud and dried sweat." in state1 state2, _, _, _ = env2.step("examine me") env2.close() assert "As good-looking as ever." in state2
def reset(self): self.close() # In case, it is running. self.game_state = self.GAME_STATE_CLASS(self) # Start the game using Jericho. self._jericho = jericho.FrotzEnv(self.game_filename, self._seed) # Grab start info from game. start_output = self._jericho.reset() self.game_state.init(start_output) self.game_state._score = self._jericho.get_score() self.game_state._max_score = self._jericho.get_max_score() return self.game_state
def extract_vocab_from_gamefile(gamefile: str) -> Set[str]: vocab = set() jsonfile = os.path.splitext(gamefile)[0] + ".json" if os.path.isfile(jsonfile): game = Game.load(jsonfile) vocab |= extract_vocab(game) if re.search(r"\.z[1-8]$", gamefile): # For Z-Machine games, extract vocab using Jericho. import jericho env = jericho.FrotzEnv(gamefile) vocab |= set(entry.word for entry in env.get_dictionary()) return vocab
def reset(self): # Since Jericho is not thread-safe, we load it locally so # its global memory is not shared accross multiple processes # when forking. import jericho self.close() # In case, it is running. # Start the game using Jericho. self._jericho = jericho.FrotzEnv(self.gamefile, self._seed) self.state = GameState() self.state.raw, _ = self._jericho.reset() self._gather_infos() return self.state
def load(self, z_file: str) -> None: self.gamefile = os.path.abspath(z_file) _, ext = os.path.splitext(os.path.basename(self.gamefile)) # Check if game is supported by Jericho. if not ext.startswith(".z"): raise ValueError("Only .z[1-8] files are supported!") if not os.path.isfile(self.gamefile): raise FileNotFoundError(self.gamefile) if self._jericho is None: # Start the game using Jericho. self._jericho = jericho.FrotzEnv(self.gamefile, self._seed) else: self._jericho.load(self.gamefile)
def test_valid_action_identification(): rom_path = pjoin(DATA_PATH, '905.z5') env = jericho.FrotzEnv(rom_path) bindings = jericho.load_bindings(rom_path) act_gen = TemplateActionGenerator(bindings) obs, info = env.reset() # interactive_objs = [obj[0] for obj in env.identify_interactive_objects(use_object_tree=True)] interactive_objs = ['phone', 'keys', 'wallet'] candidate_actions = act_gen.generate_actions(interactive_objs) valid = env.find_valid_actions(candidate_actions) assert 'take wallet' in valid assert 'open wallet' in valid assert 'take keys' in valid assert 'get up' in valid assert 'take phone' in valid
def test_copy(): rom = pjoin(DATA_PATH, "905.z5") env = jericho.FrotzEnv(rom) env.reset() walkthrough = env.get_walkthrough() expected = [env.step(act) for act in walkthrough] env.reset() for i, act in enumerate(walkthrough): obs, rew, done, info = env.step(act) if i + 1 < len(walkthrough): fork = env.copy() for j, cmd in enumerate(walkthrough[i+1:], start=i+1): obs, rew, done, info = fork.step(cmd) assert (obs, rew, done, info) == expected[j]
def test_copy(): rom = pjoin(DATA_PATH, "905.z5") bindings = jericho.load_bindings(rom) env = jericho.FrotzEnv(rom, seed=bindings['seed']) env.reset() walkthrough = bindings['walkthrough'].split('/') expected = [env.step(act) for act in walkthrough] env.reset() for i, act in enumerate(walkthrough): obs, rew, done, info = env.step(act) if i + 1 < len(walkthrough): fork = env.copy() for j, cmd in enumerate(walkthrough[i+1:], start=i+1): obs, rew, done, info = fork.step(cmd) assert (obs, rew, done, info) == expected[j]
def test_cleaning_observation(): gamefile = "tw-cooking-recipe3+cook+cut-2057SPdQu0mWiv0k.z8" env = jericho.FrotzEnv(pjoin(DATA_PATH, gamefile)) state, info = env.reset() EXPECTED = textwrap.dedent( r""" ________ ________ __ __ ________ | \| \| \ | \| \ \$$$$$$$$| $$$$$$$$| $$ | $$ \$$$$$$$$ | $$ | $$__ \$$\/ $$ | $$ | $$ | $$ \ >$$ $$ | $$ | $$ | $$$$$ / $$$$\ | $$ | $$ | $$_____ | $$ \$$\ | $$ | $$ | $$ \| $$ | $$ | $$ \$$ \$$$$$$$$ \$$ \$$ \$$ __ __ ______ _______ __ _______ | \ _ | \ / \ | \ | \ | \ | $$ / \ | $$| $$$$$$\| $$$$$$$\| $$ | $$$$$$$\ | $$/ $\| $$| $$ | $$| $$__| $$| $$ | $$ | $$ | $$ $$$\ $$| $$ | $$| $$ $$| $$ | $$ | $$ | $$ $$\$$\$$| $$ | $$| $$$$$$$\| $$ | $$ | $$ | $$$$ \$$$$| $$__/ $$| $$ | $$| $$_____ | $$__/ $$ | $$$ \$$$ \$$ $$| $$ | $$| $$ \| $$ $$ \$$ \$$ \$$$$$$ \$$ \$$ \$$$$$$$$ \$$$$$$$ You are hungry! Let's cook a delicious meal. Check the cookbook in the kitchen for the recipe. Once done, enjoy your meal! -= Kitchen =- You arrive in a kitchen. An ordinary kind of place. The room seems oddly familiar, as though it were only superficially different from the other rooms in the building. You lean against the wall, inadvertently pressing a secret button. The wall opens up to reveal a fridge. The fridge is empty, what a horrible day! You make out a closed oven. You lean against the wall, inadvertently pressing a secret button. The wall opens up to reveal a table. You see a cookbook on the table. Now that's what I call TextWorld! What's that over there? It looks like it's a counter. You see a red apple and a knife on the counter. You make out a stove. But the thing hasn't got anything on it. """) assert [line.strip() for line in state.split("\n")] == [line.strip() for line in EXPECTED.split("\n")]
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 games. json_files = [ os.path.splitext(gamefile)[0] + ".json" for gamefile in self.gamefiles ] games_iter = (textworld.Game.load(gamefile) for gamefile in json_files if os.path.isfile(gamefile)) vocab = set(textworld.text_utils.extract_vocab(games_iter)) zmachine_games = [ gamefile for gamefile in self.gamefiles if re.search(r"\.z[1-8]$", gamefile) ] for gamefile in zmachine_games: env = jericho.FrotzEnv(gamefile) vocab |= set(entry.word for entry in env.get_dictionary()) vocab = sorted(vocab) 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 parse_args(): parser = argparse.ArgumentParser() parser.add_argument("filename", help="Path to a Z-Machine game.") parser.add_argument("--walkthrough", help="External walkthrough (one command per line). Default: use Jericho's one, if it exists.") parser.add_argument("--skip-to", type=int, default=0, help="Auto-play walkthrough until the nth command before dropping into interactive mode.") return parser.parse_args() args = parse_args() bindings = jericho.load_bindings(args.filename) env = jericho.FrotzEnv(args.filename, seed=bindings['seed']) history = [] obs, info = env.reset() history.append(env.get_state()) STEP_BY_STEP_WALKTRHOUGH = True walkthrough = bindings.get('walkthrough', '').split('/') if args.walkthrough: walkthrough = [] for line in open(args.walkthrough): cmd = line.split("#")[0].strip() if cmd: walkthrough.append(cmd)
help="Launch ipdb on FAIL.") parser.add_argument( "-v", "--verbose", action="store_true", help="Print the last observation when not achieving max score.") return parser.parse_args() args = parse_args() filename_max_length = max(map(len, args.filenames)) for filename in sorted(args.filenames): print(filename.ljust(filename_max_length), end=" ") env = jericho.FrotzEnv(filename) if not env.is_fully_supported: print(colored("SKIP\tUnsupported game", 'yellow')) continue if "walkthrough" not in env.bindings: print(colored("SKIP\tMissing walkthrough", 'yellow')) continue env.reset() #walkthrough = bindings['walkthrough'].split('/') for cmd in env.get_walkthrough(): obs, rew, done, info = env.step(cmd) if not done:
def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("filename", help="Path to a Z-Machine game.") parser.add_argument("--walkthrough", help="External walkthrough (one command per line). Default: use Jericho's one, if it exists.") parser.add_argument("--skip-to", type=int, default=0, help="Auto-play walkthrough until the nth command before dropping into interactive mode.") return parser.parse_args() args = parse_args() history = [] env = jericho.FrotzEnv(args.filename) obs, info = env.reset() history.append(env.get_state()) STEP_BY_STEP_WALKTRHOUGH = True walkthrough = env.get_walkthrough() if args.walkthrough: walkthrough = [] for line in open(args.walkthrough): cmd = line.split("#")[0].strip() if cmd: walkthrough.append(cmd) obs_list = [obs]