diff --git a/README.md b/README.md index 16c8ca2..74713c1 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ To install Crafter, run `pip3 install crafter`. The environment follows the outputs are one of 17 categorical actions. ```py -import gym +import gymnasium as gym import crafter env = gym.make('CrafterReward-v1') # Or CrafterNoReward-v1 @@ -93,7 +93,7 @@ obs = env.reset() done = False while not done: action = env.action_space.sample() - obs, reward, done, info = env.step(action) + obs, reward, terminated, truncated, info = env.step(action) ``` [gym]: https://github.com/openai/gym diff --git a/crafter/__init__.py b/crafter/__init__.py index caa8a0d..e42bc40 100644 --- a/crafter/__init__.py +++ b/crafter/__init__.py @@ -2,7 +2,7 @@ from .recorder import Recorder try: - import gym + import gymnasium as gym gym.register( id='CrafterReward-v1', entry_point='crafter:Env', diff --git a/crafter/env.py b/crafter/env.py index e0d8e7b..5fab058 100644 --- a/crafter/env.py +++ b/crafter/env.py @@ -10,7 +10,7 @@ # Gym is an optional dependency. try: - import gym + import gymnasium as gym DiscreteSpace = gym.spaces.Discrete BoxSpace = gym.spaces.Box DictSpace = gym.spaces.Dict @@ -36,7 +36,6 @@ def __init__( self._reward = reward self._length = length self._seed = seed - self._episode = 0 self._world = engine.World(area, constants.materials, (12, 12)) self._textures = engine.Textures(constants.root / 'assets') item_rows = int(np.ceil(len(constants.items) / view[0])) @@ -67,18 +66,22 @@ def action_space(self): def action_names(self): return constants.actions - def reset(self): + def reset(self, seed=None, options={}): center = (self._world.area[0] // 2, self._world.area[1] // 2) - self._episode += 1 self._step = 0 - self._world.reset(seed=hash((self._seed, self._episode)) % (2 ** 31 - 1)) + if not seed: + seed = self._seed + if not seed: + seed = 0 + self._world.reset(seed=seed) self._update_time() self._player = objects.Player(self._world, center) self._last_health = self._player.health self._world.add(self._player) self._unlocked = set() worldgen.generate_world(self._world, self._player) - return self._obs() + info = {"options": options} + return self._obs(), info def step(self, action): self._step += 1 @@ -115,7 +118,9 @@ def step(self, action): } if not self._reward: reward = 0.0 - return obs, reward, done, info + terminated = done + truncated = False + return obs, reward, terminated, truncated, info def render(self, size=None): size = size or self._size diff --git a/crafter/recorder.py b/crafter/recorder.py index 76dc6b0..6995d23 100644 --- a/crafter/recorder.py +++ b/crafter/recorder.py @@ -5,8 +5,10 @@ import imageio import numpy as np +import gymnasium -class Recorder: + +class Recorder(gymnasium.Env): def __init__( self, env, directory, save_stats=True, save_video=True, @@ -24,6 +26,12 @@ def __getattr__(self, name): raise AttributeError(name) return getattr(self._env, name) + def reset(self, seed=None, options={}): + return self._env.reset(seed, options) + + def step(self, action): + return self._env.step(action) + class StatsRecorder: @@ -42,8 +50,8 @@ def __getattr__(self, name): raise AttributeError(name) return getattr(self._env, name) - def reset(self): - obs = self._env.reset() + def reset(self, seed=None, options={}): + obs = self._env.reset(seed=seed, options=options) self._length = 0 self._reward = 0 self._unlocked = None @@ -51,7 +59,8 @@ def reset(self): return obs def step(self, action): - obs, reward, done, info = self._env.step(action) + obs, reward, terminated, truncated, info = self._env.step(action) + done = terminated or truncated self._length += 1 self._reward += info['reward'] if done: @@ -59,7 +68,7 @@ def step(self, action): for key, value in info['achievements'].items(): self._stats[f'achievement_{key}'] = value self._save() - return obs, reward, done, info + return obs, reward, terminated, truncated, info def _save(self): self._file.write(json.dumps(self._stats) + '\n') @@ -70,7 +79,7 @@ class VideoRecorder: def __init__(self, env, directory, size=(512, 512)): if not hasattr(env, 'episode_name'): - env = EpisodeName(env) + env = EpisodeName self._env = env self._directory = pathlib.Path(directory).expanduser() self._directory.mkdir(exist_ok=True, parents=True) @@ -82,17 +91,18 @@ def __getattr__(self, name): raise AttributeError(name) return getattr(self._env, name) - def reset(self): - obs = self._env.reset() + def reset(self, seed=None, options={}): + obs = self._env.reset(seed=seed, options=options) self._frames = [self._env.render(self._size)] return obs def step(self, action): - obs, reward, done, info = self._env.step(action) + obs, reward, terminated, truncated, info = self._env.step(action) + done = terminated or truncated self._frames.append(self._env.render(self._size)) if done: self._save() - return obs, reward, done, info + return obs, reward, terminated, truncated, info def _save(self): filename = str(self._directory / (self._env.episode_name + '.mp4')) @@ -103,7 +113,7 @@ class EpisodeRecorder: def __init__(self, env, directory): if not hasattr(env, 'episode_name'): - env = EpisodeName(env) + env = EpisodeName self._env = env self._directory = pathlib.Path(directory).expanduser() self._directory.mkdir(exist_ok=True, parents=True) @@ -114,8 +124,8 @@ def __getattr__(self, name): raise AttributeError(name) return getattr(self._env, name) - def reset(self): - obs = self._env.reset() + def reset(self, seed=None, options={}): + obs = self._env.reset(seed=seed, options=options) self._episode = [{'image': obs}] return obs @@ -123,7 +133,8 @@ def step(self, action): # Transitions are defined from the environment perspective, meaning that a # transition contains the action and the resulting reward and next # observation produced by the environment in response to said action. - obs, reward, done, info = self._env.step(action) + obs, reward, terminated, truncated, info = self._env.step(action) + done = terminated or truncated transition = { 'action': action, 'image': obs, 'reward': reward, 'done': done, } @@ -138,7 +149,7 @@ def step(self, action): self._episode.append(transition) if done: self._save() - return obs, reward, done, info + return obs, reward, terminated, truncated, info def _save(self): filename = str(self._directory / (self._env.episode_name + '.npz')) @@ -165,20 +176,21 @@ def __getattr__(self, name): raise AttributeError(name) return getattr(self._env, name) - def reset(self): - obs = self._env.reset() + def reset(self, seed=None, options={}): + obs = self._env.reset(seed=seed, options=options) self._timestamp = None self._unlocked = None self._length = 0 return obs def step(self, action): - obs, reward, done, info = self._env.step(action) + obs, reward, terminated, truncated, info = self._env.step(action) + done = terminated or truncated self._length += 1 if done: self._timestamp = datetime.datetime.now().strftime('%Y%m%dT%H%M%S') self._unlocked = sum(int(v >= 1) for v in info['achievements'].values()) - return obs, reward, done, info + return obs, reward, terminated, truncated, info @property def episode_name(self): diff --git a/crafter/run_gui.py b/crafter/run_gui.py index 06885f9..2e08834 100644 --- a/crafter/run_gui.py +++ b/crafter/run_gui.py @@ -60,9 +60,12 @@ def main(): size[1] = size[1] or args.window[1] env = crafter.Env( - area=args.area, view=args.view, length=args.length, seed=args.seed) + area=args.area, view=args.view, length=args.length) env = crafter.Recorder(env, args.record) - env.reset() + + seed = args.seed + + env.reset(seed=seed) achievements = set() duration = 0 return_ = 0 @@ -108,7 +111,8 @@ def main(): action = 'noop' # Environment step. - _, reward, done, _ = env.step(env.action_names.index(action)) + _, reward, terminated, truncated, _ = env.step(env.action_names.index(action)) + done = terminated or truncated duration += 1 # Achievements. @@ -135,7 +139,8 @@ def main(): running = False if args.death == 'reset': print('\nStarting a new episode.') - env.reset() + seed = hash(seed) % (2 ** 31 - 1) + env.reset(seed=seed) achievements = set() was_done = False duration = 0 diff --git a/crafter/run_random.py b/crafter/run_random.py index 8ab1367..9575d87 100644 --- a/crafter/run_random.py +++ b/crafter/run_random.py @@ -17,6 +17,8 @@ def main(): parser.add_argument('--episodes', type=int, default=1) args = parser.parse_args() + seed = args.seed + random = np.random.RandomState(args.seed) crafter.constants.items['health']['max'] = args.health crafter.constants.items['health']['initial'] = args.health @@ -26,7 +28,8 @@ def main(): for _ in range(args.episodes): start = time.time() - obs = env.reset() + seed = hash(seed) % (2 ** 31 - 1) + obs = env.reset(seed=seed) print('') print(f'Reset time: {1000*(time.time()-start):.2f}ms') print('Coal exist: ', env._world.count('coal')) @@ -37,7 +40,8 @@ def main(): done = False while not done: action = random.randint(0, env.action_space.n) - obs, reward, done, info = env.step(action) + obs, reward, terminated, truncated, info = env.step(action) + done = terminated or truncated duration = time.time() - start step = env._step print(f'Step time: {1000*duration/step:.2f}ms ({int(step/duration)} FPS)') diff --git a/crafter/run_terrain.py b/crafter/run_terrain.py index 009955b..b0f83cf 100644 --- a/crafter/run_terrain.py +++ b/crafter/run_terrain.py @@ -16,10 +16,13 @@ def main(): parser.add_argument('--filename', type=str, default='terrain.png') args = parser.parse_args() + seed = args.seed + env = crafter.Env(args.area, args.area, args.size, seed=args.seed) images = [] for index in range(args.amount): - images.append(env.reset()) + seed = hash(seed) % (2 ** 31 - 1) + images.append(env.reset(seed)) diamonds = env._world.count('diamond') print(f'Map: {index:>2}, diamonds: {diamonds:>2}') diff --git a/examples/run_random.py b/examples/run_random.py index 217a5b5..18cfc75 100644 --- a/examples/run_random.py +++ b/examples/run_random.py @@ -6,8 +6,11 @@ parser = argparse.ArgumentParser() parser.add_argument('--outdir', default='logdir/crafter_noreward-random/0') parser.add_argument('--steps', type=float, default=1e6) +parser.add_argument('--seed', type=int, default=None) args = parser.parse_args() +seed = args.seed + env = crafter.Env() env = crafter.Recorder( env, args.outdir, @@ -22,8 +25,10 @@ bar = tqdm.tqdm(total=args.steps, smoothing=0) while step < args.steps or not done: if done: - env.reset() + seed = hash(seed) % (2 ** 31 - 1) + env.reset(seed) done = False - _, _, done, _ = env.step(action_space.sample()) + _, _, terminated, truncated, _ = env.step(action_space.sample()) + done = terminated or truncated step += 1 bar.update(1) diff --git a/setup.py b/setup.py index 26c0c63..3b779fb 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setuptools.setup( name='crafter', - version='1.8.3', + version='1.8.4', description='Open world survival game for reinforcement learning.', url='http://github.com/danijar/crafter', long_description=pathlib.Path('README.md').read_text(),