├── .gitignore
├── LICENSE
├── README.md
├── analysis
├── common.py
├── plot_counts.py
├── plot_reward.py
├── plot_scores.py
├── plot_spectrum.py
├── print_reward.py
├── print_scores.py
├── print_spectrum.py
└── read_metrics.py
├── crafter
├── __init__.py
├── assets
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ ├── arrow-down.png
│ ├── arrow-left.png
│ ├── arrow-right.png
│ ├── arrow-up.png
│ ├── coal.png
│ ├── cow.png
│ ├── debug-2.png
│ ├── debug-3.png
│ ├── debug.png
│ ├── diamond.png
│ ├── drink.png
│ ├── energy.png
│ ├── fence.png
│ ├── food.png
│ ├── furnace.png
│ ├── grass.png
│ ├── health.png
│ ├── iron.png
│ ├── iron_pickaxe.png
│ ├── iron_sword.png
│ ├── lava.png
│ ├── leaves.png
│ ├── log.png
│ ├── path.png
│ ├── plant-ripe.png
│ ├── plant-young.png
│ ├── plant.png
│ ├── player-down.png
│ ├── player-left.png
│ ├── player-right.png
│ ├── player-sleep.png
│ ├── player-up.png
│ ├── player.png
│ ├── sand.png
│ ├── sapling.png
│ ├── skeleton.png
│ ├── stone.png
│ ├── stone_pickaxe.png
│ ├── stone_sword.png
│ ├── table.png
│ ├── tree.png
│ ├── unknown.png
│ ├── water.png
│ ├── wood.png
│ ├── wood_pickaxe.png
│ ├── wood_sword.png
│ └── zombie.png
├── constants.py
├── data.yaml
├── engine.py
├── env.py
├── objects.py
├── recorder.py
├── run_gui.py
├── run_random.py
├── run_terrain.py
└── worldgen.py
├── examples
├── run_ppo.py
└── run_random.py
├── media
├── scores.png
├── terrain.png
├── video.gif
└── video.mp4
├── plots
├── counts-dreamerv2.pdf
├── counts-ppo.pdf
├── counts-rainbow.pdf
├── counts-random.pdf
├── counts-unsup_plan2explore.pdf
├── counts-unsup_rnd.pdf
├── reward.pdf
├── scores-agents.pdf
├── scores-human.pdf
├── spectrum-noreward.pdf
└── spectrum-reward.pdf
├── scores
├── crafter_noreward-random.json
├── crafter_noreward-unsup_plan2explore.json
├── crafter_noreward-unsup_rnd.json
├── crafter_reward-dreamerv2.json
├── crafter_reward-human.json
├── crafter_reward-ppo.json
└── crafter_reward-rainbow.json
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.py[cod]
3 | *.egg-info
4 | /dist
5 | /*.png
6 | /*.mp4
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Danijar Hafner
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Status:** Stable release
2 |
3 | [](https://pypi.python.org/pypi/crafter/#history)
4 |
5 | # Crafter
6 |
7 | Open world survival game for evaluating a wide range of agent abilities within
8 | a single environment.
9 |
10 | 
11 |
12 | ## Overview
13 |
14 | Crafter features randomly generated 2D worlds where the player needs to forage
15 | for food and water, find shelter to sleep, defend against monsters, collect
16 | materials, and build tools. Crafter aims to be a fruitful benchmark for
17 | reinforcement learning by focusing on the following design goals:
18 |
19 | - **Research challenges:** Crafter poses substantial challenges to current
20 | methods, evaluating strong generalization, wide and deep exploration,
21 | representation learning, and long-term reasoning and credit assignment.
22 |
23 | - **Meaningful evaluation:** Agents are evaluated by semantically meaningful
24 | achievements that can be unlocked in each episode, offering insights into the
25 | ability spectrum of both reward agents and unsupervised agents.
26 |
27 | - **Iteration speed:** Crafter evaluates many agent abilities within a single
28 | env, vastly reducing the computational requirements over benchmarks suites
29 | that require training on many separate envs from scratch.
30 |
31 | See the research paper to find out more: [Benchmarking the Spectrum of Agent
32 | Capabilities](https://arxiv.org/pdf/2109.06780.pdf)
33 |
34 | ```
35 | @article{hafner2021crafter,
36 | title={Benchmarking the Spectrum of Agent Capabilities},
37 | author={Danijar Hafner},
38 | year={2021},
39 | journal={arXiv preprint arXiv:2109.06780},
40 | }
41 | ```
42 |
43 | ## Play Yourself
44 |
45 | ```sh
46 | python3 -m pip install crafter # Install Crafter
47 | python3 -m pip install pygame # Needed for human interface
48 | python3 -m crafter.run_gui # Start the game
49 | ```
50 |
51 |
52 | Keyboard mapping (click to expand)
53 |
54 | | Key | Action |
55 | | :-: | :----- |
56 | | WASD | Move around |
57 | | SPACE| Collect material, drink from lake, hit creature |
58 | | TAB | Sleep |
59 | | T | Place a table |
60 | | R | Place a rock |
61 | | F | Place a furnace |
62 | | P | Place a plant |
63 | | 1 | Craft a wood pickaxe |
64 | | 2 | Craft a stone pickaxe |
65 | | 3 | Craft an iron pickaxe |
66 | | 4 | Craft a wood sword |
67 | | 5 | Craft a stone sword |
68 | | 6 | Craft an iron sword |
69 |
70 |
71 |
72 | 
73 |
74 | ## Interface
75 |
76 | To install Crafter, run `pip3 install crafter`. The environment follows the
77 | [OpenAI Gym][gym] interface. Observations are images of size (64, 64, 3) and
78 | outputs are one of 17 categorical actions.
79 |
80 | ```py
81 | import gym
82 | import crafter
83 |
84 | env = gym.make('CrafterReward-v1') # Or CrafterNoReward-v1
85 | env = crafter.Recorder(
86 | env, './path/to/logdir',
87 | save_stats=True,
88 | save_video=False,
89 | save_episode=False,
90 | )
91 |
92 | obs = env.reset()
93 | done = False
94 | while not done:
95 | action = env.action_space.sample()
96 | obs, reward, done, info = env.step(action)
97 | ```
98 |
99 | [gym]: https://github.com/openai/gym
100 |
101 | ## Evaluation
102 |
103 | Agents are allowed a budget of 1M environmnent steps and are evaluated by their
104 | success rates of the 22 achievements and by their geometric mean score. Example
105 | scripts for computing these are included in the `analysis` directory of the
106 | repository.
107 |
108 | - **Reward:** The sparse reward is `+1` for unlocking an achievement during
109 | the episode and `-0.1` or `+0.1` for lost or regenerated health points.
110 | Results should be reported not as reward but as success rates and score.
111 |
112 | - **Success rates:** The success rates of the 22 achievemnts are computed
113 | as the percentage across all training episodes in which the achievement was
114 | unlocked, allowing insights into the ability spectrum of an agent.
115 |
116 | - **Crafter score:** The score is the geometric mean of success rates, so that
117 | improvements on difficult achievements contribute more than improvements on
118 | achievements with already high success rates.
119 |
120 | ## Scoreboards
121 |
122 | Please create a pull request if you would like to add your or another algorithm
123 | to the scoreboards. For the reinforcement learning and unsupervised agents
124 | categories, the interaction budget is 1M. The external knowledge category is
125 | defined more broadly.
126 |
127 | ### Reinforcement Learning
128 |
129 | | Algorithm | Score (%) | Reward | Open Source |
130 | |:----------|----------:|-------:|:-----------:|
131 | | [Curious Replay](https://arxiv.org/pdf/2306.15934.pdf) | 19.4±1.6 | - | [AutonomousAgentsLab/cr-dv3](https://github.com/AutonomousAgentsLab/cr-dv3) |
132 | | [PPO (ResNet)](https://arxiv.org/pdf/2307.03486.pdf)| 15.6±1.6 | 10.3±0.5 | [snu-mllab/Achievement-Distillation](https://github.com/snu-mllab/Achievement-Distillation)
133 | | [DreamerV3](https://arxiv.org/pdf/2301.04104v1.pdf) | 14.5±1.6 | 11.7±1.9 | [danijar/dreamerv3](https://github.com/danijar/dreamerv3) |
134 | | [LSTM-SPCNN](https://arxiv.org/pdf/2208.03374.pdf) | 12.1±0.8 | — | [astanic/crafter-ood](https://github.com/astanic/crafter-ood) |
135 | | [EDE](https://openreview.net/pdf?id=GZDsKahGY-2) | 11.7±1.0 | — | [yidingjiang/ede](https://github.com/yidingjiang/ede) |
136 | | [OC-SA](https://arxiv.org/pdf/2208.03374.pdf) | 11.1±0.7 | — | [astanic/crafter-ood](https://github.com/astanic/crafter-ood) |
137 | | [DreamerV2](https://arxiv.org/pdf/2010.02193.pdf) | 10.0±1.2 | 9.0±1.7 | [danijar/dreamerv2](https://github.com/danijar/dreamerv2) |
138 | | [PPO](https://arxiv.org/pdf/1710.02298.pdf) | 4.6±0.3 | 4.2±1.2 | [DLR-RM/stable-baselines3](https://github.com/DLR-RM/stable-baselines3) |
139 | | [Rainbow](https://arxiv.org/pdf/1710.02298.pdf) | 4.3±0.2 | 6.0±1.3 | [Kaixhin/Rainbow](https://github.com/Kaixhin/Rainbow) |
140 |
141 | ### Unsupervised Agents
142 |
143 | | Algorithm | Score (%) | Reward | Open Source |
144 | |:----------|----------:|-------:|:-----------:|
145 | | [Plan2Explore](https://arxiv.org/pdf/2010.02193.pdf) | 2.1±0.1 | 2.1±1.5 | [danijar/dreamerv2](https://github.com/danijar/dreamerv2) |
146 | | [RND](https://arxiv.org/pdf/1810.12894.pdf) | 2.0±0.1 | 0.7±1.3 | [alirezakazemipour/PPO-RND](https://github.com/alirezakazemipour/PPO-RND) |
147 | | Random | 1.6±0.0 | 2.1±1.3 | — |
148 |
149 | ### External Knowledge
150 |
151 | | Algorithm | Score (%) | Reward | Uses | Interaction | Open Source |
152 | |:----------|----------:|-------:|:-----|:-----------:|:-----------:|
153 | | [Human](https://en.wikipedia.org/wiki/Human) | 50.5±6.8 | 14.3±2.3 | Life experience | 0 | [crafter_human_dataset](https://archive.org/details/crafter_human_dataset) |
154 | | [SPRING](https://arxiv.org/pdf/2305.15486.pdf) | 27.3±1.2 | 12.3±0.7 | LLM, scene description, Crafter paper | 0 | ❌ |
155 | | [Achievement Distillation](https://arxiv.org/pdf/2307.03486.pdf) | 21.8±1.4 | 12.6±0.3 | Reward structure | 1M | [snu-mllab/Achievement-Distillation](https://github.com/snu-mllab/Achievement-Distillation) |
156 | | [ELLM](https://arxiv.org/pdf/2302.06692.pdf) | — | 6.0±0.4 | LLM, scene description | 5M | ❌ |
157 |
158 | ## Baselines
159 |
160 | Baseline scores of various agents are available for Crafter, both with and
161 | without rewards. The scores are available in JSON format in the `scores`
162 | directory of the repository. For comparison, the score of human expert players
163 | is 50.5\%. The [baseline
164 | implementations](https://github.com/danijar/crafter-baselines) are available as
165 | a separate repository.
166 |
167 |
168 |
169 | ## Questions
170 |
171 | Please [open an issue][issues] on Github.
172 |
173 | [issues]: https://github.com/danijar/crafter/issues
174 |
--------------------------------------------------------------------------------
/analysis/common.py:
--------------------------------------------------------------------------------
1 | import json
2 | import pathlib
3 | import warnings
4 |
5 | import numpy as np
6 |
7 |
8 | def load_runs(filenames, budget=1e6, verbose=True):
9 | verbose and print('')
10 | runs = []
11 | for filename in filenames:
12 | loaded = json.loads(pathlib.Path(filename).read_text())
13 | for run in [loaded] if isinstance(loaded, dict) else loaded:
14 | message = f'Loading {run["method"]} seed {run["seed"]}'
15 | verbose and print(message, flush=True)
16 | if run['xs'][-1] < budget - 1e4:
17 | verbose and print(f' Contains only {run["xs"][-1]} steps!')
18 | runs.append(run)
19 | verbose and print('')
20 | return runs
21 |
22 |
23 | def compute_success_rates(runs, budget=1e6, sortby=None):
24 | methods = sorted(set(run['method'] for run in runs))
25 | seeds = sorted(set(run['seed'] for run in runs))
26 | tasks = sorted(key for key in runs[0] if key.startswith('achievement_'))
27 | percents = np.empty((len(methods), len(seeds), len(tasks)))
28 | percents[:] = np.nan
29 | for run in runs:
30 | episodes = (np.array(run['xs']) <= budget).sum()
31 | i = methods.index(run['method'])
32 | j = seeds.index(run['seed'])
33 | for key, values in run.items():
34 | if key in tasks:
35 | k = tasks.index(key)
36 | percent = 100 * (np.array(values[:episodes]) >= 1).mean()
37 | percents[i, j, k] = percent
38 | if isinstance(sortby, (str, int)):
39 | if isinstance(sortby, str):
40 | sortby = methods.index(sortby)
41 | order = np.argsort(-np.nanmean(percents[sortby], 0), -1)
42 | percents = percents[:, :, order]
43 | tasks = np.array(tasks)[order].tolist()
44 | return percents, methods, seeds, tasks
45 |
46 |
47 | def compute_scores(percents):
48 | # Geometric mean with an offset of 1%.
49 | assert (0 <= percents).all() and (percents <= 100).all()
50 | if (percents <= 1.0).all():
51 | print('Warning: The input may not be in the right range.')
52 | with warnings.catch_warnings(): # Empty seeds become NaN.
53 | warnings.simplefilter('ignore', category=RuntimeWarning)
54 | scores = np.exp(np.nanmean(np.log(1 + percents), -1)) - 1
55 | return scores
56 |
57 |
58 | def binning(xs, ys, borders, reducer=np.nanmean, fill='nan'):
59 | xs, ys = np.array(xs), np.array(ys)
60 | order = np.argsort(xs)
61 | xs, ys = xs[order], ys[order]
62 | binned = []
63 | with warnings.catch_warnings(): # Empty buckets become NaN.
64 | warnings.simplefilter('ignore', category=RuntimeWarning)
65 | for start, stop in zip(borders[:-1], borders[1:]):
66 | left = (xs <= start).sum()
67 | right = (xs <= stop).sum()
68 | if left < right:
69 | value = reducer(ys[left:right])
70 | elif binned:
71 | value = {'nan': np.nan, 'last': binned[-1]}[fill]
72 | else:
73 | value = np.nan
74 | binned.append(value)
75 | return borders[1:], np.array(binned)
76 |
--------------------------------------------------------------------------------
/analysis/plot_counts.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import numpy as np
4 | import matplotlib.pyplot as plt
5 | import matplotlib.ticker as ticker
6 |
7 | import common
8 |
9 |
10 | def plot_counts(
11 | inpath, outpath, color, budget=1e6, cols=4, size=(2, 1.8)):
12 | runs = common.load_runs([inpath], budget)
13 | percents, methods, seeds, tasks = common.compute_success_rates(runs, budget)
14 | borders = np.arange(0, budget, 1e4)
15 | keys = ['reward', 'length'] + tasks
16 |
17 | rows = len(keys) // cols
18 | fig, axes = plt.subplots(
19 | nrows=rows, ncols=cols, figsize=(size[0] * cols, size[1] * rows))
20 |
21 | for ax, key in zip(axes.flatten(), keys):
22 | ax.set_title(key.replace('achievement_', '').replace('_', ' ').title())
23 | xs = np.concatenate([run['xs'] for run in runs])
24 | ys = np.concatenate([run[key] for run in runs])
25 |
26 | binxs, binys = common.binning(xs, ys, borders, np.nanmean)
27 | ax.plot(binxs, binys, color=color)
28 |
29 | mins = common.binning(xs, ys, borders, np.nanmin)[1]
30 | maxs = common.binning(xs, ys, borders, np.nanmax)[1]
31 | ax.fill_between(binxs, mins, maxs, linewidths=0, alpha=0.2, color=color)
32 |
33 | ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0))
34 | ax.xaxis.set_major_locator(ticker.MaxNLocator(4, steps=[1, 2, 2.5, 5, 10]))
35 | ax.yaxis.set_major_locator(ticker.MaxNLocator(5, steps=[1, 2, 2.5, 5, 10]))
36 |
37 | if maxs.max() == 0:
38 | ax.set_ylim(-0.05, 1.05)
39 |
40 | fig.tight_layout()
41 | pathlib.Path(outpath).parent.mkdir(exist_ok=True, parents=True)
42 | fig.savefig(outpath)
43 | print(f'Saved {outpath}')
44 |
45 |
46 | plot_counts(
47 | 'scores/crafter_reward-dreamerv2.json',
48 | 'plots/counts-dreamerv2.pdf', '#377eb8')
49 |
50 | plot_counts(
51 | 'scores/crafter_reward-ppo.json',
52 | 'plots/counts-ppo.pdf', '#5fc35d')
53 |
54 | plot_counts(
55 | 'scores/crafter_reward-rainbow.json',
56 | 'plots/counts-rainbow.pdf', '#984ea3')
57 |
58 | plot_counts(
59 | 'scores/crafter_noreward-unsup_plan2explore.json',
60 | 'plots/counts-unsup_plan2explore.pdf', '#bf3217')
61 |
62 | plot_counts(
63 | 'scores/crafter_noreward-unsup_rnd.json',
64 | 'plots/counts-unsup_rnd.pdf', '#de9f42')
65 |
66 | plot_counts(
67 | 'scores/crafter_noreward-random.json',
68 | 'plots/counts-random.pdf', '#6a554d')
69 |
--------------------------------------------------------------------------------
/analysis/plot_reward.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import numpy as np
4 | import matplotlib.pyplot as plt
5 | import matplotlib.ticker as ticker
6 |
7 | import common
8 |
9 |
10 | def plot_reward(inpaths, outpath, legend, colors, cols=4, budget=1e6):
11 | runs = common.load_runs(inpaths, budget)
12 | percents, methods, seeds, tasks = common.compute_success_rates(runs, budget)
13 | if not legend:
14 | methods = sorted(set(run['method'] for run in runs))
15 | legend = {x: x.replace('_', ' ').title() for x in methods}
16 | borders = np.arange(0, budget, 1e4)
17 |
18 | fig, ax = plt.subplots(figsize=(4.5, 2.3))
19 | for j, (method, label) in enumerate(legend.items()):
20 | relevant = [run for run in runs if run['method'] == method]
21 | if not relevant:
22 | print(f'No runs found for method {method}.')
23 | # Average within each time bucket.
24 | binned_xs, binned_ys = [], []
25 | for run in relevant:
26 | xs, ys = common.binning(run['xs'], run['reward'], borders, np.nanmean)
27 | binned_xs.append(xs)
28 | binned_ys.append(ys)
29 | xs = np.concatenate(binned_xs)
30 | ys = np.concatenate(binned_ys)
31 | # Compute mean and stddev over seeds.
32 | means = common.binning(xs, ys, borders, np.nanmean)[1]
33 | stds = common.binning(xs, ys, borders, np.nanstd)[1]
34 | # Plot line and shaded area.
35 | kwargs = dict(alpha=0.2, linewidths=0, color=colors[j], zorder=10 - j)
36 | ax.fill_between(borders[1:], means - stds, means + stds, **kwargs)
37 | ax.plot(borders[1:], means, label=label, color=colors[j], zorder=100 - j)
38 |
39 | ax.axhline(y=22, c='#888888', ls='--', lw=1)
40 | ax.text(6.2e5, 18, 'Optimal', c='#888888')
41 |
42 | ax.set_title('Crafter Reward')
43 | ax.set_xlim(0, budget)
44 | ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0))
45 | ax.grid(alpha=0.3)
46 | ax.xaxis.set_major_locator(ticker.MaxNLocator(5, steps=[1, 2, 2.5, 5, 10]))
47 | ax.yaxis.set_major_locator(ticker.MaxNLocator(6, steps=[1, 2, 2.5, 5, 10]))
48 |
49 | fig.tight_layout(rect=(0, 0, 0.55, 1))
50 | fig.legend(bbox_to_anchor=(0.52, 0.54), loc='center left', frameon=False)
51 |
52 | pathlib.Path(outpath).parent.mkdir(exist_ok=True, parents=True)
53 | fig.savefig(outpath)
54 | print(f'Saved {outpath}')
55 |
56 |
57 | inpaths = [
58 | 'scores/crafter_reward-dreamerv2.json',
59 | 'scores/crafter_reward-rainbow.json',
60 | 'scores/crafter_reward-ppo.json',
61 | 'scores/crafter_noreward-random.json',
62 | ]
63 | legend = {
64 | 'dreamerv2': 'DreamerV2',
65 | 'rainbow': 'Rainbow',
66 | 'ppo': 'PPO',
67 | 'random': 'Random',
68 | }
69 | colors = ['#377eb8', '#4daf4a', '#984ea3', '#6a554d']
70 | plot_reward(inpaths, 'plots/reward.pdf', legend, colors)
71 |
--------------------------------------------------------------------------------
/analysis/plot_scores.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import numpy as np
4 | import matplotlib.pyplot as plt
5 |
6 | import common
7 |
8 |
9 | def plot_scores(inpaths, outpath, legend, colors, budget=1e6, ylim=None):
10 | runs = common.load_runs(inpaths, budget)
11 | percents, methods, seeds, tasks = common.compute_success_rates(runs, budget)
12 | scores = common.compute_scores(percents)
13 | if not legend:
14 | methods = sorted(set(run['method'] for run in runs))
15 | legend = {x: x.replace('_', ' ').title() for x in methods}
16 | legend = dict(reversed(legend.items()))
17 |
18 | scores = scores[np.array([methods.index(m) for m in legend.keys()])]
19 | mean = np.nanmean(scores, -1)
20 | std = np.nanstd(scores, -1)
21 |
22 | fig, ax = plt.subplots(figsize=(4, 3))
23 | centers = np.arange(len(legend))
24 | width = 0.7
25 | colors = list(reversed(colors[:len(legend)]))
26 | error_kw = dict(capsize=5, c='#000')
27 | ax.bar(centers, mean, yerr=std, color=colors, error_kw=error_kw)
28 |
29 | ax.spines['top'].set_visible(False)
30 | ax.spines['right'].set_visible(False)
31 | ax.spines['bottom'].set_visible(False)
32 | ax.tick_params(
33 | axis='x', which='both', width=50, length=0.8, direction='inout')
34 | ax.set_xlim(centers[0] - 2 * (1 - width), centers[-1] + 2 * (1 - width))
35 | ax.set_xticks(centers + 0.0)
36 | ax.set_xticklabels(
37 | list(legend.values()), rotation=45, ha='right', rotation_mode='anchor')
38 |
39 | ax.set_ylabel('Crafter Score (%)')
40 | if ylim:
41 | ax.set_ylim(0, ylim)
42 |
43 | fig.tight_layout()
44 | pathlib.Path(outpath).parent.mkdir(exist_ok=True, parents=True)
45 | fig.savefig(outpath)
46 | print(f'Saved {outpath}')
47 |
48 |
49 | inpaths = [
50 | 'scores/crafter_reward-human.json',
51 | 'scores/crafter_reward-dreamerv2.json',
52 | 'scores/crafter_reward-ppo.json',
53 | 'scores/crafter_reward-rainbow.json',
54 | 'scores/crafter_noreward-unsup_plan2explore.json',
55 | 'scores/crafter_noreward-unsup_rnd.json',
56 | 'scores/crafter_noreward-random.json',
57 | ]
58 | legend = {
59 | 'human': 'Human Experts',
60 | 'dreamerv2': 'DreamerV2',
61 | 'ppo': 'PPO',
62 | 'rainbow': 'Rainbow',
63 | 'unsup_plan2explore': 'Plan2Explore\n(Unsup)',
64 | 'unsup_rnd': 'RND\n(Unsup)',
65 | 'random': 'Random',
66 | }
67 | colors = [
68 | '#cccccc',
69 | '#377eb8', '#5fc35d', '#984ea3',
70 | '#bf3217', '#de9f42', '#6a554d',
71 | ]
72 | plot_scores(inpaths, 'plots/scores-human.pdf', legend, colors, ylim=100)
73 |
74 | inpaths = [
75 | 'scores/crafter_reward-dreamerv2.json',
76 | 'scores/crafter_reward-ppo.json',
77 | 'scores/crafter_reward-rainbow.json',
78 | 'scores/crafter_noreward-unsup_plan2explore.json',
79 | 'scores/crafter_noreward-unsup_rnd.json',
80 | 'scores/crafter_noreward-random.json',
81 | ]
82 | legend = {
83 | 'dreamerv2': 'DreamerV2',
84 | 'ppo': 'PPO',
85 | 'rainbow': 'Rainbow',
86 | 'unsup_plan2explore': 'Plan2Explore\n(Unsup)',
87 | 'unsup_rnd': 'RND\n(Unsup)',
88 | 'random': 'Random',
89 | }
90 | colors = [
91 | '#377eb8', '#5fc35d', '#984ea3',
92 | '#bf3217', '#de9f42', '#6a554d',
93 | ]
94 | plot_scores(inpaths, 'plots/scores-agents.pdf', legend, colors, ylim=12)
95 |
--------------------------------------------------------------------------------
/analysis/plot_spectrum.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import numpy as np
4 | import matplotlib.pyplot as plt
5 |
6 | import common
7 |
8 |
9 | def plot_spectrum(inpaths, outpath, legend, colors, budget=1e6, sort=False):
10 | runs = common.load_runs(inpaths, budget)
11 | percents, methods, seeds, tasks = common.compute_success_rates(
12 | runs, budget, sortby=sort and legend and list(legend.keys())[0])
13 | if not legend:
14 | methods = sorted(set(run['method'] for run in runs))
15 | legend = {x: x.replace('_', ' ').title() for x in methods}
16 |
17 | fig, ax = plt.subplots(figsize=(7, 3))
18 | centers = np.arange(len(tasks))
19 | width = 0.7
20 | for index, (method, label) in enumerate(legend.items()):
21 | heights = np.nanmean(percents[methods.index(method)], 0)
22 | pos = centers + width * (0.5 / len(methods) + index / len(methods) - 0.5)
23 | color = colors[index]
24 | ax.bar(pos, heights, width / len(methods), label=label, color=color)
25 |
26 | names = [x[len('achievement_'):].replace('_', ' ').title() for x in tasks]
27 | ax.spines['top'].set_visible(False)
28 | ax.spines['right'].set_visible(False)
29 | ax.spines['bottom'].set_visible(False)
30 | ax.tick_params(
31 | axis='x', which='both', width=14, length=0.8, direction='inout')
32 | ax.set_xlim(centers[0] - 2 * (1 - width), centers[-1] + 2 * (1 - width))
33 | ax.set_xticks(centers + 0.0)
34 | ax.set_xticklabels(names, rotation=45, ha='right', rotation_mode='anchor')
35 |
36 | ax.set_ylabel('Success Rate (%)')
37 | ax.set_yscale('log')
38 | ax.set_ylim(0.01, 100)
39 | ax.set_yticks([0.01, 0.1, 1, 10, 100])
40 | ax.set_yticklabels('0.01 0.1 1 10 100'.split())
41 |
42 | fig.tight_layout(rect=(0, 0, 1, 0.95))
43 | fig.legend(
44 | loc='upper center', ncol=10, frameon=False, borderpad=0, borderaxespad=0)
45 |
46 | pathlib.Path(outpath).parent.mkdir(exist_ok=True, parents=True)
47 | fig.savefig(outpath)
48 | print(f'Saved {outpath}')
49 |
50 |
51 | inpaths = [
52 | 'scores/crafter_reward-dreamerv2.json',
53 | 'scores/crafter_reward-ppo.json',
54 | 'scores/crafter_reward-rainbow.json',
55 | ]
56 | legend = {
57 | 'dreamerv2': 'DreamerV2',
58 | 'ppo': 'PPO',
59 | 'rainbow': 'Rainbow',
60 | }
61 | colors = ['#377eb8', '#5fc35d', '#984ea3']
62 | plot_spectrum(inpaths, 'plots/spectrum-reward.pdf', legend, colors)
63 |
64 | inpaths = [
65 | 'scores/crafter_noreward-unsup_plan2explore.json',
66 | 'scores/crafter_noreward-unsup_rnd.json',
67 | 'scores/crafter_noreward-random.json',
68 | ]
69 | legend = {
70 | 'unsup_plan2explore': 'Plan2Explore',
71 | 'unsup_rnd': 'RND',
72 | 'random': 'Random',
73 | }
74 | colors = ['#bf3217', '#de9f42', '#6a554d']
75 | plot_spectrum(inpaths, 'plots/spectrum-noreward.pdf', legend, colors)
76 |
--------------------------------------------------------------------------------
/analysis/print_reward.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import common
4 |
5 |
6 | def print_reward(inpaths, legend, budget=1e6, last=1e5, sort=False):
7 | runs = common.load_runs(inpaths, budget)
8 | if not legend:
9 | methods = sorted(set(run['method'] for run in runs))
10 | legend = {x: x.replace('_', ' ').title() for x in methods}
11 | seeds = sorted({x['seed'] for x in runs})
12 | rewards = np.empty((len(legend), len(seeds)))
13 | rewards[:] = np.nan
14 | for i, (method, label) in enumerate(legend.items()):
15 | relevant = [run for run in runs if run['method'] == method]
16 | if not relevant:
17 | print(f'No runs found for method {method}.')
18 | for run in relevant:
19 | j = seeds.index(run['seed'])
20 | xs = np.array(run['xs'])
21 | ys = np.array(run['reward'])
22 | rewards[i][j] = ys[-(xs >= (xs.max() - last)).sum()]
23 | means = np.nanmean(rewards, -1)
24 | stds = np.nanstd(rewards, -1)
25 | print('')
26 | print(r'\textbf{Method} & \textbf{Reward} \\')
27 | print('')
28 | for method, mean, std in zip(legend.values(), means, stds):
29 | mean = f'{mean:.1f}'
30 | mean = (r'\o' if len(mean) < 4 else ' ') + mean
31 | print(rf'{method:<25} & ${mean} \pm {std:4.1f}$ \\')
32 | print('')
33 |
34 |
35 | inpaths = [
36 | 'scores/crafter_reward-dreamerv2.json',
37 | 'scores/crafter_reward-ppo.json',
38 | 'scores/crafter_reward-rainbow.json',
39 | 'scores/crafter_noreward-unsup_plan2explore.json',
40 | 'scores/crafter_noreward-unsup_rnd.json',
41 | 'scores/crafter_noreward-random.json',
42 | 'scores/crafter_reward-human.json',
43 | ]
44 | legend = {
45 | 'human': 'Human Experts',
46 | 'dreamerv2': 'DreamerV2',
47 | 'ppo': 'PPO',
48 | 'rainbow': 'Rainbow',
49 | 'unsup_plan2explore': 'Plan2Explore (Unsup)',
50 | 'unsup_rnd': 'RND (Unsup)',
51 | 'random': 'Random',
52 | }
53 | print_reward(inpaths, legend)
54 |
--------------------------------------------------------------------------------
/analysis/print_scores.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import common
4 |
5 |
6 | def print_scores(inpaths, legend, budget=1e6, sort=False):
7 | runs = common.load_runs(inpaths, budget)
8 | percents, methods, seeds, tasks = common.compute_success_rates(runs, budget)
9 | scores = common.compute_scores(percents)
10 | if not legend:
11 | methods = sorted(set(run['method'] for run in runs))
12 | legend = {x: x.replace('_', ' ').title() for x in methods}
13 |
14 | scores = scores[np.array([methods.index(m) for m in legend.keys()])]
15 | means = np.nanmean(scores, -1)
16 | stds = np.nanstd(scores, -1)
17 |
18 | print('')
19 | print(r'\textbf{Method} & \textbf{Score} \\')
20 | print('')
21 | for method, mean, std in zip(legend.values(), means, stds):
22 | mean = f'{mean:.1f}'
23 | mean = (r'\o' if len(mean) < 4 else ' ') + mean
24 | print(rf'{method:<25} & ${mean} \pm {std:4.1f}\%$ \\')
25 | print('')
26 |
27 |
28 | inpaths = [
29 | 'scores/crafter_reward-dreamerv2.json',
30 | 'scores/crafter_reward-ppo.json',
31 | 'scores/crafter_reward-rainbow.json',
32 | 'scores/crafter_noreward-unsup_plan2explore.json',
33 | 'scores/crafter_noreward-unsup_rnd.json',
34 | 'scores/crafter_noreward-random.json',
35 | 'scores/crafter_reward-human.json',
36 | ]
37 | legend = {
38 | 'human': 'Human Experts',
39 | 'dreamerv2': 'DreamerV2',
40 | 'ppo': 'PPO',
41 | 'rainbow': 'Rainbow',
42 | 'unsup_plan2explore': 'Plan2Explore (Unsup)',
43 | 'unsup_rnd': 'RND (Unsup)',
44 | 'random': 'Random',
45 | }
46 | print_scores(inpaths, legend)
47 |
--------------------------------------------------------------------------------
/analysis/print_spectrum.py:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | import numpy as np
4 |
5 | import common
6 |
7 |
8 | def print_spectrum(inpaths, legend, budget=1e6, sort=False):
9 | runs = common.load_runs(inpaths, budget)
10 | percents, methods, seeds, tasks = common.compute_success_rates(runs, budget)
11 | scores = common.compute_scores(percents)
12 | if not legend:
13 | methods = sorted(set(run['method'] for run in runs))
14 | legend = {x: x.replace('_', ' ').title() for x in methods}
15 |
16 | scores = np.nanmean(scores, 1)
17 | percents = np.nanmean(percents, 1)
18 |
19 | if sort:
20 | first = next(iter(legend.keys()))
21 | tasks = sorted(tasks, key=lambda task: -np.nanmean(percents[first, task]))
22 | legend = dict(reversed(legend.items()))
23 |
24 | cols = ''.join(rf' & \textbf{{{k}}}' for k in legend.values())
25 | print(r'\newcommand{\o}{\hphantom{0}}')
26 | print(r'\newcommand{\b}[1]{\textbf{#1}}')
27 | print('')
28 | print(f'{"Achievement":<20}' + cols + r' \\')
29 | print('')
30 | wins = collections.defaultdict(int)
31 | for task in tasks:
32 | k = tasks.index(task)
33 | if task.startswith('achievement_'):
34 | name = task[len('achievement_'):].replace('_', ' ').title()
35 | else:
36 | name = task.replace('_', ' ').title()
37 | print(f'{name:<20}', end='')
38 | best = max(percents[methods.index(m), k] for m in legend.keys())
39 | for method in legend.keys():
40 | i = methods.index(method)
41 | value = percents[i][k]
42 | winner = value >= 0.95 * best and value > 0
43 | fmt = rf'{value:.1f}\%'
44 | fmt = (r'\o' if len(fmt) < 6 else ' ') + fmt
45 | fmt = rf'\b{{{fmt}}}' if winner else f' {fmt} '
46 | if winner:
47 | wins[method] += 1
48 | print(rf' & ${fmt}$', end='')
49 | print(r' \\')
50 | print('')
51 |
52 | print(f'{"Score":<20}', end='')
53 | best = max(scores[methods.index(m)] for m in legend.keys())
54 | for method in legend.keys():
55 | value = scores[methods.index(method)]
56 | bold = value >= 0.95 * best and value > 0
57 | fmt = rf'{value:.1f}\%'
58 | fmt = (r'\o' if len(fmt) < 6 else ' ') + fmt
59 | fmt = rf'\b{{{fmt}}}' if bold else f' {fmt} '
60 | print(rf' & ${fmt}$', end='')
61 | print(r' \\')
62 |
63 |
64 | inpaths = [
65 | 'scores/crafter_reward-human.json',
66 | ]
67 | legend = {
68 | 'human': 'Human Experts',
69 | }
70 | print_spectrum(inpaths, legend)
71 |
72 | inpaths = [
73 | 'scores/crafter_reward-dreamerv2.json',
74 | 'scores/crafter_reward-ppo.json',
75 | 'scores/crafter_reward-rainbow.json',
76 | ]
77 | legend = {
78 | 'dreamerv2': 'DreamerV2',
79 | 'ppo': 'PPO',
80 | 'rainbow': 'Rainbow',
81 | }
82 | print_spectrum(inpaths, legend)
83 |
84 |
85 | inpaths = [
86 | 'scores/crafter_noreward-unsup_plan2explore.json',
87 | 'scores/crafter_noreward-unsup_rnd.json',
88 | 'scores/crafter_noreward-random.json',
89 | ]
90 | legend = {
91 | 'unsup_plan2explore': 'Plan2Explore',
92 | 'unsup_rnd': 'RND',
93 | 'random': 'Random',
94 | }
95 | print_spectrum(inpaths, legend)
96 |
--------------------------------------------------------------------------------
/analysis/read_metrics.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import json
3 | import pathlib
4 |
5 | import numpy as np
6 |
7 | import common
8 |
9 |
10 | def read_stats(indir, outdir, task, method, budget=int(1e6), verbose=False):
11 | indir = pathlib.Path(indir)
12 | outdir = pathlib.Path(outdir)
13 | runs = []
14 | print(f'Loading {indir.name}...')
15 | filenames = sorted(list(indir.glob('**/stats.jsonl')))
16 | for index, filename in enumerate(filenames):
17 | if not filename.is_file():
18 | continue
19 | rewards, lengths, achievements = load_stats(filename, budget)
20 | if sum(lengths) < budget - 1e4:
21 | message = f'Skipping incomplete run ({sum(lengths)} < {budget} steps): '
22 | message += f'{filename.relative_to(indir.parent)}'
23 | print(f'==> {message}')
24 | continue
25 | runs.append(dict(
26 | task=task,
27 | method=method,
28 | seed=str(index),
29 | xs=np.cumsum(lengths).tolist(),
30 | reward=rewards,
31 | length=lengths,
32 | **achievements,
33 | ))
34 | if not runs:
35 | print('No completed runs.\n')
36 | return
37 | print_summary(runs, budget, verbose)
38 | outdir.mkdir(exist_ok=True, parents=True)
39 | filename = (outdir / f'{task}-{method}.json')
40 | filename.write_text(json.dumps(runs))
41 | print('Wrote', filename)
42 | print('')
43 |
44 |
45 | def load_stats(filename, budget):
46 | steps = 0
47 | rewards = []
48 | lengths = []
49 | achievements = collections.defaultdict(list)
50 | for line in filename.read_text().split('\n'):
51 | if not line.strip():
52 | continue
53 | episode = json.loads(line)
54 | steps += episode['length']
55 | if steps > budget:
56 | break
57 | lengths.append(episode['length'])
58 | for key, value in episode.items():
59 | if key.startswith('achievement_'):
60 | achievements[key].append(value)
61 | unlocks = int(np.sum([(v[-1] >= 1) for v in achievements.values()]))
62 | health = -0.9
63 | rewards.append(unlocks + health)
64 | return rewards, lengths, achievements
65 |
66 |
67 | def print_summary(runs, budget, verbose):
68 | episodes = np.array([len(x['length']) for x in runs])
69 | rewards = np.array([np.mean(x['reward']) for x in runs])
70 | lengths = np.array([np.mean(x['length']) for x in runs])
71 | percents, methods, seeds, tasks = common.compute_success_rates(
72 | runs, budget, sortby=0)
73 | scores = np.squeeze(common.compute_scores(percents))
74 | print(f'Score: {np.mean(scores):10.2f} ± {np.std(scores):.2f}')
75 | print(f'Reward: {np.mean(rewards):10.2f} ± {np.std(rewards):.2f}')
76 | print(f'Length: {np.mean(lengths):10.2f} ± {np.std(lengths):.2f}')
77 | print(f'Episodes: {np.mean(episodes):10.2f} ± {np.std(episodes):.2f}')
78 | if verbose:
79 | for task, percent in sorted(tasks, np.squeeze(percents).T):
80 | name = task[len('achievement_'):].replace('_', ' ').title()
81 | print(f'{name:<20} {np.mean(percent):6.2f}%')
82 |
83 |
84 | read_stats(
85 | 'logdir/crafter_reward-dreamerv2',
86 | 'scores', 'crafter_reward', 'dreamerv2')
87 |
88 | read_stats(
89 | 'logdir/crafter_reward-ppo',
90 | 'scores', 'crafter_reward', 'ppo')
91 |
92 | read_stats(
93 | 'logdir/crafter_reward-rainbow',
94 | 'scores', 'crafter_reward', 'rainbow')
95 |
96 | read_stats(
97 | 'logdir/crafter_noreward-unsup_plan2explore',
98 | 'scores', 'crafter_noreward', 'unsup_plan2explore')
99 |
100 | read_stats(
101 | 'logdir/crafter_noreward-unsup_rnd',
102 | 'scores', 'crafter_noreward', 'unsup_rnd')
103 |
104 | read_stats(
105 | 'logdir/crafter_noreward-random',
106 | 'scores', 'crafter_noreward', 'random')
107 |
--------------------------------------------------------------------------------
/crafter/__init__.py:
--------------------------------------------------------------------------------
1 | from .env import Env
2 | from .recorder import Recorder
3 |
4 | try:
5 | import gym
6 | gym.register(
7 | id='CrafterReward-v1',
8 | entry_point='crafter:Env',
9 | max_episode_steps=10000,
10 | kwargs={'reward': True})
11 | gym.register(
12 | id='CrafterNoReward-v1',
13 | entry_point='crafter:Env',
14 | max_episode_steps=10000,
15 | kwargs={'reward': False})
16 | except ImportError:
17 | pass
18 |
--------------------------------------------------------------------------------
/crafter/assets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/1.png
--------------------------------------------------------------------------------
/crafter/assets/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/2.png
--------------------------------------------------------------------------------
/crafter/assets/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/3.png
--------------------------------------------------------------------------------
/crafter/assets/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/4.png
--------------------------------------------------------------------------------
/crafter/assets/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/5.png
--------------------------------------------------------------------------------
/crafter/assets/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/6.png
--------------------------------------------------------------------------------
/crafter/assets/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/7.png
--------------------------------------------------------------------------------
/crafter/assets/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/8.png
--------------------------------------------------------------------------------
/crafter/assets/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/9.png
--------------------------------------------------------------------------------
/crafter/assets/arrow-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/arrow-down.png
--------------------------------------------------------------------------------
/crafter/assets/arrow-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/arrow-left.png
--------------------------------------------------------------------------------
/crafter/assets/arrow-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/arrow-right.png
--------------------------------------------------------------------------------
/crafter/assets/arrow-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/arrow-up.png
--------------------------------------------------------------------------------
/crafter/assets/coal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/coal.png
--------------------------------------------------------------------------------
/crafter/assets/cow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/cow.png
--------------------------------------------------------------------------------
/crafter/assets/debug-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/debug-2.png
--------------------------------------------------------------------------------
/crafter/assets/debug-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/debug-3.png
--------------------------------------------------------------------------------
/crafter/assets/debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/debug.png
--------------------------------------------------------------------------------
/crafter/assets/diamond.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/diamond.png
--------------------------------------------------------------------------------
/crafter/assets/drink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/drink.png
--------------------------------------------------------------------------------
/crafter/assets/energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/energy.png
--------------------------------------------------------------------------------
/crafter/assets/fence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/fence.png
--------------------------------------------------------------------------------
/crafter/assets/food.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/food.png
--------------------------------------------------------------------------------
/crafter/assets/furnace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/furnace.png
--------------------------------------------------------------------------------
/crafter/assets/grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/grass.png
--------------------------------------------------------------------------------
/crafter/assets/health.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/health.png
--------------------------------------------------------------------------------
/crafter/assets/iron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/iron.png
--------------------------------------------------------------------------------
/crafter/assets/iron_pickaxe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/iron_pickaxe.png
--------------------------------------------------------------------------------
/crafter/assets/iron_sword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/iron_sword.png
--------------------------------------------------------------------------------
/crafter/assets/lava.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/lava.png
--------------------------------------------------------------------------------
/crafter/assets/leaves.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/leaves.png
--------------------------------------------------------------------------------
/crafter/assets/log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/log.png
--------------------------------------------------------------------------------
/crafter/assets/path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/path.png
--------------------------------------------------------------------------------
/crafter/assets/plant-ripe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/plant-ripe.png
--------------------------------------------------------------------------------
/crafter/assets/plant-young.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/plant-young.png
--------------------------------------------------------------------------------
/crafter/assets/plant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/plant.png
--------------------------------------------------------------------------------
/crafter/assets/player-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player-down.png
--------------------------------------------------------------------------------
/crafter/assets/player-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player-left.png
--------------------------------------------------------------------------------
/crafter/assets/player-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player-right.png
--------------------------------------------------------------------------------
/crafter/assets/player-sleep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player-sleep.png
--------------------------------------------------------------------------------
/crafter/assets/player-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player-up.png
--------------------------------------------------------------------------------
/crafter/assets/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/player.png
--------------------------------------------------------------------------------
/crafter/assets/sand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/sand.png
--------------------------------------------------------------------------------
/crafter/assets/sapling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/sapling.png
--------------------------------------------------------------------------------
/crafter/assets/skeleton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/skeleton.png
--------------------------------------------------------------------------------
/crafter/assets/stone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/stone.png
--------------------------------------------------------------------------------
/crafter/assets/stone_pickaxe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/stone_pickaxe.png
--------------------------------------------------------------------------------
/crafter/assets/stone_sword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/stone_sword.png
--------------------------------------------------------------------------------
/crafter/assets/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/table.png
--------------------------------------------------------------------------------
/crafter/assets/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/tree.png
--------------------------------------------------------------------------------
/crafter/assets/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/unknown.png
--------------------------------------------------------------------------------
/crafter/assets/water.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/water.png
--------------------------------------------------------------------------------
/crafter/assets/wood.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/wood.png
--------------------------------------------------------------------------------
/crafter/assets/wood_pickaxe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/wood_pickaxe.png
--------------------------------------------------------------------------------
/crafter/assets/wood_sword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/wood_sword.png
--------------------------------------------------------------------------------
/crafter/assets/zombie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/crafter/assets/zombie.png
--------------------------------------------------------------------------------
/crafter/constants.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 |
3 | import ruamel.yaml
4 |
5 | root = pathlib.Path(__file__).parent
6 | yaml = ruamel.yaml.YAML(typ='safe', pure=True)
7 | for key, value in yaml.load((root / 'data.yaml').read_text()).items():
8 | globals()[key] = value
9 |
--------------------------------------------------------------------------------
/crafter/data.yaml:
--------------------------------------------------------------------------------
1 | actions:
2 | - noop
3 | - move_left
4 | - move_right
5 | - move_up
6 | - move_down
7 | - do
8 | - sleep
9 | - place_stone
10 | - place_table
11 | - place_furnace
12 | - place_plant
13 | - make_wood_pickaxe
14 | - make_stone_pickaxe
15 | - make_iron_pickaxe
16 | - make_wood_sword
17 | - make_stone_sword
18 | - make_iron_sword
19 |
20 | materials:
21 | - water
22 | - grass
23 | - stone
24 | - path
25 | - sand
26 | - tree
27 | - lava
28 | - coal
29 | - iron
30 | - diamond
31 | - table
32 | - furnace
33 |
34 | walkable:
35 | - grass
36 | - path
37 | - sand
38 |
39 | items:
40 | health: {max: 9, initial: 9}
41 | food: {max: 9, initial: 9}
42 | drink: {max: 9, initial: 9}
43 | energy: {max: 9, initial: 9}
44 | sapling: {max: 9, initial: 0}
45 | wood: {max: 9, initial: 0}
46 | stone: {max: 9, initial: 0}
47 | coal: {max: 9, initial: 0}
48 | iron: {max: 9, initial: 0}
49 | diamond: {max: 9, initial: 0}
50 | wood_pickaxe: {max: 9, initial: 0}
51 | stone_pickaxe: {max: 9, initial: 0}
52 | iron_pickaxe: {max: 9, initial: 0}
53 | wood_sword: {max: 9, initial: 0}
54 | stone_sword: {max: 9, initial: 0}
55 | iron_sword: {max: 9, initial: 0}
56 |
57 | collect:
58 | tree: {require: {}, receive: {wood: 1}, leaves: grass}
59 | stone: {require: {wood_pickaxe: 1}, receive: {stone: 1}, leaves: path}
60 | coal: {require: {wood_pickaxe: 1}, receive: {coal: 1}, leaves: path}
61 | iron: {require: {stone_pickaxe: 1}, receive: {iron: 1}, leaves: path}
62 | diamond: {require: {iron_pickaxe: 1}, receive: {diamond: 1}, leaves: path}
63 | water: {require: {}, receive: {drink: 1}, leaves: water}
64 | grass: {require: {}, receive: {sapling: 1}, probability: 0.1, leaves: grass}
65 |
66 | place:
67 | stone: {uses: {stone: 1}, where: [grass, sand, path, water, lava], type: material}
68 | table: {uses: {wood: 2}, where: [grass, sand, path], type: material}
69 | furnace: {uses: {stone: 4}, where: [grass, sand, path], type: material}
70 | plant: {uses: {sapling: 1}, where: [grass], type: object}
71 |
72 | make:
73 | wood_pickaxe: {uses: {wood: 1}, nearby: [table], gives: 1}
74 | stone_pickaxe: {uses: {wood: 1, stone: 1}, nearby: [table], gives: 1}
75 | iron_pickaxe: {uses: {wood: 1, coal: 1, iron: 1}, nearby: [table, furnace], gives: 1}
76 | wood_sword: {uses: {wood: 1}, nearby: [table], gives: 1}
77 | stone_sword: {uses: {wood: 1, stone: 1}, nearby: [table], gives: 1}
78 | iron_sword: {uses: {wood: 1, coal: 1, iron: 1}, nearby: [table, furnace], gives: 1}
79 |
80 | achievements:
81 | - collect_coal
82 | - collect_diamond
83 | - collect_drink
84 | - collect_iron
85 | - collect_sapling
86 | - collect_stone
87 | - collect_wood
88 | - defeat_skeleton
89 | - defeat_zombie
90 | - eat_cow
91 | - eat_plant
92 | - make_iron_pickaxe
93 | - make_iron_sword
94 | - make_stone_pickaxe
95 | - make_stone_sword
96 | - make_wood_pickaxe
97 | - make_wood_sword
98 | - place_furnace
99 | - place_plant
100 | - place_stone
101 | - place_table
102 | - wake_up
103 |
--------------------------------------------------------------------------------
/crafter/engine.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import functools
3 | import pathlib
4 |
5 | import imageio.v3 as imageio
6 | import numpy as np
7 | from PIL import Image, ImageEnhance
8 |
9 |
10 | class AttrDict(dict):
11 |
12 | __getattr__ = dict.__getitem__
13 |
14 |
15 | class staticproperty:
16 |
17 | def __init__(self, function):
18 | self.function = function
19 |
20 | def __get__(self, instance, owner=None):
21 | return self.function()
22 |
23 |
24 | class World:
25 |
26 | def __init__(self, area, materials, chunk_size):
27 | self.area = area
28 | self._chunk_size = chunk_size
29 | self._mat_names = {i: x for i, x in enumerate([None] + materials)}
30 | self._mat_ids = {x: i for i, x in enumerate([None] + materials)}
31 | self.reset()
32 |
33 | def reset(self, seed=None):
34 | self.random = np.random.RandomState(seed)
35 | self.daylight = 0.0
36 | self._chunks = collections.defaultdict(set)
37 | self._objects = [None]
38 | self._mat_map = np.zeros(self.area, np.uint8)
39 | self._obj_map = np.zeros(self.area, np.uint32)
40 |
41 | @property
42 | def objects(self):
43 | # Return a new list so the objects cannot change while being iterated over.
44 | return [obj for obj in self._objects if obj]
45 |
46 | @property
47 | def chunks(self):
48 | return self._chunks.copy()
49 |
50 | def add(self, obj):
51 | assert hasattr(obj, 'pos')
52 | obj.pos = np.array(obj.pos)
53 | assert self._obj_map[tuple(obj.pos)] == 0
54 | index = len(self._objects)
55 | self._objects.append(obj)
56 | self._obj_map[tuple(obj.pos)] = index
57 | self._chunks[self.chunk_key(obj.pos)].add(obj)
58 |
59 | def remove(self, obj):
60 | if obj.removed:
61 | return
62 | self._objects[self._obj_map[tuple(obj.pos)]] = None
63 | self._obj_map[tuple(obj.pos)] = 0
64 | self._chunks[self.chunk_key(obj.pos)].remove(obj)
65 | obj.removed = True
66 |
67 | def move(self, obj, pos):
68 | if obj.removed:
69 | return
70 | pos = np.array(pos)
71 | assert self._obj_map[tuple(pos)] == 0
72 | index = self._obj_map[tuple(obj.pos)]
73 | self._obj_map[tuple(pos)] = index
74 | self._obj_map[tuple(obj.pos)] = 0
75 | old_chunk = self.chunk_key(obj.pos)
76 | new_chunk = self.chunk_key(pos)
77 | if old_chunk != new_chunk:
78 | self._chunks[old_chunk].remove(obj)
79 | self._chunks[new_chunk].add(obj)
80 | obj.pos = pos
81 |
82 | def __setitem__(self, pos, material):
83 | if material not in self._mat_ids:
84 | id_ = len(self._mat_ids)
85 | self._mat_ids[material] = id_
86 | self._mat_map[tuple(pos)] = self._mat_ids[material]
87 |
88 | def __getitem__(self, pos):
89 | if not _inside((0, 0), pos, self.area):
90 | return None, None
91 | material = self._mat_names[self._mat_map[tuple(pos)]]
92 | obj = self._objects[self._obj_map[tuple(pos)]]
93 | return material, obj
94 |
95 | def nearby(self, pos, distance):
96 | (x, y), d = pos, distance
97 | ids = set(self._mat_map[
98 | x - d: x + d + 1, y - d: y + d + 1].flatten().tolist())
99 | materials = tuple(self._mat_names[x] for x in ids)
100 | indices = self._obj_map[
101 | x - d: x + d + 1, y - d: y + d + 1].flatten().tolist()
102 | objs = {self._objects[i] for i in indices if i > 0}
103 | return materials, objs
104 |
105 | def mask(self, xmin, xmax, ymin, ymax, material):
106 | region = self._mat_map[xmin: xmax, ymin: ymax]
107 | return (region == self._mat_ids[material])
108 |
109 | def count(self, material):
110 | return (self._mat_map == self._mat_ids[material]).sum()
111 |
112 | def chunk_key(self, pos):
113 | (x, y), (csx, csy) = pos, self._chunk_size
114 | xmin, ymin = (x // csx) * csx, (y // csy) * csy
115 | xmax = min(xmin + csx, self.area[0])
116 | ymax = min(ymin + csy, self.area[1])
117 | return (xmin, xmax, ymin, ymax)
118 |
119 |
120 | class Textures:
121 |
122 | def __init__(self, directory):
123 | self._originals = {}
124 | self._textures = {}
125 | for filename in pathlib.Path(directory).glob('*.png'):
126 | image = imageio.imread(filename.read_bytes())
127 | image = image.transpose((1, 0) + tuple(range(2, len(image.shape))))
128 | self._originals[filename.stem] = image
129 | self._textures[(filename.stem, image.shape[:2])] = image
130 |
131 | def get(self, name, size):
132 | if name is None:
133 | name = 'unknown'
134 | size = int(size[0]), int(size[1])
135 | key = name, size
136 | if key not in self._textures:
137 | image = self._originals[name]
138 | image = Image.fromarray(image)
139 | image = image.resize(size[::-1], resample=Image.NEAREST)
140 | image = np.array(image)
141 | self._textures[key] = image
142 | return self._textures[key]
143 |
144 |
145 | class GlobalView:
146 |
147 | pass
148 |
149 |
150 | class UncoverView:
151 |
152 | pass
153 |
154 |
155 | class LocalView:
156 |
157 | def __init__(self, world, textures, grid):
158 | self._world = world
159 | self._textures = textures
160 | self._grid = np.array(grid)
161 | self._offset = self._grid // 2
162 | self._area = np.array(self._world.area)
163 | self._center = None
164 |
165 | def __call__(self, player, unit):
166 | self._unit = np.array(unit)
167 | self._center = np.array(player.pos)
168 | canvas = np.zeros(tuple(self._grid * unit) + (3,), np.uint8) + 127
169 | for x in range(self._grid[0]):
170 | for y in range(self._grid[1]):
171 | pos = self._center + np.array([x, y]) - self._offset
172 | if not _inside((0, 0), pos, self._area):
173 | continue
174 | texture = self._textures.get(self._world[pos][0], unit)
175 | _draw(canvas, np.array([x, y]) * unit, texture)
176 | for obj in self._world.objects:
177 | pos = obj.pos - self._center + self._offset
178 | if not _inside((0, 0), pos, self._grid):
179 | continue
180 | texture = self._textures.get(obj.texture, unit)
181 | _draw_alpha(canvas, pos * unit, texture)
182 | canvas = self._light(canvas, self._world.daylight)
183 | if player.sleeping:
184 | canvas = self._sleep(canvas)
185 | # if player.health < 1:
186 | # canvas = self._tint(canvas, (128, 0, 0), 0.6)
187 | return canvas
188 |
189 | def _light(self, canvas, daylight):
190 | night = canvas
191 | if daylight < 0.5:
192 | night = self._noise(night, 2 * (0.5 - daylight), 0.5)
193 | night = np.array(ImageEnhance.Color(
194 | Image.fromarray(night.astype(np.uint8))).enhance(0.4))
195 | night = self._tint(night, (0, 16, 64), 0.5)
196 | return daylight * canvas + (1 - daylight) * night
197 |
198 | def _sleep(self, canvas):
199 | canvas = np.array(ImageEnhance.Color(
200 | Image.fromarray(canvas.astype(np.uint8))).enhance(0.0))
201 | canvas = self._tint(canvas, (0, 0, 16), 0.5)
202 | return canvas
203 |
204 | def _tint(self, canvas, color, amount):
205 | color = np.array(color)
206 | return (1 - amount) * canvas + amount * color
207 |
208 | def _noise(self, canvas, amount, stddev):
209 | noise = self._world.random.uniform(32, 127, canvas.shape[:2])[..., None]
210 | mask = amount * self._vignette(canvas.shape, stddev)[..., None]
211 | return (1 - mask) * canvas + mask * noise
212 |
213 | @functools.lru_cache(10)
214 | def _vignette(self, shape, stddev):
215 | xs, ys = np.meshgrid(
216 | np.linspace(-1, 1, shape[0]),
217 | np.linspace(-1, 1, shape[1]))
218 | return 1 - np.exp(-0.5 * (xs ** 2 + ys ** 2) / (stddev ** 2)).T
219 |
220 |
221 | class ItemView:
222 |
223 | def __init__(self, textures, grid):
224 | self._textures = textures
225 | self._grid = np.array(grid)
226 |
227 | def __call__(self, inventory, unit):
228 | unit = np.array(unit)
229 | canvas = np.zeros(tuple(self._grid * unit) + (3,), np.uint8)
230 | for index, (item, amount) in enumerate(inventory.items()):
231 | if amount < 1:
232 | continue
233 | self._item(canvas, index, item, unit)
234 | self._amount(canvas, index, amount, unit)
235 | return canvas
236 |
237 | def _item(self, canvas, index, item, unit):
238 | pos = index % self._grid[0], index // self._grid[0]
239 | pos = (pos * unit + 0.1 * unit).astype(np.int32)
240 | texture = self._textures.get(item, 0.8 * unit)
241 | _draw_alpha(canvas, pos, texture)
242 |
243 | def _amount(self, canvas, index, amount, unit):
244 | pos = index % self._grid[0], index // self._grid[0]
245 | pos = (pos * unit + 0.4 * unit).astype(np.int32)
246 | text = str(amount) if amount in list(range(10)) else 'unknown'
247 | texture = self._textures.get(text, 0.6 * unit)
248 | _draw_alpha(canvas, pos, texture)
249 |
250 |
251 | class SemanticView:
252 |
253 | def __init__(self, world, obj_types):
254 | self._world = world
255 | self._mat_ids = world._mat_ids.copy()
256 | self._obj_ids = {
257 | c: len(self._mat_ids) + i
258 | for i, c in enumerate(obj_types)}
259 |
260 | def __call__(self):
261 | canvas = self._world._mat_map.copy()
262 | for obj in self._world.objects:
263 | canvas[tuple(obj.pos)] = self._obj_ids[type(obj)]
264 | return canvas
265 |
266 |
267 | def _inside(lhs, mid, rhs):
268 | return (lhs[0] <= mid[0] < rhs[0]) and (lhs[1] <= mid[1] < rhs[1])
269 |
270 | def _draw(canvas, pos, texture):
271 | (x, y), (w, h) = pos, texture.shape[:2]
272 | if texture.shape[-1] == 4:
273 | texture = texture[..., :3]
274 | canvas[x: x + w, y: y + h] = texture
275 |
276 | def _draw_alpha(canvas, pos, texture):
277 | (x, y), (w, h) = pos, texture.shape[:2]
278 | if texture.shape[-1] == 4:
279 | alpha = texture[..., 3:].astype(np.float32) / 255
280 | texture = texture[..., :3].astype(np.float32) / 255
281 | current = canvas[x: x + w, y: y + h].astype(np.float32) / 255
282 | blended = alpha * texture + (1 - alpha) * current
283 | texture = (255 * blended).astype(np.uint8)
284 | canvas[x: x + w, y: y + h] = texture
285 |
--------------------------------------------------------------------------------
/crafter/env.py:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | import numpy as np
4 |
5 | from . import constants
6 | from . import engine
7 | from . import objects
8 | from . import worldgen
9 |
10 |
11 | # Gym is an optional dependency.
12 | try:
13 | import gym
14 | DiscreteSpace = gym.spaces.Discrete
15 | BoxSpace = gym.spaces.Box
16 | DictSpace = gym.spaces.Dict
17 | BaseClass = gym.Env
18 | except ImportError:
19 | DiscreteSpace = collections.namedtuple('DiscreteSpace', 'n')
20 | BoxSpace = collections.namedtuple('BoxSpace', 'low, high, shape, dtype')
21 | DictSpace = collections.namedtuple('DictSpace', 'spaces')
22 | BaseClass = object
23 |
24 |
25 | class Env(BaseClass):
26 |
27 | def __init__(
28 | self, area=(64, 64), view=(9, 9), size=(64, 64),
29 | reward=True, length=10000, seed=None):
30 | view = np.array(view if hasattr(view, '__len__') else (view, view))
31 | size = np.array(size if hasattr(size, '__len__') else (size, size))
32 | seed = np.random.randint(0, 2**31 - 1) if seed is None else seed
33 | self._area = area
34 | self._view = view
35 | self._size = size
36 | self._reward = reward
37 | self._length = length
38 | self._seed = seed
39 | self._episode = 0
40 | self._world = engine.World(area, constants.materials, (12, 12))
41 | self._textures = engine.Textures(constants.root / 'assets')
42 | item_rows = int(np.ceil(len(constants.items) / view[0]))
43 | self._local_view = engine.LocalView(
44 | self._world, self._textures, [view[0], view[1] - item_rows])
45 | self._item_view = engine.ItemView(
46 | self._textures, [view[0], item_rows])
47 | self._sem_view = engine.SemanticView(self._world, [
48 | objects.Player, objects.Cow, objects.Zombie,
49 | objects.Skeleton, objects.Arrow, objects.Plant])
50 | self._step = None
51 | self._player = None
52 | self._last_health = None
53 | self._unlocked = None
54 | # Some libraries expect these attributes to be set.
55 | self.reward_range = None
56 | self.metadata = None
57 |
58 | @property
59 | def observation_space(self):
60 | return BoxSpace(0, 255, tuple(self._size) + (3,), np.uint8)
61 |
62 | @property
63 | def action_space(self):
64 | return DiscreteSpace(len(constants.actions))
65 |
66 | @property
67 | def action_names(self):
68 | return constants.actions
69 |
70 | def reset(self):
71 | center = (self._world.area[0] // 2, self._world.area[1] // 2)
72 | self._episode += 1
73 | self._step = 0
74 | self._world.reset(seed=hash((self._seed, self._episode)) % (2 ** 31 - 1))
75 | self._update_time()
76 | self._player = objects.Player(self._world, center)
77 | self._last_health = self._player.health
78 | self._world.add(self._player)
79 | self._unlocked = set()
80 | worldgen.generate_world(self._world, self._player)
81 | return self._obs()
82 |
83 | def step(self, action):
84 | self._step += 1
85 | self._update_time()
86 | self._player.action = constants.actions[action]
87 | for obj in self._world.objects:
88 | if self._player.distance(obj) < 2 * max(self._view):
89 | obj.update()
90 | if self._step % 10 == 0:
91 | for chunk, objs in self._world.chunks.items():
92 | # xmin, xmax, ymin, ymax = chunk
93 | # center = (xmax - xmin) // 2, (ymax - ymin) // 2
94 | # if self._player.distance(center) < 4 * max(self._view):
95 | self._balance_chunk(chunk, objs)
96 | obs = self._obs()
97 | reward = (self._player.health - self._last_health) / 10
98 | self._last_health = self._player.health
99 | unlocked = {
100 | name for name, count in self._player.achievements.items()
101 | if count > 0 and name not in self._unlocked}
102 | if unlocked:
103 | self._unlocked |= unlocked
104 | reward += 1.0
105 | dead = self._player.health <= 0
106 | over = self._length and self._step >= self._length
107 | done = dead or over
108 | info = {
109 | 'inventory': self._player.inventory.copy(),
110 | 'achievements': self._player.achievements.copy(),
111 | 'discount': 1 - float(dead),
112 | 'semantic': self._sem_view(),
113 | 'player_pos': self._player.pos,
114 | 'reward': reward,
115 | }
116 | if not self._reward:
117 | reward = 0.0
118 | return obs, reward, done, info
119 |
120 | def render(self, size=None):
121 | size = size or self._size
122 | unit = size // self._view
123 | canvas = np.zeros(tuple(size) + (3,), np.uint8)
124 | local_view = self._local_view(self._player, unit)
125 | item_view = self._item_view(self._player.inventory, unit)
126 | view = np.concatenate([local_view, item_view], 1)
127 | border = (size - (size // self._view) * self._view) // 2
128 | (x, y), (w, h) = border, view.shape[:2]
129 | canvas[x: x + w, y: y + h] = view
130 | return canvas.transpose((1, 0, 2))
131 |
132 | def _obs(self):
133 | return self.render()
134 |
135 | def _update_time(self):
136 | # https://www.desmos.com/calculator/grfbc6rs3h
137 | progress = (self._step / 300) % 1 + 0.3
138 | daylight = 1 - np.abs(np.cos(np.pi * progress)) ** 3
139 | self._world.daylight = daylight
140 |
141 | def _balance_chunk(self, chunk, objs):
142 | light = self._world.daylight
143 | self._balance_object(
144 | chunk, objs, objects.Zombie, 'grass', 6, 0, 0.3, 0.4,
145 | lambda pos: objects.Zombie(self._world, pos, self._player),
146 | lambda num, space: (
147 | 0 if space < 50 else 3.5 - 3 * light, 3.5 - 3 * light))
148 | self._balance_object(
149 | chunk, objs, objects.Skeleton, 'path', 7, 7, 0.1, 0.1,
150 | lambda pos: objects.Skeleton(self._world, pos, self._player),
151 | lambda num, space: (0 if space < 6 else 1, 2))
152 | self._balance_object(
153 | chunk, objs, objects.Cow, 'grass', 5, 5, 0.01, 0.1,
154 | lambda pos: objects.Cow(self._world, pos),
155 | lambda num, space: (0 if space < 30 else 1, 1.5 + light))
156 |
157 | def _balance_object(
158 | self, chunk, objs, cls, material, span_dist, despan_dist,
159 | spawn_prob, despawn_prob, ctor, target_fn):
160 | xmin, xmax, ymin, ymax = chunk
161 | random = self._world.random
162 | creatures = [obj for obj in objs if isinstance(obj, cls)]
163 | mask = self._world.mask(*chunk, material)
164 | target_min, target_max = target_fn(len(creatures), mask.sum())
165 | if len(creatures) < int(target_min) and random.uniform() < spawn_prob:
166 | xs = np.tile(np.arange(xmin, xmax)[:, None], [1, ymax - ymin])
167 | ys = np.tile(np.arange(ymin, ymax)[None, :], [xmax - xmin, 1])
168 | xs, ys = xs[mask], ys[mask]
169 | i = random.randint(0, len(xs))
170 | pos = np.array((xs[i], ys[i]))
171 | empty = self._world[pos][1] is None
172 | away = self._player.distance(pos) >= span_dist
173 | if empty and away:
174 | self._world.add(ctor(pos))
175 | elif len(creatures) > int(target_max) and random.uniform() < despawn_prob:
176 | obj = creatures[random.randint(0, len(creatures))]
177 | away = self._player.distance(obj.pos) >= despan_dist
178 | if away:
179 | self._world.remove(obj)
180 |
--------------------------------------------------------------------------------
/crafter/objects.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | from . import constants
4 | from . import engine
5 |
6 |
7 | class Object:
8 |
9 | def __init__(self, world, pos):
10 | self.world = world
11 | self.pos = np.array(pos)
12 | self.random = world.random
13 | self.inventory = {'health': 0}
14 | self.removed = False
15 |
16 | @property
17 | def texture(self):
18 | raise 'unknown'
19 |
20 | @property
21 | def walkable(self):
22 | return constants.walkable
23 |
24 | @property
25 | def health(self):
26 | return self.inventory['health']
27 |
28 | @health.setter
29 | def health(self, value):
30 | self.inventory['health'] = max(0, value)
31 |
32 | @property
33 | def all_dirs(self):
34 | return ((-1, 0), (+1, 0), (0, -1), (0, +1))
35 |
36 | def move(self, direction):
37 | direction = np.array(direction)
38 | target = self.pos + direction
39 | if self.is_free(target):
40 | self.world.move(self, target)
41 | return True
42 | return False
43 |
44 | def is_free(self, target, materials=None):
45 | materials = self.walkable if materials is None else materials
46 | material, obj = self.world[target]
47 | return obj is None and material in materials
48 |
49 | def distance(self, target):
50 | if hasattr(target, 'pos'):
51 | target = target.pos
52 | return np.abs(target - self.pos).sum()
53 |
54 | def toward(self, target, long_axis=True):
55 | if hasattr(target, 'pos'):
56 | target = target.pos
57 | offset = target - self.pos
58 | dists = np.abs(offset)
59 | if (dists[0] > dists[1] if long_axis else dists[0] <= dists[1]):
60 | return np.array((np.sign(offset[0]), 0))
61 | else:
62 | return np.array((0, np.sign(offset[1])))
63 |
64 | def random_dir(self):
65 | return self.all_dirs[self.random.randint(0, 4)]
66 |
67 |
68 | class Player(Object):
69 |
70 | def __init__(self, world, pos):
71 | super().__init__(world, pos)
72 | self.facing = (0, 1)
73 | self.inventory = {
74 | name: info['initial'] for name, info in constants.items.items()}
75 | self.achievements = {name: 0 for name in constants.achievements}
76 | self.action = 'noop'
77 | self.sleeping = False
78 | self._last_health = self.health
79 | self._hunger = 0
80 | self._thirst = 0
81 | self._fatigue = 0
82 | self._recover = 0
83 |
84 | @property
85 | def texture(self):
86 | if self.sleeping:
87 | return 'player-sleep'
88 | return {
89 | (-1, 0): 'player-left',
90 | (+1, 0): 'player-right',
91 | (0, -1): 'player-up',
92 | (0, +1): 'player-down',
93 | }[tuple(self.facing)]
94 |
95 | @property
96 | def walkable(self):
97 | return constants.walkable + ['lava']
98 |
99 | def update(self):
100 | target = (self.pos[0] + self.facing[0], self.pos[1] + self.facing[1])
101 | material, obj = self.world[target]
102 | action = self.action
103 | if self.sleeping:
104 | if self.inventory['energy'] < constants.items['energy']['max']:
105 | action = 'sleep'
106 | else:
107 | self.sleeping = False
108 | self.achievements['wake_up'] += 1
109 | if action == 'noop':
110 | pass
111 | elif action.startswith('move_'):
112 | self._move(action[len('move_'):])
113 | elif action == 'do' and obj:
114 | self._do_object(obj)
115 | elif action == 'do':
116 | self._do_material(target, material)
117 | elif action == 'sleep':
118 | if self.inventory['energy'] < constants.items['energy']['max']:
119 | self.sleeping = True
120 | elif action.startswith('place_'):
121 | self._place(action[len('place_'):], target, material)
122 | elif action.startswith('make_'):
123 | self._make(action[len('make_'):])
124 | self._update_life_stats()
125 | self._degen_or_regen_health()
126 | for name, amount in self.inventory.items():
127 | maxmium = constants.items[name]['max']
128 | self.inventory[name] = max(0, min(amount, maxmium))
129 | # This needs to happen after the inventory states are clamped
130 | # because it involves the health water inventory count.
131 | self._wake_up_when_hurt()
132 |
133 | def _update_life_stats(self):
134 | self._hunger += 0.5 if self.sleeping else 1
135 | if self._hunger > 25:
136 | self._hunger = 0
137 | self.inventory['food'] -= 1
138 | self._thirst += 0.5 if self.sleeping else 1
139 | if self._thirst > 20:
140 | self._thirst = 0
141 | self.inventory['drink'] -= 1
142 | if self.sleeping:
143 | self._fatigue = min(self._fatigue - 1, 0)
144 | else:
145 | self._fatigue += 1
146 | if self._fatigue < -10:
147 | self._fatigue = 0
148 | self.inventory['energy'] += 1
149 | if self._fatigue > 30:
150 | self._fatigue = 0
151 | self.inventory['energy'] -= 1
152 |
153 | def _degen_or_regen_health(self):
154 | necessities = (
155 | self.inventory['food'] > 0,
156 | self.inventory['drink'] > 0,
157 | self.inventory['energy'] > 0 or self.sleeping)
158 | if all(necessities):
159 | self._recover += 2 if self.sleeping else 1
160 | else:
161 | self._recover -= 0.5 if self.sleeping else 1
162 | if self._recover > 25:
163 | self._recover = 0
164 | self.health += 1
165 | if self._recover < -15:
166 | self._recover = 0
167 | self.health -= 1
168 |
169 | def _wake_up_when_hurt(self):
170 | if self.health < self._last_health:
171 | self.sleeping = False
172 | self._last_health = self.health
173 |
174 | def _move(self, direction):
175 | directions = dict(left=(-1, 0), right=(+1, 0), up=(0, -1), down=(0, +1))
176 | self.facing = directions[direction]
177 | self.move(self.facing)
178 | if self.world[self.pos][0] == 'lava':
179 | self.health = 0
180 |
181 | def _do_object(self, obj):
182 | damage = max([
183 | 1,
184 | self.inventory['wood_sword'] and 2,
185 | self.inventory['stone_sword'] and 3,
186 | self.inventory['iron_sword'] and 5,
187 | ])
188 | if isinstance(obj, Plant):
189 | if obj.ripe:
190 | obj.grown = 0
191 | self.inventory['food'] += 4
192 | self.achievements['eat_plant'] += 1
193 | if isinstance(obj, Fence):
194 | self.world.remove(obj)
195 | self.inventory['fence'] += 1
196 | self.achievements['collect_fence'] += 1
197 | if isinstance(obj, Zombie):
198 | obj.health -= damage
199 | if obj.health <= 0:
200 | self.achievements['defeat_zombie'] += 1
201 | if isinstance(obj, Skeleton):
202 | obj.health -= damage
203 | if obj.health <= 0:
204 | self.achievements['defeat_skeleton'] += 1
205 | if isinstance(obj, Cow):
206 | obj.health -= damage
207 | if obj.health <= 0:
208 | self.inventory['food'] += 6
209 | self.achievements['eat_cow'] += 1
210 | # TODO: Keep track of previous inventory state to do this in a more
211 | # general way.
212 | self._hunger = 0
213 |
214 | def _do_material(self, target, material):
215 | if material == 'water':
216 | # TODO: Keep track of previous inventory state to do this in a more
217 | # general way.
218 | self._thirst = 0
219 | info = constants.collect.get(material)
220 | if not info:
221 | return
222 | for name, amount in info['require'].items():
223 | if self.inventory[name] < amount:
224 | return
225 | self.world[target] = info['leaves']
226 | if self.random.uniform() <= info.get('probability', 1):
227 | for name, amount in info['receive'].items():
228 | self.inventory[name] += amount
229 | self.achievements[f'collect_{name}'] += 1
230 |
231 | def _place(self, name, target, material):
232 | if self.world[target][1]:
233 | return
234 | info = constants.place[name]
235 | if material not in info['where']:
236 | return
237 | if any(self.inventory[k] < v for k, v in info['uses'].items()):
238 | return
239 | for item, amount in info['uses'].items():
240 | self.inventory[item] -= amount
241 | if info['type'] == 'material':
242 | self.world[target] = name
243 | elif info['type'] == 'object':
244 | cls = {
245 | 'fence': Fence,
246 | 'plant': Plant,
247 | }[name]
248 | self.world.add(cls(self.world, target))
249 | self.achievements[f'place_{name}'] += 1
250 |
251 | def _make(self, name):
252 | nearby, _ = self.world.nearby(self.pos, 1)
253 | info = constants.make[name]
254 | if not all(util in nearby for util in info['nearby']):
255 | return
256 | if any(self.inventory[k] < v for k, v in info['uses'].items()):
257 | return
258 | for item, amount in info['uses'].items():
259 | self.inventory[item] -= amount
260 | self.inventory[name] += info['gives']
261 | self.achievements[f'make_{name}'] += 1
262 |
263 |
264 | class Cow(Object):
265 |
266 | def __init__(self, world, pos):
267 | super().__init__(world, pos)
268 | self.health = 3
269 |
270 | @property
271 | def texture(self):
272 | return 'cow'
273 |
274 | def update(self):
275 | if self.health <= 0:
276 | self.world.remove(self)
277 | if self.random.uniform() < 0.5:
278 | direction = self.random_dir()
279 | self.move(direction)
280 |
281 |
282 | class Zombie(Object):
283 |
284 | def __init__(self, world, pos, player):
285 | super().__init__(world, pos)
286 | self.player = player
287 | self.health = 5
288 | self.cooldown = 0
289 |
290 | @property
291 | def texture(self):
292 | return 'zombie'
293 |
294 | def update(self):
295 | if self.health <= 0:
296 | self.world.remove(self)
297 | dist = self.distance(self.player)
298 | if dist <= 8 and self.random.uniform() < 0.9:
299 | self.move(self.toward(self.player, self.random.uniform() < 0.8))
300 | else:
301 | self.move(self.random_dir())
302 | dist = self.distance(self.player)
303 | if dist <= 1:
304 | if self.cooldown:
305 | self.cooldown -= 1
306 | else:
307 | if self.player.sleeping:
308 | damage = 7
309 | else:
310 | damage = 2
311 | self.player.health -= damage
312 | self.cooldown = 5
313 |
314 |
315 | class Skeleton(Object):
316 |
317 | def __init__(self, world, pos, player):
318 | super().__init__(world, pos)
319 | self.player = player
320 | self.health = 3
321 | self.reload = 0
322 |
323 | @property
324 | def texture(self):
325 | return 'skeleton'
326 |
327 | def update(self):
328 | if self.health <= 0:
329 | self.world.remove(self)
330 | self.reload = max(0, self.reload - 1)
331 | dist = self.distance(self.player.pos)
332 | if dist <= 3:
333 | moved = self.move(-self.toward(self.player, self.random.uniform() < 0.6))
334 | if moved:
335 | return
336 | if dist <= 5 and self.random.uniform() < 0.5:
337 | self._shoot(self.toward(self.player))
338 | elif dist <= 8 and self.random.uniform() < 0.3:
339 | self.move(self.toward(self.player, self.random.uniform() < 0.6))
340 | elif self.random.uniform() < 0.2:
341 | self.move(self.random_dir())
342 |
343 | def _shoot(self, direction):
344 | if self.reload > 0:
345 | return
346 | if direction[0] == 0 and direction[1] == 0:
347 | return
348 | pos = self.pos + direction
349 | if self.is_free(pos, Arrow.walkable):
350 | self.world.add(Arrow(self.world, pos, direction))
351 | self.reload = 4
352 |
353 |
354 | class Arrow(Object):
355 |
356 | def __init__(self, world, pos, facing):
357 | super().__init__(world, pos)
358 | self.facing = facing
359 |
360 | @property
361 | def texture(self):
362 | return {
363 | (-1, 0): 'arrow-left',
364 | (+1, 0): 'arrow-right',
365 | (0, -1): 'arrow-up',
366 | (0, +1): 'arrow-down',
367 | }[tuple(self.facing)]
368 |
369 | @engine.staticproperty
370 | def walkable():
371 | return constants.walkable + ['water', 'lava']
372 |
373 | def update(self):
374 | target = self.pos + self.facing
375 | material, obj = self.world[target]
376 | if obj:
377 | obj.health -= 2
378 | self.world.remove(self)
379 | elif material not in self.walkable:
380 | self.world.remove(self)
381 | if material in ['table', 'furnace']:
382 | self.world[target] = 'path'
383 | else:
384 | self.move(self.facing)
385 |
386 |
387 | class Plant(Object):
388 |
389 | def __init__(self, world, pos):
390 | super().__init__(world, pos)
391 | self.health = 1
392 | self.grown = 0
393 |
394 | @property
395 | def texture(self):
396 | if self.ripe:
397 | return 'plant-ripe'
398 | else:
399 | return 'plant'
400 |
401 | @property
402 | def ripe(self):
403 | return self.grown > 300
404 |
405 | def update(self):
406 | self.grown += 1
407 | objs = [self.world[self.pos + dir_][1] for dir_ in self.all_dirs]
408 | if any(isinstance(obj, (Zombie, Skeleton, Cow)) for obj in objs):
409 | self.health -= 1
410 | if self.health <= 0:
411 | self.world.remove(self)
412 |
413 |
414 | class Fence(Object):
415 |
416 | def __init__(self, world, pos):
417 | super().__init__(world, pos)
418 |
419 | @property
420 | def texture(self):
421 | return 'fence'
422 |
423 | def update(self):
424 | pass
425 |
--------------------------------------------------------------------------------
/crafter/recorder.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | import pathlib
4 |
5 | import imageio
6 | import numpy as np
7 |
8 |
9 | class Recorder:
10 |
11 | def __init__(
12 | self, env, directory, save_stats=True, save_video=True,
13 | save_episode=True, video_size=(512, 512)):
14 | if directory and save_stats:
15 | env = StatsRecorder(env, directory)
16 | if directory and save_video:
17 | env = VideoRecorder(env, directory, video_size)
18 | if directory and save_episode:
19 | env = EpisodeRecorder(env, directory)
20 | self._env = env
21 |
22 | def __getattr__(self, name):
23 | if name.startswith('__'):
24 | raise AttributeError(name)
25 | return getattr(self._env, name)
26 |
27 |
28 | class StatsRecorder:
29 |
30 | def __init__(self, env, directory):
31 | self._env = env
32 | self._directory = pathlib.Path(directory).expanduser()
33 | self._directory.mkdir(exist_ok=True, parents=True)
34 | self._file = (self._directory / 'stats.jsonl').open('a')
35 | self._length = None
36 | self._reward = None
37 | self._unlocked = None
38 | self._stats = None
39 |
40 | def __getattr__(self, name):
41 | if name.startswith('__'):
42 | raise AttributeError(name)
43 | return getattr(self._env, name)
44 |
45 | def reset(self):
46 | obs = self._env.reset()
47 | self._length = 0
48 | self._reward = 0
49 | self._unlocked = None
50 | self._stats = None
51 | return obs
52 |
53 | def step(self, action):
54 | obs, reward, done, info = self._env.step(action)
55 | self._length += 1
56 | self._reward += info['reward']
57 | if done:
58 | self._stats = {'length': self._length, 'reward': round(self._reward, 1)}
59 | for key, value in info['achievements'].items():
60 | self._stats[f'achievement_{key}'] = value
61 | self._save()
62 | return obs, reward, done, info
63 |
64 | def _save(self):
65 | self._file.write(json.dumps(self._stats) + '\n')
66 | self._file.flush()
67 |
68 |
69 | class VideoRecorder:
70 |
71 | def __init__(self, env, directory, size=(512, 512)):
72 | if not hasattr(env, 'episode_name'):
73 | env = EpisodeName(env)
74 | self._env = env
75 | self._directory = pathlib.Path(directory).expanduser()
76 | self._directory.mkdir(exist_ok=True, parents=True)
77 | self._size = size
78 | self._frames = None
79 |
80 | def __getattr__(self, name):
81 | if name.startswith('__'):
82 | raise AttributeError(name)
83 | return getattr(self._env, name)
84 |
85 | def reset(self):
86 | obs = self._env.reset()
87 | self._frames = [self._env.render(self._size)]
88 | return obs
89 |
90 | def step(self, action):
91 | obs, reward, done, info = self._env.step(action)
92 | self._frames.append(self._env.render(self._size))
93 | if done:
94 | self._save()
95 | return obs, reward, done, info
96 |
97 | def _save(self):
98 | filename = str(self._directory / (self._env.episode_name + '.mp4'))
99 | imageio.mimsave(filename, self._frames)
100 |
101 |
102 | class EpisodeRecorder:
103 |
104 | def __init__(self, env, directory):
105 | if not hasattr(env, 'episode_name'):
106 | env = EpisodeName(env)
107 | self._env = env
108 | self._directory = pathlib.Path(directory).expanduser()
109 | self._directory.mkdir(exist_ok=True, parents=True)
110 | self._episode = None
111 |
112 | def __getattr__(self, name):
113 | if name.startswith('__'):
114 | raise AttributeError(name)
115 | return getattr(self._env, name)
116 |
117 | def reset(self):
118 | obs = self._env.reset()
119 | self._episode = [{'image': obs}]
120 | return obs
121 |
122 | def step(self, action):
123 | # Transitions are defined from the environment perspective, meaning that a
124 | # transition contains the action and the resulting reward and next
125 | # observation produced by the environment in response to said action.
126 | obs, reward, done, info = self._env.step(action)
127 | transition = {
128 | 'action': action, 'image': obs, 'reward': reward, 'done': done,
129 | }
130 | for key, value in info.items():
131 | if key in ('inventory', 'achievements'):
132 | continue
133 | transition[key] = value
134 | for key, value in info['achievements'].items():
135 | transition[f'achievement_{key}'] = value
136 | for key, value in info['inventory'].items():
137 | transition[f'ainventory_{key}'] = value
138 | self._episode.append(transition)
139 | if done:
140 | self._save()
141 | return obs, reward, done, info
142 |
143 | def _save(self):
144 | filename = str(self._directory / (self._env.episode_name + '.npz'))
145 | # Fill in zeros for keys missing at the first time step.
146 | for key, value in self._episode[1].items():
147 | if key not in self._episode[0]:
148 | self._episode[0][key] = np.zeros_like(value)
149 | episode = {
150 | k: np.array([step[k] for step in self._episode])
151 | for k in self._episode[0]}
152 | np.savez_compressed(filename, **episode)
153 |
154 |
155 | class EpisodeName:
156 |
157 | def __init__(self, env):
158 | self._env = env
159 | self._timestamp = None
160 | self._unlocked = None
161 | self._length = None
162 |
163 | def __getattr__(self, name):
164 | if name.startswith('__'):
165 | raise AttributeError(name)
166 | return getattr(self._env, name)
167 |
168 | def reset(self):
169 | obs = self._env.reset()
170 | self._timestamp = None
171 | self._unlocked = None
172 | self._length = 0
173 | return obs
174 |
175 | def step(self, action):
176 | obs, reward, done, info = self._env.step(action)
177 | self._length += 1
178 | if done:
179 | self._timestamp = datetime.datetime.now().strftime('%Y%m%dT%H%M%S')
180 | self._unlocked = sum(int(v >= 1) for v in info['achievements'].values())
181 | return obs, reward, done, info
182 |
183 | @property
184 | def episode_name(self):
185 | return f'{self._timestamp}-ach{self._unlocked}-len{self._length}'
186 |
--------------------------------------------------------------------------------
/crafter/run_gui.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | import numpy as np
4 | try:
5 | import pygame
6 | except ImportError:
7 | print('Please install the pygame package to use the GUI.')
8 | raise
9 | from PIL import Image
10 |
11 | import crafter
12 |
13 |
14 | def main():
15 | boolean = lambda x: bool(['False', 'True'].index(x))
16 | parser = argparse.ArgumentParser()
17 | parser.add_argument('--seed', type=int, default=None)
18 | parser.add_argument('--area', nargs=2, type=int, default=(64, 64))
19 | parser.add_argument('--view', type=int, nargs=2, default=(9, 9))
20 | parser.add_argument('--length', type=int, default=None)
21 | parser.add_argument('--health', type=int, default=9)
22 | parser.add_argument('--window', type=int, nargs=2, default=(600, 600))
23 | parser.add_argument('--size', type=int, nargs=2, default=(0, 0))
24 | parser.add_argument('--record', type=str, default=None)
25 | parser.add_argument('--fps', type=int, default=5)
26 | parser.add_argument('--wait', type=boolean, default=False)
27 | parser.add_argument('--death', type=str, default='reset', choices=[
28 | 'continue', 'reset', 'quit'])
29 | args = parser.parse_args()
30 |
31 | keymap = {
32 | pygame.K_a: 'move_left',
33 | pygame.K_d: 'move_right',
34 | pygame.K_w: 'move_up',
35 | pygame.K_s: 'move_down',
36 | pygame.K_SPACE: 'do',
37 | pygame.K_TAB: 'sleep',
38 |
39 | pygame.K_r: 'place_stone',
40 | pygame.K_t: 'place_table',
41 | pygame.K_f: 'place_furnace',
42 | pygame.K_p: 'place_plant',
43 |
44 | pygame.K_1: 'make_wood_pickaxe',
45 | pygame.K_2: 'make_stone_pickaxe',
46 | pygame.K_3: 'make_iron_pickaxe',
47 | pygame.K_4: 'make_wood_sword',
48 | pygame.K_5: 'make_stone_sword',
49 | pygame.K_6: 'make_iron_sword',
50 | }
51 | print('Actions:')
52 | for key, action in keymap.items():
53 | print(f' {pygame.key.name(key)}: {action}')
54 |
55 | crafter.constants.items['health']['max'] = args.health
56 | crafter.constants.items['health']['initial'] = args.health
57 |
58 | size = list(args.size)
59 | size[0] = size[0] or args.window[0]
60 | size[1] = size[1] or args.window[1]
61 |
62 | env = crafter.Env(
63 | area=args.area, view=args.view, length=args.length, seed=args.seed)
64 | env = crafter.Recorder(env, args.record)
65 | env.reset()
66 | achievements = set()
67 | duration = 0
68 | return_ = 0
69 | was_done = False
70 | print('Diamonds exist:', env._world.count('diamond'))
71 |
72 | pygame.init()
73 | screen = pygame.display.set_mode(args.window)
74 | clock = pygame.time.Clock()
75 | running = True
76 | while running:
77 |
78 | # Rendering.
79 | image = env.render(size)
80 | if size != args.window:
81 | image = Image.fromarray(image)
82 | image = image.resize(args.window, resample=Image.NEAREST)
83 | image = np.array(image)
84 | surface = pygame.surfarray.make_surface(image.transpose((1, 0, 2)))
85 | screen.blit(surface, (0, 0))
86 | pygame.display.flip()
87 | clock.tick(args.fps)
88 |
89 | # Keyboard input.
90 | action = None
91 | pygame.event.pump()
92 | for event in pygame.event.get():
93 | if event.type == pygame.QUIT:
94 | running = False
95 | elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
96 | running = False
97 | elif event.type == pygame.KEYDOWN and event.key in keymap.keys():
98 | action = keymap[event.key]
99 | if action is None:
100 | pressed = pygame.key.get_pressed()
101 | for key, action in keymap.items():
102 | if pressed[key]:
103 | break
104 | else:
105 | if args.wait and not env._player.sleeping:
106 | continue
107 | else:
108 | action = 'noop'
109 |
110 | # Environment step.
111 | _, reward, done, _ = env.step(env.action_names.index(action))
112 | duration += 1
113 |
114 | # Achievements.
115 | unlocked = {
116 | name for name, count in env._player.achievements.items()
117 | if count > 0 and name not in achievements}
118 | for name in unlocked:
119 | achievements |= unlocked
120 | total = len(env._player.achievements.keys())
121 | print(f'Achievement ({len(achievements)}/{total}): {name}')
122 | if env._step > 0 and env._step % 100 == 0:
123 | print(f'Time step: {env._step}')
124 | if reward:
125 | print(f'Reward: {reward}')
126 | return_ += reward
127 |
128 | # Episode end.
129 | if done and not was_done:
130 | was_done = True
131 | print('Episode done!')
132 | print('Duration:', duration)
133 | print('Return:', return_)
134 | if args.death == 'quit':
135 | running = False
136 | if args.death == 'reset':
137 | print('\nStarting a new episode.')
138 | env.reset()
139 | achievements = set()
140 | was_done = False
141 | duration = 0
142 | return_ = 0
143 | if args.death == 'continue':
144 | pass
145 |
146 | pygame.quit()
147 |
148 |
149 | if __name__ == '__main__':
150 | main()
151 |
--------------------------------------------------------------------------------
/crafter/run_random.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import pathlib
3 | import time
4 |
5 | import numpy as np
6 |
7 | import crafter
8 |
9 |
10 | def main():
11 | parser = argparse.ArgumentParser()
12 | parser.add_argument('--seed', type=int, default=None)
13 | parser.add_argument('--area', nargs=2, type=int, default=(64, 64))
14 | parser.add_argument('--length', type=int, default=10000)
15 | parser.add_argument('--health', type=int, default=9)
16 | parser.add_argument('--record', type=pathlib.Path, default=None)
17 | parser.add_argument('--episodes', type=int, default=1)
18 | args = parser.parse_args()
19 |
20 | random = np.random.RandomState(args.seed)
21 | crafter.constants.items['health']['max'] = args.health
22 | crafter.constants.items['health']['initial'] = args.health
23 | env = crafter.Env(area=args.area, length=args.length, seed=args.seed)
24 | env = crafter.Recorder(env, args.record)
25 |
26 | for _ in range(args.episodes):
27 |
28 | start = time.time()
29 | obs = env.reset()
30 | print('')
31 | print(f'Reset time: {1000*(time.time()-start):.2f}ms')
32 | print('Coal exist: ', env._world.count('coal'))
33 | print('Iron exist: ', env._world.count('iron'))
34 | print('Diamonds exist:', env._world.count('diamond'))
35 |
36 | start = time.time()
37 | done = False
38 | while not done:
39 | action = random.randint(0, env.action_space.n)
40 | obs, reward, done, info = env.step(action)
41 | duration = time.time() - start
42 | step = env._step
43 | print(f'Step time: {1000*duration/step:.2f}ms ({int(step/duration)} FPS)')
44 | print('Episode length:', step)
45 |
46 |
47 | if __name__ == '__main__':
48 | main()
49 |
--------------------------------------------------------------------------------
/crafter/run_terrain.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | import imageio
4 | import numpy as np
5 |
6 | import crafter
7 |
8 |
9 | def main():
10 | parser = argparse.ArgumentParser()
11 | parser.add_argument('--seed', type=int, default=None)
12 | parser.add_argument('--amount', type=int, default=4)
13 | parser.add_argument('--cols', type=int, default=4)
14 | parser.add_argument('--area', nargs=2, type=int, default=(64, 64))
15 | parser.add_argument('--size', type=int, default=1024)
16 | parser.add_argument('--filename', type=str, default='terrain.png')
17 | args = parser.parse_args()
18 |
19 | env = crafter.Env(args.area, args.area, args.size, seed=args.seed)
20 | images = []
21 | for index in range(args.amount):
22 | images.append(env.reset())
23 | diamonds = env._world.count('diamond')
24 | print(f'Map: {index:>2}, diamonds: {diamonds:>2}')
25 |
26 | rows = len(images) // args.cols
27 | strips = []
28 | for row in range(rows):
29 | strip = []
30 | for col in range(args.cols):
31 | try:
32 | strip.append(images[row * args.cols + col])
33 | except IndexError:
34 | strip.append(np.zeros_like(strip[-1]))
35 | strips.append(np.concatenate(strip, 1))
36 | grid = np.concatenate(strips, 0)
37 |
38 | imageio.imsave(args.filename, grid)
39 | print('Saved', args.filename)
40 |
41 |
42 | if __name__ == '__main__':
43 | main()
44 |
--------------------------------------------------------------------------------
/crafter/worldgen.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 | import numpy as np
4 | import opensimplex
5 |
6 | from . import constants
7 | from . import objects
8 |
9 |
10 | def generate_world(world, player):
11 | simplex = opensimplex.OpenSimplex(seed=world.random.randint(0, 2 ** 31 - 1))
12 | tunnels = np.zeros(world.area, bool)
13 | for x in range(world.area[0]):
14 | for y in range(world.area[1]):
15 | _set_material(world, (x, y), player, tunnels, simplex)
16 | for x in range(world.area[0]):
17 | for y in range(world.area[1]):
18 | _set_object(world, (x, y), player, tunnels)
19 |
20 |
21 | def _set_material(world, pos, player, tunnels, simplex):
22 | x, y = pos
23 | simplex = functools.partial(_simplex, simplex)
24 | uniform = world.random.uniform
25 | start = 4 - np.sqrt((x - player.pos[0]) ** 2 + (y - player.pos[1]) ** 2)
26 | start += 2 * simplex(x, y, 8, 3)
27 | start = 1 / (1 + np.exp(-start))
28 | water = simplex(x, y, 3, {15: 1, 5: 0.15}, False) + 0.1
29 | water -= 2 * start
30 | mountain = simplex(x, y, 0, {15: 1, 5: 0.3})
31 | mountain -= 4 * start + 0.3 * water
32 | if start > 0.5:
33 | world[x, y] = 'grass'
34 | elif mountain > 0.15:
35 | if (simplex(x, y, 6, 7) > 0.15 and mountain > 0.3): # cave
36 | world[x, y] = 'path'
37 | elif simplex(2 * x, y / 5, 7, 3) > 0.4: # horizonal tunnle
38 | world[x, y] = 'path'
39 | tunnels[x, y] = True
40 | elif simplex(x / 5, 2 * y, 7, 3) > 0.4: # vertical tunnle
41 | world[x, y] = 'path'
42 | tunnels[x, y] = True
43 | elif simplex(x, y, 1, 8) > 0 and uniform() > 0.85:
44 | world[x, y] = 'coal'
45 | elif simplex(x, y, 2, 6) > 0.4 and uniform() > 0.75:
46 | world[x, y] = 'iron'
47 | elif mountain > 0.18 and uniform() > 0.994:
48 | world[x, y] = 'diamond'
49 | elif mountain > 0.3 and simplex(x, y, 6, 5) > 0.35:
50 | world[x, y] = 'lava'
51 | else:
52 | world[x, y] = 'stone'
53 | elif 0.25 < water <= 0.35 and simplex(x, y, 4, 9) > -0.2:
54 | world[x, y] = 'sand'
55 | elif 0.3 < water:
56 | world[x, y] = 'water'
57 | else: # grassland
58 | if simplex(x, y, 5, 7) > 0 and uniform() > 0.8:
59 | world[x, y] = 'tree'
60 | else:
61 | world[x, y] = 'grass'
62 |
63 |
64 | def _set_object(world, pos, player, tunnels):
65 | x, y = pos
66 | uniform = world.random.uniform
67 | dist = np.sqrt((x - player.pos[0]) ** 2 + (y - player.pos[1]) ** 2)
68 | material, _ = world[x, y]
69 | if material not in constants.walkable:
70 | pass
71 | elif dist > 3 and material == 'grass' and uniform() > 0.985:
72 | world.add(objects.Cow(world, (x, y)))
73 | elif dist > 10 and uniform() > 0.993:
74 | world.add(objects.Zombie(world, (x, y), player))
75 | elif material == 'path' and tunnels[x, y] and uniform() > 0.95:
76 | world.add(objects.Skeleton(world, (x, y), player))
77 |
78 |
79 | def _simplex(simplex, x, y, z, sizes, normalize=True):
80 | if not isinstance(sizes, dict):
81 | sizes = {sizes: 1}
82 | value = 0
83 | for size, weight in sizes.items():
84 | if hasattr(simplex, 'noise3d'):
85 | noise = simplex.noise3d(x / size, y / size, z)
86 | else:
87 | noise = simplex.noise3(x / size, y / size, z)
88 | value += weight * noise
89 | if normalize:
90 | value /= sum(sizes.values())
91 | return value
92 |
--------------------------------------------------------------------------------
/examples/run_ppo.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | import crafter
4 | import stable_baselines3
5 |
6 | parser = argparse.ArgumentParser()
7 | parser.add_argument('--outdir', default='logdir/crafter_reward-ppo/0')
8 | parser.add_argument('--steps', type=float, default=1e6)
9 | args = parser.parse_args()
10 |
11 | env = crafter.Env()
12 | env = crafter.Recorder(
13 | env, args.outdir,
14 | save_stats=True,
15 | save_episode=False,
16 | save_video=False,
17 | )
18 |
19 | model = stable_baselines3.PPO('CnnPolicy', env, verbose=1)
20 | model.learn(total_timesteps=args.steps)
21 |
--------------------------------------------------------------------------------
/examples/run_random.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | import crafter
4 | import tqdm
5 |
6 | parser = argparse.ArgumentParser()
7 | parser.add_argument('--outdir', default='logdir/crafter_noreward-random/0')
8 | parser.add_argument('--steps', type=float, default=1e6)
9 | args = parser.parse_args()
10 |
11 | env = crafter.Env()
12 | env = crafter.Recorder(
13 | env, args.outdir,
14 | save_stats=True,
15 | save_episode=False,
16 | save_video=False,
17 | )
18 | action_space = env.action_space
19 |
20 | done = True
21 | step = 0
22 | bar = tqdm.tqdm(total=args.steps, smoothing=0)
23 | while step < args.steps or not done:
24 | if done:
25 | env.reset()
26 | done = False
27 | _, _, done, _ = env.step(action_space.sample())
28 | step += 1
29 | bar.update(1)
30 |
--------------------------------------------------------------------------------
/media/scores.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/media/scores.png
--------------------------------------------------------------------------------
/media/terrain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/media/terrain.png
--------------------------------------------------------------------------------
/media/video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/media/video.gif
--------------------------------------------------------------------------------
/media/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/media/video.mp4
--------------------------------------------------------------------------------
/plots/counts-dreamerv2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-dreamerv2.pdf
--------------------------------------------------------------------------------
/plots/counts-ppo.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-ppo.pdf
--------------------------------------------------------------------------------
/plots/counts-rainbow.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-rainbow.pdf
--------------------------------------------------------------------------------
/plots/counts-random.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-random.pdf
--------------------------------------------------------------------------------
/plots/counts-unsup_plan2explore.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-unsup_plan2explore.pdf
--------------------------------------------------------------------------------
/plots/counts-unsup_rnd.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/counts-unsup_rnd.pdf
--------------------------------------------------------------------------------
/plots/reward.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/reward.pdf
--------------------------------------------------------------------------------
/plots/scores-agents.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/scores-agents.pdf
--------------------------------------------------------------------------------
/plots/scores-human.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/scores-human.pdf
--------------------------------------------------------------------------------
/plots/spectrum-noreward.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/spectrum-noreward.pdf
--------------------------------------------------------------------------------
/plots/spectrum-reward.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danijar/crafter/e04542a2159f1aad3d4c5ad52e8185717380ee3a/plots/spectrum-reward.pdf
--------------------------------------------------------------------------------
/scores/crafter_reward-human.json:
--------------------------------------------------------------------------------
1 | [{"task": "crafter-reward", "method": "human", "seed": "0", "xs": [353, 634, 1170, 1896, 2799, 3249, 3863, 4145, 4503, 5492, 5780, 6110, 6392, 6974, 7525, 7809, 8660, 8840, 9484, 10020], "length": [353, 281, 536, 726, 903, 450, 614, 282, 358, 989, 288, 330, 282, 582, 551, 284, 851, 180, 644, 536], "reward": [12.1, 9.0, 15.1, 14.0, 13.0, 13.1, 9.1, 13.0, 13.1, 14.0, 10.1, 9.0, 10.1, 10.0, 12.1, 11.0, 14.1, 11.1, 13.1, 13.1], "achievement_collect_coal": [2, 5, 5, 3, 0, 1, 0, 2, 2, 2, 1, 1, 1, 4, 2, 4, 1, 2, 4, 2], "achievement_collect_diamond": [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_collect_drink": [13, 0, 15, 39, 27, 17, 24, 0, 8, 39, 10, 10, 12, 24, 17, 0, 28, 8, 18, 21], "achievement_collect_iron": [0, 0, 2, 2, 3, 1, 0, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2], "achievement_collect_sapling": [1, 6, 1, 5, 3, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 7, 3, 0, 1], "achievement_collect_stone": [15, 20, 22, 15, 9, 22, 11, 15, 17, 17, 11, 5, 6, 10, 9, 6, 16, 2, 39, 13], "achievement_collect_wood": [11, 9, 6, 7, 20, 8, 12, 9, 7, 11, 9, 9, 7, 6, 9, 6, 10, 13, 8, 15], "achievement_defeat_skeleton": [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1], "achievement_defeat_zombie": [2, 1, 0, 1, 4, 4, 4, 2, 0, 4, 2, 1, 1, 0, 5, 0, 3, 3, 0, 0], "achievement_eat_cow": [1, 0, 2, 4, 6, 1, 3, 2, 1, 5, 0, 2, 2, 2, 4, 1, 3, 1, 3, 2], "achievement_eat_plant": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_iron_pickaxe": [0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], "achievement_make_iron_sword": [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_stone_pickaxe": [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1], "achievement_make_stone_sword": [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1], "achievement_make_wood_pickaxe": [1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_sword": [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0], "achievement_place_furnace": [0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], "achievement_place_plant": [0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_place_stone": [1, 1, 5, 10, 3, 3, 9, 1, 1, 6, 2, 2, 1, 3, 2, 1, 4, 0, 8, 4], "achievement_place_table": [2, 1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2], "achievement_wake_up": [1, 0, 1, 1, 3, 0, 1, 0, 0, 3, 1, 0, 1, 3, 1, 1, 2, 0, 1, 1]}, {"task": "crafter-reward", "method": "human", "seed": "1", "xs": [664, 826, 3603, 3797, 4597, 5091, 5385, 5679, 6005, 6222, 6418, 6665, 7752, 8242, 8518, 8777, 9123, 9730, 10072, 10340], "length": [664, 162, 2777, 194, 800, 494, 294, 294, 326, 217, 196, 247, 1087, 490, 276, 259, 346, 607, 342, 268], "reward": [13.1, 9.1, 21.1, 11.1, 14.1, 13.1, 8.8, 13.1, 11.0, 10.8, 9.1, 15.1, 15.1, 16.0, 11.1, 10.0, 13.1, 16.1, 10.1, 9.1], "achievement_collect_coal": [3, 1, 4, 4, 3, 3, 1, 3, 1, 0, 4, 2, 4, 4, 0, 0, 1, 2, 4, 1], "achievement_collect_diamond": [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_collect_drink": [24, 5, 141, 7, 25, 21, 3, 5, 9, 8, 0, 7, 41, 14, 7, 4, 17, 16, 10, 8], "achievement_collect_iron": [2, 0, 5, 2, 0, 0, 0, 0, 0, 0, 6, 2, 3, 5, 0, 0, 2, 4, 0, 0], "achievement_collect_sapling": [6, 1, 10, 0, 5, 1, 2, 3, 1, 1, 0, 2, 4, 0, 4, 4, 1, 1, 2, 3], "achievement_collect_stone": [25, 7, 69, 2, 20, 8, 3, 7, 4, 4, 6, 11, 27, 18, 9, 1, 12, 11, 18, 1], "achievement_collect_wood": [9, 6, 17, 8, 12, 8, 4, 6, 7, 10, 10, 9, 8, 10, 8, 10, 7, 17, 7, 8], "achievement_defeat_skeleton": [0, 0, 4, 2, 2, 1, 0, 0, 0, 1, 0, 1, 6, 0, 0, 0, 0, 0, 0, 0], "achievement_defeat_zombie": [3, 0, 7, 1, 7, 1, 0, 2, 1, 1, 3, 2, 4, 2, 1, 3, 2, 3, 0, 2], "achievement_eat_cow": [3, 1, 11, 1, 6, 2, 1, 1, 2, 1, 1, 0, 5, 2, 1, 2, 3, 5, 1, 1], "achievement_eat_plant": [0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_iron_pickaxe": [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], "achievement_make_iron_sword": [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], "achievement_make_stone_pickaxe": [1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0], "achievement_make_stone_sword": [1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0], "achievement_make_wood_pickaxe": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_sword": [0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1], "achievement_place_furnace": [0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], "achievement_place_plant": [0, 0, 10, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0], "achievement_place_stone": [2, 0, 31, 0, 18, 1, 1, 2, 1, 2, 0, 1, 6, 1, 4, 1, 3, 3, 1, 0], "achievement_place_table": [1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1], "achievement_wake_up": [2, 0, 8, 0, 2, 1, 1, 1, 1, 0, 0, 0, 3, 1, 0, 0, 0, 2, 1, 0]}, {"task": "crafter-reward", "method": "human", "seed": "2", "xs": [590, 892, 1165, 1469, 1781, 2075, 2667, 4325, 4640, 6295, 6790, 9070, 9977, 12313, 13301, 13967, 14226, 14470, 14891, 15195], "length": [590, 302, 273, 304, 312, 294, 592, 1658, 315, 1655, 495, 2280, 907, 2336, 988, 666, 259, 244, 421, 304], "reward": [12.1, 11.1, 12.1, 11.1, 11.1, 9.1, 14.1, 19.1, 11.1, 21.1, 10.9, 20.1, 15.1, 21.0, 19.1, 17.1, 9.1, 9.0, 11.1, 9.0], "achievement_collect_coal": [2, 3, 3, 1, 1, 1, 4, 10, 2, 2, 0, 9, 6, 4, 5, 2, 0, 3, 1, 2], "achievement_collect_diamond": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], "achievement_collect_drink": [13, 6, 6, 9, 17, 10, 15, 62, 3, 83, 17, 119, 36, 121, 41, 26, 0, 10, 13, 0], "achievement_collect_iron": [4, 1, 1, 4, 0, 0, 0, 6, 2, 5, 0, 4, 0, 9, 2, 2, 0, 0, 0, 0], "achievement_collect_sapling": [0, 0, 0, 0, 1, 3, 3, 3, 0, 5, 4, 7, 2, 8, 2, 2, 0, 1, 0, 0], "achievement_collect_stone": [18, 7, 12, 10, 9, 3, 8, 44, 6, 48, 4, 84, 23, 72, 41, 17, 4, 5, 7, 8], "achievement_collect_wood": [8, 8, 6, 8, 5, 8, 7, 19, 10, 29, 14, 26, 10, 18, 11, 11, 7, 10, 10, 12], "achievement_defeat_skeleton": [0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0, 4, 0, 3, 1, 2, 1, 0, 0, 0], "achievement_defeat_zombie": [1, 2, 2, 0, 1, 0, 2, 8, 0, 11, 4, 12, 1, 14, 1, 3, 2, 0, 1, 0], "achievement_eat_cow": [2, 0, 1, 1, 1, 0, 2, 10, 1, 10, 3, 13, 4, 9, 5, 2, 2, 0, 2, 1], "achievement_eat_plant": [0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 5, 0, 12, 0, 0, 0, 0, 0, 0], "achievement_make_iron_pickaxe": [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 2, 1, 1, 0, 0, 0, 0], "achievement_make_iron_sword": [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0], "achievement_make_stone_pickaxe": [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_stone_sword": [1, 1, 1, 1, 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_pickaxe": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_sword": [0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], "achievement_place_furnace": [0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 2, 1, 1, 0, 0, 0, 0], "achievement_place_plant": [0, 0, 0, 0, 1, 0, 1, 1, 0, 4, 0, 6, 2, 6, 2, 0, 0, 0, 0, 0], "achievement_place_stone": [3, 1, 1, 1, 3, 1, 6, 10, 1, 17, 2, 20, 4, 21, 9, 4, 1, 0, 2, 2], "achievement_place_table": [1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 2, 3, 2, 1, 2, 1, 2, 1], "achievement_wake_up": [1, 1, 1, 1, 0, 1, 1, 5, 1, 5, 1, 7, 3, 10, 3, 2, 0, 0, 1, 1]}, {"task": "crafter-reward", "method": "human", "seed": "3", "xs": [678, 949, 1235, 2043, 2529, 2824, 3154, 3383, 4183, 4699, 5476, 7209, 8520, 8824, 9271, 10096, 10861, 11179, 11483, 12468], "length": [678, 271, 286, 808, 486, 295, 330, 229, 800, 516, 777, 1733, 1311, 304, 447, 825, 765, 318, 304, 985], "reward": [17.1, 10.0, 10.1, 12.0, 12.1, 10.0, 8.9, 9.0, 17.0, 12.1, 13.1, 18.1, 17.0, 12.0, 14.0, 19.0, 18.1, 13.0, 11.1, 19.1], "achievement_collect_coal": [4, 6, 1, 4, 3, 1, 0, 2, 2, 1, 0, 3, 6, 0, 4, 4, 2, 2, 0, 9], "achievement_collect_diamond": [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1], "achievement_collect_drink": [30, 6, 4, 29, 14, 5, 7, 5, 28, 18, 27, 76, 36, 11, 15, 33, 18, 0, 6, 38], "achievement_collect_iron": [2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 3, 2, 1, 0, 3, 2, 1, 2, 4], "achievement_collect_sapling": [5, 3, 0, 3, 4, 0, 1, 0, 0, 4, 3, 3, 3, 3, 2, 6, 3, 0, 0, 1], "achievement_collect_stone": [19, 6, 7, 12, 6, 7, 13, 2, 25, 8, 12, 22, 24, 11, 9, 18, 29, 25, 17, 35], "achievement_collect_wood": [8, 9, 7, 10, 6, 7, 4, 6, 9, 14, 12, 19, 9, 9, 10, 17, 12, 15, 5, 12], "achievement_defeat_skeleton": [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2], "achievement_defeat_zombie": [1, 1, 2, 2, 2, 1, 2, 2, 9, 6, 2, 8, 2, 2, 2, 9, 1, 1, 1, 4], "achievement_eat_cow": [2, 0, 1, 3, 4, 1, 0, 0, 7, 3, 4, 9, 6, 1, 4, 4, 5, 1, 2, 5], "achievement_eat_plant": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0], "achievement_make_iron_pickaxe": [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1], "achievement_make_iron_sword": [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1], "achievement_make_stone_pickaxe": [1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_stone_sword": [1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_pickaxe": [1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "achievement_make_wood_sword": [0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 0, 1], "achievement_place_furnace": [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1], "achievement_place_plant": [4, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 6, 3, 0, 0, 0], "achievement_place_stone": [12, 1, 2, 5, 1, 3, 1, 0, 4, 1, 9, 12, 4, 1, 2, 3, 9, 1, 1, 10], "achievement_place_table": [1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 2], "achievement_wake_up": [1, 1, 1, 2, 1, 0, 1, 0, 2, 1, 1, 6, 4, 0, 1, 2, 2, 0, 1, 4]}, {"task": "crafter-reward", "method": "human", "seed": "4", "xs": [896, 1171, 1609, 1815, 2145, 2844, 3446, 3968, 4922, 6919, 10062, 10351, 10795, 11121, 11876, 12150, 12802, 13109, 13453, 13879], "length": [896, 275, 438, 206, 330, 699, 602, 522, 954, 1997, 3143, 289, 444, 326, 755, 274, 652, 307, 344, 426], "reward": [17.1, 11.1, 14.1, 11.0, 10.1, 10.1, 17.9, 14.0, 12.1, 21.1, 21.1, 10.0, 11.0, 10.1, 12.1, 10.1, 15.0, 11.1, 11.1, 10.1], "achievement_collect_coal": [2, 3, 3, 1, 2, 0, 7, 2, 2, 5, 5, 0, 0, 2, 3, 2, 3, 2, 2, 4], "achievement_collect_diamond": [0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_collect_drink": [34, 9, 21, 2, 9, 22, 26, 20, 31, 115, 130, 4, 13, 10, 31, 0, 17, 8, 8, 10], "achievement_collect_iron": [4, 2, 2, 0, 0, 0, 2, 1, 3, 4, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0], "achievement_collect_sapling": [2, 0, 2, 1, 1, 0, 1, 3, 0, 8, 8, 2, 2, 0, 0, 1, 1, 0, 4, 0], "achievement_collect_stone": [44, 12, 17, 5, 8, 12, 11, 14, 16, 41, 72, 6, 11, 4, 9, 6, 11, 19, 7, 8], "achievement_collect_wood": [14, 5, 9, 8, 12, 8, 13, 9, 10, 11, 20, 8, 15, 4, 9, 12, 9, 7, 12, 9], "achievement_defeat_skeleton": [3, 0, 1, 0, 0, 0, 0, 1, 0, 4, 2, 0, 0, 1, 2, 0, 2, 0, 0, 1], "achievement_defeat_zombie": [4, 2, 1, 2, 1, 1, 0, 2, 2, 8, 6, 1, 2, 0, 2, 1, 3, 2, 2, 2], "achievement_eat_cow": [4, 0, 1, 1, 2, 2, 2, 3, 3, 6, 6, 2, 3, 2, 5, 1, 4, 0, 1, 1], "achievement_eat_plant": [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_iron_pickaxe": [1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_iron_sword": [1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_make_stone_pickaxe": [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0], "achievement_make_stone_sword": [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0], "achievement_make_wood_pickaxe": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1], "achievement_make_wood_sword": [0, 0, 0, 1, 1, 2, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1], "achievement_place_furnace": [1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0], "achievement_place_plant": [0, 0, 0, 0, 0, 0, 1, 0, 0, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0], "achievement_place_stone": [9, 1, 1, 0, 2, 6, 1, 6, 13, 22, 22, 1, 0, 1, 4, 1, 4, 1, 2, 0], "achievement_place_table": [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 2, 1], "achievement_wake_up": [2, 0, 1, 0, 0, 1, 2, 1, 1, 6, 10, 1, 1, 1, 2, 0, 2, 1, 0, 1]}]
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 | import pathlib
3 |
4 |
5 | setuptools.setup(
6 | name='crafter',
7 | version='1.8.3',
8 | description='Open world survival game for reinforcement learning.',
9 | url='http://github.com/danijar/crafter',
10 | long_description=pathlib.Path('README.md').read_text(),
11 | long_description_content_type='text/markdown',
12 | packages=['crafter'],
13 | package_data={'crafter': ['data.yaml', 'assets/*']},
14 | entry_points={'console_scripts': ['crafter=crafter.run_gui:main']},
15 | install_requires=[
16 | 'numpy', 'imageio', 'pillow', 'opensimplex', 'ruamel.yaml',
17 | ],
18 | extras_require={'gui': ['pygame']},
19 | classifiers=[
20 | 'Intended Audience :: Science/Research',
21 | 'License :: OSI Approved :: MIT License',
22 | 'Programming Language :: Python :: 3',
23 | 'Topic :: Games/Entertainment',
24 | 'Topic :: Scientific/Engineering :: Artificial Intelligence',
25 | ],
26 | )
27 |
--------------------------------------------------------------------------------