miniworld-maze 1.0.0__py3-none-any.whl → 1.1.0__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.

Potentially problematic release.


This version of miniworld-maze might be problematic. Click here for more details.

@@ -18,8 +18,6 @@ Main modules:
18
18
  from .core import ObservationLevel
19
19
  from .environments.factory import (
20
20
  NineRoomsEnvironmentWrapper,
21
- create_drstrategy_env,
22
- create_nine_rooms_env,
23
21
  )
24
22
  from .environments.nine_rooms import NineRooms
25
23
  from .environments.spiral_nine_rooms import SpiralNineRooms
@@ -27,8 +25,6 @@ from .environments.twenty_five_rooms import TwentyFiveRooms
27
25
 
28
26
  __version__ = "1.0.0"
29
27
  __all__ = [
30
- "create_drstrategy_env",
31
- "create_nine_rooms_env", # deprecated but kept for backward compatibility
32
28
  "NineRoomsEnvironmentWrapper",
33
29
  "NineRooms",
34
30
  "SpiralNineRooms",
@@ -3,6 +3,7 @@
3
3
  import math
4
4
  from ctypes import POINTER
5
5
  from enum import IntEnum
6
+ from typing import List, Optional
6
7
 
7
8
  import gymnasium as gym
8
9
  import numpy as np
@@ -10,6 +11,7 @@ import pyglet
10
11
  from gymnasium import spaces
11
12
  from pyglet.gl import *
12
13
 
14
+ from ..observation_types import ObservationLevel
13
15
  from .entities import *
14
16
  from .math import *
15
17
  from .objmesh import *
@@ -95,6 +97,7 @@ class UnifiedMiniWorldEnv(gym.Env):
95
97
  window_height=DEFAULT_WINDOW_HEIGHT,
96
98
  params=DEFAULT_PARAMS,
97
99
  domain_rand=False,
100
+ info_obs: Optional[List[ObservationLevel]] = None,
98
101
  ):
99
102
  """
100
103
  Initialize unified MiniWorld environment.
@@ -110,6 +113,7 @@ class UnifiedMiniWorldEnv(gym.Env):
110
113
  window_height: Window height for human rendering
111
114
  params: Environment parameters for domain randomization
112
115
  domain_rand: Whether to enable domain randomization
116
+ info_obs: List of observation levels to include in info dictionary
113
117
  """
114
118
  # Store configuration
115
119
  self.obs_level = obs_level
@@ -118,6 +122,7 @@ class UnifiedMiniWorldEnv(gym.Env):
118
122
  self.max_episode_steps = max_episode_steps
119
123
  self.params = params
120
124
  self.domain_rand = domain_rand
125
+ self.info_obs = info_obs
121
126
 
122
127
  # Setup action space
123
128
  self._setup_action_space()
@@ -329,8 +334,20 @@ class UnifiedMiniWorldEnv(gym.Env):
329
334
  # Generate the first camera image
330
335
  obs = self._generate_observation()
331
336
 
337
+ # Generate additional observations for info dictionary if specified
338
+ info = {}
339
+ if self.info_obs is not None:
340
+ for obs_level in self.info_obs:
341
+ # Temporarily change obs_level to generate the desired observation
342
+ original_obs_level = self.obs_level
343
+ self.obs_level = obs_level
344
+ info_obs = self._generate_observation()
345
+ self.obs_level = original_obs_level
346
+ # Use the observation level name as key
347
+ info[str(obs_level)] = info_obs
348
+
332
349
  # Return first observation with info dict for Gymnasium compatibility
333
- return obs, {}
350
+ return obs, info
334
351
 
335
352
  def _generate_observation(self, render_agent: bool = None):
