Hack in underlay generation
[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 i = random.randint(0,100)
9
10
11 ATTRIBUTE_MAP = {
12     '#': {'floor': {'base': 'cwall',
13                       'behaviour': [],
14                    },
15          },
16     ' ': {'floor': {'base': 'floor',
17                       'behaviour': ['walk', 'fly'],
18                    },
19          },
20     'o': {'tunnels': {'base': 'cwall',
21                       'behaviour': [],
22                      }
23          },
24     '-': {'tunnels': {'base': 'floor',
25                       'behaviour': ['walk',],
26                    },
27          },
28 }
29
30
31 class LevelGenerator:
32     width = 0
33     height = 0
34     rooms = 0
35     map = None
36     min_room_size = 0
37     max_room_size = 0
38     dist_from_edge = 0
39     dist_from_other_rooms = 0
40     regions = None
41     region = 0
42     region_size_in_tiles = 0
43
44     def __init__(self, width, height, rooms, min_room_size, max_room_size, dist_from_edge,
45                  dist_from_other_rooms, region_size_in_tiles):
46         """ Initialize the level parameters
47         """
48         self.width = width
49         self.height = height
50         self.rooms = rooms
51         self.min_room_size = min_room_size
52         self.max_room_size = max_room_size
53         self.dist_from_edge = dist_from_edge
54         self.dist_from_other_rooms = dist_from_other_rooms
55         self.region_size_in_tiles = region_size_in_tiles
56
57     def generate(self):
58         """ Generate a random level map
59         """
60         self.generate_regions()
61         row = ['#' for x in range(self.width * self.region_size_in_tiles)]
62         self.map = [row[:] for x in range(self.height * self.region_size_in_tiles)]
63         regions_selected = random.sample(range(self.region), min(self.region, self.rooms))
64         print('Regions: %s' % str(regions_selected))
65         for region in regions_selected:
66             self.generate_room(region)
67         self.generate_underlayer()
68
69     def generate_regions(self):
70         """ Generate a random level region map
71         """
72         row = ['#' for x in range(self.width)]
73         self.regions = [row[:] for x in range(self.height)]
74         for h in range(self.height):
75             for w in range(self.width):
76                 random_number = random.randint(0, 2)
77                 if w == h == 0:
78                     self.regions[h][w] = self.region
79                     self.region += 1
80                 elif h == 0:
81                     if random_number > 1:
82                         try:
83                             self.regions[h][w] = self.regions[h][w - 1]
84                         except:
85                             print(h, w)
86                             raise
87                     else:
88                         self.regions[h][w] = self.region
89                         self.region += 1
90                 elif w == 0:
91                     if random_number > 1:
92                         self.regions[h][w] = self.regions[h - 1][w]
93                     else:
94                         self.regions[h][w] = self.region
95                         self.region += 1
96                 else:
97                     if random_number > 1:
98                         self.regions[h][w] = self.regions[h - 1][w]
99                     elif random_number > 0:
100                         self.regions[h][w] = self.regions[h][w - 1]
101                     else:
102                         self.regions[h][w] = self.region
103                         self.region += 1
104
105     def generate_underlayer(self):
106         """Generate a small mess of tunnels to have something."""
107         width = len(self.map[0])
108         height = len(self.map)
109         row = ['o' for x in range(width)]
110         self.underlayer = [row[:] for x in range(height)]
111         # we create a set of biased random walks to create the tunnel network
112         for walk in range(random.randint(3, 6)):
113             x = width // 2 + random.randint(-8, 8)
114             y = height // 2 + random.randint(-8, 8)
115             dir_x = random.randint(-1, 1)
116             dir_y = random.randint(-1, 1)
117             max_steps = random.randint(40, width * height // 4)
118             for step in range(20, max_steps):
119                 if 0 < x < width - 1:
120                     if 0 < y < height - 1:
121                         self.underlayer[y][x] = '-'
122                 if random.random() > 0.7:
123                    dir_x = random.randint(-1, 1)
124                    dir_y = random.randint(-1, 1)
125                 x += dir_x
126                 y += dir_y
127
128     def generate_room(self, region_selected):
129         """
130         """
131         for h in range(self.height):
132             for w in range(self.width):
133                 if self.regions[h][w] == region_selected:
134                     if w == 0:
135                         w_dist = self.dist_from_other_rooms
136                     elif self.regions[h][w-1] == region_selected:
137                         w_dist = 0
138                     else:
139                         w_dist = self.dist_from_other_rooms
140
141                     if w + 1 == self.width:
142                         e_dist = self.region_size_in_tiles - self.dist_from_other_rooms
143                     elif self.regions[h][w+1] == region_selected:
144                         e_dist = self.region_size_in_tiles
145                     else:
146                         e_dist = self.region_size_in_tiles - self.dist_from_other_rooms
147
148                     if h == 0:
149                         n_dist = self.dist_from_other_rooms
150                     elif self.regions[h-1][w] == region_selected:
151                         n_dist = 0
152                     else:
153                         n_dist = self.dist_from_other_rooms
154
155                     if h + 1 == self.height:
156                         s_dist = self.region_size_in_tiles - self.dist_from_other_rooms
157                     elif self.regions[h+1][w] == region_selected:
158                         s_dist = self.region_size_in_tiles
159                     else:
160                         s_dist = self.region_size_in_tiles - self.dist_from_other_rooms
161
162                     for wt in range(w_dist, e_dist):
163                         for ht in range(n_dist, s_dist):
164                             self.map[h * self.region_size_in_tiles + ht][w * self.region_size_in_tiles + wt] = ' '
165
166     def display(self):
167         file = open('map.txt', 'w')
168         for l in self.map:
169             print(''.join(l))
170             file.write(''.join(l))
171             file.write('\n')
172         print('')
173         for l in self.underlayer:
174             print(''.join(l))
175             file.write(''.join(l))
176             file.write('\n')
177         file.close()
178         self._to_json()
179         for l in self.regions:
180             print(l)
181
182     def _to_json(self):
183         level = {}
184         level['tileset'] = 'dungeon'
185         level['tiles'] = []
186         for l, lu in zip(self.map, self.underlayer):
187             row = []
188             for t1, t2 in zip(l, lu):
189                 tile = ATTRIBUTE_MAP[t1].copy()
190                 tile.update(ATTRIBUTE_MAP[t2])
191                 row.append(tile)
192             level['tiles'].append(row)
193         # FIXME: Do a lot better here
194         # Crude hack so the level is written into the levels folder
195         name = os.path.join(os.path.dirname(__file__), '..', 'levels', 'map.json')
196         f = open(name, 'w')
197         json.dump(level, f)
198         f.close()
199
200
201 if __name__ == '__main__':
202     while True:
203         level = LevelGenerator(width=8, height=5, rooms=12, min_room_size=5, max_room_size=20,
204                                dist_from_edge=2, dist_from_other_rooms=1, region_size_in_tiles=6)
205         level.generate()
206         level.display()
207         input("Press Enter to continue...")