|
|
@@ -6,6 +6,15 @@ import heapq
|
|
|
import sys
|
|
|
import time
|
|
|
|
|
|
+# TODO
|
|
|
+# * always priorize the attack if level allows it
|
|
|
+# * take levels in account
|
|
|
+# * priorize the attack of the ennemy hq
|
|
|
+# x build lvl2 and 3 units
|
|
|
+# * priorize move on mines
|
|
|
+# * priorize move on 'pivot-cells' which would cause desactivation of others
|
|
|
+# * don't let ennemies inside the defense (=> nearer of the QG than other units)
|
|
|
+
|
|
|
debug = True
|
|
|
t0 = time.time()
|
|
|
|
|
|
@@ -61,7 +70,8 @@ class Action(Base):
|
|
|
self.interest = 0
|
|
|
self.target = target
|
|
|
self.x, self.y = target.pos
|
|
|
- self.eval(target, *args, **kwargs)
|
|
|
+
|
|
|
+ self.eval()
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
return self.interest < other.interest
|
|
|
@@ -70,54 +80,98 @@ class Action(Base):
|
|
|
raise NotImplementedError
|
|
|
|
|
|
class Move(Action):
|
|
|
- def eval(self, target, unit):
|
|
|
+ def __init__(self, target, unit):
|
|
|
+ self.unit = unit
|
|
|
+ super().__init__(target, unit)
|
|
|
+
|
|
|
+ def eval(self):
|
|
|
# the lower the better
|
|
|
+
|
|
|
self.interest = 0
|
|
|
- if target.owned and target.active:
|
|
|
+ if self.target.owned and self.target.active:
|
|
|
self.interest += 15 # already owned and active
|
|
|
|
|
|
- elif target.opponents and target.active:
|
|
|
+ elif self.target.opponents and self.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])
|
|
|
+ self.interest += 3 * len([n for n in self.target.neighbors if not grid[n].movable])
|
|
|
|
|
|
# an ennemy here
|
|
|
- if target.unit and target.unit.opponents:
|
|
|
- self.interest -= 20
|
|
|
+ if self.target.unit and self.target.unit.opponents:
|
|
|
+ if self.target.unit.level < self.unit.level or self.unit.level == 3:
|
|
|
+ self.interest -= 30
|
|
|
|
|
|
# an ennemy nearby
|
|
|
- if any((grid[n].unit and grid[n].unit.opponents) for n in target.neighbors):
|
|
|
- self.interest += 15
|
|
|
+ for n in self.target.neighbors:
|
|
|
+ if grid[n].unit and grid[n].unit.opponents and grid[n].unit.level >= self.unit.level:
|
|
|
+ self.interest += 15
|
|
|
|
|
|
# priorize adjacent cells
|
|
|
- self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
|
|
|
+ self.interest -= (2 * len([n for n in self.target.neighbors if grid[n].owned]))
|
|
|
|
|
|
# include 'heatmap'
|
|
|
- self.interest += 3 * target.heat
|
|
|
+ self.interest += 3 * self.target.heat
|
|
|
+
|
|
|
+ # priorize mines or HQ
|
|
|
+ if self.target.building:
|
|
|
+ if self.target.building.type_ == Building.HQ:
|
|
|
+ self.interest -= 50
|
|
|
+ if self.target.building.type_ == Building.MINE:
|
|
|
+ self.interest -= 15
|
|
|
+ if self.target.building.type_ == Building.TOWER and self.unit.level == 3:
|
|
|
+ self.interest -= 15
|
|
|
+
|
|
|
+
|
|
|
+ def resolution(self):
|
|
|
+ return f"MOVE {self.unit.id_} {self.target.x} {self.target.y}"
|
|
|
|
|
|
class Train(Move):
|
|
|
- def eval(self, target):
|
|
|
+ def __init__(self, target, level=1):
|
|
|
+ self.level = level
|
|
|
+ super().__init__(target, level)
|
|
|
+
|
|
|
+ def eval(self):
|
|
|
# the lower the better
|
|
|
self.interest = 0
|
|
|
- if target.owned and target.active:
|
|
|
+ if self.target.owned and self.target.active:
|
|
|
self.interest += 15 # already owned and active
|
|
|
|
|
|
- elif target.opponents and target.active:
|
|
|
+ elif self.target.opponents and self.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
|
|
|
+ self.interest += 2 * len([n for n in self.target.neighbors if not grid[n].movable])
|
|
|
|
|
|
# priorize adjacent cells
|
|
|
- self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
|
|
|
+ self.interest -= (2 * len([n for n in self.target.neighbors if grid[n].owned]))
|
|
|
|
|
|
# include 'heatmap'
|
|
|
- self.interest += 3 * target.heat
|
|
|
+ self.interest += 3 * self.target.heat
|
|
|
+
|
|
|
+ # needs reinforcement?
|
|
|
+ for n in self.target.neighbors:
|
|
|
+ if grid[n].unit and grid[n].unit.owned:
|
|
|
+ self.interest += 5
|
|
|
+ if grid[n].unit and grid[n].unit.opponents:
|
|
|
+ self.interest -= 5
|
|
|
+
|
|
|
+ def resolution(self):
|
|
|
+ return f"TRAIN {self.level} {self.target.x} {self.target.y}"
|
|
|
+
|
|
|
+class Build(Action):
|
|
|
+ def __init__(self, target, type_):
|
|
|
+ self.type_ = type_
|
|
|
+ super().__init__(target, type_)
|
|
|
+
|
|
|
+ def eval(self):
|
|
|
+ # the lower the better
|
|
|
+ self.interest = 0
|
|
|
+ self.interest -= self.target.heat
|
|
|
+
|
|
|
+ def resolution(self):
|
|
|
+ str_type = {1: "MINE", 2: "TOWER"}[self.type_]
|
|
|
+ return f"BUILD {str_type} {self.target.x} {self.target.y}"
|
|
|
|
|
|
class BaseLoc(Base):
|
|
|
def __init__(self, x, y):
|
|
|
@@ -128,7 +182,7 @@ class BaseLoc(Base):
|
|
|
def pos(self):
|
|
|
return self.x, self.y
|
|
|
|
|
|
-class Mine(BaseLoc):
|
|
|
+class MineSite(BaseLoc):
|
|
|
def __init__(self, x, y):
|
|
|
super().__init__(x, y)
|
|
|
|
|
|
@@ -147,6 +201,8 @@ class BaseOwnedLoc(BaseLoc):
|
|
|
|
|
|
class Building(BaseOwnedLoc):
|
|
|
HQ = 0
|
|
|
+ MINE = 1
|
|
|
+ TOWER = 2
|
|
|
|
|
|
def __init__(self, owner, type_, x, y):
|
|
|
super().__init__(x, y, owner)
|
|
|
@@ -157,6 +213,8 @@ class Building(BaseOwnedLoc):
|
|
|
return self.type_ == Building.HQ
|
|
|
|
|
|
class Unit(BaseOwnedLoc):
|
|
|
+ training_cost = {1: 10, 2: 20, 3: 30}
|
|
|
+ maintenance = {1: 1, 2: 4, 3: 20}
|
|
|
def __init__(self, owner, id_, level, x, y):
|
|
|
super().__init__(x, y, owner)
|
|
|
self.id_ = id_
|
|
|
@@ -186,6 +244,7 @@ class Cell(Base):
|
|
|
self.neighbors = []
|
|
|
self.unit = None
|
|
|
self.building = None
|
|
|
+ self.mine_site = None
|
|
|
|
|
|
self.heat = 0
|
|
|
|
|
|
@@ -233,7 +292,7 @@ class Grid(Base):
|
|
|
dim = 12
|
|
|
hqs = [(0,0), (11,11)]
|
|
|
|
|
|
- def __init__(self, mines = []):
|
|
|
+ def __init__(self, mines_sites = []):
|
|
|
|
|
|
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():
|
|
|
@@ -241,7 +300,9 @@ class Grid(Base):
|
|
|
|
|
|
self.units = {}
|
|
|
self.buildings = {}
|
|
|
- self.mines = {(m.x, m.y): m for m in mines}
|
|
|
+ for m in mines_sites:
|
|
|
+ self.cells[(m.x, m.y)].mine_site = m
|
|
|
+ self.reservation = []
|
|
|
|
|
|
def print_grid(self):
|
|
|
return "\n".join(["".join([c for c in row]) for row in self.grid])
|
|
|
@@ -267,6 +328,7 @@ class Grid(Base):
|
|
|
self.units.get((x, y), None),
|
|
|
self.buildings.get((x, y), None))
|
|
|
|
|
|
+ self.reservation = []
|
|
|
self.update_heat_map()
|
|
|
|
|
|
@staticmethod
|
|
|
@@ -309,19 +371,28 @@ class Grid(Base):
|
|
|
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)
|
|
|
+ return (self.cells[p] for p in (owned | neighbors) if self.cells[p].movable and not self.cells[p].occupied and not p in self.reservation)
|
|
|
|
|
|
- def get_next_training(self):
|
|
|
+ def get_next_training(self, max_level=3):
|
|
|
q = InterestQueue()
|
|
|
for cell in self.training_places():
|
|
|
q.put(Train(cell))
|
|
|
if not q:
|
|
|
return None
|
|
|
- return q.get()
|
|
|
+ action = q.get()
|
|
|
+
|
|
|
+ level = 1
|
|
|
+ for ennemy in opponent.units:
|
|
|
+ if Grid.manhattan(action.target.pos, ennemy.pos) < 4:
|
|
|
+ level = min(ennemy.level + 1, max_level)
|
|
|
+ break
|
|
|
+ action.level = level
|
|
|
+
|
|
|
+ return action
|
|
|
|
|
|
def moving_zone(self, unit):
|
|
|
return (self.cells[p] for p in self.cells[unit.pos].neighbors
|
|
|
- if self.cells[p].movable and
|
|
|
+ if self.cells[p].movable and not p in self.reservation and
|
|
|
(not self.cells[p].building or self.cells[p].building.opponents) and
|
|
|
(not self.cells[p].unit or self.cells[p].unit.opponents))
|
|
|
|
|
|
@@ -332,6 +403,20 @@ class Grid(Base):
|
|
|
if not q:
|
|
|
return None
|
|
|
return q.get()
|
|
|
+
|
|
|
+ def building_zone(self, type_):
|
|
|
+ if type_ == Building.MINE:
|
|
|
+ return [cell for cell in self.cells.values() if cell.mine_site and cell.heat > 3]
|
|
|
+ else:
|
|
|
+ return []
|
|
|
+
|
|
|
+ def get_building_site(self, type_):
|
|
|
+ q = InterestQueue()
|
|
|
+ for cell in self.building_zone(type_):
|
|
|
+ q.put(Build(cell, type_))
|
|
|
+ if not q:
|
|
|
+ return None
|
|
|
+ return q.get()
|
|
|
|
|
|
|
|
|
# ******** MAIN *************
|
|
|
@@ -344,18 +429,13 @@ 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}")
|
|
|
+mines_sites = [MineSite(*[int(j) for j in item.split()]) for item in mines_input]
|
|
|
+# log(f"* mines: {mines_sites}")
|
|
|
|
|
|
-grid = Grid()
|
|
|
+grid = Grid(mines_sites)
|
|
|
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"
|
|
|
@@ -391,10 +471,10 @@ while True:
|
|
|
new_grid = [list(row) for row in new_grid_input]
|
|
|
|
|
|
buildings = [Building(*[int(j) for j in item.split()]) for item in buildings_input]
|
|
|
- log(f"* buildings: {buildings}")
|
|
|
+# log(f"* buildings: {buildings}")
|
|
|
|
|
|
units = [Unit(*[int(j) for j in item.split()]) for item in units_input]
|
|
|
- log(f"* units: {units}")
|
|
|
+# log(f"* units: {units}")
|
|
|
# --->
|
|
|
|
|
|
# <--- update
|
|
|
@@ -402,27 +482,50 @@ while True:
|
|
|
# log(f"grid:\n{grid.print_grid()}")
|
|
|
|
|
|
player.update(gold, income, units, buildings)
|
|
|
- log(f"player: {player}")
|
|
|
+# log(f"player: {player}")
|
|
|
|
|
|
opponent.update(opponent_gold, opponent_income, units, buildings)
|
|
|
- log(f"opponent: {opponent}")
|
|
|
+# log(f"opponent: {opponent}")
|
|
|
# --->
|
|
|
|
|
|
commands = []
|
|
|
|
|
|
# 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))
|
|
|
-
|
|
|
+ log("# Moving")
|
|
|
for unit in player.units:
|
|
|
- target = grid.get_next_move(unit)
|
|
|
- if target:
|
|
|
- commands.append(cmd_move(unit, target))
|
|
|
+ action = grid.get_next_move(unit)
|
|
|
+ if action:
|
|
|
+ grid.reservation.append(action.target.pos)
|
|
|
+ commands.append(action.resolution())
|
|
|
+
|
|
|
+ log("# Training")
|
|
|
+ spent, charges = 0, 0
|
|
|
+ while (player.income - charges) > len(player.units) and (player.gold - spent) > 10:
|
|
|
+ max_level = next((i for i in range(3, 0, -1) if (player.gold - spent) > Unit.training_cost[i] and (player.income - charges) > Unit.maintenance[i]), 1)
|
|
|
+ action = grid.get_next_training(max_level)
|
|
|
+ if action:
|
|
|
+ grid.reservation.append(action.target.pos)
|
|
|
+ spent += Unit.training_cost[action.level]
|
|
|
+ charges += Unit.maintenance[action.level]
|
|
|
+ commands.append(action.resolution())
|
|
|
+ else:
|
|
|
+ break
|
|
|
|
|
|
+ log("# Building")
|
|
|
+ mine_cost = 20 + 4 * len([b for b in player.buildings if b.type_ == Building.MINE])
|
|
|
+ if (player.gold - spent) > mine_cost:
|
|
|
+ action = grid.get_building_site(Building.MINE)
|
|
|
+ if action:
|
|
|
+ spent += mine_cost
|
|
|
+ commands.append(action.resolution())
|
|
|
+
|
|
|
+# if player.gold - spent > 15:
|
|
|
+# action = grid.get_building_site(Building.TOWER)
|
|
|
+
|
|
|
+
|
|
|
if not commands:
|
|
|
log("nothing to do: wait")
|
|
|
commands = ["WAIT"]
|
|
|
+
|
|
|
log(commands)
|
|
|
print(";".join(commands))
|