olinox14 3 年之前
父节点
当前提交
1a5585dee1
共有 1 个文件被更改,包括 129 次插入134 次删除
  1. 129 134
      cultist_war/main.py

+ 129 - 134
cultist_war/main.py

@@ -222,6 +222,7 @@ class Grid(BaseClass):
     def __init__(self, width, height):
         self.width = width
         self.height = height
+        self.round = 1
 
         self.cells = []
         self.obstacles = []
@@ -229,10 +230,16 @@ class Grid(BaseClass):
 
         self.index = {}
         self.units = {}
-        self.round = 1
         self.threat = {}
-        self.control = {}
-        self.heat_map = {}
+        self.conversion_path = []
+
+        self.cult_leader = None
+        self.opponent_cult_leader = None
+        self.allied_cultists = []
+        self.owned_units = []
+        self.opponent_units = []
+        self.opponent_cultists = []
+        self.neutrals = []
 
     def pre_compute(self):
         self.cells = [(x, y) for x in range(self.width) for y in range(self.height)]
@@ -252,15 +259,42 @@ class Grid(BaseClass):
         unit.y = y
         unit.owner = owner
 
+    def update_index(self):
+        self.index = {}
+        self.cult_leader = None
+        self.opponent_cult_leader = None
+        self.allied_cultists = []
+        self.owned_units = []
+        self.opponent_units = []
+        self.opponent_cultists = []
+        self.neutrals = []
+
+        for unit in self.units.values():
+            self.index[(unit.x, unit.y)] = unit
+
+            if unit.owner == ME.id:
+                self.owned_units.append(unit)
+                if type(unit) is CultLeader:
+                    self.cult_leader = unit
+                else:
+                    self.allied_cultists.append(unit)
+            elif unit.owner == OPPONENT.id:
+                self.opponent_units.append(unit)
+                if type(unit) is CultLeader:
+                    self.opponent_cult_leader = unit
+                else:
+                    self.opponent_cultists.append(unit)
+            else:
+                self.neutrals.append(unit)
+
     def update_threat_map(self):
         self.threat = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
 
-        sources = self.opponent_cultists()
+        sources = self.opponent_cultists
 
         # On ajoute les neutres voisins du leader ennemi aux sources de menace possible
-        opponent_cult_leader = self.opponent_cult_leader()
-        if opponent_cult_leader:
-            for pos in self.neighbors(*opponent_cult_leader.pos):
+        if self.opponent_cult_leader:
+            for pos in self.neighbors(*self.opponent_cult_leader.pos):
                 if pos in self.index and self.index[pos].neutral:
                     sources.append(self.index[pos])
 
@@ -279,104 +313,62 @@ class Grid(BaseClass):
                 if threat > self.threat[(x, y)]:
                     self.threat[(x, y)] = threat
 
-    def update_control(self):
-        self.control = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
-
-        for u in self.allied_cultists():
-            shooting_zone = self.zone(u.pos, Unit.SHOOTING_RANGE)
-            for x, y in shooting_zone:
-                dist = shooting_zone[(x, y)]
-
-                if not self.line_of_sight(u.pos, (x, y)):
-                    continue
-
-                control = Unit.SHOOTING_RANGE + 1 - dist
-                if control > self.control[(x, y)]:
-                    self.control[(x, y)] = control
-
-    def update_heat_map(self):
-        lines = []
-        self.heat_map = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
-
-        cult_leader = self.cult_leader()
-
-        for o in self.opponent_cultists():
-            if cult_leader:
-                lines += self.line(o.pos, cult_leader.pos)
-
-            for n in self.neutrals():
-                lines += self.line(o.pos, n.pos)
+    def compute_conversion_path(self):
+        conversion_path = []
 
-        for pos in lines:
-            self.heat_map[pos] += 1
+        if self.cult_leader and self.neutrals:
+            conversion_path = self.get_conversion_path(
+                self.cult_leader,
+                key=(lambda pos: pos in self.index and self.index[pos].neutral),
+                limit=min(4, len(self.neutrals))
+            )
+            log(conversion_path)
+        self.conversion_path = conversion_path
 
     def update(self):
-        self.index = {}
-        for unit in self.units.values():
-            self.index[(unit.x, unit.y)] = unit
-
+        log('update indexes')
+        self.update_index()
         self.update_threat_map()
