X-Git-Url: https://plomlompom.com/repos/foo.html?a=blobdiff_plain;f=plomrogue%2Fmapping.py;h=4f2f5f9e3b7c465bc05ed1409af9b318befb8d71;hb=0a25fa6dadb1560ed64c22fe12a6c3d8de567b84;hp=e0a59d8e225f56af79b0b516bb7f6e366de164a4;hpb=540aec0e9bf55d0452cffda4b34e1995d3724abf;p=plomrogue2 diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index e0a59d8..4f2f5f9 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -56,6 +56,10 @@ class MapGeometryWithLeftRightMoves(MapGeometry): class MapGeometrySquare(MapGeometryWithLeftRightMoves): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fov_map_class = FovMapSquare + def move_UP(self, start_pos): return YX(start_pos.y - 1, start_pos.x) @@ -63,9 +67,12 @@ class MapGeometrySquare(MapGeometryWithLeftRightMoves): return YX(start_pos.y + 1, start_pos.x) - class MapGeometryHex(MapGeometryWithLeftRightMoves): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fov_map_class = FovMapHex + def move_UPLEFT(self, start_pos): start_indented = start_pos.y % 2 if start_indented: @@ -134,3 +141,133 @@ class Map(): width = self.size.x for y in range(self.size.y): yield (y, self.terrain[y * width:(y + 1) * width]) + + + +class FovMap(Map): + + def __init__(self, source_map, center): + self.source_map = source_map + self.size = self.source_map.size + self.fov_radius = (self.size.y / 2) - 0.5 + self.start_indented = True #source_map.start_indented + self.terrain = '?' * self.size_i + self.center = center + self[self.center] = '.' + self.shadow_cones = [] + self.geometry = self.geometry_class(self.size) + self.circle_out(self.center, self.shadow_process) + + def shadow_process(self, yx, distance_to_center, dir_i, dir_progress): + # Possible optimization: If no shadow_cones yet and self[yx] == '.', + # skip all. + CIRCLE = 360 # Since we'll float anyways, number is actually arbitrary. + + def correct_arm(arm): + if arm > CIRCLE: + arm -= CIRCLE + return arm + + def in_shadow_cone(new_cone): + for old_cone in self.shadow_cones: + if old_cone[0] <= new_cone[0] and \ + new_cone[1] <= old_cone[1]: + return True + # We might want to also shade tiles whose middle arm is inside a + # shadow cone for a darker FOV. Note that we then could not for + # optimization purposes rely anymore on the assumption that a + # shaded tile cannot add growth to existing shadow cones. + return False + + def merge_cone(new_cone): + import math + for old_cone in self.shadow_cones: + if new_cone[0] < old_cone[0] and \ + (new_cone[1] > old_cone[0] or + math.isclose(new_cone[1], old_cone[0])): + old_cone[0] = new_cone[0] + return True + if new_cone[1] > old_cone[1] and \ + (new_cone[0] < old_cone[1] or + math.isclose(new_cone[0], old_cone[1])): + old_cone[1] = new_cone[1] + return True + return False + + def eval_cone(cone): + if in_shadow_cone(cone): + return + self[yx] = '.' + if self.source_map[yx] == 'X': + unmerged = True + while merge_cone(cone): + unmerged = False + if unmerged: + self.shadow_cones += [cone] + + step_size = (CIRCLE/len(self.circle_out_directions)) / distance_to_center + number_steps = dir_i * distance_to_center + dir_progress + left_arm = correct_arm(step_size/2 + step_size*number_steps) + right_arm = correct_arm(left_arm + step_size) + + # Optimization potential: left cone could be derived from previous + # right cone. Better even: Precalculate all cones. + if right_arm < left_arm: + eval_cone([left_arm, CIRCLE]) + eval_cone([0, right_arm]) + else: + eval_cone([left_arm, right_arm]) + + def basic_circle_out_move(self, pos, direction): + """Move position pos into direction. Return whether still in map.""" + mover = getattr(self.geometry, 'move_' + direction) + pos = mover(pos) #, self.start_indented) + if pos.y < 0 or pos.x < 0 or \ + pos.y >= self.size.y or pos.x >= self.size.x: + return pos, False + return pos, True + + def circle_out(self, yx, f): + # Optimization potential: Precalculate movement positions. (How to check + # circle_in_map then?) + # Optimization potential: Precalculate what tiles are shaded by what tile + # and skip evaluation of already shaded tile. (This only works if tiles + # shading implies they completely lie in existing shades; otherwise we + # would lose shade growth through tiles at shade borders.) + circle_in_map = True + distance = 1 + yx = YX(yx.y, yx.x) + while circle_in_map: + if distance > self.fov_radius: + break + circle_in_map = False + yx, _ = self.basic_circle_out_move(yx, 'RIGHT') + for dir_i in range(len(self.circle_out_directions)): + for dir_progress in range(distance): + direction = self.circle_out_directions[dir_i] + yx, test = self.circle_out_move(yx, direction) + if test: + f(yx, distance, dir_i, dir_progress) + circle_in_map = True + distance += 1 + + + +class FovMapHex(FovMap): + circle_out_directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', + 'UPRIGHT', 'RIGHT', 'DOWNRIGHT') + geometry_class = MapGeometryHex + + def circle_out_move(self, yx, direction): + return self.basic_circle_out_move(yx, direction) + + + +class FovMapSquare(FovMap): + circle_out_directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'), + ('UP', 'RIGHT'), ('RIGHT', 'DOWN')) + geometry_class = MapGeometrySquare + + def circle_out_move(self, yx, direction): + yx, _ = self.basic_circle_out_move(yx, direction[0]) + return self.basic_circle_out_move(yx, direction[1])