Merge
[koperkapel.git] / koperkapel / generators / maps.py
1 """ Procedural map generation for levels """
2
3 import random
4 import math
5 import json
6 import os
7
8 ATTRIBUTE_MAP = {
9     '#': {'base': 'cwall',
10           'behaviour': [],
11          },
12     ' ': {'base': 'floor',
13           'behaviour': ['walk', 'fly'],
14          },
15 }
16
17
18 class Room:
19     def __init__(self, coordinates, region):
20         """
21         """
22         self.coordinates = [coordinates]
23         self.region = region
24         self.max_connections = 1
25         self.passages = []
26         self.tunnels = []
27
28     def is_linked(self):
29         """
30         Check if the room is linked to another room
31         :return: Whether the room has any links or not
32         """
33         return len(self.passages) + len(self.tunnels) > 0
34
35     def add_region(self, coordinates):
36         """
37         Add a new region into an existing room
38         :param coordinates: region coordinates to be added to room
39         :return:
40         """
41         self.coordinates.append(coordinates)
42
43     def connect_rooms(self, other_rooms):
44         """ Find the nearest rooms to this room
45         :param other_rooms: list of Rooms objects that we are searching
46         :return:
47         """
48         distance = []
49         other_tile = []
50         this_tile = []
51         target_rooms = []
52         for coord in self.coordinates:
53             for room in other_rooms:
54                 if self == room:
55                     continue
56                 for new_coord in room.coordinates:
57                     distance.append(
58                         math.sqrt((coord[0] - new_coord[0]) ** 2 +
59                                   (coord[1] - new_coord[1]) ** 2))
60                     other_tile.append(new_coord)
61                     this_tile.append(coord)
62                     target_rooms.append(room)
63
64         sorted_indices = [i[0] for i in sorted(enumerate(distance),
65                                                key=lambda x:x[0])]
66         for index in sorted_indices:
67             if len(self.passages) + len(self.tunnels) >= self.max_connections:
68                 break
69             if not target_rooms[index].is_linked():
70                 self.link_passage(this_tile[index], other_tile[index])
71                 target_rooms[index].link_passage(
72                     other_tile[index], this_tile[index])
73
74     def link_passage(self, local_tile, foriegn_tile):
75         """ Link a passage between two rooms
76         :param local_tile: tile in this room to which we wish to link
77         :param foriegn_tile: tile in another room to which we wish to link
78         :return:
79         """
80         self.passages.append([local_tile, foriegn_tile])
81
82     def render_region(self, region, room_dist, region_size, tile_map, x, y):
83         """ Check if a region is in this room and return the required tiles
84         :param region: Region that we wish to render
85         :param room_dist: Tile separation distance from other rooms
86         :param region_size: Region size in tiles
87         :return:
88         """
89         if region in self.coordinates:
90             print(region)
91             print(self.region)
92             print(self.coordinates)
93             for ht in range(room_dist, region_size - room_dist):
94                 for wt in range(room_dist, region_size - room_dist):
95                     tile_map[x + ht][y + wt] = ' '
96
97                     # if w == 0:
98                     #     w_dist = self.dist_from_other_rooms
99                     # elif self.region_map[h][w-1] == region_selected:
100                     #     w_dist = 0
101                     # else:
102                     #     w_dist = self.dist_from_other_rooms
103                     #
104                     # if w + 1 == self.width:
105                     #     e_dist = self.region_size - self.dist_from_other_rooms
106                     # elif self.region_map[h][w+1] == region_selected:
107                     #     e_dist = self.region_size
108                     # else:
109                     #     e_dist = self.region_size - self.dist_from_other_rooms
110                     #
111                     # if h == 0:
112                     #     n_dist = self.dist_from_other_rooms
113                     # elif self.region_map[h-1][w] == region_selected:
114                     #     n_dist = 0
115                     # else:
116                     #     n_dist = self.dist_from_other_rooms
117                     #
118                     # if h + 1 == self.height:
119                     #     s_dist = self.region_size - self.dist_from_other_rooms
120                     # elif self.region_map[h+1][w] == region_selected:
121                     #     s_dist = self.region_size
122                     # else:
123                     #     s_dist = self.region_size - self.dist_from_other_rooms
124                     #
125                     # for wt in range(w_dist, e_dist):
126                     #     for ht in range(n_dist, s_dist):
127                     #         self.map[h * self.region_size + ht]\
128                     #             [w * self.region_size + wt] = ' '
129
130
131 class LevelGenerator:
132     width = 0
133     height = 0
134     no_rooms = 0
135     rooms = []
136     map = None
137     map2 = None
138     dist_from_other_rooms = 0
139     region_map = None
140     regions = 0
141     region_size = 0
142
143     def __init__(self, width, height, no_rooms, dist_from_other_rooms,
144                  region_size):
145         """ Initialize the level parameters
146         """
147         self.width = width
148         self.height = height
149         self.no_rooms = no_rooms
150         self.dist_from_other_rooms = dist_from_other_rooms
151         self.region_size = region_size
152         self.region_coordinates = []
153
154     def generate(self):
155         """ Generate a random level map
156         """
157         self.generate_rooms()
158         regions_selected = random.sample(range(self.regions),
159                                          min(self.regions, self.no_rooms))
160         row = ['#' for x in range(self.width * self.region_size)]
161         self.map = [row[:] for x in range(self.height * self.region_size)]
162         self.map2 = [row[:] for x in range(self.height * self.region_size)]
163         print('Regions: %s' % str(regions_selected))
164         for region in regions_selected:
165             self.rooms[region].connect_rooms(
166                 [self.rooms[i] for i in regions_selected])
167             self.generate_tiles(region)
168         region_coordinates_selected = [p for p in self.region_coordinates if
169                                        p[0] in regions_selected]
170         print('Coords: %s' % str(region_coordinates_selected))
171         for coord in region_coordinates_selected:
172             print(str(coord))
173             # self.rooms[coord[0]].render_region(
174             #     coord[1], self.dist_from_other_rooms, self.region_size,
175             #     self.map2, coord[1][0] * self.region_size,
176             #     coord[1][1] * self.region_size)
177
178     def generate_rooms(self):
179         """ Generate a random level region map
180         """
181         row = [0 for x in range(self.width)]
182         self.region_map = [row[:] for x in range(self.height)]
183         for h in range(self.height):
184             for w in range(self.width):
185                 random_number = random.randint(0, 2)
186                 increment_region = False
187                 if w == h == 0:
188                     update_value = self.regions
189                     increment_region = True
190                 elif h == 0:
191                     if random_number > 1:
192                         update_value = self.region_map[h][w - 1]
193                     else:
194                         update_value = self.regions
195                         increment_region = True
196                 elif w == 0:
197                     if random_number > 1:
198                         update_value = self.region_map[h - 1][w]
199                     else:
200                         update_value = self.regions
201                         increment_region = True
202                 else:
203                     if random_number > 1:
204                         update_value = self.region_map[h - 1][w]
205                     elif random_number > 0:
206                         update_value = self.region_map[h][w - 1]
207                     else:
208                         update_value = self.regions
209                         increment_region = True
210                 self.region_map[h][w] = update_value
211                 if increment_region:
212                     r = Room([h, w], self.regions)
213                     self.rooms.append(r)
214                     self.region_coordinates.append([update_value, [h, w]])
215                     self.regions += 1
216                 else:
217                     self.rooms[-1].add_region([h, w])
218                     self.region_coordinates.append([update_value, [h, w]])
219
220     def generate_tiles(self, region_selected):
221         """
222         :param region_selected:
223         :return:
224         """
225         for h in range(self.height):
226             for w in range(self.width):
227                 if self.region_map[h][w] == region_selected:
228                     if w == 0:
229                         w_dist = self.dist_from_other_rooms
230                     elif self.region_map[h][w-1] == region_selected:
231                         w_dist = 0
232                     else:
233                         w_dist = self.dist_from_other_rooms
234
235                     if w + 1 == self.width:
236                         e_dist = self.region_size - self.dist_from_other_rooms
237                     elif self.region_map[h][w+1] == region_selected:
238                         e_dist = self.region_size
239                     else:
240                         e_dist = self.region_size - self.dist_from_other_rooms
241
242                     if h == 0:
243                         n_dist = self.dist_from_other_rooms
244                     elif self.region_map[h-1][w] == region_selected:
245                         n_dist = 0
246                     else:
247                         n_dist = self.dist_from_other_rooms
248
249                     if h + 1 == self.height:
250                         s_dist = self.region_size - self.dist_from_other_rooms
251                     elif self.region_map[h+1][w] == region_selected:
252                         s_dist = self.region_size
253                     else:
254                         s_dist = self.region_size - self.dist_from_other_rooms
255
256                     for wt in range(w_dist, e_dist):
257                         for ht in range(n_dist, s_dist):
258                             self.map[h * self.region_size + ht]\
259                                 [w * self.region_size + wt] = str(region_selected)
260
261     def display(self):
262         file = open('map.txt', 'w')
263         print('-----------------')
264         for l in self.map:
265             print(''.join(l))
266             file.write(''.join(l))
267             file.write('\n')
268         print('-----------------')
269         for l in self.map2:
270             print(''.join(l))
271             file.write(''.join(l))
272             file.write('\n')
273         print('-----------------')
274         file.close()
275         for l in self.region_map:
276             # self._to_json()
277             print(l)
278
279     def _to_json(self):
280         level = {}
281         level['tiles'] = []
282         for l in self.map:
283             row = []
284             for t in l:
285                 row.append(ATTRIBUTE_MAP[t])
286             level['tiles'].append(row)
287         name = os.path.join(os.path.dirname(__file__), '..', 'levels', 'map.json')
288         # FIXME: Do a lot better here 
289         # Crude hack so the level is written into the levels folder
290         f = open(name, 'w')
291         json.dump(level, f)
292         f.close()
293
294
295 if __name__ == '__main__':
296     while True:
297         level = LevelGenerator(width=4, height=3, no_rooms=4,
298                                dist_from_other_rooms=0, region_size=3)
299         level.generate()
300         level.display()
301         input("Press Enter to continue...")