| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017 |
- import sys
- import time
- import heapq
- import math
- debug = True
- t0 = time.time()
- # todo: attacking strategy: place one guy nearer to opponent hq
- # todo: considerate shielding monsters threatening opponent hq
- # todo: review the order heroes are playing in each strategy
- def log(*msg):
- if debug:
- print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr, flush=True)
- def time_to(total, step):
- """ number of steps to reach total """
- return total // step + (1 if total % step > 0 else 0)
- class BaseClass:
- def __repr__(self):
- return f"<{self.__class__.__name__}: {self.__dict__}>"
- class Queue(BaseClass):
- def __init__(self):
- self.items = []
- def __bool__(self):
- return bool(self.items)
- def __repr__(self):
- return str(self.items)
- def values(self):
- return (v for _, v in self.items)
- def put(self, priority, item):
- while priority in [p for p, _ in self.items]:
- priority += 1
- heapq.heappush(self.items, (priority, item))
- def get(self):
- return heapq.heappop(self.items)[1]
- def get_items(self):
- return heapq.heappop(self.items)
- class GridItem(BaseClass):
- def __init__(self, pos=(-1, -1)):
- self._previous_pos = None
- self._pos = pos
- @property
- def x(self):
- return self.pos[0]
- @property
- def y(self):
- return self.pos[1]
- @property
- def pos(self):
- return self._pos
- @pos.setter
- def pos(self, pos):
- self._previous_pos = self.pos
- self._pos = pos
- @property
- def previous_pos(self):
- return self._previous_pos
- class HeadQuarter(GridItem):
- SIGHT = 6000
- def __init__(self, pos):
- super().__init__(pos)
- self.health = 3
- self.mana = 10
- class Role:
- NAME = "None"
- POSITION = (1500, 1500)
- ALT_POSITIONS = []
- def __repr__(self):
- return self.__class__.__name__
- class Defender(Role):
- NAME = "Defender"
- POSITION = (1250, 1250)
- class DefenderRight(Defender):
- NAME = "Defender (right)"
- POSITION = (500, 3000)
- class DefenderLeft(Defender):
- NAME = "Defender (left)"
- POSITION = (3000, 500)
- class GateKeeper(Role):
- NAME = "Gatekeeper"
- POSITION = (4500, 3000)
- class GateKeeperRight(GateKeeper):
- NAME = "Gatekeeper (right)"
- POSITION = (1500, 5000)
- class GateKeeperLeft(GateKeeper):
- NAME = "Gatekeeper (left)"
- POSITION = (5500, 1000)
- class Hunter(Role):
- NAME = "Hunter"
- POSITION = (6000, 4000)
- ALT_POSITIONS = [(3500, 6500), (8000, 1500)]
- class HunterRight(Hunter):
- NAME = "Hunter (right)"
- POSITION = (3500, 6500)
- ALT_POSITIONS = [(1500, 7500), (6000, 4000)]
- class HunterLeft(Hunter):
- NAME = "Hunter (left)"
- POSITION = (8000, 1500)
- ALT_POSITIONS = [(9000, 500), (6000, 4000)]
- class Attacker(Role):
- NAME = "Attacker"
- POSITION = (7500, 4000)
- ALT_POSITIONS = [(5000, 6500), (8000, 2000)]
- class AttackerRight(Attacker):
- NAME = "Attacker (right)"
- POSITION = (5000, 6500)
- ALT_POSITIONS = [(1500, 7500), (6000, 4000)]
- class AttackerLeft(Attacker):
- NAME = "Attacker (left)"
- POSITION = (8000, 2000)
- ALT_POSITIONS = [(9000, 500), (6000, 4000)]
- class Saboteur(Role):
- NAME = "Saboteur"
- POSITION = (15000, 6000)
- ALT_POSITIONS = [(12000, 3500), (10000, 6000)]
- class Strategy(BaseClass):
- def __init__(self, roles, default=Defender):
- self.roles = roles
- self.pos = 0
- self.default = default
- def pick(self):
- """ Get the next role in line for the hero, or defender by default """
- try:
- role = self.roles[self.pos]
- self.pos += 1
- return role
- except IndexError:
- return self.default
- class StrategyFullDefense(Strategy):
- NAME = "Full Defense"
- def __init__(self):
- super().__init__([Defender, DefenderLeft, DefenderRight])
- class StrategyDefensive(Strategy):
- NAME = "Defensive"
- def __init__(self):
- super().__init__([Attacker, GateKeeper, Defender])
- class StrategyBalanced(Strategy):
- NAME = "Balanced"
- def __init__(self):
- super().__init__([Attacker, Hunter, Defender])
- class StrategyPrudentFarm(Strategy):
- NAME = "Farm (prudent)"
- def __init__(self):
- super().__init__([HunterLeft, HunterRight, GateKeeper])
- class StrategyFarm(Strategy):
- NAME = "Farm"
- def __init__(self):
- super().__init__([Attacker, HunterLeft, HunterRight])
- class StrategyAggressive(Strategy):
- NAME = "Aggressive"
- def __init__(self):
- super().__init__([Saboteur, Attacker, GateKeeper])
- class Action(BaseClass):
- def __init__(self, priority=1000):
- self.priority = priority
- self.feasibility = 0
- self.done = False
- self._pos = None
- @property
- def pos(self):
- return self._pos
- def do(self):
- self.done = True
- def __repr__(self):
- return f"{self.__class__.__name__} [{self.priority}/{self.feasibility}]"
- class Move(Action):
- def __init__(self, pos, priority=1000):
- super().__init__(priority)
- self._pos = pos
- def do(self):
- super().do()
- print(f"MOVE {self.pos[0]} {self.pos[1]}")
- def __repr__(self):
- return f"{self.__class__.__name__} {self.pos} [{self.priority}/{self.feasibility}]"
- class Explore(Move):
- pass
- class MoveAt(Action):
- def __init__(self, target, priority=1000):
- super().__init__(priority)
- self.target = target
- @property
- def pos(self):
- return self.target.pos
- def do(self):
- super().do()
- print(f"MOVE {self.pos[0]} {self.pos[1]}")
- def __repr__(self):
- return f"{self.__class__.__name__} {self.target.id} [{self.priority}/{self.feasibility}]"
- class Defend(MoveAt):
- pass
- class Hunt(MoveAt):
- pass
- class Reposition(Move):
- pass
- class Cast(Action):
- SPELL = ""
- MANA_COST = 10
- def __init__(self, priority=1000):
- super().__init__(priority)
- self.params = []
- def do(self):
- super().do()
- print(f"SPELL {self.SPELL} {' '.join(map(str, self.params))}")
- def __repr__(self):
- return f"{self.__class__.__name__} {' '.join(map(str, self.params))} [{self.priority}/{self.feasibility}]"
- class CastWind(Cast):
- SPELL = "WIND"
- RADIUS = 1280
- DEPTH = 2200
- def __init__(self, pos, priority=1000):
- super().__init__(priority)
- self.params = pos
- class CastControl(Cast):
- SPELL = "CONTROL"
- RANGE = 2200
- def __init__(self, target_id, pos, priority=1000):
- super().__init__(priority)
- x, y = pos
- self.params = [target_id, x, y]
- class CastShield(Cast):
- SPELL = "SHIELD"
- RANGE = 2200
- DURATION = 12
- def __init__(self, target_id, priority=1000):
- super().__init__(priority)
- self.params = [target_id]
- class Wait(Action):
- def do(self):
- print(f"WAIT")
- class Entity(GridItem):
- TYPE_MONSTER = 0
- TYPE_HERO = 1
- TYPE_OPPONENT_HERO = 2
- def __init__(self, id_, pos=(-1, -1)):
- super().__init__(pos)
- self.id = id_ # Unique identifier
- self.shield_life = -1 # Count down until shield spell fades
- self.health = -1 # Remaining health of this monster
- self.is_controlled = False
- # Computed properties
- self.visible = False
- self.distance_to_hq = None
- self.moving_time_to_hq = None
- self.distance_to_opponent_hq = None
- self.moving_time_to_opponent_hq = None
- self.distance_from_entity = {}
- self.moving_time_from_entity = {}
- self.go_toward_hq = False
- self.go_toward_opponent_hq = False
- self.is_on_my_side = False
- self.is_on_opponent_side = False
- self.camper = False
- def init_round(self):
- self.visible = False
- self.distance_to_hq = None
- self.moving_time_to_hq = None
- self.distance_to_opponent_hq = None
- self.moving_time_to_opponent_hq = None
- self.distance_from_entity = {}
- self.moving_time_from_entity = {}
- self.go_toward_hq = False
- self.go_toward_opponent_hq = False
- self.is_on_my_side = False
- self.is_on_opponent_side = False
- self.camper = False
- class Hero(Entity):
- ATTACK = 2
- ATTACK_RANGE = 800
- SPEED = 800
- SIGHT = 2200
- def __init__(self, id_):
- super().__init__(id_)
- self.role = Role
- self.current_role_pos = None
- class OpponentHero(Hero):
- def __init__(self, id_):
- super().__init__(id_)
- class Monster(Entity):
- THREAT_FOR_ME = 1
- THREAT_FOR_OPPONENT = 2
- NO_THREAT = 0
- SPEED = 400
- THREAT_RANGE = 5000
- ATTACK_RANGE = 300
- def __init__(self, id_):
- super().__init__(id_)
- self.near_base = False
- self.threat_for = self.NO_THREAT
- self.vx = 0 # Trajectory of this monster
- self.vy = 0
- @property
- def next_pos(self):
- return self.x + self.vx, self.y + self.vy
- @property
- def threats_hq(self):
- return self.threat_for == self.THREAT_FOR_ME
- @property
- def threats_opponent_hq(self):
- return self.threat_for == self.THREAT_FOR_OPPONENT
- @property
- def alive(self):
- return self.health > 0
- class Grid(BaseClass):
- MAX_X = 17630
- MAX_Y = 9000
- def __init__(self):
- self.threat_level = 0
- self.round = 0
- self._index = {}
- self.hq = None
- self.opponent_hq = None
- self.strategy = None
- @property
- def index(self):
- return self._index
- def get(self, _id):
- return self._index[_id]
- @property
- def entities(self):
- return [e for e in self._index.values() if e.visible]
- @property
- def heroes(self):
- return sorted([v for v in self.entities if type(v) is Hero], key=lambda x: x.id)
- @property
- def monsters(self):
- return [v for v in self.entities if type(v) is Monster]
- @property
- def opponent_heroes(self):
- return [v for v in self.entities if type(v) is OpponentHero]
- def init_game(self):
- self.hq = HeadQuarter([int(i) for i in input().split()])
- self.opponent_hq = HeadQuarter((Grid.MAX_X, Grid.MAX_Y) if self.hq.x == 0 else (0, 0))
- _ = int(input()) # Heroes per player, always 3
- def update_entity(self, entity):
- entity.distance_to_hq = self.distance(self.hq.pos, entity.pos)
- entity.moving_time_to_hq = self.moving_time_to(entity, self.hq.pos)
- entity.distance_to_opponent_hq = self.distance(self.opponent_hq.pos, entity.pos)
- entity.moving_time_to_opponent_hq = self.moving_time_to(entity, self.opponent_hq.pos)
- entity.distance_from_entity = {e.id: self.distance(e.pos, entity.pos) for e in self.entities if
- e.id != entity.id}
- entity.moving_time_from_entity = {e.id: self.moving_time_to(entity, e.pos) for e in self.entities if
- e.id != entity.id}
- if type(entity) is Monster:
- entity.go_toward_hq = (entity.vx <= 0 and entity.vy <= 0 and self.hq.x == 0 or
- entity.vx >= 0 and entity.vy >= 0 and self.hq.x != 0)
- entity.go_toward_opponent_hq = (entity.vx >= 0 and entity.vy >= 0 and self.opponent_hq.x != 0 or
- entity.vx <= 0 and entity.vy <= 0 and self.opponent_hq.x == 0)
- entity.is_on_my_side = entity.distance_to_hq < entity.distance_to_opponent_hq
- entity.is_on_opponent_side = not entity.is_on_my_side
- if type(entity) is OpponentHero:
- if entity.previous_pos and entity.pos == entity.previous_pos and entity.is_on_my_side:
- entity.camper = True
- def init_round(self):
- """ get inputs and reinit the state of entities """
- self.round += 1
- log(f"Round {self.round}")
- # *** Update HQ status
- self.hq.health, self.hq.mana = [int(j) for j in input().split()]
- self.opponent_hq.health, self.opponent_hq.mana = [int(j) for j in input().split()]
- # *** Reinit the entities state
- for entity in self.entities:
- entity.init_round()
- # *** Update entities
- entity_count = int(input()) # Amount of heroes and monsters you can see
- for i in range(entity_count):
- _id, _type, _x, _y, shield_life, is_controlled, health, vx, vy, near_base, threat_for = [int(j) for j in
- input().split()]
- # Entity is not indexed already
- if _id not in self.index:
- if _type == Entity.TYPE_MONSTER:
- entity = Monster(_id)
- elif _type == Entity.TYPE_HERO:
- entity = Hero(_id)
- elif _type == Entity.TYPE_OPPONENT_HERO:
- entity = OpponentHero(_id)
- else:
- log("Error: unknown type ({_type})")
- break
- self.index[_id] = entity
- # Update entity
- entity = self.get(_id)
- entity.pos = (_x, _y)
- entity.shield_life = shield_life
- entity.is_controlled = is_controlled
- entity.health = health
- entity.visible = True
- if type(entity) is Monster:
- entity.near_base = bool(near_base)
- entity.threat_for = threat_for
- entity.vx = vx
- entity.vy = vy
- # purge index
- self.purge()
- # update entities
- for entity in self.entities:
- self.update_entity(entity)
- # *** define strategy and roles
- self.threat_level = self.compute_threat_level()
- self.strategy = self.define_strategy()
- for hero in self.heroes:
- role = self.strategy.pick()
- if hero.role != role:
- hero.current_role_pos = None
- elif hero.pos in self.get_role_positions(hero.role):
- # remember as the last occupied role position
- hero.current_role_pos = hero.pos
- hero.role = role
- log(f'## Threat: {self.threat_level} - Strategy: {self.strategy.NAME} ##')
- log("Heroes: {}".format(','.join(
- [f"<{h.id},P:{h.pos},M:{h.moving_time_to_hq},O:{int(h.is_on_opponent_side)}>" for h in self.heroes])))
- log("Monsters: {}".format(','.join(
- [f"<{m.id},P:{m.pos},T:{int(m.threats_hq)},V:{int(m.visible)},M:{m.moving_time_to_hq},"
- f"N:{int(m.go_toward_hq)},O:{int(m.go_toward_opponent_hq)},S:{int(m.shield_life > 0)}>" for m
- in self.monsters])))
- log("Opponents: {}".format(','.join(
- [f"<{o.id},P:{o.pos},S:{o.shield_life},M:{o.moving_time_to_hq},C:{int(o.camper)}>" for o in self.opponent_heroes])))
- def purge(self):
- """ remove dead entities from the index """
- self._index = {k: e for k, e in self._index.items() if type}
- index = {}
- for entity in self.entities:
- if isinstance(entity, Monster):
- if self.visible(entity.next_pos) and not entity.visible:
- # entity should be visible, but has not been seen, presumed dead
- continue
- index[entity.id] = entity
- self._index = index
- def compute_threat_level(self):
- level = 0
- for monster in self.monsters:
- if monster.threats_hq:
- level += (monster.health + monster.shield_life) * max(24 - monster.moving_time_to_hq, 0)
- for enemy in self.opponent_heroes:
- if enemy.is_on_my_side:
- level += 50
- return level
- def define_strategy(self):
- # *** Define strategy
- if self.threat_level > 1400:
- return StrategyFullDefense()
- if self.threat_level > 1000:
- return StrategyDefensive()
- elif self.threat_level > 800:
- return StrategyBalanced()
- if self.round < 40:
- if self.threat_level:
- return StrategyPrudentFarm()
- else:
- return StrategyFarm()
- elif self.round < 100:
- return StrategyBalanced()
- else:
- return StrategyAggressive()
- def get_action_for(self, hero):
- actions = []
- k0_defend = 10
- k0_reposition = 250
- k0_hunt = 200
- k0_cast_wind = 150
- k0_cast_offensive_wind = 220
- k0_cast_control = 100
- k0_cast_control_monster = 100
- k0_cast_offensive_control = 200
- k0_cast_shield = 150
- k0_cast_shield_on_monster = 80
- k_attack_time = 5
- k_hero_nearer = 30
- k_defend_against_shielded = -20
- k_defender_priority_bonus = -50
- k_go_toward_hq = -30
- k_hunt_distance = 5
- k_hunt_pv = 2
- k_position_distance_from_pos = -8
- k_cast_wind_threats = -1
- k_cast_wind_enemy = -20
- k_cast_wind_enemy_on_my_side = -10
- k_cast_wind_enemy_near_hq = -5
- k_cast_wind_proximity = 5
- k_cast_wind_bad_target = 10
- k_cast_wind_last_defense = -30
- k_cast_wind_last_defense_limit = 10
- k_control_health = -2
- k_control_time = 5
- k_control_going_toward = -20
- k_shield_enemy_near = -20
- k_shield_monster = -1
- k_shield_monster_distance = 3
- k_cast_shield_distance = 10
- k_camper_bonus = -50
- k_cast_control_on_opponent_defender = 10
- k_cast_control_on_opponent_defender_threat = -1
- defend_threshold = 18
- min_control_hp = 15
- min_shield_distance = max(CastWind.RADIUS, CastControl.RANGE)
- min_shield_threat = 60
- offensive_buff = -1
- offensive_buff_control = -2
- k_feasibility_moving_time = 10
- k_feasibility_casting_cost = 5
- k_feasibility_far_from_pos = 5
- k_feasibility_defender_far_from_pos = 10
- min_mana_priority = 240
- # *** Defend
- for monster in self.monsters:
- if monster.threats_hq and monster.moving_time_to_hq < defend_threshold:
- priority = k0_defend
- # Réduire la priorité en fonction de la distance
- priority += (k_attack_time * monster.moving_time_to_hq)
- # TODO: réduire priority pour chaque monstre suppl que l'attaque pourrait toucher
- priority += k_defend_against_shielded * int(monster.shield_life > 0)
- # Réduire la priorité pour chaque héros plus proche du qg
- for other_hero in [h for h in self.heroes if h is not hero]:
- if other_hero.moving_time_to_hq < monster.moving_time_to_hq:
- priority += k_hero_nearer
- if isinstance(hero, (Defender, GateKeeper)):
- priority += k_defender_priority_bonus
- action = Defend(monster, priority)
- actions.append(action)
- # *** Position
- role_pos = self.get_role_position(hero)
- if hero.pos != role_pos:
- priority = k0_reposition
- priority += k_position_distance_from_pos * self.moving_time_to(hero, role_pos)
- action = Reposition(role_pos, priority)
- actions.append(action)
- # *** Hunt
- if hero.is_on_my_side and \
- not issubclass(hero.role, (Defender, GateKeeper)) and \
- not (issubclass(hero.role, Saboteur) and hero.is_on_my_side):
- for monster in self.monsters:
- if monster.threats_opponent_hq:
- # it's defense domain or not worth it
- continue
- priority = k0_hunt
- priority += k_hunt_distance * hero.moving_time_from_entity[monster.id]
- priority += k_go_toward_hq * monster.go_toward_hq
- priority += k_hunt_pv * monster.health
- action = Hunt(monster, priority)
- actions.append(action)
- # *** Cast WIND on threats
- if self.hq.mana >= CastWind.MANA_COST:
- affected = [e for e in self.in_wind_area(hero) if not e.shield_life]
- if affected:
- priority = k0_cast_wind
- # Affected entities
- priority += k_cast_wind_threats * sum([m.health for m in affected if type(m) is Monster and m.threats_hq])
- priority += k_cast_wind_enemy * len([m for m in affected if type(m) is OpponentHero])
- priority += k_cast_wind_enemy_on_my_side * len([m for m in affected if type(m) is OpponentHero and m.is_on_my_side])
- priority += k_cast_wind_enemy_near_hq * sum([m.moving_time_to_hq for m in affected if type(m) is OpponentHero if m.distance_to_hq < HeadQuarter.SIGHT])
- priority += k_camper_bonus * len([m for m in affected if type(m) is OpponentHero and m.is_on_my_side and m.camper])
- # Hq average proximity
- priority += k_cast_wind_proximity * hero.moving_time_to_hq
- # last defence
- priority += sum([k_cast_wind_last_defense * (k_cast_wind_last_defense_limit - m.moving_time_to_hq) for m in affected if type(m) is Monster and m.moving_time_to_hq < k_cast_wind_last_defense_limit])
- action = CastWind(self.opponent_hq.pos, priority)
- actions.append(action)
- # *** Cast CONTROL on approaching monsters
- if self.hq.mana >= (CastWind.MANA_COST + CastControl.MANA_COST):
- possible_targets = [m for m in self.monsters if
- m.distance_from_entity[hero.id] < CastControl.RANGE and not m.shield_life]
- for monster in possible_targets:
- if monster.health < min_control_hp:
- # too weak
- continue
- if monster.go_toward_opponent_hq:
- # already targeting enemy
- continue
- if monster.distance_to_hq <= (monster.THREAT_RANGE + monster.SPEED):
- # too late...
- continue
- if monster.is_controlled:
- # already controlled
- continue
- priority = k0_cast_control
- priority += k_control_health * monster.health
- priority += k_control_time * min(monster.moving_time_to_opponent_hq, monster.moving_time_to_hq)
- priority += k_control_going_toward * monster.go_toward_hq
- action = CastControl(monster.id, self.opponent_hq.pos, priority)
- actions.append(action)
- # *** Cast CONTROL on campers
- if self.hq.mana >= (CastWind.MANA_COST + CastControl.MANA_COST):
- possible_targets = [e for e in self.opponent_heroes if
- e.distance_from_entity[hero.id] < CastControl.RANGE
- and not e.shield_life and e.camper]
- for enemy in possible_targets:
- if enemy.is_controlled:
- # already controlled
- continue
- priority = k0_cast_control
- action = CastControl(enemy.id, self.opponent_hq.pos, priority)
- actions.append(action)
- # *** Cast SHIELD on self
- opponents_within_casting_range = [o.id for o in self.opponent_heroes if
- o.distance_from_entity[hero.id] < min_shield_distance]
- if hero.shield_life == 0 and \
- self.hq.mana >= (CastWind.MANA_COST + CastShield.MANA_COST) and \
- opponents_within_casting_range\
- and self.threat_level > min_shield_threat\
- and hero.is_on_my_side:
- # log(f"{hero.id}: shield against {[(o.id, o.pos, o.distance_from_entity[hero.id]) for o in self.opponent_heroes]}")
- priority = k0_cast_shield
- priority += k_shield_enemy_near * len(opponents_within_casting_range)
- priority += k_cast_shield_distance * min(hero.moving_time_to_hq, hero.moving_time_to_opponent_hq)
- action = CastShield(hero.id, priority)
- actions.append(action)
- if hero.is_on_opponent_side and not isinstance(self.strategy, (StrategyDefensive, StrategyFullDefense)):
- threat_on_opponent_hq = sum(m.health + m.shield_life for m in self.monsters if m.threats_opponent_hq)
- threat_on_opponent_hq -= 10 * len([o for o in self.opponent_heroes if o.distance_to_opponent_hq < Monster.THREAT_RANGE])
- log(f"Offensive buff: {threat_on_opponent_hq}")
- for monster in self.monsters:
- # cast control on monsters near opponent hq
- if self.hq.mana >= (CastWind.MANA_COST + CastControl.MANA_COST) and monster.distance_from_entity[hero.id] < CastControl.RANGE:
- if monster.health < min_control_hp:
- # too weak
- continue
- if monster.go_toward_opponent_hq:
- # already targeting enemy
- continue
- if monster.shield_life > 0:
- continue
- priority = k0_cast_control_monster
- action = CastControl(monster.id, self.opponent_hq.pos, priority)
- actions.append(action)
- if threat_on_opponent_hq > 50:
- for monster in self.monsters:
- # cast shield on monsters threatening opponent hq
- if self.hq.mana >= (CastWind.MANA_COST + CastShield.MANA_COST) and monster.distance_from_entity[hero.id] < CastShield.RANGE:
- if not monster.threats_opponent_hq or not monster.go_toward_opponent_hq:
- # not targeting enemy
- continue
- if monster.shield_life > 0:
- continue
- priority = k0_cast_shield_on_monster
- priority += k_shield_monster_distance * monster.moving_time_to_opponent_hq
- priority += k_shield_monster * monster.health
- action = CastShield(monster.id, priority)
- actions.append(action)
- # cast wind on monsters threatening opponent hq
- affected = [e for e in self.in_wind_area(hero) if not e.shield_life]
- if self.hq.mana >= (CastWind.MANA_COST + CastShield.MANA_COST) and affected:
- priority = k0_cast_offensive_wind
- priority += k_cast_wind_threats * sum([m.health for m in affected if type(m) is Monster and m.threats_opponent_hq and not m.shield_life])
- priority += k_cast_wind_bad_target * len([e for e in affected if type(e) is OpponentHero and not e.shield_life])
- priority += k_cast_wind_proximity * hero.moving_time_to_opponent_hq
- priority += offensive_buff * threat_on_opponent_hq
- action = CastWind(self.opponent_hq.pos, priority)
- actions.append(action)
- # cast wind on enemies defending opponent hq
- affected = [e for e in self.in_wind_area(hero) if not e.shield_life]
- if self.hq.mana >= (CastWind.MANA_COST + CastShield.MANA_COST) and affected:
- priority = k0_cast_offensive_wind
- priority += k_cast_wind_enemy * len([e for e in affected if type(e) is OpponentHero and not e.shield_life])
- priority += k_cast_wind_bad_target * len([e for e in affected if type(e) is Monster and not e.shield_life])
- priority += k_cast_wind_proximity * hero.moving_time_to_opponent_hq
- priority += offensive_buff * threat_on_opponent_hq
- action = CastWind(self.hq.pos, priority)
- actions.append(action)
- for enemy in self.opponent_heroes:
- if self.hq.mana >= (CastWind.MANA_COST + CastControl.MANA_COST) and enemy.distance_from_entity[hero.id] < CastControl.RANGE:
- if enemy.is_controlled:
- # already controlled
- continue
- if enemy.shield_life:
- continue
- priority = k0_cast_offensive_control
- priority += k_cast_control_on_opponent_defender * enemy.moving_time_to_opponent_hq
- priority += k_cast_control_on_opponent_defender_threat * threat_on_opponent_hq
- priority += offensive_buff_control * threat_on_opponent_hq
- action = CastControl(enemy.id, self.hq.pos, priority)
- actions.append(action)
- # Estimate feasibility
- for action in actions:
- action.feasibility = -1
- # ignore impossibles actions
- if isinstance(action, Defend):
- if action.target.moving_time_to_hq < hero.moving_time_to_hq:
- # too late...
- continue
- action.feasibility = 0
- if action.pos and not isinstance(action, Reposition):
- action.feasibility += k_feasibility_moving_time * self.moving_time_to(hero, action.pos)
- if isinstance(action, Cast):
- action.feasibility += k_feasibility_casting_cost * action.MANA_COST
- action.feasibility += k_feasibility_far_from_pos * self.moving_time_to(hero, hero.current_role_pos or hero.role.POSITION)
- actions = sorted([a for a in actions if a.feasibility >= 0], key=lambda a: a.priority + a.feasibility)
- try:
- log(actions)
- return actions[0]
- except IndexError:
- return Wait()
- def update_after_action(self, hero, action):
- if isinstance(action, (Move, MoveAt)):
- hero.pos = self.position_after_moving(hero, action.pos, hero.SPEED)
- # log(f"{hero.id}: move to {action.pos}, new pos: {hero.pos}")
- for monster in self.in_attack_area(hero):
- monster.health -= hero.ATTACK
- if monster.health <= 0:
- del self._index[monster.id]
- if isinstance(action, Cast):
- self.hq.mana -= action.MANA_COST
- if isinstance(action, CastWind):
- affected = self.in_wind_area(hero)
- for entity in affected:
- entity.pos = self.position_after_moving(entity, action.params, action.DEPTH)
- self.update_entity(entity)
- if isinstance(action, CastShield):
- self.index[action.params[0]].shield_life = CastShield.DURATION
- if isinstance(action, CastControl):
- target_id, x, y = action.params
- target = self.index[target_id]
- target.pos = self.position_after_moving(target, (x, y), target.SPEED)
- self.update_entity(target)
- target.is_controlled = True
- if type(target) is Monster:
- target.go_toward_opponent_hq = True
- if not isinstance(action, Reposition):
- # reinit alternative positions if hero has acted elsewhere
- hero.current_role_pos = None
- def play(self, hero):
- action = self.get_action_for(hero)
- log(f"> {action}")
- action.do()
- self.update_after_action(hero, action)
- @staticmethod
- def manhattan(from_, to_):
- xa, ya = from_
- xb, yb = to_
- return abs(xa - xb) + abs(ya - yb)
- @staticmethod
- def distance(from_, to_):
- xa, ya = from_
- xb, yb = to_
- return int(math.sqrt((xa - xb) ** 2 + abs(ya - yb) ** 2))
- @staticmethod
- def moving_time_to(entity, target):
- distance = Grid.distance(entity.pos, target)
- return time_to(distance, entity.SPEED)
- @staticmethod
- def position_after_moving(entity, pos, speed):
- """ get the entity position after it moved toward `pos` from `speed` """
- x0, y0 = entity.pos
- x1, y1 = pos
- dx, dy = (x1 - x0) / (x1 or 1), (y1 - y0) / (y1 or 1)
- return x0 + int(dx * speed), y0 + int(dy * speed)
- def visible(self, pos):
- return self.distance(self.hq.pos, pos) < self.hq.SIGHT or \
- any(self.distance(h.pos, pos) < h.SIGHT for h in self.heroes)
- def get_role_positions(self, role):
- positions = [role.POSITION] + role.ALT_POSITIONS
- if self.hq.x != 0:
- return [(self.MAX_X - x, self.MAX_Y - y) for x, y in positions]
- else:
- return positions
- def get_role_position(self, hero):
- """ get the position for the given role """
- role_positions = self.get_role_positions(hero.role)
- pos = role_positions[0]
- # log(f"{hero.pos} - {hero.current_role_pos} - {role_positions}")
- if hero.current_role_pos:
- try:
- i = role_positions.index(hero.current_role_pos)
- pos = role_positions[i + 1]
- except (ValueError, IndexError):
- pass
- return pos
- def in_attack_area(self, hero):
- return [e for e in self.monsters if e.distance_from_entity[hero.id] <= Hero.ATTACK_RANGE]
- def in_wind_area(self, hero):
- return [e for e in self.entities if type(e) is not Hero and e.distance_from_entity[hero.id] <= CastWind.RADIUS]
- def main():
- # *** init game
- grid = Grid()
- grid.init_game()
- while True:
- # *** get inputs and init round
- grid.init_round()
- # *** Compute priorities
- for hero in grid.heroes:
- grid.play(hero)
- main()
|