瀏覽代碼

review of the interest algo of the actions

olinox 6 年之前
父節點
當前提交
024f2c7471
共有 1 個文件被更改,包括 346 次插入97 次删除
  1. 346 97
      i_n_f/script.py

+ 346 - 97
i_n_f/script.py

@@ -7,13 +7,13 @@ import sys
 import time
 
 # TODO
-# * always priorize the attack if level allows it
-# * take levels in account
-# * priorize the attack of the ennemy hq
-# x build lvl2 and 3 units
-# * priorize move on mines
-# * priorize move on 'pivot-cells' which would cause desactivation of others
-# * don't let ennemies inside the defense (=> nearer of the QG than other units)
+# x update the grid state after each action
+# * improve the conditions for training
+# * estimate the threat level of ennemies (proximity to HQ or to a pivot, unprotected cells around or inferior units)
+# * use a propagation loop to estimate the weight of a pivot (both opponents and owned)
+# * modelize fronts
+# * moving part: order units action order by proximity to an ennemy unit / building
+# * add the 'lasso' technique
 
 debug = True
 t0 = time.time()
@@ -82,46 +82,59 @@ class Action(Base):
 class Move(Action):
     def __init__(self, target, unit):
         self.unit = unit
+        
+        self.possession = 0
+        self.pivot = False
+        self.prey_level = 0
+        self.union = 0
+        self.depth = 0
+        self.hq = False
+        self.tower = False
+        self.mine = False
+        self.dist_to_goal = 0
+        
         super().__init__(target, unit)
         
     def eval(self):
         # the lower the better
         
         self.interest = 0
-        if self.target.owned and self.target.active:
-            self.interest += 15 # already owned and active
-            
-        elif self.target.opponents and self.target.active:
-            self.interest -= 15 # owned by opponents and active
+        
+        if self.target.active_owned:
+            self.possession = 1
+        elif self.target.active_opponent:
+            self.possession = -1
             
-        # non-passable cells around
-        self.interest += 3 * len([n for n in self.target.neighbors if not grid[n].movable])
+        self.pivot = self.target.pivot
+
+        self.dist_to_goal = Grid.manhattan(self.target.pos, opponent.hq.pos)
         
         # an ennemy here
         if self.target.unit and self.target.unit.opponents:
             if self.target.unit.level < self.unit.level or self.unit.level == 3:
-                self.interest -= 30
-        
-        # an ennemy nearby
-        for n in self.target.neighbors:
-            if grid[n].unit and grid[n].unit.opponents and grid[n].unit.level >= self.unit.level:
-                self.interest += 15
+                self.prey_level = self.target.unit.level
         
         # priorize adjacent cells 
-        self.interest -= (2 * len([n for n in self.target.neighbors if grid[n].owned]))
+        self.union = len([n for n in self.target.neighbors if grid[n].active_owned])
         
-        # include 'heatmap'
-        self.interest += 3 * self.target.heat
+        # include 'depthmap'
+        self.depth = self.target.depth
         
         # priorize mines or HQ
-        if self.target.building:
-            if self.target.building.type_ == Building.HQ:
-                self.interest -= 50
-            if self.target.building.type_ == Building.MINE:
-                self.interest -= 15
-            if self.target.building.type_ == Building.TOWER and self.unit.level == 3:
-                self.interest -= 15
-            
+        if self.target.building and self.target.building.opponents:
+            self.hq = self.target.building.type_ == Building.HQ
+            self.tower = self.target.building.type_ == Building.TOWER and self.unit.level == 3
+            self.mine = self.target.building.type_ == Building.MINE
+        
+        self.interest = 15 * self.possession - 10 * self.pivot - 10 * self.prey_level \
+                        - 2 * self.union + 3 * self.depth + self.dist_to_goal \
+                        - 30 * self.tower - 15 * self.mine \
+                        - 100 * self.hq
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} ({self.x}, {self.y}), {self.unit.id_}: {self.interest} (" \
+                f"{self.possession} / {self.pivot} / {self.prey_level} / {self.union} / {self.dist_to_goal}" \
+                f" / {self.depth} / {self.tower} / {self.mine} / {self.hq})>"
         
     def resolution(self):
         return f"MOVE {self.unit.id_} {self.target.x} {self.target.y}"
@@ -129,33 +142,55 @@ class Move(Action):
 class Train(Move):
     def __init__(self, target, level=1):
         self.level = level
