Explorar el Código

update caribbean

olinox hace 6 años
padre
commit
662e59e383
Se han modificado 1 ficheros con 255 adiciones y 179 borrados
  1. 255 179
      carribean/script.py

+ 255 - 179
carribean/script.py

@@ -5,9 +5,9 @@
 import heapq
 import sys
 
+
 # TODO:
 # * add an esquive manoeuvre / try to avoid cannonballs
-# * separate the main loop in two: planning, then acting
 # * consider targeting rum barrels if an ennemy is nearer
 # * compute first and second target instead of only one to anticipate the next move
 # * if an enemy is near a mine, shoot the mine instead of the ship
@@ -23,19 +23,99 @@ current_turn = 0
 class DidNotAct(Exception):
     pass
 
+class Queue():
+    def __init__(self):
+        self.items = []
+
+    def put(self, item, priority):
+        heapq.heappush(self.items, (priority, item))
+
+    def get(self):
+        return heapq.heappop(self.items)[1]
+
+    @classmethod
+    def merge(cls, *args, reverse=False):
+        q = cls()
+        q.items = list(heapq.merge(*[a.items for a in args], key=lambda x: x[1], reverse=reverse))
+        return q
+
+class InterestQueue(Queue):
+    def __add__(self, other):
+        self.items += other.items
+        return self
+    
+    def __bool__(self):
+        return bool(self.items)
+    
+    def put(self, item):
+        heapq.heappush(self.items, item)
+        
+    def get(self):
+        return heapq.heappop(self.items)
+    
+    @classmethod
+    def merge(cls, *args, reverse=False):
+        q = cls()
+        q.items = list(heapq.merge(*[a.items for a in args], reverse=reverse))
+        return q
+    
+class ObjectivesQueue(InterestQueue):
+    pass
+
+    
 class Base():
     def __repr__(self):
         return f"<{self.__class__.__name__}: {self.__dict__}>"
 
+class Objective(Base):
+    def __init__(self, ship, target):
+        self.ship = ship
+        self.target = target
+        self.interest = 0
 
-class Position(Base):
-    def __init__(self, x, y):
-        self.pos = (x, y)
+    def __lt__(self, other):
+        return self.interest < other.interest
 
-class ShootingSpot(Position):
-    def __init__(self, *args):
-        super().__init__(*args)
-        self.interest = 0
+    def __repr__(self):
+        return f"<{self.__class__.__name__}({self.target.id})>"
+
+    def update_interest(self, from_= None):
+        # the lower the better
+        if from_ is None:
+            from_ = self.ship.next_pos
+        self.interest = Grid.manhattan(from_, self.target.pos)
+
+class GetBarrel(Objective):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.distance = 0
+        self.alignment = 0
+        
+    def update_interest(self, from_= None, orientation=None):
+        if from_ is None:
+            from_ = self.ship.next_pos
+        if orientation is None:
+            orientation = self.ship.orientation
+        distance = Grid.manhattan(from_, self.target.pos)
+        alignment = abs(Grid.diff_directions(Grid.direction_to(*from_, *self.target.pos), orientation))
+        
+        self.interest = 6 * distance + 9 * alignment + 3 * self.target.dispersal + self.target.mine_threat ** 2 - 36 * self.target.ennemy_near
+
+class Attack(Objective):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.distance = 0
+        self.alignment = 0
+        
+    def update_interest(self, from_= None, orientation=None):
+        if from_ is None:
+            from_ = self.ship.next_pos
+        if orientation is None:
+            orientation = self.ship.orientation
+        distance = Grid.manhattan(from_, self.target.next_pos)
+        alignment = abs(Grid.diff_directions(Grid.direction_to(*from_, *self.target.next_pos), orientation))
+        
+        self.interest = 7 * distance + 3 * alignment - 20 * self.target.blocked_since - 10 * self.target.same_traject_since
 
 class PathNode(tuple):
     def __new__(self, x, y, parent=None):
@@ -129,77 +209,88 @@ class Grid(Base):
             wtotal += b.amount
         return (wx // wtotal, wy // wtotal) if wtotal else None
 
-    def pre_evaluate_barrels_interest(self):
+    def pre_eval_barrels(self):
         grav_center = self.barrels_gravity_center()
         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))
