mettagrid 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +75 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/actions.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/attack.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/gift.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/move.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/noop.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/rotate.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/shield.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/use.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/mettagrid_c.cpython-311-darwin.so +0 -0
- build/lib.macosx-11.0-arm64-cpython-311/mettagrid/objects.cpython-311-darwin.so +0 -0
- build/mettagrid/actions/actions.cpp +33466 -0
- build/mettagrid/actions/attack.cpp +33253 -0
- build/mettagrid/actions/gift.cpp +32378 -0
- build/mettagrid/actions/move.cpp +32552 -0
- build/mettagrid/actions/noop.cpp +32376 -0
- build/mettagrid/actions/rotate.cpp +32394 -0
- build/mettagrid/actions/shield.cpp +32437 -0
- build/mettagrid/actions/use.cpp +32851 -0
- build/mettagrid/mettagrid.cpp +37718 -0
- build/mettagrid/objects.cpp +36083 -0
- mettagrid/actions/actions.cpython-311-darwin.so +0 -0
- mettagrid/actions/actions.pxd +2 -2
- mettagrid/actions/actions.pyx +19 -5
- mettagrid/actions/attack.cpython-311-darwin.so +0 -0
- mettagrid/actions/attack.pyx +12 -3
- mettagrid/actions/gift.cpython-311-darwin.so +0 -0
- mettagrid/actions/gift.pyx +4 -1
- mettagrid/actions/move.cpython-311-darwin.so +0 -0
- mettagrid/actions/move.pyx +16 -6
- mettagrid/actions/noop.cpython-311-darwin.so +0 -0
- mettagrid/actions/noop.pxd +4 -0
- mettagrid/actions/noop.pyx +25 -0
- mettagrid/actions/rotate.cpython-311-darwin.so +0 -0
- mettagrid/actions/rotate.pyx +5 -3
- mettagrid/actions/shield.cpython-311-darwin.so +0 -0
- mettagrid/actions/shield.pyx +10 -3
- mettagrid/actions/use.cpython-311-darwin.so +0 -0
- mettagrid/actions/use.pyx +9 -4
- mettagrid/mettagrid.pyx +35 -1
- mettagrid/mettagrid_c.cpython-311-darwin.so +0 -0
- mettagrid/mettagrid_env.py +44 -72
- mettagrid/objects.cpython-311-darwin.so +0 -0
- mettagrid/objects.pxd +25 -12
- mettagrid/objects.pyx +19 -21
- mettagrid/renderer/assets/agent.png +0 -0
- mettagrid/renderer/assets/altar.png +0 -0
- mettagrid/renderer/assets/arial.ttf +0 -0
- mettagrid/renderer/assets/converter.png +0 -0
- mettagrid/renderer/assets/generator.png +0 -0
- mettagrid/renderer/json_renderer.py +0 -0
- mettagrid/renderer/raylib_renderer.py +410 -0
- mettagrid/renderer/render_code_example.py +56 -0
- mettagrid-0.0.3.dist-info/LICENSE +21 -0
- mettagrid-0.0.3.dist-info/METADATA +96 -0
- mettagrid-0.0.3.dist-info/RECORD +81 -0
- setup.py +91 -0
- test_perf.py +107 -0
- mettagrid/renderer/raylib_client.py +0 -180
- mettagrid-0.0.1.dist-info/METADATA +0 -23
- mettagrid-0.0.1.dist-info/RECORD +0 -38
- /mettagrid-0.0.1.dist-info/LICENSE → /LICENSE +0 -0
- /mettagrid/renderer/assets/{tiny_galaxy_items.png → items.png} +0 -0
- /mettagrid/renderer/assets/{tiny_galaxy_monsters.png → monsters.png} +0 -0
- /mettagrid/renderer/assets/{wall1-0.png → wall.png} +0 -0
- {mettagrid-0.0.1.dist-info → mettagrid-0.0.3.dist-info}/WHEEL +0 -0
mettagrid/objects.pyx
CHANGED
|
@@ -5,7 +5,7 @@ from libcpp.string cimport string
|
|
|
5
5
|
from libcpp.vector cimport vector
|
|
6
6
|
from puffergrid.grid_object cimport GridObject, GridObjectId
|
|
7
7
|
|
|
8
|
-
cdef vector[string] ObjectTypeNames = [
|
|
8
|
+
cdef vector[string] ObjectTypeNames = <vector[string]>[
|
|
9
9
|
"agent",
|
|
10
10
|
"wall",
|
|
11
11
|
"generator",
|
|
@@ -13,7 +13,7 @@ cdef vector[string] ObjectTypeNames = [
|
|
|
13
13
|
"altar"
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
-
cdef vector[string] InventoryItemNames = [
|
|
16
|
+
cdef vector[string] InventoryItemNames = <vector[string]>[
|
|
17
17
|
"r1",
|
|
18
18
|
"r2",
|
|
19
19
|
"r3"
|
|
@@ -30,36 +30,34 @@ ObjectLayers = {
|
|
|
30
30
|
cdef class MettaObservationEncoder(ObservationEncoder):
|
|
31
31
|
def __init__(self) -> None:
|
|
32
32
|
self._offsets.resize(ObjectType.Count)
|
|
33
|
-
|
|
33
|
+
self._type_feature_names.resize(ObjectType.Count)
|
|
34
34
|
features = []
|
|
35
|
-
self._offsets[ObjectType.AgentT] = 0
|
|
36
|
-
features.extend(Agent.feature_names())
|
|
37
|
-
|
|
38
|
-
self._offsets[ObjectType.WallT] = len(features)
|
|
39
|
-
features.extend(Wall.feature_names())
|
|
40
|
-
|
|
41
|
-
self._offsets[ObjectType.GeneratorT] = len(features)
|
|
42
|
-
features.extend(Generator.feature_names())
|
|
43
35
|
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self.
|
|
48
|
-
|
|
36
|
+
self._type_feature_names[ObjectType.AgentT] = Agent.feature_names()
|
|
37
|
+
self._type_feature_names[ObjectType.WallT] = Wall.feature_names()
|
|
38
|
+
self._type_feature_names[ObjectType.GeneratorT] = Generator.feature_names()
|
|
39
|
+
self._type_feature_names[ObjectType.ConverterT] = Converter.feature_names()
|
|
40
|
+
self._type_feature_names[ObjectType.AltarT] = Altar.feature_names()
|
|
49
41
|
|
|
42
|
+
for type_id in range(ObjectType.Count):
|
|
43
|
+
self._offsets[type_id] = len(features)
|
|
44
|
+
features.extend(self._type_feature_names[type_id])
|
|
50
45
|
self._feature_names = features
|
|
51
46
|
|
|
52
47
|
cdef encode(self, GridObject *obj, ObsType[:] obs):
|
|
48
|
+
self._encode(obj, obs, self._offsets[obj._type_id])
|
|
49
|
+
|
|
50
|
+
cdef _encode(self, GridObject *obj, ObsType[:] obs, unsigned int offset):
|
|
53
51
|
if obj._type_id == ObjectType.AgentT:
|
|
54
|
-
(<Agent*>obj).obs(obs[
|
|
52
|
+
(<Agent*>obj).obs(obs[offset:])
|
|
55
53
|
elif obj._type_id == ObjectType.WallT:
|
|
56
|
-
(<Wall*>obj).obs(obs[
|
|
54
|
+
(<Wall*>obj).obs(obs[offset:])
|
|
57
55
|
elif obj._type_id == ObjectType.GeneratorT:
|
|
58
|
-
(<Generator*>obj).obs(obs[
|
|
56
|
+
(<Generator*>obj).obs(obs[offset:])
|
|
59
57
|
elif obj._type_id == ObjectType.ConverterT:
|
|
60
|
-
(<Converter*>obj).obs(obs[
|
|
58
|
+
(<Converter*>obj).obs(obs[offset:])
|
|
61
59
|
elif obj._type_id == ObjectType.AltarT:
|
|
62
|
-
(<Altar*>obj).obs(obs[
|
|
60
|
+
(<Altar*>obj).obs(obs[offset:])
|
|
63
61
|
else:
|
|
64
62
|
printf("Encoding object of unknown type: %d\n", obj._type_id)
|
|
65
63
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# disable pylint for raylib
|
|
2
|
+
# pylint: disable=no-member
|
|
3
|
+
# type: ignore
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pyray as ray
|
|
9
|
+
import torch
|
|
10
|
+
from cffi import FFI
|
|
11
|
+
from omegaconf import OmegaConf
|
|
12
|
+
from raylib import colors, rl
|
|
13
|
+
from types import SimpleNamespace
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Actions = SimpleNamespace(
|
|
17
|
+
Noop = 0,
|
|
18
|
+
Move = 1,
|
|
19
|
+
Rotate = 2,
|
|
20
|
+
Use = 3,
|
|
21
|
+
Attack = 4,
|
|
22
|
+
ToggleShield = 5,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class ObjectRenderer:
|
|
26
|
+
def __init__(self, sprite_sheet, tile_size=24):
|
|
27
|
+
sprites_dir = "deps/mettagrid/mettagrid/renderer/assets/"
|
|
28
|
+
sprite_sheet_path = os.path.join(sprites_dir, sprite_sheet)
|
|
29
|
+
assert os.path.exists(sprite_sheet_path), f"Sprite sheet {sprite_sheet_path} does not exist"
|
|
30
|
+
self.sprite_sheet = rl.LoadTexture(sprite_sheet_path.encode())
|
|
31
|
+
self.tile_size = tile_size
|
|
32
|
+
|
|
33
|
+
def _sprite_sheet_idx(self, obj):
|
|
34
|
+
return (0, 0)
|
|
35
|
+
|
|
36
|
+
def render(self, obj, render_tile_size):
|
|
37
|
+
dest_rect = (
|
|
38
|
+
obj["c"] * render_tile_size, obj["r"] * render_tile_size,
|
|
39
|
+
render_tile_size, render_tile_size)
|
|
40
|
+
tile_idx_x, tile_idx_y = self._sprite_sheet_idx(obj)
|
|
41
|
+
src_rect = (
|
|
42
|
+
tile_idx_x * self.tile_size, tile_idx_y * self.tile_size,
|
|
43
|
+
self.tile_size, self.tile_size)
|
|
44
|
+
|
|
45
|
+
rl.DrawTexturePro(
|
|
46
|
+
self.sprite_sheet, src_rect, dest_rect,
|
|
47
|
+
(0, 0), 0, colors.WHITE)
|
|
48
|
+
|
|
49
|
+
class AgentRenderer(ObjectRenderer):
|
|
50
|
+
def __init__(self, cfg: OmegaConf):
|
|
51
|
+
super().__init__("monsters.png", 16)
|
|
52
|
+
self.cfg = cfg
|
|
53
|
+
|
|
54
|
+
def _sprite_sheet_idx(self, obj):
|
|
55
|
+
# orientation: 0 = Up, 1 = Down, 2 = Left, 3 = Right
|
|
56
|
+
# sprites: 0 = Right, 1 = Up, 2 = Down, 3 = Left
|
|
57
|
+
orientation_offset = [1, 2, 3, 0][obj["agent:orientation"]]
|
|
58
|
+
return (4 * ((obj["agent_id"] // 12) % 4) + orientation_offset, 2 * (obj["agent_id"] % 12))
|
|
59
|
+
|
|
60
|
+
def render(self, obj, render_tile_size):
|
|
61
|
+
super().render(obj, render_tile_size)
|
|
62
|
+
self.draw_energy_bar(obj, render_tile_size)
|
|
63
|
+
# self.draw_hp_bar(obj, render_tile_size)
|
|
64
|
+
self.draw_frozen_effect(obj, render_tile_size)
|
|
65
|
+
self.draw_shield_effect(obj, render_tile_size)
|
|
66
|
+
|
|
67
|
+
def draw_energy_bar(self, obj, render_tile_size):
|
|
68
|
+
x = obj["c"] * render_tile_size
|
|
69
|
+
y = obj["r"] * render_tile_size - 8 # 8 pixels above the agent
|
|
70
|
+
width = render_tile_size
|
|
71
|
+
height = 3 # 3 pixels tall
|
|
72
|
+
max_energy = self.cfg.max_energy
|
|
73
|
+
|
|
74
|
+
energy = min(max(obj["agent:energy"], 0), max_energy)
|
|
75
|
+
blue_width = int(width * energy / max_energy)
|
|
76
|
+
|
|
77
|
+
# Draw red background
|
|
78
|
+
rl.DrawRectangle(x, y, width, height, colors.RED)
|
|
79
|
+
# Draw blue foreground based on energy
|
|
80
|
+
rl.DrawRectangle(x, y, blue_width, height, colors.BLUE)
|
|
81
|
+
|
|
82
|
+
def draw_hp_bar(self, obj, render_tile_size):
|
|
83
|
+
x = obj["c"] * render_tile_size
|
|
84
|
+
y = obj["r"] * render_tile_size - 4 # 4 pixels above the agent, below energy bar
|
|
85
|
+
width = render_tile_size
|
|
86
|
+
height = 3 # 3 pixels tall
|
|
87
|
+
|
|
88
|
+
hp = min(max(obj["agent:hp"], 0), 10) # Clamp between 0 and 10
|
|
89
|
+
green_width = int(width * hp / 10)
|
|
90
|
+
|
|
91
|
+
# Draw red background
|
|
92
|
+
rl.DrawRectangle(x, y, width, height, colors.RED)
|
|
93
|
+
# Draw green foreground based on HP
|
|
94
|
+
rl.DrawRectangle(x, y, green_width, height, colors.GREEN)
|
|
95
|
+
|
|
96
|
+
def draw_frozen_effect(self, obj, render_tile_size):
|
|
97
|
+
frozen = obj.get("agent:frozen", 0)
|
|
98
|
+
if frozen > 0:
|
|
99
|
+
x = obj["c"] * render_tile_size + render_tile_size // 2
|
|
100
|
+
y = obj["r"] * render_tile_size + render_tile_size // 2
|
|
101
|
+
radius = render_tile_size // 2
|
|
102
|
+
|
|
103
|
+
# Calculate alpha based on frozen value
|
|
104
|
+
base_alpha = 102 # 40% of 255
|
|
105
|
+
alpha = int(base_alpha * (frozen / self.cfg.freeze_duration))
|
|
106
|
+
|
|
107
|
+
# Create a semi-transparent gray color
|
|
108
|
+
frozen_color = ray.Color(128, 128, 128, alpha)
|
|
109
|
+
|
|
110
|
+
# Draw the semi-transparent circle
|
|
111
|
+
ray.draw_circle(x, y, radius, frozen_color)
|
|
112
|
+
|
|
113
|
+
def draw_shield_effect(self, obj, render_tile_size):
|
|
114
|
+
if obj.get("agent:shield", False):
|
|
115
|
+
x = obj["c"] * render_tile_size + render_tile_size // 2
|
|
116
|
+
y = obj["r"] * render_tile_size + render_tile_size // 2
|
|
117
|
+
radius = render_tile_size // 2 + 2 # Slightly larger than the agent
|
|
118
|
+
|
|
119
|
+
# Draw a blue circle
|
|
120
|
+
ray.draw_circle_lines(x, y, radius, ray.BLUE)
|
|
121
|
+
|
|
122
|
+
class WallRenderer(ObjectRenderer):
|
|
123
|
+
def __init__(self):
|
|
124
|
+
super().__init__("wall.png")
|
|
125
|
+
|
|
126
|
+
class GeneratorRenderer(ObjectRenderer):
|
|
127
|
+
def __init__(self):
|
|
128
|
+
super().__init__("items.png", 16)
|
|
129
|
+
|
|
130
|
+
def _sprite_sheet_idx(self, obj):
|
|
131
|
+
if obj["generator:ready"]:
|
|
132
|
+
return (14, 2)
|
|
133
|
+
else:
|
|
134
|
+
return (13, 2)
|
|
135
|
+
|
|
136
|
+
class ConverterRenderer(ObjectRenderer):
|
|
137
|
+
def __init__(self):
|
|
138
|
+
super().__init__("items.png", 16)
|
|
139
|
+
|
|
140
|
+
def _sprite_sheet_idx(self, obj):
|
|
141
|
+
if obj["converter:ready"]:
|
|
142
|
+
return (12, 0)
|
|
143
|
+
else:
|
|
144
|
+
return (13, 0)
|
|
145
|
+
class AltarRenderer(ObjectRenderer):
|
|
146
|
+
def __init__(self):
|
|
147
|
+
super().__init__("items.png", 16)
|
|
148
|
+
|
|
149
|
+
def _sprite_sheet_idx(self, obj):
|
|
150
|
+
if obj["altar:ready"]:
|
|
151
|
+
return (11, 2)
|
|
152
|
+
else:
|
|
153
|
+
return (12, 2)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class MettaGridRaylibRenderer:
|
|
157
|
+
def __init__(self, map_width: int, map_height: int, cfg: OmegaConf):
|
|
158
|
+
self.width = map_width
|
|
159
|
+
self.height = map_height
|
|
160
|
+
self.sidebar_width = 250
|
|
161
|
+
self.tile_size = cfg.game.tile_size
|
|
162
|
+
self.cfg = cfg
|
|
163
|
+
|
|
164
|
+
rl.InitWindow(self.width*self.tile_size + self.sidebar_width, self.height*self.tile_size,
|
|
165
|
+
"PufferLib Ray Grid".encode())
|
|
166
|
+
|
|
167
|
+
# Load custom font
|
|
168
|
+
font_path = os.path.join("deps", "mettagrid", "mettagrid", "renderer", "assets", "arial.ttf")
|
|
169
|
+
assert os.path.exists(font_path), f"Font {font_path} does not exist"
|
|
170
|
+
self.font = rl.LoadFont(font_path.encode())
|
|
171
|
+
|
|
172
|
+
self.sprite_renderers = [
|
|
173
|
+
AgentRenderer(cfg.game.objects.agent),
|
|
174
|
+
WallRenderer(),
|
|
175
|
+
GeneratorRenderer(),
|
|
176
|
+
ConverterRenderer(),
|
|
177
|
+
AltarRenderer(),
|
|
178
|
+
]
|
|
179
|
+
rl.SetTargetFPS(10)
|
|
180
|
+
self.colors = colors
|
|
181
|
+
|
|
182
|
+
camera = ray.Camera2D()
|
|
183
|
+
camera.target = ray.Vector2(0.0, 0.0)
|
|
184
|
+
camera.rotation = 0.0
|
|
185
|
+
camera.zoom = 1.0
|
|
186
|
+
self.camera = camera
|
|
187
|
+
|
|
188
|
+
self.ffi = FFI()
|
|
189
|
+
self.selected_object_id = None
|
|
190
|
+
self.selected_agent_idx = None
|
|
191
|
+
self.hover_object_id = None
|
|
192
|
+
self.mind_control = False
|
|
193
|
+
self.paused = False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _cdata_to_numpy(self):
|
|
197
|
+
image = rl.LoadImageFromScreen()
|
|
198
|
+
width, height, channels = image.width, image.height, 4
|
|
199
|
+
cdata = self.ffi.buffer(image.data, width*height*channels)
|
|
200
|
+
return np.frombuffer(cdata, dtype=np.uint8).reshape((height, width, channels))[:, :, :3]
|
|
201
|
+
|
|
202
|
+
def render(self, current_timestep: int, game_objects, actions):
|
|
203
|
+
while True:
|
|
204
|
+
rl.BeginDrawing()
|
|
205
|
+
rl.BeginMode2D(self.camera)
|
|
206
|
+
rl.ClearBackground([6, 24, 24, 255])
|
|
207
|
+
|
|
208
|
+
agents = [None for _ in range(self.cfg.game.num_agents)]
|
|
209
|
+
for obj_id, obj in game_objects.items():
|
|
210
|
+
obj["id"] = obj_id
|
|
211
|
+
self.sprite_renderers[obj["type"]].render(obj, self.tile_size)
|
|
212
|
+
if obj_id == self.selected_object_id:
|
|
213
|
+
self.draw_selection(obj)
|
|
214
|
+
if "agent_id" in obj:
|
|
215
|
+
agents[obj["agent_id"]] = obj
|
|
216
|
+
self.handle_mouse_input(game_objects)
|
|
217
|
+
self.draw_mouse()
|
|
218
|
+
|
|
219
|
+
if self.mind_control and self.selected_agent_idx is not None:
|
|
220
|
+
actions[self.selected_agent_idx][0] = Actions.Noop
|
|
221
|
+
|
|
222
|
+
action = self.get_action()
|
|
223
|
+
if self.selected_agent_idx is not None and action is not None:
|
|
224
|
+
actions[self.selected_agent_idx][0] = action[0]
|
|
225
|
+
actions[self.selected_agent_idx][1] = action[1]
|
|
226
|
+
|
|
227
|
+
self.draw_attacks(game_objects, actions, agents)
|
|
228
|
+
|
|
229
|
+
rl.EndMode2D()
|
|
230
|
+
self.render_sidebar(current_timestep, game_objects)
|
|
231
|
+
rl.EndDrawing()
|
|
232
|
+
|
|
233
|
+
if not self.paused or action is not None:
|
|
234
|
+
return {
|
|
235
|
+
"cdata": self._cdata_to_numpy(),
|
|
236
|
+
"actions": actions
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def handle_mouse_input(self, game_objects):
|
|
241
|
+
pos = ray.get_mouse_position()
|
|
242
|
+
grid_x = int(pos.x // self.tile_size)
|
|
243
|
+
grid_y = int(pos.y // self.tile_size)
|
|
244
|
+
|
|
245
|
+
self.hover_object_id = None
|
|
246
|
+
for obj_id, obj in game_objects.items():
|
|
247
|
+
if obj["c"] == grid_x and obj["r"] == grid_y:
|
|
248
|
+
self.hover_object_id = obj_id
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
if ray.is_mouse_button_pressed(ray.MOUSE_LEFT_BUTTON):
|
|
252
|
+
self.selected_object_id = self.hover_object_id
|
|
253
|
+
if self.selected_object_id is not None and "agent_id" in game_objects[self.selected_object_id]:
|
|
254
|
+
self.selected_agent_idx = game_objects[self.selected_object_id]["agent_id"]
|
|
255
|
+
|
|
256
|
+
def render_sidebar(self, current_timestep, game_objects):
|
|
257
|
+
font_size = 14
|
|
258
|
+
sidebar_x = int(self.width * self.tile_size)
|
|
259
|
+
sidebar_height = int(self.height * self.tile_size)
|
|
260
|
+
rl.DrawRectangle(sidebar_x, 0, self.sidebar_width, sidebar_height, colors.DARKGRAY)
|
|
261
|
+
|
|
262
|
+
y = 10
|
|
263
|
+
line_height = font_size + 4
|
|
264
|
+
|
|
265
|
+
def draw_object_info(title, obj_id, color):
|
|
266
|
+
nonlocal y
|
|
267
|
+
if obj_id and obj_id in game_objects:
|
|
268
|
+
obj = game_objects[obj_id]
|
|
269
|
+
rl.DrawTextEx(self.font, f"{title}:".encode(),
|
|
270
|
+
(sidebar_x + 10, y), font_size + 2, 1, color)
|
|
271
|
+
y += line_height * 2
|
|
272
|
+
|
|
273
|
+
for key, value in obj.items():
|
|
274
|
+
if ":" in key:
|
|
275
|
+
key = ":".join(key.split(":")[1:])
|
|
276
|
+
text = f"{key}: {value}"
|
|
277
|
+
if len(text) > 25:
|
|
278
|
+
text = text[:22] + "..."
|
|
279
|
+
rl.DrawTextEx(self.font, text.encode(),
|
|
280
|
+
(sidebar_x + 10, y), font_size, 1, colors.WHITE)
|
|
281
|
+
y += line_height
|
|
282
|
+
|
|
283
|
+
y += line_height
|
|
284
|
+
rl.DrawLine(sidebar_x + 5, y, sidebar_x + self.sidebar_width - 5, y, colors.LIGHTGRAY)
|
|
285
|
+
y += line_height
|
|
286
|
+
|
|
287
|
+
mc = "(locked)" if self.mind_control else ""
|
|
288
|
+
draw_object_info("Selected" + mc, self.selected_object_id, colors.YELLOW)
|
|
289
|
+
draw_object_info("Hover", self.hover_object_id, colors.GREEN)
|
|
290
|
+
|
|
291
|
+
# Display current timestep at the bottom of the sidebar
|
|
292
|
+
timestep_text = f"Timestep: {current_timestep}"
|
|
293
|
+
rl.DrawTextEx(self.font, timestep_text.encode(),
|
|
294
|
+
(sidebar_x + 10, sidebar_height - 30), font_size, 1, colors.WHITE)
|
|
295
|
+
|
|
296
|
+
def draw_selection(self, obj):
|
|
297
|
+
x, y = obj["c"] * self.tile_size, obj["r"] * self.tile_size
|
|
298
|
+
color = ray.GREEN if self.mind_control else ray.LIGHTGRAY
|
|
299
|
+
ray.draw_rectangle_lines(x, y, self.tile_size, self.tile_size, color)
|
|
300
|
+
|
|
301
|
+
def draw_attacks(self, objects, actions, agents):
|
|
302
|
+
for agent_id, action in enumerate(actions):
|
|
303
|
+
if action[0] != Actions.Attack:
|
|
304
|
+
continue
|
|
305
|
+
agent = agents[agent_id]
|
|
306
|
+
if agent["agent:energy"] < self.cfg.game.actions.attack.cost:
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
distance = 1 + (action[1] - 1) // 3
|
|
310
|
+
offset = -((action[1] - 1) % 3 - 1)
|
|
311
|
+
target_loc = self._relative_location(
|
|
312
|
+
agent["r"], agent["c"], agent["agent:orientation"], distance, offset)
|
|
313
|
+
|
|
314
|
+
# Draw red rectangle around target
|
|
315
|
+
ray.draw_circle_lines(
|
|
316
|
+
target_loc[1] * self.tile_size + self.tile_size // 2,
|
|
317
|
+
target_loc[0] * self.tile_size + self.tile_size // 2,
|
|
318
|
+
self.tile_size * 0.2,
|
|
319
|
+
ray.RED
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Draw red line from attacker to target
|
|
323
|
+
start_x = agent["c"] * self.tile_size + self.tile_size // 2
|
|
324
|
+
start_y = agent["r"] * self.tile_size + self.tile_size // 2
|
|
325
|
+
end_x = target_loc[1] * self.tile_size + self.tile_size // 2
|
|
326
|
+
end_y = target_loc[0] * self.tile_size + self.tile_size // 2
|
|
327
|
+
ray.draw_line(int(start_x), int(start_y), int(end_x), int(end_y), ray.RED)
|
|
328
|
+
|
|
329
|
+
def get_action(self):
|
|
330
|
+
if rl.IsKeyDown(rl.KEY_ESCAPE):
|
|
331
|
+
sys.exit(0)
|
|
332
|
+
|
|
333
|
+
key_actions = {
|
|
334
|
+
# move
|
|
335
|
+
rl.KEY_E: (Actions.Move, 0),
|
|
336
|
+
rl.KEY_Q: (Actions.Move, 1),
|
|
337
|
+
# rotate
|
|
338
|
+
rl.KEY_W: (Actions.Rotate, 0),
|
|
339
|
+
rl.KEY_S: (Actions.Rotate, 1),
|
|
340
|
+
rl.KEY_A: (Actions.Rotate, 2),
|
|
341
|
+
rl.KEY_D: (Actions.Rotate, 3),
|
|
342
|
+
# use
|
|
343
|
+
rl.KEY_U: (Actions.Use, 0),
|
|
344
|
+
# attack
|
|
345
|
+
rl.KEY_KP_1: (Actions.Attack, 1), # KEY_1
|
|
346
|
+
rl.KEY_KP_2: (Actions.Attack, 2), # KEY_2
|
|
347
|
+
rl.KEY_KP_3: (Actions.Attack, 3), # KEY_3
|
|
348
|
+
rl.KEY_KP_4: (Actions.Attack, 4), # KEY_4
|
|
349
|
+
rl.KEY_KP_5: (Actions.Attack, 5), # KEY_5
|
|
350
|
+
rl.KEY_KP_6: (Actions.Attack, 6), # KEY_6
|
|
351
|
+
rl.KEY_KP_7: (Actions.Attack, 7), # KEY_7
|
|
352
|
+
rl.KEY_KP_8: (Actions.Attack, 8), # KEY_8
|
|
353
|
+
rl.KEY_KP_9: (Actions.Attack, 9), # KEY_9
|
|
354
|
+
# toggle shield
|
|
355
|
+
rl.KEY_O: (Actions.ToggleShield, 0),
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
for key, action in key_actions.items():
|
|
359
|
+
if rl.IsKeyDown(key):
|
|
360
|
+
return action
|
|
361
|
+
|
|
362
|
+
if rl.IsKeyDown(rl.KEY_GRAVE) and self.selected_object_id is not None:
|
|
363
|
+
self.mind_control = not self.mind_control
|
|
364
|
+
|
|
365
|
+
if rl.IsKeyDown(rl.KEY_SPACE):
|
|
366
|
+
self.paused = not self.paused
|
|
367
|
+
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def draw_mouse(self):
|
|
372
|
+
ts = self.tile_size
|
|
373
|
+
pos = ray.get_mouse_position()
|
|
374
|
+
mouse_x = int(pos.x // ts)
|
|
375
|
+
mouse_y = int(pos.y // ts)
|
|
376
|
+
|
|
377
|
+
# Draw border around the tile
|
|
378
|
+
ray.draw_rectangle_lines(mouse_x * ts, mouse_y * ts, ts, ts, ray.GRAY)
|
|
379
|
+
|
|
380
|
+
def __del__(self):
|
|
381
|
+
# Unload the font when the object is destroyed
|
|
382
|
+
rl.UnloadFont(self.font)
|
|
383
|
+
|
|
384
|
+
def _selected_agent(self, objects):
|
|
385
|
+
if self.selected_object_id is None:
|
|
386
|
+
return None
|
|
387
|
+
if "agent" not in objects[self.selected_object_id]:
|
|
388
|
+
return None
|
|
389
|
+
return objects[self.selected_object_id]
|
|
390
|
+
|
|
391
|
+
def _relative_location(self, r, c, orientation, distance, offset):
|
|
392
|
+
new_r = r
|
|
393
|
+
new_c = c
|
|
394
|
+
|
|
395
|
+
if orientation == 0:
|
|
396
|
+
new_r = r - distance
|
|
397
|
+
new_c = c + offset
|
|
398
|
+
elif orientation == 1:
|
|
399
|
+
new_r = r + distance
|
|
400
|
+
new_c = c - offset
|
|
401
|
+
elif orientation == 2:
|
|
402
|
+
new_r = r + offset
|
|
403
|
+
new_c = c - distance
|
|
404
|
+
elif orientation == 3:
|
|
405
|
+
new_r = r - offset
|
|
406
|
+
new_c = c + distance
|
|
407
|
+
|
|
408
|
+
new_r = max(0, new_r)
|
|
409
|
+
new_c = max(0, new_c)
|
|
410
|
+
return (new_r, new_c)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
# def draw_bars(rl, entity, x, y, width, height=4, draw_text=False):
|
|
3
|
+
# health_bar = entity.health / entity.max_health
|
|
4
|
+
# mana_bar = entity.mana / entity.max_mana
|
|
5
|
+
# if entity.max_health == 0:
|
|
6
|
+
# health_bar = 2
|
|
7
|
+
# if entity.max_mana == 0:
|
|
8
|
+
# mana_bar = 2
|
|
9
|
+
# rl.DrawRectangle(x, y, width, height, [255, 0, 0, 255])
|
|
10
|
+
# rl.DrawRectangle(x, y, int(width*health_bar), height, [0, 255, 0, 255])
|
|
11
|
+
|
|
12
|
+
# if entity.entity_type == 0:
|
|
13
|
+
# rl.DrawRectangle(x, y - height - 2, width, height, [255, 0, 0, 255])
|
|
14
|
+
# rl.DrawRectangle(x, y - height - 2, int(width*mana_bar), height, [0, 255, 255, 255])
|
|
15
|
+
|
|
16
|
+
# if draw_text:
|
|
17
|
+
# health = int(entity.health)
|
|
18
|
+
# mana = int(entity.mana)
|
|
19
|
+
# max_health = int(entity.max_health)
|
|
20
|
+
# max_mana = int(entity.max_mana)
|
|
21
|
+
# rl.DrawText(f'Health: {health}/{max_health}'.encode(),
|
|
22
|
+
# x+8, y+2, 20, [255, 255, 255, 255])
|
|
23
|
+
# rl.DrawText(f'Mana: {mana}/{max_mana}'.encode(),
|
|
24
|
+
# x+8, y+2 - height - 2, 20, [255, 255, 255, 255])
|
|
25
|
+
|
|
26
|
+
# #rl.DrawRectangle(x, y - 2*height - 4, int(width*mana_bar), height, [255, 255, 0, 255])
|
|
27
|
+
# rl.DrawText(f'Experience: {entity.xp}'.encode(),
|
|
28
|
+
# x+8, y - 2*height - 4, 20, [255, 255, 255, 255])
|
|
29
|
+
|
|
30
|
+
# elif entity.entity_type == 0:
|
|
31
|
+
# rl.DrawText(f'Level: {entity.level}'.encode(),
|
|
32
|
+
# x+4, y -2*height - 12, 12, [255, 255, 255, 255])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Draw HUD
|
|
37
|
+
# player = entities[0]
|
|
38
|
+
# hud_y = self.height*ts - 2*ts
|
|
39
|
+
# draw_bars(rl, player, 2*ts, hud_y, 10*ts, 24, draw_text=True)
|
|
40
|
+
|
|
41
|
+
# off_color = [255, 255, 255, 255]
|
|
42
|
+
# on_color = [0, 255, 0, 255]
|
|
43
|
+
|
|
44
|
+
# q_color = on_color if skill_q else off_color
|
|
45
|
+
# w_color = on_color if skill_w else off_color
|
|
46
|
+
# e_color = on_color if skill_e else off_color
|
|
47
|
+
|
|
48
|
+
# q_cd = player.q_timer
|
|
49
|
+
# w_cd = player.w_timer
|
|
50
|
+
# e_cd = player.e_timer
|
|
51
|
+
|
|
52
|
+
# rl.DrawText(f'Q: {q_cd}'.encode(), 13*ts, hud_y - 20, 40, q_color)
|
|
53
|
+
# rl.DrawText(f'W: {w_cd}'.encode(), 17*ts, hud_y - 20, 40, w_color)
|
|
54
|
+
# rl.DrawText(f'E: {e_cd}'.encode(), 21*ts, hud_y - 20, 40, e_color)
|
|
55
|
+
# rl.DrawText(f'Stun: {player.stun_timer}'.encode(), 25*ts, hud_y - 20, 20, e_color)
|
|
56
|
+
# rl.DrawText(f'Move: {player.move_timer}'.encode(), 25*ts, hud_y, 20, e_color)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 David Bloomin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mettagrid
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A fast grid-based open-ended MARL environment
|
|
5
|
+
Home-page: https://daveey.github.io
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: puffergrid,gridworld,minigrid,rl,reinforcement-learning,environment,gym
|
|
8
|
+
Author: David Bloomin
|
|
9
|
+
Author-email: daveey@gmail.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Dist: cython (>=3.0.11,<4.0.0)
|
|
17
|
+
Requires-Dist: numpy (>=1.21.0,<2.0.0)
|
|
18
|
+
Project-URL: Repository, https://github.com/Metta-AI/mettagrid
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# MettaGrid Environment
|
|
22
|
+
|
|
23
|
+
MettaGrid is a multi-agent gridworld environment for studying the emergence of cooperation and social behaviors in reinforcement learning agents. The environment features a variety of objects and actions that agents can interact with to manage resources, engage in combat, share with others, and optimize their rewards.
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
In MettaGrid, agents navigate a gridworld and interact with various objects to manage their energy, harvest resources, engage in combat, and cooperate with other agents. The key dynamics include:
|
|
28
|
+
|
|
29
|
+
- **Energy Management**: Agents must efficiently manage their energy, which is required for all actions. They can harvest resources and convert them to energy at charger stations.
|
|
30
|
+
- **Resource Gathering**: Agents can gather resources from generator objects scattered throughout the environment.
|
|
31
|
+
- **Cooperation and Sharing**: Agents have the ability to share resources with other agents and use energy to power the heart altar, which provides rewards.
|
|
32
|
+
- **Combat**: Agents can attack other agents to temporarily freeze them and steal their resources. They can also use shields to defend against attacks.
|
|
33
|
+
|
|
34
|
+
The environment is highly configurable, allowing for experimentation with different world layouts, object placements, and agent capabilities.
|
|
35
|
+
|
|
36
|
+
## Objects
|
|
37
|
+
|
|
38
|
+
### Agent
|
|
39
|
+
|
|
40
|
+
<img src="https://github.com/daveey/Griddly/blob/develop/resources/images/oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_astronaut_u1.png?raw=true" width="32"/>
|
|
41
|
+
|
|
42
|
+
The `Agent` object represents an individual agent in the environment. Agents can move, rotate, attack, and interact with other objects. Each agent has energy, resources, and shield properties that govern its abilities and interactions.
|
|
43
|
+
|
|
44
|
+
### Altar
|
|
45
|
+
|
|
46
|
+
<img src="https://github.com/daveey/Griddly/blob/develop/resources/images/oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_heart_full.png?raw=true" width="32"/>
|
|
47
|
+
|
|
48
|
+
The `Altar` object allows agents to spend energy to gain rewards. Agents can power the altar by using the `use` action when near it. The altar has a cooldown period between uses.
|
|
49
|
+
|
|
50
|
+
### Converter
|
|
51
|
+
|
|
52
|
+
<img src="https://github.com/daveey/Griddly/blob/develop/resources/images/oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_pda_A.png?raw=true" width="32"/>
|
|
53
|
+
|
|
54
|
+
The `Converter` object allows agents to convert their harvested resources into energy. Agents can use converters by moving to them and taking the `use` action. Each use of a converter provides a specified amount of energy and has a cooldown period.
|
|
55
|
+
|
|
56
|
+
### Generator
|
|
57
|
+
|
|
58
|
+
<img src="https://github.com/daveey/Griddly/blob/develop/resources/images/oryx/oryx_fantasy/ore-0.png?raw=true" width="32"/>
|
|
59
|
+
|
|
60
|
+
The `Generator` object produces resources that agents can harvest. Agents can gather resources from generators by moving to them and taking the `use` action. Generators have a specified capacity and replenish resources over time.
|
|
61
|
+
|
|
62
|
+
### Wall
|
|
63
|
+
|
|
64
|
+
<img src="https://github.com/daveey/Griddly/blob/develop/resources/images/oryx/oryx_fantasy/wall2-0.png?raw=true" width="32"/>
|
|
65
|
+
|
|
66
|
+
The `Wall` object acts as an impassable barrier in the environment, restricting agent movement.
|
|
67
|
+
|
|
68
|
+
## Actions
|
|
69
|
+
|
|
70
|
+
### Move / Rotate
|
|
71
|
+
|
|
72
|
+
The `move` action allows agents to move to an adjacent cell in the gridworld. The action has two modes: moving forward and moving backward relative to the agent's current orientation.
|
|
73
|
+
|
|
74
|
+
The `rotate` action enables agents to change their orientation within the gridworld. Agents can rotate to face in four directions: down, left, right, and up.
|
|
75
|
+
|
|
76
|
+
### Attack
|
|
77
|
+
|
|
78
|
+
The `attack` action allows agents to attack other agents or objects within their attack range. Successful attacks temporarily freeze the target and may allow the attacker to steal resources.
|
|
79
|
+
|
|
80
|
+
### Shield (Toggle)
|
|
81
|
+
|
|
82
|
+
The `shield` action turns on a shield. When the shield is active, the agent is protected from attacks by other agents. The shield consumes energy while active. Attack damage is subtracted from the agent's energy, rather than freezing the agent.
|
|
83
|
+
|
|
84
|
+
### Transfer
|
|
85
|
+
|
|
86
|
+
The `transfer` action enables agents to share resources with other agents. Agents can choose to transfer specific resources to another agent in an adjacent cell.
|
|
87
|
+
|
|
88
|
+
### Use
|
|
89
|
+
|
|
90
|
+
The `use` action allows agents to interact with objects such as altars, converters, and generators. The specific effects of the `use` action depend on the target object and can include converting resources to energy, powering the altar for rewards, or harvesting resources from generators.
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
The MettaGrid environment is highly configurable through the use of YAML configuration files. These files specify the layout of the gridworld, the placement of objects, and various properties of the objects and agents.
|
|
95
|
+
|
|
96
|
+
|