+    
+        self.possession = 0
+        self.pivot = False
+        self.prey_level = 0
+        self.union = 0
+        self.depth = 0
+        self.hq = False
+        self.tower = False
+        self.mine = False
+        self.dist_to_goal = 0
+        
         super().__init__(target, level)
+
     
     def eval(self):
         # the lower the better
         self.interest = 0
-        if self.target.owned and self.target.active:
-            self.interest += 15 # already owned and active
-            
-        elif self.target.opponents and self.target.active:
-            self.interest -= 15 # owned by opponents and active
+        
+        if self.target.active_owned:
+            self.possession = 1
+        elif self.target.active_opponent:
+            self.possession = -1
             
-        # non-passable cells around
-        self.interest += 2 * len([n for n in self.target.neighbors if not grid[n].movable])
+        self.pivot = self.target.pivot
+
+        self.dist_to_goal = Grid.manhattan(self.target.pos, opponent.hq.pos)
+
+        # an ennemy here
+        if self.target.unit and self.target.unit.opponents:
+            if self.target.unit.level < self.level or self.level == 3:
+                self.prey_level = self.target.unit.level
         
         # priorize adjacent cells 
-        self.interest -= (2 * len([n for n in self.target.neighbors if grid[n].owned]))
-        
-        # include 'heatmap'
-        self.interest += 3 * self.target.heat
+        self.union = len([n for n in self.target.neighbors if grid[n].active_owned])
         
-        # needs reinforcement?
-        for n in self.target.neighbors:
-            if grid[n].unit and grid[n].unit.owned:
-                self.interest += 5
-            if grid[n].unit and grid[n].unit.opponents:
-                self.interest -= 5
+        # include 'depthmap'
+        self.depth = self.target.depth
+
+        # priorize mines or HQ
+        if self.target.building and self.target.building.opponents:
+            self.hq = self.target.building.type_ == Building.HQ
+            self.tower = self.target.building.type_ == Building.TOWER and self.level == 3
+            self.mine = self.target.building.type_ == Building.MINE
         
+        self.interest = 15 * self.possession - 10 * self.pivot - 10 * self.prey_level \
+                        - 2 * self.union + 3 * self.depth + self.dist_to_goal \
+                        - 30 * self.tower - 15 * self.mine \
+                        - 100 * self.hq
+
     def resolution(self):
         return f"TRAIN {self.level} {self.target.x} {self.target.y}"
         
@@ -167,7 +202,13 @@ class Build(Action):
     def eval(self):
         # the lower the better
         self.interest = 0
-        self.interest -= self.target.heat
+        
+        if self.type_ == Building.MINE:
+            self.interest -= self.target.depth
+            
+        elif self.type_ == Building.TOWER:
+            if self.target.pivot:
+                self.interest -= 20
         
     def resolution(self):
         str_type = {1: "MINE", 2: "TOWER"}[self.type_]
@@ -204,6 +245,9 @@ class Building(BaseOwnedLoc):
     MINE = 1
     TOWER = 2
     
+    cost = {0: 0, 1: 20, 2: 15}
+    maintenance = {0: 0, 1: 0, 2: 0}
+    
     def __init__(self, owner, type_, x, y):
         super().__init__(x, y, owner)
         self.type_ = type_
@@ -213,7 +257,7 @@ class Building(BaseOwnedLoc):
         return self.type_ == Building.HQ
 
 class Unit(BaseOwnedLoc):
-    training_cost = {1: 10, 2: 20, 3: 30}
+    cost = {1: 10, 2: 20, 3: 30}
     maintenance = {1: 1, 2: 4, 3: 20}
     def __init__(self, owner, id_, level, x, y):
         super().__init__(x, y, owner)
@@ -246,7 +290,16 @@ class Cell(Base):
         self.building = None
         self.mine_site = None
         
-        self.heat = 0
+        self.under_tower = False
+        self.depth = 0
+        self.pivot = False
+        self.needs_unit = False
+        
+        # front cells
+        self.facing = []
+        self.support = []
+        self.threat = 0
+        self.in_front_of = []
         
     @property
     def pos(self):
@@ -261,8 +314,41 @@ class Cell(Base):
         self.unit = unit
         self.building = building
         
