|
|
@@ -2,22 +2,123 @@
|
|
|
>> https://www.codingame.com/ide/173171838252e7c6fd6f3ff9cb8169431a08eec1
|
|
|
@author: olivier.massot, may 2019
|
|
|
'''
|
|
|
+import heapq
|
|
|
import sys
|
|
|
import time
|
|
|
|
|
|
-
|
|
|
debug = True
|
|
|
-
|
|
|
t0 = time.time()
|
|
|
|
|
|
def log(*msg):
|
|
|
if debug:
|
|
|
print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
|
|
|
|
|
|
+# OWNER
|
|
|
+ME = 0
|
|
|
+OPPONENT = 1
|
|
|
+
|
|
|
+# BUILDING TYPE
|
|
|
+HQ = 0
|
|
|
+
|
|
|
class Base():
|
|
|
def __repr__(self):
|
|
|
return f"<{self.__class__.__name__}: {self.__dict__}>"
|
|
|
|
|
|
+class Queue(Base):
|
|
|
+ def __init__(self):
|
|
|
+ self.items = []
|
|
|
+
|
|
|
+ def __bool__(self):
|
|
|
+ return bool(self.items)
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return str(self.items)
|
|
|
+
|
|
|
+ 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]
|
|
|
+
|
|
|
+class InterestQueue(Queue):
|
|
|
+ def __add__(self, other):
|
|
|
+ self.items += other.items
|
|
|
+ return self
|
|
|
+
|
|
|
+ def put(self, item):
|
|
|
+ heapq.heappush(self.items, item)
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ return heapq.heappop(self.items)
|
|
|
+
|
|
|
+class Action(Base):
|
|
|
+ def __init__(self, target, *args, **kwargs):
|
|
|
+ self.interest = 0
|
|
|
+ self.target = target
|
|
|
+ self.x, self.y = target.pos
|
|
|
+ self.eval(target, *args, **kwargs)
|
|
|
+
|
|
|
+ def __lt__(self, other):
|
|
|
+ return self.interest < other.interest
|
|
|
+
|
|
|
+ def eval(self):
|
|
|
+ raise NotImplementedError
|
|
|
+
|
|
|
+class Move(Action):
|
|
|
+ def eval(self, target, unit):
|
|
|
+ # the lower the better
|
|
|
+ self.interest = 0
|
|
|
+ if target.owned and target.active:
|
|
|
+ self.interest += 15 # already owned and active
|
|
|
+
|
|
|
+ elif target.opponents and target.active:
|
|
|
+ self.interest -= 15 # owned by opponents and active
|
|
|
+
|
|
|
+ # non-passable cells around
|
|
|
+ self.interest += 3 * len([n for n in target.neighbors if not grid[n].movable])
|
|
|
+
|
|
|
+ # an ennemy here
|
|
|
+ if target.unit and target.unit.opponents:
|
|
|
+ self.interest -= 20
|
|
|
+
|
|
|
+ # an ennemy nearby
|
|
|
+ if any((grid[n].unit and grid[n].unit.opponents) for n in target.neighbors):
|
|
|
+ self.interest += 15
|
|
|
+
|
|
|
+ # priorize adjacent cells
|
|
|
+ self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
|
|
|
+
|
|
|
+ # include 'heatmap'
|
|
|
+ self.interest += 3 * target.heat
|
|
|
+
|
|
|
+class Train(Move):
|
|
|
+ def eval(self, target):
|
|
|
+ # the lower the better
|
|
|
+ self.interest = 0
|
|
|
+ if target.owned and target.active:
|
|
|
+ self.interest += 15 # already owned and active
|
|
|
+
|
|
|
+ elif target.opponents and target.active:
|
|
|
+ self.interest -= 15 # owned by opponents and active
|
|
|
+
|
|
|
+ # non-passable cells around
|
|
|
+ self.interest += 3 * len([n for n in target.neighbors if not grid[n].movable])
|
|
|
+
|
|
|
+ # an ennemy nearby
|
|
|
+ if any((grid[n].unit and grid[n].unit.opponents) for n in target.neighbors):
|
|
|
+ self.interest += 15
|
|
|
+
|
|
|
+ # priorize adjacent cells
|
|
|
+ self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
|
|
|
+
|
|
|
+ # include 'heatmap'
|
|
|
+ self.interest += 3 * target.heat
|
|
|
+
|
|
|
class BaseLoc(Base):
|
|
|
def __init__(self, x, y):
|
|
|
self.x = x
|
|
|
@@ -38,20 +139,29 @@ class BaseOwnedLoc(BaseLoc):
|
|
|
|
|
|
@property
|
|
|
def owned(self):
|
|
|
- return self.owner == 0
|
|
|
+ return self.owner == ME
|
|
|
+
|
|
|
+ @property
|
|
|
+ def opponents(self):
|
|
|
+ return self.owner == OPPONENT
|
|
|
|
|
|
class Building(BaseOwnedLoc):
|
|
|
+ HQ = 0
|
|
|
+
|
|
|
def __init__(self, owner, type_, x, y):
|
|
|
super().__init__(x, y, owner)
|
|
|
self.type_ = type_
|
|
|
|
|
|
+ @property
|
|
|
+ def hq(self):
|
|
|
+ return self.type_ == Building.HQ
|
|
|
+
|
|
|
class Unit(BaseOwnedLoc):
|
|
|
def __init__(self, owner, id_, level, x, y):
|
|
|
super().__init__(x, y, owner)
|
|
|
self.id_ = id_
|
|
|
self.level = level
|
|
|
|
|
|
-
|
|
|
class Player(Base):
|
|
|
def __init__(self, id_):
|
|
|
self.id_ = id_
|
|
|
@@ -66,15 +176,19 @@ class Player(Base):
|
|
|
self.income = income
|
|
|
self.units = [u for u in units if u.owner == self.id_]
|
|
|
self.buildings = [b for b in buildings if b.owner == self.id_]
|
|
|
+ self.hq = next((b for b in self.buildings if b.type_ == HQ))
|
|
|
|
|
|
class Cell(Base):
|
|
|
def __init__(self, x, y):
|
|
|
self.x = x
|
|
|
self.y = y
|
|
|
self._content = "#"
|
|
|
+ self.neighbors = []
|
|
|
self.unit = None
|
|
|
self.building = None
|
|
|
|
|
|
+ self.heat = 0
|
|
|
+
|
|
|
@property
|
|
|
def pos(self):
|
|
|
return self.x, self.y
|
|
|
@@ -88,6 +202,8 @@ class Cell(Base):
|
|
|
self.unit = unit
|
|
|
self.building = building
|
|
|
|
|
|
+ self.heat = 0
|
|
|
+
|
|
|
@property
|
|
|
def movable(self):
|
|
|
return self._content != "#"
|
|
|
@@ -109,15 +225,20 @@ class Cell(Base):
|
|
|
return self.unit or self.building
|
|
|
|
|
|
@property
|
|
|
- def is_active(self):
|
|
|
+ def active(self):
|
|
|
return self._content.isupper()
|
|
|
|
|
|
+
|
|
|
class Grid(Base):
|
|
|
dim = 12
|
|
|
hqs = [(0,0), (11,11)]
|
|
|
|
|
|
def __init__(self, mines = []):
|
|
|
+
|
|
|
self.cells = {(x, y): Cell(x, y) for x in range(Grid.dim) for y in range(Grid.dim)}
|
|
|
+ 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.mines = {(m.x, m.y): m for m in mines}
|
|
|
@@ -146,61 +267,162 @@ class Grid(Base):
|
|
|
self.units.get((x, y), None),
|
|
|
self.buildings.get((x, y), None))
|
|
|
|
|
|
- def neighbors(self, x, y, diags=True):
|
|
|
+ self.update_heat_map()
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def manhattan(from_, to_):
|
|
|
+ xa, ya = from_
|
|
|
+ xb, yb = to_
|
|
|
+ return abs(xa - xb) + abs(ya - yb)
|
|
|
+
|
|
|
+ def neighbors(self, x, y, diags=False):
|
|
|
neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
|
|
|
if diags:
|
|
|
neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
|
|
|
return [(x, y) for x, y in neighs if 0 <= x < Grid.dim and 0 <= y < Grid.dim]
|
|
|
|
|
|
- def where_to_train(self):
|
|
|
- available = set()
|
|
|
- for p, c in self.cells.items():
|
|
|
- if c.owned:
|
|
|
- available.add(p)
|
|
|
- available |= set(self.neighbors(*p))
|
|
|
- return (self.cells[p] for p in available if not self.cells[p].occupied)
|
|
|
+ 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):
|
|
|
+
|
|
|
+ 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)
|
|
|
+
|
|
|
+ buffer = frontier
|
|
|
+ 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
|
|
|
+ next_buffer.append(n)
|
|
|
+
|
|
|
+ buffer = list(next_buffer)
|
|
|
+ next_buffer = []
|
|
|
+
|
|
|
+ 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 not self.cells[p].occupied)
|
|
|
+
|
|
|
+ def get_next_training(self):
|
|
|
+ q = InterestQueue()
|
|
|
+ for cell in self.training_places():
|
|
|
+ q.put(Train(cell))
|
|
|
+ if not q:
|
|
|
+ return None
|
|
|
+ return q.get()
|
|
|
+
|
|
|
+ def moving_zone(self, unit):
|
|
|
+ return (self.cells[p] for p in self.cells[unit.pos].neighbors
|
|
|
+ if self.cells[p].movable and
|
|
|
+ (not self.cells[p].building or self.cells[p].building.opponents) and
|
|
|
+ (not self.cells[p].unit or self.cells[p].unit.opponents))
|
|
|
+
|
|
|
+ def get_next_move(self, unit):
|
|
|
+ q = InterestQueue()
|
|
|
+ for cell in self.moving_zone(unit):
|
|
|
+ q.put(Move(cell, unit))
|
|
|
+ if not q:
|
|
|
+ return None
|
|
|
+ return q.get()
|
|
|
+
|
|
|
+
|
|
|
+# ******** MAIN *************
|
|
|
|
|
|
-mines = [Mine(*[int(j) for j in input().split()]) for _ in range(int(input()))]
|
|
|
+test = False
|
|
|
+
|
|
|
+if test:
|
|
|
+ mines_input = []
|
|
|
+else:
|
|
|
+ mines_input = [input() for _ in range(int(input()))]
|
|
|
+log(mines_input)
|
|
|
+
|
|
|
+mines = [Mine(*[int(j) for j in item.split()]) for item in mines_input]
|
|
|
log(f"* mines: {mines}")
|
|
|
|
|
|
grid = Grid()
|
|
|
-player = Player(0)
|
|
|
-opponent = Player(1)
|
|
|
+player = Player(ME)
|
|
|
+opponent = Player(OPPONENT)
|
|
|
+
|
|
|
+def cmd_train(target, level=1):
|
|
|
+ return f"TRAIN {level} {target.x} {target.y}"
|
|
|
+
|
|
|
+def cmd_move(unit, target):
|
|
|
+ return f"MOVE {unit.id_} {target.x} {target.y}"
|
|
|
+
|
|
|
+def cmd_wait():
|
|
|
+ return "WAIT"
|
|
|
+
|
|
|
+def get_input():
|
|
|
+ if test:
|
|
|
+ gold, income = 20, 1
|
|
|
+ opponent_gold, opponent_income = 20, 1
|
|
|
+ new_grid_input = ['O..#########', '...###...###', '...###....##', '#..##.....##', '...#......##', '#.........##', '##.........#', '##......#...', '##.....##..#', '##....###...', '###...###...', '#########..X']
|
|
|
+ buildings_input = ['0 0 0 0', '1 0 11 11']
|
|
|
+ units_input = []
|
|
|
+
|
|
|
+ else:
|
|
|
+ gold, income = int(input()), int(input())
|
|
|
+ opponent_gold, opponent_income = int(input()), int(input())
|
|
|
+ new_grid_input = [input() for _ in range(12)]
|
|
|
+ buildings_input = [input() for _ in range(int(input()))]
|
|
|
+ units_input = [input() for _ in range(int(input()))]
|
|
|
+
|
|
|
+# log(gold, income, opponent_gold, opponent_income)
|
|
|
+# log(new_grid_input)
|
|
|
+# log(buildings_input)
|
|
|
+# log(units_input)
|
|
|
+
|
|
|
+ return gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input
|
|
|
+
|
|
|
|
|
|
while True:
|
|
|
|
|
|
- # <--- get input
|
|
|
- gold, income = int(input()), int(input())
|
|
|
- opponent_gold, opponent_income = int(input()), int(input())
|
|
|
- new_grid = [list(input()) for _ in range(12)]
|
|
|
+ # <--- get and parse input
|
|
|
+ gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input = get_input()
|
|
|
+
|
|
|
+ new_grid = [list(row) for row in new_grid_input]
|
|
|
|
|
|
- buildings = [Building(*[int(j) for j in input().split()]) for _ in range(int(input()))]
|
|
|
+ buildings = [Building(*[int(j) for j in item.split()]) for item in buildings_input]
|
|
|
log(f"* buildings: {buildings}")
|
|
|
-
|
|
|
- units = [Unit(*[int(j) for j in input().split()]) for _ in range(int(input()))]
|
|
|
+
|
|
|
+ units = [Unit(*[int(j) for j in item.split()]) for item in units_input]
|
|
|
log(f"* units: {units}")
|
|
|
# --->
|
|
|
|
|
|
- # <--- update data
|
|
|
+ # <--- update
|
|
|
grid.update(new_grid, buildings, units)
|
|
|
- log(f"grid:\n{grid.print_grid()}")
|
|
|
+# log(f"grid:\n{grid.print_grid()}")
|
|
|
|
|
|
player.update(gold, income, units, buildings)
|
|
|
- if player.hq is None:
|
|
|
- player.hq = next((c for c in [grid[(x, y)] for x, y in Grid.hqs] if c.owned))
|
|
|
- log(f"player:\n{player}")
|
|
|
+ log(f"player: {player}")
|
|
|
|
|
|
opponent.update(opponent_gold, opponent_income, units, buildings)
|
|
|
- if opponent.hq is None:
|
|
|
- opponent.hq = next((c for c in [grid[(x, y)] for x, y in Grid.hqs] if c.opponents))
|
|
|
- log(f"opponent:\n{opponent}")
|
|
|
+ log(f"opponent: {opponent}")
|
|
|
# --->
|
|
|
|
|
|
- # start
|
|
|
+ commands = []
|
|
|
|
|
|
- if not player.units:
|
|
|
- c = next(grid.where_to_train())
|
|
|
- print(f"TRAIN 1 {c.x} {c.y}")
|
|
|
+ # start
|
|
|
+ if not player.units or (player.gold > 20 and player.income > (5 * len(player.units))):
|
|
|
+ target = grid.get_next_training()
|
|
|
+ if target:
|
|
|
+ commands.append(cmd_train(target))
|
|
|
|
|
|
+ for unit in player.units:
|
|
|
+ target = grid.get_next_move(unit)
|
|
|
+ if target:
|
|
|
+ commands.append(cmd_move(unit, target))
|
|
|
|
|
|
- print("WAIT")
|
|
|
+ if not commands:
|
|
|
+ log("nothing to do: wait")
|
|
|
+ commands = ["WAIT"]
|
|
|
+ log(commands)
|
|
|
+ print(";".join(commands))
|