def is_oriented_to_unit(me: Wizard, game: Game, unit: CircularUnit) -> (bool, float): angle_to_unit = me.get_angle_to_unit(unit) cast_angle = abs(angle_to_unit) - math.atan(unit.radius / me.get_distance_to_unit(unit)) if cast_angle < 0.0: # We can attack. return True, 0.0 # Attack with some cast angle. if cast_angle < game.staff_sector / 2.0: # Cast angle has the same sign as turn angle. return True, math.copysign(cast_angle, angle_to_unit) # :( return False, None
def attack(me: Wizard, game: Game, move: Move, skills: Set, unit: LivingUnit, allow_fireball: bool): action_type, min_cast_distance = MyStrategy.get_action(me, game, skills, unit, allow_fireball) if action_type == ActionType.NONE: return False # We can cast something. is_oriented, cast_angle = MyStrategy.is_oriented_to_unit(me, game, unit) if is_oriented: # Attack! move.cast_angle = cast_angle move.action = action_type move.min_cast_distance = min_cast_distance return True # Turn around to the enemy. move.turn = me.get_angle_to_unit(unit) return True
def _find_problem_units(self, me: Wizard, reverse=False): def is_problem_unit(angle, reverse, problem_sector): if reverse: return fabs(angle) > radians(180) - problem_sector else: return fabs(angle) < problem_sector units = self.W.buildings + self.W.wizards + self.W.minions + self.W.trees connected_u = [ t for t in units if me.get_distance_to_unit(t) <= (me.radius + t.radius) * 1.05 and me.id != t.id ] problem_u = [ t for t in connected_u if is_problem_unit(me.get_angle_to_unit( t), reverse, self.PROBLEM_ANGLE) ] return None if not problem_u else problem_u[0]
def _goto_forward(self, me: Wizard): coords_tuple = self._get_next_waypoint(me) problem_unit = self._find_problem_units(me) if problem_unit: angle_to_connected_unit = me.get_angle_to_unit(problem_unit) self.log('found connected unit %s (%.4f angle)' % (problem_unit.id, angle_to_connected_unit)) if angle_to_connected_unit >= 0: self.log('run left') self.MOVE_STRAFE_SPEED = -1 * self.G.wizard_strafe_speed else: self.log('run right') self.MOVE_STRAFE_SPEED = self.G.wizard_strafe_speed else: turn_only = False if coords_tuple is None: turn_only = True coords_tuple = (self.ENEMY_BASE.x, self.ENEMY_BASE.y) angle = me.get_angle_to(*coords_tuple) self.MOVE_TURN = angle if fabs(angle) < self.STAFF_SECTOR / 4.0 and not turn_only: self.MOVE_SPEED = self.MAX_SPEED
def _goto_backward(self, me: Wizard): coords_tuple = self._get_prev_waypoint(me) problem_unit = self._find_problem_units(me, True) if problem_unit: angle_to_connected_unit = me.get_angle_to_unit(problem_unit) self.log('backward angle %.4f' % angle_to_connected_unit) self.log('found connected unit %s (%.4f angle)' % (problem_unit.id, angle_to_connected_unit)) if angle_to_connected_unit > 0: self.log('run right') self.MOVE_STRAFE_SPEED = -1 * self.G.wizard_strafe_speed else: self.log('run left') self.MOVE_STRAFE_SPEED = self.G.wizard_strafe_speed else: angle = me.get_angle_to(*coords_tuple) angle_reverse = radians(180) - fabs(angle) if angle > 0: angle_reverse *= -1 self.log('backward angle %.4f %.4f' % (angle, angle_reverse)) self.log('backward angle staff %.4f' % self.STAFF_SECTOR) self.MOVE_TURN = angle_reverse if fabs(angle_reverse) < self.STAFF_SECTOR / 4.0: self.MOVE_SPEED = -1 * self.MAX_SPEED
def move(self, me: Wizard, world: World, game: Game, move: Move): if self.debug: self.debug.syncronize(world) if not self.initialized: self.initialize(me, world, game) self.initialize_tick(me, world, game) score_threshold = -0.1 wizard_score = me.life / (me.max_life * 0.5) - 1 if self.battle_front: self.battle_front.init(world, me) front_score = self.battle_front.get_front_score(me) k = 0.6 common_score = front_score * (1 - k) + wizard_score * k else: common_score = wizard_score nearest_target = Points2D.get_nearest_target(me, world) if nearest_target is not None: distance = me.get_distance_to_unit(nearest_target) angle = me.get_angle_to_unit(nearest_target) if (distance <= game.staff_range) and (abs(angle) < game.staff_sector / 2.0): move.action = ActionType.STAFF elif (distance <= me.cast_range) and (abs(angle) < game.staff_sector / 2.0): move.action = ActionType.MAGIC_MISSILE move.cast_angle = angle move.min_cast_distance = distance - nearest_target.radius + game.magic_missile_radius if common_score < score_threshold: previous_waypoint = Points2D.get_previous_waypoint( self.waypoints, me) self.map.note_enemy_angle = False self.go_to(move, previous_waypoint, note_angle=False) self.last_actions[world.tick_index % STOP_CHECK_TICK_COUNT] = Action.BACK return else: nearest_target = Points2D.get_nearest_target(me, world) if nearest_target is not None: distance = me.get_distance_to_unit(nearest_target) angle = me.get_angle_to_unit(nearest_target) if distance <= me.cast_range: self.go_to(move, None, note_angle=False) if abs(angle) < game.staff_sector / 2.0: move.action = ActionType.MAGIC_MISSILE move.cast_angle = angle move.min_cast_distance = distance - nearest_target.radius + game.magic_missile_radius self.last_actions[world.tick_index % STOP_CHECK_TICK_COUNT] = Action.ENEMY return else: min_attack_dist = min_atack_distance(me) * self.life_coef new_x = me.x + (nearest_target.x - me.x) / distance * ( distance - min_attack_dist) new_y = me.y + (nearest_target.y - me.y) / distance * ( distance - min_attack_dist) self.go_to(move, Point2D(new_x, new_y), note_angle=True) self.last_actions[world.tick_index % STOP_CHECK_TICK_COUNT] = Action.NEXT return else: next_waypoint = Points2D.get_next_waypoint(self.waypoints, me) note_angle = True if world.tick_index > STOP_CHECK_TICK_COUNT: last_pos_index = (world.tick_index + 1) % STOP_CHECK_TICK_COUNT dist_last_poses = me.get_distance_to_unit( self.last_poses[last_pos_index]) # если далеко не ушел и если последние все действия NEXT if ((dist_last_poses < STOP_CHECK_TICK_COUNT * 0.2 * game.wizard_forward_speed) and (sum([x == Action.NEXT for x in self.last_actions]) == STOP_CHECK_TICK_COUNT)): note_angle = False self.map.neutral_coef = 1000 * np.random.normal(1, 0.3) self.map.neutral_dist_coef = 1000 self.go_to(move, next_waypoint, note_angle=note_angle) self.last_actions[world.tick_index % STOP_CHECK_TICK_COUNT] = Action.NEXT if world.tick_index < SLOW_MOVE_TICK_COUNT: move.speed *= 0.5 move.strafe_speed *= 0.5 return
def _enemy_in_attack_sector(self, me: Wizard, e): return fabs(me.get_angle_to_unit(e)) <= self.STAFF_SECTOR / 2.0
def _sort_by_angle(me: Wizard, el: list): return sorted(el, key=lambda u: fabs(me.get_angle_to_unit(u)))
def _goto_enemy(self, me: Wizard, e: LivingUnit): angle = me.get_angle_to_unit(e) self.log('enemy angle for projectile care is %.2f' % angle) if fabs(angle) < self.STAFF_SECTOR / 4.0: self.MOVE_TURN = angle self.MOVE_SPEED = self.MAX_SPEED
def move(self, me: Wizard, world: World, game: Game, move: Move): self.log('TICK %s' % world.tick_index) self._init(game, me, world, move) self.log('me %s %s %f' % (me.x, me.y, me.get_distance_to(*self.INIT_POINT))) # initial cooldown if self.PASS_TICK_COUNT: self.PASS_TICK_COUNT -= 1 self.log('initial cooldown pass turn') return # select lane self._select_lane(me) # STRATEGY LOGIC enemy_targets = self._enemies_in_attack_distance(me) enemy_who_can_attack_me = self._enemies_who_can_attack_me(me) retreat_move_lock = False retreat_by_low_hp = False # чистим уже погибшие снаряды из карты current_projectiles_id = set( [p.id for p in self.W.projectiles if p.owner_unit_id == me.id]) cached_projectiles_id = set(self.PROJECTILE_MAP.keys()) for k in cached_projectiles_id - current_projectiles_id: del self.PROJECTILE_MAP[k] # ищем последний созданный снаряд и его цель для карты снарядов if self.PROJECTILE_LAST_ENEMY: for p in self.W.projectiles: if p.owner_unit_id == me.id and p.id not in self.PROJECTILE_MAP: self.PROJECTILE_MAP[p.id] = self.PROJECTILE_LAST_ENEMY self.PROJECTILE_LAST_ENEMY = None break self.log('projectile map %s' % str(self.PROJECTILE_MAP)) # если ХП мало отступаем if me.life < me.max_life * self.LOW_HP_FACTOR: retreat_by_low_hp = True self.log('retreat by low HP') if len(enemy_who_can_attack_me): self._goto_backward(me) retreat_move_lock = True if len(enemy_who_can_attack_me) > self.MAX_ENEMIES_IN_DANGER_ZONE: self.log('retreat by enemies in danger zone') self._goto_backward(me) retreat_move_lock = True # если врагов в радиусе обстрела нет - идём к их базе если не находимся в режиме отступления if not enemy_targets and not retreat_by_low_hp: self.log('move to next waypoint') self._goto_forward(me) # если на поле есть наши снаряды и расстояние до цели меньше расстояния каста # пробуем подойти к цели (если не находимся в отступлении) if not retreat_by_low_hp: potential_miss_enemies = self._find_potential_miss_enemy(me) self.log('found %s potential miss enemies' % potential_miss_enemies) if potential_miss_enemies: e = self._sort_by_angle(me, potential_miss_enemies)[0] self._goto_enemy(me, e) if enemy_targets: # есть враги в радиусе обстрела self.log('found %d enemies for attack' % len(enemy_targets)) selected_enemy = self._select_enemy_for_attack(me, enemy_targets) angle_to_enemy = me.get_angle_to_unit(selected_enemy) # если цель не в секторе атаки - поворачиваемся к ней (приоритет за направлением на точку отступления) if not self._enemy_in_attack_sector(me, selected_enemy): if not retreat_move_lock: self.log('select enemy for turn %s' % selected_enemy.id) self.MOVE_TURN = angle_to_enemy else: self.log('ignore select enemy for turn %s by retreat' % selected_enemy.id) else: # если можем атаковать - атакуем self.log('select enemy for attack %s' % selected_enemy.id) move.cast_angle = angle_to_enemy if self._enemy_in_cast_distance(me, selected_enemy): self.log('cast attack') move.action = ActionType.MAGIC_MISSILE move.min_cast_distance = self._cast_distance( me, selected_enemy) self.PROJECTILE_LAST_ENEMY = selected_enemy.id else: self.log('staff attack') move.action = ActionType.STAFF if self.MOVE_TURN is not None: move.turn = self.MOVE_TURN self.MOVE_TURN = None if self.MOVE_SPEED is not None: move.speed = self.MOVE_SPEED self.MOVE_SPEED = None if self.MOVE_STRAFE_SPEED is not None: move.strafe_speed = self.MOVE_STRAFE_SPEED self.MOVE_STRAFE_SPEED = None