336
353
  """Generate observation based on current observation level.
@@ -369,18 +386,6 @@ class UnifiedMiniWorldEnv(gym.Env):
369
386
  f"Invalid obs_level {self.obs_level}. Must be one of {valid_levels}"
370
387
  )
371
388
 
372
- def get_observation(self, render_agent: bool = None):
373
- """Public method to generate observation with optional agent rendering control.
374
-
375
- Args:
376
- render_agent: Whether to render the agent in the observation.
377
- If None, uses default behavior based on observation level.
378
-
379
- Returns:
380
- np.ndarray: Generated observation image
381
- """
382
- return self._generate_observation(render_agent=render_agent)
383
-
384
389
  def _calculate_carried_object_position(self, agent_pos, ent):
385
390
  """Compute the position at which to place an object being carried."""
386
391
  dist = self.agent.radius + ent.radius + self.max_forward_step
@@ -577,21 +582,37 @@ class UnifiedMiniWorldEnv(gym.Env):
577
582
  if self.obs_level != 2: # Not TOP_DOWN_FULL
578
583
  topdown = self.render_top_view(POMDP=False, frame_buffer=self.topdown_fb)
579
584
 
585
+ # Generate additional observations for info dictionary if specified
586
+ info = {}
587
+ if self.info_obs is not None:
588
+ for obs_level in self.info_obs:
589
+ # Temporarily change obs_level to generate the desired observation
590
+ original_obs_level = self.obs_level
591
+ self.obs_level = obs_level
592
+ info_obs = self._generate_observation()
593
+ self.obs_level = original_obs_level
594
+ # Use the observation level name as key
595
+ info[str(obs_level)] = info_obs
596
+
580
597
  # Check termination
581
598
  if self.step_count >= self.max_episode_steps:
582
599
  terminated = True
583
600
  reward = 0
584
- info = {
585
- "pos": self.agent.pos,
586
- "mdp_view": topdown if topdown is not None else observation,
587
- }
601
+ info.update(
602
+ {
603
+ "pos": self.agent.pos,
604
+ "mdp_view": topdown if topdown is not None else observation,
605
+ }
606
+ )
588
607
  else:
589
608
  reward = 0
590
609
  terminated = False
591
- info = {
592
- "pos": self.agent.pos,
593
- "mdp_view": topdown if topdown is not None else observation,
594
- }
610
+ info.update(
611
+ {
612
+ "pos": self.agent.pos,
613
+ "mdp_view": topdown if topdown is not None else observation,
614
+ }
615
+ )
595
616
 
596
617
  return reward, terminated, info
597
618
 
@@ -1,14 +1,13 @@
1
1
  """Nine Rooms environment implementations."""
2
2
 
3
3
  from .base_grid_rooms import GridRoomsEnvironment
4
- from .factory import NineRoomsEnvironmentWrapper, create_nine_rooms_env
4
+ from .factory import NineRoomsEnvironmentWrapper
5
5
  from .nine_rooms import NineRooms
6
6
  from .spiral_nine_rooms import SpiralNineRooms
7
7
  from .twenty_five_rooms import TwentyFiveRooms
8
8
 
9
9
  __all__ = [
10
10
  "GridRoomsEnvironment",
11
- "create_nine_rooms_env",
12
11
  "NineRoomsEnvironmentWrapper",
13
12
  "NineRooms",
14
13
  "SpiralNineRooms",
@@ -2,6 +2,8 @@
2
2
 
3
3
  from typing import List, Optional, Tuple, Union
4
4
 
5
+ import cv2
6
+ import numpy as np
5
7
  from gymnasium import spaces
6
8
 
7
9
  from ..core import COLORS, Box, ObservationLevel
@@ -35,6 +37,7 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
35
37
  grid_size: int,
36
38
  connections: List[Tuple[int, int]],
37
39
  textures: List[str],
40
+ goal_positions: List[List[List[float]]],
38
41
  placed_room: Optional[int] = None,
39
42
  obs_level: ObservationLevel = ObservationLevel.TOP_DOWN_PARTIAL,
40
43
  continuous: bool = False,
@@ -52,6 +55,7 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
52
55
  grid_size: Size of the grid (e.g., 3 for 3x3 grid)
53
56
  connections: List of (room1, room2) tuples for connections
54
57
  textures: List of texture names for each room
58
+ goal_positions: List of goal positions for each room
55
59
  placed_room: Initial room index (defaults to 0)
56
60
  obs_level: Observation level (defaults to 1)
57
61
  continuous: Whether to use continuous actions (defaults to False)
@@ -77,6 +81,9 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
77
81
  )
78
82
  self.textures = textures
79
83
 
84
+ # Set goal positions
85
+ self.goal_positions = goal_positions
86
+
80
87
  # Set placed room
81
88
  if placed_room is None:
82
89
  self.placed_room = 0 # Start in the first room
@@ -101,6 +108,10 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
101
108
  # Mark this as a custom environment for background color handling
102
109
  self._is_custom_env = True
103
110
 
111
+ # Store observation dimensions for rendering (needed before super().__init__)
112
+ self.obs_width = obs_width
113
+ self.obs_height = obs_height
114
+
104
115
  super().__init__(
105
116
  obs_level=obs_level,
106
117
  max_episode_steps=MAX_EPISODE_STEPS,
@@ -114,6 +125,18 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
114
125
  if not self.continuous:
115
126
  self.action_space = spaces.Discrete(self.actions.move_forward + 1)
116
127
 
128
+ # Store original observation space before updating
129
+ original_obs_space = self.observation_space
130
+
131
+ # Update observation space to include desired_goal and achieved_goal
132
+ self.observation_space = spaces.Dict(
133
+ {
134
+ "observation": original_obs_space,
135
+ "desired_goal": original_obs_space,
136
+ "achieved_goal": original_obs_space,
137
+ }
138
+ )
139
+
117
140
  def _generate_world_layout(self, pos=None):
118
141
  rooms = []
119
142
 
@@ -201,4 +224,120 @@ class GridRoomsEnvironment(UnifiedMiniWorldEnv):
201
224
 
202
225
  def step(self, action):
203
226
  obs, reward, terminated, truncated, info = super().step(action)
204
- return obs, reward, terminated, truncated, info
227
+
228
+ # Check if goal is achieved
229
+ if self.is_goal_achieved():
230
+ terminated = True
231
+ reward = 1.0 # Positive reward for achieving goal
232
+
233
+ # Return observation as dict
234
+ obs_dict = {
235
+ "observation": obs,
236
+ "desired_goal": self.desired_goal,
237
+ "achieved_goal": obs,
238
+ }
239
+ return obs_dict, reward, terminated, truncated, info
240
+
241
+ def reset(self, seed=None, options=None, pos=None):
242
+ """
243
+ Reset the environment and generate a new goal.
244
+
245
+ Args:
246
+ seed: Random seed
247
+ options: Additional options
248
+ pos: Agent starting position
249
+
250
+ Returns:
251
+ tuple: (observation, info)
252
+ """
253
+ # Call parent reset
254
+ obs, info = super().reset(seed=seed, options=options, pos=pos)
255
+
256
+ # Generate goal
257
+ self.desired_goal = self.get_goal()
258
+
259
+ # Return observation as dict with desired_goal and achieved_goal
260
+ obs_dict = {
261
+ "observation": obs,
262
+ "desired_goal": self.desired_goal,
263
+ "achieved_goal": obs,
264
+ }
265
+ return obs_dict, info
266
+
267
+ def get_goal(self):
268
+ """
269
+ Generate a goal by randomly selecting a room and goal position.
270
+
271
+ Returns:
272
+ np.ndarray: Rendered goal image
273
+ """
274
+ # Select random room
275
+ room_idx = np.random.randint(len(self.goal_positions))
276
+
277
+ # Select random goal within room
278
+ goal_idx = np.random.randint(len(self.goal_positions[room_idx]))
279
+
280
+ # Get goal position
281
+ goal_position = self.goal_positions[room_idx][goal_idx]
282
+ self._current_goal_position = goal_position
283
+ self._current_goal_room = room_idx
284
+ self._current_goal_idx = goal_idx
285
+
286
+ # Render goal image
287
+ goal_image = self.render_on_pos(goal_position)
288
+
289
+ return goal_image
290
+
291
+ def render_on_pos(self, pos):
292
+ """
293
+ Render observation from a specific position.
294
+
295
+ Args:
296
+ pos: Position to render from [x, y, z]
297
+
298
+ Returns:
299
+ np.ndarray: Rendered observation
300
+ """
301
+ # Store current agent position
302
+ current_pos = self.agent.pos.copy()
303
+
304
+ # Move agent to target position
305
+ self.place_agent(pos=pos)
306
+
307
+ # Render observation from this position
308
+ obs = self.render_top_view(POMDP=True, render_ag=False)
309
+
310
+ # Resize to match observation dimensions if needed
311
+ if obs.shape[:2] != (self.obs_height, self.obs_width):
312
+ obs = cv2.resize(
313
+ obs, (self.obs_width, self.obs_height), interpolation=cv2.INTER_AREA
314
+ )
315
+
316
+ # Restore agent position
317
+ self.place_agent(pos=current_pos)
318
+
319
+ return obs
320
+
321
+ def is_goal_achieved(self, pos=None, threshold=0.5):
322
+ """
323
+ Check if the agent has achieved the current goal.
324
+
325
+ Args:
326
+ pos: Agent position to check (uses current agent pos if None)
327
+ threshold: Distance threshold for goal achievement
328
+
329
+ Returns:
330
+ bool: True if goal is achieved
331
+ """
332
+ if pos is None:
333
+ pos = self.agent.pos
334
+
335
+ if not hasattr(self, "_current_goal_position"):
336
+ return False
337
+
338
+ # Convert to numpy arrays and calculate distance
339
+ pos_array = np.array(pos)
340
+ goal_array = np.array(self._current_goal_position)
341
+ distance = np.linalg.norm(pos_array - goal_array)
342
+
343
+ return bool(distance < threshold)
@@ -1,11 +1,12 @@
1
1
  """Factory for creating Nine Rooms environment variants."""
2
2
 
3
+ from typing import List
4
+
3
5
  import gymnasium as gym
4
6
  import numpy as np
5
7
 
6
8
  from ..core import ObservationLevel
7
9
  from ..core.constants import FACTORY_DOOR_SIZE, FACTORY_ROOM_SIZE
8
- from ..wrappers.image_transforms import ImageToPyTorch
9
10
  from .nine_rooms import NineRooms
10
11
  from .spiral_nine_rooms import SpiralNineRooms
11
12
  from .twenty_five_rooms import TwentyFiveRooms
@@ -23,6 +24,7 @@ class NineRoomsEnvironmentWrapper(gym.Wrapper):
23
24
  room_size=FACTORY_ROOM_SIZE,
24
25
  door_size=FACTORY_DOOR_SIZE,
25
26
  agent_mode=None,
27
+ info_obs: List[ObservationLevel] = None,
26
28
  ):
27
29
  """