-        self.heat = 0
-        
+        self.under_tower = False
+        self.depth = 0
+        self.pivot = False
+        self.needs_unit = False
+    
+        self.facing = []
+        self.support = []
+        self.threat = 0
+        self.in_front_of = []
+    
+    def update_threat(self):
+        
+        self.threat = 0
+        
+        for n in self.neighbors:
+            ncell = grid[n]
+            if ncell.active_owned:
+                self.support.append(ncell)
+            else:
+                ncell.in_front_of.append(self)
+                self.facing.append(ncell)
+                if ncell.opponents:
+                    self.threat -= 1
+        
+        if self.unit:
+            self.threat -= 2 * self.unit.level
+        
+        for cell in self.support:
+            if cell.unit:
+                self.threat -= cell.unit.level
+                
+        for cell in self.facing:
+            if cell.unit:
+                self.threat += 2 * cell.unit.level
+    
     @property
     def movable(self):
         return self._content != "#"
@@ -275,6 +361,10 @@ class Cell(Base):
     def opponents(self):
         return self._content.lower() == "x"
     
+    @property
+    def owner(self):
+        return ME if self.owned else OPPONENT
+    
     @property
     def headquarter(self):
         return self.pos in Grid.hqs
@@ -287,7 +377,26 @@ class Cell(Base):
     def active(self):
         return self._content.isupper()
     
+    @property
+    def active_owned(self):
+        return self._content == "O"
+    
+    @property
+    def active_opponent(self):
+        return self._content == "X"
+    
+    def owned_unit(self):
+        if self.unit and self.unit.owned:
+            return self.unit
     
+    def owned_building(self):
+        if self.building and self.building.owned:
+            return self.building
+    
+    def take_possession(self):
+        self._content = "O"
+    
+
 class Grid(Base):
     dim = 12
     hqs = [(0,0), (11,11)]
@@ -298,11 +407,10 @@ class Grid(Base):
         for pos, cell in self.cells.items():
             cell.neighbors = [p for p in self.neighbors(*pos) if p in self.cells]
             
-        self.units = {}
-        self.buildings = {}
+        self.units = []
+        self.buildings = []
         for m in mines_sites:
             self.cells[(m.x, m.y)].mine_site = m
-        self.reservation = []
         
     def print_grid(self):
         return "\n".join(["".join([c for c in row]) for row in self.grid])
@@ -319,17 +427,22 @@ class Grid(Base):
         return self.cells[key]
 
     def update(self, grid, buildings, units):
-        self.buildings = {(b.x, b.y): b for b in buildings}
-        self.units = {(u.x, u.y): u for u in units}
+        buildings_ix = {(b.x, b.y): b for b in buildings}
+        units_ix= {(u.x, u.y): u for u in units}
+        
+        self.buildings = list(buildings)
+        self.units = list(units)
         
         for y, row in enumerate(grid):
             for x, c in enumerate(row):
                 self.cells[(x, y)].update(c, 
-                                          self.units.get((x, y), None), 
-                                          self.buildings.get((x, y), None))
+                                          units_ix.get((x, y), None), 
+                                          buildings_ix.get((x, y), None))
 
-        self.reservation = []
-        self.update_heat_map()
+        self.update_tower_areas()
+        self.update_frontlines()
+        self.update_depth_map()
+        self.update_pivots()
 
     @staticmethod
     def manhattan(from_, to_):
@@ -346,55 +459,100 @@ class Grid(Base):
     def get_hq(self, player):
         return next((b for b in self.buildings if b.owner == player and b.hq))
     
-    def update_heat_map(self):
+    def update_tower_areas(self):
+        for b in self.buildings:
+            if b.type_ == Building.TOWER:
+                self.cells[b.pos].under_tower = True
+                for n in self.cells[b.pos].neighbors:
+                    self.cells[n].under_tower = True
+    
+    def update_frontlines(self):
+        # update the current frontlines
+        self.frontline = []
+        self.frontex = []
         
-        frontier = []
-        for p, cell in self.cells.items():
-            if not p in frontier and cell.owned and cell.active \
-               and any(self.cells[c].movable and (not self.cells[c].owned or not self.cells[c].active) for c in cell.neighbors):
-                cell.heat = 1
-                frontier.append(p)
+        for cell in self.cells.values():
+            if cell.active_owned:
+                if any(self.cells[c].movable and not self.cells[c].active_owned
+                       for c in cell.neighbors):
+                    cell.update_threat()
+                    self.frontline.append(cell)
+
         
-        buffer = frontier
+
+
+    def update_depth_map(self):
+        buffer = [c.pos for c in self.frontline]
+        for p in buffer:
+            self.cells[p].depth = 1
+            
         next_buffer = []
         while buffer:
             for p in buffer:
                 for n in self.cells[p].neighbors:
