|
|
@@ -222,6 +222,7 @@ class Grid(BaseClass):
|
|
|
def __init__(self, width, height):
|
|
|
self.width = width
|
|
|
self.height = height
|
|
|
+ self.round = 1
|
|
|
|
|
|
self.cells = []
|
|
|
self.obstacles = []
|
|
|
@@ -229,10 +230,16 @@ class Grid(BaseClass):
|
|
|
|
|
|
self.index = {}
|
|
|
self.units = {}
|
|
|
- self.round = 1
|
|
|
self.threat = {}
|
|
|
- self.control = {}
|
|
|
- self.heat_map = {}
|
|
|
+ self.conversion_path = []
|
|
|
+
|
|
|
+ self.cult_leader = None
|
|
|
+ self.opponent_cult_leader = None
|
|
|
+ self.allied_cultists = []
|
|
|
+ self.owned_units = []
|
|
|
+ self.opponent_units = []
|
|
|
+ self.opponent_cultists = []
|
|
|
+ self.neutrals = []
|
|
|
|
|
|
def pre_compute(self):
|
|
|
self.cells = [(x, y) for x in range(self.width) for y in range(self.height)]
|
|
|
@@ -252,15 +259,42 @@ class Grid(BaseClass):
|
|
|
unit.y = y
|
|
|
unit.owner = owner
|
|
|
|
|
|
+ def update_index(self):
|
|
|
+ self.index = {}
|
|
|
+ self.cult_leader = None
|
|
|
+ self.opponent_cult_leader = None
|
|
|
+ self.allied_cultists = []
|
|
|
+ self.owned_units = []
|
|
|
+ self.opponent_units = []
|
|
|
+ self.opponent_cultists = []
|
|
|
+ self.neutrals = []
|
|
|
+
|
|
|
+ for unit in self.units.values():
|
|
|
+ self.index[(unit.x, unit.y)] = unit
|
|
|
+
|
|
|
+ if unit.owner == ME.id:
|
|
|
+ self.owned_units.append(unit)
|
|
|
+ if type(unit) is CultLeader:
|
|
|
+ self.cult_leader = unit
|
|
|
+ else:
|
|
|
+ self.allied_cultists.append(unit)
|
|
|
+ elif unit.owner == OPPONENT.id:
|
|
|
+ self.opponent_units.append(unit)
|
|
|
+ if type(unit) is CultLeader:
|
|
|
+ self.opponent_cult_leader = unit
|
|
|
+ else:
|
|
|
+ self.opponent_cultists.append(unit)
|
|
|
+ else:
|
|
|
+ self.neutrals.append(unit)
|
|
|
+
|
|
|
def update_threat_map(self):
|
|
|
self.threat = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
|
|
|
|
|
|
- sources = self.opponent_cultists()
|
|
|
+ sources = self.opponent_cultists
|
|
|
|
|
|
# On ajoute les neutres voisins du leader ennemi aux sources de menace possible
|
|
|
- opponent_cult_leader = self.opponent_cult_leader()
|
|
|
- if opponent_cult_leader:
|
|
|
- for pos in self.neighbors(*opponent_cult_leader.pos):
|
|
|
+ if self.opponent_cult_leader:
|
|
|
+ for pos in self.neighbors(*self.opponent_cult_leader.pos):
|
|
|
if pos in self.index and self.index[pos].neutral:
|
|
|
sources.append(self.index[pos])
|
|
|
|
|
|
@@ -279,104 +313,62 @@ class Grid(BaseClass):
|
|
|
if threat > self.threat[(x, y)]:
|
|
|
self.threat[(x, y)] = threat
|
|
|
|
|
|
- def update_control(self):
|
|
|
- self.control = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
|
|
|
-
|
|
|
- for u in self.allied_cultists():
|
|
|
- shooting_zone = self.zone(u.pos, Unit.SHOOTING_RANGE)
|
|
|
- for x, y in shooting_zone:
|
|
|
- dist = shooting_zone[(x, y)]
|
|
|
-
|
|
|
- if not self.line_of_sight(u.pos, (x, y)):
|
|
|
- continue
|
|
|
-
|
|
|
- control = Unit.SHOOTING_RANGE + 1 - dist
|
|
|
- if control > self.control[(x, y)]:
|
|
|
- self.control[(x, y)] = control
|
|
|
-
|
|
|
- def update_heat_map(self):
|
|
|
- lines = []
|
|
|
- self.heat_map = {(x, y): 0 for x in range(self.width) for y in range(self.height)}
|
|
|
-
|
|
|
- cult_leader = self.cult_leader()
|
|
|
-
|
|
|
- for o in self.opponent_cultists():
|
|
|
- if cult_leader:
|
|
|
- lines += self.line(o.pos, cult_leader.pos)
|
|
|
-
|
|
|
- for n in self.neutrals():
|
|
|
- lines += self.line(o.pos, n.pos)
|
|
|
+ def compute_conversion_path(self):
|
|
|
+ conversion_path = []
|
|
|
|
|
|
- for pos in lines:
|
|
|
- self.heat_map[pos] += 1
|
|
|
+ if self.cult_leader and self.neutrals:
|
|
|
+ conversion_path = self.get_conversion_path(
|
|
|
+ self.cult_leader,
|
|
|
+ key=(lambda pos: pos in self.index and self.index[pos].neutral),
|
|
|
+ limit=min(4, len(self.neutrals))
|
|
|
+ )
|
|
|
+ log(conversion_path)
|
|
|
+ self.conversion_path = conversion_path
|
|
|
|
|
|
def update(self):
|
|
|
- self.index = {}
|
|
|
- for unit in self.units.values():
|
|
|
- self.index[(unit.x, unit.y)] = unit
|
|
|
-
|
|
|
+ log('update indexes')
|
|
|
+ self.update_index()
|
|
|
self.update_threat_map()
|
|
|
- self.update_control()
|
|
|
- self.update_heat_map()
|
|
|
+ self.compute_conversion_path()
|
|
|
|
|
|
- def cult_leader(self):
|
|
|
- return next((u for u in self.units.values() if type(u) is CultLeader and u.owned), None)
|
|
|
+ # log(self.obstacles + [u.pos for u in self.allied_cultists])
|
|
|
+ # log([n.pos for n in self.neutrals])
|
|
|
|
|
|
- def allied_cultists(self):
|
|
|
- return [u for u in self.units.values() if type(u) is not CultLeader and u.owned]
|
|
|
+ def build_actions(self):
|
|
|
+ actions = Queue()
|
|
|
|
|
|
- def owned_units(self):
|
|
|
- return [u for u in self.units.values() if u.owned]
|
|
|
+ # Leader take cover
|
|
|
+ k0_protect_cult_leader = 30
|
|
|
+ k_protect_threat_level = -5
|
|
|
+ k_cover_threat = 10
|
|
|
+ k_cover_interest = -10
|
|
|
|
|
|
- def opponent_units(self):
|
|
|
- return [u for u in self.units.values() if u.opponent]
|
|
|
+ if self.cult_leader:
|
|
|
+ current_threat = self.threat[self.cult_leader.pos]
|
|
|
|
|
|
- def opponent_cult_leader(self):
|
|
|
- return next((u for u in self.units.values() if type(u) is CultLeader and not u.owned), None)
|
|
|
+ if current_threat:
|
|
|
+ covers = [n for n in self.neighbors(*self.cult_leader.pos) if self.can_move_on(self.cult_leader, n)]
|
|
|
|
|
|
- def opponent_cultists(self):
|
|
|
- return [u for u in self.units.values() if type(u) is not CultLeader and u.opponent]
|
|
|
+ for pos in covers:
|
|
|
+ action = ActionMove(self.cult_leader, pos, f'take cover (t: {current_threat})')
|
|
|
|
|
|
- def neutrals(self):
|
|
|
- return [u for u in self.units.values() if u.neutral]
|
|
|
+ interest = self.conversion_path and self.conversion_path.steps and pos in [s.pos for s in
|
|
|
+ self.conversion_path.steps]
|
|
|
|
|
|
- def list_actions(self):
|
|
|
- actions = Queue()
|
|
|
+ priority = k0_protect_cult_leader
|
|
|
+ priority += k_protect_threat_level * current_threat
|
|
|
+ priority += k_cover_threat * self.threat[pos]
|
|
|
+ priority += k_cover_interest * interest
|
|
|
+
|
|
|
+ actions.put(priority, action)
|
|
|
|
|
|
+ # Convert
|
|
|
k_convert_number = -10
|
|
|
k_convert_distance = 10
|
|
|
k_convert_danger = 20
|
|
|
- k_shoot_opponent_cultist = 15
|
|
|
- k_shoot_opponent_cult_leader = 10
|
|
|
- k0_protect_cult_leader = 30
|
|
|
- k_protect_threat_level = -5
|
|
|
- k_cover_threat = 10
|
|
|
- k_cover_interest = -10
|
|
|
- k0_position = 50
|
|
|
- k_position_distance = 10
|
|
|
- k_position_heat = -2
|
|
|
- k_position_danger = 10
|
|
|
- k_position_advantage = 10
|
|
|
-
|
|
|
- cult_leader = self.cult_leader()
|
|
|
- opponent_cult_leader = self.opponent_cult_leader()
|
|
|
-
|
|
|
- # log(self.obstacles + [u.pos for u in self.allied_cultists()])
|
|
|
- # log([n.pos for n in self.neutrals()])
|
|
|
-
|
|
|
- # compute conversion paths
|
|
|
- conversion_path = []
|
|
|
- if cult_leader and self.neutrals():
|
|
|
- conversion_path = self.get_conversion_path(
|
|
|
- cult_leader,
|
|
|
- key=(lambda pos: pos in self.index and self.index[pos].neutral),
|
|
|
- limit=min(4, len(self.neutrals()))
|
|
|
- )
|
|
|
- log(conversion_path)
|
|
|
|
|
|
- # Conversion des neutres
|
|
|
- if cult_leader and conversion_path:
|
|
|
- path = conversion_path.next_candidate()
|
|
|
+ if self.cult_leader and self.conversion_path:
|
|
|
+ path = self.conversion_path.next_candidate()
|
|
|
if path:
|
|
|
targets = [self.index[c] for c in path[-1].candidates]
|
|
|
|
|
|
@@ -386,69 +378,69 @@ class Grid(BaseClass):
|
|
|
priority += k_convert_danger * sum([self.threat[s.pos] for s in path])
|
|
|
|
|
|
if len(path) == 1:
|
|
|
- action = ActionConvert(cult_leader, targets[0])
|
|
|
+ action = ActionConvert(self.cult_leader, targets[0])
|
|
|
else:
|
|
|
- action = ActionMove(cult_leader, path[1].pos, f'go convert {",".join([str(t.id) for t in targets])}')
|
|
|
+ action = ActionMove(self.cult_leader, path[1].pos,
|
|
|
+ f'go convert {",".join([str(t.id) for t in targets])}')
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
- # Tire sur une unités ennemies
|
|
|
- targets = self.opponent_cultists()
|
|
|
- if opponent_cult_leader:
|
|
|
- targets.append(opponent_cult_leader)
|
|
|
-
|
|
|
- has_advantage = sum([t.hp for t in targets]) < sum([u.hp for u in self.owned_units()])
|
|
|
+ # Shoot
|
|
|
+ k_shoot_opponent_cultist = 8
|
|
|
+ k_shoot_opponent_cult_leader = 4
|
|
|
|
|
|
- for a in self.allied_cultists():
|
|
|
- for u in targets:
|
|
|
+ for a in self.allied_cultists:
|
|
|
+ for u in self.opponent_units:
|
|
|
shooting_distance = self.shooting_distance(a.pos, u.pos)
|
|
|
- if a.id == 4 and u.id == 1:
|
|
|
- log(self.line_of_sight(a.pos, u.pos))
|
|
|
- if shooting_distance and shooting_distance < u.SHOOTING_RANGE:
|
|
|
+ if not shooting_distance:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if shooting_distance <= u.SHOOTING_RANGE:
|
|
|
+ # la cible est à portée
|
|
|
action = ActionShoot(a, u)
|
|
|
|
|
|
priority = (k_shoot_opponent_cult_leader if type(
|
|
|
u) is CultLeader else k_shoot_opponent_cultist) * shooting_distance
|
|
|
|
|
|
actions.put(priority, action)
|
|
|
+ else:
|
|
|
+ # la cible est hors de portée, mais elle est plus faible et sans soutien
|
|
|
+ # TODO: implémenter
|
|
|
+ pass
|
|
|
|
|
|
# Position
|
|
|
- for a in self.allied_cultists():
|
|
|
- # on garde les trois points les plus chauds
|
|
|
- hot_spots = sorted(self.heat_map.items(), key=lambda p: p[1], reverse=True)[:3]
|
|
|
-
|
|
|
- results = self.discover(a, key=lambda x: x in [s[0] for s in hot_spots], limit=3)
|
|
|
-
|
|
|
- for path, target_pos in results:
|
|
|
- if not path:
|
|
|
- break
|
|
|
-
|
|
|
- heat = self.heat_map[target_pos]
|
|
|
-
|
|
|
- priority = k0_position
|
|
|
- priority += k_position_heat * heat
|
|
|
- priority += k_position_distance * len(path)
|
|
|
- priority += k_position_danger * sum(self.threat[p] for p in path)
|
|
|
+ k0_position = 50
|
|
|
+ k_advantage = 40
|
|
|
+ k_position_distance = 10
|
|
|
+ k_position_heat = -2
|
|
|
+ k_position_danger = 10
|
|
|
|
|
|
- action = ActionMove(a, path[0], f'go for hotspot {target_pos} by {path}')
|
|
|
+ advantage = len(self.owned_units) > len(self.opponent_units) + len(self.neutrals)
|
|
|
|
|
|
- actions.put(priority, action)
|
|
|
+ for a in self.allied_cultists:
|
|
|
|
|
|
- # Mise en sécurité du chef
|
|
|
- if cult_leader:
|
|
|
- current_threat = self.threat[cult_leader.pos]
|
|
|
- if current_threat:
|
|
|
- covers = [n for n in self.neighbors(*cult_leader.pos) if self.can_move_on(cult_leader, n)]
|
|
|
+ if self.threat[a.pos]:
|
|
|
+ # l'unité est déjà dans une zone à risque
|
|
|
+ # TODO: envisager un retrait
|
|
|
+ continue
|
|
|
|
|
|
- for pos in covers:
|
|
|
- action = ActionMove(cult_leader, pos, f'take cover (t: {current_threat})')
|
|
|
+ else:
|
|
|
+ # l'unité semble en sécurité, go to front-line
|
|
|
+ nearest_frontline = self.discover(a, key=lambda x: self.threat[x] == 1, limit=1)
|
|
|
+ if not nearest_frontline:
|
|
|
+ log(f"<!> {u.id} can not join nearest frontline")
|
|
|
+ continue
|
|
|
|
|
|
- interest = conversion_path and conversion_path.steps and pos in [s.pos for s in conversion_path.steps]
|
|
|
+ path, target = nearest_frontline[0]
|
|
|
+ if not path or len(path) == 1:
|
|
|
+ # already in place
|
|
|
+ continue
|
|
|
|
|
|
- priority = k0_protect_cult_leader
|
|
|
- priority += k_protect_threat_level * current_threat
|
|
|
- priority += k_cover_threat * self.threat[pos]
|
|
|
- priority += k_cover_interest * interest
|
|
|
+ priority = k0_position
|
|
|
+ priority += k_position_distance * len(path)
|
|
|
+ priority += k_position_danger * sum(self.threat[p] for p in path)
|
|
|
+ priority += k_advantage * advantage
|
|
|
|
|
|
+ action = ActionMove(a, path[0], f'go to frontline {target} by {path}')
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
return actions
|
|
|
@@ -680,7 +672,10 @@ class Grid(BaseClass):
|
|
|
try:
|
|
|
best_node = winners.get()
|
|
|
except IndexError:
|
|
|
- best_node = nodes.get()
|
|
|
+ if nodes:
|
|
|
+ best_node = nodes.get()
|
|
|
+ else:
|
|
|
+ best_node = origin
|
|
|
return ConversionPath.make_from_discovery_node(best_node)
|
|
|
|
|
|
def _repr_cell(self, pos):
|
|
|
@@ -729,7 +724,7 @@ while 1:
|
|
|
log(f"start round {GRID.round}")
|
|
|
GRID.update()
|
|
|
|
|
|
- actions = GRID.list_actions()
|
|
|
+ actions = GRID.build_actions()
|
|
|
|
|
|
print("\n" + GRID.graph(), file=sys.stderr)
|
|
|
|
|
|
@@ -745,4 +740,4 @@ while 1:
|
|
|
log(f"exec : {action}")
|
|
|
|
|
|
action.exec()
|
|
|
- GRID.round += 1
|
|
|
+ GRID.round += 1
|