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.
Files changed (66) hide show
  1. README.md +75 -0
  2. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/actions.cpython-311-darwin.so +0 -0
  3. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/attack.cpython-311-darwin.so +0 -0
  4. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/gift.cpython-311-darwin.so +0 -0
  5. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/move.cpython-311-darwin.so +0 -0
  6. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/noop.cpython-311-darwin.so +0 -0
  7. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/rotate.cpython-311-darwin.so +0 -0
  8. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/shield.cpython-311-darwin.so +0 -0
  9. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/actions/use.cpython-311-darwin.so +0 -0
  10. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/mettagrid_c.cpython-311-darwin.so +0 -0
  11. build/lib.macosx-11.0-arm64-cpython-311/mettagrid/objects.cpython-311-darwin.so +0 -0
  12. build/mettagrid/actions/actions.cpp +33466 -0
  13. build/mettagrid/actions/attack.cpp +33253 -0
  14. build/mettagrid/actions/gift.cpp +32378 -0
  15. build/mettagrid/actions/move.cpp +32552 -0
  16. build/mettagrid/actions/noop.cpp +32376 -0
  17. build/mettagrid/actions/rotate.cpp +32394 -0
  18. build/mettagrid/actions/shield.cpp +32437 -0
  19. build/mettagrid/actions/use.cpp +32851 -0
  20. build/mettagrid/mettagrid.cpp +37718 -0
  21. build/mettagrid/objects.cpp +36083 -0
  22. mettagrid/actions/actions.cpython-311-darwin.so +0 -0
  23. mettagrid/actions/actions.pxd +2 -2
  24. mettagrid/actions/actions.pyx +19 -5
  25. mettagrid/actions/attack.cpython-311-darwin.so +0 -0
  26. mettagrid/actions/attack.pyx +12 -3
  27. mettagrid/actions/gift.cpython-311-darwin.so +0 -0
  28. mettagrid/actions/gift.pyx +4 -1
  29. mettagrid/actions/move.cpython-311-darwin.so +0 -0
  30. mettagrid/actions/move.pyx +16 -6
  31. mettagrid/actions/noop.cpython-311-darwin.so +0 -0
  32. mettagrid/actions/noop.pxd +4 -0
  33. mettagrid/actions/noop.pyx +25 -0
  34. mettagrid/actions/rotate.cpython-311-darwin.so +0 -0
  35. mettagrid/actions/rotate.pyx +5 -3
  36. mettagrid/actions/shield.cpython-311-darwin.so +0 -0
  37. mettagrid/actions/shield.pyx +10 -3
  38. mettagrid/actions/use.cpython-311-darwin.so +0 -0
  39. mettagrid/actions/use.pyx +9 -4
  40. mettagrid/mettagrid.pyx +35 -1
  41. mettagrid/mettagrid_c.cpython-311-darwin.so +0 -0
  42. mettagrid/mettagrid_env.py +44 -72
  43. mettagrid/objects.cpython-311-darwin.so +0 -0
  44. mettagrid/objects.pxd +25 -12
  45. mettagrid/objects.pyx +19 -21
  46. mettagrid/renderer/assets/agent.png +0 -0
  47. mettagrid/renderer/assets/altar.png +0 -0
  48. mettagrid/renderer/assets/arial.ttf +0 -0
  49. mettagrid/renderer/assets/converter.png +0 -0
  50. mettagrid/renderer/assets/generator.png +0 -0
  51. mettagrid/renderer/json_renderer.py +0 -0
  52. mettagrid/renderer/raylib_renderer.py +410 -0
  53. mettagrid/renderer/render_code_example.py +56 -0
  54. mettagrid-0.0.3.dist-info/LICENSE +21 -0
  55. mettagrid-0.0.3.dist-info/METADATA +96 -0
  56. mettagrid-0.0.3.dist-info/RECORD +81 -0
  57. setup.py +91 -0
  58. test_perf.py +107 -0
  59. mettagrid/renderer/raylib_client.py +0 -180
  60. mettagrid-0.0.1.dist-info/METADATA +0 -23
  61. mettagrid-0.0.1.dist-info/RECORD +0 -38
  62. /mettagrid-0.0.1.dist-info/LICENSE → /LICENSE +0 -0
  63. /mettagrid/renderer/assets/{tiny_galaxy_items.png → items.png} +0 -0
  64. /mettagrid/renderer/assets/{tiny_galaxy_monsters.png → monsters.png} +0 -0
  65. /mettagrid/renderer/assets/{wall1-0.png → wall.png} +0 -0
  66. {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._offsets[ObjectType.ConverterT] = len(features)
45
- features.extend(Converter.feature_names())
46
-
47
- self._offsets[ObjectType.AltarT] = len(features)
48
- features.extend(Altar.feature_names())
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[self._offsets[ObjectType.AgentT]:])
52
+ (<Agent*>obj).obs(obs[offset:])
55
53
  elif obj._type_id == ObjectType.WallT:
56
- (<Wall*>obj).obs(obs[self._offsets[ObjectType.WallT]:])
54
+ (<Wall*>obj).obs(obs[offset:])
57
55
  elif obj._type_id == ObjectType.GeneratorT:
58
- (<Generator*>obj).obs(obs[self._offsets[ObjectType.GeneratorT]:])
56
+ (<Generator*>obj).obs(obs[offset:])
59
57
  elif obj._type_id == ObjectType.ConverterT:
60
- (<Converter*>obj).obs(obs[self._offsets[ObjectType.ConverterT]:])
58
+ (<Converter*>obj).obs(obs[offset:])
61
59
  elif obj._type_id == ObjectType.AltarT:
62
- (<Altar*>obj).obs(obs[self._offsets[ObjectType.AltarT]:])
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
+