Browse Source

changelog 2019-04-17

olinox 6 years ago
parent
commit
eae99e66f0
1 changed files with 158 additions and 62 deletions
  1. 158 62
      carribean/script.py

+ 158 - 62
carribean/script.py

@@ -9,15 +9,10 @@ import time
 
 # TODO:
 # * find a way to change direction without slowing down if possible
-# * avoid getting blocked by a side-by-side with an ennemy
-# * Fix: do not target a barrel that an ennemy is going to take
-# * Try to interger extra steps into the path algo, instead of adding independents paths
-# Check if cannonballs with countdown 0 can be ignored in the plan_next_move algo ?
-# Fix: do not shoot a mine if we are close to it
-# Fix: if every move leads to collision, avoid those whop hit the center of the ship
+# * Try to integer extra steps into the path algo, instead of adding independents paths
+# * Increase the shooting rate in the second part of the game (no barrels left)
+# * get_shooting_spots: try to intercept ennemy
 
-# Notes:
-# * Fix: Do not target a barrel that is in the dead angle -> barrels are estimated from the prow
 
 debug = True
 
@@ -45,6 +40,11 @@ class Queue():
     def put(self, item, priority):
         heapq.heappush(self.items, (priority, item))
 
+    def fput(self, item, priority):
+        while priority in [p for p, _ in self.items]:
+            priority += 1
+        self.put(item, priority)
+
     def get(self):
         return heapq.heappop(self.items)[1]
 
@@ -99,20 +99,28 @@ class BaseObjective(Base):
     def __repr__(self):
         return f"<{self.__class__.__name__}: target={self.target.id};int={self.interest})>"
 
-    def eval(self, pos = None, d = None):
+    def _pre_eval(self, pos = None, d = None):
         self.distance = Grid.manhattan(pos, self.target.pos) if pos is not None else 0
         self.alignment = abs(Grid.diff_directions(Grid.direction_to(*pos, *self.target.pos), d)) if d is not None else 0
+        
+    def eval(self, pos = None, d = None):
+        self._pre_eval(pos, d)
         self._compute_interest()
         
     def _compute_interest(self):
         self.interest = 7 * self.distance + 3 * self.alignment
         
 class GetBarrel(BaseObjective):
+    
+    def _pre_eval(self, pos = None, d = None):
+        super()._pre_eval(pos, d)
+        self.ennemy_near = any(Grid.manhattan(e.next_pos, self.target.pos) < self.distance for e in grid.ennemy_ships)
+    
     def _compute_interest(self):
-        self.interest = 6 * self.distance + 9 * self.alignment + 3 * self.target.dispersal + self.target.mine_threat * 2 + 12 * self.target.ennemy_near
+        self.interest = 6 * self.distance + 9 * self.alignment + 3 * self.target.dispersal + self.target.mine_threat * 2 + 12 * self.ennemy_near
         if self.distance <= 2 and self.alignment > 1:
             # dead angle
-            self.interest -= 18
+            self.interest += 36
 
 class Attack(BaseObjective):
     def _compute_interest(self):
@@ -188,6 +196,8 @@ class Grid(Base):
         self.mines = []
         self.cannonballs = []
         
+        self.threat = {}
+        
         for e in list(entities.values()) + ghost_mines:
             self.index[e.pos] = e
             type_ = type(e)
@@ -207,6 +217,8 @@ class Grid(Base):
 
             elif type_ is Cannonball:
                 self.cannonballs.append(e)
+                if e.pos not in self.threat or self.threat[e.pos] > e.countdown:
+                    self.threat = {e.pos: e.countdown}
                 
         for s in self.owned_ships:
             s.allies = [other for other in self.owned_ships if other is not s]
@@ -232,7 +244,6 @@ class Grid(Base):
         for b in self.barrels:
             b.dispersal = Grid.manhattan(grav_center, b.pos) if grav_center != None else 0
             b.mine_threat = any(type(self.at(*c)) is Mine for c in self.neighbors(*b.pos))
-            b.ennemy_near = any(b.pos in e.next_area for e in self.ennemy_ships)
                 
         for s in self.owned_ships:
             
@@ -252,7 +263,6 @@ class Grid(Base):
                 obj = Attack(e)
                 obj.eval(s.pos, s.orientation)
                 s.ennemies.put(obj)
-
         
     def at(self, x, y):
         try:
@@ -294,26 +304,37 @@ class Grid(Base):
             base_costs[c] += 30
         for m in self.mines:
             base_costs[m.pos] += 1000
+            
+            if m.pos in self.threat:
+                if self.threat[m.pos] <= 2:
+                    # avoid the area of a mines going to explode
+                    for n in self.neighbors(*m.pos):
+                        base_costs[n] += 1000
+                        
         for c in self.cannonballs:
