olinox 6 лет назад
Родитель
Сommit
10b5ca951e
2 измененных файлов с 320 добавлено и 101 удалено
  1. 179 39
      i_n_f/script.py
  2. 141 62
      i_n_f/test_pivot.py

+ 179 - 39
i_n_f/script.py

@@ -6,16 +6,18 @@ import heapq
 import sys
 import time
 
+# for the future me:
+# * find a way to cut the ennemy territory, by sparing for 4 or 5 units and 
+# going straigh trough his territory, todo recto!
+
+
+
 # TODO
-# * priorize attack on neighbors of pivots (allies'pivots to colonize and ennemies to threat)
-# * !!! moves: check before if unit is not on a zone which needs to be protected
-# x optimize moves, especially at start! (by introducing dispersion?)
+
 # * take units and towers in account when computing the threat
-# x priorize colonization of cells near HQ
 # ? when priority needs a lvl3 unit, find a way to spare
 # * resurrect the strategic value: number of cells depending of the cell
 # * consider defending also cells not owned
-# x limit the training of level 3 when it's really needed
 # * make a first turn of moves for units deep inside the territory, to avoid unecessary training and computing
 
 debug = True
@@ -133,7 +135,11 @@ class Position(BasePosition):
         self.covers = sum([grid[n].owned for n in self.cell.neighbors])
         
         # eval pivot
-        self.pivot = sum([1 + grid[p].get_unit_level() for p in self.cell.pivot_for])
+        self.pivot = self.cell.pivot_value
+        self.next_to_pivot = any(grid[n].pivot_value for n in self.cell.neighbors)
+        
+        # cell is not easily reachable
+        self.protected = len([n for n in grid.neighbors(*self.cell.pos, True) if not grid[n].movable]) == 6
 
         # distance to the ennemy HQ
         self.dist_to_goal = Grid.manhattan(self.cell.pos, opponent.hq.pos)
@@ -141,6 +147,9 @@ class Position(BasePosition):
         # distance to the own HQ
         self.dist_to_hq = Grid.manhattan(self.cell.pos, player.hq.pos)
         
+        # distance to the center of the map
+        self.dist_to_center = Grid.manhattan(self.cell.pos, (6,6))
+        
         # priorize adjacent cells
         self.union = len([n for n in self.cell.neighbors if grid[n].active_owned])
         
@@ -179,43 +188,75 @@ class Position(BasePosition):
         
         
 class Defend(Position):
-    def __init__(self, cell, emergency = False):
+    def __init__(self, cell):
         super().__init__(cell)
-        self.emergency = emergency
     
     def __repr__(self):