-            
-    def evaluate_barrels_interest(self, ship):
+            b.ennemy_near = any(b.pos in e.next_area for e in self.ennemy_ships)
+
+    def eval_objectives(self):
+        objectives = {GetBarrel: {}, Attack: {}}
+        
         for b in self.barrels:
-            b.distance = Grid.manhattan(ship.next_pos, b.pos)
-            b.alignement = abs(Grid.diff_directions(Grid.direction_to(*ship.prow, *b.pos), ship.orientation))
-            b.about_to_be_picked = any(b.pos in s.next_area for s in self.ennemy_ships)
-                    
-    def evaluate_ennemies_interest(self, ship):
-        for s in self.ennemy_ships:
-            s.distance = Grid.manhattan(ship.next_pos, s.next_pos)
-            s.alignement = abs(self.diff_directions(self.direction_to(*ship.prow, *s.next_pos), ship.orientation))
+            for s in self.owned_ships:
+                obj = GetBarrel(s, b)
+                obj.update_interest()
+                if not s in objectives[GetBarrel]:
+                    objectives[GetBarrel][s] = ObjectivesQueue()
+                objectives[GetBarrel][s].put(obj)
+                
+        for s in self.owned_ships:
+            objectives[Attack][s] = ObjectivesQueue()
+            for e in self.ennemy_ships:
+                obj = Attack(s, e)
+                obj.update_interest()
+                objectives[Attack][s].put(obj)
+                
+        return objectives
     
-    def pre_update_moving_costs(self):
-        self.moving_costs = {}
+    def update_moving_costs(self):
+        base_costs = {}
         
         for x in range(-1, self.w + 1):
             for y in range(-1, self.h + 1):
                 if x in (0, self.w) or y in (0, self.h):
-                    self.moving_costs[(x, y)] = 15 # borders are a little more expensive
+                    base_costs[(x, y)] = 15 # borders are a little more expensive
                 elif x in (-1, self.w + 1) or y in (-1, self.h + 1):
-                    self.moving_costs[(x, y)] = 1000 # out of the map
+                    base_costs[(x, y)] = 1000 # out of the map
                 else:
-                    self.moving_costs[(x, y)] = 10 # base moving cost
+                    base_costs[(x, y)] = 10 # base moving cost
         
         for m in self.mines:
             for n in self.neighbors(*m.pos):
-                self.moving_costs[n] += 30
+                base_costs[n] += 30
         for m in self.mines:
-            self.moving_costs[m.pos] += 1000
+            base_costs[m.pos] += 1000
         for c in self.cannonballs:
-            self.moving_costs[c.pos] += (100 + (5 - c.countdown) * 200)
+            base_costs[c.pos] += (100 + (5 - c.countdown) * 200)
         
-    def update_moving_costs(self, ship):
-        for s in self.ships:
-            if s is ship:
-                continue
-            dist = self.manhattan(ship.pos, s.pos)
-            if dist > 8:
-                continue
-            for c in self.neighbors(*s.pos):
-                self.moving_costs[c] += 100 * abs(3 - s.speed)
-            for c in self.zone(s.next_pos, 4):
-                self.moving_costs[c] += 20
-    
-    def shooting_spot(self, ship, targetted_ship):
-        self.shooting_spots = []
-        for x, y in self.zone(targetted_ship.next_pos, 10):
-            if self.moving_costs[(x, y)] > 10:
+        for ship in self.ships:
+            ship._moving_costs = base_costs
+            for other in self.ships:
+                if other is ship:
+                    continue
+                dist = self.manhattan(ship.pos, other.pos)
+                if dist > 8:
+                    continue
+                for c in self.neighbors(*other.pos):
+                    ship._moving_costs[c] += 100 * abs(3 - other.speed)
+                for c in self.zone(other.next_pos, 4):
+                    ship._moving_costs[c] += 20
+
+    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):
+            if ship.moving_cost(x, y) > 10:
                 continue
-            if self.manhattan((x, y), targetted_ship.next_pos) < 2:
+            if self.manhattan((x, y), target_pos) < 2:
                 continue
             
-            spot = ShootingSpot(x, y)
-            
-            spot.interest -= self.moving_costs[(x, y)]
+            interest = 0 # the lower the better
             
             # avoid cells too close from borders
             if not (3 <= x <= (self.w - 3) and 3 <= y < (self.h - 3)):
-                spot.interest -= 10
+                interest += 10
             
             # priorize spots at distance 5 from active ship