-            base_costs[c.pos] += (150 + (6 - c.countdown) * 200)
-        
+            if 0 < c.countdown <= 2:
+                base_costs[c.pos] += 1000
+                
         for ship in self.ships:
             ship._moving_costs = {}
             ship._moving_costs.update(base_costs)
+            played_before = True
             for other in self.ships:
                 if other is ship:
+                    played_before = False
                     continue
                 dist = self.manhattan(ship.pos, other.pos)
                 if dist > 6:
                     continue
                 for c in self.zone(other.pos, 3):
                     ship._moving_costs[c] += 25
-                    
+                
                 next_positions = other.next_pos_proba()
-                for c, proba in next_positions[1].items():
-                    ship._moving_costs[c] = ship._moving_costs.get(c, 0) + 10 * proba
+                for c, proba in next_positions[1 if played_before else 0].items():
+                    if proba >= 20:
+                        ship._moving_costs[c] = ship._moving_costs.get(c, 0) + 13 * proba
 
-    def shooting_spot(self, ship, target):
+    def shooting_spot(self, ship, target, current=None):
         shooting_spots = Queue()
         target_pos = target.next_pos if type(target) is Ship else target.pos
         
@@ -325,6 +346,9 @@ class Grid(Base):
             
             interest = 0 # the lower the better
             
+            if (x, y) == current:
+                interest -= 20
+            
             interest += ship.moving_cost(x, y)
             
             # avoid cells too close from borders
@@ -333,13 +357,15 @@ class Grid(Base):
             if not 3 <= y < (self.h - 3):
                 interest += 50
             
-            diff = abs(Grid.direction_to(*ship.prow, x, y))
-            if diff > 1:
-                interest += 15 * abs(diff)
+            # priorize cells in the current direction
+            diff = abs(Grid.diff_directions(ship.orientation, Grid.direction_to(*ship.prow, x, y)))
+            interest += 10 * abs(diff)
             
-            # priorize spots at distance 5 from active ship
-            interest += (10 * abs(5 - self.manhattan((x, y), ship.pos)))
+            # priorize spots at distance 6 from active ship
+            interest += (10 * abs(6 - self.manhattan((x, y), ship.pos)))
             
+            # priorize spots at distance 6 from targetted ship
+            interest += (10 * abs(6 - self.manhattan((x, y), target.pos)))
             
             shooting_spots.put((x, y), interest)
         return shooting_spots.get()
@@ -483,19 +509,19 @@ class Grid(Base):
         return result
 
     # pathfinding
-    def path(self, start, d0, target, moving_costs={}, inertia=0, incl_start=False, limit=10000):
+    def path(self, start, start_d, target, moving_costs={}, inertia=0, incl_start=False, limit=10000):
         nodes = Queue()
         break_on, iteration = limit, 0
         broken = False
         
         effective_start = start
-        for _ in range(inertia):
-            effective_start = self.next_cell(*effective_start, d0)
-            n = PathNode(*effective_start)
-            n.orientation = d0
-        
         origin = PathNode(*effective_start)
-        origin.orientation = d0
+        origin.orientation = start_d
+        
+        for _ in range(inertia):
+            effective_start = self.next_cell(*effective_start, start_d)
+            origin = PathNode(*effective_start, origin)
+            origin.orientation = start_d
         
         nodes.put(origin, 0)
         neighbors = []
@@ -503,7 +529,8 @@ class Grid(Base):
         while nodes:
             current = nodes.get()
 
-            if current == target or broken:
+            if broken or current == target:
+                
                 path = []
                 previous = current
                 while previous:
@@ -537,12 +564,16 @@ class Grid(Base):
                 if any((moving_costs.get(c, 1000) >= 1000 and not Grid.is_border(*c)) for c in area):
                     continue
                     
-                cost = current.cost + moving_cost + diff * 10
+                cost = current.cost + moving_cost + diff * 20
                     
-                if (x, y) == effective_start and d == d0:
-                    # prefer to go right at start
+                if (x, y) == start and d == start_d:
+                    # prefer to go right at start (if not speed)
                     cost -= 10
 
+#                 if diff and current.parent and current.orientation != current.parent.orientation:
+#                     # avoid consecutives direction changes
+#                     cost += 20
+
                 priority = cost + 10 * Grid.manhattan((x, y), target)
                 
                 node = PathNode(x, y, current)
@@ -814,6 +845,10 @@ class Ship(Entity):
     
     def plan_next_move(self):
         