-                    if self.cells[n].owned and self.cells[n].active and not self.cells[n].heat:
-                        self.cells[n].heat = self.cells[p].heat + 1
+                    if self.cells[n].active_owned and not self.cells[n].depth:
+                        self.cells[n].depth = self.cells[p].depth + 1
                         next_buffer.append(n)
                     
             buffer = list(next_buffer)
             next_buffer = []
     
+    def update_pivot_for(self, player_id):
+#         for cell in self.cells.values():
+#             if cell.owner == player_id and \
+#                len([n for n in cell.neighbors if self.cells[n].owner == player_id]) == 2:
+#                 cell.pivot = True
+        pass
+    
+    def update_pivots(self):
+        self.update_pivot_for(ME)
+        self.update_pivot_for(OPPONENT)
+    
     def training_places(self):
         owned, neighbors = {p for p, c in self.cells.items() if c.owned}, set()
         for p in owned:
             neighbors |= set(self.neighbors(*p))
-        return (self.cells[p] for p in (owned | neighbors) if self.cells[p].movable and not self.cells[p].occupied and not p in self.reservation)
+        return (self.cells[p] for p in (owned | neighbors) if self.can_move(p))
     
     def get_next_training(self, max_level=3):
         q = InterestQueue()
         for cell in self.training_places():
-            q.put(Train(cell))
+            q.put(Train(cell, max_level))
         if not q:
             return None
+        
         action = q.get()
     
+        if max_level < 3: 
+            while action.target.under_tower:
+                try:
+                    action = q.get()
+                except IndexError:
+                    return None
         level = 1
         for ennemy in opponent.units:
-            if Grid.manhattan(action.target.pos, ennemy.pos) < 4:
+            if Grid.manhattan(action.target.pos, ennemy.pos) < 3:
                 level = min(ennemy.level + 1, max_level)
                 break
+                
         action.level = level
-        
         return action
     
+    def can_move(self, pos, level=1):
+        cell = self.cells[pos]
+        can_move = True
+        
+        can_move &= cell.movable
+        can_move &= not cell.owned_unit()
+        can_move &= not cell.owned_building()
+        if level != 3:
+            can_move &= (cell.unit is None or cell.unit.level < level)
+            can_move &= not cell.under_tower
+        return can_move
+    
     def moving_zone(self, unit):
         return (self.cells[p] for p in self.cells[unit.pos].neighbors 
-                if self.cells[p].movable and not p in self.reservation and
-                (not self.cells[p].building or self.cells[p].building.opponents) and
-                (not self.cells[p].unit or self.cells[p].unit.opponents))
+                if self.can_move(p, unit.level))
         
     def get_next_move(self, unit):
         q = InterestQueue()
@@ -402,11 +560,16 @@ class Grid(Base):
             q.put(Move(cell, unit))
         if not q:
             return None
-        return q.get()
+        log(q.items)
+        action = q.get()
+        if action.interest > Move(self.cells[unit.pos], unit).interest:
+            # move has lower interest than staying there
+            return None
+        return action
     
     def building_zone(self, type_):
         if type_ == Building.MINE:
-            return [cell for cell in self.cells.values() if cell.mine_site and cell.heat > 3]
+            return [cell for cell in self.cells.values() if cell.mine_site and cell.depth > 3]
         else:
             return []
 
@@ -418,6 +581,76 @@ class Grid(Base):
             return None
         return q.get()
 
