mettagrid 0.0.1__tar.gz
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.
Potentially problematic release.
This version of mettagrid might be problematic. Click here for more details.
- mettagrid-0.0.1/LICENSE +21 -0
- mettagrid-0.0.1/PKG-INFO +23 -0
- mettagrid-0.0.1/README.md +2 -0
- mettagrid-0.0.1/mettagrid/__init__.py +0 -0
- mettagrid-0.0.1/mettagrid/actions/__init__.py +0 -0
- mettagrid-0.0.1/mettagrid/actions/actions.pxd +31 -0
- mettagrid-0.0.1/mettagrid/actions/actions.pyx +59 -0
- mettagrid-0.0.1/mettagrid/actions/attack.pxd +6 -0
- mettagrid-0.0.1/mettagrid/actions/attack.pyx +61 -0
- mettagrid-0.0.1/mettagrid/actions/gift.pxd +4 -0
- mettagrid-0.0.1/mettagrid/actions/gift.pyx +24 -0
- mettagrid-0.0.1/mettagrid/actions/move.pxd +4 -0
- mettagrid-0.0.1/mettagrid/actions/move.pyx +31 -0
- mettagrid-0.0.1/mettagrid/actions/rotate.pxd +4 -0
- mettagrid-0.0.1/mettagrid/actions/rotate.pyx +28 -0
- mettagrid-0.0.1/mettagrid/actions/shield.pxd +4 -0
- mettagrid-0.0.1/mettagrid/actions/shield.pyx +26 -0
- mettagrid-0.0.1/mettagrid/actions/use.pxd +4 -0
- mettagrid-0.0.1/mettagrid/actions/use.pyx +66 -0
- mettagrid-0.0.1/mettagrid/config/game_builder.py +101 -0
- mettagrid-0.0.1/mettagrid/config/sample_config.py +27 -0
- mettagrid-0.0.1/mettagrid/mettagrid.pyx +78 -0
- mettagrid-0.0.1/mettagrid/mettagrid_env.py +181 -0
- mettagrid-0.0.1/mettagrid/objects.pxd +197 -0
- mettagrid-0.0.1/mettagrid/objects.pyx +67 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-0.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-1.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-2.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-3.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-4.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-5.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/ore-6.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/puffer_chars.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/tiny_galaxy_items.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/tiny_galaxy_monsters.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/assets/wall1-0.png +0 -0
- mettagrid-0.0.1/mettagrid/renderer/raylib_client.py +180 -0
- mettagrid-0.0.1/pyproject.toml +22 -0
mettagrid-0.0.1/LICENSE
ADDED
|
@@ -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.
|
mettagrid-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mettagrid
|
|
3
|
+
Version: 0.0.1
|
|
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
|
|
22
|
+
A fast grid-based open-ended MARL environment
|
|
23
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
from libcpp.string cimport string
|
|
4
|
+
from libcpp.map cimport map
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport TypeId, GridObjectId
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport Agent
|
|
9
|
+
|
|
10
|
+
cdef struct StatNames:
|
|
11
|
+
string action
|
|
12
|
+
string action_energy
|
|
13
|
+
map[TypeId, string] target
|
|
14
|
+
map[TypeId, string] target_energy
|
|
15
|
+
|
|
16
|
+
cdef class MettaActionHandler(ActionHandler):
|
|
17
|
+
cdef StatNames _stats
|
|
18
|
+
cdef string action_name
|
|
19
|
+
cdef int action_cost
|
|
20
|
+
|
|
21
|
+
cdef char handle_action(
|
|
22
|
+
self,
|
|
23
|
+
unsigned int actor_id,
|
|
24
|
+
GridObjectId actor_object_id,
|
|
25
|
+
ActionArg arg)
|
|
26
|
+
|
|
27
|
+
cdef char _handle_action(
|
|
28
|
+
self,
|
|
29
|
+
unsigned int actor_id,
|
|
30
|
+
Agent * actor,
|
|
31
|
+
ActionArg arg)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport GridObjectId
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport Agent, ObjectTypeNames
|
|
9
|
+
|
|
10
|
+
cdef extern from "<string>" namespace "std":
|
|
11
|
+
string to_string(int val)
|
|
12
|
+
|
|
13
|
+
cdef class MettaActionHandler(ActionHandler):
|
|
14
|
+
def __init__(self, cfg: OmegaConf, action_name, action_cost=0):
|
|
15
|
+
self.action_name = action_name
|
|
16
|
+
|
|
17
|
+
self._stats.action = "action." + action_name
|
|
18
|
+
self._stats.action_energy = "action." + action_name + ".energy"
|
|
19
|
+
|
|
20
|
+
for t, n in enumerate(ObjectTypeNames):
|
|
21
|
+
self._stats.target[t] = self._stats.action + "." + n
|
|
22
|
+
self._stats.target_energy[t] = self._stats.action_energy + "." + n
|
|
23
|
+
|
|
24
|
+
self.action_cost = cfg.cost
|
|
25
|
+
|
|
26
|
+
cdef char handle_action(
|
|
27
|
+
self,
|
|
28
|
+
unsigned int actor_id,
|
|
29
|
+
GridObjectId actor_object_id,
|
|
30
|
+
ActionArg arg):
|
|
31
|
+
|
|
32
|
+
cdef Agent *actor = <Agent*>self.env._grid.object(actor_object_id)
|
|
33
|
+
|
|
34
|
+
if actor.frozen:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
if actor.energy < self.action_cost:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
actor.energy -= self.action_cost
|
|
41
|
+
self.env._stats.agent_add(actor_id, self._stats.action_energy.c_str(), self.action_cost)
|
|
42
|
+
|
|
43
|
+
cdef char result = self._handle_action(actor_id, actor, arg)
|
|
44
|
+
|
|
45
|
+
if result:
|
|
46
|
+
self.env._stats.agent_incr(actor_id, self._stats.action.c_str())
|
|
47
|
+
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
cdef char _handle_action(
|
|
51
|
+
self,
|
|
52
|
+
unsigned int actor_id,
|
|
53
|
+
Agent * actor,
|
|
54
|
+
ActionArg arg):
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
from omegaconf import OmegaConf
|
|
4
|
+
|
|
5
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, Orientation, GridObject
|
|
6
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
7
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
8
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
9
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
10
|
+
|
|
11
|
+
cdef class Attack(MettaActionHandler):
|
|
12
|
+
def __init__(self, cfg: OmegaConf):
|
|
13
|
+
MettaActionHandler.__init__(self, cfg, "attack")
|
|
14
|
+
|
|
15
|
+
cdef char _handle_action(
|
|
16
|
+
self,
|
|
17
|
+
unsigned int actor_id,
|
|
18
|
+
Agent * actor,
|
|
19
|
+
ActionArg arg):
|
|
20
|
+
|
|
21
|
+
if arg > 9 or arg < 1:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
cdef short distance = 0
|
|
25
|
+
cdef short offset = 0
|
|
26
|
+
distance = 1 + (arg - 1) // 3
|
|
27
|
+
offset = (arg - 1) % 3 - 1
|
|
28
|
+
|
|
29
|
+
cdef GridLocation target_loc = self.env._grid.relative_location(
|
|
30
|
+
actor.location,
|
|
31
|
+
<Orientation>actor.orientation,
|
|
32
|
+
distance, offset)
|
|
33
|
+
|
|
34
|
+
target_loc.layer = GridLayer.Agent_Layer
|
|
35
|
+
cdef Agent * agent_target = <Agent *>self.env._grid.object_at(target_loc)
|
|
36
|
+
if agent_target:
|
|
37
|
+
self.env._stats.agent_incr(actor_id, self._stats.target[agent_target._type_id].c_str())
|
|
38
|
+
if agent_target.shield and agent_target.energy >= self.damage:
|
|
39
|
+
agent_target.energy -= self.damage
|
|
40
|
+
self.env._stats.agent_add(actor_id, "shield_damage", self.damage)
|
|
41
|
+
else:
|
|
42
|
+
self.env._stats.agent_add(actor_id, "shield_damage", agent_target.energy)
|
|
43
|
+
agent_target.energy = 0
|
|
44
|
+
agent_target.shield = False
|
|
45
|
+
agent_target.frozen = True
|
|
46
|
+
self.env._stats.agent_incr(actor_id, "attack.frozen")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
target_loc.layer = GridLayer.Object_Layer
|
|
50
|
+
cdef MettaObject * object_target = <MettaObject *>self.env._grid.object_at(target_loc)
|
|
51
|
+
if object_target:
|
|
52
|
+
self.env._stats.agent_incr(actor_id, self._stats.target[object_target._type_id].c_str())
|
|
53
|
+
object_target.hp -= 1
|
|
54
|
+
self.env._stats.agent_incr(actor_id, "damage." + ObjectTypeNames[object_target._type_id])
|
|
55
|
+
if object_target.hp <= 0:
|
|
56
|
+
self.env._grid.remove_object(object_target)
|
|
57
|
+
self.env._stats.agent_incr(actor_id, "destroyed." + ObjectTypeNames[object_target._type_id])
|
|
58
|
+
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
return False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
from omegaconf import OmegaConf
|
|
4
|
+
|
|
5
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, Orientation, GridObject
|
|
6
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
7
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
8
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
9
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
cdef class Gift(MettaActionHandler):
|
|
13
|
+
def __init__(self, cfg: OmegaConf):
|
|
14
|
+
MettaActionHandler.__init__(self, cfg, "gift")
|
|
15
|
+
|
|
16
|
+
cdef char _handle_action(
|
|
17
|
+
self,
|
|
18
|
+
unsigned int actor_id,
|
|
19
|
+
Agent * actor,
|
|
20
|
+
ActionArg arg):
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, GridObject, Orientation
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
9
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
10
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
11
|
+
|
|
12
|
+
cdef class Move(MettaActionHandler):
|
|
13
|
+
def __init__(self, cfg: OmegaConf):
|
|
14
|
+
MettaActionHandler.__init__(self, cfg, "move")
|
|
15
|
+
|
|
16
|
+
cdef char _handle_action(
|
|
17
|
+
self,
|
|
18
|
+
unsigned int actor_id,
|
|
19
|
+
Agent * actor,
|
|
20
|
+
ActionArg arg):
|
|
21
|
+
|
|
22
|
+
cdef unsigned short direction = arg
|
|
23
|
+
if direction >= 2:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
cdef Orientation orientation = <Orientation>((actor.orientation + 2*(direction)) % 4)
|
|
27
|
+
cdef GridLocation old_loc = actor.location
|
|
28
|
+
cdef GridLocation new_loc = self.env._grid.relative_location(old_loc, orientation)
|
|
29
|
+
if not self.env._grid.is_empty(new_loc.r, new_loc.c):
|
|
30
|
+
return False
|
|
31
|
+
return self.env._grid.move_object(actor.id, new_loc)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, Orientation, GridObject
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
9
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
10
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
cdef class Rotate(MettaActionHandler):
|
|
14
|
+
def __init__(self, cfg: OmegaConf):
|
|
15
|
+
MettaActionHandler.__init__(self, cfg, "rotate")
|
|
16
|
+
|
|
17
|
+
cdef char _handle_action(
|
|
18
|
+
self,
|
|
19
|
+
unsigned int actor_id,
|
|
20
|
+
Agent * actor,
|
|
21
|
+
ActionArg arg):
|
|
22
|
+
|
|
23
|
+
cdef unsigned short orientation = arg
|
|
24
|
+
if orientation >= 4:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
actor.orientation = orientation
|
|
28
|
+
return True
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, Orientation, GridObject
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
9
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
10
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
11
|
+
|
|
12
|
+
cdef class Shield(MettaActionHandler):
|
|
13
|
+
def __init__(self, cfg: OmegaConf):
|
|
14
|
+
MettaActionHandler.__init__(self, cfg, "shield")
|
|
15
|
+
|
|
16
|
+
cdef char _handle_action(
|
|
17
|
+
self,
|
|
18
|
+
unsigned int actor_id,
|
|
19
|
+
Agent * actor,
|
|
20
|
+
ActionArg arg):
|
|
21
|
+
|
|
22
|
+
if actor.shield:
|
|
23
|
+
actor.shield = True
|
|
24
|
+
else:
|
|
25
|
+
actor.shield = False
|
|
26
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
from puffergrid.grid_object cimport GridLocation, GridObjectId, Orientation, GridObject
|
|
7
|
+
from puffergrid.action cimport ActionHandler, ActionArg
|
|
8
|
+
from mettagrid.objects cimport MettaObject, ObjectType, Usable, Altar, Agent, Events, GridLayer
|
|
9
|
+
from mettagrid.objects cimport Generator, Converter, InventoryItem, ObjectTypeNames, InventoryItemNames
|
|
10
|
+
from mettagrid.actions.actions cimport MettaActionHandler
|
|
11
|
+
|
|
12
|
+
cdef class Use(MettaActionHandler):
|
|
13
|
+
def __init__(self, cfg: OmegaConf):
|
|
14
|
+
MettaActionHandler.__init__(self, cfg, "use")
|
|
15
|
+
|
|
16
|
+
cdef char _handle_action(
|
|
17
|
+
self,
|
|
18
|
+
unsigned int actor_id,
|
|
19
|
+
Agent * actor,
|
|
20
|
+
ActionArg arg):
|
|
21
|
+
|
|
22
|
+
cdef GridLocation target_loc = self.env._grid.relative_location(
|
|
23
|
+
actor.location,
|
|
24
|
+
<Orientation>actor.orientation
|
|
25
|
+
)
|
|
26
|
+
target_loc.layer = GridLayer.Object_Layer
|
|
27
|
+
cdef MettaObject *target = <MettaObject*>self.env._grid.object_at(target_loc)
|
|
28
|
+
if target == NULL:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
if not target.usable(actor):
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
cdef Usable *usable = <Usable*> target
|
|
35
|
+
actor.energy -= usable.use_cost
|
|
36
|
+
|
|
37
|
+
usable.ready = 0
|
|
38
|
+
self.env._event_manager.schedule_event(Events.Reset, usable.cooldown, usable.id, 0)
|
|
39
|
+
|
|
40
|
+
self.env._stats.agent_incr(actor_id, self._stats.target[target._type_id].c_str())
|
|
41
|
+
self.env._stats.agent_add(actor_id, self._stats.target_energy[target._type_id].c_str(), usable.use_cost + self.action_cost)
|
|
42
|
+
|
|
43
|
+
if target._type_id == ObjectType.AltarT:
|
|
44
|
+
self.env._rewards[actor_id] += 1
|
|
45
|
+
|
|
46
|
+
cdef Generator *generator
|
|
47
|
+
if target._type_id == ObjectType.GeneratorT:
|
|
48
|
+
generator = <Generator*>target
|
|
49
|
+
generator.r1 -= 1
|
|
50
|
+
actor.update_inventory(InventoryItem.r1, 1)
|
|
51
|
+
self.env._stats.agent_incr(actor_id, "r1.gained")
|
|
52
|
+
self.env._stats.game_incr("r1.harvested")
|
|
53
|
+
|
|
54
|
+
cdef Converter *converter
|
|
55
|
+
if target._type_id == ObjectType.ConverterT:
|
|
56
|
+
converter = <Converter*>target
|
|
57
|
+
actor.update_inventory(converter.input_resource, -1)
|
|
58
|
+
self.env._stats.agent_incr(actor_id, InventoryItemNames[converter.input_resource] + ".used")
|
|
59
|
+
|
|
60
|
+
actor.update_inventory(converter.output_resource, 1)
|
|
61
|
+
self.env._stats.agent_incr(actor_id, InventoryItemNames[converter.input_resource] + ".gained")
|
|
62
|
+
|
|
63
|
+
actor.energy += converter.output_energy
|
|
64
|
+
self.env._stats.agent_add(actor_id, "energy.gained", converter.output_energy)
|
|
65
|
+
|
|
66
|
+
return True
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
import numpy as np
|
|
3
|
+
import yaml
|
|
4
|
+
from omegaconf import OmegaConf
|
|
5
|
+
|
|
6
|
+
class NoAliasDumper(yaml.Dumper):
|
|
7
|
+
def ignore_aliases(self, data):
|
|
8
|
+
return True
|
|
9
|
+
|
|
10
|
+
class MettaGridGameBuilder():
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
obs_width: int,
|
|
14
|
+
obs_height: int,
|
|
15
|
+
tile_size: int,
|
|
16
|
+
max_steps: int,
|
|
17
|
+
num_agents: int,
|
|
18
|
+
no_energy_steps: int,
|
|
19
|
+
objects,
|
|
20
|
+
actions,
|
|
21
|
+
map):
|
|
22
|
+
|
|
23
|
+
self.obs_width = obs_width
|
|
24
|
+
self.obs_height = obs_height
|
|
25
|
+
self.tile_size = tile_size
|
|
26
|
+
self.num_agents = num_agents
|
|
27
|
+
self.max_steps = max_steps
|
|
28
|
+
|
|
29
|
+
self._symbols = {
|
|
30
|
+
"agent": "A",
|
|
31
|
+
"altar": "a",
|
|
32
|
+
"converter": "c",
|
|
33
|
+
"generator": "g",
|
|
34
|
+
"wall": "w",
|
|
35
|
+
"empty": " ",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
self.no_energy_steps = no_energy_steps
|
|
39
|
+
objects = OmegaConf.create(objects)
|
|
40
|
+
self.object_configs = objects
|
|
41
|
+
actions = OmegaConf.create(actions)
|
|
42
|
+
self.action_configs = actions
|
|
43
|
+
self.map_config = OmegaConf.create(map)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def level(self):
|
|
47
|
+
layout = self.map_config.layout
|
|
48
|
+
|
|
49
|
+
if "rooms" in layout:
|
|
50
|
+
return self.build_map(layout.rooms)
|
|
51
|
+
else:
|
|
52
|
+
return self.build_map(
|
|
53
|
+
[["room"] * layout.rooms_x] * layout.rooms_y)
|
|
54
|
+
|
|
55
|
+
def build_map(self, rooms):
|
|
56
|
+
num_agents = 0
|
|
57
|
+
layers = []
|
|
58
|
+
for layer in rooms:
|
|
59
|
+
rooms = []
|
|
60
|
+
for room_name in layer:
|
|
61
|
+
room_config = self.map_config[room_name]
|
|
62
|
+
rooms.append(self.build_room(room_config, num_agents + 1))
|
|
63
|
+
num_agents += room_config.objects.agent
|
|
64
|
+
layers.append(np.concatenate(rooms, axis=1))
|
|
65
|
+
level = np.concatenate(layers, axis=0)
|
|
66
|
+
assert num_agents == self.num_agents, f"Number of agents in map ({num_agents}) does not match num_agents ({self.num_agents})"
|
|
67
|
+
|
|
68
|
+
footer = np.full((1, level.shape[1]), "W", dtype="U6")
|
|
69
|
+
footer[0, 0] = "q"
|
|
70
|
+
|
|
71
|
+
level = np.concatenate([level, footer], axis=0)
|
|
72
|
+
return level
|
|
73
|
+
|
|
74
|
+
def build_room(self, room_config, starting_agent=1):
|
|
75
|
+
symbols = []
|
|
76
|
+
content_width = room_config.width - 2*room_config.border
|
|
77
|
+
content_height = room_config.height - 2*room_config.border
|
|
78
|
+
area = content_width * content_height
|
|
79
|
+
|
|
80
|
+
for obj_name, count in room_config.objects.items():
|
|
81
|
+
symbol = self._symbols[obj_name]
|
|
82
|
+
if obj_name == "agent":
|
|
83
|
+
symbols.extend([f"{symbol}{i+starting_agent}" for i in range(count)])
|
|
84
|
+
else:
|
|
85
|
+
symbols.extend([symbol] * count)
|
|
86
|
+
|
|
87
|
+
assert(len(symbols) <= area), f"Too many objects in room: {len(symbols)} > {area}"
|
|
88
|
+
symbols.extend(["."] * (area - len(symbols)))
|
|
89
|
+
symbols = np.array(symbols).astype("U8")
|
|
90
|
+
np.random.shuffle(symbols)
|
|
91
|
+
content = symbols.reshape(content_height, content_width)
|
|
92
|
+
room = np.full((room_config.height, room_config.width), "W", dtype="U6")
|
|
93
|
+
room[room_config.border:room_config.border+content_height,
|
|
94
|
+
room_config.border:room_config.border+content_width] = content
|
|
95
|
+
|
|
96
|
+
return room
|
|
97
|
+
|
|
98
|
+
def termination_conditions(self):
|
|
99
|
+
return {
|
|
100
|
+
"Win": [ {"lt": ["game:max_steps", "game:step"]} ],
|
|
101
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from omegaconf import DictConfig, ListConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def sample_config(value):
|
|
8
|
+
if isinstance(value, int):
|
|
9
|
+
return value
|
|
10
|
+
if isinstance(value, float):
|
|
11
|
+
return value
|
|
12
|
+
if isinstance(value, DictConfig):
|
|
13
|
+
return {
|
|
14
|
+
key: sample_config(value)
|
|
15
|
+
for key, value in value.items()
|
|
16
|
+
}
|
|
17
|
+
if isinstance(value, ListConfig):
|
|
18
|
+
if len(value) == 0:
|
|
19
|
+
return value
|
|
20
|
+
if isinstance(value[0], int):
|
|
21
|
+
assert len(value) == 2, f"Found a list with length != 2 {value}"
|
|
22
|
+
return np.random.randint(value[0], value[1])
|
|
23
|
+
if isinstance(value[0], float):
|
|
24
|
+
assert len(value) == 2, f"Found a list with length != 2 {value}"
|
|
25
|
+
return np.random.uniform(value[0], value[1])
|
|
26
|
+
return value
|
|
27
|
+
return value
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
from libc.stdio cimport printf
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import gymnasium as gym
|
|
6
|
+
from omegaconf import OmegaConf
|
|
7
|
+
|
|
8
|
+
from puffergrid.grid_env cimport GridEnv
|
|
9
|
+
|
|
10
|
+
from mettagrid.objects cimport ObjectLayers, Agent, ResetHandler, Wall, Generator, Converter, Altar
|
|
11
|
+
from mettagrid.objects cimport MettaObservationEncoder
|
|
12
|
+
from mettagrid.actions.move import Move
|
|
13
|
+
from mettagrid.actions.rotate import Rotate
|
|
14
|
+
from mettagrid.actions.use import Use
|
|
15
|
+
from mettagrid.actions.attack import Attack
|
|
16
|
+
from mettagrid.actions.shield import Shield
|
|
17
|
+
from mettagrid.actions.gift import Gift
|
|
18
|
+
|
|
19
|
+
cdef class MettaGrid(GridEnv):
|
|
20
|
+
cdef:
|
|
21
|
+
object _cfg
|
|
22
|
+
|
|
23
|
+
def __init__(self, cfg: OmegaConf, map: np.ndarray):
|
|
24
|
+
self._cfg = cfg
|
|
25
|
+
|
|
26
|
+
GridEnv.__init__(
|
|
27
|
+
self,
|
|
28
|
+
cfg.num_agents,
|
|
29
|
+
map.shape[1],
|
|
30
|
+
map.shape[0],
|
|
31
|
+
cfg.max_steps,
|
|
32
|
+
dict(ObjectLayers).values(),
|
|
33
|
+
cfg.obs_width, cfg.obs_height,
|
|
34
|
+
MettaObservationEncoder(),
|
|
35
|
+
[
|
|
36
|
+
Move(cfg.actions.move),
|
|
37
|
+
Rotate(cfg.actions.rotate),
|
|
38
|
+
Use(cfg.actions.use),
|
|
39
|
+
Attack(cfg.actions.attack),
|
|
40
|
+
Shield(cfg.actions.shield),
|
|
41
|
+
Gift(cfg.actions.gift),
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
ResetHandler()
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
cdef Agent *agent
|
|
50
|
+
for r in range(map.shape[0]):
|
|
51
|
+
for c in range(map.shape[1]):
|
|
52
|
+
if map[r,c] == "W":
|
|
53
|
+
self._grid.add_object(new Wall(r, c, cfg.objects.wall))
|
|
54
|
+
self._stats.game_incr("objects.wall")
|
|
55
|
+
elif map[r,c] == "g":
|
|
56
|
+
self._grid.add_object(new Generator(r, c, cfg.objects.generator))
|
|
57
|
+
self._stats.game_incr("objects.generator")
|
|
58
|
+
elif map[r,c] == "c":
|
|
59
|
+
self._grid.add_object(new Converter(r, c, cfg.objects.converter))
|
|
60
|
+
self._stats.game_incr("objects.converter")
|
|
61
|
+
elif map[r,c] == "a":
|
|
62
|
+
self._grid.add_object(new Altar(r, c, cfg.objects.altar))
|
|
63
|
+
self._stats.game_incr("objects.altar")
|
|
64
|
+
elif map[r,c][0] == "A":
|
|
65
|
+
agent = new Agent(r, c, cfg.objects.agent)
|
|
66
|
+
self._grid.add_object(agent)
|
|
67
|
+
self.add_agent(agent)
|
|
68
|
+
self._stats.game_incr("objects.agent")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def render(self):
|
|
72
|
+
grid = self.render_ascii(["A", "#", "g", "c", "a"])
|
|
73
|
+
for r in grid:
|
|
74
|
+
print("".join(r))
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def action_space(self):
|
|
78
|
+
return gym.spaces.MultiDiscrete((self.num_actions(), 10), dtype=np.uint32)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
import pufferlib
|
|
4
|
+
import numpy as np
|
|
5
|
+
from omegaconf import OmegaConf
|
|
6
|
+
|
|
7
|
+
from mettagrid.config.game_builder import MettaGridGameBuilder
|
|
8
|
+
from mettagrid.renderer.raylib_client import MettaRaylibClient
|
|
9
|
+
from mettagrid.config.sample_config import sample_config
|
|
10
|
+
from mettagrid.mettagrid_c import MettaGrid
|
|
11
|
+
from pufferlib.environments.ocean.render import GridRender
|
|
12
|
+
|
|
13
|
+
class GridClient:
|
|
14
|
+
def __init__(self, width, height):
|
|
15
|
+
self._width = width
|
|
16
|
+
self._height = height
|
|
17
|
+
|
|
18
|
+
class MettaGridEnv(pufferlib.PufferEnv):
|
|
19
|
+
def __init__(self, render_mode: str, **cfg):
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
self._render_mode = render_mode
|
|
23
|
+
self._cfg = OmegaConf.create(cfg)
|
|
24
|
+
self.make_env()
|
|
25
|
+
|
|
26
|
+
if render_mode == "human":
|
|
27
|
+
self._renderer = MettaRaylibClient(
|
|
28
|
+
self._env.map_width(), self._env.map_height(),
|
|
29
|
+
)
|
|
30
|
+
elif render_mode == "raylib":
|
|
31
|
+
self._renderer = GridRender(
|
|
32
|
+
self._env.map_width(), self._env.map_height(),
|
|
33
|
+
fps=10
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def make_env(self):
|
|
38
|
+
game_cfg = OmegaConf.create(sample_config(self._cfg.game))
|
|
39
|
+
self._game_builder = MettaGridGameBuilder(**game_cfg)
|
|
40
|
+
level = self._game_builder.level()
|
|
41
|
+
self._c_env = MettaGrid(game_cfg, level)
|
|
42
|
+
self._grid_env = self._c_env
|
|
43
|
+
self._num_agents = self._c_env.num_agents()
|
|
44
|
+
|
|
45
|
+
# self._grid_env = PufferGridEnv(self._c_env)
|
|
46
|
+
env = self._grid_env
|
|
47
|
+
|
|
48
|
+
self._env = env
|
|
49
|
+
#self._env = LastActionTracker(self._grid_env)
|
|
50
|
+
#self._env = Kinship(**sample_config(self._cfg.kinship), env=self._env)
|
|
51
|
+
#self._env = RewardTracker(self._env)
|
|
52
|
+
#self._env = FeatureMasker(self._env, self._cfg.hidden_features)
|
|
53
|
+
self.done = False
|
|
54
|
+
|
|
55
|
+
def reset(self, **kwargs):
|
|
56
|
+
self.make_env()
|
|
57
|
+
if hasattr(self, "buf"):
|
|
58
|
+
self._c_env.set_buffers(
|
|
59
|
+
self.buf.observations,
|
|
60
|
+
self.buf.terminals,
|
|
61
|
+
self.buf.truncations,
|
|
62
|
+
self.buf.rewards)
|
|
63
|
+
|
|
64
|
+
# obs, infos = self._env.reset(**kwargs)
|
|
65
|
+
# self._compute_max_energy()
|
|
66
|
+
# return obs, infos
|
|
67
|
+
obs, infos = self._c_env.reset()
|
|
68
|
+
return obs, infos
|
|
69
|
+
|
|
70
|
+
def step(self, actions):
|
|
71
|
+
obs, rewards, terminated, truncated, infos = self._c_env.step(actions.astype(np.int32))
|
|
72
|
+
|
|
73
|
+
rewards_sum = rewards.sum()
|
|
74
|
+
if rewards_sum != 0:
|
|
75
|
+
reward_mean = rewards_sum / self._num_agents
|
|
76
|
+
rewards -= reward_mean
|
|
77
|
+
|
|
78
|
+
if terminated.all() or truncated.all():
|
|
79
|
+
self.done = True
|
|
80
|
+
|
|
81
|
+
stats = self._c_env.get_episode_stats()
|
|
82
|
+
episode_rewards = self._c_env.get_episode_rewards()
|
|
83
|
+
episode_rewards_sum = episode_rewards.sum()
|
|
84
|
+
episode_rewards_mean = episode_rewards_sum / self._num_agents
|
|
85
|
+
|
|
86
|
+
infos = {
|
|
87
|
+
"episode/reward.sum": episode_rewards_sum,
|
|
88
|
+
"episode/reward.mean": episode_rewards_mean,
|
|
89
|
+
"episode/reward.min": episode_rewards.min(),
|
|
90
|
+
"episode/reward.max": episode_rewards.max(),
|
|
91
|
+
"episode_length": self._c_env.current_timestep(),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
agent_stats = {}
|
|
95
|
+
for a_stats in stats["agent_stats"]:
|
|
96
|
+
for k, v in a_stats.items():
|
|
97
|
+
if k not in agent_stats:
|
|
98
|
+
agent_stats[k] = 0
|
|
99
|
+
agent_stats[k] += v
|
|
100
|
+
|
|
101
|
+
for k, v in agent_stats.items():
|
|
102
|
+
infos[f"agent_stats/{k}"] = float(v) / self._num_agents
|
|
103
|
+
|
|
104
|
+
return obs, list(rewards), terminated.all(), truncated.all(), infos
|
|
105
|
+
|
|
106
|
+
def process_episode_stats(self, episode_stats: Dict[str, Any]):
|
|
107
|
+
for agent_stats in episode_stats["agent_stats"]:
|
|
108
|
+
extra_stats = {}
|
|
109
|
+
for stat_name in agent_stats.keys():
|
|
110
|
+
if stat_name.startswith("action_"):
|
|
111
|
+
extra_stats[stat_name + "_pct"] = agent_stats[stat_name] / self._grid_env.current_timestep
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# for object in self._game_builder.object_configs.keys():
|
|
115
|
+
# if stat_name.startswith(f"stats_{object}_") and object != "agent":
|
|
116
|
+
# symbol = self._game_builder._objects[object].symbol
|
|
117
|
+
# num_obj = self._griddly_yaml["Environment"]["Levels"][0].count(symbol)
|
|
118
|
+
# if num_obj == 0:
|
|
119
|
+
# num_obj = 1
|
|
120
|
+
# extra_stats[stat_name + "_pct"] = agent_stats[stat_name] / num_obj
|
|
121
|
+
|
|
122
|
+
agent_stats.update(extra_stats)
|
|
123
|
+
agent_stats.update(episode_stats["game_stats"])
|
|
124
|
+
# agent_stats["level_max_energy"] = self._max_level_energy
|
|
125
|
+
# agent_stats["level_max_energy_per_agent"] = self._max_level_energy_per_agent
|
|
126
|
+
# agent_stats["level_max_reward_per_agent"] = self._max_level_reward_per_agent
|
|
127
|
+
|
|
128
|
+
def _compute_max_energy(self):
|
|
129
|
+
pass
|
|
130
|
+
# num_generators = self._griddly_yaml["Environment"]["Levels"][0].count("g")
|
|
131
|
+
# num_converters = self._griddly_yaml["Environment"]["Levels"][0].count("c")
|
|
132
|
+
# max_resources = num_generators * min(
|
|
133
|
+
# self._game_builder.object_configs.generator.initial_resources,
|
|
134
|
+
# self._max_steps / self._game_builder.object_configs.generator.cooldown)
|
|
135
|
+
|
|
136
|
+
# max_conversions = num_converters * (
|
|
137
|
+
# self._max_steps / self._game_builder.object_configs.converter.cooldown
|
|
138
|
+
# )
|
|
139
|
+
# max_conv_energy = min(max_resources, max_conversions) * \
|
|
140
|
+
# np.mean(list(self._game_builder.object_configs.converter.energy_output.values()))
|
|
141
|
+
|
|
142
|
+
# initial_energy = self._game_builder.object_configs.agent.initial_energy * self._game_builder.num_agents
|
|
143
|
+
|
|
144
|
+
# self._max_level_energy = max_conv_energy + initial_energy
|
|
145
|
+
# self._max_level_energy_per_agent = self._max_level_energy / self._game_builder.num_agents
|
|
146
|
+
|
|
147
|
+
# self._max_level_reward_per_agent = self._max_level_energy_per_agent
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def _max_steps(self):
|
|
152
|
+
return self._game_builder.max_steps
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def observation_space(self):
|
|
156
|
+
return self._env.observation_space
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def action_space(self):
|
|
160
|
+
return self._env.action_space
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def player_count(self):
|
|
164
|
+
return self._num_agents
|
|
165
|
+
|
|
166
|
+
def render(self, *args, **kwargs):
|
|
167
|
+
return self._renderer.render(
|
|
168
|
+
self._c_env.grid_objects(),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def grid_features(self):
|
|
173
|
+
return self._env.grid_features()
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def global_features(self):
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def render_mode(self):
|
|
181
|
+
return self._render_mode
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# distutils: language=c++
|
|
2
|
+
# cython: warn.undeclared=False
|
|
3
|
+
|
|
4
|
+
cimport cython
|
|
5
|
+
|
|
6
|
+
from libcpp.vector cimport vector
|
|
7
|
+
from libcpp.map cimport map
|
|
8
|
+
from libcpp.string cimport string
|
|
9
|
+
from puffergrid.grid_env import StatsTracker
|
|
10
|
+
from libc.stdio cimport printf
|
|
11
|
+
from puffergrid.observation_encoder cimport ObservationEncoder, ObsType
|
|
12
|
+
from puffergrid.grid_object cimport GridObject, TypeId, GridCoord, GridLocation, GridObjectId
|
|
13
|
+
from puffergrid.event cimport EventHandler, EventArg
|
|
14
|
+
|
|
15
|
+
cdef enum GridLayer:
|
|
16
|
+
Agent_Layer = 0
|
|
17
|
+
Object_Layer = 1
|
|
18
|
+
|
|
19
|
+
ctypedef map[string, int] ObjectConfig
|
|
20
|
+
|
|
21
|
+
cdef cppclass MettaObject(GridObject):
|
|
22
|
+
unsigned int hp
|
|
23
|
+
|
|
24
|
+
inline void init_mo(ObjectConfig cfg):
|
|
25
|
+
this.hp = cfg[b"hp"]
|
|
26
|
+
|
|
27
|
+
inline char usable(const Agent *actor):
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
inline char attackable():
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
cdef cppclass Usable(MettaObject):
|
|
34
|
+
unsigned int use_cost
|
|
35
|
+
unsigned int cooldown
|
|
36
|
+
unsigned char ready
|
|
37
|
+
|
|
38
|
+
inline void init_usable(ObjectConfig cfg):
|
|
39
|
+
this.use_cost = cfg[b"use_cost"]
|
|
40
|
+
this.cooldown = cfg[b"cooldown"]
|
|
41
|
+
this.ready = 1
|
|
42
|
+
|
|
43
|
+
inline char usable(const Agent *actor):
|
|
44
|
+
return this.ready and this.use_cost <= actor.energy
|
|
45
|
+
|
|
46
|
+
cdef enum ObjectType:
|
|
47
|
+
AgentT = 0
|
|
48
|
+
WallT = 1
|
|
49
|
+
GeneratorT = 2
|
|
50
|
+
ConverterT = 3
|
|
51
|
+
AltarT = 4
|
|
52
|
+
Count = 5
|
|
53
|
+
|
|
54
|
+
cdef vector[string] ObjectTypeNames # defined in objects.pyx
|
|
55
|
+
|
|
56
|
+
cdef enum InventoryItem:
|
|
57
|
+
r1 = 0,
|
|
58
|
+
r2 = 1,
|
|
59
|
+
r3 = 2,
|
|
60
|
+
InventoryCount = 3
|
|
61
|
+
|
|
62
|
+
cdef vector[string] InventoryItemNames # defined in objects.pyx
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
cdef cppclass Agent(MettaObject):
|
|
66
|
+
char frozen
|
|
67
|
+
unsigned int energy
|
|
68
|
+
unsigned int orientation
|
|
69
|
+
char shield
|
|
70
|
+
vector[unsigned short] inventory
|
|
71
|
+
|
|
72
|
+
inline Agent(GridCoord r, GridCoord c, ObjectConfig cfg):
|
|
73
|
+
GridObject.init(ObjectType.AgentT, GridLocation(r, c, GridLayer.Agent_Layer))
|
|
74
|
+
MettaObject.init_mo(cfg)
|
|
75
|
+
this.frozen = False
|
|
76
|
+
this.energy = cfg[b"initial_energy"]
|
|
77
|
+
this.orientation = 0
|
|
78
|
+
this.inventory.resize(InventoryItem.InventoryCount)
|
|
79
|
+
|
|
80
|
+
inline void update_inventory(InventoryItem item, short amount):
|
|
81
|
+
this.inventory[<InventoryItem>item] += amount
|
|
82
|
+
|
|
83
|
+
inline void obs(ObsType[:] obs):
|
|
84
|
+
obs[0] = 1
|
|
85
|
+
obs[1] = this.hp
|
|
86
|
+
obs[2] = this.frozen
|
|
87
|
+
obs[3] = this.energy
|
|
88
|
+
obs[4] = this.orientation
|
|
89
|
+
obs[5] = this.shield
|
|
90
|
+
|
|
91
|
+
cdef unsigned short idx = 6
|
|
92
|
+
cdef unsigned short i
|
|
93
|
+
for i in range(InventoryItem.InventoryCount):
|
|
94
|
+
obs[idx + i] = this.inventory[i]
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
inline vector[string] feature_names():
|
|
98
|
+
return [
|
|
99
|
+
"agent", "agent:hp", "agent:frozen", "agent:energy", "agent:orientation",
|
|
100
|
+
"agent:shield"
|
|
101
|
+
] + [
|
|
102
|
+
"agent:inv:" + n for n in InventoryItemNames]
|
|
103
|
+
|
|
104
|
+
cdef cppclass Wall(MettaObject):
|
|
105
|
+
inline Wall(GridCoord r, GridCoord c, ObjectConfig cfg):
|
|
106
|
+
GridObject.init(ObjectType.WallT, GridLocation(r, c, GridLayer.Object_Layer))
|
|
107
|
+
MettaObject.init_mo(cfg)
|
|
108
|
+
|
|
109
|
+
inline void obs(ObsType[:] obs):
|
|
110
|
+
obs[0] = 1
|
|
111
|
+
obs[1] = hp
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
inline vector[string] feature_names():
|
|
115
|
+
return ["wall", "wall:hp"]
|
|
116
|
+
|
|
117
|
+
cdef cppclass Generator(Usable):
|
|
118
|
+
unsigned int r1
|
|
119
|
+
|
|
120
|
+
inline Generator(GridCoord r, GridCoord c, ObjectConfig cfg):
|
|
121
|
+
GridObject.init(ObjectType.GeneratorT, GridLocation(r, c, GridLayer.Object_Layer))
|
|
122
|
+
MettaObject.init_mo(cfg)
|
|
123
|
+
Usable.init_usable(cfg)
|
|
124
|
+
this.r1 = cfg[b"initial_resources"]
|
|
125
|
+
|
|
126
|
+
inline char usable(const Agent *actor):
|
|
127
|
+
return Usable.usable(actor) and this.r1 > 0
|
|
128
|
+
|
|
129
|
+
inline void obs(ObsType[:] obs):
|
|
130
|
+
obs[0] = 1
|
|
131
|
+
obs[1] = this.hp
|
|
132
|
+
obs[2] = this.r1
|
|
133
|
+
obs[3] = this.ready
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
inline vector[string] feature_names():
|
|
138
|
+
return ["generator", "generator:hp", "generator:r1", "generator:ready"]
|
|
139
|
+
|
|
140
|
+
cdef cppclass Converter(Usable):
|
|
141
|
+
InventoryItem input_resource
|
|
142
|
+
InventoryItem output_resource
|
|
143
|
+
short output_energy
|
|
144
|
+
|
|
145
|
+
inline Converter(GridCoord r, GridCoord c, ObjectConfig cfg):
|
|
146
|
+
GridObject.init(ObjectType.ConverterT, GridLocation(r, c, GridLayer.Object_Layer))
|
|
147
|
+
MettaObject.init_mo(cfg)
|
|
148
|
+
Usable.init_usable(cfg)
|
|
149
|
+
this.input_resource = InventoryItem.r1
|
|
150
|
+
this.output_resource = InventoryItem.r2
|
|
151
|
+
this.output_energy = cfg[b"energy_output.r1"]
|
|
152
|
+
|
|
153
|
+
inline char usable(const Agent *actor):
|
|
154
|
+
return Usable.usable(actor) and actor.inventory[this.input_resource] > 0
|
|
155
|
+
|
|
156
|
+
inline obs(ObsType[:] obs):
|
|
157
|
+
obs[0] = 1
|
|
158
|
+
obs[1] = hp
|
|
159
|
+
obs[2] = input_resource
|
|
160
|
+
obs[3] = output_resource
|
|
161
|
+
obs[4] = output_energy
|
|
162
|
+
obs[5] = ready
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
inline vector[string] feature_names():
|
|
166
|
+
return ["converter", "converter:hp", "converter:input_resource", "converter:output_resource", "converter:output_energy", "converter:ready"]
|
|
167
|
+
|
|
168
|
+
cdef cppclass Altar(Usable):
|
|
169
|
+
inline Altar(GridCoord r, GridCoord c, ObjectConfig cfg):
|
|
170
|
+
GridObject.init(ObjectType.AltarT, GridLocation(r, c, GridLayer.Object_Layer))
|
|
171
|
+
MettaObject.init_mo(cfg)
|
|
172
|
+
Usable.init_usable(cfg)
|
|
173
|
+
|
|
174
|
+
inline void obs(ObsType[:] obs):
|
|
175
|
+
obs[0] = 1
|
|
176
|
+
obs[1] = hp
|
|
177
|
+
obs[2] = ready
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
inline vector[string] feature_names():
|
|
181
|
+
return ["altar", "altar:hp", "altar:ready"]
|
|
182
|
+
|
|
183
|
+
cdef map[TypeId, GridLayer] ObjectLayers
|
|
184
|
+
|
|
185
|
+
cdef class ResetHandler(EventHandler):
|
|
186
|
+
cdef inline void handle_event(self, GridObjectId obj_id, EventArg arg):
|
|
187
|
+
cdef Usable *usable = <Usable*>self.env._grid.object(obj_id)
|
|
188
|
+
usable.ready = True
|
|
189
|
+
self.env._stats.game_incr("resets." + ObjectTypeNames[usable._type_id])
|
|
190
|
+
|
|
191
|
+
cdef enum Events:
|
|
192
|
+
Reset = 0
|
|
193
|
+
|
|
194
|
+
cdef class MettaObservationEncoder(ObservationEncoder):
|
|
195
|
+
cdef vector[short] _offsets
|
|
196
|
+
cdef vector[string] _feature_names
|
|
197
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# distutils: language=c++
|
|
2
|
+
|
|
3
|
+
from libc.stdio cimport printf
|
|
4
|
+
from libcpp.string cimport string
|
|
5
|
+
from libcpp.vector cimport vector
|
|
6
|
+
from puffergrid.grid_object cimport GridObject, GridObjectId
|
|
7
|
+
|
|
8
|
+
cdef vector[string] ObjectTypeNames = [
|
|
9
|
+
"agent",
|
|
10
|
+
"wall",
|
|
11
|
+
"generator",
|
|
12
|
+
"converter",
|
|
13
|
+
"altar"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
cdef vector[string] InventoryItemNames = [
|
|
17
|
+
"r1",
|
|
18
|
+
"r2",
|
|
19
|
+
"r3"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
ObjectLayers = {
|
|
23
|
+
ObjectType.AgentT: GridLayer.Agent_Layer,
|
|
24
|
+
ObjectType.WallT: GridLayer.Object_Layer,
|
|
25
|
+
ObjectType.GeneratorT: GridLayer.Object_Layer,
|
|
26
|
+
ObjectType.ConverterT: GridLayer.Object_Layer,
|
|
27
|
+
ObjectType.AltarT: GridLayer.Object_Layer,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cdef class MettaObservationEncoder(ObservationEncoder):
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self._offsets.resize(ObjectType.Count)
|
|
33
|
+
|
|
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
|
+
|
|
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())
|
|
49
|
+
|
|
50
|
+
self._feature_names = features
|
|
51
|
+
|
|
52
|
+
cdef encode(self, GridObject *obj, ObsType[:] obs):
|
|
53
|
+
if obj._type_id == ObjectType.AgentT:
|
|
54
|
+
(<Agent*>obj).obs(obs[self._offsets[ObjectType.AgentT]:])
|
|
55
|
+
elif obj._type_id == ObjectType.WallT:
|
|
56
|
+
(<Wall*>obj).obs(obs[self._offsets[ObjectType.WallT]:])
|
|
57
|
+
elif obj._type_id == ObjectType.GeneratorT:
|
|
58
|
+
(<Generator*>obj).obs(obs[self._offsets[ObjectType.GeneratorT]:])
|
|
59
|
+
elif obj._type_id == ObjectType.ConverterT:
|
|
60
|
+
(<Converter*>obj).obs(obs[self._offsets[ObjectType.ConverterT]:])
|
|
61
|
+
elif obj._type_id == ObjectType.AltarT:
|
|
62
|
+
(<Altar*>obj).obs(obs[self._offsets[ObjectType.AltarT]:])
|
|
63
|
+
else:
|
|
64
|
+
printf("Encoding object of unknown type: %d\n", obj._type_id)
|
|
65
|
+
|
|
66
|
+
cdef vector[string] feature_names(self):
|
|
67
|
+
return self._feature_names
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from pdb import set_trace as T
|
|
2
|
+
import numpy as np
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import pettingzoo
|
|
6
|
+
import gymnasium
|
|
7
|
+
|
|
8
|
+
import pufferlib
|
|
9
|
+
from pufferlib.environments.ocean import render
|
|
10
|
+
|
|
11
|
+
class MettaRaylibClient:
|
|
12
|
+
def __init__(self, width, height, tile_size=32):
|
|
13
|
+
self.width = width
|
|
14
|
+
self.height = height
|
|
15
|
+
self.tile_size = tile_size
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
sprite_sheet_path = os.path.join(
|
|
19
|
+
*self.__module__.split('.')[:-1], './puffer_chars.png')
|
|
20
|
+
self.asset_map = {
|
|
21
|
+
1: (0, 0, 128, 128),
|
|
22
|
+
3: (128, 0, 128, 128),
|
|
23
|
+
4: (256, 0, 128, 128),
|
|
24
|
+
# 5: (384, 0, 128, 128),
|
|
25
|
+
5: (512, 0, 128, 128), #star
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
from raylib import rl, colors
|
|
29
|
+
rl.InitWindow(width*tile_size, height*tile_size,
|
|
30
|
+
"PufferLib Ray Grid".encode())
|
|
31
|
+
rl.SetTargetFPS(10)
|
|
32
|
+
self.puffer = rl.LoadTexture(sprite_sheet_path.encode())
|
|
33
|
+
self.rl = rl
|
|
34
|
+
self.colors = colors
|
|
35
|
+
|
|
36
|
+
import pyray as ray
|
|
37
|
+
camera = ray.Camera2D()
|
|
38
|
+
camera.target = ray.Vector2(0.0, 0.0)
|
|
39
|
+
camera.rotation = 0.0
|
|
40
|
+
camera.zoom = 1.0
|
|
41
|
+
self.camera = camera
|
|
42
|
+
|
|
43
|
+
from cffi import FFI
|
|
44
|
+
self.ffi = FFI()
|
|
45
|
+
|
|
46
|
+
def _cdata_to_numpy(self):
|
|
47
|
+
image = self.rl.LoadImageFromScreen()
|
|
48
|
+
width, height, channels = image.width, image.height, 4
|
|
49
|
+
cdata = self.ffi.buffer(image.data, width*height*channels)
|
|
50
|
+
return np.frombuffer(cdata, dtype=np.uint8
|
|
51
|
+
).reshape((height, width, channels))[:, :, :3]
|
|
52
|
+
|
|
53
|
+
def render(self, grid):
|
|
54
|
+
rl = self.rl
|
|
55
|
+
colors = self.colors
|
|
56
|
+
ay, ax = None, None
|
|
57
|
+
|
|
58
|
+
ts = self.tile_size
|
|
59
|
+
|
|
60
|
+
pos = rl.GetMousePosition()
|
|
61
|
+
raw_mouse_x = pos.x
|
|
62
|
+
raw_mouse_y = pos.y
|
|
63
|
+
mouse_x = int(raw_mouse_x // ts)
|
|
64
|
+
mouse_y = int(raw_mouse_y // ts)
|
|
65
|
+
ay = int(np.clip((pos.y - ts*self.height//2) / 50, -3, 3)) + 3
|
|
66
|
+
ax = int(np.clip((pos.x - ts*self.width//2) / 50, -3, 3)) + 3
|
|
67
|
+
|
|
68
|
+
if rl.IsKeyDown(rl.KEY_ESCAPE):
|
|
69
|
+
exit(0)
|
|
70
|
+
|
|
71
|
+
action_id = 0
|
|
72
|
+
action_arg = 0
|
|
73
|
+
|
|
74
|
+
if rl.IsKeyDown(rl.KEY_E):
|
|
75
|
+
action_id = 0
|
|
76
|
+
action_arg = 0
|
|
77
|
+
elif rl.IsKeyDown(rl.KEY_Q):
|
|
78
|
+
action_id = 0
|
|
79
|
+
action_arg = 1
|
|
80
|
+
|
|
81
|
+
elif rl.IsKeyDown(rl.KEY_W):
|
|
82
|
+
action_id = 1
|
|
83
|
+
action_arg = 0
|
|
84
|
+
elif rl.IsKeyDown(rl.KEY_S):
|
|
85
|
+
action_id = 1
|
|
86
|
+
action_arg = 1
|
|
87
|
+
elif rl.IsKeyDown(rl.KEY_A):
|
|
88
|
+
action_id = 1
|
|
89
|
+
action_arg = 2
|
|
90
|
+
elif rl.IsKeyDown(rl.KEY_R):
|
|
91
|
+
action_id = 1
|
|
92
|
+
action_arg = 3
|
|
93
|
+
|
|
94
|
+
# if rl.IsKeyDown(rl.KEY_LEFT_SHIFT):
|
|
95
|
+
# target_heros = 2
|
|
96
|
+
|
|
97
|
+
action = (action_id, action_arg)
|
|
98
|
+
|
|
99
|
+
rl.BeginDrawing()
|
|
100
|
+
rl.BeginMode2D(self.camera)
|
|
101
|
+
rl.ClearBackground([6, 24, 24, 255])
|
|
102
|
+
for y in range(self.height):
|
|
103
|
+
for x in range(self.width):
|
|
104
|
+
tile = grid[y, x]
|
|
105
|
+
tx = x*ts
|
|
106
|
+
ty = y*ts
|
|
107
|
+
if tile == 0:
|
|
108
|
+
continue
|
|
109
|
+
elif tile == 2:
|
|
110
|
+
# Wall
|
|
111
|
+
rl.DrawRectangle(x*ts, y*ts, ts, ts, [0, 0, 0, 255])
|
|
112
|
+
continue
|
|
113
|
+
else:
|
|
114
|
+
# Player
|
|
115
|
+
source_rect = self.asset_map[tile]
|
|
116
|
+
dest_rect = (tx, ty, ts, ts)
|
|
117
|
+
rl.DrawTexturePro(self.puffer, source_rect, dest_rect,
|
|
118
|
+
(0, 0), 0, colors.WHITE)
|
|
119
|
+
|
|
120
|
+
# Draw circle at mouse x, y
|
|
121
|
+
rl.DrawCircle(ts*mouse_x + ts//2, ts*mouse_y + ts//8, ts//8, [255, 0, 0, 255])
|
|
122
|
+
|
|
123
|
+
rl.EndMode2D()
|
|
124
|
+
|
|
125
|
+
# Draw HUD
|
|
126
|
+
# player = entities[0]
|
|
127
|
+
# hud_y = self.height*ts - 2*ts
|
|
128
|
+
# draw_bars(rl, player, 2*ts, hud_y, 10*ts, 24, draw_text=True)
|
|
129
|
+
|
|
130
|
+
# off_color = [255, 255, 255, 255]
|
|
131
|
+
# on_color = [0, 255, 0, 255]
|
|
132
|
+
|
|
133
|
+
# q_color = on_color if skill_q else off_color
|
|
134
|
+
# w_color = on_color if skill_w else off_color
|
|
135
|
+
# e_color = on_color if skill_e else off_color
|
|
136
|
+
|
|
137
|
+
# q_cd = player.q_timer
|
|
138
|
+
# w_cd = player.w_timer
|
|
139
|
+
# e_cd = player.e_timer
|
|
140
|
+
|
|
141
|
+
# rl.DrawText(f'Q: {q_cd}'.encode(), 13*ts, hud_y - 20, 40, q_color)
|
|
142
|
+
# rl.DrawText(f'W: {w_cd}'.encode(), 17*ts, hud_y - 20, 40, w_color)
|
|
143
|
+
# rl.DrawText(f'E: {e_cd}'.encode(), 21*ts, hud_y - 20, 40, e_color)
|
|
144
|
+
# rl.DrawText(f'Stun: {player.stun_timer}'.encode(), 25*ts, hud_y - 20, 20, e_color)
|
|
145
|
+
# rl.DrawText(f'Move: {player.move_timer}'.encode(), 25*ts, hud_y, 20, e_color)
|
|
146
|
+
|
|
147
|
+
rl.EndDrawing()
|
|
148
|
+
return self._cdata_to_numpy(), action
|
|
149
|
+
|
|
150
|
+
def draw_bars(rl, entity, x, y, width, height=4, draw_text=False):
|
|
151
|
+
health_bar = entity.health / entity.max_health
|
|
152
|
+
mana_bar = entity.mana / entity.max_mana
|
|
153
|
+
if entity.max_health == 0:
|
|
154
|
+
health_bar = 2
|
|
155
|
+
if entity.max_mana == 0:
|
|
156
|
+
mana_bar = 2
|
|
157
|
+
rl.DrawRectangle(x, y, width, height, [255, 0, 0, 255])
|
|
158
|
+
rl.DrawRectangle(x, y, int(width*health_bar), height, [0, 255, 0, 255])
|
|
159
|
+
|
|
160
|
+
if entity.entity_type == 0:
|
|
161
|
+
rl.DrawRectangle(x, y - height - 2, width, height, [255, 0, 0, 255])
|
|
162
|
+
rl.DrawRectangle(x, y - height - 2, int(width*mana_bar), height, [0, 255, 255, 255])
|
|
163
|
+
|
|
164
|
+
if draw_text:
|
|
165
|
+
health = int(entity.health)
|
|
166
|
+
mana = int(entity.mana)
|
|
167
|
+
max_health = int(entity.max_health)
|
|
168
|
+
max_mana = int(entity.max_mana)
|
|
169
|
+
rl.DrawText(f'Health: {health}/{max_health}'.encode(),
|
|
170
|
+
x+8, y+2, 20, [255, 255, 255, 255])
|
|
171
|
+
rl.DrawText(f'Mana: {mana}/{max_mana}'.encode(),
|
|
172
|
+
x+8, y+2 - height - 2, 20, [255, 255, 255, 255])
|
|
173
|
+
|
|
174
|
+
#rl.DrawRectangle(x, y - 2*height - 4, int(width*mana_bar), height, [255, 255, 0, 255])
|
|
175
|
+
rl.DrawText(f'Experience: {entity.xp}'.encode(),
|
|
176
|
+
x+8, y - 2*height - 4, 20, [255, 255, 255, 255])
|
|
177
|
+
|
|
178
|
+
elif entity.entity_type == 0:
|
|
179
|
+
rl.DrawText(f'Level: {entity.level}'.encode(),
|
|
180
|
+
x+4, y -2*height - 12, 12, [255, 255, 255, 255])
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=1.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[tool.poetry]
|
|
6
|
+
name = "mettagrid"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "A fast grid-based open-ended MARL environment"
|
|
9
|
+
authors = ["David Bloomin <daveey@gmail.com>"]
|
|
10
|
+
license = "MIT"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
homepage = "https://daveey.github.io"
|
|
13
|
+
repository = "https://github.com/Metta-AI/mettagrid"
|
|
14
|
+
keywords = ["puffergrid", "gridworld", "minigrid", "rl", "reinforcement-learning", "environment", "gym"]
|
|
15
|
+
|
|
16
|
+
[tool.poetry.dependencies]
|
|
17
|
+
python = ">=3.10,<4.0"
|
|
18
|
+
numpy = "^1.21.0"
|
|
19
|
+
cython = "^3.0.11"
|
|
20
|
+
|
|
21
|
+
[tool.poetry.dev-dependencies]
|
|
22
|
+
pytest = "^6.2.4"
|