-        self.update_control()
-        self.update_heat_map()
+        self.compute_conversion_path()
 
-    def cult_leader(self):
-        return next((u for u in self.units.values() if type(u) is CultLeader and u.owned), None)
+        # log(self.obstacles + [u.pos for u in self.allied_cultists])
+        # log([n.pos for n in self.neutrals])
 
-    def allied_cultists(self):
-        return [u for u in self.units.values() if type(u) is not CultLeader and u.owned]
+    def build_actions(self):
+        actions = Queue()
 
-    def owned_units(self):
-        return [u for u in self.units.values() if u.owned]
+        # Leader take cover
+        k0_protect_cult_leader = 30
+        k_protect_threat_level = -5
+        k_cover_threat = 10
+        k_cover_interest = -10
 
-    def opponent_units(self):
-        return [u for u in self.units.values() if u.opponent]
+        if self.cult_leader:
+            current_threat = self.threat[self.cult_leader.pos]
 
-    def opponent_cult_leader(self):
-        return next((u for u in self.units.values() if type(u) is CultLeader and not u.owned), None)
+            if current_threat:
+                covers = [n for n in self.neighbors(*self.cult_leader.pos) if self.can_move_on(self.cult_leader, n)]
 
-    def opponent_cultists(self):
-        return [u for u in self.units.values() if type(u) is not CultLeader and u.opponent]
+                for pos in covers:
+                    action = ActionMove(self.cult_leader, pos, f'take cover (t: {current_threat})')
 
-    def neutrals(self):
-        return [u for u in self.units.values() if u.neutral]
+                    interest = self.conversion_path and self.conversion_path.steps and pos in [s.pos for s in
+                                                                                               self.conversion_path.steps]
 
-    def list_actions(self):
-        actions = Queue()
+                    priority = k0_protect_cult_leader
+                    priority += k_protect_threat_level * current_threat
+                    priority += k_cover_threat * self.threat[pos]
+                    priority += k_cover_interest * interest
+
+                    actions.put(priority, action)
 
+        # Convert
         k_convert_number = -10
         k_convert_distance = 10
         k_convert_danger = 20
-        k_shoot_opponent_cultist = 15
-        k_shoot_opponent_cult_leader = 10
-        k0_protect_cult_leader = 30
-        k_protect_threat_level = -5
-        k_cover_threat = 10
-        k_cover_interest = -10
-        k0_position = 50
-        k_position_distance = 10
-        k_position_heat = -2
-        k_position_danger = 10
-        k_position_advantage = 10
-
-        cult_leader = self.cult_leader()
-        opponent_cult_leader = self.opponent_cult_leader()
-
-        # log(self.obstacles + [u.pos for u in self.allied_cultists()])
-        # log([n.pos for n in self.neutrals()])
-
-        # compute conversion paths
-        conversion_path = []
-        if cult_leader and self.neutrals():
-            conversion_path = self.get_conversion_path(
-                cult_leader,
-                key=(lambda pos: pos in self.index and self.index[pos].neutral),
-                limit=min(4, len(self.neutrals()))
-            )
-            log(conversion_path)
 
-        # Conversion des neutres
-        if cult_leader and conversion_path:
-            path = conversion_path.next_candidate()
+        if self.cult_leader and self.conversion_path:
+            path = self.conversion_path.next_candidate()
             if path:
                 targets = [self.index[c] for c in path[-1].candidates]
 
@@ -386,69 +378,69 @@ class Grid(BaseClass):
                 priority += k_convert_danger * sum([self.threat[s.pos] for s in path])
 
                 if len(path) == 1:
-                    action = ActionConvert(cult_leader, targets[0])
+                    action = ActionConvert(self.cult_leader, targets[0])
                 else:
-                    action = ActionMove(cult_leader, path[1].pos, f'go convert {",".join([str(t.id) for t in targets])}')
+                    action = ActionMove(self.cult_leader, path[1].pos,
+                                        f'go convert {",".join([str(t.id) for t in targets])}')
                 actions.put(priority, action)
 
