mpe2 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.
Files changed (37) hide show
  1. mpe2-0.0.1/LICENSE +21 -0
  2. mpe2-0.0.1/MANIFEST.in +3 -0
  3. mpe2-0.0.1/PKG-INFO +44 -0
  4. mpe2-0.0.1/README.md +18 -0
  5. mpe2-0.0.1/mpe2/__init__.py +1 -0
  6. mpe2-0.0.1/mpe2/_mpe_utils/__init__.py +0 -0
  7. mpe2-0.0.1/mpe2/_mpe_utils/core.py +218 -0
  8. mpe2-0.0.1/mpe2/_mpe_utils/scenario.py +6 -0
  9. mpe2-0.0.1/mpe2/_mpe_utils/secrcode.ttf +0 -0
  10. mpe2-0.0.1/mpe2/_mpe_utils/simple_env.py +360 -0
  11. mpe2-0.0.1/mpe2/all_modules.py +23 -0
  12. mpe2-0.0.1/mpe2/simple/simple.py +131 -0
  13. mpe2-0.0.1/mpe2/simple_adversary/simple_adversary.py +260 -0
  14. mpe2-0.0.1/mpe2/simple_adversary_v3.py +3 -0
  15. mpe2-0.0.1/mpe2/simple_crypto/simple_crypto.py +263 -0
  16. mpe2-0.0.1/mpe2/simple_crypto_v3.py +3 -0
  17. mpe2-0.0.1/mpe2/simple_push/simple_push.py +199 -0
  18. mpe2-0.0.1/mpe2/simple_push_v3.py +3 -0
  19. mpe2-0.0.1/mpe2/simple_reference/simple_reference.py +186 -0
  20. mpe2-0.0.1/mpe2/simple_reference_v3.py +3 -0
  21. mpe2-0.0.1/mpe2/simple_speaker_listener/simple_speaker_listener.py +181 -0
  22. mpe2-0.0.1/mpe2/simple_speaker_listener_v4.py +7 -0
  23. mpe2-0.0.1/mpe2/simple_spread/simple_spread.py +204 -0
  24. mpe2-0.0.1/mpe2/simple_spread_v3.py +3 -0
  25. mpe2-0.0.1/mpe2/simple_tag/simple_tag.py +275 -0
  26. mpe2-0.0.1/mpe2/simple_tag_v3.py +3 -0
  27. mpe2-0.0.1/mpe2/simple_v3.py +3 -0
  28. mpe2-0.0.1/mpe2/simple_world_comm/simple_world_comm.py +480 -0
  29. mpe2-0.0.1/mpe2/simple_world_comm_v3.py +3 -0
  30. mpe2-0.0.1/mpe2.egg-info/PKG-INFO +44 -0
  31. mpe2-0.0.1/mpe2.egg-info/SOURCES.txt +35 -0
  32. mpe2-0.0.1/mpe2.egg-info/dependency_links.txt +1 -0
  33. mpe2-0.0.1/mpe2.egg-info/requires.txt +10 -0
  34. mpe2-0.0.1/mpe2.egg-info/top_level.txt +1 -0
  35. mpe2-0.0.1/pyproject.toml +44 -0
  36. mpe2-0.0.1/setup.cfg +4 -0
  37. mpe2-0.0.1/setup.py +21 -0
