1 """ Procedural map generation for levels """
9 ' ': {'floor': {'base': 'floor',
10 'behaviour': ['walk', 'fly'],
13 'o': {'tunnels': {'base': 'underground',
17 '-': {'tunnels': {'base': 'tunnel',
18 'behaviour': ['walk', ],
25 def __init__(self, coordinates, region):
28 self.coordinates = [coordinates]
30 self.max_connections = 1
36 Check if the room is linked to another room
37 :return: Whether the room has any links or not
39 return len(self.passages) + len(self.tunnels) > 0
41 def add_region(self, coordinates):
43 Add a new region into an existing room
44 :param coordinates: region coordinates to be added to room
47 self.coordinates.append(coordinates)
49 def connect_rooms(self, other_rooms):
50 """ Find the nearest rooms to this room
51 :param other_rooms: list of Rooms objects that we are searching
58 for coord in self.coordinates:
59 for room in other_rooms:
62 for new_coord in room.coordinates:
64 math.sqrt((coord[0] - new_coord[0]) ** 2 +
65 (coord[1] - new_coord[1]) ** 2))
66 other_tile.append(new_coord)
67 this_tile.append(coord)
68 target_rooms.append(room)
70 sorted_indices = [i[0] for i in sorted(enumerate(distance),
72 for index in sorted_indices:
73 if len(self.passages) + len(self.tunnels) >= self.max_connections:
75 if not target_rooms[index].is_linked():
76 self.link_passage(this_tile[index], other_tile[index])
77 target_rooms[index].link_passage(
78 other_tile[index], this_tile[index])
80 def link_passage(self, local_tile, foreign_tile):
81 """ Link a passage between two rooms
82 :param local_tile: tile in this room to which we wish to link
83 :param foreign_tile: tile in another room to which we wish to link
86 self.passages.append([local_tile, foreign_tile])
88 def render_region(self, region, room_dist, region_size, tile_map, x, y):
89 """ Check if a region is in this room and return the required tiles
90 :param region: Region that we wish to render
91 :param room_dist: Tile separation distance from other rooms
92 :param region_size: Region size in tiles
93 :param tile_map: Tile map to update
94 :param x: X coordinate
95 :param y: Y coordinate
98 if region in self.coordinates:
99 print(self.coordinates)
100 for ht in range(room_dist, region_size - room_dist):
101 for wt in range(room_dist, region_size - room_dist):
102 tile_map[x + ht][y + wt] = ' '
105 # w_dist = self.dist_from_other_rooms
106 # elif self.region_map[h][w-1] == region_selected:
109 # w_dist = self.dist_from_other_rooms
111 # if w + 1 == self.width:
112 # e_dist = self.region_size - self.dist_from_other_rooms
113 # elif self.region_map[h][w+1] == region_selected:
114 # e_dist = self.region_size
116 # e_dist = self.region_size - self.dist_from_other_rooms
119 # n_dist = self.dist_from_other_rooms
120 # elif self.region_map[h-1][w] == region_selected:
123 # n_dist = self.dist_from_other_rooms
125 # if h + 1 == self.height:
126 # s_dist = self.region_size - self.dist_from_other_rooms
127 # elif self.region_map[h+1][w] == region_selected:
128 # s_dist = self.region_size
130 # s_dist = self.region_size - self.dist_from_other_rooms
132 # for wt in range(w_dist, e_dist):
133 # for ht in range(n_dist, s_dist):
134 # self.map[h * self.region_size + ht]\
135 # [w * self.region_size + wt] = ' '
137 def random_cardinal():
138 """Return a random cardinal direction for random walks."""
139 return random.choice([(0, 1), (0, -1), (1, 0), (-1, 0)])
142 class LevelGenerator:
149 dist_from_other_rooms = 0
154 def __init__(self, width, height, no_rooms, dist_from_other_rooms,
156 """ Initialize the level parameters
160 self.no_rooms = no_rooms
161 self.dist_from_other_rooms = dist_from_other_rooms
162 self.region_size = region_size
163 self.region_coordinates = []
166 """ Generate a random level map
168 self.generate_rooms()
169 regions_selected = random.sample(range(self.regions),
170 min(self.regions, self.no_rooms))
171 row = ['#' for x in range(self.width * self.region_size)]
172 self.map = [row[:] for x in range(self.height * self.region_size)]
173 self.map2 = [row[:] for x in range(self.height * self.region_size)]
174 print('Regions: %s' % str(regions_selected))
175 for region in regions_selected:
176 self.rooms[region].connect_rooms(
177 [self.rooms[i] for i in regions_selected])
178 self.generate_room(region)
179 region_coordinates_selected = [p for p in self.region_coordinates if
180 p[0] in regions_selected]
181 print('Coords: %s' % str(region_coordinates_selected))
182 for coord in region_coordinates_selected:
184 self.rooms[coord[0]].render_region(
185 coord[1], self.dist_from_other_rooms, self.region_size,
186 self.map2, coord[1][0] * self.region_size,
187 coord[1][1] * self.region_size)
188 # self.generate_underlayer()
190 def generate_rooms(self):
191 """ Generate a random level region map
193 row = [0 for x in range(self.width)]
194 self.region_map = [row[:] for x in range(self.height)]
195 for h in range(self.height):
196 for w in range(self.width):
197 random_number = random.randint(0, 2)
198 increment_region = False
200 update_value = self.regions
201 increment_region = True
203 if random_number > 1:
204 update_value = self.region_map[h][w - 1]
206 update_value = self.regions
207 increment_region = True
209 if random_number > 1:
210 update_value = self.region_map[h - 1][w]
212 update_value = self.regions
213 increment_region = True
215 if random_number > 1:
216 update_value = self.region_map[h - 1][w]
217 elif random_number > 0:
218 update_value = self.region_map[h][w - 1]
220 update_value = self.regions
221 increment_region = True
222 self.region_map[h][w] = update_value
224 r = Room([h, w], update_value)
226 self.region_coordinates.append([update_value, [h, w]])
230 if r.region == update_value:
232 self.region_coordinates.append([update_value, [h, w]])
234 def generate_underlayer(self):
235 """Generate a small mess of tunnels to have something."""
236 width = len(self.map[0])
237 height = len(self.map)
238 row = ['o' for x in range(width)]
239 self.underlayer = [row[:] for x in range(height)]
240 # we create a set of biased random walks to create the tunnel network
241 for walk in range(random.randint(3, 6)):
242 x = width // 2 + random.randint(-8, 8)
243 y = height // 2 + random.randint(-8, 8)
244 dir_x, dir_y = random_cardinal()
245 max_steps = random.randint(40, width * height // 4)
246 for step in range(20, max_steps):
247 if 0 < x < width - 1:
248 if 0 < y < height - 1:
249 self.underlayer[y][x] = '-'
250 if random.random() > 0.7:
251 dir_x, dir_y = random_cardinal()
255 def generate_tiles(self, region_selected):
256 """Generate a small mess of tunnels to have something."""
257 width = len(self.map[0])
258 height = len(self.map)
259 row = ['o' for x in range(width)]
260 self.underlayer = [row[:] for x in range(height)]
261 # we create a set of biased random walks to create the tunnel network
262 for walk in range(random.randint(3, 6)):
263 x = width // 2 + random.randint(-8, 8)
264 y = height // 2 + random.randint(-8, 8)
265 dir_x, dir_y = random_cardinal()
266 max_steps = random.randint(40, width * height // 4)
267 for step in range(20, max_steps):
268 if 0 < x < width - 1:
269 if 0 < y < height - 1:
270 self.underlayer[y][x] = '-'
271 if random.random() > 0.7:
272 dir_x, dir_y = random_cardinal()
276 def generate_room(self, region_selected):
278 :param region_selected:
281 for h in range(self.height):
282 for w in range(self.width):
283 if self.region_map[h][w] == region_selected:
285 w_dist = self.dist_from_other_rooms
286 elif self.region_map[h][w-1] == region_selected:
289 w_dist = self.dist_from_other_rooms
291 if w + 1 == self.width:
292 e_dist = self.region_size - self.dist_from_other_rooms
293 elif self.region_map[h][w+1] == region_selected:
294 e_dist = self.region_size
296 e_dist = self.region_size - self.dist_from_other_rooms
299 n_dist = self.dist_from_other_rooms
300 elif self.region_map[h-1][w] == region_selected:
303 n_dist = self.dist_from_other_rooms
305 if h + 1 == self.height:
306 s_dist = self.region_size - self.dist_from_other_rooms
307 elif self.region_map[h+1][w] == region_selected:
308 s_dist = self.region_size
310 s_dist = self.region_size - self.dist_from_other_rooms
312 for wt in range(w_dist, e_dist):
313 for ht in range(n_dist, s_dist):
314 self.map[h * self.region_size + ht]\
315 [w * self.region_size + wt] = str(region_selected)
318 file = open('map.txt', 'w')
319 print('-----------------')
322 file.write(''.join(l))
324 print('-----------------')
327 file.write(''.join(l))
329 print('-----------------')
331 for l in self.underlayer:
333 file.write(''.join(l))
335 except AttributeError:
338 for l in self.region_map:
344 level['tileset'] = 'bunker'
346 for l, lu in zip(self.map, self.underlayer):
348 for t1, t2 in zip(l, lu):
349 tile = ATTRIBUTE_MAP[t1].copy()
350 tile.update(ATTRIBUTE_MAP[t2])
352 level['tiles'].append(row)
353 # FIXME: Do a lot better here
354 # Crude hack so the level is written into the levels folder
355 name = os.path.join(os.path.dirname(__file__), '..', 'levels', 'map.json')
361 if __name__ == '__main__':
363 level = LevelGenerator(width=4, height=3, no_rooms=4,
364 dist_from_other_rooms=0, region_size=3)
367 input("Press Enter to continue...")