Bladeren bron

various fixes

olinox 6 jaren geleden
bovenliggende
commit
f975045667
1 gewijzigde bestanden met toevoegingen van 191 en 112 verwijderingen
  1. 191 112
      carribean/script.py

+ 191 - 112
carribean/script.py

@@ -4,6 +4,7 @@
 '''
 import heapq
 import sys
+import time
 
 
 # TODO:
@@ -12,12 +13,15 @@ import sys
 # * avoid getting blocked by a side-by-side with an ennemy
 # * use a queue to choose the best shoot instead of a strict equality
 # * why do mines explode when turning around?
+# * improve next_pos_proba: eliminate next positions if not passables
 
 debug = True
 
+t0 = time.time()
+
 def log(*msg):
     if debug:
-        print(*msg, file=sys.stderr)
+        print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
 
 current_turn = 0
 
@@ -101,7 +105,7 @@ class BaseObjective(Base):
         
 class GetBarrel(BaseObjective):
     def _compute_interest(self):
-        self.interest = 6 * self.distance + 9 * self.alignment + 3 * self.target.dispersal + self.target.mine_threat ** 2 - 36 * self.target.ennemy_near
+        self.interest = 6 * self.distance + 12 * self.alignment + 3 * self.target.dispersal + self.target.mine_threat ** 2 - 24 * self.target.ennemy_near
 
 class Attack(BaseObjective):
     def _compute_interest(self):
@@ -119,19 +123,36 @@ class PathNode(tuple):
         return f"<{self[0]}, {self[1]}, c:{self.cost}, o:{self.orientation}>"
 
 class Grid(Base):
-    def __init__(self):
-        self.w = 23
-        self.h = 21
+    w = 23
+    h = 21
+    _neighbors = {}
+    _next_cell = {}
         
-        self._neighbors = {}
-        for x in range(-1, self.w + 1):
-            for y in range(-1, self.h + 1):
-                self.cache_neighbors(x, y)
- 
+    def __init__(self):
         self.load_entities({})
         
+    @classmethod
+    def preload(cls):
+        cls._neighbors = {}
+        for x in range(-1, cls.w + 1):
+            for y in range(-1, cls.h + 1):
+                cls.cache_neighbors(x, y)
+                
+        cls._next_cell = {}
+        for x in range(0, cls.w):
+            for y in range(0, cls.h):
+                cls.cache_next_cell(x, y)
+                
+        for x in range(0, cls.w):
+            for y in range(0, cls.h):
+                Ship.cache_area(x, y)
+    
+    @classmethod
+    def contains(cls, x, y):
+        return 0 <= x < cls.w and 0 <= y < cls.h 
+    
     def __contains__(self, key):
-        return 0 <= key[0] < self.w and 0 <= key[1] < self.h
+        return self.contains(*key)
 
     def __iter__(self):
         for item in ((x, y) for x in range(self.w) for y in range(self.h)):
@@ -145,7 +166,7 @@ class Grid(Base):
         ghost_mines = []
         if hasattr(self, "mines"):
             for m in self.mines:
-                if not m.pos in [e.pos for e in entities.values() if type(e) is Mine]:
+                if not m.id in entities:
                     if all((self.manhattan(m.pos, ship.pos) > 5) for ship in self.owned_ships):
                         m.ghost = True
                         ghost_mines.append(m)
@@ -186,7 +207,7 @@ class Grid(Base):
             s.allies = [other for other in self.ennemy_ships if other is not s]
 
         for s in self.ships:
-            s.next_positions = s.next_pos_proba(4)
+            s.next_pos_proba(2)
 
         self.update_moving_costs()
         
@@ -198,27 +219,33 @@ class Grid(Base):
                 
         for s in self.owned_ships:
             
-            s._can_move = {c: (s.moving_cost(*c) < 1000) for c in [s.front, s.front_left, s.left, s.front_right,
-                                                                  s.right, s.back_left, s.back_right]}
+            s._can_move = {c: (s.moving_cost(*c) < 1000) 
+                           for c in [s.front, s.front_left, s.left, s.front_right,
+                                     s.right, s.back_left, s.back_right]}
+            
             s.objectives = ObjectivesQueue()
             s.ennemies = ObjectivesQueue()
             
             for b in self.barrels:
                 obj = GetBarrel(b)
-                obj.eval(s.next_pos if s.speed else s.prow, s.orientation)
+                obj.eval(s.pos, s.orientation)
                 s.objectives.put(obj)
                 
             for e in self.ennemy_ships:
                 obj = Attack(e)
-                obj.eval(s.next_pos, s.orientation)
+                obj.eval(s.pos, s.orientation)
                 s.ennemies.put(obj)
 
+        
     def at(self, x, y):
         try:
             return self.index[(x, y)]
         except KeyError:
             return None
         
+    def borderline(self, x, y):
+        return x == -1 or y == -1 or x == self.w or y == self.h
+        
     def collision_at(self, x, y):
         e = self.at(x, y)
         return type(e) in [Mine, Ship, Cannonball] or not (x, y) in self.__iter__()
@@ -260,26 +287,22 @@ class Grid(Base):
                 if other is ship:
                     continue
                 dist = self.manhattan(ship.pos, other.pos)
-                if dist > 8:
+                if dist > 6:
                     continue
-                if not other.speed:
-                    for c in other.area:
+                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():
+                    if proba > 20 and not c in ship.area:
                         ship._moving_costs[c] += 1000
-                else:
-                    for c in self.zone(other.next_pos, 4):
-                        if c in other.next_positions[1]:
-                            if other.next_positions[1][c] > 10:
-                                ship._moving_costs[c] += 1000
-                            else:
-                                ship._moving_costs[c] += 100
-                        else:
-                            ship._moving_costs[c] += 20
+                    else:
+                        ship._moving_costs[c] += 100
 
     def shooting_spot(self, ship, target):
         shooting_spots = Queue()
         target_pos = target.next_pos if type(target) is Ship else target.pos
         
-        for x, y in self.zone(target_pos, 10):
+        for x, y in self.zone(target_pos, 8):
             if ship.moving_cost(x, y) > 100:
                 continue
             if self.manhattan((x, y), target_pos) <= 2:
@@ -323,14 +346,10 @@ class Grid(Base):
         xa, ya = from_
         xb, yb = to_
         return abs(xa - xb) + abs(ya - yb) 
-
-    def zone(self, center, radius):
-        buffer = frozenset([center])
-        for _ in range(0, radius):
-            current = buffer
-            for x, y in current:
-                buffer |= frozenset(self.abs_neighbors(x, y))
-        return [c for c in buffer if 0 <= c[0] < self.w and 0 <= c[1] < self.h]       
+    
+    @classmethod
+    def zone(cls, center, radius):
+        return [(x, y) for x in range(0, cls.w) for y in range(0, cls.h) if cls.manhattan(center, (x, y)) <= radius]      
 
     @staticmethod
     def closest(from_, in_):
@@ -384,12 +403,12 @@ class Grid(Base):
             d -= 6
         return d
     
-    @staticmethod
-    def next_cell(x, y, d, repeat=1):
-        for _ in range(repeat):
-            dx, dy = Grid.directions(y)[d]
-            x, y = x + dx, y + dy
-        return x, y
+#     @staticmethod
+#     def next_cell(x, y, d, repeat=1):
+#         for _ in range(repeat):
+#             dx, dy = Grid.directions(y)[d]
+#             x, y = x + dx, y + dy
+#         return x, y
     
     @staticmethod
     def symetry(d):
@@ -399,16 +418,38 @@ class Grid(Base):
     def abs_neighbors(x, y):
         return ((x + dx, y + dy) for dx, dy in Grid.directions(y))
     
-    def cache_neighbors(self, xc, yc):
-        self._neighbors[(xc, yc)] = [(x, y) for x, y in Grid.abs_neighbors(xc, yc) if 0 <= x < self.w and 0 <= y < self.h]
+    @classmethod
+    def cache_neighbors(cls, xc, yc):
+        cls._neighbors[(xc, yc)] = [(x, y) for x, y in Grid.abs_neighbors(xc, yc) if 0 <= x < cls.w and 0 <= y < cls.h]
 
-    def neighbors(self, x, y):
+    @classmethod
+    def neighbors(cls, x, y):
         try:
-            return self._neighbors[(x, y)]
+            return cls._neighbors[(x, y)]
         except KeyError:
-            self.cache_neighbors(x, y)
-            return self._neighbors[(x, y)]
-        
+            cls.cache_neighbors(x, y)
+            return cls._neighbors[(x, y)]
+    
+    @classmethod
+    def abs_next_cell(cls, x, y, d):
+        dx, dy = Grid.directions(y)[d]
+        return x + dx, y + dy
+            
+    @classmethod
+    def cache_next_cell(cls, x, y):
+        for d, dv in enumerate(Grid.directions(y)):
+            dx, dy = dv
+            cls._next_cell[(x, y, d)] = (x + dx, y + dy)
+
+    @classmethod
+    def next_cell(cls, x, y, d, repeat=1):
+        for _ in range(repeat):
+            try:
+                x, y = cls._next_cell[(x, y, d)]
+            except KeyError:
+                x, y = cls.abs_next_cell(x, y, d)
+        return x, y
+    
     def rotate(self, center, coordinates, rotations):
         if coordinates == [center] or rotations % 6 == 0:
             return coordinates
@@ -456,7 +497,7 @@ class Grid(Base):
                     if previous != origin or incl_start:
                         path.insert(0, previous)
                     previous = previous.parent
-                return inertia_path + path
+                return inertia_path + path, iteration
 
             neighbors = self.neighbors(*current)
 
@@ -464,11 +505,10 @@ class Grid(Base):
                 
                 if (x, y) == current.parent:
                     continue
-                
+
                 iteration += 1
                 if break_on > 0 and iteration >= break_on:
                     broken = True
-                    break
                 
                 moving_cost = moving_costs.get((x, y), 1000)
                 if moving_cost >= 1000:
@@ -480,7 +520,8 @@ class Grid(Base):
                     # change direction one degree at a time
                     continue
                     
-                if any(moving_costs.get(c, 1000) >= 1000 for c in Ship.get_area(x, y, d)):
+                area = Ship.get_area(x, y, d)
+                if any(moving_costs.get(c, 1000) >= 1000 for c in area):
                     continue
                     
                 cost = current.cost + moving_cost + diff * 10
@@ -495,8 +536,8 @@ class Grid(Base):
                 node.cost = cost
                 node.orientation = d
                 nodes.put(node, priority)
-        else:
-            return None
+                
+        return None, iteration
 
 class Entity(Base):
     def __init__(self, ent_id):
@@ -530,9 +571,11 @@ class Ship(Entity):
     SPEED_UP = 2
     TURN_LEFT = 3
     TURN_RIGHT = 4
-    MOVES = [SLOW_DOWN, SPEED_UP, TURN_LEFT, TURN_RIGHT]
+    MOVES = [SPEED_UP, TURN_LEFT, TURN_RIGHT, SLOW_DOWN]
     COMMANDS = {SLOW_DOWN: "SLOWER", SPEED_UP: "FASTER", TURN_LEFT: "PORT", TURN_RIGHT: "STARBOARD", None: "NONE"}
     
+    areas = {}
+    
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.x, self.y = 0, 0
@@ -550,6 +593,7 @@ class Ship(Entity):
         self.last_action = ""
         self.allies = []
         self._moving_costs = {}
+        self.cached_next_pos_proba = {}
         
         self.objectives = ObjectivesQueue()
         self.ennemies = ObjectivesQueue()
@@ -566,6 +610,21 @@ class Ship(Entity):
     def __repr__(self):
         return f"<Ship {self.id}: pos=({self.x}, {self.y}), orientation={self.orientation}, speed={self.speed}, blocked={self.blocked_since}, last_fire={self.last_fire}, next_pos={self.next_pos}, area={self.area}>"
 
+    @classmethod
+    def cache_area(cls, x, y):
+        for d in range(3):
+            area = [Grid.next_cell(x, y, d), (x, y), Grid.next_cell(x, y, d + 3)]
+            Ship.areas[(x, y, d)] = area
+            Ship.areas[(x, y, d + 3)] = list(reversed(area))
+            
+    @classmethod
+    def get_area(cls, x, y, d):
+        try:
+            return list(Ship.areas[(x, y, d)])
+        except KeyError:
+            log(f"Error: area key missing {(x, y, d)}")
+            return []
+        
     def update(self, x, y, *args):
         previous_state = self.state()
         previous_traject = self.traject()
@@ -623,22 +682,17 @@ class Ship(Entity):
     def get_pos_in(cls, current, speed, orientation, in_=1):
         return Grid.next_cell(*current, orientation, repeat=speed * in_)
 
-    @classmethod
-    def get_area(cls, x, y, orientation):
-        prow = Grid.next_cell(x, y, orientation)
-        stern = Grid.next_cell(x, y, Grid.add_directions(orientation, 3))
-        return [prow, (x, y), stern]
-
     def get_next_pos(self, in_=1):
         return self.get_pos_in(self.pos, self.speed, self.orientation, in_)
     
     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_):
-            
             positions[i + 1] = []
             
             for p in positions[i]:
@@ -681,7 +735,7 @@ class Ship(Entity):
                     if self.moving_cost(*c) >= 1000:
                         continue
                     proba[i][c] = proba[i].get(c, 0) + 10
-        
+                    
         # involve the moving cost
         for i in proba:
             for c in proba[i]:
@@ -690,10 +744,12 @@ class Ship(Entity):
             # if ship is blocked, current area is more accurate
             for c in self.area:
                 proba[i][c] = proba[i].get(c, 0) + 40 * self.blocked_since
-        
+                
+        self.cached_next_pos_proba = proba
+                
         return proba
     
-    def guess_next_positions(self, in_=3):
+    def guess_next_positions(self, in_=1):
         proba = self.next_pos_proba(in_)
         best = {}
         for i in proba:
@@ -710,10 +766,12 @@ class Ship(Entity):
         return self._moving_costs.get((x, y), 1000)
     
     def can_turn_left(self):
-        return self._can_move[self.left] and self._can_move[self.back_right]
+        return (self._can_move[self.left] or grid.borderline(*self.left)) \
+            and (self._can_move[self.back_right] or grid.borderline(*self.right))
     
     def can_turn_right(self):
-        return self._can_move[self.right] and self._can_move[self.back_left]
+        return (self._can_move[self.right] or grid.borderline(*self.right)) \
+               and (self._can_move[self.back_left] or grid.borderline(*self.back_left))
     
     def can_move_fwd(self):
         return self._can_move[self.front]
@@ -721,20 +779,44 @@ class Ship(Entity):
     def can_move(self):
         return self.can_move_fwd() or self.can_turn_left() or self.can_turn_left()
     
+    def area_after_moving(self, move):
+        new_speed = self.speed
+        new_orientation = self.orientation
+        if move == Ship.SPEED_UP:
+            new_speed += 1
+        elif move == Ship.SLOW_DOWN:
+            new_speed -= 1
+        elif move == Ship.TURN_LEFT:
+            new_orientation = Grid.add_directions(self.orientation, 1)
+        elif move == Ship.TURN_RIGHT:
+            new_orientation = Grid.add_directions(self.orientation, -1)
+        
+        new_pos = self.get_next_cell(new_speed)
+        return self.get_area(*new_pos, new_orientation)
+    
     def move(self, path):
-            
+        broken = False
+        
         if path:
             planned = self._plan_move(path)
             
         if path is None:
             if self.can_move():
-                if self.can_move_fwd():
-                    planned = Ship.SPEED_UP
-                elif self.can_turn_left():
-                    planned = Ship.TURN_LEFT
-                elif self.can_turn_right():
-                    planned = Ship.TURN_RIGHT
-                log(f"(!) broken: automove ({planned})")
+                available = {}
+                
+                broken = True
+                if not self.speed and self.can_move_fwd():
+                    available[Ship.SPEED_UP] = 0
+                if self.can_turn_left():
+                    available[Ship.TURN_LEFT] = 0
+                if self.can_turn_right():
+                    available[Ship.TURN_RIGHT] = 0
+                    
+                for m in available:
+                    available[m] = sum([self.moving_cost(*c) for c in self.area_after_moving(m)])
+                    
+                planned = min(available.items(), key=lambda x: x[1])[0]
+                log(f"(!) broken: automove ({Ship.COMMANDS[planned]})")
             else:
                 log(f"(!) broken: can not move")
                 return False
@@ -745,21 +827,8 @@ class Ship(Entity):
         available_moves = [next_move] + [m for m in Ship.MOVES if m != planned]
         
         for move in available_moves:
+            new_area = self.area_after_moving(move)
             
-            new_speed = self.speed
-            new_orientation = self.orientation
-            if move == Ship.SPEED_UP:
-                new_speed += 1
-            elif move == Ship.SLOW_DOWN:
-                new_speed -= 1
-            elif move == Ship.TURN_LEFT:
-                new_orientation = Grid.add_directions(self.orientation, 1)
-            elif move == Ship.TURN_RIGHT:
-                new_orientation = Grid.add_directions(self.orientation, -1)
-            
-            new_pos = self.get_next_cell(new_speed)
-            new_area = self.get_area(*new_pos, new_orientation)
-    
             # 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")
@@ -767,8 +836,13 @@ class Ship(Entity):
                 next_move = move
                 break
         else:
+#             if broken:
+#                 log("* No collision-free move was found, try automove")
+#                 self.auto_move(*self.goto)
+#                 return True
+#             else:
             log("* No collision-free move was found, go to the initial one")
-            
+        
         if next_move == Ship.SPEED_UP:
             self.speed_up()
         elif next_move == Ship.SLOW_DOWN:
@@ -832,7 +906,7 @@ class Ship(Entity):
                 elif diff < 0:
                     return Ship.TURN_RIGHT
             
-            elif next_flag > 3 or (next_flag > 2 and afternext_flag >= (next_flag + 2)):
+            elif next_flag > 3:
                 return Ship.SPEED_UP
         
         return None
@@ -850,8 +924,7 @@ class Ship(Entity):
         for ally in allies:
             avoid += ally.mobility_zone
         
-        next_positions = target.guess_next_positions(in_=4)
-        log(next_positions)
+        next_positions = target.guess_next_positions(4)
         for t, next_pos in next_positions.items():
             dist = Grid.manhattan(self.prow, next_pos)
             if dist > self.SCOPE:
@@ -865,7 +938,7 @@ class Ship(Entity):
                 log(f"[x] precise shoot: dt={dt}, pos={next_pos}")
                 ship.fire(*next_pos)
                 return True
-            
+        
         # give a try
         next_pos = next_positions[2]
         if not next_pos in avoid:
@@ -952,10 +1025,9 @@ map_entity = {"SHIP": Ship,
              "MINE": Mine,
              "CANNONBALL": Cannonball}
 
+Grid.preload()
 grid = Grid()
 
-
-
 ### *** Main Loop ***
 
 while True:
@@ -964,17 +1036,21 @@ while True:
     
     # <--- get input
     my_ship_count, entity_count = int(input()), int(input())
+    ent_input = [input().split() for _ in range(entity_count)]
+    # --->
+
+    log(f"### TURN {current_turn}")
+    log(">> Load input")
+    # <--- load input
     previous_ent, entities = grid.entities, {}
-    for _ in range(entity_count):
-        ent_id, ent_type, *data = input().split()
+    for e in ent_input:
+        ent_id, ent_type, *data = e
         ent_id = int(ent_id)
         entities[ent_id] = grid.entities.get(ent_id, map_entity[ent_type](ent_id))
         entities[ent_id].update(*data)
-    # --->
-    
+        
     grid.load_entities(entities)
-    
-    log(f"### TURN {current_turn}")
+    # --->
     
 #     log(f"Owned Ships: {grid.owned_ships}")
     log(f"Ennemy Ships: {grid.ennemy_ships}")
@@ -991,7 +1067,6 @@ while True:
     while not all(s.objective for s in grid.owned_ships):
         try:
             acquired = sorted([(s, s.objectives.get()) for s in grid.owned_ships if not s.objective], key= lambda x: x[1].interest)
-            
             for s, o in acquired:
                 if not s.objective and not any(al.objective.target is o.target for al in s.allies if al.objective):
                     s.objective = o
@@ -1017,14 +1092,15 @@ while True:
             log("ERROR: No target")
             continue
         
-        ship.path = grid.path(ship.pos, 
+        ship.path, its = grid.path(ship.pos, 
                               ship.orientation, 
                               ship.goto, 
                               moving_costs=ship._moving_costs, 
                               inertia=ship.speed, 
                               limit=(max_it - it_consumed))
+        it_consumed += its
         
-        if ship.objective and ship.path:
+        if ship.objective and ship.path and ship.path[-1] == ship.goto:
             while ship.objectives and len(ship.path) < 10:
                 pos, d = ship.path[-1], ship.path[-1].orientation
                 
@@ -1033,11 +1109,14 @@ while True:
                 
                 ship.objectives_next.append(current_obj)
             
-                new_path = grid.path(pos, d, 
+                new_path, its = grid.path(pos, d, 
                                        current_obj.target.pos, 
                                        ship._moving_costs,
-                                       limit=(max_it - it_consumed)) or []
-                if new_path:
+                                       limit=(max_it - it_consumed))
+                
+                it_consumed += its
+                
+                if new_path and new_path[-1] == current_obj.target.pos:
                     ship.path += new_path
                 else:
                     break