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