|
|
@@ -2,18 +2,21 @@
|
|
|
>> https://www.codingame.com/ide/173171838252e7c6fd6f3ff9cb8169431a08eec1
|
|
|
@author: olivier.massot, may 2019
|
|
|
'''
|
|
|
-from collections import Counter
|
|
|
import heapq
|
|
|
import sys
|
|
|
import time
|
|
|
|
|
|
# TODO
|
|
|
-# * when building a tower, check that there is not already one, and block the cell for moving
|
|
|
-# * do not train a unit of a cell if it is in a threatened pivot zone, especially not a level 3 unit!
|
|
|
+# * 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
|
|
|
-# * take units and towers in account when computing the strategig value
|
|
|
-# * review the tower placement in case of 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
|
|
|
t0 = time.time()
|
|
|
@@ -43,6 +46,9 @@ class Queue(Base):
|
|
|
def __repr__(self):
|
|
|
return str(self.items)
|
|
|
|
|
|
+ def values(self):
|
|
|
+ return (v for _, v in self.items)
|
|
|
+
|
|
|
def put(self, item, priority):
|
|
|
heapq.heappush(self.items, (priority, item))
|
|
|
|
|
|
@@ -95,7 +101,6 @@ class Position(BasePosition):
|
|
|
|
|
|
self.possession = 0
|
|
|
self.threat = 0
|
|
|
- self.strategic_value = 0
|
|
|
self.pivot = 0
|
|
|
self.union = 0
|
|
|
self.depth = 0
|
|
|
@@ -125,11 +130,7 @@ class Position(BasePosition):
|
|
|
if self.cell.active_owned:
|
|
|
self.threat = self.cell.threat
|
|
|
|
|
|
- # eval strategic value
|
|
|
- self.strategic_value = self.cell.strategic_value
|
|
|
-
|
|
|
- # covers (towers only)
|
|
|
- self.covers = self.strategic_value + sum([grid[n].strategic_value for n in self.cell.neighbors])
|
|
|
+ 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])
|
|
|
@@ -143,9 +144,17 @@ class Position(BasePosition):
|
|
|
# priorize adjacent cells
|
|
|
self.union = len([n for n in self.cell.neighbors if grid[n].active_owned])
|
|
|
|
|
|
+ # favorize dispersion
|
|
|
+ self.concentration = len([n for n in self.cell.neighbors if grid[n].unit and grid[n].unit.owned])
|
|
|
+
|
|
|
+ self.deadends = len([n for n in self.cell.neighbors if not grid[n].movable])
|
|
|
+
|
|
|
# include 'depthmap'
|
|
|
self.depth = self.cell.depth
|
|
|
|
|
|
+ self.under_tower = self.cell.under_tower
|
|
|
+ self.overlaps_tower = sum([grid[n].under_tower for n in self.cell.neighbors])
|
|
|
+
|
|
|
# priorize mines or HQ
|
|
|
if self.cell.building:
|
|
|
self.hq = int(self.cell.building.type_ == Building.HQ)
|
|
|
@@ -161,11 +170,11 @@ class Position(BasePosition):
|
|
|
|
|
|
def eval(self):
|
|
|
self.pre_eval()
|
|
|
- self.interest = 3 * self.depth + self.dist_to_goal
|
|
|
+ self.interest = 3 * self.depth + self.dist_to_goal + 2 * self.concentration + 2 * self.deadends
|
|
|
|
|
|
def __repr__(self):
|
|
|
- detail = [self.possession, self.threat, self.strategic_value, self.pivot, self.dist_to_goal,
|
|
|
- self.union, self.depth, self.hq, self.tower, self.mine]
|
|
|
+ detail = [self.possession, self.threat, self.pivot, self.dist_to_goal,
|
|
|
+ self.union, self.concentration, self.depth, self.hq, self.tower, self.mine]
|
|
|
return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
|
|
|
|
|
|
|
|
|
@@ -175,16 +184,21 @@ class Defend(Position):
|
|
|
self.emergency = emergency
|
|
|
|
|
|
def __repr__(self):
|
|
|
- detail = [self.threat, self.covers, self.pivot, self.emergency, self.dist_to_hq]
|
|
|
+ detail = [self.threat, self.covers, self.pivot, self.emergency,
|
|
|
+ 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 = 100 \
|
|
|
- - 10 * self.threat \
|
|
|
- - self.covers // 5 \
|
|
|
- - 5 * self.pivot \
|
|
|
- - 20 * self.emergency * (22 - self.dist_to_hq)
|
|
|
+ self.interest = (200 if not self.emergency else 50) \
|
|
|
+ - 2 * self.threat \
|
|
|
+ - 2 * self.covers \
|
|
|
+ - 8 * self.pivot \
|
|
|
+ - 80 * self.cell.critical \
|
|
|
+ - 25 * self.cell.danger \
|
|
|
+ + 25 * self.cell.under_tower \
|
|
|
+ + 10 * self.overlaps_tower
|
|
|
|
|
|
class Attack(Position):
|
|
|
def eval(self):
|
|
|
@@ -192,27 +206,23 @@ class Attack(Position):
|
|
|
self.interest = 15 * self.possession \
|
|
|
- 5 * self.pivot \
|
|
|
- 2 * self.union \
|
|
|
- + 3 * self.depth \
|
|
|
+ + 3 * self.concentration \
|
|
|
+ + 2 * self.deadends \
|
|
|
+ + 4 * self.depth \
|
|
|
+ self.dist_to_goal \
|
|
|
+ - 2 * max([0, 11 - self.dist_to_hq]) \
|
|
|
- 30 * self.tower \
|
|
|
- 15 * self.mine \
|
|
|
- - 100 * self.hq
|
|
|
+ - 100 * self.hq \
|
|
|
+ + 10 * (self.min_level - 1)
|
|
|
|
|
|
class MinePosition(BasePosition):
|
|
|
- def __init__(self, target, type_):
|
|
|
- self.type_ = type_
|
|
|
- super().__init__(target, type_)
|
|
|
+ def __init__(self, target):
|
|
|
+ super().__init__(target)
|
|
|
|
|
|
def eval(self):
|
|
|
# the lower the better
|
|
|
- self.interest = 0
|
|
|
-
|
|
|
- if self.type_ == Building.MINE:
|
|
|
- self.interest -= self.target.depth
|
|
|
-
|
|
|
- elif self.type_ == Building.TOWER:
|
|
|
- if self.target.pivot:
|
|
|
- self.interest -= 20
|
|
|
+ self.interest -= self.cell.depth
|
|
|
|
|
|
class BaseLoc(Base):
|
|
|
def __init__(self, x, y):
|
|
|
@@ -301,9 +311,12 @@ class Player(Base):
|
|
|
def training_capacity(self):
|
|
|
return min([(self.gold - self.spent) // Unit.cost[1], (self.income - self.new_charges) // Unit.maintenance[1]])
|
|
|
|
|
|
+ def can_afford(self, lvl):
|
|
|
+ return (self.gold - self.spent) >= Unit.cost[lvl] and (self.income - self.new_charges) >= Unit.maintenance[lvl]
|
|
|
+
|
|
|
def max_affordable(self):
|
|
|
for lvl in range(3, 0, -1):
|
|
|
- if (self.gold - self.spent) >= Unit.cost[lvl] and (self.income - self.new_charges) >= Unit.maintenance[lvl]:
|
|
|
+ if self.can_afford(lvl):
|
|
|
return lvl
|
|
|
return 0
|
|
|
|
|
|
@@ -323,13 +336,10 @@ class Cell(Base):
|
|
|
self.under_tower = False
|
|
|
self.depth = 0
|
|
|
self.pivot_for = []
|
|
|
- self.strategic_value = 0
|
|
|
+ self.critical = False
|
|
|
+ self.danger = False
|
|
|
self.threat = 0
|
|
|
-
|
|
|
- # front cells
|
|
|
- self.facing = []
|
|
|
- self.support = []
|
|
|
- self.in_front_of = []
|
|
|
+ self.threat_by = None
|
|
|
|
|
|
@property
|
|
|
def pos(self):
|
|
|
@@ -347,13 +357,11 @@ class Cell(Base):
|
|
|
self.under_tower = False
|
|
|
self.depth = 0
|
|
|
self.pivot_for = []
|
|
|
- self.strategic_value = 0
|
|
|
self.threat = 0
|
|
|
-
|
|
|
- self.facing = []
|
|
|
- self.support = []
|
|
|
- self.in_front_of = []
|
|
|
-
|
|
|
+ self.threat_by = None
|
|
|
+ self.critical = False
|
|
|
+ self.danger = False
|
|
|
+
|
|
|
@property
|
|
|
def movable(self):
|
|
|
return self._content != "#"
|
|
|
@@ -374,6 +382,7 @@ class Cell(Base):
|
|
|
return OPPONENT
|
|
|
else:
|
|
|
return None
|
|
|
+
|
|
|
|
|
|
@property
|
|
|
def headquarter(self):
|
|
|
@@ -395,6 +404,14 @@ class Cell(Base):
|
|
|
def active_opponent(self):
|
|
|
return self._content == "X"
|
|
|
|
|
|
+ def is_active_owner(self, player_id):
|
|
|
+ if player_id == ME:
|
|
|
+ return self.active_owned
|
|
|
+ elif player_id == ME:
|
|
|
+ return self.active_opponent
|
|
|
+ else:
|
|
|
+ return False
|
|
|
+
|
|
|
def owned_unit(self):
|
|
|
if self.unit and self.unit.owned:
|
|
|
return self.unit
|
|
|
@@ -416,6 +433,16 @@ class Node(Base):
|
|
|
def __init__(self, pos, path=[]):
|
|
|
self.pos = pos
|
|
|
self.path = path
|
|
|
+
|
|
|
+class PathNode(tuple):
|
|
|
+ def __new__(self, x, y, parent=None):
|
|
|
+ n = tuple.__new__(self, (x, y))
|
|
|
+ n.parent = parent
|
|
|
+ n.cost = 0
|
|
|
+ return n
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return f"<{self[0]}, {self[1]}, c:{self.cost}>"
|
|
|
|
|
|
class Grid(Base):
|
|
|
dim = 12
|
|
|
@@ -460,15 +487,18 @@ class Grid(Base):
|
|
|
units_ix.get((x, y), None),
|
|
|
buildings_ix.get((x, y), None))
|
|
|
|
|
|
+ log(" * update pivots")
|
|
|
self.update_pivots()
|
|
|
- self.update_state()
|
|
|
|
|
|
+ log(" * update threats")
|
|
|
+ self.update_threats()
|
|
|
+
|
|
|
+ self.update_state()
|
|
|
|
|
|
def update_state(self):
|
|
|
self.update_tower_areas()
|
|
|
self.update_frontlines()
|
|
|
self.update_depth_map()
|
|
|
- self.update_threats()
|
|
|
|
|
|
@staticmethod
|
|
|
def manhattan(from_, to_):
|
|
|
@@ -534,8 +564,7 @@ class Grid(Base):
|
|
|
next_buffer = []
|
|
|
|
|
|
def _active_owned(self, pos, player_id):
|
|
|
- c = self.cells[pos]
|
|
|
- return c.owner == player_id and c.active
|
|
|
+ return self.cells[pos].is_active_owner(player_id)
|
|
|
|
|
|
def update_pivot_for(self, player_id):
|
|
|
start = self.get_hq(player_id).pos
|
|
|
@@ -544,11 +573,18 @@ class Grid(Base):
|
|
|
buffer = [start_node]
|
|
|
nodes = {start_node}
|
|
|
|
|
|
- ignored = [p for p in self.cells if len([n for n in self.neighbors(*p, diags=True) if self._active_owned(n, player_id)]) == 8]
|
|
|
+ its, breakdown = 0, 1200
|
|
|
+
|
|
|
+ ignored = [p for p, c in self.cells.items() if c.is_active_owner(player_id) and
|
|
|
+ len([n for n in self.neighbors(*p, diags=True) if self._active_owned(n, player_id)]) == 8]
|
|
|
|
|
|
while buffer:
|
|
|
new_buffer = []
|
|
|
for node in buffer:
|
|
|
+ its += 1
|
|
|
+ if its > breakdown:
|
|
|
+ log("pivots: broken")
|
|
|
+ return
|
|
|
if node.pos in ignored:
|
|
|
continue
|
|
|
for n in self.neighbors(*node.pos):
|
|
|
@@ -576,49 +612,115 @@ class Grid(Base):
|
|
|
pivots[candidate] = []
|
|
|
pivots[candidate].append(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
|
|
|
|
|
|
- occurrences = Counter(sum(sum(paths_to.values(), []), []))
|
|
|
-
|
|
|
- while ignored:
|
|
|
- new_ignored = set()
|
|
|
- for p in ignored:
|
|
|
- occured_neighbors = [occurrences[n] for n in self.neighbors(*p) if n in occurrences]
|
|
|
- if not occured_neighbors:
|
|
|
- new_ignored.add[p]
|
|
|
- continue
|
|
|
- occurrences[p] = 2 * sum(occured_neighbors) // len(occured_neighbors)
|
|
|
- ignored = new_ignored
|
|
|
-
|
|
|
- max_occ = max(occurrences.values()) if occurrences else 1
|
|
|
- for p, occ in occurrences.items():
|
|
|
- self.cells[p].strategic_value = (100 * occ) // max_occ
|
|
|
-
|
|
|
-# if player_id == ME:
|
|
|
-# log(pivots)
|
|
|
-# log(occurrences)
|
|
|
-# log({cell.pos: cell.strategic_value for cell in self.cells.values() if cell.owned})
|
|
|
-
|
|
|
def update_pivots(self):
|
|
|
self.update_pivot_for(ME)
|
|
|
self.update_pivot_for(OPPONENT)
|
|
|
|
|
|
def update_threats(self):
|
|
|
# 1 + max number of units opponents can produce in one turn
|
|
|
- self.threat_level = 1 + min([(opponent.gold + opponent.income) // Unit.cost[1], opponent.income // Unit.maintenance[1]])
|
|
|
+ self.threat_level = 2 + (opponent.gold + opponent.income) // Unit.cost[1]
|
|
|
|
|
|
- ennemy_frontier = [c for c in self.cells.values() if c.opponents \
|
|
|
+ ennemy_frontier = [c for c in self.cells.values() if c.active_opponent \
|
|
|
and any(self.cells[n].movable and not self.cells[n].opponents for n in c.neighbors)]
|
|
|
|
|
|
for cell in self.cells.values():
|
|
|
if cell.owned:
|
|
|
- threat = min([Grid.manhattan(cell.pos, o.pos) for o in ennemy_frontier])
|
|
|
- cell.threat = self.threat_level - threat
|
|
|
+ closest, dist = min([(o, Grid.manhattan(cell.pos, o.pos)) for o in ennemy_frontier], key=lambda x: x[1])
|
|
|
+ if cell.unit:
|
|
|
+ dist += cell.unit.level
|
|
|
+ if cell.under_tower:
|
|
|
+ dist += 2
|
|
|
+
|
|
|
+ if dist < self.threat_level:
|
|
|
+ cell.threat = self.threat_level - dist
|
|
|
+ cell.threat_by = closest
|
|
|
+
|
|
|
+ hqcell = grid[player.hq.pos]
|
|
|
+ self.emergency = hqcell.threat > 0
|
|
|
|
|
|
- self.emergency = grid[player.hq.pos].threat > 0
|
|
|
+ self.update_threat_path()
|
|
|
|
|
|
-# log({c.pos: c.threat for c in self.cells.values() if c.owned and c.threat is not None})
|
|
|
+# log({c.pos: (c.threat, c.threat_by) for c in self.cells.values() if c.owned and c.threat > 0})
|
|
|
+
|
|
|
+ def update_threat_path(self):
|
|
|
+
|
|
|
+ hqcell = grid[player.hq.pos]
|
|
|
+ if not hqcell.threat > 0:
|
|
|
+ return
|
|
|
+ closest_threat = hqcell.threat_by
|
|
|
+# log(f"* {closest_threat.pos} threats HQ")
|
|
|
+
|
|
|
+ path = self.path(closest_threat.pos, player.hq.pos)
|
|
|
+ if 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
|
|
|
+ for p in extended_path:
|
|
|
+ if self._active_owned(p, ME):
|
|
|
+ self.cells[p].danger = True
|
|
|
+
|
|
|
+
|
|
|
+ def path(self, start, target):
|
|
|
+ nodes = Queue()
|
|
|
+ its, break_on = 0, 2000
|
|
|
+
|
|
|
+ origin = PathNode(*start)
|
|
|
+ nodes.put(origin, 0)
|
|
|
+
|
|
|
+ neighbors = []
|
|
|
+
|
|
|
+ while nodes:
|
|
|
+ current = nodes.get()
|
|
|
+
|
|
|
+ if current == target:
|
|
|
+ path = []
|
|
|
+ previous = current
|
|
|
+ while previous:
|
|
|
+ if previous != start:
|
|
|
+ path.insert(0, previous)
|
|
|
+ previous = previous.parent
|
|
|
+ return path
|
|
|
+
|
|
|
+ neighbors = self.neighbors(*current)
|
|
|
+
|
|
|
+ for x, y in neighbors:
|
|
|
+ its += 1
|
|
|
+ if its > break_on:
|
|
|
+ log("<!> pathfinding broken")
|
|
|
+ return None
|
|
|
+
|
|
|
+ if (x, y) == current.parent:
|
|
|
+ continue
|
|
|
+
|
|
|
+ cell = self.cells[(x, y)]
|
|
|
+ if not cell.movable:
|
|
|
+ continue
|
|
|
+
|
|
|
+ moving_cost = 1
|
|
|
+ if cell.unit and cell.unit.owned:
|
|
|
+ moving_cost += cell.unit.level
|
|
|
+ if cell.under_tower:
|
|
|
+ moving_cost += 2
|
|
|
+
|
|
|
+ cost = current.cost + moving_cost
|
|
|
+ priority = cost + 10 * Grid.manhattan((x, y), target)
|
|
|
+
|
|
|
+ node = PathNode(x, y, current)
|
|
|
+ node.cost = cost
|
|
|
+ nodes.put(node, priority)
|
|
|
+
|
|
|
+ return None
|
|
|
|
|
|
def influence_zone(self, player_id):
|
|
|
owned, neighbors = {p for p, c in self.cells.items() if c.owner == player_id and c.active}, set()
|
|
|
@@ -689,7 +791,7 @@ class Grid(Base):
|
|
|
def get_building_site(self, type_):
|
|
|
q = InterestQueue()
|
|
|
for cell in self.building_zone(type_):
|
|
|
- q.put(Position(cell, type_))
|
|
|
+ q.put(MinePosition(cell))
|
|
|
if not q:
|
|
|
return None
|
|
|
return q.get()
|
|
|
@@ -708,17 +810,19 @@ class Grid(Base):
|
|
|
|
|
|
if new_cell.unit:
|
|
|
if new_cell.unit.owned:
|
|
|
- log("ERROR: impossible move")
|
|
|
+ log("ERROR: impossible move (owned unit here)")
|
|
|
return
|
|
|
if action.unit.level < 3 and new_cell.unit.level >= action.unit.level:
|
|
|
- log("ERROR: impossible move")
|
|
|
+ log("ERROR: impossible move (unit here, level too low)")
|
|
|
return
|
|
|
# cell is occupied by an opponent's unit with an inferior level
|
|
|
self.remove_unit_from(new_cell)
|
|
|
|
|
|
if new_cell.building and new_cell.building.type_ == Building.TOWER:
|
|
|
+ if new_cell.owned:
|
|
|
+ log("ERROR: impossible move (allied tower here)")
|
|
|
if action.unit.level < 3:
|
|
|
- log("ERROR: impossible move")
|
|
|
+ log("ERROR: impossible move (tower here, level too low)")
|
|
|
opponent.buildings.remove(new_cell.building)
|
|
|
self.buildings.remove(new_cell.building)
|
|
|
new_cell.building = None
|
|
|
@@ -756,7 +860,7 @@ class Grid(Base):
|
|
|
# cell is occupied by an opponent's unit with an inferior level
|
|
|
self.remove_unit_from(new_cell)
|
|
|
|
|
|
- if new_cell.building and new_cell.building.type_ == Building.TOWER:
|
|
|
+ if new_cell.building and new_cell.building.opponents and new_cell.building.type_ == Building.TOWER:
|
|
|
if unit.level < 3:
|
|
|
log("ERROR: impossible training")
|
|
|
opponent.buildings.remove(new_cell.building)
|
|
|
@@ -789,6 +893,8 @@ class Grid(Base):
|
|
|
self.cells[n].under_tower = True
|
|
|
|
|
|
self.update_state()
|
|
|
+ if self.emergency:
|
|
|
+ self.update_threat_path()
|
|
|
|
|
|
elif type(action) is BuildMine:
|
|
|
|
|
|
@@ -895,6 +1001,7 @@ def get_input():
|
|
|
|
|
|
while True:
|
|
|
|
|
|
+
|
|
|
# <--- get and parse input
|
|
|
gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input = get_input()
|
|
|
|
|
|
@@ -907,6 +1014,9 @@ while True:
|
|
|
# log(f"* units: {units}")
|
|
|
# --->
|
|
|
|
|
|
+ log("## Start ##")
|
|
|
+ log("* Update")
|
|
|
+
|
|
|
# <--- update
|
|
|
player.update(gold, income, units, buildings)
|
|
|
# log(f"player: {player}")
|
|
|
@@ -927,43 +1037,78 @@ while True:
|
|
|
|
|
|
todo = []
|
|
|
abandonned = []
|
|
|
+ acted = True
|
|
|
+ defs, max_def = 0, 8
|
|
|
|
|
|
while player.can_act():
|
|
|
|
|
|
- q = InterestQueue()
|
|
|
- for cell in grid.influence_zone(ME):
|
|
|
- if cell.movable and not cell in abandonned:
|
|
|
- if cell.owned and cell.threat > 0 and cell.pos != player.hq.pos:
|
|
|
- p = Defend(cell, grid.emergency)
|
|
|
- elif not cell.owned:
|
|
|
- p = Attack(cell)
|
|
|
- else:
|
|
|
- continue
|
|
|
- p.eval()
|
|
|
- q.put(p)
|
|
|
-
|
|
|
+ if acted:
|
|
|
+ # 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.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:
|
|
|
+ p = Attack(cell)
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+ p.eval()
|
|
|
+ q.put(p)
|
|
|
+
|
|
|
if not q:
|
|
|
break
|
|
|
|
|
|
+ acted = False
|
|
|
+
|
|
|
objective = q.get()
|
|
|
+
|
|
|
if type(objective) is Defend:
|
|
|
+ defs += 1
|
|
|
+
|
|
|
if player.current_gold > Building.cost[Building.TOWER] \
|
|
|
and not objective.cell.mine_site \
|
|
|
and not objective.cell.building:
|
|
|
action = BuildTower(objective.cell)
|
|
|
|
|
|
- else:
|
|
|
- # TODO: recruit units
|
|
|
- abandonned.append(objective.cell)
|
|
|
+ elif objective.cell.unit and objective.cell.unit.owned:
|
|
|
+ # already a unit here: stay on place
|
|
|
+ objective.cell.unit.has_moved = True
|
|
|
continue
|
|
|
+
|
|
|
+ elif player.can_afford(1):
|
|
|
+ action = Train(1, objective.cell)
|
|
|
+
|
|
|
+ else:
|
|
|
+ near_unit = next((grid[n].unit for n in objective.cell.neighbors
|
|
|
+ if grid[n].unit
|
|
|
+ and grid[n].unit.owned
|
|
|
+ and grid[n].unit.level == 1
|
|
|
+ and not grid[n].unit.has_moved),
|
|
|
+ None)
|
|
|
+ if near_unit and grid.can_move(objective.cell.pos, near_unit.level):
|
|
|
+ action = Move(near_unit, objective.cell)
|
|
|
+ else:
|
|
|
+ abandonned.append(objective.cell)
|
|
|
+ continue
|
|
|
|
|
|
elif type(objective) is Attack:
|
|
|
- near_unit = next((grid[n].unit for n in objective.cell.neighbors if grid[n].unit \
|
|
|
+
|
|
|
+ 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),
|
|
|
- None)
|
|
|
- if near_unit:
|
|
|
+ and grid[n].unit.level >= objective.min_level]
|
|
|
+
|
|
|
+ if len(near_units) == 1:
|
|
|
+ near_unit = near_units[0]
|
|
|
+ elif len(near_units) > 1:
|
|
|
+ near_unit = min(near_units, key=lambda u: len([u.pos == o.cell.pos for o in q.items]))
|
|
|
+ else:
|
|
|
+ near_unit = None
|
|
|
+
|
|
|
+ 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():
|
|
|
@@ -971,9 +1116,15 @@ while True:
|
|
|
continue
|
|
|
action = Train(objective.min_level, objective.cell)
|
|
|
|
|
|
- log(f"priority: {action} -> {objective}")
|
|
|
+ log(f"priority: {objective}")
|
|
|
+
|
|
|
+ # a unit occupy the cell
|
|
|
already_there = action.cell.unit
|
|
|
if already_there and already_there.owned:
|
|
|
+ if already_there.has_moved:
|
|
|
+ log("cell is occupied: abandon")
|
|
|
+ abandonned.append(objective.cell)
|
|
|
+ continue
|
|
|
log(f"* needs to evacuate unit {already_there.id_} before")
|
|
|
evacuate = grid.get_next_move(already_there)
|
|
|
if evacuate:
|
|
|
@@ -983,7 +1134,11 @@ while True:
|
|
|
todo.append(evac_action)
|
|
|
else:
|
|
|
log(f"<!> No move available for unit {already_there.id_}")
|
|
|
+ abandonned.append(objective.cell)
|
|
|
+ continue
|
|
|
|
|
|
+ acted = True
|
|
|
+ log(f"> Apply action {action}")
|
|
|
grid.apply(action)
|
|
|
todo.append(action)
|
|
|
|
|
|
@@ -997,15 +1152,18 @@ while True:
|
|
|
grid.apply(action)
|
|
|
todo.append(action)
|
|
|
else:
|
|
|
- log(f"<!> No move available for unit {already_there.id_}")
|
|
|
+ log(f"<!> No move available for unit {unit.id_}")
|
|
|
+ unit.has_moved = True
|
|
|
|
|
|
# can build mines?
|
|
|
if player.current_gold > grid.cost_for_new_mine():
|
|
|
- action = BuildMine(grid.get_building_site(Building.MINE))
|
|
|
- if action:
|
|
|
- log(f"default: {action}")
|
|
|
- grid.apply(action)
|
|
|
- todo.append(action)
|
|
|
+ site = grid.get_building_site(Building.MINE)
|
|
|
+ if site:
|
|
|
+ action = BuildMine(site.cell)
|
|
|
+ if action:
|
|
|
+ log(f"default: {action}")
|
|
|
+ grid.apply(action)
|
|
|
+ todo.append(action)
|
|
|
|
|
|
log(f"* todo: {todo}")
|
|
|
|