home · contact · privacy
Add FOV algorithm for Hex grids.
authorChristian Heller <c.heller@plomlompom.de>
Sat, 19 Jan 2019 04:47:38 +0000 (05:47 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 19 Jan 2019 04:47:38 +0000 (05:47 +0100)
server_/game.py
server_/map_.py

index 07dde9d5011144f155758a0304479b1c046a94df..8068556ca89521f59648ad80afeae73ebee8e1a3 100644 (file)
@@ -127,11 +127,7 @@ class Thing(game_common.Thing):
     def get_stencil(self):
         if self._stencil is not None:
             return self._stencil
-        m = self.world.map_.new_from_shape('?')
-        for pos in m:
-            if pos == self.position or m.are_neighbors(pos, self.position):
-                m[pos] = '.'
-        self._stencil = m
+        self._stencil = self.world.map_.get_fov_map(self.position)
         return self._stencil
 
     def get_visible_map(self):
index 069af8dc92fe8c68a52f609bcb75daa3cef96e49..d1aa53851b1ee91846d7ee7a861903725b8d9a45 100644 (file)
@@ -28,6 +28,11 @@ class Map(game_common.Map):
         for y in range(self.size[0]):
             yield (y, self.terrain[y * width:(y + 1) * width])
 
+    def get_fov_map(self, yx):
+        # TODO: Currently only have MapFovHex. Provide MapFovSquare.
+        fov_map_class = map_manager.get_map_class('Fov' + self.geometry)
+        return fov_map_class(self, yx)
+
     # The following is used nowhere, so not implemented.
     #def items(self):
     #    for y in range(self.size[0]):
@@ -65,16 +70,17 @@ class Map(game_common.Map):
 
 class MapHex(Map):
 
-    def are_neighbors(self, pos_1, pos_2):
-        if pos_1[0] == pos_2[0] and abs(pos_1[1] - pos_2[1]) <= 1:
-            return True
-        elif abs(pos_1[0] - pos_2[0]) == 1:
-            if pos_1[0] % 2 == 0:
-                if pos_2[1] in (pos_1[1], pos_1[1] - 1):
-                    return True
-            elif pos_2[1] in (pos_1[1], pos_1[1] + 1):
-                return True
-        return False
+    # The following is used nowhere, so not implemented.
+    #def are_neighbors(self, pos_1, pos_2):
+    #    if pos_1[0] == pos_2[0] and abs(pos_1[1] - pos_2[1]) <= 1:
+    #        return True
+    #    elif abs(pos_1[0] - pos_2[0]) == 1:
+    #        if pos_1[0] % 2 == 0:
+    #            if pos_2[1] in (pos_1[1], pos_1[1] - 1):
+    #                return True
+    #        elif pos_2[1] in (pos_1[1], pos_1[1] + 1):
+    #            return True
+    #    return False
 
     def move_UPLEFT(self, start_pos):
         if start_pos[0] % 2 == 0:
@@ -101,10 +107,107 @@ class MapHex(Map):
             return [start_pos[0] + 1, start_pos[1] + 1]
 
 
+class MapFovHex(MapHex):
+
+    def __init__(self, source_map, yx):
+        self.source_map = source_map
+        self.size = self.source_map.size
+        self.terrain = '?' * self.size_i
+        self[yx] = '.'
+        self.shadow_angles = []
+        self.circle_out(yx, self.shadow_process_hex)
+
+    def shadow_process_hex(self, yx, distance_to_center, dir_i, hex_i):
+        CIRCLE = 360  # Since we'll float anyways, number is actually arbitrary.
+
+        def correct_angle(angle):
+            if angle < 0:
+                angle += CIRCLE
+            return angle
+
+        def under_shadow_angle(new_angle):
+            for old_angle in self.shadow_angles:
+                if old_angle[0] >= new_angle[0] and \
+                    new_angle[1] >= old_angle[1]:
+                    #print('DEBUG shadowed by:', old_angle)
+                    return True
+            return False
+
+        def merge_angle(new_angle):
+            for old_angle in self.shadow_angles:
+                if new_angle[0] > old_angle[0] and \
+                    new_angle[1] <= old_angle[0]:
+                    #print('DEBUG merging to', old_angle)
+                    old_angle[0] = new_angle[0]
+                    #print('DEBUG merged angle:', old_angle)
+                    return True
+                if new_angle[1] < old_angle[1] and \
+                    new_angle[0] >= old_angle[1]:
+                    #print('DEBUG merging to', old_angle)
+                    old_angle[1] = new_angle[1]
+                    #print('DEBUG merged angle:', old_angle)
+                    return True
+            return False
+
+        def eval_angle(angle):
+            new_angle = [left_angle, right_angle]
+            #print('DEBUG ANGLE', angle, '(', step_size, distance_to_center, number_steps, ')')
+            if under_shadow_angle(angle):
+                return
+            self[yx] = '.'
+            if not self.source_map[yx] == '.':
+                #print('DEBUG throws shadow', angle)
+                unmerged = True
+                while merge_angle(angle):
+                    unmerged = False
+                if unmerged:
+                    self.shadow_angles += [angle]
+
+        #print('DEBUG', yx)
+        step_size = (CIRCLE/6)/distance_to_center
+        number_steps = dir_i * distance_to_center + hex_i
+        left_angle = correct_angle(-(step_size/2) - step_size*number_steps)
+        right_angle = correct_angle(left_angle - step_size)
+        if right_angle > left_angle:
+            eval_angle([left_angle, 0])
+            eval_angle([CIRCLE, right_angle])
+        else:
+            eval_angle([left_angle, right_angle])
+
+    def circle_out(self, yx, f):
+
+        def move(pos, direction):
+            """Move position pos into direction. Return whether still in map."""
+            mover = getattr(self, 'move_' + direction)
+            pos[:] = mover(pos)
+            if pos[0] < 0 or pos[1] < 0 or \
+               pos[0] >= self.size[0] or pos[1] >= self.size[1]:
+                return False
+            return True
+
+        directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', 'UPRIGHT', 'RIGHT', 'DOWNRIGHT')
+        circle_in_map = True
+        distance = 1
+        first_direction = 'RIGHT'
+        yx = yx[:]
+        #print('DEBUG CIRCLE_OUT', yx)
+        while circle_in_map:
+            circle_in_map = False
+            move(yx, 'RIGHT')
+            for dir_i in range(len(directions)):
+                for hex_i in range(distance):
+                    direction = directions[dir_i]
+                    if move(yx, direction):
+                        f(yx, distance, dir_i, hex_i)
+                        circle_in_map = True
+            distance += 1
+
+
 class MapSquare(Map):
 
-    def are_neighbors(self, pos_1, pos_2):
-        return abs(pos_1[0] - pos_2[0]) <= 1 and abs(pos_1[1] - pos_2[1] <= 1)
+    # The following is used nowhere, so not implemented.
+    #def are_neighbors(self, pos_1, pos_2):
+    #    return abs(pos_1[0] - pos_2[0]) <= 1 and abs(pos_1[1] - pos_2[1] <= 1)
 
     def move_UP(self, start_pos):
         return [start_pos[0] - 1, start_pos[1]]
@@ -113,8 +216,4 @@ class MapSquare(Map):
         return [start_pos[0] + 1, start_pos[1]]
 
 
-def get_map_class(geometry):
-    return globals()['Map' + geometry]
-
-
 map_manager = game_common.MapManager(globals())