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_coords(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, coords, room_dist, region_size, tile_map, x, y):
89 """ Check if a region is in this room and return the required tiles
90 :param coords: Coordinates of the 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 coords in self.coordinates:
99 x_pre_room_dist = room_dist
100 x_post_room_dist = region_size - room_dist
101 y_pre_room_dist = room_dist
102 y_post_room_dist = region_size - room_dist
103 if [x - 1, y] in self.coordinates:
105 if [x + 1, y] in self.coordinates:
106 y_post_room_dist = region_size
107 if [x, y - 1] in self.coordinates:
109 if [x, y + 1] in self.coordinates:
110 x_post_room_dist = region_size
112 for ht in range(y_pre_room_dist, y_post_room_dist):
113 for wt in range(x_pre_room_dist, x_post_room_dist):
114 tile_map[(x * region_size) + ht][(y * region_size) + wt] =\
116 for p in self.passages:
118 x_regions = p[0][0] - p[1][0]
119 y_regions = p[0][1] - p[1][1]
120 if p[0][0] < p[1][0]:
122 elif p[0][0] < p[1][0]:
126 if p[0][1] < p[1][1]:
128 elif p[0][1] < p[1][1]:
132 for ht in range(0, region_size, x_direction):
133 tile_map[(p[0][0] * region_size) + int(region_size / 2)]\
134 [(p[0][1] * region_size) + int(region_size / 2) + ht] = 'p'
135 for wt in range(0, y_regions, y_direction):
136 tile_map[(p[0][0] * region_size) + int(region_size / 2) + wt]\
137 [(p[0][1] * region_size) + int(region_size / 2)] = 'p'
140 def random_cardinal():
141 """Return a random cardinal direction for random walks."""
142 return random.choice([(0, 1), (0, -1), (1, 0), (-1, 0)])
145 class LevelGenerator:
152 dist_from_other_rooms = 0
157 def __init__(self, width, height, no_rooms, dist_from_other_rooms,
159 """ Initialize the level parameters
163 self.no_rooms = no_rooms
164 self.dist_from_other_rooms = dist_from_other_rooms
165 self.region_size = region_size
166 self.region_coordinates = []
169 """ Generate a random level map
171 self.generate_rooms()
172 regions_selected = random.sample(range(self.regions),
173 min(self.regions, self.no_rooms))
174 row = ['#' for x in range(self.width * self.region_size)]
175 self.map = [row[:] for x in range(self.height * self.region_size)]
176 self.map2 = [row[:] for x in range(self.height * self.region_size)]
177 for region in regions_selected:
178 self.rooms[region].connect_rooms(
179 [self.rooms[i] for i in regions_selected])
180 region_coordinates_selected = [p for p in self.region_coordinates if
181 p[0] in regions_selected]
182 for coord in region_coordinates_selected:
183 self.rooms[coord[0]].render_region(
184 coord[1], self.dist_from_other_rooms, self.region_size,
185 self.map2, coord[1][0], coord[1][1])
186 # self.generate_underlayer()
188 def generate_rooms(self):
189 """ Generate a random level region map
191 row = [0 for x in range(self.width)]
192 self.region_map = [row[:] for x in range(self.height)]
193 for h in range(self.height):
194 for w in range(self.width):
195 random_number = random.randint(0, 2)
196 increment_region = False
198 update_value = self.regions
199 increment_region = True
201 if random_number > 1:
202 update_value = self.region_map[h][w - 1]
204 update_value = self.regions
205 increment_region = True
207 if random_number > 1:
208 update_value = self.region_map[h - 1][w]
210 update_value = self.regions
211 increment_region = True
213 if random_number > 1:
214 update_value = self.region_map[h - 1][w]
215 elif random_number > 0:
216 update_value = self.region_map[h][w - 1]
218 update_value = self.regions
219 increment_region = True
220 self.region_map[h][w] = update_value
222 r = Room([h, w], update_value)
224 self.region_coordinates.append([update_value, [h, w]])
228 if r.region == update_value:
230 self.region_coordinates.append([update_value, [h, w]])
232 def generate_underlayer(self):
233 """Generate a small mess of tunnels to have something."""
234 width = len(self.map[0])
235 height = len(self.map)
236 row = ['o' for x in range(width)]
237 self.underlayer = [row[:] for x in range(height)]
238 # we create a set of biased random walks to create the tunnel network
239 for walk in range(random.randint(3, 6)):
240 x = width // 2 + random.randint(-8, 8)
241 y = height // 2 + random.randint(-8, 8)
242 dir_x, dir_y = random_cardinal()
243 max_steps = random.randint(40, width * height // 4)
244 for step in range(20, max_steps):
245 if 0 < x < width - 1:
246 if 0 < y < height - 1:
247 self.underlayer[y][x] = '-'
248 if random.random() > 0.7:
249 dir_x, dir_y = random_cardinal()
253 def generate_tiles(self, region_selected):
254 """Generate a small mess of tunnels to have something."""
255 width = len(self.map[0])
256 height = len(self.map)
257 row = ['o' for x in range(width)]
258 self.underlayer = [row[:] for x in range(height)]
259 # we create a set of biased random walks to create the tunnel network
260 for walk in range(random.randint(3, 6)):
261 x = width // 2 + random.randint(-8, 8)
262 y = height // 2 + random.randint(-8, 8)
263 dir_x, dir_y = random_cardinal()
264 max_steps = random.randint(40, width * height // 4)
265 for step in range(20, max_steps):
266 if 0 < x < width - 1:
267 if 0 < y < height - 1:
268 self.underlayer[y][x] = '-'
269 if random.random() > 0.7:
270 dir_x, dir_y = random_cardinal()
275 file = open('map.txt', 'w')
276 print('-----------------')
279 file.write(''.join(l))
281 print('-----------------')
283 for l in self.underlayer:
285 file.write(''.join(l))
287 except AttributeError:
290 for l in self.region_map:
296 level['tileset'] = 'bunker'
298 for l, lu in zip(self.map, self.underlayer):
300 for t1, t2 in zip(l, lu):
301 tile = ATTRIBUTE_MAP[t1].copy()
302 tile.update(ATTRIBUTE_MAP[t2])
304 level['tiles'].append(row)
305 # FIXME: Do a lot better here
306 # Crude hack so the level is written into the levels folder
307 name = os.path.join(os.path.dirname(__file__), '..', 'levels', 'map.json')
313 if __name__ == '__main__':
314 level = LevelGenerator(width=4, height=3, no_rooms=4,
315 dist_from_other_rooms=1, region_size=5)