mpe2-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Farama Foundation
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.
mpe2-0.0.1/MANIFEST.in ADDED
@@ -0,0 +1,3 @@
1
+ recursive-include mpe2 *
2
+ global-exclude __pycache__
3
+ global-exclude *.pyc
mpe2-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.1
2
+ Name: mpe2
3
+ Version: 0.0.1
4
+ Summary: Multi Particle Environments Version 2
5
+ Author-email: Farama Foundation <contact@farama.org>
6
+ License: MIT License
7
+ Keywords: Reinforcement Learning,game,RL,AI,gymnasium
8
+ Classifier: Development Status :: 1 - Planning
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: pettingzoo>=1.24.3
18
+ Requires-Dist: pygame>=2.6.0
19
+ Provides-Extra: testing
20
+ Requires-Dist: pynput==1.7.6; extra == "testing"
21
+ Requires-Dist: pytest==8.0.0; extra == "testing"
22
+ Requires-Dist: pytest-cov==4.1.0; extra == "testing"
23
+ Requires-Dist: pytest-xdist==3.5.0; extra == "testing"
24
+ Requires-Dist: pre-commit==3.5.0; extra == "testing"
25
+ Requires-Dist: pytest-markdown-docs==0.5.0; extra == "testing"
26
+
27
+ # MPE2
28
+
29
+ ## Installation Steps
30
+
31
+ ```bash
32
+ # create venv
33
+ conda create -n mpe2_env python=3.10
34
+ conda activate mpe2_env
35
+
36
+ # clone repo
37
+ git clone https://github.com/Farama-Foundation/MPE2.git
38
+
39
+ # for development
40
+ python3 -m pip install -e .[testing, documentation]
41
+
42
+ # for usage
43
+ python3 -m pip install -e .
44
+ ```
mpe2-0.0.1/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # MPE2
2
+
3
+ ## Installation Steps
4
+
5
+ ```bash
6
+ # create venv
7
+ conda create -n mpe2_env python=3.10
8
+ conda activate mpe2_env
9
+
10
+ # clone repo
11
+ git clone https://github.com/Farama-Foundation/MPE2.git
12
+
13
+ # for development
14
+ python3 -m pip install -e .[testing, documentation]
15
+
16
+ # for usage
17
+ python3 -m pip install -e .
18
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
File without changes
@@ -0,0 +1,218 @@
1
+ import numpy as np
2
+
3
+
4
+ class EntityState: # physical/external base state of all entities
5
+ def __init__(self):
6
+ # physical position
7
+ self.p_pos = None
8
+ # physical velocity
9
+ self.p_vel = None
10
+
11
+
12
+ class AgentState(
13
+ EntityState
14
+ ): # state of agents (including communication and internal/mental state)
15
+ def __init__(self):
16
+ super().__init__()
17
+ # communication utterance
18
+ self.c = None
19
+
20
+
21
+ class Action: # action of the agent
22
+ def __init__(self):
23
+ # physical action
24
+ self.u = None
25
+ # communication action
26
+ self.c = None
27
+
28
+
29
+ class Entity: # properties and state of physical world entity
30
+ def __init__(self):
31
+ # name
32
+ self.name = ""
33
+ # properties:
34
+ self.size = 0.050
35
+ # entity can move / be pushed
36
+ self.movable = False
37
+ # entity collides with others
38
+ self.collide = True
39
+ # material density (affects mass)
40
+ self.density = 25.0
41
+ # color
42
+ self.color = None
43
+ # max speed and accel
44
+ self.max_speed = None
45
+ self.accel = None
46
+ # state
47
+ self.state = EntityState()
48
+ # mass
49
+ self.initial_mass = 1.0
50
+
51
+ @property
52
+ def mass(self):
53
+ return self.initial_mass
54
+
55
+
56
+ class Landmark(Entity): # properties of landmark entities
57
+ def __init__(self):
58
+ super().__init__()
59
+
60
+
61
+ class Agent(Entity): # properties of agent entities
62
+ def __init__(self):
63
+ super().__init__()
64
+ # agents are movable by default
65
+ self.movable = True
66
+ # cannot send communication signals
67
+ self.silent = False
68
+ # cannot observe the world
69
+ self.blind = False
70
+ # physical motor noise amount
71
+ self.u_noise = None
72
+ # communication noise amount
73
+ self.c_noise = None
74
+ # control range
75
+ self.u_range = 1.0
76
+ # state
77
+ self.state = AgentState()
78
+ # action
79
+ self.action = Action()
80
+ # script behavior to execute
81
+ self.action_callback = None
82
+
83
+
84
+ class World: # multi-agent world
85
+ def __init__(self):
86
+ # list of agents and entities (can change at execution-time!)
87
+ self.agents = []
88
+ self.landmarks = []
89
+ # communication channel dimensionality
90
+ self.dim_c = 0
91
+ # position dimensionality
92
+ self.dim_p = 2
93
+ # color dimensionality
94
+ self.dim_color = 3
95
+ # simulation timestep
96
+ self.dt = 0.1
97
+ # physical damping
98
+ self.damping = 0.25
99
+ # contact response parameters
100
+ self.contact_force = 1e2
101
+ self.contact_margin = 1e-3
102
+
103
+ # return all entities in the world
104
+ @property
105
+ def entities(self):
106
+ return self.agents + self.landmarks
107
+
108
+ # return all agents controllable by external policies
109
+ @property
110
+ def policy_agents(self):
111
+ return [agent for agent in self.agents if agent.action_callback is None]
112
+
113
+ # return all agents controlled by world scripts
114
+ @property
115
+ def scripted_agents(self):
116
+ return [agent for agent in self.agents if agent.action_callback is not None]
117
+
118
+ # update state of the world
119
+ def step(self):
120
+ # set actions for scripted agents
121
+ for agent in self.scripted_agents:
122
+ agent.action = agent.action_callback(agent, self)
123
+ # gather forces applied to entities
124
+ p_force = [None] * len(self.entities)
125
+ # apply agent physical controls
126
+ p_force = self.apply_action_force(p_force)
127
+ # apply environment forces
128
+ p_force = self.apply_environment_force(p_force)
129
+ # integrate physical state
130
+ self.integrate_state(p_force)
131
+ # update agent state
132
+ for agent in self.agents:
133
+ self.update_agent_state(agent)
134
+
135
+ # gather agent action forces
136
+ def apply_action_force(self, p_force):
137
+ # set applied forces
138
+ for i, agent in enumerate(self.agents):
139
+ if agent.movable:
140
+ noise = (
141
+ np.random.randn(*agent.action.u.shape) * agent.u_noise
142
+ if agent.u_noise
143
+ else 0.0
144
+ )
145
+ p_force[i] = agent.action.u + noise
146
+ return p_force
147
+
148
+ # gather physical forces acting on entities
149
+ def apply_environment_force(self, p_force):
150
+ # simple (but inefficient) collision response
151
+ for a, entity_a in enumerate(self.entities):
152
+ for b, entity_b in enumerate(self.entities):
153
+ if b <= a:
154
+ continue
155
+ [f_a, f_b] = self.get_collision_force(entity_a, entity_b)
156
+ if f_a is not None:
157
+ if p_force[a] is None:
158
+ p_force[a] = 0.0
159
+ p_force[a] = f_a + p_force[a]
160
+ if f_b is not None:
161
+ if p_force[b] is None:
162
+ p_force[b] = 0.0
163
+ p_force[b] = f_b + p_force[b]
164
+ return p_force
165
+
166
+ # integrate physical state
167
+ def integrate_state(self, p_force):
168
+ for i, entity in enumerate(self.entities):
169
+ if not entity.movable:
170
+ continue
171
+ entity.state.p_pos += entity.state.p_vel * self.dt
172
+ entity.state.p_vel = entity.state.p_vel * (1 - self.damping)
173
+ if p_force[i] is not None:
174
+ entity.state.p_vel += (p_force[i] / entity.mass) * self.dt
175
+ if entity.max_speed is not None:
176
+ speed = np.sqrt(
177
+ np.square(entity.state.p_vel[0]) + np.square(entity.state.p_vel[1])
178
+ )
179
+ if speed > entity.max_speed:
180
+ entity.state.p_vel = (
181
+ entity.state.p_vel
182
+ / np.sqrt(
183
+ np.square(entity.state.p_vel[0])
184
+ + np.square(entity.state.p_vel[1])
185
+ )
186
+ * entity.max_speed
187
+ )
188
+
189
+ def update_agent_state(self, agent):
190
+ # set communication state (directly for now)
191
+ if agent.silent:
192
+ agent.state.c = np.zeros(self.dim_c)
193
+ else:
194
+ noise = (
195
+ np.random.randn(*agent.action.c.shape) * agent.c_noise
196
+ if agent.c_noise
197
+ else 0.0
198
+ )
199
+ agent.state.c = agent.action.c + noise
200
+
201
+ # get collision forces for any contact between two entities
202
+ def get_collision_force(self, entity_a, entity_b):
203
+ if (not entity_a.collide) or (not entity_b.collide):
204
+ return [None, None] # not a collider
205
+ if entity_a is entity_b:
206
+ return [None, None] # don't collide against itself
207
+ # compute actual distance between entities
208
+ delta_pos = entity_a.state.p_pos - entity_b.state.p_pos
209
+ dist = np.sqrt(np.sum(np.square(delta_pos)))
210
+ # minimum allowable distance
211
+ dist_min = entity_a.size + entity_b.size
212
+ # softmax penetration
213
+ k = self.contact_margin
214
+ penetration = np.logaddexp(0, -(dist - dist_min) / k) * k
215
+ force = self.contact_force * delta_pos / dist * penetration
216
+ force_a = +force if entity_a.movable else None
217
+ force_b = -force if entity_b.movable else None
218
+ return [force_a, force_b]
@@ -0,0 +1,6 @@
1
+ class BaseScenario: # defines scenario upon which the world is built
2
+ def make_world(self): # create elements of the world
3
+ raise NotImplementedError()
4
+
5
+ def reset_world(self, world, np_random): # create initial conditions of the world
6
+ raise NotImplementedError()
Binary file
@@ -0,0 +1,360 @@
1
+ import os
2
+
3
+ import gymnasium
4
+ import numpy as np
5
+ import pygame
6
+ from gymnasium import spaces
7
+ from gymnasium.utils import seeding
8
+ from pettingzoo import AECEnv
9
+ from pettingzoo.utils import wrappers
10
+ from pettingzoo.utils.agent_selector import AgentSelector
11
+
12
+ from mpe2._mpe_utils.core import Agent
13
+
14
+ alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
15
+
16
+
17
+ def make_env(raw_env):
18
+ def env(**kwargs):
19
+ env = raw_env(**kwargs)
20
+ if env.continuous_actions:
21
+ env = wrappers.ClipOutOfBoundsWrapper(env)
22
+ else:
23
+ env = wrappers.AssertOutOfBoundsWrapper(env)
24
+ env = wrappers.OrderEnforcingWrapper(env)
25
+ return env
26
+
27
+ return env
28
+
29
+
30
+ class SimpleEnv(AECEnv):
31
+ metadata = {
32
+ "render_modes": ["human", "rgb_array"],
33
+ "is_parallelizable": True,
34
+ "render_fps": 10,
35
+ }
36
+
37
+ def __init__(
38
+ self,
39
+ scenario,
40
+ world,
41
+ max_cycles,
42
+ render_mode=None,
43
+ continuous_actions=False,
44
+ local_ratio=None,
45
+ dynamic_rescaling=False,
46
+ ):
47
+ super().__init__()
48
+
49
+ self.render_mode = render_mode
50
+ pygame.init()
51
+ self.viewer = None
52
+ self.width = 700
53
+ self.height = 700
54
+ self.screen = pygame.Surface([self.width, self.height])
55
+ self.max_size = 1
56
+ self.game_font = pygame.freetype.Font(
57
+ os.path.join(os.path.dirname(__file__), "secrcode.ttf"), 24
58
+ )
59
+
60
+ # Set up the drawing window
61
+
62
+ self.renderOn = False
63
+ self._seed()
64
+
65
+ self.max_cycles = max_cycles
66
+ self.scenario = scenario
67
+ self.world = world
68
+ self.continuous_actions = continuous_actions
69
+ self.local_ratio = local_ratio
70
+ self.dynamic_rescaling = dynamic_rescaling
71
+
72
+ self.scenario.reset_world(self.world, self.np_random)
73
+
74
+ self.agents = [agent.name for agent in self.world.agents]
75
+ self.possible_agents = self.agents[:]
76
+ self._index_map = {
77
+ agent.name: idx for idx, agent in enumerate(self.world.agents)
78
+ }
79
+
80
+ self._agent_selector = AgentSelector(self.agents)
81
+
82
+ # set spaces
83
+ self.action_spaces = dict()
84
+ self.observation_spaces = dict()
85
+ state_dim = 0
86
+ for agent in self.world.agents:
87
+ if agent.movable:
88
+ space_dim = self.world.dim_p * 2 + 1
89
+ elif self.continuous_actions:
90
+ space_dim = 0
91
+ else:
92
+ space_dim = 1
93
+ if not agent.silent:
94
+ if self.continuous_actions:
95
+ space_dim += self.world.dim_c
96
+ else:
97
+ space_dim *= self.world.dim_c
98
+
99
+ obs_dim = len(self.scenario.observation(agent, self.world))
100
+ state_dim += obs_dim
101
+ if self.continuous_actions:
102
+ self.action_spaces[agent.name] = spaces.Box(
103
+ low=0, high=1, shape=(space_dim,)
104
+ )
105
+ else:
106
+ self.action_spaces[agent.name] = spaces.Discrete(space_dim)
107
+ self.observation_spaces[agent.name] = spaces.Box(
108
+ low=-np.float32(np.inf),
109
+ high=+np.float32(np.inf),
110
+ shape=(obs_dim,),
111
+ dtype=np.float32,
112
+ )
113
+
114
+ self.state_space = spaces.Box(
115
+ low=-np.float32(np.inf),
116
+ high=+np.float32(np.inf),
117
+ shape=(state_dim,),
118
+ dtype=np.float32,
119
+ )
120
+
121
+ # Get the original cam_range
122
+ # This will be used to scale the rendering
123
+ all_poses = [entity.state.p_pos for entity in self.world.entities]
124
+ self.original_cam_range = np.max(np.abs(np.array(all_poses)))
125
+
126
+ self.steps = 0
127
+
128
+ self.current_actions = [None] * self.num_agents
129
+
130
+ def observation_space(self, agent):
131
+ return self.observation_spaces[agent]
132
+
133
+ def action_space(self, agent):
134
+ return self.action_spaces[agent]
135
+
136
+ def _seed(self, seed=None):
137
+ self.np_random, seed = seeding.np_random(seed)
138
+
139
+ def observe(self, agent):
140
+ return self.scenario.observation(
141
+ self.world.agents[self._index_map[agent]], self.world
142
+ ).astype(np.float32)
143
+
144
+ def state(self):
145
+ states = tuple(
146
+ self.scenario.observation(
147
+ self.world.agents[self._index_map[agent]], self.world
148
+ ).astype(np.float32)
149
+ for agent in self.possible_agents
150
+ )
151
+ return np.concatenate(states, axis=None)
152
+
153
+ def reset(self, seed=None, options=None):
154
+ if seed is not None:
155
+ self._seed(seed=seed)
156
+ self.scenario.reset_world(self.world, self.np_random)
157
+
158
+ self.agents = self.possible_agents[:]
159
+ self.rewards = {name: 0.0 for name in self.agents}
160
+ self._cumulative_rewards = {name: 0.0 for name in self.agents}
161
+ self.terminations = {name: False for name in self.agents}
162
+ self.truncations = {name: False for name in self.agents}
163
+ self.infos = {name: {} for name in self.agents}
164
+
165
+ self.agent_selection = self._agent_selector.reset()
166
+ self.steps = 0
167
+
168
+ self.current_actions = [None] * self.num_agents
169
+
170
+ def _execute_world_step(self):
171
+ # set action for each agent
172
+ for i, agent in enumerate(self.world.agents):
173
+ action = self.current_actions[i]
174
+ scenario_action = []
175
+ if agent.movable:
176
+ mdim = self.world.dim_p * 2 + 1
177
+ if self.continuous_actions:
178
+ scenario_action.append(action[0:mdim])
179
+ action = action[mdim:]
180
+ else:
181
+ scenario_action.append(action % mdim)
182
+ action //= mdim
183
+ if not agent.silent:
184
+ scenario_action.append(action)
185
+ self._set_action(scenario_action, agent, self.action_spaces[agent.name])
186
+
187
+ self.world.step()
188
+
189
+ global_reward = 0.0
190
+ if self.local_ratio is not None:
191
+ global_reward = float(self.scenario.global_reward(self.world))
192
+
193
+ for agent in self.world.agents:
194
+ agent_reward = float(self.scenario.reward(agent, self.world))
195
+ if self.local_ratio is not None:
196
+ reward = (
197
+ global_reward * (1 - self.local_ratio)
198
+ + agent_reward * self.local_ratio
199
+ )
200
+ else:
201
+ reward = agent_reward
202
+
203
+ self.rewards[agent.name] = reward
204
+
205
+ # set env action for a particular agent
206
+ def _set_action(self, action, agent, action_space, time=None):
207
+ agent.action.u = np.zeros(self.world.dim_p)
208
+ agent.action.c = np.zeros(self.world.dim_c)
209
+
210
+ if agent.movable:
211
+ # physical action
212
+ agent.action.u = np.zeros(self.world.dim_p)
213
+ if self.continuous_actions:
214
+ # Process continuous action as in OpenAI MPE
215
+ # Note: this ordering preserves the same movement direction as in the discrete case
216
+ agent.action.u[0] += action[0][2] - action[0][1]
217
+ agent.action.u[1] += action[0][4] - action[0][3]
218
+ else:
219
+ # process discrete action
220
+ if action[0] == 1:
221
+ agent.action.u[0] = -1.0
222
+ if action[0] == 2:
223
+ agent.action.u[0] = +1.0
224
+ if action[0] == 3:
225
+ agent.action.u[1] = -1.0
226
+ if action[0] == 4:
227
+ agent.action.u[1] = +1.0
228
+ sensitivity = 5.0
229
+ if agent.accel is not None:
230
+ sensitivity = agent.accel
231
+ agent.action.u *= sensitivity
232
+ action = action[1:]
233
+ if not agent.silent:
234
+ # communication action
235
+ if self.continuous_actions:
236
+ agent.action.c = action[0]
237
+ else:
238
+ agent.action.c = np.zeros(self.world.dim_c)
239
+ agent.action.c[action[0]] = 1.0
240
+ action = action[1:]
241
+ # make sure we used all elements of action
242
+ assert len(action) == 0
243
+
244
+ def step(self, action):
245
+ if (
246
+ self.terminations[self.agent_selection]
247
+ or self.truncations[self.agent_selection]
248
+ ):
249
+ self._was_dead_step(action)
250
+ return
251
+ cur_agent = self.agent_selection
252
+ current_idx = self._index_map[self.agent_selection]
253
+ next_idx = (current_idx + 1) % self.num_agents
254
+ self.agent_selection = self._agent_selector.next()
255
+
256
+ self.current_actions[current_idx] = action
257
+
258
+ if next_idx == 0:
259
+ self._execute_world_step()
260
+ self.steps += 1
261
+ if self.steps >= self.max_cycles:
262
+ for a in self.agents:
263
+ self.truncations[a] = True
264
+ else:
265
+ self._clear_rewards()
266
+
267
+ self._cumulative_rewards[cur_agent] = 0
268
+ self._accumulate_rewards()
269
+
270
+ if self.render_mode == "human":
271
+ self.render()
272
+
273
+ def enable_render(self, mode="human"):
274
+ if not self.renderOn and mode == "human":
275
+ self.screen = pygame.display.set_mode(self.screen.get_size())
276
+ self.clock = pygame.time.Clock()
277
+ self.renderOn = True
278
+
279
+ def render(self):
280
+ if self.render_mode is None:
281
+ gymnasium.logger.warn(
282
+ "You are calling render method without specifying any render mode."
283
+ )
284
+ return
285
+
286
+ self.enable_render(self.render_mode)
287
+
288
+ self.draw()
289
+ if self.render_mode == "rgb_array":
290
+ observation = np.array(pygame.surfarray.pixels3d(self.screen))
291
+ return np.transpose(observation, axes=(1, 0, 2))
292
+ elif self.render_mode == "human":
293
+ pygame.display.flip()
294
+ self.clock.tick(self.metadata["render_fps"])
295
+ return
296
+
297
+ def draw(self):
298
+ # clear screen
299
+ self.screen.fill((255, 255, 255))
300
+
301
+ # update bounds to center around agent
302
+ all_poses = [entity.state.p_pos for entity in self.world.entities]
303
+ cam_range = np.max(np.abs(np.array(all_poses)))
304
+
305
+ # The scaling factor is used for dynamic rescaling of the rendering - a.k.a Zoom In/Zoom Out effect
306
+ # The 0.9 is a factor to keep the entities from appearing "too" out-of-bounds
307
+ scaling_factor = 0.9 * self.original_cam_range / cam_range
308
+
309
+ # update geometry and text positions
310
+ text_line = 0
311
+ for e, entity in enumerate(self.world.entities):
312
+ # geometry
313
+ x, y = entity.state.p_pos
314
+ y *= (
315
+ -1
316
+ ) # this makes the display mimic the old pyglet setup (ie. flips image)
317
+ x = (
318
+ (x / cam_range) * self.width // 2 * 0.9
319
+ ) # the .9 is just to keep entities from appearing "too" out-of-bounds
320
+ y = (y / cam_range) * self.height // 2 * 0.9
321
+ x += self.width // 2
322
+ y += self.height // 2
323
+
324
+ # 350 is an arbitrary scale factor to get pygame to render similar sizes as pyglet
325
+ if self.dynamic_rescaling:
326
+ radius = entity.size * 350 * scaling_factor
327
+ else:
328
+ radius = entity.size * 350
329
+
330
+ pygame.draw.circle(self.screen, entity.color * 200, (x, y), radius)
331
+ pygame.draw.circle(self.screen, (0, 0, 0), (x, y), radius, 1) # borders
332
+ assert (
333
+ 0 < x < self.width and 0 < y < self.height
334
+ ), f"Coordinates {(x, y)} are out of bounds."
335
+
336
+ # text
337
+ if isinstance(entity, Agent):
338
+ if entity.silent:
339
+ continue
340
+ if np.all(entity.state.c == 0):
341
+ word = "_"
342
+ elif self.continuous_actions:
343
+ word = (
344
+ "[" + ",".join([f"{comm:.2f}" for comm in entity.state.c]) + "]"
345
+ )
346
+ else:
347
+ word = alphabet[np.argmax(entity.state.c)]
348
+
349
+ message = entity.name + " sends " + word + " "
350
+ message_x_pos = self.width * 0.05
351
+ message_y_pos = self.height * 0.95 - (self.height * 0.05 * text_line)
352
+ self.game_font.render_to(
353
+ self.screen, (message_x_pos, message_y_pos), message, (0, 0, 0)
354
+ )
355
+ text_line += 1
356
+
357
+ def close(self):
358
+ if self.screen is not None:
359
+ pygame.quit()
360
+ self.screen = None