Pārlūkot izejas kodu

improve bresenham and shooting actions

Olivier Massot 3 gadi atpakaļ
vecāks
revīzija
3091f10778
2 mainītis faili ar 102 papildinājumiem un 88 dzēšanām
  1. 26 63
      cultist_war/bresenham.py
  2. 76 25
      cultist_war/main.py

+ 26 - 63
cultist_war/bresenham.py

@@ -7,14 +7,6 @@ def log(*msg):
     print("{} - ".format(str(time.time() - t0)[:5]), *msg)
 
 
-obstacles = [(1, 0), (1, 1)]
-
-
-# Version serveur
-def can_see_trough(pos):
-    return pos not in obstacles
-
-
 def line(start, target, strict=True):
     """
     adapted from https://github.com/fragkakis/bresenham/blob/master/src/main/java/org/fragkakis/Bresenham.java
@@ -25,6 +17,14 @@ def line(start, target, strict=True):
     x0, y0 = start
     x1, y1 = target
 
+    reversed = y0 > y1
+
+    if reversed:
+        # on fait toujours de bas en haut, du coup on inverse au besoin
+        x0, y0, x1, y1 = x1, y1, x0, y0
+
+    # NB : not reversed = forward ; reversed = backward
+
     dx = abs(x1 - x0)
     dy = abs(y1 - y0)
 
@@ -35,13 +35,8 @@ def line(start, target, strict=True):
     x, y = x0, y0
 
     while 1:
-        line.append((x, y))
-
-        if x == x1 and y == y1:
-            break
-
         e2 = 2 * err
-        if e2 > (-1 * dy):
+        if e2 > -1 * dy:
             err -= dy
             x += sx
 
@@ -49,56 +44,24 @@ def line(start, target, strict=True):
             err += dx
             y += sy
 
-        if not can_see_trough((x, y)):
-            return None if strict else line
+        if x == x1 and y == y1:
+            break
+
+        line.append((x, y))
+
+    if reversed:
+        line = line[::-1]
+
+    line = [start] + line + [target]
 
     return line
 
 
-log(line((0, 2), (4, 3)))  # test droite / bas
-log(line((0, 5), (4, 3)))  # test droite / haut
-log(line((8, 1), (4, 3)))  # test gauche / bas
-log(line((8, 5), (4, 3)))  # test gauche / haut
-
-#
-# def bresenhamForward(start, target):
-#     x0, y0 = start
-#     x1, y1 = target
-#
-#     dx = abs(x1 - x0)
-#     dy = abs(y1 - y0)
-#
-#     sx = 1 if x0 < x1 else -1
-#     sy = 1 if y0 < y1 else -1
-#
-#     err = dx - dy
-#     x, y = x0, y0
-#
-#     while 1:
-#         e2 = 2 * err
-#         if e2 > (-1 * dy):
-#             err -= dy
-#             x += sx
-#
-#         if e2 < dx:
-#             err += dx
-#             y += sy
-#
-#         if x == x1 and y == y1:
-#             break
-#
-#         if can_see_trough((x, y)):
-#             return []
-#
-#     return []
-#
-#
-# def bresenhamBackward(start, target):
-#     pass
-#
-#
-# def checkBulletPath(start, target):
-#     if start[1] > target[1]:
-#         return bresenhamForward(start, target)
-#     else:
-#         return bresenhamBackward(start, target)
+log(line((4, 4), (7, 6)))
+log(line((7, 6), (4, 4)))
+
+
+# log(line((0, 2), (4, 3)))  # test droite / bas
+# log(line((0, 5), (4, 3)))  # test droite / haut
+# log(line((8, 1), (4, 3)))  # test gauche / bas
+# log(line((8, 5), (4, 3)))  # test gauche / haut

+ 76 - 25
cultist_war/main.py

@@ -232,7 +232,7 @@ class Grid(BaseClass):
         self.index = {}
         self.units = {}
         self.threat = {}
-        self.conversion_path = []
+        self.conversion_path = ConversionPath()
 
         self.cult_leader = None
         self.opponent_cult_leader = None
@@ -315,7 +315,7 @@ class Grid(BaseClass):
                     self.threat[(x, y)] = threat
 
     def compute_conversion_path(self):
-        conversion_path = []
+        conversion_path = ConversionPath()
 
         if self.cult_leader and self.neutrals:
             conversion_path = self.get_conversion_path(
@@ -387,37 +387,81 @@ class Grid(BaseClass):
 
         # Shoot opponent units
         k_shoot_opponent_cultist = 8
-        k_shoot_opponent_cult_leader = 4
+        k_shoot_opponent_leader = 4
         k_shoot_movement_needed = 15
+        k0_shoot_in_danger = -10
+        k_shoot_sacrifice = 50
+        k0_runaway = 10
+        k_runaway_threat = 10
 
         for a in self.allied_cultists:
+            targets = {}
             for u in self.opponent_units:
-                shooting_distance = self.shooting_distance(a.pos, u.pos)
-                if not shooting_distance:
+                line_of_sight = self.line_of_sight(a.pos, u.pos)
+                if not len(line_of_sight):
                     continue
 
-                if shooting_distance <= u.SHOOTING_RANGE:
-                    # la cible est à portée
-                    action = ActionShoot(a, u)
-
-                    priority = (k_shoot_opponent_cult_leader if type(
-                        u) is CultLeader else k_shoot_opponent_cultist) * shooting_distance
-                    # log(self.line_of_sight(a.pos, u.pos))
-                    actions.put(priority, action)
+                if len(line_of_sight) <= u.SHOOTING_RANGE:
+                    targets[u] = line_of_sight
                 else:
-                    # la cible est hors de portée, mais elle est plus faible et sans soutien
+                    # la cible est hors de portée, la poursuivre si plus faible et sans soutien?
                     # TODO: implémenter
                     pass
 
+            if not targets:
+                continue
+
+            # Il est probable que l'unité se fera tirer dessus par l'ennemi le plus proche: quel résultat?
+            dist_to_nearest_target = min([len(line) for line in targets.values()])
+            damages_received = Unit.SHOOTING_MAX_DAMAGE - dist_to_nearest_target
+            unit_in_danger = damages_received >= a.hp or (len(targets) > 1 and (damages_received + 3) >= a.hp)
+
+            for u, line in targets.items():
+                dist = len(line)
+                damages = Unit.SHOOTING_MAX_DAMAGE - dist
+                target_die = damages >= u.hp
+
+                priority = 0
+
+                if unit_in_danger and not target_die:
+                    # run away!
+                    covers = [n for n in self.neighbors(*a.pos)
+                              if self.threat[n] < self.threat[a.pos] and self.can_move_on(a, n)]
+
+                    for pos in covers:
+                        action = ActionMove(a, pos, f'run away!')
+
+                        priority += k0_runaway
+                        priority += k_runaway_threat * self.threat[pos]
+
+                        actions.put(priority, action)
+                    continue
+
+                elif unit_in_danger and target_die and len(targets) > 1:
+                    # bof, un échange, pas sûr que ça puisse être intéressant...
+                    priority += k_shoot_sacrifice
+
+                k = k_shoot_opponent_leader if u is self.opponent_cult_leader else k_shoot_opponent_cultist
+                priority += k * dist
+                priority += k0_shoot_in_danger * unit_in_danger
+
+                action = ActionShoot(a, u, f'shoot from {a.pos} ({line})')
+
+                actions.put(priority, action)
+
         # Position
         k0_position = 30
         k_advantage = 40
         k_position_distance = 3  # on veut privilégier les plus près, mais pas non plus décourager ceux de l'arrière...
+        hp_min = 5
 
         advantage = len(self.owned_units) > len(self.opponent_units) + len(self.neutrals)
 
         for a in self.allied_cultists:
 
+            if a.hp < hp_min:
+                continue
+
             if self.threat[a.pos]:
                 # l'unité est déjà dans une zone à risque
                 # TODO: envisager un retrait
@@ -472,22 +516,22 @@ class Grid(BaseClass):
                     lost_causes[target] = (dist - threaten_neutrals_from_leader.get(target, 0))  # la distance retenue est la différence entre la distance entre la cible
                                                                                  # et le leader ennemi, et celle entre la cible et le leader allié
 
-            # TODO: faire un algo pour identifier les neutres que le leader ennemi pourrait atteindre dans les prochains rounds, et avant notre leader
             for a in self.allied_cultists:
                 for pos, dist in lost_causes.items():
-                    if not pos in self.index:
+                    if pos not in self.index:
                         log(f"<!> {pos} is not in the index!!")
                         continue
 
                     u = self.index[pos]
 
-                    shooting_distance = self.shooting_distance(a.pos, u.pos)
+                    line_of_sight = self.line_of_sight(a.pos, u.pos)
+                    shooting_distance = len(line_of_sight)
                     if not shooting_distance:
                         continue
 
                     if shooting_distance <= u.SHOOTING_RANGE:
                         # la cible est à portée
-                        action = ActionShoot(a, u, f"peace keeping, shoot at {u.id}")
+                        action = ActionShoot(a, u, f"peace keeping, shoot from {a.pos} ({line_of_sight})")
 
                         priority = k_shoot_dangerous_neutral_distance * shooting_distance
                         priority += k_shoot_dangerous_neutral_threat * dist
@@ -550,7 +594,9 @@ class Grid(BaseClass):
         x0, y0 = start
         x1, y1 = target
 
-        if y0 > y1:
+        reversed = y0 > y1
+
+        if reversed:
             # on fait toujours de bas en haut, du coup on inverse au besoin
             x0, y0, x1, y1 = x1, y1, x0, y0
 
@@ -564,13 +610,8 @@ class Grid(BaseClass):
         x, y = x0, y0
 
         while 1:
-            line.append((x, y))
-
-            if x == x1 and y == y1:
-                break
-
             e2 = 2 * err
-            if e2 > (-1 * dy):
+            if e2 > -1 * dy:
                 err -= dy
                 x += sx
 
@@ -578,6 +619,16 @@ class Grid(BaseClass):
                 err += dx
                 y += sy
 
+            if x == x1 and y == y1:
+                break
+
+            line.append((x, y))
+
+        if reversed:
+            line = line[::-1]
+
+        line = [start] + line + [target]
+
         return line
 
     def line_of_sight(self, from_, to_):