home · contact · privacy
6e14c5a2508736df66a487a9d551838d25727d5d
[plomrogue2-experiments] / server_ / map_.py
1 import sys
2 sys.path.append('../')
3 import game_common
4 import server_.game
5
6
7 class Map(game_common.Map):
8
9     def __getitem__(self, yx):
10         return self.terrain[self.get_position_index(yx)]
11
12     def __setitem__(self, yx, c):
13         pos_i = self.get_position_index(yx)
14         self.terrain = self.terrain[:pos_i] + c + self.terrain[pos_i + 1:]
15
16     def __iter__(self):
17         """Iterate over YX position coordinates."""
18         for y in range(self.size[0]):
19             for x in range(self.size[1]):
20                 yield [y, x]
21
22     @property
23     def geometry(self):
24         return self.__class__.__name__[3:]
25
26     def lines(self):
27         width = self.size[1]
28         for y in range(self.size[0]):
29             yield (y, self.terrain[y * width:(y + 1) * width])
30
31     def get_fov_map(self, yx):
32         # TODO: Currently only have MapFovHex. Provide MapFovSquare.
33         fov_map_class = map_manager.get_map_class('Fov' + self.geometry)
34         return fov_map_class(self, yx)
35
36     # The following is used nowhere, so not implemented.
37     #def items(self):
38     #    for y in range(self.size[0]):
39     #        for x in range(self.size[1]):
40     #            yield ([y, x], self.terrain[self.get_position_index([y, x])])
41
42     def get_directions(self):
43         directions = []
44         for name in dir(self):
45             if name[:5] == 'move_':
46                 directions += [name[5:]]
47         return directions
48
49     def new_from_shape(self, init_char):
50         import copy
51         new_map = copy.deepcopy(self)
52         for pos in new_map:
53             new_map[pos] = init_char
54         return new_map
55
56     def move(self, start_pos, direction):
57         mover = getattr(self, 'move_' + direction)
58         new_pos = mover(start_pos)
59         if new_pos[0] < 0 or new_pos[1] < 0 or \
60                 new_pos[0] >= self.size[0] or new_pos[1] >= self.size[1]:
61             raise server_.game.GameError('would move outside map bounds')
62         return new_pos
63
64     def move_LEFT(self, start_pos):
65         return [start_pos[0], start_pos[1] - 1]
66
67     def move_RIGHT(self, start_pos):
68         return [start_pos[0], start_pos[1] + 1]
69
70
71 class MapHex(Map):
72
73     # The following is used nowhere, so not implemented.
74     #def are_neighbors(self, pos_1, pos_2):
75     #    if pos_1[0] == pos_2[0] and abs(pos_1[1] - pos_2[1]) <= 1:
76     #        return True
77     #    elif abs(pos_1[0] - pos_2[0]) == 1:
78     #        if pos_1[0] % 2 == 0:
79     #            if pos_2[1] in (pos_1[1], pos_1[1] - 1):
80     #                return True
81     #        elif pos_2[1] in (pos_1[1], pos_1[1] + 1):
82     #            return True
83     #    return False
84
85     def move_UPLEFT(self, start_pos):
86         if start_pos[0] % 2 == 1:
87             return [start_pos[0] - 1, start_pos[1] - 1]
88         else:
89             return [start_pos[0] - 1, start_pos[1]]
90
91     def move_UPRIGHT(self, start_pos):
92         if start_pos[0] % 2 == 1:
93             return [start_pos[0] - 1, start_pos[1]]
94         else:
95             return [start_pos[0] - 1, start_pos[1] + 1]
96
97     def move_DOWNLEFT(self, start_pos):
98         if start_pos[0] % 2 == 1:
99             return [start_pos[0] + 1, start_pos[1] - 1]
100         else:
101             return [start_pos[0] + 1, start_pos[1]]
102
103     def move_DOWNRIGHT(self, start_pos):
104         if start_pos[0] % 2 == 1:
105             return [start_pos[0] + 1, start_pos[1]]
106         else:
107             return [start_pos[0] + 1, start_pos[1] + 1]
108
109
110 class MapFovHex(MapHex):
111
112     def __init__(self, source_map, yx):
113         self.source_map = source_map
114         self.size = self.source_map.size
115         self.terrain = '?' * self.size_i
116         self[yx] = '.'
117         self.shadow_cones = []
118         self.circle_out(yx, self.shadow_process_hex)
119
120     def shadow_process_hex(self, yx, distance_to_center, dir_i, dir_progress):
121         # Possible optimization: If no shadow_cones yet and self[yx] == '.',
122         # skip all.
123         CIRCLE = 360  # Since we'll float anyways, number is actually arbitrary.
124
125         def correct_arm(arm):
126             if arm < 0:
127                 arm += CIRCLE
128             return arm
129
130         def in_shadow_cone(new_cone):
131             for old_cone in self.shadow_cones:
132                 if old_cone[0] >= new_cone[0] and \
133                     new_cone[1] >= old_cone[1]:
134                     #print('DEBUG shadowed by:', old_cone)
135                     return True
136                 # We might want to also shade hexes whose middle arm is inside a
137                 # shadow cone for a darker FOV. Note that we then could not for
138                 # optimization purposes rely anymore on the assumption that a
139                 # shaded hex cannot add growth to existing shadow cones.
140             return False
141
142         def merge_cone(new_cone):
143             for old_cone in self.shadow_cones:
144                 if new_cone[0] > old_cone[0] and \
145                     new_cone[1] <= old_cone[0]:
146                     #print('DEBUG merging to', old_cone)
147                     old_cone[0] = new_cone[0]
148                     #print('DEBUG merged cone:', old_cone)
149                     return True
150                 if new_cone[1] < old_cone[1] and \
151                     new_cone[0] >= old_cone[1]:
152                     #print('DEBUG merging to', old_cone)
153                     old_cone[1] = new_cone[1]
154                     #print('DEBUG merged cone:', old_cone)
155                     return True
156             return False
157
158         def eval_cone(cone):
159             new_cone = [left_arm, right_arm]
160             #print('DEBUG CONE', cone, '(', step_size, distance_to_center, number_steps, ')')
161             if in_shadow_cone(cone):
162                 return
163             self[yx] = '.'
164             if self.source_map[yx] != '.':
165                 #print('DEBUG throws shadow', cone)
166                 unmerged = True
167                 while merge_cone(cone):
168                     unmerged = False
169                 if unmerged:
170                     self.shadow_cones += [cone]
171
172         #print('DEBUG', yx)
173         step_size = (CIRCLE/6)/distance_to_center
174         number_steps = dir_i * distance_to_center + dir_progress
175         left_arm = correct_arm(-(step_size/2) - step_size*number_steps)
176         right_arm = correct_arm(left_arm - step_size)
177         # Optimization potential: left cone could be derived from previous
178         # right cone. Better even: Precalculate all cones.
179         if right_arm > left_arm:
180             eval_cone([left_arm, 0])
181             eval_cone([CIRCLE, right_arm])
182         else:
183             eval_cone([left_arm, right_arm])
184
185     def circle_out(self, yx, f):
186         # Optimization potential: Precalculate movement positions. (How to check
187         # circle_in_map then?)
188         # Optimization potential: Precalculate what hexes are shaded by what hex
189         # and skip evaluation of already shaded hexes. (This only works if hex
190         # shading implies they completely lie in existing shades; otherwise we
191         # would lose shade growth through hexes at shade borders.)
192
193         def move(pos, direction):
194             """Move position pos into direction. Return whether still in map."""
195             mover = getattr(self, 'move_' + direction)
196             pos[:] = mover(pos)
197             if pos[0] < 0 or pos[1] < 0 or \
198                pos[0] >= self.size[0] or pos[1] >= self.size[1]:
199                 return False
200             return True
201
202         # TODO: Start circling only in earliest obstacle distance.
203         directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', 'UPRIGHT', 'RIGHT', 'DOWNRIGHT')
204         circle_in_map = True
205         distance = 1
206         yx = yx[:]
207         #print('DEBUG CIRCLE_OUT', yx)
208         while circle_in_map:
209             circle_in_map = False
210             move(yx, 'RIGHT')
211             for dir_i in range(len(directions)):
212                 for dir_progress in range(distance):
213                     direction = directions[dir_i]
214                     if move(yx, direction):
215                         f(yx, distance, dir_i, dir_progress)
216                         circle_in_map = True
217             distance += 1
218
219
220 class MapSquare(Map):
221
222     # The following is used nowhere, so not implemented.
223     #def are_neighbors(self, pos_1, pos_2):
224     #    return abs(pos_1[0] - pos_2[0]) <= 1 and abs(pos_1[1] - pos_2[1] <= 1)
225
226     def move_UP(self, start_pos):
227         return [start_pos[0] - 1, start_pos[1]]
228
229     def move_DOWN(self, start_pos):
230         return [start_pos[0] + 1, start_pos[1]]
231
232
233 class MapFovSquare(MapSquare):
234     """Just a marginally and unsatisfyingly adapted variant of MapFovHex."""
235
236     def __init__(self, source_map, yx):
237         self.source_map = source_map
238         self.size = self.source_map.size
239         self.terrain = '?' * self.size_i
240         self[yx] = '.'
241         self.shadow_cones = []
242         self.circle_out(yx, self.shadow_process_hex)
243
244     def shadow_process_hex(self, yx, distance_to_center, dir_i, dir_progress):
245         CIRCLE = 360  # Since we'll float anyways, number is actually arbitrary.
246
247         def correct_arm(arm):
248             if arm < 0:
249                 arm += CIRCLE
250             return arm
251
252         def in_shadow_cone(new_cone):
253             for old_cone in self.shadow_cones:
254                 if old_cone[0] >= new_cone[0] and \
255                     new_cone[1] >= old_cone[1]:
256                     #print('DEBUG shadowed by:', old_cone)
257                     return True
258             return False
259
260         def merge_cone(new_cone):
261             for old_cone in self.shadow_cones:
262                 if new_cone[0] > old_cone[0] and \
263                     new_cone[1] <= old_cone[0]:
264                     #print('DEBUG merging to', old_cone)
265                     old_cone[0] = new_cone[0]
266                     #print('DEBUG merged cone:', old_cone)
267                     return True
268                 if new_cone[1] < old_cone[1] and \
269                     new_cone[0] >= old_cone[1]:
270                     #print('DEBUG merging to', old_cone)
271                     old_cone[1] = new_cone[1]
272                     #print('DEBUG merged cone:', old_cone)
273                     return True
274             return False
275
276         def eval_cone(cone):
277             new_cone = [left_arm, right_arm]
278             #print('DEBUG CONE', cone, '(', step_size, distance_to_center, number_steps, ')')
279             if in_shadow_cone(cone):
280                 return
281             self[yx] = '.'
282             if self.source_map[yx] != '.':
283                 #print('DEBUG throws shadow', cone)
284                 unmerged = True
285                 while merge_cone(cone):
286                     unmerged = False
287                 if unmerged:
288                     self.shadow_cones += [cone]
289
290         #print('DEBUG', yx)
291         step_size = (CIRCLE/4)/distance_to_center
292         number_steps = dir_i * distance_to_center + dir_progress
293         left_arm = correct_arm(-(step_size/2) - step_size*number_steps)
294         right_arm = correct_arm(left_arm - step_size)
295         if right_arm > left_arm:
296             eval_cone([left_arm, 0])
297             eval_cone([CIRCLE, right_arm])
298         else:
299             eval_cone([left_arm, right_arm])
300
301     def circle_out(self, yx, f):
302
303         def move(pos, direction):
304             """Move position pos into direction. Return whether still in map."""
305             mover = getattr(self, 'move_' + direction)
306             pos[:] = mover(pos)
307             if pos[0] < 0 or pos[1] < 0 or \
308                pos[0] >= self.size[0] or pos[1] >= self.size[1]:
309                 return False
310             return True
311
312         directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'),
313                       ('UP', 'RIGHT'), ('RIGHT', 'DOWN'))
314         circle_in_map = True
315         distance = 1
316         yx = yx[:]
317         #print('DEBUG CIRCLE_OUT', yx)
318         while circle_in_map:
319             circle_in_map = False
320             move(yx, 'RIGHT')
321             for dir_i in range(len(directions)):
322                 for dir_progress in range(distance):
323                     direction = directions[dir_i]
324                     move(yx, direction[0])
325                     if move(yx, direction[1]):
326                         f(yx, distance, dir_i, dir_progress)
327                         circle_in_map = True
328             distance += 1
329
330
331 map_manager = game_common.MapManager(globals())