-        detail = [self.threat, self.covers, self.pivot, self.emergency, 
+        detail = [self.threat, self.covers, self.pivot, 
                   self.dist_to_hq, self.cell.danger, self.cell.critical, 
                   self.under_tower, self.overlaps_tower]
         return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
     
     def eval(self):
         self.pre_eval()
-        self.interest = (200 if not self.emergency else 50) \
-                        - 2 * self.threat \
+        self.interest = 20 \
                         - 2 * self.covers \
-                        - 8 * self.pivot \
+                        - (8 * self.pivot if not self.protected else 4 * self.pivot) \
                         - 80 * self.cell.critical \
                         - 25 * self.cell.danger \
                         + 25 * self.cell.under_tower \
-                        + 10 * self.overlaps_tower
+                        + 10 * self.overlaps_tower \
+                        + self.dist_to_hq
+        
+class Secure(Position):
+    def __init__(self, cell, emergency = False):
+        super().__init__(cell)
+        self.emergency = emergency
+    
+    def __repr__(self):
+        detail = [self.threat, self.covers, self.pivot,  
+                  self.under_tower, self.overlaps_tower]
+        return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
+    
+    def eval(self):
+        self.pre_eval()
+        self.interest = 30 \
+                        - 2 * self.threat \
+                        - 2 * self.covers \
+                        - (8 * self.pivot if not self.protected else 2 * self.pivot) \
+                        + 25 * self.cell.under_tower \
+                        + 10 * self.overlaps_tower \
+                        + self.dist_to_center
         
 class Attack(Position):
     def eval(self):
         self.pre_eval()
         self.interest = 15 * self.possession \
-                        - 5 * self.pivot \
+                        - 7 * self.pivot \
+                        - 5 * self.next_to_pivot \
                         - 2 * self.union \
-                        + 3 * self.concentration \
-                        + 2 * self.deadends \
-                        + 4 * self.depth \
-                        + self.dist_to_goal \
-                        - 2 * max([0, 11 - self.dist_to_hq]) \
+                        + 2 * self.dist_to_goal \
                         - 30 * self.tower \
                         - 15 * self.mine \
                         - 100 * self.hq \
+                        - 50 * self.cell.critical \
+                        - 20 * self.cell.danger \
                         + 10 * (self.min_level - 1)
         
+class Colonize(Position):
+    def eval(self):
+        self.pre_eval()
+        self.interest = 15 * self.possession \
+                        - 5 * self.next_to_pivot \
+                        - 2 * self.union \
+                        + 4 * self.concentration \
+                        + 2 * self.deadends \
+                        + 4 * self.depth \
+                        + self.dist_to_goal \
+                        - max([0, 3 - self.dist_to_hq]) \
+                        + self.dist_to_center
+                        
+        
 class MinePosition(BasePosition):
     def __init__(self, target):
         super().__init__(target)
@@ -289,6 +330,7 @@ class Player(Base):
         
         self.spent = 0
         self.new_charges = 0
+        self.to_spare = 0
 
     def update(self, gold, income, units, buildings):
         self.gold = gold
@@ -336,6 +378,7 @@ class Cell(Base):
         self.under_tower = False
         self.depth = 0
         self.pivot_for = []
+        self.pivot_value = 0
         self.critical = False
         self.danger = False
         self.threat = 0
@@ -357,6 +400,7 @@ class Cell(Base):
         self.under_tower = False
         self.depth = 0
         self.pivot_for = []
+        self.pivot_value = 0
         self.threat = 0
         self.threat_by = None
         self.critical = False
@@ -407,7 +451,7 @@ class Cell(Base):
     def is_active_owner(self, player_id):
         if player_id == ME:
             return self.active_owned
-        elif player_id == ME:
+        elif player_id == OPPONENT:
             return self.active_opponent
         else:
             return False
@@ -488,7 +532,9 @@ class Grid(Base):
                                           buildings_ix.get((x, y), None))
 
         log(" * update pivots")
-        self.update_pivots()
+#         self.update_pivots()
+        self.update_propagation(ME)
+        self.update_propagation(OPPONENT)
         
         log(" * update threats")
         self.update_threats()
@@ -564,8 +610,84 @@ class Grid(Base):
             next_buffer = []
     
     def _active_owned(self, pos, player_id):
-        return self.cells[pos].is_active_owner(player_id)
-    
+        try:
+            return self.cells[pos].is_active_owner(player_id)
+        except KeyError:
+            return False
+        
+    def update_propagation(self, player_id):
+        start = self.get_hq(player_id).pos
+        lvl = 0
+        propagation = {start: (lvl, [])}
+        
+        pivots_cells = []
+        for x, y in self.cells:
+            if (x, y) != start and self._active_owned((x, y), player_id):
+                
+                around = [(x, y - 1), (x + 1, y - 1), (x + 1, y), (x + 1, y + 1),
+                             (x, y + 1), (x - 1, y + 1), (x - 1, y), (x - 1, y - 1)]
+                owned = [self._active_owned(p, player_id) for p in around]
+                changes = [x for x in zip(owned, owned[1:]) if x == (True, False)]
+                
+                if len(changes) > 1:
+                    pivots_cells.append((x, y))
+        pivots = {p: [] for p in pivots_cells}
+        
+        buffer = [start]
+        while buffer:
+            new_buffer = []
+            lvl += 1
+            for pos in buffer:
+                for n in self.neighbors(*pos):
+                    if self._active_owned(n, player_id):
+                        if not n in propagation:
+                            propagation[n] = (lvl, [pos])
+                            new_buffer.append(n)
+                        else:
+                            # already visited
+                            if propagation[pos][1] != [n] and propagation[n][0] >= propagation[pos][0]:
+                                propagation[n][1].append(pos)
+            
+            buffer = new_buffer
+        
+        self.propagation = propagation
+
+        children = {}
+        for p, data in self.propagation.items():
+            _, parents = data
+            for parent in parents:
+                if not parent in children:
+                    children[parent] = []
+                children[parent].append(p)
+        
+        for pivot in pivots:
+            buffer = set(children.get(pivot, []))
+            
+            while buffer:
+                new_buffer = set()
+                 
+                for child in buffer:
+                    new_buffer |= set(children.get(child, []))
+               
+                pivots[pivot] += list(buffer)
+                buffer = new_buffer
+            
+        # cleaning 'false children'
+        for pivot, children in pivots.items():
+            invalid = []
+            for child in children:
+                parents = self.propagation[child][1]
+                if any((p != pivot and p not in children) or p in invalid for p in parents):
+                    invalid.append(child)
+            for p in invalid:
+                children.remove(p)
+                
+        log("player {}, pivots: {}".format(player_id, {k: len(v) for k, v in pivots.items()}))
+        
+        for pivot, pivot_for in pivots.items():
+            self.cells[pivot].pivot_for = pivot_for
+            self.cells[pivot].pivot_value = sum([1 + grid[p].get_unit_level() for p in pivot_for])
+            
     def update_pivot_for(self, player_id):
         start = self.get_hq(player_id).pos
         start_node = Node(start)
