8 class Map(game_common.Map):
10 def __getitem__(self, yx):
11 return self.terrain[self.get_position_index(yx)]
13 def __setitem__(self, yx, c):
14 pos_i = self.get_position_index(yx)
15 self.terrain = self.terrain[:pos_i] + c + self.terrain[pos_i + 1:]
18 """Iterate over YX position coordinates."""
19 for y in range(self.size[0]):
20 for x in range(self.size[1]):
25 return self.__class__.__name__[3:]
29 for y in range(self.size[0]):
30 yield (y, self.terrain[y * width:(y + 1) * width])
32 def get_fov_map(self, yx):
33 # TODO: Currently only have MapFovHex. Provide MapFovSquare.
34 fov_map_class = map_manager.get_map_class('Fov' + self.geometry)
35 return fov_map_class(self, yx)
37 # The following is used nowhere, so not implemented.
39 # for y in range(self.size[0]):
40 # for x in range(self.size[1]):
41 # yield ([y, x], self.terrain[self.get_position_index([y, x])])
43 def get_directions(self):
45 for name in dir(self):
46 if name[:5] == 'move_':
47 directions += [name[5:]]
50 def new_from_shape(self, init_char):
52 new_map = copy.deepcopy(self)
54 new_map[pos] = init_char
57 def move(self, start_pos, direction):
58 mover = getattr(self, 'move_' + direction)
59 new_pos = mover(start_pos)
60 if new_pos[0] < 0 or new_pos[1] < 0 or \
61 new_pos[0] >= self.size[0] or new_pos[1] >= self.size[1]:
62 raise server_.game.GameError('would move outside map bounds')
65 def move_LEFT(self, start_pos):
66 return [start_pos[0], start_pos[1] - 1]
68 def move_RIGHT(self, start_pos):
69 return [start_pos[0], start_pos[1] + 1]
74 # The following is used nowhere, so not implemented.
75 #def are_neighbors(self, pos_1, pos_2):
76 # if pos_1[0] == pos_2[0] and abs(pos_1[1] - pos_2[1]) <= 1:
78 # elif abs(pos_1[0] - pos_2[0]) == 1:
79 # if pos_1[0] % 2 == 0:
80 # if pos_2[1] in (pos_1[1], pos_1[1] - 1):
82 # elif pos_2[1] in (pos_1[1], pos_1[1] + 1):
86 def move_UPLEFT(self, start_pos):
87 if start_pos[0] % 2 == 1:
88 return [start_pos[0] - 1, start_pos[1] - 1]
90 return [start_pos[0] - 1, start_pos[1]]
92 def move_UPRIGHT(self, start_pos):
93 if start_pos[0] % 2 == 1:
94 return [start_pos[0] - 1, start_pos[1]]
96 return [start_pos[0] - 1, start_pos[1] + 1]
98 def move_DOWNLEFT(self, start_pos):
99 if start_pos[0] % 2 == 1:
100 return [start_pos[0] + 1, start_pos[1] - 1]
102 return [start_pos[0] + 1, start_pos[1]]
104 def move_DOWNRIGHT(self, start_pos):
105 if start_pos[0] % 2 == 1:
106 return [start_pos[0] + 1, start_pos[1]]
108 return [start_pos[0] + 1, start_pos[1] + 1]
111 class MapFovHex(MapHex):
113 def __init__(self, source_map, yx):
114 self.source_map = source_map
115 self.size = self.source_map.size
116 self.terrain = '?' * self.size_i
118 self.shadow_cones = []
119 self.circle_out(yx, self.shadow_process_hex)
121 def shadow_process_hex(self, yx, distance_to_center, dir_i, dir_progress):
122 # Possible optimization: If no shadow_cones yet and self[yx] == '.',
124 CIRCLE = 360 # Since we'll float anyways, number is actually arbitrary.
126 def correct_arm(arm):
131 def in_shadow_cone(new_cone):
132 for old_cone in self.shadow_cones:
133 if old_cone[0] >= new_cone[0] and \
134 new_cone[1] >= old_cone[1]:
135 #print('DEBUG shadowed by:', old_cone)
137 # We might want to also shade hexes whose middle arm is inside a
138 # shadow cone for a darker FOV. Note that we then could not for
139 # optimization purposes rely anymore on the assumption that a
140 # shaded hex cannot add growth to existing shadow cones.
143 def merge_cone(new_cone):
144 for old_cone in self.shadow_cones:
145 if new_cone[0] > old_cone[0] and \
146 new_cone[1] <= old_cone[0]:
147 #print('DEBUG merging to', old_cone)
148 old_cone[0] = new_cone[0]
149 #print('DEBUG merged cone:', old_cone)
151 if new_cone[1] < old_cone[1] and \
152 new_cone[0] >= old_cone[1]:
153 #print('DEBUG merging to', old_cone)
154 old_cone[1] = new_cone[1]
155 #print('DEBUG merged cone:', old_cone)
160 new_cone = [left_arm, right_arm]
161 #print('DEBUG CONE', cone, '(', step_size, distance_to_center, number_steps, ')')
162 if in_shadow_cone(cone):
165 if self.source_map[yx] != '.':
166 #print('DEBUG throws shadow', cone)
168 while merge_cone(cone):
171 self.shadow_cones += [cone]
174 step_size = fractions.Fraction(CIRCLE, 6) / distance_to_center
175 number_steps = dir_i * distance_to_center + dir_progress
176 left_arm = correct_arm(-(step_size/2) - step_size*number_steps)
177 right_arm = correct_arm(left_arm - step_size)
178 # Optimization potential: left cone could be derived from previous
179 # right cone. Better even: Precalculate all cones.
180 if right_arm > left_arm:
181 eval_cone([left_arm, 0])
182 eval_cone([CIRCLE, right_arm])
184 eval_cone([left_arm, right_arm])
186 def circle_out(self, yx, f):
187 # Optimization potential: Precalculate movement positions. (How to check
188 # circle_in_map then?)
189 # Optimization potential: Precalculate what hexes are shaded by what hex
190 # and skip evaluation of already shaded hexes. (This only works if hex
191 # shading implies they completely lie in existing shades; otherwise we
192 # would lose shade growth through hexes at shade borders.)
194 def move(pos, direction):
195 """Move position pos into direction. Return whether still in map."""
196 mover = getattr(self, 'move_' + direction)
198 if pos[0] < 0 or pos[1] < 0 or \
199 pos[0] >= self.size[0] or pos[1] >= self.size[1]:
203 # TODO: Start circling only in earliest obstacle distance.
204 directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', 'UPRIGHT', 'RIGHT', 'DOWNRIGHT')
208 #print('DEBUG CIRCLE_OUT', yx)
210 circle_in_map = False
212 for dir_i in range(len(directions)):
213 for dir_progress in range(distance):
214 direction = directions[dir_i]
215 if move(yx, direction):
216 f(yx, distance, dir_i, dir_progress)
221 class MapSquare(Map):
223 # The following is used nowhere, so not implemented.
224 #def are_neighbors(self, pos_1, pos_2):
225 # return abs(pos_1[0] - pos_2[0]) <= 1 and abs(pos_1[1] - pos_2[1] <= 1)
227 def move_UP(self, start_pos):
228 return [start_pos[0] - 1, start_pos[1]]
230 def move_DOWN(self, start_pos):
231 return [start_pos[0] + 1, start_pos[1]]
234 class MapFovSquare(MapSquare):
235 """Just a marginally and unsatisfyingly adapted variant of MapFovHex."""
237 def __init__(self, source_map, yx):
238 self.source_map = source_map
239 self.size = self.source_map.size
240 self.terrain = '?' * self.size_i
242 self.shadow_cones = []
243 self.circle_out(yx, self.shadow_process_hex)
245 def shadow_process_hex(self, yx, distance_to_center, dir_i, dir_progress):
246 CIRCLE = 360 # Since we'll float anyways, number is actually arbitrary.
248 def correct_arm(arm):
253 def in_shadow_cone(new_cone):
254 for old_cone in self.shadow_cones:
255 if old_cone[0] >= new_cone[0] and \
256 new_cone[1] >= old_cone[1]:
257 #print('DEBUG shadowed by:', old_cone)
261 def merge_cone(new_cone):
262 for old_cone in self.shadow_cones:
263 if new_cone[0] > old_cone[0] and \
264 new_cone[1] <= old_cone[0]:
265 #print('DEBUG merging to', old_cone)
266 old_cone[0] = new_cone[0]
267 #print('DEBUG merged cone:', old_cone)
269 if new_cone[1] < old_cone[1] and \
270 new_cone[0] >= old_cone[1]:
271 #print('DEBUG merging to', old_cone)
272 old_cone[1] = new_cone[1]
273 #print('DEBUG merged cone:', old_cone)
278 new_cone = [left_arm, right_arm]
279 #print('DEBUG CONE', cone, '(', step_size, distance_to_center, number_steps, ')')
280 if in_shadow_cone(cone):
283 if self.source_map[yx] != '.':
284 #print('DEBUG throws shadow', cone)
286 while merge_cone(cone):
289 self.shadow_cones += [cone]
292 step_size = fractions.Fraction(CIRCLE, 4) / distance_to_center
293 number_steps = dir_i * distance_to_center + dir_progress
294 left_arm = correct_arm(-(step_size/2) - step_size*number_steps)
295 right_arm = correct_arm(left_arm - step_size)
296 if right_arm > left_arm:
297 eval_cone([left_arm, 0])
298 eval_cone([CIRCLE, right_arm])
300 eval_cone([left_arm, right_arm])
302 def circle_out(self, yx, f):
304 def move(pos, direction):
305 """Move position pos into direction. Return whether still in map."""
306 mover = getattr(self, 'move_' + direction)
308 if pos[0] < 0 or pos[1] < 0 or \
309 pos[0] >= self.size[0] or pos[1] >= self.size[1]:
313 directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'),
314 ('UP', 'RIGHT'), ('RIGHT', 'DOWN'))
318 #print('DEBUG CIRCLE_OUT', yx)
320 circle_in_map = False
322 for dir_i in range(len(directions)):
323 for dir_progress in range(distance):
324 direction = directions[dir_i]
325 move(yx, direction[0])
326 if move(yx, direction[1]):
327 f(yx, distance, dir_i, dir_progress)
332 map_manager = game_common.MapManager(globals())