28
30
  Create a Nine Rooms environment variant.
@@ -35,6 +37,7 @@ class NineRoomsEnvironmentWrapper(gym.Wrapper):
35
37
  room_size: Size of each room in environment units
36
38
  door_size: Size of doors between rooms
37
39
  agent_mode: Agent rendering mode ('empty', 'circle', 'triangle', or None for default)
40
+ info_obs: List of observation levels to include in info dictionary
38
41
  """
39
42
  self.variant = variant
40
43
 
@@ -61,13 +64,13 @@ class NineRoomsEnvironmentWrapper(gym.Wrapper):
61
64
  obs_width=size,
62
65
  obs_height=size,
63
66
  agent_mode=agent_mode,
67
+ info_obs=info_obs,
64
68
  )
65
69
 
66
70
  # Apply wrappers - no resize needed since we render at target size
67
- env = ImageToPyTorch(base_env)
68
71
 
69
- # Initialize gym.Wrapper with the wrapped environment
70
- super().__init__(env)
72
+ # Initialize gym.Wrapper with the base environment
73
+ super().__init__(base_env)
71
74
 
72
75
  def render_on_pos(self, pos):
73
76
  """Render observation from a specific position."""
@@ -100,56 +103,3 @@ class NineRoomsEnvironmentWrapper(gym.Wrapper):
100
103
  return obs
101
104
 
102
105
 
103
- def create_drstrategy_env(variant="NineRooms", **kwargs):
104
- """
105
- Factory function to create DrStrategy environment variants.
106
-
107
- Args:
108
- variant: Environment variant ("NineRooms", "SpiralNineRooms", "TwentyFiveRooms")
109
- **kwargs: Additional arguments passed to NineRoomsEnvironmentWrapper
110
-
111
- Returns:
112
- NineRoomsEnvironmentWrapper instance
113
- """
114
- return NineRoomsEnvironmentWrapper(variant=variant, **kwargs)
115
-
116
-
117
- # Backward compatibility alias
118
- def create_nine_rooms_env(variant="NineRooms", **kwargs):
119
- """
120
- Legacy factory function for backward compatibility.
121
-
122
- Deprecated: Use create_drstrategy_env() instead.
123
- """
124
- import warnings
125
-
126
- warnings.warn(
127
- "create_nine_rooms_env() is deprecated. Use create_drstrategy_env() instead.",
128
- DeprecationWarning,
129
- stacklevel=2,
130
- )
131
- return create_drstrategy_env(variant=variant, **kwargs)
132
-
133
-
134
- # Legacy function - deprecated
135
- def NineRoomsFullyPureGymnasium(
136
- name="NineRooms",
137
- obs_level=ObservationLevel.TOP_DOWN_PARTIAL,
138
- continuous=False,
139
- size=64,
140
- ):
141
- """
142
- Legacy function for backward compatibility.
143
-
144
- Deprecated: Use create_drstrategy_env() instead.
145
- """
146
- import warnings
147
-
148
- warnings.warn(
149
- "NineRoomsFullyPureGymnasium() is deprecated. Use create_drstrategy_env() instead.",
150
- DeprecationWarning,
151
- stacklevel=2,
152
- )
153
- return create_drstrategy_env(
154
- variant="NineRooms", obs_level=obs_level, continuous=continuous, size=size
155
- )
@@ -58,10 +58,25 @@ class NineRooms(GridRoomsEnvironment):
58
58
  "cobaltgreen",
59
59
  ]
60
60
 
61
+ # Initialize goal positions for each room (2 goals per room)
62
+ goal_positions = []
63
+ for i in range(3): # rows
64
+ for j in range(3): # columns
65
+ center_x = room_size * j + room_size / 2
66
+ center_z = room_size * i + room_size / 2
67
+ # Two goals per room: center-left and center-right
68
+ goal_positions.append(
69
+ [
70
+ [center_x - 1.0, 0.0, center_z], # left goal
71
+ [center_x + 1.0, 0.0, center_z], # right goal
72
+ ]
73
+ )
74
+
61
75
  super().__init__(
62
76
  grid_size=3,
63
77
  connections=connections or default_connections,
64
78
  textures=textures or default_textures,
79
+ goal_positions=goal_positions,
65
80
  placed_room=placed_room,
66
81
  obs_level=obs_level,
67
82
  continuous=continuous,
@@ -54,10 +54,25 @@ class SpiralNineRooms(GridRoomsEnvironment):
54
54
  "cobaltgreen",
55
55
  ]
56
56
 
57
+ # Initialize goal positions for each room (2 goals per room)
58
+ goal_positions = []
59
+ for i in range(3): # rows
60
+ for j in range(3): # columns
61
+ center_x = room_size * j + room_size / 2
62
+ center_z = room_size * i + room_size / 2
63
+ # Two goals per room: center-left and center-right
64
+ goal_positions.append(
65
+ [
66
+ [center_x - 1.0, 0.0, center_z], # left goal
67
+ [center_x + 1.0, 0.0, center_z], # right goal
68
+ ]
69
+ )
70
+
57
71
  super().__init__(
58
72
  grid_size=3,
59
73
  connections=connections or default_connections,
60
74
  textures=textures or default_textures,
75
+ goal_positions=goal_positions,
61
76
  placed_room=placed_room,
62
77
  obs_level=obs_level,
63
78
  continuous=continuous,
@@ -106,10 +106,24 @@ class TwentyFiveRooms(GridRoomsEnvironment):
106
106
  "realblueberry",
107
107
  ]
108
108
 
109
+ # Initialize goal positions for each room (1 goal per room at center)
110
+ goal_positions = []
111
+ for i in range(5): # rows
112
+ for j in range(5): # columns
113
+ center_x = room_size * j + room_size / 2
114
+ center_z = room_size * i + room_size / 2
115
+ # One goal per room at the center
116
+ goal_positions.append(
117
+ [
118
+ [center_x, 0.0, center_z],
119
+ ]
120
+ )
121
+
109
122
  super().__init__(
110
123
  grid_size=5,
111
124
  connections=connections or default_connections,
112
125
  textures=textures or default_textures,
126
+ goal_positions=goal_positions,
113
127
  placed_room=placed_room,
114
128
  obs_level=obs_level,
115
129
  continuous=continuous,
@@ -11,7 +11,7 @@ import numpy as np
11
11
  from PIL import Image
12
12
 
13
13
  from ..core import FrameBuffer
14
- from ..environments.factory import create_nine_rooms_env
14
+ from ..environments.factory import NineRoomsEnvironmentWrapper
15
15
 
16
16
 
17
17
  def generate_observations(variant, output_dir=None, high_res_full_views=False):
@@ -22,10 +22,10 @@ def generate_observations(variant, output_dir=None, high_res_full_views=False):
22
22
  os.makedirs(output_dir, exist_ok=True)
23
23
 
24
24
  # Create environment
25
- env = create_nine_rooms_env(variant=variant, size=64)
25
+ env = NineRoomsEnvironmentWrapper(variant=variant, size=64)
26
26
 
27
27
  # Get base environment for direct render access
28
- base_env = getattr(env, 'env', getattr(env, '_env', env))
28
+ base_env = getattr(env, "env", getattr(env, "_env", env))
29
29
  while hasattr(base_env, "env") or hasattr(base_env, "_env"):
30
30
  if hasattr(base_env, "env"):
31
31
  base_env = base_env.env
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.3
2
+ Name: miniworld-maze
3
+ Version: 1.1.0
4
+ Summary: Multi-room maze environments from the DrStrategy paper for reinforcement learning research
5
+ Keywords: reinforcement-learning,environment,gymnasium,multi-room-maze,drstrategy,maze-navigation,partial-observability,3d-environments
6
+ Author: Tim Joseph
7
+ Author-email: Tim Joseph <tim@mctigger.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2025 Tim Joseph
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.8
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Classifier: Environment :: Console
42
+ Classifier: Typing :: Typed
43
+ Requires-Dist: gymnasium>=1.0.0,<2.0.0
44
+ Requires-Dist: numpy>=1.20.0,<3.0.0
45
+ Requires-Dist: opencv-python>=4.5.0,<5.0.0
46
+ Requires-Dist: pillow>=8.0.0,<11.0.0
47
+ Requires-Dist: pyopengl>=3.1.0,<4.0.0
48
+ Requires-Dist: pyglet>=1.5.0,<2.0.0
49
+ Requires-Dist: black>=22.0,<25.0 ; extra == 'dev'
50
+ Requires-Dist: isort>=5.10,<6.0 ; extra == 'dev'
51
+ Requires-Dist: flake8>=4.0,<7.0 ; extra == 'dev'
52
+ Requires-Dist: ruff>=0.1.0,<1.0.0 ; extra == 'dev'
53
+ Requires-Dist: build>=0.8.0,<2.0.0 ; extra == 'dev'
54
+ Requires-Dist: twine>=4.0.0,<6.0.0 ; extra == 'dev'
55
+ Requires-Dist: mujoco>=2.3.0,<4.0.0 ; extra == 'mujoco'
56
+ Requires-Python: >=3.8
57
+ Project-URL: Bug Tracker, https://github.com/mctigger/miniworld-maze/issues
58
+ Project-URL: Documentation, https://github.com/mctigger/miniworld-maze#readme
59
+ Project-URL: Homepage, https://github.com/mctigger/miniworld-maze
60
+ Project-URL: Repository, https://github.com/mctigger/miniworld-maze
61
+ Provides-Extra: dev
62
+ Provides-Extra: mujoco
63
+ Description-Content-Type: text/markdown
64
+
65
+ # MiniWorld DrStrategy - Multi-Room Maze Environment
66
+
67
+ A refactored implementation of Dr. Strategy's MiniWorld-based maze environments with updated dependencies and modern Python packaging. Based on the now-deprecated [MiniWorld](https://github.com/Farama-Foundation/Miniworld) project and the original [DrStrategy implementation](https://github.com/ahn-ml/drstrategy).
68
+
69
+ ## Environment Observations
70
+
71
+ ### Environment Views
72
+ Full environment layout and render-on-position views:
73
+
74
+ | Full Environment | Partial Top-Down Observations | Partial First-Person Observations |
75
+ |---|---|---|
76
+ | ![Full View Clean](assets/images/full_view_clean.png) | ![Top Middle TD](assets/images/render_on_pos_1_top_middle_room_topdown.png) ![Center TD](assets/images/render_on_pos_3_environment_center_topdown.png) | ![Top Middle FP](assets/images/render_on_pos_1_top_middle_room_firstperson.png) ![Center FP](assets/images/render_on_pos_3_environment_center_firstperson.png) |
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ pip install miniworld-maze
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ ### Basic Usage
87
+
88
+ See `examples/basic_usage.py` for a complete working example:
89
+
90
+ ```python
91
+ #!/usr/bin/env python3
92
+ """
93
+ Basic usage example for miniworld-maze environments.
94
+
95
+ This is a minimal example showing how to create and interact with the environment.
96
+ """
97
+
98
+ from miniworld_maze import NineRoomsEnvironmentWrapper
99
+
100
+
101
+ def main():
102
+ # Create environment
103
+ env = NineRoomsEnvironmentWrapper(variant="NineRooms", size=64)
104
+ obs, info = env.reset()
105
+
106
+ # obs is a dictionary containing:
107
+ # - 'observation': (64, 64, 3) RGB image array
108
+ # - 'desired_goal': (64, 64, 3) RGB image of the goal state
109
+ # - 'achieved_goal': (64, 64, 3) RGB image of the current state
110
+
111
+ # Take a few random actions
112
+ for step in range(10):
113
+ action = env.action_space.sample()
114
+ obs, reward, terminated, truncated, info = env.step(action)
115
+
116
+ print(f"Step {step + 1}: reward={reward:.3f}, terminated={terminated}")
117
+
118
+ if terminated or truncated:
119
+ obs, info = env.reset()
120
+
121
+ env.close()
122
+ print("Environment closed successfully!")
123
+
124
+
125
+ if __name__ == "__main__":
126
+ main()
127
+ ```
128
+
129
+ ### Headless Environments
130
+
131
+ When running in headless environments (servers, CI/CD, Docker containers) or when encountering X11/OpenGL context issues, you need to enable headless rendering:
132
+
133
+ ```bash
134
+ # Set environment variable before running Python
135
+ export PYGLET_HEADLESS=1
136
+ python your_script.py
137
+ ```
138
+
139
+ Or in your Python code (must be set before importing the library):
140
+
141
+ ```python
142
+ import os
143
+ os.environ['PYGLET_HEADLESS'] = '1'
144
+
145
+ import miniworld_maze
146
+ # ... rest of your code
147
+ ```
148
+
149
+ This configures the underlying pyglet library to use EGL rendering instead of X11, allowing the environments to run without a display server.
150
+
151
+ ## Environment Variants
152
+
153
+ ### Available Environments
154
+
155
+ The package provides three main environment variants, each with different room layouts and connection patterns:
156
+
157
+ #### 1. NineRooms (3×3 Grid)
158
+ ```
159
+ -------------
160
+ | 0 | 1 | 2 |
161
+ -------------
162
+ | 3 | 4 | 5 |
163
+ -------------
164
+ | 6 | 7 | 8 |
165
+ -------------
166
+ ```
167
+ A standard 3×3 grid where adjacent rooms are connected. The agent can navigate between rooms through doorways, with connections forming a fully connected grid pattern.
168
+
169
+ #### 2. SpiralNineRooms (3×3 Spiral Pattern)
170
+ ```
171
+ -------------
172
+ | 0 | 1 | 2 |
173
+ -------------
174
+ | 3 | 4 | 5 |
175
+ -------------
176
+ | 6 | 7 | 8 |
177
+ -------------
178
+ ```
179
+ Same room layout as NineRooms but with a spiral connection pattern. Only specific room pairs are connected, creating a more challenging navigation task with fewer available paths.
180
+
181
+ #### 3. TwentyFiveRooms (5×5 Grid)
182
+ ```
183
+ ---------------------
184
+ | 0 | 1 | 2 | 3 | 4 |
185
+ ---------------------
186
+ | 5 | 6 | 7 | 8 | 9 |
187
+ ---------------------
188
+ |10 |11 |12 |13 |14 |
189
+ ---------------------
190
+ |15 |16 |17 |18 |19 |
191
+ ---------------------
192
+ |20 |21 |22 |23 |24 |
193
+ ---------------------
194
+ ```
195
+ A larger 5×5 grid environment with 25 rooms, providing more complex navigation challenges and longer episode lengths.
196
+
197
+ ### Observation Types
198
+
199
+ Each environment supports three different observation modes:
200
+
201
+ - **`TOP_DOWN_PARTIAL`** (default): Agent-centered partial top-down view with limited visibility range (POMDP)
202
+ - **`TOP_DOWN_FULL`**: Complete top-down view showing the entire environment
203
+ - **`FIRST_PERSON`**: 3D first-person perspective view from the agent's current position
204
+
205
+ ### Action Space
206
+
207
+ - **Discrete Actions** (default): 7 discrete actions (turn left/right, move forward/backward, strafe left/right, no-op)
208
+ - **Continuous Actions**: Continuous control with `continuous=True` parameter
209
+
210
+ ### Environment Configuration
211
+
212
+ All environments can be customized with the following parameters:
213
+
214
+ ```python
215
+ from miniworld_maze import NineRoomsEnvironmentWrapper
216
+ from miniworld_maze.core import ObservationLevel
217
+
218
+ env = NineRoomsEnvironmentWrapper(
219
+ variant="NineRooms", # "NineRooms", "SpiralNineRooms", "TwentyFiveRooms"
220
+ obs_level=ObservationLevel.TOP_DOWN_PARTIAL, # Observation type
221
+ continuous=False, # Use continuous actions
222
+ size=64, # Observation image size (64x64)
223
+ room_size=5, # Size of each room in environment units
224
+ door_size=2, # Size of doors between rooms
225
+ agent_mode="empty", # Agent rendering: "empty", "circle", "triangle"
226
+ )
227
+ ```
228
+
229
+ ### Observation Format
230
+
231
+ The environment returns observations in dictionary format:
232
+
233
+ ```python
234
+ obs = {
235
+ 'observation': np.ndarray, # (64, 64, 3) RGB image of current view
236
+ 'desired_goal': np.ndarray, # (64, 64, 3) RGB image of goal location
237
+ 'achieved_goal': np.ndarray, # (64, 64, 3) RGB image of current state
238
+ }
239
+ ```
240
+
241
+ ### Reward Structure
242
+
243
+ - **Goal reaching**: Positive reward when agent reaches the goal location
244
+ - **Step penalty**: Small negative reward per step to encourage efficiency
245
+ - **Episode termination**: When goal is reached or maximum steps exceeded
246
+
247
+
248
+ ## License
249
+
250
+ MIT License - see LICENSE file for details.
@@ -1,4 +1,4 @@
1
- miniworld_maze/__init__.py,sha256=P5cqGqyF1wSO-ERuRWW8jTHXFVOAelvlIvNUrZKoJio,1215
1
+ miniworld_maze/__init__.py,sha256=kz5QqyvwIV_XkjXw_yHRkhqlPSl8X1Usra3DF0Zy4_E,1053
2
2
  miniworld_maze/core/__init__.py,sha256=5BA4WKXQjrG55TNaEid2JGrnf1KQniJZ1HhRqovM1Q0,293
3
3
  miniworld_maze/core/constants.py,sha256=GX1pSnaWGOkangYStxaBy7gIQhWxVKIbXnTSd-eA_vs,3962
4
4
  miniworld_maze/core/miniworld_gymnasium/README.md,sha256=kkZgkRKBdgixpot3uHuiBFlRIKRFIVBVfXwu68XTEv0,74
@@ -260,21 +260,19 @@ miniworld_maze/core/miniworld_gymnasium/textures/white_1.png,sha256=wRrgs92I_Ids
260
260
  miniworld_maze/core/miniworld_gymnasium/textures/wood_1.png,sha256=XRZyIN34HFo14olbxRcsHGrzCAFqUlowc6nLR22IFBE,184713
261
261
  miniworld_maze/core/miniworld_gymnasium/textures/wood_2.png,sha256=qSDHB-ZO11JJLQuiQse-0edpbuTg1YO-eIBhdTvNUhc,93121
262
262
  miniworld_maze/core/miniworld_gymnasium/textures/wood_planks_1.png,sha256=E4SNN1s4yOtkLfZFQy905eip6KvDWnnPUrpS82FxMAg,847259
263
- miniworld_maze/core/miniworld_gymnasium/unified_env.py,sha256=lKP9IdBJSl2sUHPLIP5OK3ncNuod_dKvm4CJ3Gc0egQ,45674
263
+ miniworld_maze/core/miniworld_gymnasium/unified_env.py,sha256=oY2YXCZ-ro8kl6ie0h8TEBooWjB6bvmQu42-1tDsrw4,46689
264
264
  miniworld_maze/core/miniworld_gymnasium/utils.py,sha256=9cfpg4qYz-Esxvu8nTMPFJc-Tl0TRxTrX6cfg0YuK_o,1007
265
265
  miniworld_maze/core/miniworld_gymnasium/wrappers.py,sha256=cD0nGSJYNU96zoWv63aEiKd986POhtHfGGEpNpRL5ec,122
266
266
  miniworld_maze/core/observation_types.py,sha256=Co8mEIXzIgk0MLx6tqeBd1EE0PuZOL1gbZwobiEde08,1316
267
- miniworld_maze/environments/__init__.py,sha256=hIrk8MulC725dMfslsqHtQus3dHL8C-vasUQeDphEnU,467
268
- miniworld_maze/environments/base_grid_rooms.py,sha256=RqL8Te1vk3JaCC2aq5RxI2omXm1lkfiOfVNx2Pe-ZWU,7602
269
- miniworld_maze/environments/factory.py,sha256=U7SnhTzsPy6VhARDMpnexKZwtJj0kh4CSzgRQBeEVjc,4914
270
- miniworld_maze/environments/nine_rooms.py,sha256=hZwRb2jjsGxbwCBv0dhpMKvgkpCNGrNZvFJWLTaFjxo,1727
271
- miniworld_maze/environments/spiral_nine_rooms.py,sha256=gDI2mLvaNmaxeM4Ara7RD1hGCjvETo60V3ar-SZZ-Dk,1675
272
- miniworld_maze/environments/twenty_five_rooms.py,sha256=xpc_d91s5j_mWhH-r9YpCTVqCEzB4mVcyoQ7ywWHXPU,2905
267
+ miniworld_maze/environments/__init__.py,sha256=6O1G4vlhUSn8OuR46u-t4Wz3de8da76Tz21TsVlzBZo,415
268
+ miniworld_maze/environments/base_grid_rooms.py,sha256=kpF6psn0YCfvA-q1vtU2KaqwmfQaPpX0w9jBTxt2qs8,11833
269
+ miniworld_maze/environments/factory.py,sha256=g8qYF6UF9DsKExZIHhMsP8PX3b11kvmkLMwE2IpgBxc,3501
270
+ miniworld_maze/environments/nine_rooms.py,sha256=0CzUkRy2hJZ4JEEj9E-6xCopseZ1u8iGCfKLaxwATGo,2368
271
+ miniworld_maze/environments/spiral_nine_rooms.py,sha256=LB9wKQXp9S8pVd8JhM9zfsc5z4ksxsQxQKoEXrDgPhE,2316
272
+ miniworld_maze/environments/twenty_five_rooms.py,sha256=ExF4Mt0B3pkaVQtBF7oaVxdj_9uaAkTFYCe83Lw8Evk,3448
273
273
  miniworld_maze/tools/__init__.py,sha256=PgkKMO21xnwIfJtD437P_RtkguOHoOSFV1ohsO-n7tc,150
274
- miniworld_maze/tools/generate_observations.py,sha256=1etBz2ndTZwPFghnLhp15l1WVUaBN5DkMwjyGtzyepA,6769
275
- miniworld_maze/wrappers/__init__.py,sha256=cdTbUbwDBRC_d9kdFV0e392F3-718elthLoBJII5Npg,130
276
- miniworld_maze/wrappers/image_transforms.py,sha256=4xr4HfateUKeJjFRBBew8z6LO72ygue4yQjAZgOgo5g,1199
277
- miniworld_maze-1.0.0.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
278
- miniworld_maze-1.0.0.dist-info/entry_points.txt,sha256=Ue03NHCOQCiJ87tqfJSns29C3Dw02jsZXL_Auzw2pb4,91
279
- miniworld_maze-1.0.0.dist-info/METADATA,sha256=GhSg5-xatBRjjkKZuhnkjfFExyH_R77BUDrCjgoLCEE,4775
280
- miniworld_maze-1.0.0.dist-info/RECORD,,
274
+ miniworld_maze/tools/generate_observations.py,sha256=-WWoex0KY6PSMs4BenMTepeE4tA2NYZ26jIQL2tYfZY,6781
275
+ miniworld_maze-1.1.0.dist-info/WHEEL,sha256=NHRAbdxxzyL9K3IO2LjmlNqKSyPZnKv2BD16YYVKo18,79
276
+ miniworld_maze-1.1.0.dist-info/entry_points.txt,sha256=Ue03NHCOQCiJ87tqfJSns29C3Dw02jsZXL_Auzw2pb4,91
277
+ miniworld_maze-1.1.0.dist-info/METADATA,sha256=atsdko20fA_iqS8y2muB0dQkLDnmLpP4ppuTuNLfcxA,9270
278
+ miniworld_maze-1.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.13
2
+ Generator: uv 0.8.14
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- """Gymnasium wrappers for Nine Rooms environments."""
2
-
3
- from .image_transforms import ImageToPyTorch
4
-
5
- __all__ = ["ImageToPyTorch"]
@@ -1,40 +0,0 @@
1
- """Image transformation wrappers for Nine Rooms environments."""
2
-
3
- import gymnasium as gym
4
- import numpy as np
5
- from gymnasium import spaces
6
-
7
-
8
- class ImageToPyTorch(gym.ObservationWrapper):
9
- """Convert HWC to CHW format for PyTorch compatibility."""
10
-
11
- def __init__(self, env):
12
- """
13
- Initialize PyTorch-compatible image transformation wrapper.
14
-
15
- Transforms observation space from HWC (Height, Width, Channels) format
16
- to CHW (Channels, Height, Width) format expected by PyTorch models.
17
-
18
- Args:
19
- env: The environment to wrap
20
- """
21
- super(ImageToPyTorch, self).__init__(env)
22
- obs_shape = self.observation_space.shape
23
- self.observation_space = spaces.Box(
24
- low=0,
25
- high=255,
26
- shape=(obs_shape[2], obs_shape[0], obs_shape[1]),
27
- dtype=np.uint8,
28
- )
29
-
30
- def observation(self, observation):
31
- """
32
- Transform observation from HWC to CHW format.
33
-
34
- Args:
35
- observation: Input observation in HWC format (H, W, C)
36
-
37
- Returns:
38
- np.ndarray: Observation in CHW format (C, H, W)
39
- """
40
- return np.transpose(observation, (2, 0, 1))
@@ -1,108 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: miniworld-maze
3
- Version: 1.0.0
4
- Summary: Multi-room maze environments from the DrStrategy paper for reinforcement learning research
5
- Keywords: reinforcement-learning,environment,gymnasium,multi-room-maze,drstrategy,maze-navigation,partial-observability,3d-environments
6
- Author: Tim Joseph
7
- Author-email: Tim Joseph <tim@mctigger.com>
8
- License: MIT License
9
-
10
- Copyright (c) 2025 DrStrategy Research Team
11
-
12
- Permission is hereby granted, free of charge, to any person obtaining a copy
13
- of this software and associated documentation files (the "Software"), to deal
14
- in the Software without restriction, including without limitation the rights
15
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
- copies of the Software, and to permit persons to whom the Software is
17
- furnished to do so, subject to the following conditions:
18
-
19
- The above copyright notice and this permission notice shall be included in all
20
- copies or substantial portions of the Software.
21
-
22
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
- SOFTWARE.
29
- Classifier: Development Status :: 4 - Beta
30
- Classifier: Intended Audience :: Science/Research
31
- Classifier: Intended Audience :: Developers
32
- Classifier: Operating System :: OS Independent
33
- Classifier: Programming Language :: Python :: 3
34
- Classifier: Programming Language :: Python :: 3.8
35
- Classifier: Programming Language :: Python :: 3.9
36
- Classifier: Programming Language :: Python :: 3.10
37
- Classifier: Programming Language :: Python :: 3.11
38
- Classifier: Programming Language :: Python :: 3.12
39
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
40
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
- Classifier: Environment :: Console
42
- Classifier: Typing :: Typed
43
- Requires-Dist: gymnasium>=1.0.0,<2.0.0
44
- Requires-Dist: numpy>=1.20.0,<3.0.0
45
- Requires-Dist: opencv-python>=4.5.0,<5.0.0
46
- Requires-Dist: pillow>=8.0.0,<11.0.0
47
- Requires-Dist: pyopengl>=3.1.0,<4.0.0
48
- Requires-Dist: pyglet>=1.5.0,<2.0.0
49
- Requires-Dist: black>=22.0,<25.0 ; extra == 'dev'
50
- Requires-Dist: isort>=5.10,<6.0 ; extra == 'dev'
51
- Requires-Dist: flake8>=4.0,<7.0 ; extra == 'dev'
52
- Requires-Dist: build>=0.8.0,<2.0.0 ; extra == 'dev'
53
- Requires-Dist: twine>=4.0.0,<6.0.0 ; extra == 'dev'
54
- Requires-Dist: mujoco>=2.3.0,<4.0.0 ; extra == 'mujoco'
55
- Requires-Python: >=3.8
56
- Project-URL: Bug Tracker, https://github.com/mctigger/miniworld-maze/issues
57
- Project-URL: Documentation, https://github.com/mctigger/miniworld-maze#readme
58
- Project-URL: Homepage, https://github.com/mctigger/miniworld-maze
59
- Project-URL: Repository, https://github.com/mctigger/miniworld-maze
60
- Provides-Extra: dev
61
- Provides-Extra: mujoco
62
- Description-Content-Type: text/markdown
63
-
64
- # MiniWorld DrStrategy - Multi-Room Maze Environment
65
-
66
- A refactored implementation of Dr. Strategy's MiniWorld-based maze environments with updated dependencies and modern Python packaging. Based on the now-deprecated [MiniWorld](https://github.com/Farama-Foundation/Miniworld) project and the original [DrStrategy implementation](https://github.com/ahn-ml/drstrategy).
67
-
68
- ## Environment Observations
69
-
70
- ### Environment Views
71
- Full environment layout and render-on-position views:
72
-
73
- | Full Environment | Partial Top-Down Observations | Partial First-Person Observations |
74
- |---|---|---|
75
- | ![Full View Clean](assets/images/full_view_clean.png) | ![Top Middle TD](assets/images/render_on_pos_1_top_middle_room_topdown.png) ![Center TD](assets/images/render_on_pos_3_environment_center_topdown.png) | ![Top Middle FP](assets/images/render_on_pos_1_top_middle_room_firstperson.png) ![Center FP](assets/images/render_on_pos_3_environment_center_firstperson.png) |
76
-
77
- ## Installation
78
-
79
- ```bash
80
- pip install miniworld-maze
81
- ```
82
-
83
- ## Usage
84
-
85
- ```python
86
- from miniworld_drstrategy import create_nine_rooms_env
87
-
88
- # Create environment
89
- env = create_nine_rooms_env(variant="NineRooms", size=64)
90
- obs, info = env.reset()
91
-
92
- # Take actions
93
- action = env.action_space.sample()
94
- obs, reward, terminated, truncated, info = env.step(action)
95
-
96
- env.close()
97
- ```
98
-
99
- ## Environment Variants
100
-
101
- - **NineRooms**: 3×3 grid layout
102
- - **SpiralNineRooms**: Spiral connection pattern
103
- - **TwentyFiveRooms**: 5×5 grid layout
104
-
105
-
106
- ## License
107
-
108
- MIT License - see LICENSE file for details.