+        if self.speed and any(self.front in s.area for s in grid.ships if s is not self):
+            log("Blocked: speed 0")
+            self.speed = 0
+            
         if self.path:
             planned = self._follow_path(self.path)
             
@@ -832,7 +867,8 @@ class Ship(Entity):
                     available[Ship.TURN_RIGHT] = 0
                     
                 for m in available:
-                    available[m] = sum([self.moving_cost(*c) for c in self.area_after_moving(m)])
+                    new_area = self.area_after_moving(m)
+                    available[m] = 2 * self.moving_cost(*new_area[0]) + 2 * self.moving_cost(*new_area[1]) + self.moving_cost(*new_area[2])
                     
                 planned = min(available.items(), key=lambda x: x[1])[0]
                 log(f"(!) broken: automove ({Ship.COMMANDS[planned]})")
@@ -843,21 +879,43 @@ class Ship(Entity):
         elif not self.path:
             return False
         
-        next_move = planned
-        available_moves = [next_move] + [m for m in Ship.MOVES if m != planned]
+        next_move = None
+        available_moves = [planned] + [m for m in Ship.MOVES if m != planned]
+        risk = Queue()
         
         for move in available_moves:
             new_area = self.area_after_moving(move)
+            r = 0
             
-            # special: extra-grid cells are not consider as collisions since a part of the ship can go there
-            if any((self.moving_cost(*c) >= 1000 and c in grid) for c in new_area):
-                log(f"/!\ Danger: planned move <{Ship.COMMANDS[move]}> would lead to collision ({new_area})")
+            for i, c in enumerate(new_area):
+                mc = self.moving_cost(*c)
+                
+                if mc >= 1000 and c in grid:  # special: extra-grid cells are not consider as collisions since a part of the ship can go there
+                    if grid.threat.get(c, 0) > 1 and mc < 2000:
+                        r += 10
+                    else:
+                        if i == 1:
+                            # the center of the ship is threaten
+                            r += 50
+                        else:
+                            r += 25
+
+            risk.fput(move, r)
+            if r:
+                log(f"/!\ Danger: planned move <{Ship.COMMANDS[move]}> could lead to collision (risk={r}, area={new_area})")
             else:
+                log(f"Safe move: {move}")
                 next_move = move
                 break
+
         else:
-            log("* No collision-free move was found, go to the initial one")
-        
+            try:
+                next_move = risk.get()
+                log(f"* No collision-free move was found, go to the less risky: {next_move}")
+            except IndexError:
+                next_move = planned
+                log("* No collision-free move was found, go to the initial one: {next_move}")
+                
         self.next_move = next_move
         self.next_area = new_area
         return True
@@ -865,7 +923,7 @@ class Ship(Entity):
     def _follow_path(self, path):
         
         # flags represent direction changes or end of the path
-        last_flag = len(path) - 1
+        last_flag = len(path)
         next_flag = next((i for i, n in enumerate(path) if n.orientation != self.orientation), last_flag)
         afternext_flag = next((i for i, n in enumerate(path[next_flag:]) if n.orientation != path[next_flag].orientation), last_flag)
         
@@ -889,21 +947,24 @@ class Ship(Entity):
         
         elif self.speed == self.MAX_SPEED:
             
-            if self.speed == next_flag and afternext_flag >= (next_flag + 2): # there is at least one straight cell after this drift
-                # drift
-                diff = Grid.diff_directions(self.orientation, path[next_flag].orientation)
-                if diff > 0:
-                    return Ship.TURN_LEFT
-                elif diff < 0:
-                    return Ship.TURN_RIGHT
-            
-            if (self.speed + 1) >= next_flag:
+            if next_flag <= self.speed:
+                if afternext_flag >= (next_flag + 2): # there is at least one straight cell after this drift
+                    # drift
+                    diff = Grid.diff_directions(self.orientation, path[next_flag].orientation)
+                    if diff > 0:
+                        return Ship.TURN_LEFT
+                    elif diff < 0:
+                        return Ship.TURN_RIGHT
+                else:
+                    return Ship.SLOW_DOWN
+                
+            if next_flag <= self.speed + 1:
                 # next direction change or target will be passed at current speed
                 return Ship.SLOW_DOWN
 
         elif self.speed == 1:
             
-            if self.speed == next_flag:
+            if next_flag <= 1:
                 diff = Grid.diff_directions(self.orientation, path[next_flag].orientation)
                 if diff > 0:
                     return Ship.TURN_LEFT
@@ -912,7 +973,7 @@ class Ship(Entity):
             
             elif next_flag >= 4:
                 return Ship.SPEED_UP
-        
+            
         return None
            
     def move(self):
