|
|
@@ -131,15 +131,22 @@ PLAYERS_ORDER = sorted([ME, OPPONENT], key=lambda p: p.id)
|
|
|
|
|
|
|
|
|
class Threat(BaseClass):
|
|
|
- def __init__(self, unit, damages, target, line):
|
|
|
+ def __init__(self, unit, damages, pos, line):
|
|
|
self.unit = unit
|
|
|
self.damages = damages
|
|
|
- self.target = target
|
|
|
+ self.pos = pos
|
|
|
self.line = line if line else []
|
|
|
- self.fatal = damages >= target.hp
|
|
|
+
|
|
|
+ self.target = None
|
|
|
+ self.fatal = False
|
|
|
+ self.neutral = False
|
|
|
|
|
|
def __repr__(self):
|
|
|
- return f"<Threat (unit: {self.unit.id}, damages={self.damages}, target={self.target.id}, fatal={self.fatal}, line={self.line})>"
|
|
|
+ return f"<Threat (unit: {self.unit.id}, damages={self.damages}, pos={self.pos}, target={self.target.id if self.target else None}, fatal={self.fatal}, line={self.line})>"
|
|
|
+
|
|
|
+ def fatal_for(self, unit):
|
|
|
+ return self.damages >= unit.hp
|
|
|
+
|
|
|
|
|
|
class Unit(BaseClass):
|
|
|
TYPE_CULTIST = 0
|
|
|
@@ -149,18 +156,20 @@ class Unit(BaseClass):
|
|
|
OWNER_1 = 1
|
|
|
NO_OWNER = 2
|
|
|
|
|
|
+ MAX_HP = 10
|
|
|
SHOOTING_RANGE = 6
|
|
|
SHOOTING_MAX_DAMAGE = 7
|
|
|
|
|
|
def __init__(self, id_):
|
|
|
self.id = id_
|
|
|
- self.hp = 10
|
|
|
+ self.hp = Unit.MAX_HP
|
|
|
self.x = None
|
|
|
self.y = None
|
|
|
self.owner = None
|
|
|
|
|
|
- self.threats = []
|
|
|
+ self.targets = []
|
|
|
self.threaten_by = []
|
|
|
+ self.has_been_shooted = False
|
|
|
|
|
|
@property
|
|
|
def pos(self):
|
|
|
@@ -245,8 +254,10 @@ class Grid(BaseClass):
|
|
|
|
|
|
self.index = {}
|
|
|
self.units = {}
|
|
|
- self.threat = {}
|
|
|
+ self.threats_on = {}
|
|
|
+ self.max_threat = {}
|
|
|
self.conversion_path = ConversionPath()
|
|
|
+ self.ideal_conversion_path = ConversionPath()
|
|
|
|
|
|
self.cult_leader = None
|
|
|
self.opponent_cult_leader = None
|
|
|
@@ -255,6 +266,7 @@ class Grid(BaseClass):
|
|
|
self.opponent_units = []
|
|
|
self.opponent_cultists = []
|
|
|
self.neutrals = []
|
|
|
+ self.previous_hps = {}
|
|
|
|
|
|
def pre_compute(self):
|
|
|
self.cells = [(x, y) for x in range(self.width) for y in range(self.height)]
|
|
|
@@ -264,15 +276,31 @@ class Grid(BaseClass):
|
|
|
0 <= xn < self.width and 0 <= yn < self.height]
|
|
|
|
|
|
def reinit_round(self):
|
|
|
+ self.previous_hps = {u.id: u.hp for u in self.owned_units}
|
|
|
self.units = {}
|
|
|
+ self.threats_on = {pos: [] for pos in self.cells}
|
|
|
+ self.max_threat = {pos: 0 for pos in self.cells}
|
|
|
+ self.conversion_path = ConversionPath()
|
|
|
+ self.ideal_conversion_path = ConversionPath()
|
|
|
|
|
|
def update_unit(self, id_, type_, hp, x, y, owner):
|
|
|
+
|
|
|
+ # quand le bresenham foire, histoire de "corriger le tir", on retient le coup pour en tenir compte
|
|
|
+ has_been_shooted = False
|
|
|
+ if id_ in self.previous_hps:
|
|
|
+ if hp < self.previous_hps[id_]:
|
|
|
+ has_been_shooted = True
|
|
|
+
|
|
|
self.units[id_] = Unit(id_) if type_ != Unit.TYPE_CULT_LEADER else CultLeader(id_)
|
|
|
unit = self.units[id_]
|
|
|
unit.hp = hp
|
|
|
unit.x = x
|
|
|
unit.y = y
|
|
|
unit.owner = owner
|
|
|
+ unit.targets = []
|
|
|
+ unit.threaten_by = []
|
|
|
+ unit.has_been_shooted = has_been_shooted
|
|
|
+
|
|
|
|
|
|
def update_index(self):
|
|
|
self.index = {}
|
|
|
@@ -302,78 +330,83 @@ class Grid(BaseClass):
|
|
|
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)}
|
|
|
+ def update_threat(self):
|
|
|
|
|
|
- sources = self.opponent_cultists
|
|
|
+ sources = self.allied_cultists + self.opponent_cultists
|
|
|
|
|
|
- # On ajoute les neutres voisins du leader ennemi aux sources de menace possible
|
|
|
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])
|
|
|
+ for n in self.neighbors(*self.opponent_cult_leader.pos):
|
|
|
+ if n in self.index and self.index[n].neutral:
|
|
|
+ sources.append(self.index[n])
|
|
|
|
|
|
for u in sources:
|
|
|
- shooting_zone = self.zone(u.pos, Unit.SHOOTING_RANGE)
|
|
|
- for x, y in shooting_zone:
|
|
|
- dist = shooting_zone[(x, y)]
|
|
|
+ threat_zone = self.zone(u.pos, Unit.SHOOTING_RANGE)
|
|
|
+ for pos in threat_zone:
|
|
|
+ if pos in self.obstacles:
|
|
|
+ continue
|
|
|
|
|
|
- if not self.line_of_sight(u.pos, (x, y)):
|
|
|
+ line_of_sight = self.line_of_sight(u.pos, pos)
|
|
|
+ if not line_of_sight:
|
|
|
continue
|
|
|
|
|
|
- threat = Unit.SHOOTING_RANGE + 1 - dist
|
|
|
- if u.neutral:
|
|
|
- threat //= 2
|
|
|
+ dist = self.manhattan(u.pos, pos)
|
|
|
|
|
|
- if threat > self.threat[(x, y)]:
|
|
|
- self.threat[(x, y)] = threat
|
|
|
+ damages = u.SHOOTING_MAX_DAMAGE - dist
|
|
|
+ threat = Threat(u, damages, pos, line_of_sight)
|
|
|
|
|
|
- for u in self.opponent_units:
|
|
|
- u.threat = []
|
|
|
- for a in self.owned_units:
|
|
|
- line_of_sight = self.line_of_sight(u.pos, a.pos)
|
|
|
- if not line_of_sight:
|
|
|
- continue
|
|
|
+ self.threats_on[pos].append(threat)
|
|
|
|
|
|
- dist = self.manhattan(u.pos, a.pos)
|
|
|
- if dist <= u.SHOOTING_RANGE:
|
|
|
- damages = u.SHOOTING_MAX_DAMAGE - dist
|
|
|
- threat = Threat(u, damages, a, line_of_sight)
|
|
|
- u.threats.append(threat)
|
|
|
- a.threaten_by.append(threat)
|
|
|
+ if not u.owned and self.max_threat[pos] < damages:
|
|
|
+ self.max_threat[pos] = damages
|
|
|
|
|
|
- for a in self.owned_units:
|
|
|
- a.threat = []
|
|
|
- for u in self.opponent_units:
|
|
|
- line_of_sight = self.line_of_sight(a.pos, u.pos)
|
|
|
- if not line_of_sight:
|
|
|
- continue
|
|
|
+ if pos in self.index:
|
|
|
+ target = self.index[pos]
|
|
|
+ if target.owner == u.owner:
|
|
|
+ continue
|
|
|
+
|
|
|
+ threat.target = target
|
|
|
+ threat.fatal = damages >= target.hp
|
|
|
+ threat.neutral = u.neutral
|
|
|
|
|
|
- dist = self.manhattan(u.pos, a.pos)
|
|
|
- if dist <= u.SHOOTING_RANGE:
|
|
|
- damages = u.SHOOTING_MAX_DAMAGE - dist
|
|
|
- threat = Threat(a, damages, u, line_of_sight)
|
|
|
- a.threats.append(threat)
|
|
|
- u.threaten_by.append(threat)
|
|
|
+ u.targets.append(threat)
|
|
|
+ target.threaten_by.append(threat)
|
|
|
+
|
|
|
+ # fallback pour compenser le bresenham inégal
|
|
|
+ for a in self.owned_units:
|
|
|
+ for t in a.threaten_by:
|
|
|
+ if not t in a.targets:
|
|
|
+ threat = Threat(a, t.damages, a.pos, t.line[::-1])
|
|
|
+ threat.target = t.unit
|
|
|
+ threat.neutral = False
|
|
|
+ a.targets.append(threat)
|
|
|
|
|
|
def compute_conversion_path(self):
|
|
|
conversion_path = ConversionPath()
|
|
|
+ ideal_conversion_path = ConversionPath()
|
|
|
|
|
|
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),
|
|
|
+ moving_key=lambda pos: self.can_move_on(self.cult_leader, pos),
|
|
|
limit=min(4, len(self.neutrals))
|
|
|
)
|
|
|
log(f"conversion : {conversion_path}")
|
|
|
+
|
|
|
+ ideal_conversion_path = self.get_conversion_path(
|
|
|
+ self.cult_leader,
|
|
|
+ key=(lambda pos: pos in self.index and self.index[pos].neutral),
|
|
|
+ moving_key=lambda pos: self.can_move_on(self.cult_leader, pos) or (pos in self.index and self.index[pos].owned),
|
|
|
+ limit=min(4, len(self.neutrals))
|
|
|
+ )
|
|
|
+ log(f"ideal conversion : {ideal_conversion_path}")
|
|
|
self.conversion_path = conversion_path
|
|
|
+ self.ideal_conversion_path = ideal_conversion_path
|
|
|
|
|
|
def update(self):
|
|
|
- log('update indexes')
|
|
|
self.update_index()
|
|
|
- self.update_threat_map()
|
|
|
+ self.update_threat()
|
|
|
self.compute_conversion_path()
|
|
|
- log('indexes updated')
|
|
|
|
|
|
# log(self.obstacles + [u.pos for u in self.allied_cultists])
|
|
|
# log([n.pos for n in self.neutrals])
|
|
|
@@ -384,22 +417,33 @@ class Grid(BaseClass):
|
|
|
advantage = len(self.owned_units) > len(self.opponent_units)
|
|
|
sure_advantage = len(self.owned_units) > (len(self.opponent_units) + len(self.neutrals))
|
|
|
|
|
|
- for u in self.allied_cultists:
|
|
|
- if u.threats:
|
|
|
- log(f"Threats : {u.id} - {u.threats}")
|
|
|
+ for u in self.owned_units:
|
|
|
if u.threaten_by:
|
|
|
- log(f"Threaten : {u.id} - {u.threaten_by}")
|
|
|
+ log(f"Threats : {u.targets}")
|
|
|
+ log(f"Threats : {u.threaten_by}")
|
|
|
+ elif u.has_been_shooted:
|
|
|
+ log(f"<!!!> {u.id} has been shooted by unknown threat!")
|
|
|
|
|
|
# Leader take cover
|
|
|
- k0_protect_cult_leader = 30
|
|
|
+ k0_protect_cult_leader = 25
|
|
|
k_protect_threat_level = -5
|
|
|
k_protect_worth_risk = 3
|
|
|
k_cover_interest = -10
|
|
|
|
|
|
if self.cult_leader:
|
|
|
- current_threat = self.threat[self.cult_leader.pos]
|
|
|
+ threat_level = self.max_threat[self.cult_leader.pos]
|
|
|
+
|
|
|
+ # pas de menace apparente, mais le leader a reçu un tir depuis le tour précédent (pbm de bresenham)
|
|
|
+ if not threat_level and self.cult_leader.has_been_shooted:
|
|
|
+ log("<!!> leader seems not threaten but has been hurted!")
|
|
|
+ threat_level = 10
|
|
|
+
|
|
|
+ # une unité voisine peut être convertie, on laisse ça à l'action convert
|
|
|
+ threats = [t
|
|
|
+ for t in self.cult_leader.threaten_by
|
|
|
+ if t.unit not in self.neighbors(*self.cult_leader.pos)]
|
|
|
|
|
|
- if current_threat:
|
|
|
+ if threats or self.cult_leader.has_been_shooted:
|
|
|
covers = [n for n in self.neighbors(*self.cult_leader.pos) if self.can_move_on(self.cult_leader, n)]
|
|
|
|
|
|
for pos in covers:
|
|
|
@@ -407,105 +451,164 @@ class Grid(BaseClass):
|
|
|
self.conversion_path.steps])
|
|
|
|
|
|
priority = k0_protect_cult_leader
|
|
|
- priority += k_protect_threat_level * (current_threat - self.threat[pos])
|
|
|
+ priority += k_protect_threat_level * (threat_level - self.max_threat[pos])
|
|
|
priority += k_cover_interest * interest
|
|
|
- priority += k_protect_worth_risk * (self.cult_leader.hp - (current_threat + 1))
|
|
|
+ priority += k_protect_worth_risk * (self.cult_leader.hp - (threat_level + 1))
|
|
|
|
|
|
- action = ActionMove(self.cult_leader, pos, f'take cover (current: {current_threat}, new: {self.threat[pos]})')
|
|
|
+ action = ActionMove(self.cult_leader, pos,
|
|
|
+ f'take cover (current: {threat_level}, new: {self.max_threat[pos]})')
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
# Convert
|
|
|
k0_convert = 15
|
|
|
k_convert_number = -10
|
|
|
- k_convert_distance = 10
|
|
|
k_convert_danger = 20
|
|
|
+ k_convert_threat_on = 3
|
|
|
+ k_convert_hurted = 1
|
|
|
+ k_convert_neighbor_threat = -15
|
|
|
+ k_convert_neighbor_opponent = -20
|
|
|
+ k_convert_distance = 10
|
|
|
|
|
|
- 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]
|
|
|
-
|
|
|
+ if self.cult_leader:
|
|
|
+ immediate_targets = [
|
|
|
+ self.index[n] for n in self.neighbors(*self.cult_leader.pos)
|
|
|
+ if n in self.index and self.index[n] not in self.owned_units and self.index[n] is not self.opponent_cult_leader
|
|
|
+ ]
|
|
|
+ for target in immediate_targets:
|
|
|
priority = k0_convert
|
|
|
- priority += k_convert_number * len(targets)
|
|
|
- priority += k_convert_distance * len(path)
|
|
|
- priority += k_convert_danger * sum([self.threat[s.pos] for s in path])
|
|
|
+ priority += k_convert_neighbor_threat * (target.neutral and target in [t.unit for t in self.cult_leader.threaten_by])
|
|
|
+ priority += k_convert_neighbor_opponent * target.opponent
|
|
|
+ priority += k_convert_threat_on * max([t.damages for t in self.threats_on[target.pos] if t.unit.opponent], default=0)
|
|
|
+
|
|
|
+ action = ActionConvert(self.cult_leader, target)
|
|
|
+ actions.put(priority, action)
|
|
|
+
|
|
|
+ # Go Convert
|
|
|
+ if not immediate_targets and self.conversion_path:
|
|
|
+ path = self.conversion_path.next_candidate()
|
|
|
+ if path:
|
|
|
+ targets = [self.index[c] for c in path[-1].candidates]
|
|
|
+
|
|
|
+ priority = k0_convert
|
|
|
+ priority += k_convert_number * len(targets)
|
|
|
+ priority += k_convert_distance * len(path)
|
|
|
+ priority += k_convert_danger * sum([self.max_threat[s.pos] for s in path])
|
|
|
+
|
|
|
+ for target in targets:
|
|
|
+ priority += k_convert_hurted * (Unit.MAX_HP - target.hp)
|
|
|
+ priority += k_convert_threat_on * max([t.damages for t in self.threats_on[target.pos] if t.unit.opponent], default=0)
|
|
|
|
|
|
- if len(path) == 1:
|
|
|
- action = ActionConvert(self.cult_leader, targets[0])
|
|
|
- else:
|
|
|
action = ActionMove(self.cult_leader, path[1].pos,
|
|
|
f'go convert {",".join([str(t.id) for t in targets])}')
|
|
|
- actions.put(priority, action)
|
|
|
+ actions.put(priority, action)
|
|
|
|
|
|
# Shoot opponent units
|
|
|
k0_shoot = 0
|
|
|
k_shoot_opponent_cultist = 6
|
|
|
- k_shoot_opponent_leader = 5
|
|
|
+ k_shoot_opponent_leader = 6 if self.neutrals else 10
|
|
|
+ k_shoot_fatal = -5
|
|
|
k_shoot_kill_before_he_kills = -5
|
|
|
k_shoot_sacrifice = 30
|
|
|
k0_runaway = 10
|
|
|
k_runaway_threat = 10
|
|
|
|
|
|
for a in self.allied_cultists:
|
|
|
- if not a.threats:
|
|
|
+ if not a.targets:
|
|
|
continue
|
|
|
|
|
|
- for threat in a.threats:
|
|
|
+ for threat in a.targets:
|
|
|
priority = k0_shoot
|
|
|
|
|
|
+ if threat.target.neutral:
|
|
|
+ continue
|
|
|
+
|
|
|
# Il est probable que l'unité se fera tirer dessus par l'ennemi le plus proche: quel résultat?
|
|
|
- can_be_killed_by = [t for t in a.threaten_by if t.fatal]
|
|
|
+ deadly_threaten_by = [t for t in a.threaten_by if t.fatal]
|
|
|
+ if not a.threaten_by and a.has_been_shooted:
|
|
|
+ log(f"<!!> unit {a.id} seems not threaten but has been hurted!")
|
|
|
+ deadly_threaten_by.append(Threat(self.opponent_cult_leader, 10, a.pos, []))
|
|
|
+
|
|
|
+ if not a.threaten_by and a.has_been_shooted:
|
|
|
+ deadly_threaten_by.append(Threat(self.opponent_cult_leader, 10, a.pos, []))
|
|
|
|
|
|
- if can_be_killed_by and not threat.fatal:
|
|
|
+ if deadly_threaten_by and not threat.fatal:
|
|
|
# run away! (si possible)
|
|
|
- worth_sacrificing = any(t.target is self.cult_leader or (t.fatal and t.target is not a) for t in threat.target.threats)
|
|
|
covers = [n for n in self.neighbors(*a.pos) if self.can_move_on(a, n)]
|
|
|
|
|
|
- if covers and not worth_sacrificing:
|
|
|
+ if covers:
|
|
|
for cover in covers:
|
|
|
- if self.threat[cover] < a.hp and not any(self.line_of_sight(u.unit.pos, cover) for u in can_be_killed_by):
|
|
|
+ threats_on_cover = [t for t in self.threats_on[cover] if not t.unit.owned]
|
|
|
+ if not any(t.fatal_for(a) for t in threats_on_cover):
|
|
|
action = ActionMove(a, cover, f'run away!')
|
|
|
priority += k0_runaway
|
|
|
- priority += k_runaway_threat * self.threat[cover]
|
|
|
+ priority += k_runaway_threat * self.max_threat[cover]
|
|
|
actions.put(priority, action)
|
|
|
continue
|
|
|
|
|
|
log(f'<!> {a.id} seems lost')
|
|
|
|
|
|
- elif any(t for t in can_be_killed_by if t.unit != threat.target) and threat.fatal:
|
|
|
+ elif threat.fatal and deadly_threaten_by:
|
|
|
# l'unité peut tuer la cible, mais sera probablement tuée par une autre unité ennemie
|
|
|
priority += k_shoot_sacrifice
|
|
|
|
|
|
if not threat.fatal:
|
|
|
k = k_shoot_opponent_leader if threat.target is self.opponent_cult_leader else k_shoot_opponent_cultist
|
|
|
priority += k * len(threat.line)
|
|
|
+ else:
|
|
|
+ priority += k_shoot_fatal
|
|
|
|
|
|
# peut tuer un adversaire avant qu'il ne tue un allié
|
|
|
- priority += k_shoot_kill_before_he_kills * any(t.fatal and t.target is not a for t in threat.target.threats)
|
|
|
+ priority += k_shoot_kill_before_he_kills * any(t.fatal and t.target is not a for t in threat.target.targets)
|
|
|
|
|
|
- action = ActionShoot(a, threat.target, f'shoot from {a.pos} (damages: {threat.damages}, fatal: {threat.fatal}, line: {threat.line})')
|
|
|
+ action = ActionShoot(a, threat.target,
|
|
|
+ f'shoot from {a.pos} (damages: {threat.damages}, pos: {threat.pos}, target: {threat.target}, fatal: {threat.fatal}, line: {threat.line})')
|
|
|
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
+ # Intercept
|
|
|
+ k0_intercept = 20
|
|
|
+ position_hp_min = 5
|
|
|
+ k_intercept_diff = 3
|
|
|
+
|
|
|
+ for a in self.allied_cultists:
|
|
|
+ if a.hp < position_hp_min:
|
|
|
+ continue
|
|
|
+
|
|
|
+ for o in (u for u in self.owned_units if u is not a):
|
|
|
+ neighbors = self.neighbors(*a.pos)
|
|
|
+
|
|
|
+ for threat in o.threaten_by:
|
|
|
+ if threat.fatal and not threat.fatal_for(a):
|
|
|
+ for n in neighbors:
|
|
|
+ if n in threat.line:
|
|
|
+ action = ActionMove(a, n, f'intercept {threat}')
|
|
|
+
|
|
|
+ priority = k0_intercept
|
|
|
+ priority = k_intercept_diff * (a.hp - o.hp)
|
|
|
+
|
|
|
+ actions.put(priority, action)
|
|
|
+
|
|
|
# Position: go to front line
|
|
|
k0_position = 50
|
|
|
k_position_advantage = 40
|
|
|
k_position_distance = 3 # on veut privilégier les plus près, mais pas non plus décourager ceux de l'arrière...
|
|
|
- position_hp_min = 5
|
|
|
|
|
|
for a in self.allied_cultists:
|
|
|
if a.hp < position_hp_min:
|
|
|
continue
|
|
|
- if self.threat[a.pos]:
|
|
|
+
|
|
|
+ if self.max_threat[a.pos]:
|
|
|
# unit already under threat, let shoot action manage this
|
|
|
continue
|
|
|
- if any(self.threat[n] for n in self.neighbors(*a.pos)):
|
|
|
+
|
|
|
+ if any(self.max_threat[n] for n in self.neighbors(*a.pos)):
|
|
|
# unit already on frontline
|
|
|
continue
|
|
|
|
|
|
# l'unité semble en sécurité, go to front-line
|
|
|
- nearest_frontline = self.discover(a, key=lambda x: self.can_move_on(a, x) and self.threat[x] == 1,
|
|
|
+ nearest_frontline = self.discover(a, key=lambda x: self.can_move_on(a, x) and self.max_threat[x] >= 1,
|
|
|
limit=1)
|
|
|
+ # log(f"{a.id} - {nearest_frontline}")
|
|
|
if not nearest_frontline:
|
|
|
log(f"<!> {a.id} can not join nearest frontline")
|
|
|
continue
|
|
|
@@ -520,8 +623,43 @@ class Grid(BaseClass):
|
|
|
action = ActionMove(a, path[0], f'go to frontline {target} by {path}')
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
+ # Move out
|
|
|
+ k0_move_out = 20
|
|
|
+ k_move_out_gain = -3
|
|
|
+ k_move_out_still_in_path_but_later = 5
|
|
|
+ k_move_out_default_gain = 4
|
|
|
+
|
|
|
+ if self.cult_leader:
|
|
|
+ actual_path = [s.pos for s in (self.conversion_path.next_candidate() or [])]
|
|
|
+ ideal_path = [s.pos for s in (self.ideal_conversion_path.next_candidate() or [])]
|
|
|
+
|
|
|
+ if ideal_path:
|
|
|
+ for a in self.allied_cultists:
|
|
|
+ if a.pos in ideal_path:
|
|
|
+ gain = len(actual_path) - len(ideal_path) if actual_path else k_move_out_default_gain
|
|
|
+
|
|
|
+ if gain > 0:
|
|
|
+ for n in self.neighbors(*a.pos):
|
|
|
+ if not self.can_move_on(a, n):
|
|
|
+ continue
|
|
|
+
|
|
|
+ priority = k0_move_out
|
|
|
+ still_in_the_way = (n in ideal_path)
|
|
|
+
|
|
|
+ if still_in_the_way:
|
|
|
+ if ideal_path.index(n) > ideal_path.index(a.pos):
|
|
|
+ priority += k_move_out_still_in_path_but_later
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+
|
|
|
+ priority += k_move_out_gain * gain
|
|
|
+
|
|
|
+ action = ActionMove(a, n, f'move out of the way (gain: {gain}, in the way: {still_in_the_way})')
|
|
|
+ actions.put(priority, action)
|
|
|
+
|
|
|
+
|
|
|
# Shoot neutral units:
|
|
|
- k_shoot_dangerous_neutral_threat = 2
|
|
|
+ k_shoot_dangerous_neutral_threat = 3
|
|
|
k_shoot_dangerous_neutral_distance = 8
|
|
|
dangerous_neutral_distance_limit = 3
|
|
|
|
|
|
@@ -544,8 +682,8 @@ class Grid(BaseClass):
|
|
|
target_pos] < len(path):
|
|
|
threaten_neutrals_from_leader[target_pos] = len(path)
|
|
|
|
|
|
- log(f"Nearest from opp. leader: {neutrals_threaten}")
|
|
|
- log(f"Nearest from own leader: {threaten_neutrals_from_leader}")
|
|
|
+ # log(f"Nearest from opp. leader: {neutrals_threaten}")
|
|
|
+ # log(f"Nearest from own leader: {threaten_neutrals_from_leader}")
|
|
|
|
|
|
lost_causes = {}
|
|
|
for target, dist in neutrals_threaten.items():
|
|
|
@@ -574,7 +712,6 @@ class Grid(BaseClass):
|
|
|
|
|
|
priority = k_shoot_dangerous_neutral_distance * shooting_distance
|
|
|
priority += k_shoot_dangerous_neutral_threat * dist
|
|
|
- # log(self.line_of_sight(a.pos, u.pos))
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
# Last hope: take risks
|
|
|
@@ -588,7 +725,7 @@ class Grid(BaseClass):
|
|
|
continue
|
|
|
|
|
|
for n in self.neighbors(*a.pos):
|
|
|
- if not self.threat[n]:
|
|
|
+ if not self.max_threat[n]:
|
|
|
continue
|
|
|
|
|
|
ongoing_fights = []
|
|
|
@@ -610,11 +747,11 @@ class Grid(BaseClass):
|
|
|
action = ActionMove(a, n, f"last hope, moving")
|
|
|
|
|
|
priority = k0_last_chance
|
|
|
- priority += k_last_chance_danger * max(d for _, d in ongoing_fights)
|
|
|
+ priority += k_last_chance_danger * max([d for _, d in ongoing_fights], default=0)
|
|
|
actions.put(priority, action)
|
|
|
|
|
|
- # TODO: si les deux leaders sont de chaque côté d'un neutre, même si notre leader pourrait convertir le premier, la menace l'emporte et il se barre... repenser le mécanisme de 'take cover'
|
|
|
- # TODO: ne pas aller convertir les neutres qui sont sous le feu ennemi, ou au moins baisser leur priorité (surtout s'ils sont la seule chose entre le leader et les ennemis)
|
|
|
+ # TODO: prévoir une action intercept, où une unité alliée se place sur le chemin d'une menace fatale, si l'unité a suffisamment de pv pour affronter la menace sans mourir
|
|
|
+ # TODO: ne pas aller convertir les neutres qui sont sous le feu ennemi, ou au moins baisser leur priorité (surtout s'ils sont la seule chose entre le leader et les ennemis);
|
|
|
# TODO: augmenter la prise de risque pour la conversion, parfois ça peut changer le résultat si le leader a tous ses pvs
|
|
|
# TODO: cf Capture d’écran 2022-09-24 014757.png -> même si pas de ligne entre deux pos, la position peut se retrouver sur une ligne de mire avec une autre cible... snif
|
|
|
# TODO: l'algo actuel va privilégier un tir sur un neutre lointain voisin du leader ennemi, plutôt que sur un ennemi en train de nous tirer dessus
|
|
|
@@ -638,7 +775,7 @@ class Grid(BaseClass):
|
|
|
def moving_cost(self, unit, pos):
|
|
|
if not self.can_move_on(unit, pos):
|
|
|
return -1
|
|
|
- return 1 + self.threat[pos]
|
|
|
+ return 1 + self.max_threat[pos]
|
|
|
|
|
|
@staticmethod
|
|
|
def manhattan(from_, to_):
|
|
|
@@ -651,14 +788,12 @@ class Grid(BaseClass):
|
|
|
|
|
|
def zone(self, pos, radius):
|
|
|
x0, y0 = pos
|
|
|
- zone = {}
|
|
|
-
|
|
|
+ zone = []
|
|
|
for x in range(max(x0 - radius, 0), min(x0 + radius, self.width)):
|
|
|
for y in range(max(y0 - radius, 0), min(y0 + radius, self.height)):
|
|
|
dist = self.manhattan(pos, (x, y))
|
|
|
if dist <= radius:
|
|
|
- zone[(x, y)] = dist
|
|
|
-
|
|
|
+ zone.append((x, y))
|
|
|
return zone
|
|
|
|
|
|
@classmethod
|
|
|
@@ -800,7 +935,7 @@ class Grid(BaseClass):
|
|
|
nodes.append(node)
|
|
|
return paths
|
|
|
|
|
|
- def get_conversion_path(self, unit, key, limit=5):
|
|
|
+ def get_conversion_path(self, unit, key, moving_key, limit=5):
|
|
|
""" essaies de trouver le meilleur chemin pour relier des cases dont au moins une voisine valide
|
|
|
la condition 'key' (dans la limite de 'limit')"""
|
|
|
nodes = Queue()
|
|
|
@@ -829,10 +964,10 @@ class Grid(BaseClass):
|
|
|
current = nodes.get()
|
|
|
|
|
|
for pos in self.neighbors(*current):
|
|
|
- if not self.can_move_on(unit, pos):
|
|
|
+ if not moving_key(pos):
|
|
|
continue
|
|
|
|
|
|
- moving_cost = 1 + self.threat[pos]
|
|
|
+ moving_cost = 1 + self.max_threat[pos]
|
|
|
|
|
|
matches = []
|
|
|
for n in self.neighbors(*pos):
|
|
|
@@ -870,8 +1005,8 @@ class Grid(BaseClass):
|
|
|
return ConversionPath.make_from_discovery_node(best_node)
|
|
|
|
|
|
def _repr_cell(self, pos):
|
|
|
- return f"{self.threat[pos]}"
|
|
|
- # return f"{self.control[pos]}/{self.threat[pos]}"
|
|
|
+ return f"{self.max_threat[pos]}"
|
|
|
+ # return f"{self.control[pos]}/{self.max_threat[pos]}"
|
|
|
# return self.heat_map[pos]
|
|
|
|
|
|
if pos in self.obstacles:
|