Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c21d2d6

Browse files
author
Roger Creus Castanyer
committed
Adding code to procedurally generate mazes of different sizes
1 parent 379baef commit c21d2d6

10 files changed

Lines changed: 204 additions & 0 deletions
104 KB
Loading
106 KB
Loading
112 KB
Loading
141 KB
Loading
152 KB
Loading
147 KB
Loading
141 KB
Loading
143 KB
Loading
137 KB
Loading
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import numpy as np
2+
import gym
3+
from griddly.util.rllib.environment.level_generator import LevelGenerator
4+
5+
class LabyrinthLevelGenerator(LevelGenerator):
6+
WALL = 'w'
7+
AGENT = 'A'
8+
GOAL = 'x'
9+
EMPTY = '.'
10+
11+
def __init__(self, config):
12+
"""
13+
Initialize the LabyrinthLevelGenerator.
14+
15+
Parameters:
16+
config (dict): Configuration dictionary with the following optional keys:
17+
- width (int): Width of the maze (default: 9).
18+
- height (int): Height of the maze (default: 9).
19+
- wall_density (float): Wall density in the maze (default: 1).
20+
- num_goals (int): Number of goals to place in the maze (default: 1).
21+
22+
Example usage:
23+
--------------
24+
config = {
25+
'width': 9,
26+
'height': 9,
27+
'wall_density': 1,
28+
'num_goals': 1
29+
}
30+
level_generator = LabyrinthLevelGenerator(config)
31+
level_string = level_generator.generate()
32+
env = gym.make('GDY-Maze-v0')
33+
env.reset(level_string=level_string)
34+
"""
35+
36+
super().__init__(config)
37+
self._width = config.get('width', 9)
38+
self._height = config.get('height', 9)
39+
self._wall_density = config.get('wall_density', 1) # Adjust this value to control wall density
40+
41+
assert self._width % 2 == 1 and self._height % 2 == 1
42+
43+
self._num_goals = config.get('num_goals', 1)
44+
45+
def _generate_maze(self):
46+
"""
47+
Generate the maze grid.
48+
49+
Returns:
50+
np.ndarray: 2D array representing the maze with walls, empty spaces, and paths.
51+
52+
Note: The Recursive Backtracking algorithm is used to generate the paths in the maze.
53+
"""
54+
55+
# Create a maze grid with walls
56+
maze = np.full((self._width, self._height), LabyrinthLevelGenerator.WALL, dtype=np.dtype('U1'))
57+
58+
# Recursive Backtracking algorithm for generating maze paths
59+
def recursive_backtracking(x, y):
60+
maze[x, y] = LabyrinthLevelGenerator.EMPTY
61+
62+
# Randomize the order of directions to explore
63+
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
64+
np.random.shuffle(directions)
65+
66+
for dx, dy in directions:
67+
nx, ny = x + 2 * dx, y + 2 * dy
68+
if 0 <= nx < self._width and 0 <= ny < self._height and maze[nx, ny] == LabyrinthLevelGenerator.WALL:
69+
# Carve a path by removing walls
70+
maze[nx - dx, ny - dy] = LabyrinthLevelGenerator.EMPTY
71+
recursive_backtracking(nx, ny)
72+
73+
# Start the Recursive Backtracking from a random position
74+
start_x, start_y = np.random.choice(range(1, self._width - 1), size=2), np.random.choice(range(1, self._height - 1), size=2)
75+
recursive_backtracking(start_x[0], start_y[0])
76+
77+
# Add more open spaces by removing walls randomly
78+
for x in range(1, self._width - 1):
79+
for y in range(1, self._height - 1):
80+
if maze[x, y] == LabyrinthLevelGenerator.WALL and np.random.random() > self._wall_density:
81+
maze[x, y] = LabyrinthLevelGenerator.EMPTY
82+
83+
return maze
84+
85+
def _is_reachable(self, maze, x, y):
86+
"""
87+
Check if a tile is reachable from the agent's starting position using a flood-fill algorithm.
88+
89+
Parameters:
90+
maze (np.ndarray): 2D array representing the maze grid.
91+
x (int): X-coordinate of the tile to check.
92+
y (int): Y-coordinate of the tile to check.
93+
94+
Returns:
95+
bool: True if the tile is reachable; False otherwise.
96+
"""
97+
98+
# Flood-fill algorithm to check if (x, y) is reachable from the agent's starting position
99+
stack = [(x, y)]
100+
visited = set()
101+
102+
while stack:
103+
cx, cy = stack.pop()
104+
if (cx, cy) in visited:
105+
continue
106+
107+
visited.add((cx, cy))
108+
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
109+
nx, ny = cx + dx, cy + dy
110+
if (
111+
0 <= nx < self._width
112+
and 0 <= ny < self._height
113+
and maze[nx, ny] != LabyrinthLevelGenerator.WALL
114+
and (nx, ny) != (x, y) # Exclude the goal position from the flood-fill
115+
):
116+
stack.append((nx, ny))
117+
118+
return len(visited) == self._width * self._height - np.sum(maze == LabyrinthLevelGenerator.WALL)
119+
120+
def _place_goals(self, maze, agent_x, agent_y):
121+
"""
122+
Place the goals in the maze while ensuring they don't block the agent's access to all tiles.
123+
124+
Parameters:
125+
maze (np.ndarray): 2D array representing the maze grid.
126+
agent_x (int): X-coordinate of the agent's starting position.
127+
agent_y (int): Y-coordinate of the agent's starting position.
128+
129+
Returns:
130+
np.ndarray: Updated maze grid with goals placed.
131+
132+
Note: The goals are placed in locations that do not block the agent's navigation to all tiles.
133+
"""
134+
135+
# Get all available empty spaces for goal placement
136+
available_spaces = np.transpose(np.where(maze == LabyrinthLevelGenerator.EMPTY))
137+
138+
for _ in range(self._num_goals):
139+
np.random.shuffle(available_spaces)
140+
for goal_x, goal_y in available_spaces:
141+
# Check if the goal location does not block the agent's access to all tiles of the maze
142+
maze[goal_x, goal_y] = LabyrinthLevelGenerator.GOAL
143+
if self._is_reachable(maze, agent_x, agent_y):
144+
break
145+
else:
146+
maze[goal_x, goal_y] = LabyrinthLevelGenerator.EMPTY
147+
148+
return maze
149+
150+
def generate(self):
151+
"""
152+
Generate a new maze level.
153+
154+
Returns:
155+
str: String representation of the maze level.
156+
157+
Example usage:
158+
--------------
159+
config = {
160+
'width': 9,
161+
'height': 9,
162+
'wall_density': 1,
163+
'num_goals': 1
164+
}
165+
level_generator = LabyrinthLevelGenerator(config)
166+
level_string = level_generator.generate()
167+
env = gym.make('GDY-Maze-v0')
168+
env.reset(level_string=level_string)
169+
"""
170+
171+
maze = self._generate_maze()
172+
173+
# Place agent
174+
agent_x = 2 * np.random.randint(1, (self._width - 1) // 2)
175+
agent_y = 2 * np.random.randint(1, (self._height - 1) // 2)
176+
maze[agent_x, agent_y] = LabyrinthLevelGenerator.AGENT
177+
178+
# Place goals with minimum distance constraint
179+
maze = self._place_goals(maze, agent_x, agent_y)
180+
181+
level_string = '\n'.join([''.join(row) for row in maze])
182+
183+
return level_string
184+
185+
if __name__ == '__main__':
186+
import matplotlib.pyplot as plt
187+
188+
env = gym.make('GDY-Labyrinth-v0')
189+
sizes = ["45x45", "21x21", "13x13"]
190+
191+
for size in sizes:
192+
for i in range(3):
193+
config = {
194+
'width': int(size.split('x')[0]),
195+
'height': int(size.split('x')[1]),
196+
}
197+
198+
level_generator = LabyrinthLevelGenerator(config)
199+
env.reset(level_string=level_generator.generate())
200+
201+
obs = env.render(mode="rgb_array")
202+
plt.figure()
203+
plt.imshow(obs)
204+
plt.savefig(f"example_maze_{size}_{i+1}.png")

0 commit comments

Comments
 (0)