-            spot.interest += 10 * abs(5 - self.manhattan((x, y), ship.pos))
+            interest -= 10 * abs(5 - self.manhattan((x, y), ship.pos))
             
-            self.shooting_spots.append(spot)
+            shooting_spots.put((x, y), interest)
 
-        return max(self.shooting_spots, key= lambda x: x.interest)
+        return shooting_spots.get()
 
     # geometrical algorithms
     @staticmethod
@@ -306,17 +397,19 @@ class Grid(Base):
         return result
 
     # pathfinding
-    def path(self, origin, orient0, target, incl_start=False, limit=10000):
-        nodes = []
+    def path(self, origin, orientat0, target, moving_costs={}, incl_start=False, limit=10000):
+        nodes = Queue()
         break_on, iteration = limit, 0
         
+        
         origin = PathNode(*origin)
-        origin.orientation = orient0
-        heapq.heappush(nodes, (0, origin))
+        origin.orientation = orientat0
+        
+        nodes.put(origin, 0)
         neighbors = []
 
         while nodes:
-            current = heapq.heappop(nodes)[1]
+            current = nodes.get()
 
             if current == target:
                 path = []
@@ -338,7 +431,7 @@ class Grid(Base):
                 if break_on > 0 and iteration >= break_on:
                     return None
                 
-                moving_cost = self.moving_costs[x, y]
+                moving_cost = moving_costs[(x, y)]
                 if moving_cost >= 1000:
                     continue
                 
@@ -349,7 +442,7 @@ class Grid(Base):
                     continue
                     
                 cost = current.cost + moving_cost + diff * 10
-                if diff != 0 and any(self.moving_costs[c] >= 1000 for c in neighbors):
+                if diff != 0 and any(moving_costs[c] >= 1000 for c in neighbors):
                     # a direction change here is dangerous
                     cost += 50
                 
@@ -358,7 +451,7 @@ class Grid(Base):
                 node = PathNode(x, y, current)
                 node.cost = cost
                 node.orientation = d
-                heapq.heappush(nodes, (priority, node))
+                nodes.put(node, priority)
         else:
             return None
 
@@ -375,6 +468,11 @@ class Entity(Base):
     def pos(self):
         return (self.x, self.y)
     
+    def __lt__(self, other):
+        # default comparison, used to avoid errors when used with queues and priorities are equals
+        return self.id < other.id
+    
+    
 class Ship(Entity):
     MAX_SPEED = 2
     SCOPE = 10
@@ -394,7 +492,11 @@ class Ship(Entity):
         self.blocked_since = 0
         self.same_traject_since = 0
         self.last_action = ""
+        self._moving_costs = {}
+        
         self.target = None
+        self.second_target = None
+        self.path = []
         
         self.distance = 0
         self.alignment = 0
@@ -409,7 +511,10 @@ class Ship(Entity):
         super().update(x, y)
         self.orientation, self.speed, self.stock, self.owned = map(int, args)
 
-        self.target = None
+        self.objective = None
+        self.objective_next = None
+        self.objective_fallback = None
+        self.path = []
         
         self.area = Ship.get_area(self.x, self.y, self.orientation)
         self.prow, _, self.stern = self.area
@@ -464,15 +569,9 @@ class Ship(Entity):
     def in_current_direction(self, x, y):
         return self.orientation == Grid.direction_to(*self.pos, x, y)
 
-    @property
-    def interest(self):
-        return 7 * self.distance + 3 * self.alignment - 20 * self.blocked_since - 10 * self.same_traject_since
+    def moving_cost(self, x, y):
+        return self._moving_costs[(x, y)]
     
-    def acquire(self, target):
-        self.target = target
-        if type(target) is Barrel:
-            target.aimed = True
-
     def move(self, *args, **kwargs):
         try:
             self._move(*args, **kwargs)
@@ -480,7 +579,7 @@ class Ship(Entity):
         except DidNotAct:
             return False
 
-    def _move(self, path, avoid=[]):
+    def _move(self, path):
         
         if path is None:
             log(f"(!) broken: automove to {goto}")
@@ -492,7 +591,7 @@ class Ship(Entity):
         # <--- special: avoid blocking situations
         if current_turn > 1 and self.blocked_since >= 1:
             dx, dy = Grid.directions(self.y)[((self.orientation + 1) % 6)]