-        # Tire sur une unités ennemies
-        targets = self.opponent_cultists()
-        if opponent_cult_leader:
-            targets.append(opponent_cult_leader)
-
-        has_advantage = sum([t.hp for t in targets]) < sum([u.hp for u in self.owned_units()])
+        # Shoot
+        k_shoot_opponent_cultist = 8
+        k_shoot_opponent_cult_leader = 4
 
-        for a in self.allied_cultists():
-            for u in targets:
+        for a in self.allied_cultists:
+            for u in self.opponent_units:
                 shooting_distance = self.shooting_distance(a.pos, u.pos)
-                if a.id == 4 and u.id == 1:
-                    log(self.line_of_sight(a.pos, u.pos))
-                if shooting_distance and shooting_distance < u.SHOOTING_RANGE:
+                if not shooting_distance:
+                    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
 
                     actions.put(priority, action)
+                else:
+                    # la cible est hors de portée, mais elle est plus faible et sans soutien
+                    # TODO: implémenter
+                    pass
 
         # Position
-        for a in self.allied_cultists():
-            # on garde les trois points les plus chauds
-            hot_spots = sorted(self.heat_map.items(), key=lambda p: p[1], reverse=True)[:3]
-
-            results = self.discover(a, key=lambda x: x in [s[0] for s in hot_spots], limit=3)
-
-            for path, target_pos in results:
-                if not path:
-                    break
-
-                heat = self.heat_map[target_pos]
-
-                priority = k0_position
-                priority += k_position_heat * heat
-                priority += k_position_distance * len(path)
-                priority += k_position_danger * sum(self.threat[p] for p in path)
+        k0_position = 50
+        k_advantage = 40
+        k_position_distance = 10
+        k_position_heat = -2
+        k_position_danger = 10
 
-                action = ActionMove(a, path[0], f'go for hotspot {target_pos} by {path}')
+        advantage = len(self.owned_units) > len(self.opponent_units) + len(self.neutrals)
 
-                actions.put(priority, action)
+        for a in self.allied_cultists:
 
-        # Mise en sécurité du chef
-        if cult_leader:
-            current_threat = self.threat[cult_leader.pos]
-            if current_threat:
-                covers = [n for n in self.neighbors(*cult_leader.pos) if self.can_move_on(cult_leader, n)]
+            if self.threat[a.pos]:
+                # l'unité est déjà dans une zone à risque
+                # TODO: envisager un retrait
+                continue
 
-                for pos in covers:
-                    action = ActionMove(cult_leader, pos, f'take cover (t: {current_threat})')
+            else:
+                # l'unité semble en sécurité, go to front-line
+                nearest_frontline = self.discover(a, key=lambda x: self.threat[x] == 1, limit=1)
+                if not nearest_frontline:
+                    log(f"<!> {u.id} can not join nearest frontline")
+                    continue
 
-                    interest = conversion_path and conversion_path.steps and pos in [s.pos for s in conversion_path.steps]
+                path, target = nearest_frontline[0]
+                if not path or len(path) == 1:
+                    # already in place
+                    continue
 
-                    priority = k0_protect_cult_leader
-                    priority += k_protect_threat_level * current_threat
-                    priority += k_cover_threat * self.threat[pos]
-                    priority += k_cover_interest * interest
+                    priority = k0_position
+                    priority += k_position_distance * len(path)
+                    priority += k_position_danger * sum(self.threat[p] for p in path)
+                    priority += k_advantage * advantage
 
+                    action = ActionMove(a, path[0], f'go to frontline {target} by {path}')
                     actions.put(priority, action)
 
         return actions
@@ -680,7 +672,10 @@ class Grid(BaseClass):
         try:
             best_node = winners.get()
         except IndexError:
-            best_node = nodes.get()
+            if nodes:
+                best_node = nodes.get()
+            else:
+                best_node = origin
         return ConversionPath.make_from_discovery_node(best_node)
 
     def _repr_cell(self, pos):
@@ -729,7 +724,7 @@ while 1:
     log(f"start round {GRID.round}")
     GRID.update()
 
-    actions = GRID.list_actions()
+    actions = GRID.build_actions()
 
     print("\n" + GRID.graph(), file=sys.stderr)
 
@@ -745,4 +740,4 @@ while 1:
     log(f"exec : {action}")
 
     action.exec()
-    GRID.round += 1
+    GRID.round += 1