|
|
@@ -6,6 +6,13 @@ debug = True
|
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
|
+# Debugger : il y a des mouvements qui ne se font pas, faut tout vérifier
|
|
|
+# Faire des zones à l'intérieur des contigs, pour attirer le mouvement vers les zones à coloniser
|
|
|
+# Limiter les constructions agressives en fonction de ces zones
|
|
|
+# penser les recycleurs comme des obstacles!
|
|
|
+# Identifier le moment où la situation est "verrouillée" (plus d'accès entre alliés et ennemis)
|
|
|
+# et cesser les construction de recycleurs
|
|
|
+
|
|
|
|
|
|
def log(*msg):
|
|
|
if debug:
|
|
|
@@ -42,6 +49,17 @@ class Queue(BaseClass):
|
|
|
return heapq.heappop(self.items)
|
|
|
|
|
|
|
|
|
+class PathNode(tuple):
|
|
|
+ def __new__(cls, x, y, parent=None):
|
|
|
+ n = tuple.__new__(cls, (x, y))
|
|
|
+ n.parent = parent
|
|
|
+ n.cost = 0
|
|
|
+ return n
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return f"<{self[0]}, {self[1]}, c:{self.cost}>"
|
|
|
+
|
|
|
+
|
|
|
class Player(BaseClass):
|
|
|
ME = 1
|
|
|
OPPONENT = 0
|
|
|
@@ -50,7 +68,7 @@ class Player(BaseClass):
|
|
|
def __init__(self, id_):
|
|
|
self.id = id_
|
|
|
self.matter = 0
|
|
|
-
|
|
|
+ self.territory = 0
|
|
|
|
|
|
class Cell(BaseClass):
|
|
|
def __init__(self, x, y):
|
|
|
@@ -78,9 +96,23 @@ class Cell(BaseClass):
|
|
|
def is_opponents(self):
|
|
|
return self.owner == Player.OPPONENT
|
|
|
|
|
|
+ @property
|
|
|
+ def is_neutral(self):
|
|
|
+ return self.owner == Player.NONE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def is_grass(self):
|
|
|
+ return self.amount == 0
|
|
|
+
|
|
|
+ @property
|
|
|
def is_movable(self):
|
|
|
- return self.amount > 0 and not self.recycler
|
|
|
+ return not self.is_grass and not self.recycler
|
|
|
|
|
|
+ @property
|
|
|
+ def lifetime(self):
|
|
|
+ return 10000 if not self.in_range_of_recycler else self.amount
|
|
|
+
|
|
|
+ @property
|
|
|
def unmovable_next_round(self):
|
|
|
return self.amount == 1 and self.in_range_of_recycler
|
|
|
|
|
|
@@ -97,16 +129,30 @@ class Cell(BaseClass):
|
|
|
class RobotGroup(BaseClass):
|
|
|
COST = 10
|
|
|
|
|
|
- def __init__(self, x, y, owner, amount):
|
|
|
+ def __init__(self, x, y, owner, amount, initial_amount=None):
|
|
|
self.x = x
|
|
|
self.y = y
|
|
|
self.owner = owner
|
|
|
self.amount = amount
|
|
|
+ self.initial_amount = initial_amount if initial_amount is not None else amount
|
|
|
+
|
|
|
+ self.amount_played = 0
|
|
|
|
|
|
@property
|
|
|
def pos(self):
|
|
|
return self.x, self.y
|
|
|
|
|
|
+ @pos.setter
|
|
|
+ def pos(self, pos):
|
|
|
+ self.x, self.y = pos
|
|
|
+
|
|
|
+ @property
|
|
|
+ def owned(self):
|
|
|
+ return self.owner == Player.ME
|
|
|
+
|
|
|
+ @property
|
|
|
+ def has_played(self):
|
|
|
+ return self.amount_played >= self.initial_amount
|
|
|
|
|
|
class Recycler(BaseClass):
|
|
|
COST = 10
|
|
|
@@ -133,6 +179,43 @@ class Recycler(BaseClass):
|
|
|
f"{self.immediately_available_amount}, {self.total_available_amount}, {self.lifetime()}>"
|
|
|
|
|
|
|
|
|
+class Contig(BaseClass):
|
|
|
+ UNKNOWN = 'U'
|
|
|
+ OWNED = 'O'
|
|
|
+ PARTIALLY_OWNED = 'P'
|
|
|
+ CONFLICTUAL = 'C'
|
|
|
+ NOT_OWNED = 'N'
|
|
|
+
|
|
|
+ def __init__(self, start):
|
|
|
+ self.start = start
|
|
|
+ self.area = [start]
|
|
|
+ self.status = Contig.UNKNOWN
|
|
|
+ self.has_robots = False
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return f"<Contig(start: {self.start}, size: {len(self.area)}, status: {self.status}, robots: {self.has_robots})>"
|
|
|
+
|
|
|
+
|
|
|
+class MoveOrder(BaseClass):
|
|
|
+ def __init__(self, pos, amount, priority):
|
|
|
+ self.pos = pos
|
|
|
+ self.amount = amount
|
|
|
+ self.priority = priority
|
|
|
+
|
|
|
+ self.affected = 0
|
|
|
+
|
|
|
+ @property
|
|
|
+ def fulfilled(self):
|
|
|
+ return self.affected >= self.amount
|
|
|
+
|
|
|
+
|
|
|
+class MoveOrderCandidate(BaseClass):
|
|
|
+ def __init__(self, order, unit, destination):
|
|
|
+ self.order = order
|
|
|
+ self.unit = unit
|
|
|
+ self.destination = destination
|
|
|
+
|
|
|
+
|
|
|
class Action(BaseClass):
|
|
|
pass
|
|
|
|
|
|
@@ -144,10 +227,11 @@ class Wait(Action):
|
|
|
|
|
|
|
|
|
class Move(Action):
|
|
|
- def __init__(self, from_, to, amount):
|
|
|
+ def __init__(self, from_, to, amount, priority):
|
|
|
self.from_ = from_
|
|
|
self.to = to
|
|
|
self.amount = amount
|
|
|
+ self.priority = priority
|
|
|
|
|
|
def do(self):
|
|
|
x0, y0 = self.from_
|
|
|
@@ -157,9 +241,13 @@ class Move(Action):
|
|
|
|
|
|
class Build(Action):
|
|
|
COST = 10
|
|
|
+ SUPPORT = 'S'
|
|
|
+ AGGRESSIVE = 'A'
|
|
|
|
|
|
- def __init__(self, pos):
|
|
|
+ def __init__(self, pos, destination, priority):
|
|
|
self.pos = pos
|
|
|
+ self.priority = priority
|
|
|
+ self.destination = destination
|
|
|
|
|
|
def do(self):
|
|
|
x, y = self.pos
|
|
|
@@ -169,9 +257,10 @@ class Build(Action):
|
|
|
class Spawn(Action):
|
|
|
COST = 10
|
|
|
|
|
|
- def __init__(self, pos, amount):
|
|
|
+ def __init__(self, pos, amount, priority):
|
|
|
self.pos = pos
|
|
|
self.amount = amount
|
|
|
+ self.priority = priority
|
|
|
|
|
|
def do(self):
|
|
|
x, y = self.pos
|
|
|
@@ -191,6 +280,12 @@ class Grid(BaseClass):
|
|
|
|
|
|
self.units = {}
|
|
|
self.recyclers = {}
|
|
|
+ self.contigs = []
|
|
|
+ self._neighbors_cache = {}
|
|
|
+ self._distance_cache = {}
|
|
|
+ self.index_contigs = {}
|
|
|
+ self.index_tensions = {}
|
|
|
+ self.index_nearest_enemy = {}
|
|
|
|
|
|
@property
|
|
|
def grid(self):
|
|
|
@@ -201,18 +296,32 @@ class Grid(BaseClass):
|
|
|
key = lambda x: x
|
|
|
log("\n" + "\n".join(["".join([f"{key(c)}|" for c in row]) for row in self.grid]))
|
|
|
|
|
|
- @staticmethod
|
|
|
- def manhattan(from_, to_):
|
|
|
+ def manhattan(self, from_, to_):
|
|
|
+ if (from_, to_) in self._distance_cache:
|
|
|
+ return self._distance_cache[(from_, to_)]
|
|
|
+
|
|
|
xa, ya = from_
|
|
|
xb, yb = to_
|
|
|
- return abs(xa - xb) + abs(ya - yb)
|
|
|
-
|
|
|
- def neighbors(self, x, y):
|
|
|
- return [
|
|
|
+ dist = abs(xa - xb) + abs(ya - yb)
|
|
|
+ self._distance_cache[(from_, to_)] = dist
|
|
|
+ self._distance_cache[(to_, from_)] = dist
|
|
|
+ return dist
|
|
|
+
|
|
|
+ def neighbors(self, x, y, diags=False):
|
|
|
+ # if (x, y, diags) in self._neighbors_cache:
|
|
|
+ # return self._neighbors_cache[(x, y, diags)]
|
|
|
+
|
|
|
+ n = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
|
|
|
+ if diags:
|
|
|
+ n += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
|
|
|
+
|
|
|
+ neighbors = [
|
|
|
(x, y)
|
|
|
- for x, y in [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
|
|
|
+ for x, y in n
|
|
|
if 0 <= x < self.width and 0 <= y < self.height
|
|
|
]
|
|
|
+ # self._neighbors_cache[(x, y, diags)] = neighbors
|
|
|
+ return neighbors
|
|
|
|
|
|
@staticmethod
|
|
|
def create():
|
|
|
@@ -234,6 +343,8 @@ class Grid(BaseClass):
|
|
|
self.cells[(x, y)].update(scrap_amount, owner, units, recycler, can_build, can_spawn,
|
|
|
in_range_of_recycler)
|
|
|
|
|
|
+ log('update')
|
|
|
+
|
|
|
# update robots
|
|
|
self.units = {}
|
|
|
for cell in self.cells.values():
|
|
|
@@ -249,6 +360,11 @@ class Grid(BaseClass):
|
|
|
seen |= set(self.neighbors(*cell.pos))
|
|
|
self.recyclers[cell.pos] = Recycler(cell.x, cell.y, cell.owner, area)
|
|
|
|
|
|
+ self.update_possessions()
|
|
|
+ self.update_contigs()
|
|
|
+ self.update_tension_map()
|
|
|
+ self.update_nearest_enemy()
|
|
|
+
|
|
|
def owned_units(self):
|
|
|
return [r for r in self.units.values() if r.owner == Player.ME]
|
|
|
|
|
|
@@ -261,77 +377,471 @@ class Grid(BaseClass):
|
|
|
def opponent_recyclers(self):
|
|
|
return [r for r in self.recyclers.values() if r.owner == Player.OPPONENT]
|
|
|
|
|
|
- def act(self):
|
|
|
+ def tension(self, cell):
|
|
|
+ return self.index_tensions.get(cell.pos, 0)
|
|
|
+
|
|
|
+ def current_winner(self):
|
|
|
+ if self.me.territory > self.opponent.territory:
|
|
|
+ return Player.ME
|
|
|
+ elif self.me.territory < self.opponent.territory:
|
|
|
+ return Player.OPPONENT
|
|
|
+ else:
|
|
|
+ return Player.NONE
|
|
|
+
|
|
|
+ def update_possessions(self):
|
|
|
+ self.me.territory = 0
|
|
|
+ self.opponent.territory = 0
|
|
|
+
|
|
|
+ for c in self.cells.values():
|
|
|
+ if c.owned:
|
|
|
+ self.me.territory += 1
|
|
|
+ elif c.is_opponents:
|
|
|
+ self.opponent.territory += 1
|
|
|
+
|
|
|
+ def update_contigs(self):
|
|
|
+ """ contigs are isolated blocks of cells """
|
|
|
+ self.contigs = []
|
|
|
+ self.index_contigs = {}
|
|
|
+
|
|
|
+ seen = []
|
|
|
+ # build contigs
|
|
|
+ for c in self.cells.values():
|
|
|
+ if c.pos in seen or c.is_grass:
|
|
|
+ continue
|
|
|
+
|
|
|
+ contig = Contig(c.pos)
|
|
|
+ candidates = self.neighbors(*c.pos)
|
|
|
+
|
|
|
+ while candidates:
|
|
|
+
|
|
|
+ candidate = candidates.pop()
|
|
|
+ seen.append(candidate)
|
|
|
+
|
|
|
+ if self.cells[candidate].is_grass or self.cells[candidate].recycler or candidate in contig.area:
|
|
|
+ continue
|
|
|
+
|
|
|
+ for n in self.neighbors(*candidate):
|
|
|
+ if n not in contig.area:
|
|
|
+ candidates.append(n)
|
|
|
+
|
|
|
+ contig.area.append(candidate)
|
|
|
+
|
|
|
+ self.contigs.append(contig)
|
|
|
+
|
|
|
+ self.index_contigs = {p: None for p in self.cells}
|
|
|
+
|
|
|
+ for contig in self.contigs:
|
|
|
+ owners = set()
|
|
|
+
|
|
|
+ # update index
|
|
|
+ for p in contig.area:
|
|
|
+ self.index_contigs[p] = contig
|
|
|
+ owners.add(self.cells[p].owner)
|
|
|
+
|
|
|
+ if self.cells[p].owned and self.cells[p].units:
|
|
|
+ contig.has_robots = True
|
|
|
+
|
|
|
+ # status
|
|
|
+ if Player.ME in owners:
|
|
|
+ if Player.OPPONENT in owners:
|
|
|
+ contig.status = Contig.CONFLICTUAL
|
|
|
+ elif Player.NONE in owners:
|
|
|
+ contig.status = Contig.PARTIALLY_OWNED
|
|
|
+ else:
|
|
|
+ contig.status = Contig.OWNED
|
|
|
+ else:
|
|
|
+ contig.status = Contig.NOT_OWNED
|
|
|
+
|
|
|
+ def update_tension_map(self):
|
|
|
+ self.index_tensions = {}
|
|
|
+
|
|
|
+ for c in self.cells.values():
|
|
|
+
|
|
|
+ if not c.units:
|
|
|
+ continue
|
|
|
+
|
|
|
+ k = 1 if c.is_opponents else -1
|
|
|
+ tension = k * c.units
|
|
|
+ self.index_tensions[c.pos] = self.index_tensions.get(c.pos, 0) + tension
|
|
|
+
|
|
|
+ def update_nearest_enemy(self):
|
|
|
+ self.index_nearest_enemy = {pos: 0 for pos in self.cells}
|
|
|
+
|
|
|
+ for contig in self.contigs:
|
|
|
+ for pos in contig.area:
|
|
|
+ c = self.cells[pos]
|
|
|
+ if not c.owned or not any(self.cells[n].owned for n in self.neighbors(*pos)):
|
|
|
+ continue
|
|
|
+
|
|
|
+ nearest = None
|
|
|
+
|
|
|
+ for other_pos in contig.area:
|
|
|
+ other = self.cells[other_pos]
|
|
|
+ if not other.is_opponents:
|
|
|
+ continue
|
|
|
+
|
|
|
+ dist = self.manhattan(c.pos, other.pos)
|
|
|
+
|
|
|
+ if not nearest or dist < nearest[1]:
|
|
|
+ nearest = (other.pos, dist)
|
|
|
+
|
|
|
+ if nearest:
|
|
|
+ self.index_nearest_enemy[c.pos] = nearest[1]
|
|
|
+
|
|
|
+ def path(self, start, target):
|
|
|
+ nodes = Queue()
|
|
|
+ its, break_on = 0, 500
|
|
|
+
|
|
|
+ origin = PathNode(*start)
|
|
|
+ nodes.put(0, origin)
|
|
|
+
|
|
|
+ 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(f"<!> pathfinding broken from {start} to {target}")
|
|
|
+ return None
|
|
|
+
|
|
|
+ if (x, y) == current.parent:
|
|
|
+ continue
|
|
|
+
|
|
|
+ cell = self.cells[(x, y)]
|
|
|
+
|
|
|
+ if not cell.is_movable:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if cell.lifetime <= (current.cost + 1):
|
|
|
+ continue
|
|
|
+
|
|
|
+ cost = current.cost + 1
|
|
|
+ priority = cost + self.manhattan((x, y), target)
|
|
|
+
|
|
|
+ node = PathNode(x, y, current)
|
|
|
+ node.cost = cost
|
|
|
+ nodes.put(priority, node)
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+ def get_next_build_action(self):
|
|
|
|
|
|
build_actions = Queue()
|
|
|
|
|
|
# List available build actions
|
|
|
- places = [c for c in self.cells.values() if c.owned and c not in self.units]
|
|
|
- k0 = 100
|
|
|
- k_available_amount = -1
|
|
|
- k_already_exploited = 30
|
|
|
+ k0 = 80000
|
|
|
+ kmax_build = 70000
|
|
|
+ k_available_amount = -1000
|
|
|
+ k_already_exploited = 30000
|
|
|
+ k_area_unit = 500
|
|
|
+ k_area_owned = 1000
|
|
|
+ k_area_neutral = 0
|
|
|
+ k_area_opponents = -4000
|
|
|
+ k_area_opponent_unit = -1000
|
|
|
+ k_recyclers_owned = 5000
|
|
|
+ k_space_around = -1000
|
|
|
+ k_aggressive_build = 8000
|
|
|
+ k_aggressive_build_for_defense = -6000
|
|
|
+ k_aggressive_build_tension = -1000
|
|
|
+
|
|
|
+ k0_recyclers_owned = k_recyclers_owned * len(self.owned_recyclers())
|
|
|
+
|
|
|
+ for contig in self.contigs:
|
|
|
+ if contig.status in (Contig.OWNED, Contig.PARTIALLY_OWNED, Contig.NOT_OWNED):
|
|
|
+ # nothing to do
|
|
|
+ continue
|
|
|
|
|
|
- for place in places:
|
|
|
- action = Build(place.pos)
|
|
|
- k = k0
|
|
|
+ for p in contig.area:
|
|
|
+ place = self.cells[p]
|
|
|
+ if not place.owned or place.units or place.recycler:
|
|
|
+ continue
|
|
|
|
|
|
- area = [place.pos] + self.neighbors(*place.pos)
|
|
|
- for pos in area:
|
|
|
- k += k_available_amount * self.cells[pos].amount
|
|
|
- k += k_already_exploited * self.cells[pos].in_range_of_recycler
|
|
|
+ k = k0
|
|
|
+
|
|
|
+ area = [place.pos] + self.neighbors(*place.pos)
|
|
|
+ destination = Build.SUPPORT if not any(self.cells[p].is_opponents for p in area) else Build.AGGRESSIVE
|
|
|
+ k0_already_exploited = 0
|
|
|
+
|
|
|
+ for pos in area:
|
|
|
+ k += k_available_amount * self.cells[pos].amount
|
|
|
+ k0_already_exploited += k_already_exploited * self.cells[pos].in_range_of_recycler
|
|
|
|
|
|
- build_actions.put(k, action)
|
|
|
+ if self.cells[pos].owned:
|
|
|
+ k += k_area_owned
|
|
|
+ k += k_area_unit * self.cells[pos].units
|
|
|
+ elif self.cells[pos].is_opponents:
|
|
|
+ k += k_area_opponents
|
|
|
+ k += k_area_opponent_unit * self.cells[pos].units
|
|
|
+ else:
|
|
|
+ k += k_area_neutral
|
|
|
+
|
|
|
+ if destination == Build.SUPPORT:
|
|
|
+ neighborhood = {n for p in area for n in self.neighbors(*p) if n not in area}
|
|
|
+
|
|
|
+ for n in neighborhood:
|
|
|
+ if self.cells[n].amount > 1:
|
|
|
+ k += k_space_around
|
|
|
+
|
|
|
+ k += k0_recyclers_owned
|
|
|
+ k += k0_already_exploited
|
|
|
+
|
|
|
+ else:
|
|
|
+ k += k_aggressive_build
|
|
|
+ if self.current_winner() == Player.ME:
|
|
|
+ k += k_aggressive_build_for_defense
|
|
|
+
|
|
|
+ for n in self.neighbors(p[0], p[1], True):
|
|
|
+ k += k_aggressive_build_tension * self.index_tensions.get(n, 0)
|
|
|
+
|
|
|
+ if k > kmax_build:
|
|
|
+ continue
|
|
|
+
|
|
|
+ action = Build(place.pos, destination, k)
|
|
|
+ build_actions.put(k, action)
|
|
|
|
|
|
# List available spawn actions
|
|
|
- places = [c for c in self.cells.values() if c.owned]
|
|
|
- k0 = 60
|
|
|
- for place in places:
|
|
|
- action = Spawn(place.pos, 1)
|
|
|
- k = k0
|
|
|
- build_actions.put(k, action)
|
|
|
-
|
|
|
- # List available move actions
|
|
|
- move_actions_per_unit = {}
|
|
|
- # targets = Queue()
|
|
|
- k0 = 100
|
|
|
- k_blitzkrieg = -2
|
|
|
- k_occupy = -5
|
|
|
- k_destroy = -2
|
|
|
-
|
|
|
- for u in self.owned_units():
|
|
|
- move_actions_per_unit[u.pos] = Queue()
|
|
|
-
|
|
|
- nearest_enemy_cell = min(
|
|
|
- (c for c in self.cells.values() if c.is_opponents),
|
|
|
- key=lambda c: self.manhattan(c.pos, u.pos)
|
|
|
- )
|
|
|
- nearest_enemy_cell_dist = self.manhattan(nearest_enemy_cell.pos, u.pos)
|
|
|
-
|
|
|
- for pos in self.neighbors(*u.pos):
|
|
|
- place = self.cells[pos]
|
|
|
- action = Move(u.pos, place.pos, u.amount)
|
|
|
-
|
|
|
- if not place.owned and place.is_movable():
|
|
|
- k = k0
|
|
|
- k += k_blitzkrieg * (nearest_enemy_cell_dist - self.manhattan(place.pos, nearest_enemy_cell.pos))
|
|
|
- if place.is_opponents:
|
|
|
- k += k_occupy
|
|
|
- if place.pos in self.units:
|
|
|
- k += k_destroy * self.units[place.pos].amount
|
|
|
- move_actions_per_unit[u.pos].put(k, action)
|
|
|
+ k0 = 54000
|
|
|
+ kmax_spawn = 70000
|
|
|
+ k_opportunities = -1000
|
|
|
+ k_conquest = -3000
|
|
|
+ k_tension = -1000
|
|
|
+ k_dist_to_enemy = 400
|
|
|
+ k_bridgehead = -6000
|
|
|
+ k_reinforcement = 500
|
|
|
+
|
|
|
+ amount_default = 1
|
|
|
+ amount_bridgehead = 3
|
|
|
+ ki = 0 # little hack to avoid the while in queue.put
|
|
|
+
|
|
|
+ for contig in self.contigs:
|
|
|
+ if contig.status == Contig.OWNED:
|
|
|
+ # nothing to do
|
|
|
+ continue
|
|
|
+
|
|
|
+ if contig.status == Contig.PARTIALLY_OWNED and contig.has_robots:
|
|
|
+ # nothing to do
|
|
|
+ continue
|
|
|
+
|
|
|
+ for p in contig.area:
|
|
|
+ place = self.cells[p]
|
|
|
+ amount = amount_default
|
|
|
+
|
|
|
+ if not place.owned or not place.is_movable or place.unmovable_next_round:
|
|
|
+ continue
|
|
|
+
|
|
|
+ k = k0 + ki
|
|
|
+ ki += 1
|
|
|
+
|
|
|
+ tension = self.index_tensions.get(place.pos, 0)
|
|
|
+ for pos in self.neighbors(place.pos[0], place.pos[1], True):
|
|
|
+ tension += self.index_tensions.get(pos, 0)
|
|
|
+ k += k_tension * tension
|
|
|
+
|
|
|
+ for pos in self.neighbors(*place.pos):
|
|
|
+ n = self.cells[pos]
|
|
|
+ if not n.is_movable:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if n.is_neutral:
|
|
|
+ k += k_opportunities
|
|
|
+ elif n.is_opponents:
|
|
|
+ k += k_conquest
|
|
|
+
|
|
|
+ k += k_dist_to_enemy * self.index_nearest_enemy[p]
|
|
|
+
|
|
|
+ if sum(self.cells[n].is_opponents for n in self.neighbors(p[0], p[1], True)) > 5:
|
|
|
+ k += k_bridgehead
|
|
|
+ amount = amount_bridgehead
|
|
|
+
|
|
|
+ amount = max(sum([self.index_tensions.get(n, 0) for n in self.neighbors(*p)]), 1)
|
|
|
+
|
|
|
+ if k > kmax_spawn:
|
|
|
+ continue
|
|
|
+
|
|
|
+ for _ in range(amount):
|
|
|
+ action = Spawn(place.pos, 1, k)
|
|
|
+ build_actions.put(k, action)
|
|
|
+ k += k_reinforcement
|
|
|
+
|
|
|
+ # for action in build_actions.items[:8]:
|
|
|
+ # log(action)
|
|
|
+
|
|
|
+ if not build_actions:
|
|
|
+ return None
|
|
|
+
|
|
|
+ action = build_actions.get()
|
|
|
+
|
|
|
+ # update cells to take this order into account
|
|
|
+ place = self.cells[action.pos]
|
|
|
+
|
|
|
+ if type(action) is Build:
|
|
|
+ place.recycler = True
|
|
|
+ area = [action.pos] + self.neighbors(*action.pos)
|
|
|
+ self.recyclers[action.pos] = Recycler(action.pos[0], action.pos[1], Player.ME, area)
|
|
|
+ for pos in area:
|
|
|
+ self.cells[pos].in_range_of_recycler = True
|
|
|
+
|
|
|
+ if type(action) is Spawn:
|
|
|
+ place.units += action.amount
|
|
|
|
|
|
+ if action.pos in self.units:
|
|
|
+ self.units[action.pos].amount += action.amount
|
|
|
+ else:
|
|
|
+ self.units[action.pos] = RobotGroup(action.pos[0], action.pos[1], Player.ME, action.amount, 0)
|
|
|
+
|
|
|
+ if action.pos in self.index_tensions:
|
|
|
+ self.index_tensions[action.pos] -= action.amount
|
|
|
+ else:
|
|
|
+ self.index_tensions[action.pos] = -1 * action.amount
|
|
|
+
|
|
|
+ return action
|
|
|
+
|
|
|
+ def build_move_actions(self):
|
|
|
+ # List possible destinations per contig
|
|
|
+ move_actions = []
|
|
|
+
|
|
|
+ k0_position = 60000
|
|
|
+ k_position_distance = 2000
|
|
|
+ k_position_opponents = -5000
|
|
|
+ k_position_destroy = -3000
|
|
|
+ k_dist_to_enemy = 1000
|
|
|
+ k_expand = -800
|
|
|
+
|
|
|
+ for contig in self.contigs:
|
|
|
+ orders = []
|
|
|
+ units = []
|
|
|
+
|
|
|
+ if contig.status in (Contig.NOT_OWNED, Contig.OWNED):
|
|
|
+ # nothing to do
|
|
|
+ continue
|
|
|
+
|
|
|
+ # list destinations
|
|
|
+ for pos in contig.area:
|
|
|
+ c = self.cells[pos]
|
|
|
+ priority = k0_position
|
|
|
+
|
|
|
+ if c.owned or not c.is_movable:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not any((self.cells[n].owned and not self.cells[n].recycler) for n in self.neighbors(*pos)):
|
|
|
+ # on ne conserve que les cases voisines du territoire allié
|
|
|
+ continue
|
|
|
+
|
|
|
+ amount_needed = 1
|
|
|
+
|
|
|
+ if c.is_opponents:
|
|
|
+ priority += k_position_opponents
|
|
|
+ if c.units:
|
|
|
+ priority += k_position_destroy
|
|
|
+ amount_needed = c.amount
|
|
|
+
|
|
|
+ priority += k_dist_to_enemy * self.index_nearest_enemy[pos]
|
|
|
+
|
|
|
+ for n in self.neighbors(pos[0], pos[1], True):
|
|
|
+ if not self.cells[n].is_grass and not self.cells[n].owned:
|
|
|
+ priority += k_expand
|
|
|
+
|
|
|
+ order = MoveOrder(pos, amount_needed, priority)
|
|
|
+
|
|
|
+ orders.append(order)
|
|
|
+
|
|
|
+ # for move_order in orders:
|
|
|
+ # log(move_order)
|
|
|
+
|
|
|
+ # Prioritize units per distance
|
|
|
+ for pos in contig.area:
|
|
|
+ if pos in self.units:
|
|
|
+ unit = self.units[pos]
|
|
|
+ if not unit.owned or not unit.initial_amount:
|
|
|
+ continue
|
|
|
+ units.append(unit)
|
|
|
+
|
|
|
+ q = Queue()
|
|
|
+ for unit in units:
|
|
|
+ for order in orders:
|
|
|
+ destination = order.pos
|
|
|
+ dist = self.manhattan(unit.pos, destination)
|
|
|
+
|
|
|
+ priority = order.priority
|
|
|
+ priority += k_position_distance * dist
|
|
|
+
|
|
|
+ candidate = MoveOrderCandidate(order, unit, destination)
|
|
|
+ q.put(priority, candidate)
|
|
|
+
|
|
|
+ # affect orders
|
|
|
+ while q:
|
|
|
+ k, candidate = q.get_items()
|
|
|
+ if candidate.unit.has_played:
|
|
|
+ continue
|
|
|
+ if candidate.order.fulfilled:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # attention, on prend le montant initial car les unités spawned ce tour ne peuvent pas bouger
|
|
|
+ amount = min(candidate.order.amount, candidate.unit.initial_amount)
|
|
|
+
|
|
|
+ from_, to_ = candidate.unit.pos, candidate.destination
|
|
|
+
|
|
|
+ action = Move(from_, to_, amount, k)
|
|
|
+
|
|
|
+ candidate.unit.amount_played += amount
|
|
|
+ candidate.order.affected += amount
|
|
|
+
|
|
|
+ # update situation
|
|
|
+ # next_pos = None
|
|
|
+ # if to_ in self.neighbors(*from_):
|
|
|
+ # next_pos = to_
|
|
|
+ # # else:
|
|
|
+ # # path = self.path(from_, to_)
|
|
|
+ # # if path:
|
|
|
+ # # next_pos = path[0]
|
|
|
+ #
|
|
|
+ # self.cells[from_].units -= amount
|
|
|
+ # self.index_tensions[from_] += amount
|
|
|
+ # if next_pos:
|
|
|
+ # self.cells[next_pos].units -= amount
|
|
|
+ # candidate.unit.pos = next_pos
|
|
|
+ # self.index_tensions[next_pos] = self.index_tensions.get(next_pos, 0) - amount
|
|
|
+
|
|
|
+ move_actions.append(action)
|
|
|
+
|
|
|
+ return move_actions
|
|
|
+
|
|
|
+ def act(self):
|
|
|
+ # Resolve
|
|
|
actions = []
|
|
|
expanse = 0
|
|
|
- while build_actions and expanse <= self.me.matter:
|
|
|
- action = build_actions.get()
|
|
|
+ log('compute build actions')
|
|
|
+ while expanse < self.me.matter:
|
|
|
+ action = self.get_next_build_action()
|
|
|
+ if action is None:
|
|
|
+ break
|
|
|
+
|
|
|
+ if (expanse + action.COST) > self.me.matter:
|
|
|
+ break
|
|
|
+
|
|
|
actions.append(action)
|
|
|
expanse += action.COST
|
|
|
|
|
|
- for move_actions in move_actions_per_unit.values():
|
|
|
- if not move_actions:
|
|
|
- continue
|
|
|
- action = move_actions.get()
|
|
|
- actions.append(action)
|
|
|
+ log('computes move actions')
|
|
|
+ actions += self.build_move_actions()
|
|
|
+
|
|
|
+ if not actions:
|
|
|
+ actions.append(Wait())
|
|
|
+
|
|
|
+ log('resolve')
|
|
|
+ for action in actions:
|
|
|
+ log(action)
|
|
|
|
|
|
print(";".join([a.do() for a in actions]))
|
|
|
|
|
|
@@ -341,6 +851,8 @@ grid = Grid.create()
|
|
|
while True:
|
|
|
grid.update()
|
|
|
|
|
|
- # grid.print(lambda x: f"{x.amount:2d}")
|
|
|
+ # for contig in grid.contigs:
|
|
|
+ # log(contig)
|
|
|
+ # grid.print(lambda x: f"{grid.tension(x):02d}")
|
|
|
|
|
|
grid.act()
|