|
|
@@ -6,16 +6,18 @@ import heapq
|
|
|
import sys
|
|
|
import time
|
|
|
|
|
|
+# for the future me:
|
|
|
+# * find a way to cut the ennemy territory, by sparing for 4 or 5 units and
|
|
|
+# going straigh trough his territory, todo recto!
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
# TODO
|
|
|
-# * priorize attack on neighbors of pivots (allies'pivots to colonize and ennemies to threat)
|
|
|
-# * !!! moves: check before if unit is not on a zone which needs to be protected
|
|
|
-# x optimize moves, especially at start! (by introducing dispersion?)
|
|
|
+
|
|
|
# * take units and towers in account when computing the threat
|
|
|
-# x priorize colonization of cells near HQ
|
|
|
# ? when priority needs a lvl3 unit, find a way to spare
|
|
|
# * resurrect the strategic value: number of cells depending of the cell
|
|
|
# * consider defending also cells not owned
|
|
|
-# x limit the training of level 3 when it's really needed
|
|
|
# * make a first turn of moves for units deep inside the territory, to avoid unecessary training and computing
|
|
|
|
|
|
debug = True
|
|
|
@@ -133,7 +135,11 @@ class Position(BasePosition):
|
|
|
self.covers = sum([grid[n].owned for n in self.cell.neighbors])
|
|
|
|
|
|
# eval pivot
|
|
|
- self.pivot = sum([1 + grid[p].get_unit_level() for p in self.cell.pivot_for])
|
|
|
+ self.pivot = self.cell.pivot_value
|
|
|
+ self.next_to_pivot = any(grid[n].pivot_value for n in self.cell.neighbors)
|
|
|
+
|
|
|
+ # cell is not easily reachable
|
|
|
+ self.protected = len([n for n in grid.neighbors(*self.cell.pos, True) if not grid[n].movable]) == 6
|
|
|
|
|
|
# distance to the ennemy HQ
|
|
|
self.dist_to_goal = Grid.manhattan(self.cell.pos, opponent.hq.pos)
|
|
|
@@ -141,6 +147,9 @@ class Position(BasePosition):
|
|
|
# distance to the own HQ
|
|
|
self.dist_to_hq = Grid.manhattan(self.cell.pos, player.hq.pos)
|
|
|
|
|
|
+ # distance to the center of the map
|
|
|
+ self.dist_to_center = Grid.manhattan(self.cell.pos, (6,6))
|
|
|
+
|
|
|
# priorize adjacent cells
|
|
|
self.union = len([n for n in self.cell.neighbors if grid[n].active_owned])
|
|
|
|
|
|
@@ -179,43 +188,75 @@ class Position(BasePosition):
|
|
|
|
|
|
|
|
|
class Defend(Position):
|
|
|
- def __init__(self, cell, emergency = False):
|
|
|
+ def __init__(self, cell):
|
|
|
super().__init__(cell)
|
|
|
- self.emergency = emergency
|
|
|
|
|
|
def __repr__(self):
|
|
|
- detail = [self.threat, self.covers, self.pivot, self.emergency,
|
|
|
+ detail = [self.threat, self.covers, self.pivot,
|
|
|
self.dist_to_hq, self.cell.danger, self.cell.critical,
|
|
|
self.under_tower, self.overlaps_tower]
|
|
|
return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
|
|
|
|
|
|
def eval(self):
|
|
|
self.pre_eval()
|
|
|
- self.interest = (200 if not self.emergency else 50) \
|
|
|
- - 2 * self.threat \
|
|
|
+ self.interest = 20 \
|
|
|
- 2 * self.covers \
|
|
|
- - 8 * self.pivot \
|
|
|
+ - (8 * self.pivot if not self.protected else 4 * self.pivot) \
|
|
|
- 80 * self.cell.critical \
|
|
|
- 25 * self.cell.danger \
|
|
|
+ 25 * self.cell.under_tower \
|
|
|
- + 10 * self.overlaps_tower
|
|
|
+ + 10 * self.overlaps_tower \
|
|
|
+ + self.dist_to_hq
|
|
|
+
|
|
|
+class Secure(Position):
|
|
|
+ def __init__(self, cell, emergency = False):
|
|
|
+ super().__init__(cell)
|
|
|
+ self.emergency = emergency
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ detail = [self.threat, self.covers, self.pivot,
|
|
|
+ self.under_tower, self.overlaps_tower]
|
|
|
+ return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
|
|
|
+
|
|
|
+ def eval(self):
|
|
|
+ self.pre_eval()
|
|
|
+ self.interest = 30 \
|
|
|
+ - 2 * self.threat \
|
|
|
+ - 2 * self.covers \
|
|
|
+ - (8 * self.pivot if not self.protected else 2 * self.pivot) \
|
|
|
+ + 25 * self.cell.under_tower \
|
|
|
+ + 10 * self.overlaps_tower \
|
|
|
+ + self.dist_to_center
|
|
|
|
|
|
class Attack(Position):
|
|
|
def eval(self):
|
|
|
self.pre_eval()
|
|
|
self.interest = 15 * self.possession \
|
|
|
- - 5 * self.pivot \
|
|
|
+ - 7 * self.pivot \
|
|
|
+ - 5 * self.next_to_pivot \
|
|
|
- 2 * self.union \
|
|
|
- + 3 * self.concentration \
|
|
|
- + 2 * self.deadends \
|
|
|
- + 4 * self.depth \
|
|
|
- + self.dist_to_goal \
|
|
|
- - 2 * max([0, 11 - self.dist_to_hq]) \
|
|
|
+ + 2 * self.dist_to_goal \
|
|
|
- 30 * self.tower \
|
|
|
- 15 * self.mine \
|
|
|
- 100 * self.hq \
|
|
|
+ - 50 * self.cell.critical \
|
|
|
+ - 20 * self.cell.danger \
|
|
|
+ 10 * (self.min_level - 1)
|
|
|
|
|
|
+class Colonize(Position):
|
|
|
+ def eval(self):
|
|
|
+ self.pre_eval()
|
|
|
+ self.interest = 15 * self.possession \
|
|
|
+ - 5 * self.next_to_pivot \
|
|
|
+ - 2 * self.union \
|
|
|
+ + 4 * self.concentration \
|
|
|
+ + 2 * self.deadends \
|
|
|
+ + 4 * self.depth \
|
|
|
+ + self.dist_to_goal \
|
|
|
+ - max([0, 3 - self.dist_to_hq]) \
|
|
|
+ + self.dist_to_center
|
|
|
+
|
|
|
+
|
|
|
class MinePosition(BasePosition):
|
|
|
def __init__(self, target):
|
|
|
super().__init__(target)
|
|
|
@@ -289,6 +330,7 @@ class Player(Base):
|
|
|
|
|
|
self.spent = 0
|
|
|
self.new_charges = 0
|
|
|
+ self.to_spare = 0
|
|
|
|
|
|
def update(self, gold, income, units, buildings):
|
|
|
self.gold = gold
|
|
|
@@ -336,6 +378,7 @@ class Cell(Base):
|
|
|
self.under_tower = False
|
|
|
self.depth = 0
|
|
|
self.pivot_for = []
|
|
|
+ self.pivot_value = 0
|
|
|
self.critical = False
|
|
|
self.danger = False
|
|
|
self.threat = 0
|
|
|
@@ -357,6 +400,7 @@ class Cell(Base):
|
|
|
self.under_tower = False
|
|
|
self.depth = 0
|
|
|
self.pivot_for = []
|
|
|
+ self.pivot_value = 0
|
|
|
self.threat = 0
|
|
|
self.threat_by = None
|
|
|
self.critical = False
|
|
|
@@ -407,7 +451,7 @@ class Cell(Base):
|
|
|
def is_active_owner(self, player_id):
|
|
|
if player_id == ME:
|
|
|
return self.active_owned
|
|
|
- elif player_id == ME:
|
|
|
+ elif player_id == OPPONENT:
|
|
|
return self.active_opponent
|
|
|
else:
|
|
|
return False
|
|
|
@@ -488,7 +532,9 @@ class Grid(Base):
|
|
|
buildings_ix.get((x, y), None))
|
|
|
|
|
|
log(" * update pivots")
|
|
|
- self.update_pivots()
|
|
|
+# self.update_pivots()
|
|
|
+ self.update_propagation(ME)
|
|
|
+ self.update_propagation(OPPONENT)
|
|
|
|
|
|
log(" * update threats")
|
|
|
self.update_threats()
|
|
|
@@ -564,8 +610,84 @@ class Grid(Base):
|
|
|
next_buffer = []
|
|
|
|
|
|
def _active_owned(self, pos, player_id):
|
|
|
- return self.cells[pos].is_active_owner(player_id)
|
|
|
-
|
|
|
+ try:
|
|
|
+ return self.cells[pos].is_active_owner(player_id)
|
|
|
+ except KeyError:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def update_propagation(self, player_id):
|
|
|
+ start = self.get_hq(player_id).pos
|
|
|
+ lvl = 0
|
|
|
+ propagation = {start: (lvl, [])}
|
|
|
+
|
|
|
+ pivots_cells = []
|
|
|
+ for x, y in self.cells:
|
|
|
+ if (x, y) != start and self._active_owned((x, y), player_id):
|
|
|
+
|
|
|
+ around = [(x, y - 1), (x + 1, y - 1), (x + 1, y), (x + 1, y + 1),
|
|
|
+ (x, y + 1), (x - 1, y + 1), (x - 1, y), (x - 1, y - 1)]
|
|
|
+ owned = [self._active_owned(p, player_id) for p in around]
|
|
|
+ changes = [x for x in zip(owned, owned[1:]) if x == (True, False)]
|
|
|
+
|
|
|
+ if len(changes) > 1:
|
|
|
+ pivots_cells.append((x, y))
|
|
|
+ pivots = {p: [] for p in pivots_cells}
|
|
|
+
|
|
|
+ buffer = [start]
|
|
|
+ while buffer:
|
|
|
+ new_buffer = []
|
|
|
+ lvl += 1
|
|
|
+ for pos in buffer:
|
|
|
+ for n in self.neighbors(*pos):
|
|
|
+ if self._active_owned(n, player_id):
|
|
|
+ if not n in propagation:
|
|
|
+ propagation[n] = (lvl, [pos])
|
|
|
+ new_buffer.append(n)
|
|
|
+ else:
|
|
|
+ # already visited
|
|
|
+ if propagation[pos][1] != [n] and propagation[n][0] >= propagation[pos][0]:
|
|
|
+ propagation[n][1].append(pos)
|
|
|
+
|
|
|
+ buffer = new_buffer
|
|
|
+
|
|
|
+ self.propagation = propagation
|
|
|
+
|
|
|
+ children = {}
|
|
|
+ for p, data in self.propagation.items():
|
|
|
+ _, parents = data
|
|
|
+ for parent in parents:
|
|
|
+ if not parent in children:
|
|
|
+ children[parent] = []
|
|
|
+ children[parent].append(p)
|
|
|
+
|
|
|
+ for pivot in pivots:
|
|
|
+ buffer = set(children.get(pivot, []))
|
|
|
+
|
|
|
+ while buffer:
|
|
|
+ new_buffer = set()
|
|
|
+
|
|
|
+ for child in buffer:
|
|
|
+ new_buffer |= set(children.get(child, []))
|
|
|
+
|
|
|
+ pivots[pivot] += list(buffer)
|
|
|
+ buffer = new_buffer
|
|
|
+
|
|
|
+ # cleaning 'false children'
|
|
|
+ for pivot, children in pivots.items():
|
|
|
+ invalid = []
|
|
|
+ for child in children:
|
|
|
+ parents = self.propagation[child][1]
|
|
|
+ if any((p != pivot and p not in children) or p in invalid for p in parents):
|
|
|
+ invalid.append(child)
|
|
|
+ for p in invalid:
|
|
|
+ children.remove(p)
|
|
|
+
|
|
|
+ log("player {}, pivots: {}".format(player_id, {k: len(v) for k, v in pivots.items()}))
|
|
|
+
|
|
|
+ for pivot, pivot_for in pivots.items():
|
|
|
+ self.cells[pivot].pivot_for = pivot_for
|
|
|
+ self.cells[pivot].pivot_value = sum([1 + grid[p].get_unit_level() for p in pivot_for])
|
|
|
+
|
|
|
def update_pivot_for(self, player_id):
|
|
|
start = self.get_hq(player_id).pos
|
|
|
start_node = Node(start)
|
|
|
@@ -653,23 +775,20 @@ class Grid(Base):
|
|
|
if not hqcell.threat > 0:
|
|
|
return
|
|
|
closest_threat = hqcell.threat_by
|
|
|
-# log(f"* {closest_threat.pos} threats HQ")
|
|
|
+ log(f"* {closest_threat.pos} threats HQ")
|
|
|
|
|
|
path = self.path(closest_threat.pos, player.hq.pos)
|
|
|
if path:
|
|
|
-# log(f"* Path to HQ: {path}")
|
|
|
+ log(f"* Path to HQ: {path}")
|
|
|
extended_path = set()
|
|
|
for p in path:
|
|
|
extended_path |= set(self.neighbors(*p))
|
|
|
extended_path -= set(path)
|
|
|
|
|
|
for p in path:
|
|
|
- if self._active_owned(p, ME):
|
|
|
- self.cells[p].critical = True
|
|
|
+ self.cells[p].critical = True
|
|
|
for p in extended_path:
|
|
|
- if self._active_owned(p, ME):
|
|
|
- self.cells[p].danger = True
|
|
|
-
|
|
|
+ self.cells[p].danger = True
|
|
|
|
|
|
def path(self, start, target):
|
|
|
nodes = Queue()
|
|
|
@@ -1036,9 +1155,10 @@ while True:
|
|
|
log("<!> HQ is threaten!")
|
|
|
|
|
|
todo = []
|
|
|
- abandonned = []
|
|
|
+ played, abandonned = [], []
|
|
|
acted = True
|
|
|
defs, max_def = 0, 8
|
|
|
+ to_spare = 15 * (grid.threat_level // 5)
|
|
|
|
|
|
while player.can_act():
|
|
|
|
|
|
@@ -1046,13 +1166,18 @@ while True:
|
|
|
# only re-eval if an action occured
|
|
|
q = InterestQueue()
|
|
|
for cell in grid.influence_zone(ME):
|
|
|
- if cell.movable and not cell in abandonned:
|
|
|
+ if cell.movable and not cell in abandonned and not cell in played:
|
|
|
if cell.owned and cell.threat > 0 and cell.pos != player.hq.pos:
|
|
|
if defs > max_def:
|
|
|
continue
|
|
|
- p = Defend(cell, grid.emergency)
|
|
|
- elif not cell.owned:
|
|
|
+ if grid.emergency:
|
|
|
+ p = Defend(cell)
|
|
|
+ else:
|
|
|
+ p = Secure(cell)
|
|
|
+ elif cell.opponents:
|
|
|
p = Attack(cell)
|
|
|
+ elif not cell.owned:
|
|
|
+ p = Colonize(cell)
|
|
|
else:
|
|
|
continue
|
|
|
p.eval()
|
|
|
@@ -1065,13 +1190,24 @@ while True:
|
|
|
|
|
|
objective = q.get()
|
|
|
|
|
|
- if type(objective) is Defend:
|
|
|
- defs += 1
|
|
|
+ if type(objective) is Secure:
|
|
|
+
|
|
|
+ if (player.current_gold - to_spare) > Building.cost[Building.TOWER] \
|
|
|
+ and not objective.cell.mine_site \
|
|
|
+ and not objective.cell.building:
|
|
|
+ action = BuildTower(objective.cell)
|
|
|
+ defs += 1
|
|
|
+ else:
|
|
|
+ abandonned.append(objective.cell)
|
|
|
+ continue
|
|
|
+
|
|
|
+ elif type(objective) is Defend:
|
|
|
|
|
|
if player.current_gold > Building.cost[Building.TOWER] \
|
|
|
and not objective.cell.mine_site \
|
|
|
and not objective.cell.building:
|
|
|
action = BuildTower(objective.cell)
|
|
|
+ defs += 1
|
|
|
|
|
|
elif objective.cell.unit and objective.cell.unit.owned:
|
|
|
# already a unit here: stay on place
|
|
|
@@ -1080,6 +1216,7 @@ while True:
|
|
|
|
|
|
elif player.can_afford(1):
|
|
|
action = Train(1, objective.cell)
|
|
|
+ defs += 1
|
|
|
|
|
|
else:
|
|
|
near_unit = next((grid[n].unit for n in objective.cell.neighbors
|
|
|
@@ -1090,16 +1227,17 @@ while True:
|
|
|
None)
|
|
|
if near_unit and grid.can_move(objective.cell.pos, near_unit.level):
|
|
|
action = Move(near_unit, objective.cell)
|
|
|
+ defs += 1
|
|
|
else:
|
|
|
abandonned.append(objective.cell)
|
|
|
continue
|
|
|
|
|
|
- elif type(objective) is Attack:
|
|
|
+ elif type(objective) in (Colonize, Attack):
|
|
|
|
|
|
near_units = [grid[n].unit for n in objective.cell.neighbors if grid[n].unit \
|
|
|
and grid[n].unit.owned \
|
|
|
and not grid[n].unit.has_moved \
|
|
|
- and grid[n].unit.level >= objective.min_level]
|
|
|
+ and grid[n].unit.level == objective.min_level]
|
|
|
|
|
|
if len(near_units) == 1:
|
|
|
near_unit = near_units[0]
|
|
|
@@ -1111,9 +1249,10 @@ while True:
|
|
|
if near_unit and grid.can_move(objective.cell.pos, near_unit.level):
|
|
|
action = Move(near_unit, objective.cell)
|
|
|
else:
|
|
|
- if objective.min_level > player.max_affordable():
|
|
|
+ if objective.min_level > player.max_affordable() or player.gold < (to_spare + Unit.cost[objective.min_level]):
|
|
|
abandonned.append(objective.cell)
|
|
|
continue
|
|
|
+
|
|
|
action = Train(objective.min_level, objective.cell)
|
|
|
|
|
|
log(f"priority: {objective}")
|
|
|
@@ -1139,6 +1278,7 @@ while True:
|
|
|
|
|
|
acted = True
|
|
|
log(f"> Apply action {action}")
|
|
|
+ played.append(action.cell)
|
|
|
grid.apply(action)
|
|
|
todo.append(action)
|
|
|
|