def top_relative_to_abs_position(self, relative): if relative < 0.5: return Vec(LINES_PADDING / 2, (1 - relative * 2) * (self.world.height - LINES_PADDING) + LINES_PADDING / 2) else: return Vec((relative - 0.5) * 2 * (self.world.width - LINES_PADDING) + LINES_PADDING / 2, LINES_PADDING / 2)
def _build_path_to_farm_point(self): fp = self.farm_point if self.me.y > self.world.height - 700: return Vec(LINES_PADDING / 2, self.world.height - 800) if self.me.y > LINES_PADDING and self.me.x > LINES_PADDING: if fp.x <= LINES_PADDING or self.me.x < self.me.y: return Vec(LINES_PADDING / 2, self.me.y) else: return Vec(self.me.x, LINES_PADDING / 2) if fp.x < LINES_PADDING: return fp if self.me.y > LINES_PADDING: return Vec(LINES_PADDING / 2, LINES_PADDING / 2) else: return fp
def center_of_mass(units): x_sum, y_sum = 0, 0 count = 0 for unit in units: x_sum += unit.x y_sum += unit.y count += 1 if count == 0: return None else: return Vec(x_sum / count, y_sum / count)
def goto(self, target: Vec): if self._check_if_stacked(): return if distance(self.me, target) < TARGET_DISTANCE_TO_STAY: self.move_state = MoveState.STAYING return else: self.move_state = MoveState.MOVING_TO_TARGET self.move_obj.speed = self.game.wizard_forward_speed angle = self.me.get_angle_to_unit(target) self.move_obj.turn = angle vec = Vec(target.x - self.me.x, target.y - self.me.y) self.move_obj.strafe_speed = self._calc_strafe(vec)
def battle_goto(self, target, look_at): delta_v = target - self.me if delta_v.x != 0 or delta_v.y != 0: delta_v = self._calc_strafe(delta_v) cos_a = math.cos(-self.me.angle) sin_a = math.sin(-self.me.angle) delta_v = Vec(cos_a * delta_v.x - sin_a * delta_v.y, sin_a * delta_v.x + cos_a * delta_v.y) self.move_obj.speed = delta_v.x self.move_obj.strafe_speed = delta_v.y turn = self.me.get_angle_to_unit(look_at) self.move_obj.turn = turn
def get_pos_to_go(self): me = self.strategy.me me_row = int(me.y // self.CELL_SIZE) me_col = int(me.x // self.CELL_SIZE) best_row, best_col, best_value = me_row, me_col, self.map[me_row, me_col] for drow, dcol in ((-1, 0), (0, -1), (1, 0), (0, 1), (-1, -1), (-1, 1), (1, 1), (1, -1)): row = me_row + drow col = me_col + dcol if self.map[row, col] > best_value: best_row, best_col, best_value = row, col, self.map[row, col] x = best_col * self.CELL_SIZE + self.HALF_CELL_SIZE y = best_row * self.CELL_SIZE + self.HALF_CELL_SIZE return Vec(x, y)
def _calc_strafe(self, vec: Vec): self.straight_vec = vec shift = self.game.wizard_strafe_speed * 4 obj = self._get_closest_strafe_object() if obj: # calculation vector from point (obj.x, obj.y) to line [(self.me.x, self.me.y), vec] a = vec.y b = -vec.x c = (self.me.x - obj.x) * (-vec.y) + (self.me.y - obj.y) * vec.x x0 = (a * c) / (a**2 + b**2) y0 = (b * c) / (a**2 + b**2) vec0 = Vec(x0, y0) self.vec0 = vec0 self.vec0_obj = obj self.vec_shift = vec0.normalized * -shift overlapping = (obj.radius + self.me.radius + 5) - vec0.length if overlapping >= 0: return vec - vec0.normalized * shift return vec
class Strategy: look_at = Vec(0, 3200) plan = deque([Vec(250, 3400)]) unstack_moving = 0 unstack_strafe_direction = 1 move_state = MoveState.STAYING matrix = [[0] * MATRIX_CELL_AMOUNT for _ in range(MATRIX_CELL_AMOUNT)] def __init__(self, debug_client=None, input_event=None): self.line_state = LineState.MOVING_TO_LINE self._reset_lists() self._reset_cached_values() self.map = Map() self.potential_map = PotentialMap() if debug_client is not None: from aicup2016.drawer import Drawer self.drawer = Drawer(debug_client) else: self.drawer = None if input_event is not None: from aicup2016.debug_control import DebugControl self.debug_control = DebugControl(self, input_event) def move(self, me: Wizard, world: World, game: Game, move: Move): self.me = me self.world = world self.game = game self.move_obj = move self.update_analyzing() self._derive_nearest() self.potential_map.update(self) self._check_state() self.update() if self.drawer is not None: self.drawer.draw_all(self, me, world, game, move) def _derive_nearest(self): self.nearest_objects = [] self.enemies_in_cast_range = [] self.matrix = [[0] * MATRIX_CELL_AMOUNT for _ in range(MATRIX_CELL_AMOUNT)] matrix_top = self.me.y - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 matrix_left = self.me.x - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 for obj in chain(self.world.buildings, self.world.minions, self.world.wizards, self.world.trees): obj.distance = distance(self.me, obj) if obj.distance <= NEAREST_RADIUS and obj.id != self.me.id: self.nearest_objects.append(obj) if self._is_enemy( obj) and obj.distance <= self.game.wizard_cast_range: self.enemies_in_cast_range.append(obj) col_left = int( (obj.x - obj.radius - matrix_left) // MATRIX_CELL_SIZE) col_right = int( (obj.x + obj.radius - matrix_left) // MATRIX_CELL_SIZE) row_top = int( (obj.y - obj.radius - matrix_top) // MATRIX_CELL_SIZE) row_bottom = int( (obj.y + obj.radius - matrix_top) // MATRIX_CELL_SIZE) for row in range(row_top, row_bottom + 1): for col in range(col_left, col_right + 1): if 0 <= col < MATRIX_CELL_AMOUNT and 0 <= row < MATRIX_CELL_AMOUNT: self.matrix[row][col] = -1 def _check_state(self): if distance(self.me, self.top_line_bound) < ON_LINE_DISTANCE: new_state = LineState.ON_LINE else: new_state = LineState.MOVING_TO_LINE if self.line_state != new_state: old_state = self.line_state self.line_state = new_state self._line_state_changed(old_state) def _line_state_changed(self, old_state): pass def update(self): if self.line_state == LineState.MOVING_TO_LINE: move_target = self._build_path_to_farm_point() self.battle_goto_potential(move_target, move_target) elif self.line_state == LineState.ON_LINE: self._on_line_update() def _on_line_update(self): enemy = self._choice_enemy_to_shoot() if enemy: self.move_obj.action = ActionType.MAGIC_MISSILE self.move_obj.min_cast_distance = enemy.distance - enemy.radius look_at = enemy else: look_at = self.top_line_enemy_center_of_mass stay_at = self.farm_point self.battle_goto_potential(stay_at, look_at) # self.battle_goto_smart(stay_at, look_at) # self.battle_goto(stay_at, look_at) def battle_goto_potential(self, target, loot_at): delta = target - self.me if delta.length > self.me.vision_range: delta = delta / delta.length * self.me.vision_range target = delta + self.me radius = self.me.vision_range * 1.5 else: radius = delta.length * 1.5 self.stay_at = target self.potential_map.put_simple(self.potential_map.map, target, 60, radius) next_target = self.potential_map.get_pos_to_go() self.next_target = next_target self.battle_goto(next_target, loot_at) def battle_goto_smart(self, target, look_at): if distance(self.me, target) < 1.42 * MATRIX_CELL_SIZE: return self.battle_goto(target, look_at) shifted_target = Vec(target.x - MATRIX_CELL_SIZE / 2, target.y - MATRIX_CELL_SIZE / 2) matrix_top = self.me.y - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 matrix_left = self.me.x - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 target_row = int((shifted_target.y - matrix_top) / MATRIX_CELL_SIZE) target_row = max(0, min(MATRIX_CELL_AMOUNT - 1, target_row)) target_col = int((shifted_target.x - matrix_left) / MATRIX_CELL_SIZE) target_col = max(0, min(MATRIX_CELL_AMOUNT - 1, target_col)) me_row = me_col = int(MATRIX_CELL_AMOUNT / 2) - 1 self.matrix[me_row][me_col] = 1 self.matrix[me_row + 1][me_col] = 0 self.matrix[me_row + 1][me_col + 1] = 0 self.matrix[me_row][me_col + 1] = 0 heap = [] heappush(heap, (distance(me_row, me_col, target_row, target_col), (me_row, me_col))) stop = False while heap and not stop: _, (cur_row, cur_col) = heappop(heap) path_length = self.matrix[cur_row][cur_col] + 1 for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 and self.empty(row, col) \ and (self.matrix[row][col] == 0 or self.matrix[row][col] > path_length): self.matrix[row][col] = path_length heappush(heap, (distance(row, col, target_row, target_col), (row, col))) if row == target_row and col == target_col: stop = True break if self.matrix[target_row][target_col] <= 0: visited = set() visited.add((target_row, target_col)) queue = deque() queue.append((target_row, target_col)) stop = False while not stop: cur_row, cur_col = queue.popleft() for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol row_col = (row, col) if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 and row_col not in visited: if self.matrix[row][col] > 0: target_row, target_col = row, col stop = True break visited.add(row_col) queue.append(row_col) path = [] cur_row, cur_col = target_row, target_col path_length = self.matrix[target_row][target_col] - 1 while (cur_row, cur_col) != (me_row, me_col): for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 \ and self.matrix[row][col] == path_length: path.append((row, col)) cur_row, cur_col = row, col path_length -= 1 break for row, col in path: self.matrix[row][col] = 666 if len(path) == 0: return self.battle_goto(target, look_at) elif len(path) > 1: row, col = path[-2] else: row, col = path[-1] target = Vec(matrix_left + (col + 1) * MATRIX_CELL_SIZE, matrix_top + (row + 1) * MATRIX_CELL_SIZE) self.battle_goto(target, look_at) def empty(self, row_start, col_start): row_end = row_start + 1 col_end = col_start + 1 for row in range(row_start, row_end + 1): for col in range(col_start, col_end + 1): if self.matrix[row][col] < 0: return False return True def battle_goto(self, target, look_at): delta_v = target - self.me if delta_v.x != 0 or delta_v.y != 0: delta_v = self._calc_strafe(delta_v) cos_a = math.cos(-self.me.angle) sin_a = math.sin(-self.me.angle) delta_v = Vec(cos_a * delta_v.x - sin_a * delta_v.y, sin_a * delta_v.x + cos_a * delta_v.y) self.move_obj.speed = delta_v.x self.move_obj.strafe_speed = delta_v.y turn = self.me.get_angle_to_unit(look_at) self.move_obj.turn = turn def _choice_enemy_to_shoot(self): best_enemy = min(self.enemies_in_cast_range, key=lambda x: x.life + isinstance(x, Wizard) * (-2000) + isinstance(x, Building) * (-1000), default=None) return best_enemy def goto(self, target: Vec): if self._check_if_stacked(): return if distance(self.me, target) < TARGET_DISTANCE_TO_STAY: self.move_state = MoveState.STAYING return else: self.move_state = MoveState.MOVING_TO_TARGET self.move_obj.speed = self.game.wizard_forward_speed angle = self.me.get_angle_to_unit(target) self.move_obj.turn = angle vec = Vec(target.x - self.me.x, target.y - self.me.y) self.move_obj.strafe_speed = self._calc_strafe(vec) def _check_if_stacked(self): if self.move_state == MoveState.MOVING_TO_TARGET and self.me.speed_x == self.me.speed_y == 0: self.move_state = MoveState.STACKED self.unstack_strafe_direction = random.choice([-1, 1]) self.unstack_moving = random.randint(7, 20) self._move_to_unstack() return True if self.move_state == MoveState.STACKED and self.me.speed_x == self.me.speed_y == 0: self.move_state = MoveState.STACKED_BACKWARD self.unstack_strafe_direction = random.choice([-1, 1]) self.unstack_moving = random.randint(7, 20) self._move_to_unstack() return True if self.unstack_moving > 0: self._move_to_unstack() self.unstack_moving -= 1 return True return False def _move_to_unstack(self): self.move_obj.action = ActionType.MAGIC_MISSILE if self.move_state == MoveState.STACKED: self.move_obj.speed = -self.game.wizard_backward_speed self.move_obj.strafe_speed = math.copysign( self.game.wizard_strafe_speed, self.unstack_strafe_direction) if self.move_state == MoveState.STACKED_BACKWARD: self.move_obj.speed = self.game.wizard_forward_speed / 2 self.move_obj.turn = self.game.wizard_max_turn_angle self.move_obj.strafe_speed = math.copysign( self.game.wizard_strafe_speed, self.unstack_strafe_direction) self.unstack_moving -= 1 def _calc_turn_angle(self, vec: Vec): angle = math.atan2(vec.y, vec.x) turn = angle - self.me.angle turn = min(self.game.wizard_max_turn_angle, turn) turn = max(-self.game.wizard_max_turn_angle, turn) return turn def _calc_strafe(self, vec: Vec): self.straight_vec = vec shift = self.game.wizard_strafe_speed * 4 obj = self._get_closest_strafe_object() if obj: # calculation vector from point (obj.x, obj.y) to line [(self.me.x, self.me.y), vec] a = vec.y b = -vec.x c = (self.me.x - obj.x) * (-vec.y) + (self.me.y - obj.y) * vec.x x0 = (a * c) / (a**2 + b**2) y0 = (b * c) / (a**2 + b**2) vec0 = Vec(x0, y0) self.vec0 = vec0 self.vec0_obj = obj self.vec_shift = vec0.normalized * -shift overlapping = (obj.radius + self.me.radius + 5) - vec0.length if overlapping >= 0: return vec - vec0.normalized * shift return vec def _get_closest_strafe_object(self): closest_obj = None for obj in self.nearest_objects: if obj.distance - self.me.radius - obj.radius < STRAFE_OBJECT_MAX_DISTANCE \ and (closest_obj is None or obj.distance < closest_obj.distance): closest_obj = obj return closest_obj def _build_path_to_farm_point(self): fp = self.farm_point if self.me.y > self.world.height - 700: return Vec(LINES_PADDING / 2, self.world.height - 800) if self.me.y > LINES_PADDING and self.me.x > LINES_PADDING: if fp.x <= LINES_PADDING or self.me.x < self.me.y: return Vec(LINES_PADDING / 2, self.me.y) else: return Vec(self.me.x, LINES_PADDING / 2) if fp.x < LINES_PADDING: return fp if self.me.y > LINES_PADDING: return Vec(LINES_PADDING / 2, LINES_PADDING / 2) else: return fp def _is_enemy(self, obj): return opposite_faction(self.me.faction) == obj.faction def _reset_lists(self): self.top_line = [] self.middle_line = [] self.bottom_line = [] self.top_line_enemies = [] self.top_line_allies = [] def update_analyzing(self): self._reset_cached_values() self._reset_lists() for minion in chain(self.world.minions, self.world.buildings, self.world.wizards): if minion.x < LINES_PADDING or minion.y < LINES_PADDING: self.top_line.append(minion) if minion.faction == self.me.faction: self.top_line_allies.append(minion) elif minion.faction == opposite_faction(self.me.faction): self.top_line_enemies.append(minion) elif minion.y > self.world.height - LINES_PADDING or minion.x > self.world.width - LINES_PADDING: self.bottom_line.append(minion) else: self.middle_line.append(minion) def _reset_cached_values(self): self.__dict__['__cached_properties'] = dict() @cached_property def top_line_bound(self): most_vanguard_ally = max(self.top_line_allies, key=self.top_abs_to_relative_position, default=None) vanguard_allies = get_units_in_radius(self.top_line_allies, most_vanguard_ally, VANGUARD_ALLY_RADIUS) return center_of_mass(vanguard_allies) or most_vanguard_ally @cached_property def farm_point(self): if self.me.life < LIFE_THRESHOLD_FOR_SAVING: offset = LIFE_REGEN_OFFSET else: offset = FARM_POINT_OFFSET top_relative = self.top_abs_to_relative_position(self.top_line_bound) offset_relative = offset / (self.world.width + self.world.height - LINES_PADDING * 2) farm_point_relative = min(1, max(0, top_relative - offset_relative)) return self.top_relative_to_abs_position(farm_point_relative) @cached_property def top_line_enemy_center_of_mass(self): return center_of_mass(self.top_line_enemies) or self.top_line_bound def top_abs_to_relative_position(self, point): if point.x < point.y: return (1 - (point.y - LINES_PADDING / 2) / (self.world.height - LINES_PADDING)) / 2 else: return 0.5 + ((point.x - LINES_PADDING / 2) / (self.world.width - LINES_PADDING) / 2) def top_relative_to_abs_position(self, relative): if relative < 0.5: return Vec(LINES_PADDING / 2, (1 - relative * 2) * (self.world.height - LINES_PADDING) + LINES_PADDING / 2) else: return Vec((relative - 0.5) * 2 * (self.world.width - LINES_PADDING) + LINES_PADDING / 2, LINES_PADDING / 2)
def battle_goto_smart(self, target, look_at): if distance(self.me, target) < 1.42 * MATRIX_CELL_SIZE: return self.battle_goto(target, look_at) shifted_target = Vec(target.x - MATRIX_CELL_SIZE / 2, target.y - MATRIX_CELL_SIZE / 2) matrix_top = self.me.y - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 matrix_left = self.me.x - MATRIX_CELL_SIZE * MATRIX_CELL_AMOUNT / 2 target_row = int((shifted_target.y - matrix_top) / MATRIX_CELL_SIZE) target_row = max(0, min(MATRIX_CELL_AMOUNT - 1, target_row)) target_col = int((shifted_target.x - matrix_left) / MATRIX_CELL_SIZE) target_col = max(0, min(MATRIX_CELL_AMOUNT - 1, target_col)) me_row = me_col = int(MATRIX_CELL_AMOUNT / 2) - 1 self.matrix[me_row][me_col] = 1 self.matrix[me_row + 1][me_col] = 0 self.matrix[me_row + 1][me_col + 1] = 0 self.matrix[me_row][me_col + 1] = 0 heap = [] heappush(heap, (distance(me_row, me_col, target_row, target_col), (me_row, me_col))) stop = False while heap and not stop: _, (cur_row, cur_col) = heappop(heap) path_length = self.matrix[cur_row][cur_col] + 1 for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 and self.empty(row, col) \ and (self.matrix[row][col] == 0 or self.matrix[row][col] > path_length): self.matrix[row][col] = path_length heappush(heap, (distance(row, col, target_row, target_col), (row, col))) if row == target_row and col == target_col: stop = True break if self.matrix[target_row][target_col] <= 0: visited = set() visited.add((target_row, target_col)) queue = deque() queue.append((target_row, target_col)) stop = False while not stop: cur_row, cur_col = queue.popleft() for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol row_col = (row, col) if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 and row_col not in visited: if self.matrix[row][col] > 0: target_row, target_col = row, col stop = True break visited.add(row_col) queue.append(row_col) path = [] cur_row, cur_col = target_row, target_col path_length = self.matrix[target_row][target_col] - 1 while (cur_row, cur_col) != (me_row, me_col): for drow, dcol in ((-1, 0), (0, 1), (1, 0), (0, -1)): row = cur_row - drow col = cur_col - dcol if 0 <= row < MATRIX_CELL_AMOUNT - 1 and 0 <= col < MATRIX_CELL_AMOUNT - 1 \ and self.matrix[row][col] == path_length: path.append((row, col)) cur_row, cur_col = row, col path_length -= 1 break for row, col in path: self.matrix[row][col] = 666 if len(path) == 0: return self.battle_goto(target, look_at) elif len(path) > 1: row, col = path[-2] else: row, col = path[-1] target = Vec(matrix_left + (col + 1) * MATRIX_CELL_SIZE, matrix_top + (row + 1) * MATRIX_CELL_SIZE) self.battle_goto(target, look_at)