Prechádzať zdrojové kódy

changelog 2019-04-18

olinox 6 rokov pred
rodič
commit
b3a3ac9a09
1 zmenil súbory, kde vykonal 162 pridanie a 79 odobranie
  1. 162 79
      carribean/script.py

+ 162 - 79
carribean/script.py

@@ -12,8 +12,15 @@ import time
 # * 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
-# * collisions: take in account the probable next area of near ennemies
 # * make a difference between moving cost, mines, cannonballs, out of grid and other ships
+# * automove: priorize
+
+# Enhancements
+# * interception trajectories
+# * paths with extra-steps
+# * Part 1: Speed up
+# * Part 2: Increase shooting rate
+# * If an owned ship has the max stock of rum and no barrels are left: runaway!
 
 debug = True
 
@@ -366,6 +373,36 @@ class Grid(Base):
             shooting_spots.put((x, y), interest)
         return shooting_spots.get()
     
+    def runaway_spot(self, ship):
+        runaway_spot = Queue()
+        
+        for x, y in iter(self):
+            if ship.moving_cost(x, y) > 100:
+                continue
+            
+            interest = 0 # the lower the better
+            
+            interest += ship.moving_cost(x, y)
+            
+            # avoid cells too close from borders
+            if not 3 < x < (self.w - 3):
+                interest += 70
+            if not 3 <= y < (self.h - 3):
+                interest += 70
+            
+            # priorize cells in the current direction
+            diff = abs(Grid.diff_directions(ship.orientation, Grid.direction_to(*ship.prow, x, y)))
+            interest += 20 * abs(diff)
+            
+            # priorize spots at distance 6 from active ship
+            interest += (20 * abs(6 - self.manhattan((x, y), ship.pos)))
+            
+            # max distance from ennemies
+            interest -= (10 * min([Grid.manhattan((x, y), e.next_pos) for e in self.ennemy_ships]))
+            
+            runaway_spot.put((x, y), interest)
+        return runaway_spot.get()
+    
     # geometrical algorithms
     @staticmethod
     def from_cubic(xu, yu, zu):
@@ -514,10 +551,11 @@ class Grid(Base):
         origin = PathNode(*effective_start)
         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
+        if inertia is not None:
+            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 = []
@@ -551,12 +589,21 @@ class Grid(Base):
                     continue
                 
                 d = Grid.direction_to(*current, x, y)
+                if current == start and inertia == 0 and d != current.orientation:
+                    # special: if started with speed 0, first move will require a speed_up, 
+                    # making impossible a direction change at first node
+                    continue
+                
                 diff = abs(Grid.diff_directions(current.orientation, d))
                 if diff > 1:
                     # change direction one degree at a time
                     continue
                     
                 area = Ship.get_area(x, y, d)
+                area = [area[0], area[2]]
+                if diff:
+                    inertial_area = Ship.get_area(x, y, current.orientation)
+                    area += [inertial_area[0], inertial_area[2]]
                 if any((moving_costs.get(c, 1000) >= 1000 and not Grid.is_border(*c)) for c in area):
                     continue
                     
@@ -566,10 +613,6 @@ class Grid(Base):
                     # 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)
@@ -685,7 +728,6 @@ class Ship(Entity):
         
         self.goto = None
         self.path = []
-        self.cached_next_pos_proba = {}
         
         self.area = Ship.get_area(self.x, self.y, self.orientation)
         self.prow, _, self.stern = self.area
@@ -730,9 +772,6 @@ class Ship(Entity):
     
     def next_pos_proba(self, in_=1):
 
-        if in_ in self.cached_next_pos_proba:
-            return {k: v for k, v in self.cached_next_pos_proba.items() if k <= in_}
-
         # guess next positions
         positions = {0: [Position(self.pos, self.orientation, self.speed)]}
         for i in range(in_):
@@ -788,8 +827,6 @@ class Ship(Entity):
             for c in weights:
                 proba[i][c] = 100 * weights[c] // total_weight
 
-        self.cached_next_pos_proba = proba
-                
         return proba
     
     def guess_next_positions(self, in_=1):
@@ -819,7 +856,7 @@ class Ship(Entity):
                and (self._can_move[self.back_left] or grid.is_border(*self.back_left))
     
     def can_move_fwd(self):
-        return self._can_move[self.front]
+        return self._can_move[self.front] and not Grid.is_border(*self.prow)
     
     def can_move(self):
         return self.can_move_fwd() or self.can_turn_left() or self.can_turn_left()
@@ -841,9 +878,12 @@ 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
+        blocked = False
+        if self.speed:
+            if any(self.front in (s.area if not s.speed else [s.area[1], s.area[0], s.prow]) for s in grid.ships if s is not self):
+                log("Blocked: speed 0")
+                blocked = True
+                self.speed = 0
             
         if self.path:
             planned = self._follow_path(self.path)