@@ -653,23 +775,20 @@ class Grid(Base):
         if not hqcell.threat > 0:
             return
         closest_threat = hqcell.threat_by
-#         log(f"* {closest_threat.pos} threats HQ")
+        log(f"* {closest_threat.pos} threats HQ")
         
         path  = self.path(closest_threat.pos, player.hq.pos)
         if path:
-#             log(f"* Path to HQ: {path}")
+            log(f"* Path to HQ: {path}")
             extended_path = set()
             for p in path:
                 extended_path |= set(self.neighbors(*p))
             extended_path -= set(path)
             
             for p in path:
-                if self._active_owned(p, ME):
-                    self.cells[p].critical = True
+                self.cells[p].critical = True
             for p in extended_path:
-                if self._active_owned(p, ME):
-                    self.cells[p].danger = True
-    
+                self.cells[p].danger = True
     
     def path(self, start, target):
         nodes = Queue()
@@ -1036,9 +1155,10 @@ while True:
         log("<!> HQ is threaten!")
     
     todo = []
-    abandonned = []
+    played, abandonned = [], []
     acted = True
     defs, max_def = 0, 8
+    to_spare = 15 * (grid.threat_level // 5)
     
     while player.can_act():
         
@@ -1046,13 +1166,18 @@ while True:
             # only re-eval if an action occured
             q = InterestQueue()
             for cell in grid.influence_zone(ME):
-                if cell.movable and not cell in abandonned:
+                if cell.movable and not cell in abandonned and not cell in played:
                     if cell.owned and cell.threat > 0 and cell.pos != player.hq.pos:
                         if defs > max_def:
                             continue
-                        p = Defend(cell, grid.emergency)
-                    elif not cell.owned:
+                        if grid.emergency:
+                            p = Defend(cell)
+                        else:
+                            p = Secure(cell)
+                    elif cell.opponents:
                         p = Attack(cell)
+                    elif not cell.owned:
+                        p = Colonize(cell)
                     else:
                         continue
                     p.eval()
@@ -1065,13 +1190,24 @@ while True:
         
         objective = q.get()
             
-        if type(objective) is Defend:
-            defs += 1
+        if type(objective) is Secure:
+            
+            if (player.current_gold - to_spare) > Building.cost[Building.TOWER] \
+               and not objective.cell.mine_site \
+               and not objective.cell.building:
+                action = BuildTower(objective.cell)
+                defs += 1
+            else:
+                abandonned.append(objective.cell)
+                continue
+                
+        elif type(objective) is Defend:
             
             if player.current_gold > Building.cost[Building.TOWER] \
                and not objective.cell.mine_site \
                and not objective.cell.building:
                 action = BuildTower(objective.cell)
+                defs += 1
                 
             elif objective.cell.unit and objective.cell.unit.owned:
                 # already a unit here: stay on place
@@ -1080,6 +1216,7 @@ while True:
                 
             elif player.can_afford(1):
                 action = Train(1, objective.cell)
+                defs += 1
                 
             else:
                 near_unit = next((grid[n].unit for n in objective.cell.neighbors 
@@ -1090,16 +1227,17 @@ while True:
                                  None)
                 if near_unit and grid.can_move(objective.cell.pos, near_unit.level):
                     action = Move(near_unit, objective.cell)
+                    defs += 1
                 else:
                     abandonned.append(objective.cell)
                     continue
             
-        elif type(objective) is Attack:
+        elif type(objective) in (Colonize, Attack):
             
             near_units = [grid[n].unit for n in objective.cell.neighbors if grid[n].unit \
                                                                              and grid[n].unit.owned \
                                                                              and not grid[n].unit.has_moved \
-                                                                             and grid[n].unit.level >= objective.min_level]
+                                                                             and grid[n].unit.level == objective.min_level]
             
             if len(near_units) == 1:
                 near_unit = near_units[0]
@@ -1111,9 +1249,10 @@ while True:
             if near_unit and grid.can_move(objective.cell.pos, near_unit.level):
                 action = Move(near_unit, objective.cell)
             else:
-                if objective.min_level > player.max_affordable():
+                if objective.min_level > player.max_affordable() or player.gold < (to_spare + Unit.cost[objective.min_level]):
                     abandonned.append(objective.cell)
                     continue
+                
                 action = Train(objective.min_level, objective.cell)
         
         log(f"priority: {objective}")
@@ -1139,6 +1278,7 @@ while True:
         
         acted = True
         log(f"> Apply action {action}")
+        played.append(action.cell)
         grid.apply(action)
         todo.append(action)
         

+ 141 - 62
i_n_f/test_pivot.py

@@ -11,12 +11,27 @@ class Node():
         self.pos = pos
         self.path = path
 
+class PNode():
+    def __init__(self, pos, level = 0, parent=None):
+        self.pos = pos
+
 class Grid():
     dim = 12
     owned = 3
     def __init__(self):
         
         self.cells = [(x, y) for x in range(Grid.dim) for y in range(Grid.dim)]
+        self.hq = (0,0)
+        self.owned = []
+
+    def from_graph(self, graph):
+        for y, row in enumerate(graph):
+            row = list(row)
+            for x, c in enumerate(row):
+                if c != "-":
+                    self.owned.append((x, y))
+                if c == "H":
+                    self.hq = (x, y)
 
     def print_grid(self):
         grid = [["" for _ in range(Grid.dim)] for _ in range(Grid.dim)]
@@ -36,31 +51,93 @@ class Grid():
             neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
         return [(x, y) for x, y in neighs if 0 <= x < Grid.dim and 0 <= y < Grid.dim]
         
-    def oriented_neighbors(self, x, y, orient):
-        return [(x + 1, y), (x, y + 1)] if orient > 0 else [(x - 1, y), (x, y - 1)]
-    
-    def update_frontlines(self):
+    def update_frontlines(self, player_id):
         self.frontline = []
         
         for p in self.cells:
-            if self._active_owned(p, 0):
-                if any(not self._active_owned(n, 0) for n in self.neighbors(*p)):
+            if self._active_owned(p, player_id):
+                if any(not self._active_owned(n, player_id) for n in self.neighbors(*p)):
 #                     cell.update_threat()
                     self.frontline.append(p)
     
-    def _active_owned(self, pos, player_id):
-        return pos in [(11,11), (10,11), (9,11), (8,11), (7,11), (6,11), (5,11), (4,11),
-                       (11,10), (10,10), (9,10), (8,10), (7,10), (6,10), (5,10), (4,10),
-                       (11,9),  (10,9),  (9,9),  (8,9),  (7,9),  (6,9),  (5,9),
-                                                         (7,8),  (6,8),  (5,8),  (4,8),
-                                                         (7,7),                  (4,7)]
+    def _active_owned(self, pos, _):
+        return pos in self.owned
+
+
+    def update_propagation(self, player_id):
+        start = self.hq
+        lvl = 0
+        propagation = {start: (lvl, [])}
         
-    def __active_owned(self, pos, player_id):
-        return pos[0] < 12 and pos[1] < 12
+        pivots = []
+        for x, y in self.cells:
+            if (x, y) != start and self._active_owned((x, y), player_id):
+                
+                around = [(x, y - 1), (x + 1, y - 1), (x + 1, y), (x + 1, y + 1),
+                             (x, y + 1), (x - 1, y + 1), (x - 1, y), (x - 1, y - 1)]
+                owned = [self._active_owned(p, player_id) for p in around]
+                changes = [x for x in zip(owned, owned[1:]) if x == (True, False)]
+                
+                if len(changes) > 1:
+                    pivots.append((x, y))
+        self.pivots = {p: [] for p in pivots}
+        
+        buffer = [start]
+        while buffer:
+            new_buffer = []
+            lvl += 1
+            for pos in buffer:
+                for n in self.neighbors(*pos):
+                    if self._active_owned(n, player_id):
+                        if not n in propagation:
+                            propagation[n] = (lvl, [pos])
+                            new_buffer.append(n)
+                        else:
+                            # already visited
+                            if propagation[pos][1] != [n] and propagation[n][0] >= propagation[pos][0]:
+                                propagation[n][1].append(pos)
+            
+            buffer = new_buffer
+        
+        self.propagation = propagation
 
+        children = {}
+        for p, data in self.propagation.items():
+            _, parents = data
+            for parent in parents:
+                if not parent in children:
+                    children[parent] = []
+                children[parent].append(p)
+        print("*", children)
+        
+        for pivot in self.pivots:
+            buffer = set(children[pivot])
+            
+            while buffer:
+                new_buffer = set()
+                 
+                for child in buffer:
+                    new_buffer |= set(children.get(child, []))
+               
+                self.pivots[pivot] += list(buffer)
+                buffer = new_buffer
+            
+        # cleaning 'false children'
+        for pivot, children in self.pivots.items():
+            invalid = []
+            for child in children:
+                parents = self.propagation[child][1]
+                if any((p != pivot and p not in children) or p in invalid for p in parents):
+                    invalid.append(child)
+            for p in invalid:
+                children.remove(p)
+                
+        
+        
+        
     def update_pivot_for(self, player_id):
 #         start = self.get_hq(player_id).pos
-        start = (11,11)
+        start = self.hq
         start_node = Node(start)
         
         buffer = [start_node]
@@ -87,7 +164,7 @@ class Grid():
             if not node.pos in paths_to:
                 paths_to[node.pos] = []
             paths_to[node.pos].append(node.path)
-        print(paths_to)
+#         print(paths_to)
     
         pivots = {}
         
@@ -102,61 +179,63 @@ class Grid():
                         pivots[candidate] = []
                     pivots[candidate].append(p)
         
-        occurrences = Counter(sum(sum(paths_to.values(), []), []))
-
-        while ignored:
-            new_ignored = []
-            for p in ignored:
-                occured_neighbors = [occurrences[n] for n in self.neighbors(*p) if n in occurrences]
-                if not occured_neighbors:
-                    new_ignored.append(p)
-                    continue
-                occurrences[p] = 2 * sum(occured_neighbors) // len(occured_neighbors)
-            ignored = new_ignored
-        
-        print(occurrences)
+#         occurrences = Counter(sum(sum(paths_to.values(), []), []))
+# 
+#         while ignored:
+#             new_ignored = []
+#             for p in ignored:
+#                 occured_neighbors = [occurrences[n] for n in self.neighbors(*p) if n in occurrences]
+#                 if not occured_neighbors:
+#                     new_ignored.append(p)
+#                     continue
+#                 occurrences[p] = 2 * sum(occured_neighbors) // len(occured_neighbors)
+#             ignored = new_ignored
+#         
+#         print(occurrences)
 
         return pivots
 
+grid = Grid()
 
-    def __update_pivot_for(self, player_id):
-#         start = self.get_hq(player_id).pos
-        start = (11,11)
-        orient = 1
-        buffer = [(None, start)]
-        bounds = []
-        
-        while buffer:
-            new_buffer = []
-            for o, p in buffer:
-                for n in self.oriented_neighbors(*p, orient):
-                    if self._active_owned(n, player_id) and not (p, n) in bounds and n != o:
-                        bounds.append((p, n))
-                        new_buffer.append((p, n))
-            buffer = new_buffer
-        
-        print(bounds)
-        
-        parents_number = Counter()
-        for parent, child in bounds:
-            parents_number[child] += 1
-        print(parents_number)
-            
+graph = ["Hxxxx-------",
+         "xxxxxx------",
+         "xxx-xx------",
+         "-x--xx------",
+         "xx----------",
+         "xxxxxxxxx---",
+         "xxxxxxxxx---",
+         "xxxxxxxxx---",
+         "xxxxxxxxx---",
+         "xxxxxxxxx---",
+         "xxxxxx------",
+         "------------",
+         ]
+# graph = ["Hxxxx-------",
+#          "xxxxxx------",
+#          "xxx-xx------",
+#          "-x--xx------",
+#          "xx----------",
+#          "------------",
+#          "------------",
+#          "------------",
+#          "------------",
+#          "------------",
+#          "------------",
+#          "------------",
+#          ]
+
+grid.from_graph(graph)
 
-        pivots = set()
-        for parent, child in bounds:
-            if parent == start:
-                continue
-            if parents_number[child] == 1:
-                pivots |= {parent}
-                
-        return pivots
-    
-grid = Grid()
 Grid.owned = 5
 print(grid.print_grid())
 print()
 
+t0 = time.time()
+grid.update_propagation(0)
+print(grid.propagation)
+print(grid.pivots)
+print(time.time() - t0)
+ 
 t0 = time.time()
 a = grid.update_pivot_for(0)
 print(a)