+    def apply(self, action):
+        if type(action) is Move:
+            unit, new_cell = action.unit, action.target
+            old_cell = self.cells[unit.pos]
+            
+            if new_cell.unit:
+                if new_cell.unit.owned:
+                    log(f"ERROR: impossible {action}")
+                    return
+                if unit.level < 3 and new_cell.unit.level >= unit.level:
+                    log(f"ERROR: impossible {action}")
+                    return
+                # cell is occupied by an opponent's unit with an inferior level
+                opponent.units.remove(new_cell.unit)
+                self.units.remove(new_cell.unit)
+                new_cell.unit = None
+            
+            if new_cell.building and new_cell.building.type_ == Building.TOWER:
+                if unit.level < 3:
+                    log(f"ERROR: impossible {action}")
+                opponent.buildings.remove(new_cell.building)
+                self.buildings.remove(new_cell.building)
+                new_cell.building = None
+                
+            old_cell.unit = None
+            unit.x, unit.y = new_cell.pos
+            new_cell.unit = unit
+            
+            new_cell.take_possession()
+        
+        elif type(action) is Train:
+            level, new_cell = action.level, action.target
+            unit = Unit(ME, None, level, *new_cell.pos)
+            
+            if new_cell.unit:
+                if new_cell.unit.owned:
+                    log(f"ERROR: impossible {action}")
+                    return
+                if unit.level < 3 and new_cell.unit.level >= unit.level:
+                    log(f"ERROR: impossible {action}")
+                    return
+                # cell is occupied by an opponent's unit with an inferior level
+                opponent.units.remove(new_cell.unit)
+                self.units.remove(new_cell.unit)
+                new_cell.unit = None
+            
+            if new_cell.building and new_cell.building.type_ == Building.TOWER:
+                if unit.level < 3:
+                    log(f"ERROR: impossible {action}")
+                opponent.buildings.remove(new_cell.building)
+                self.buildings.remove(new_cell.building)
+                new_cell.building = None
+                
+            new_cell.unit = unit
+            new_cell.take_possession()
+        
+        elif type(action) is Build:
+            type_, new_cell = action.type_, action.target
+            building = Building(ME, type_, *new_cell.pos)
+            new_cell.building = building
+            
+            if building.type_ == Building.TOWER:
+                new_cell.under_tower = True
+                for n in new_cell.neighbors:
+                    self.cells[n].under_tower = True
+        
+        else: 
+            log(f"unknown action : {type(action)}")
+
+
 
 # ******** MAIN *************
 
@@ -427,7 +660,6 @@ if test:
     mines_input = []
 else:
     mines_input = [input() for _ in range(int(input()))]
-log(mines_input)
 
 mines_sites = [MineSite(*[int(j) for j in item.split()]) for item in mines_input]
 # log(f"* mines: {mines_sites}")
@@ -491,38 +723,55 @@ while True:
     commands = []
     
     # start
-    log("# Moving")
-    for unit in player.units:
-        action = grid.get_next_move(unit)
-        if action:
-            grid.reservation.append(action.target.pos)
-            commands.append(action.resolution())
+    
+    
+    has_move = []
+    for i in range(2):
+        log(f"# Moving (turn {i})")
+        for unit in player.units:
+            if unit in has_move:
+                continue
+            
+            action = grid.get_next_move(unit)
+            if action:
+                grid.apply(action)
+                has_move.append(unit)
+                commands.append(action.resolution())
     
     log("# Training")
     spent, charges = 0, 0
-    while (player.income - charges) > len(player.units) and (player.gold - spent) > 10:
-        max_level = next((i for i in range(3, 0, -1) if (player.gold - spent) > Unit.training_cost[i] and (player.income - charges) > Unit.maintenance[i]), 1)
+    
+    while player.income - charges > 0 and \
+          player.gold - spent > 10 and \
+          len(player.units) < len(grid.frontline):
+        
+        max_level = next((i for i in range(3, 0, -1) if (player.gold - spent) > Unit.cost[i] and (player.income - charges) > Unit.maintenance[i]), 1)
+        
         action = grid.get_next_training(max_level)
         if action:
-            grid.reservation.append(action.target.pos)
-            spent += Unit.training_cost[action.level]
+            grid.apply(action)
+            spent += Unit.cost[action.level]
             charges += Unit.maintenance[action.level]
             commands.append(action.resolution())
         else:
             break
     
     log("# Building")
-    mine_cost = 20 + 4 * len([b for b in player.buildings if b.type_ == Building.MINE])
+    mine_cost = Building.cost[Building.MINE] + 4 * len([b for b in player.buildings if b.type_ == Building.MINE])
     if (player.gold - spent) > mine_cost:
         action = grid.get_building_site(Building.MINE)
         if action:
             spent += mine_cost
+            grid.apply(action)
+            commands.append(action.resolution())
+            
+    if player.gold - spent > Building.cost[Building.TOWER]:
+        action = grid.get_building_site(Building.TOWER)
+        if action:
+            spent += Building.cost[Building.TOWER]
+            grid.apply(action)
             commands.append(action.resolution())
             
-#     if player.gold - spent > 15:
-#         action = grid.get_building_site(Building.TOWER)
-        
-        
     if not commands:
         log("nothing to do: wait")
         commands = ["WAIT"]