@@ -851,7 +891,6 @@ class Ship(Entity):
         if self.path is None:
             if self.can_move():
                 available = {}
-                
                 if self.can_move_fwd():
                     if self.speed:
                         available[None] = 0
@@ -864,14 +903,14 @@ class Ship(Entity):
                     
                 for m in available:
                     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])
+                    available[m] = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*new_area[1], *self.goto))) + \
+                                   (2 if self.moving_cost(*new_area[1]) > 10 else 0)
                     
                 planned = min(available.items(), key=lambda x: x[1])[0]
                 log(f"(!) broken: automove ({Ship.COMMANDS[planned]})")
             else:
                 log(f"(!) broken: can not move")
-                self.next_area = self.area
-                return False
+                planned = None
         elif not self.path:
             return False
         
@@ -880,37 +919,58 @@ class Ship(Entity):
         risk = Queue()
         
         for move in available_moves:
+                
             new_area = self.area_after_moving(move)
             r = 0
             
             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
+                if mc >= 1000 and (c in grid or i == 1):  # special: extra-grid cells are not consider as collisions since a part of the ship can go there
+                    countdown = grid.threat.get(c, 0)
+                    if countdown > 1 and mc < 2000:
+                        r += 100
                     else:
+                        if blocked and move in (Ship.SLOW_DOWN, None, Ship.SPEED_UP):
+                            r = 1000
                         if i == 1:
                             # the center of the ship is threaten
-                            r += 50
+                            r += 500
                         else:
-                            r += 25
+                            r += 250
+            
+            if r:
+                if self.speed and move == Ship.SPEED_UP or self.speed == self.MAX_SPEED and move != Ship.SLOW_DOWN:
+                    # danger: better slow down
+                    r += 20
+            
+            if not r:
+                if move == planned:
+                    r = -1
+                elif self.speed <= 1 and move == Ship.SLOW_DOWN:
+                    r = 50 # we don't want to slow down except there is a danger
+                else:
+                    # distance from the prow to the current objective
+                    r = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*new_area[1], *self.goto)))
+                    r += (2 if self.moving_cost(*new_area[1]) > 10 else 0)
 
             risk.fput(move, r)
-            if r:
+            if r >= 100:
                 log(f"/!\ Danger: planned move <{Ship.COMMANDS[move]}> could lead to collision (risk={r}, area={new_area})")
-            else:
-                log(f"Safe move: {Ship.COMMANDS[move]}")
+            elif r < 0:
                 next_move = move
+                log(f"Safe move: {Ship.COMMANDS[move]}")
                 break
+            else:
+                log(f"Available move <{Ship.COMMANDS[move]}> (risk={r}, area={new_area})")
 
         else:
             try:
                 next_move = risk.get()
-                log(f"* No collision-free move was found, go to the less risky: {next_move}")
+                log(f"* Go to the less risky: {Ship.COMMANDS[next_move]}")
             except IndexError:
                 next_move = planned
-                log("* No collision-free move was found, go to the initial one: {next_move}")
+                log(f"* No collision-free move was found, go to the initial one: {Ship.COMMANDS[next_move]}")
                 
         self.next_move = next_move
         self.next_area = new_area
@@ -988,7 +1048,7 @@ class Ship(Entity):
     def fire_at_will(self, *args, **kwargs):
         return self._fire_at_will(*args, **kwargs)
            
-    def _fire_at_will(self, target):
+    def _fire_at_will(self):
         if not self.can_fire():
             return False
         
@@ -996,54 +1056,62 @@ class Ship(Entity):
         for ship in grid.owned_ships:
             avoid += ship.next_area
         
-        next_positions = target.next_pos_proba(4)
         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):
+            if any(Grid.manhattan(ship.pos, bpos) <= Grid.manhattan(e.pos, bpos) for ship in grid.owned_ships for e in grid.ennemy_ships):
                 avoid.append(bpos)
         
-        all_shots = []
-        for t, probas in next_positions.items():
-            
-            # include mines and barrels
-            mines_next = {}
-            for c, proba in probas.items():
-                if c in grid.next_to_mine:
-                    mpos = grid.next_to_mine[c].pos
-                    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)
-            
-            for c in probas:
-                if c in barrels:
-                    probas[c] *= 2
-            
-            shots = sorted(probas.items(), key=lambda x: x[1], reverse=True)
+        for m in grid.mines:
+            if any((Grid.manhattan(s.next_pos, m.pos) <= 2 or \
+                    abs(Grid.diff_directions(self.orientation, Grid.direction_to(*s.next_pos, *m.pos))) <= 1)\
+                    for s in grid.owned_ships):
+                avoid.append(m.pos)
+        
+        all_shots = {}
+        
+        for target in grid.ennemy_ships:
+        
+            next_positions = target.next_pos_proba(4)
             