@@ -940,7 +1001,11 @@ class Ship(Entity):
             avoid += ship.next_area
         
         next_positions = target.next_pos_proba(4)
-        barrels = [b.pos for b in grid.barrels if not any(Grid.manhattan(ally.pos, b.pos) <= Grid.manhattan(target.pos, b.pos) for ally in self.allies)]
+        barrels = [b.pos for b in grid.barrels]
+        
+        for bpos in barrels:
+            if any(Grid.manhattan(ship.pos, bpos) <= Grid.manhattan(target.pos, bpos) for ship in grid.owned_ships):
+                avoid.append(bpos)
         
         for t, probas in next_positions.items():
             
@@ -949,8 +1014,13 @@ class Ship(Entity):
             for c, proba in probas.items():
                 if c in grid.next_to_mine:
                     mpos = grid.next_to_mine[c].pos
-                    if Grid.manhattan(self.next_pos, mpos) <= 2:
+                    dist = Grid.manhattan(self.next_pos, mpos)
+                    if dist <= 2:
                         continue
+                    alignment = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*self.pos, *mpos)))
+                    if alignment <= 1:
+                        continue
+                    
                     mines_next[mpos] = mines_next.get(mpos, 1) + proba
             probas.update(mines_next)
             
@@ -989,7 +1059,7 @@ class Ship(Entity):
             
     def mine_maybe(self):
         if self.can_mine():
-            if not any(Grid.manhattan(self.pos, ally.pos) <= 5 for ally in self.allies):
+            if not any(Grid.manhattan(self.prow, ally.next_pos) <= 5 for ally in self.allies):
                 self.mine()
                 return True
         return False
@@ -1123,7 +1193,7 @@ while True:
     for ship in grid.owned_ships:
         log(f"---- ship {ship.id} ---")
         log(f"ship: {ship}")
-        log(f"obj: {ship.objective}; next: {ship.objectives_next}")
+        
         log(f"target: {ship.target_ennemy}")
         
         it_consumed = 0
@@ -1131,7 +1201,7 @@ while True:
         if ship.objective:
             ship.goto = ship.objective.target.pos
         elif ship.target_ennemy:
-            ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target)
+            ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target, current=ship.goto)
         else:
             log("ERROR: No target")
             continue
@@ -1147,7 +1217,7 @@ while True:
         it_consumed += its
         
         if ship.objective and ship.path and ship.path[-1] == ship.goto:
-            while ship.objectives and len(ship.path) < 10:
+            while ship.objectives and len(ship.path) < 15:
                 pos, d = ship.path[-1], ship.path[-1].orientation
                 
                 ship.objectives = ObjectivesQueue.re_eval(ship.objectives, pos, d)
@@ -1157,7 +1227,7 @@ while True:
             
                 new_path, its = grid.path(pos, d, 
                                        current_obj.target.pos, 
-                                       ship._moving_costs,
+                                       moving_costs=ship._moving_costs,
                                        limit=(max_it - it_consumed))
                 
                 it_consumed += its
@@ -1169,6 +1239,7 @@ while True:
  
         ship.plan_next_move()
 
+        log(f"obj: {ship.objective}; next: {ship.objectives_next}")       
         log(f"path: {ship.path}")
         log(f"next_move: {Ship.COMMANDS[ship.next_move]}")
         
@@ -1194,3 +1265,28 @@ while True:
         
         log("ERROR: Did not act, wait")
         ship.wait()
+
+
+
+
+# Changelog:
+# 2019-04-17 
+# * if no available move, takes the less risky
+# * add the ennemy_near evaluation to the pre_eval method of GetBarrel and update it
+# * ignore probas < 20 in the moving cost calc (about ennemy next positions)
+# * get_shooting_spots: discurage direction changes
+# * increase shooting_spots distance from 5 to 7
+# * fire on mines, do not shoot mines that are in front of the ship
+# * increase the weight of a dead angle in GetBarrel.eval() from 18 to 36 <=> the weight of a distance of 6 needed to turn
+# * mines: increase moving_cost of neighbors if a cannon ball is going to hit the mine
+# * Improve the _follow_path algo
+# * anticipate the speed at 0 when blocked
+# * coeff 13 instead of 10 for the presence probability, making it impassable from env. 77% instead of 100%
+# * moving cost takes now in account the order of play of the ships
+# * increase the max length of the path from 10 to 15 when seeking for next objectives
+# * minor improvement to automove
+# * Avoid shooting barrels unless ennemy is nearest
+# * include distance to ennemy in the eval of the shooting spot
+# * (disactivated because of below) avoid consecutives direction changes 
+# * path: direction change moving cost changed from 10 to 20 because of the slowing effect
+