-            if grid.moving_costs[self.x + dx, self.y + dy] <= 50:
+            if self.moving_cost(self.x + dx, self.y + dy) <= 50:
                 self.turn_left()
             else:
                 self.turn_right()
@@ -627,12 +726,10 @@ class Barrel(Entity):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.amount = 0
-        self.distance = 0
+
         self.dispersal = 0
-        self.alignement = False
-        self.mine_threat = 0
-        self.about_to_be_picked = False
-        self.aimed = False
+        self.mine_threat = False
+        self.ennemy_near = False
 
     def __repr__(self):
         return f"<Barrel {self.id}: pos=({self.x}, {self.y}), amount={self.amount}>"
@@ -640,16 +737,6 @@ class Barrel(Entity):
     def update(self, x, y, *args):
         super().update(x, y)
         self.amount = int(args[0])
-#         self.aimed = False
-        
-    @property
-    def interest(self):
-        # the lower the better
-        return 7 * self.distance \
-               + 3 * self.dispersal \
-               + self.mine_threat ** 2 \
-               + 7 * self.alignement \
-               - 100 * self.about_to_be_picked
 
 class Mine(Entity):
     def __init__(self, *args, **kwargs):
@@ -666,33 +753,6 @@ class Cannonball(Entity):
         self.sender, self.countdown = int(args[0]), int(args[1])
 
 