-            for c, proba in shots:
-                if c in avoid:
-                    continue
-                if proba < 20:
-                    continue
-                dist = Grid.manhattan(self.prow, c)
-                if dist > self.SCOPE:
-                    continue
+            # include avoid, mines, barrels, and other ennemies
+            for t in next_positions:
+                probas = next_positions[t]
+                mines_next = {}
                 
-                # time for the cannonball to reach this pos (including fire turn)
-                delay = 1 + (1 + round(dist / 3)) 
-                if delay != t:
-                    continue
+                for c, proba in probas.items():
+                    if c in grid.next_to_mine:
+                        mpos = grid.next_to_mine[c].pos
+                        mines_next[mpos] = mines_next.get(mpos, 1) + proba
+                probas.update(mines_next)
                 
-                all_shots.append((c, proba,  t))
+                for c in probas:
+                    if c in barrels:
+                        probas[c] *= 2
+            
+            for t, probas in next_positions.items():
                 
+                shots = sorted(probas.items(), key=lambda x: x[1], reverse=True)
+                for c, proba in shots:
+                    if c in avoid:
+                        continue
+                    if proba < 20:
+                        continue
+                    dist = Grid.manhattan(self.prow, c)
+                    if dist > self.SCOPE:
+                        continue
+                    
+                    # time for the cannonball to reach this pos (including fire turn)
+                    delay = 1 + round(dist / 3)
+                    if delay != t:
+                        continue
+                    
+                    if not c in all_shots:
+                        all_shots[c] = (proba - t)
+                    else:
+                        all_shots[c] += (proba - t)
+                    
         if all_shots:
-            best_shot = max(all_shots, key=lambda x: x[1:])[0]
+            best_shot = max(all_shots.items(), key=lambda x: x[1])[0]
             log(f"[x] precise shoot: pos={best_shot}")
             ship.fire(*best_shot)
             return True
@@ -1193,14 +1261,16 @@ while True:
         log(f"---- ship {ship.id} ---")
         log(f"ship: {ship}")
         
-        log(f"target: {ship.target_ennemy}")
-        
         it_consumed = 0
         
         if ship.objective:
             ship.goto = ship.objective.target.pos
         elif ship.target_ennemy:
-            ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target, current=ship.goto)
+            if all(s.stock < ship.stock for s in grid.ships if not s is ship):
+                log("Best stock: runaway!")
+                ship.goto = grid.runaway_spot(ship)
+            else:
+                ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target, current=ship.goto)
         else:
             log("ERROR: No target")
             continue
@@ -1214,6 +1284,7 @@ while True:
                               inertia=ship.speed, 
                               limit=(max_it - it_consumed))
         it_consumed += its
+        log(ship.path)
         
         if ship.objective and ship.path and ship.path[-1] == ship.goto:
             while ship.objectives and len(ship.path) < 15:
@@ -1227,18 +1298,21 @@ while True:
                 new_path, its = grid.path(pos, d, 
                                        current_obj.target.pos, 
                                        moving_costs=ship._moving_costs,
+                                       inertia=None, 
                                        limit=(max_it - it_consumed))
                 
                 it_consumed += its
                 
                 if new_path and new_path[-1] == current_obj.target.pos:
+                    log(new_path)
                     ship.path += new_path
                 else:
                     break
  
         ship.plan_next_move()
 
-        log(f"obj: {ship.objective}; next: {ship.objectives_next}")       
+        log(f"obj: {ship.objective}; next: {ship.objectives_next}")
+        log(f"target: {ship.target_ennemy}")
         log(f"path: {ship.path}")
         log(f"next_move: {Ship.COMMANDS[ship.next_move]}")
         
@@ -1255,7 +1329,7 @@ while True:
             continue
         
         # no movement was required, can fire
-        if ship.fire_at_will(ship.target_ennemy.target):
+        if ship.fire_at_will():
             continue
         
         # or mine
@@ -1288,4 +1362,13 @@ while True:
 # * 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
-# * Complete the the fire_at_will method
+
+# 2019-04-18
+# * take in account the inertial_area in path computing when direction change
+# * improve the esquive algo by priorizing moves in direction of the target
+# * remove cache on 'next_pos_proba'
+# * improve the fire_at_will algo
+# * takes all ennemies in account to find the best shot
+# * add the runaway behaviour
+# * fix the can_go_fwd method
+# * better path computing for the next objectives (with inertia which can be None)