-
-class Action(Base):
-    def __init__(self, *args):
-        self.args = args
-        
-    def resolve(self):
-        raise NotImplementedError
-
-class Move(Action):
-    pass
-
-class Fire(Action):
-    pass
-
-class TurnLeft(Action):
-    pass
-
-class TurnRight(Action):
-    pass
-
-class SpeedUp(Action):
-    pass
-
-class SlowDown(Action):
-    pass
-
-
 entities = {}
 map_entity = {"SHIP": Ship, 
              "BARREL": Barrel,
@@ -707,94 +767,110 @@ while True:
     current_turn += 1
     
     # <--- get input
-    if 1:
-        my_ship_count, entity_count = int(input()), int(input())
-        for _ in range(entity_count):
-            ent_id, ent_type, *data = input().split()
-            ent_id = int(ent_id)
-            seen.append(ent_id)
+    my_ship_count, entity_count = int(input()), int(input())
+    for _ in range(entity_count):
+        ent_id, ent_type, *data = input().split()
+        ent_id = int(ent_id)
+        seen.append(ent_id)
+           
+        if not ent_id in entities:
+            entities[ent_id] = map_entity[ent_type](ent_id)
                
-            if not ent_id in entities:
-                entities[ent_id] = map_entity[ent_type](ent_id)
-                   
-            ent = entities[ent_id]
-            ent.update(*data)
-         
-        entities = {k: v for k, v in entities.items() if k in seen}
-    # --->
-    # <--- test input
-    else:
-        ship = Ship(0)
-        ship.update(3,3,0,1,0,1)
-        ennemy = Ship(1)
-        ennemy.update(1,1,0,1,0,0)
-        barrel1 = Barrel(10)
-        barrel1.update(8,2,40,0,0,0)
-        barrel2 = Barrel(11)
-        barrel2.update(4,2,50,0,0,0)
-        mine = Mine(20)
-        mine.update(10, 2)
-        entities = {0: ship, 1: ennemy, 20: mine}
-#         entities = {0: ship, 1: ennemy, 10: barrel1, 11: barrel2, 20: mine}
-        seen = [0, 1]
+        ent = entities[ent_id]
+        ent.update(*data)
+     
+    entities = {k: v for k, v in entities.items() if k in seen}
     # --->
     
-    # log(entities)
     grid.load_entities(entities)
     
-    log(f"### turn {current_turn}")
+    log(f"### TURN {current_turn}")
+    
 #     log(f"Owned Ships: {grid.owned_ships}")
     log(f"Ennemy Ships: {grid.ennemy_ships}")
-    log(f"Barrels: {grid.barrels}")
+#     log(f"Barrels: {grid.barrels}")
 #     log(f"Mines: {grid.mines}")
     log(f"Cannonballs: {grid.cannonballs}")
 
-    grid.pre_update_moving_costs()
-    grid.pre_evaluate_barrels_interest()
 
-    for ship in grid.owned_ships:
-        log(f"---- ship {ship.id} ---")
-        log(f"ship: {ship}")
-        grid.update_moving_costs(ship)
-        allies = [s for s in grid.owned_ships if s is not ship]
-        
-        target = None
-        
-        if grid.barrels:
-            grid.evaluate_barrels_interest(ship)
-            log("barrels interest: {}".format({b.pos: f"{b.interest} ({b.distance}/{b.dispersal}/{b.mine_threat}/{b.alignement})" for b in grid.barrels}))
-        if grid.ennemy_ships:
-            grid.evaluate_ennemies_interest(ship)
-            log("ennemies interest: {}".format({s.pos: f"{s.interest} ({s.distance}/{s.alignement}/{s.blocked_since}/{s.same_traject_since})" for s in grid.ennemy_ships}))
+    ### Evaluate
+    grid.pre_eval_barrels()
+    
+    objectives = grid.eval_objectives()
+    
+    grid.update_moving_costs()
+    
+    ### Acquire
+    log("# Acquiring")
+    
+    if objectives[GetBarrel]:
+        # objectives are shared between allies by priority
+        merged_objs = ObjectivesQueue.merge(*[obj_lst for ship, obj_lst in objectives[GetBarrel].items() if ship.owned])
+        aimed = []
+        
+        # first objective
+        obj = merged_objs.get()
+        while not all(s.objective for s in grid.owned_ships):
+            if not obj.ship.objective and not obj.target in aimed:
+                obj.ship.objective = obj
+                aimed.append(obj.target)
+            if not merged_objs:
+                break
+            obj = merged_objs.get()
+            
+        del merged_objs, aimed
 
-        allies_targets = [a.target for a in allies]
-        
-        targetted_barrel = next((b for b in sorted(grid.barrels, key=lambda x: x.interest) if not b in allies_targets and not b.pos in ship.next_area), None)
-        targetted_ennemy = next((s for s in sorted(grid.ennemy_ships, key=lambda x: x.interest)), None)
+    if objectives[Attack]:
+        for ship in grid.owned_ships:
+            ship.objective_fallback = objectives[Attack][ship].get()
+    else:
+        log("ERROR: no objectives")
     
-        if not targetted_barrel and not targetted_ennemy:
-            log("(!) No target, wait")
-            ship.wait()
-            continue
+    for ship in grid.owned_ships:
+        log(f"Ship {ship.id}: obj: {ship.objective}; next: {ship.objective_next}; fallback: {ship.objective_fallback}")
     
-        target = targetted_barrel or targetted_ennemy
+    ### Plan
+    log("# Planning")
     
-        log(f"target ({target.__class__.__name__}): {target}")
-        ship.acquire(target)
+    for ship in grid.owned_ships:
         
-        if type(target) is Ship:
-            goto = grid.shooting_spot(ship, target).pos
+        log(f"---- ship {ship.id} ---")
+        log(f"ship: {ship}")
+        
+        if ship.objective:
+            goto = ship.objective.target.pos
+        elif ship.objective_fallback:
+            goto = grid.shooting_spot(ship, ship.objective_fallback.target)
         else:
-            goto = target.pos
+            log("ERROR: No target")
+            continue
+        
         log(f"goto: {goto}")
         
-        path = grid.path(ship.next_pos, ship.orientation, goto, limit=6000 // len(grid.owned_ships))
-        log(f"path: {path}")
+        ship.path = grid.path(ship.next_pos, ship.orientation, goto, ship._moving_costs, limit=6000 // len(grid.owned_ships))
+        
+        if ship.objective_next and ship.path:
+            ship.path += grid.path(goto, 
+                                   ship.path[-1].orientation, 
+                                   ship.objective_next.target.pos, 
+                                   ship._moving_costs,
+                                   limit=6000 // len(grid.owned_ships)) or []
+        
+        log(f"path: {ship.path}")
         
-        if ship.move(path):
+    ### Process
+    log("# Processing")
+    
+    for ship in grid.owned_ships:
+        if not ship.objective and not ship.objective_fallback:
+            log("No target: wait")
+            ship.wait()
+        
+        if ship.move(ship.path):
             continue
-            
-        if ship.fire_at_will(targetted_ennemy, allies=grid.owned_ships):
+        
+        # no movement was required, can fire
+        if ship.fire_at_will(ship.objective_fallback.target, allies=grid.owned_ships):
             continue
         
         log("ERROR: Did not act, wait")