| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- #!/usr/bin/env python3
- """近距离一次到位(略慢于瞬移);远距离沿直线分多段移到目标,段间微停顿,便于看清轨迹。对外入口为 ``start``。"""
- from __future__ import annotations
- import math
- import random
- import sys
- import time
- from pathlib import Path
- _ROOT = Path(__file__).resolve().parent.parent
- if str(_ROOT) not in sys.path:
- sys.path.insert(0, str(_ROOT))
- from workplace import pyautogui as pa # noqa: E402
- # -----------------------------------------------------------------------------
- # 可配置:最终落点相对传入 ``(x, y)`` 的随机微小偏差(屏幕像素)。
- # 每轴独立在 ``[-FINAL_LANDING_JITTER_MAX_ABS_PX, +FINAL_LANDING_JITTER_MAX_ABS_PX]`` 内均匀取样,
- # 再夹到屏幕范围内。设为 ``0`` 则关闭微调(严格落在传入整数坐标)。
- # -----------------------------------------------------------------------------
- FINAL_LANDING_JITTER_MAX_ABS_PX: float = 2.5
- def _final_screen_xy_with_optional_micro_jitter(
- logical_target_x: int,
- logical_target_y: int,
- *,
- screen_width_pixels: int,
- screen_height_pixels: int,
- ) -> tuple[int, int]:
- jm = float(FINAL_LANDING_JITTER_MAX_ABS_PX)
- if jm <= 0.0:
- fx = logical_target_x
- fy = logical_target_y
- else:
- fx = int(
- round(logical_target_x + random.uniform(-jm, jm)),
- )
- fy = int(
- round(logical_target_y + random.uniform(-jm, jm)),
- )
- fx = max(0, min(screen_width_pixels - 1, fx))
- fy = max(0, min(screen_height_pixels - 1, fy))
- return fx, fy
- def _jitter_offset_tangent_and_perpendicular(
- delta_x: float,
- delta_y: float,
- path_length_pixels: float,
- *,
- along_max_abs: float,
- perp_max_abs: float,
- ) -> tuple[float, float]:
- """在路径局部坐标系下抖动:沿前进方向略小、法向略大,更接近手抖轨迹。"""
- if path_length_pixels <= 1e-9:
- return 0.0, 0.0
- tx = delta_x / path_length_pixels
- ty = delta_y / path_length_pixels
- px, py = -ty, tx
- along = random.uniform(-along_max_abs, along_max_abs)
- perp = random.uniform(-perp_max_abs, perp_max_abs)
- return tx * along + px * perp, ty * along + py * perp
- def start(
- x: int,
- y: int,
- *,
- near_distance_threshold_pixels: float = 120.0,
- minimum_number_of_move_segments_for_far_distance: int = 3,
- maximum_number_of_move_segments_for_far_distance: int = 4,
- ) -> None:
- """
- 从当前光标位置移到 ``(x, y)``(屏幕逻辑像素)。
- 剩余距离 ≤ ``near_distance_threshold_pixels`` 且略远时:先 ``move_to`` 至带手抖偏差的途经点,再
- ``move_to`` 至落点(见模块顶部 ``FINAL_LANDING_JITTER_MAX_ABS_PX``);极短距离仍为单次移动。
- 更远:``3``~``4`` 段移动,中间点在切向/法向组合抖动,最后一段落在目标附近(同上微调);段间短停。
- """
- target_screen_x, target_screen_y = int(x), int(y)
- current_pointer_x, current_pointer_y = pa.raw.position()
- current_pointer_x = float(current_pointer_x)
- current_pointer_y = float(current_pointer_y)
- delta_x_to_target = target_screen_x - current_pointer_x
- delta_y_to_target = target_screen_y - current_pointer_y
- distance_to_target_pixels = math.hypot(delta_x_to_target, delta_y_to_target)
- if distance_to_target_pixels <= 1e-6:
- return
- screen_width_pixels, screen_height_pixels = pa.screen_size()
- if distance_to_target_pixels <= near_distance_threshold_pixels:
- # 先移到「靠近目标但略偏」的一点(法向抖动为主),再落到带微偏差的终点像素。
- if distance_to_target_pixels <= 10.0:
- land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
- target_screen_x,
- target_screen_y,
- screen_width_pixels=screen_width_pixels,
- screen_height_pixels=screen_height_pixels,
- )
- pa.move_to(
- land_x,
- land_y,
- duration=random.uniform(0.18, 0.40),
- )
- return
- approach_fraction_along_segment = random.uniform(0.55, 0.88)
- base_x = (
- current_pointer_x + delta_x_to_target * approach_fraction_along_segment
- )
- base_y = (
- current_pointer_y + delta_y_to_target * approach_fraction_along_segment
- )
- jx, jy = _jitter_offset_tangent_and_perpendicular(
- delta_x_to_target,
- delta_y_to_target,
- distance_to_target_pixels,
- along_max_abs=2.8,
- perp_max_abs=5.5,
- )
- waypoint_x = base_x + jx
- waypoint_y = base_y + jy
- waypoint_x = max(0, min(screen_width_pixels - 1, waypoint_x))
- waypoint_y = max(0, min(screen_height_pixels - 1, waypoint_y))
- pa.move_to(
- int(round(waypoint_x)),
- int(round(waypoint_y)),
- duration=random.uniform(0.14, 0.30),
- )
- land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
- target_screen_x,
- target_screen_y,
- screen_width_pixels=screen_width_pixels,
- screen_height_pixels=screen_height_pixels,
- )
- pa.move_to(
- land_x,
- land_y,
- duration=random.uniform(0.10, 0.24),
- )
- return
- number_of_segments = random.randint(
- minimum_number_of_move_segments_for_far_distance,
- maximum_number_of_move_segments_for_far_distance,
- )
- for step_index in range(1, number_of_segments + 1):
- fraction_along_line_from_start_to_target = step_index / number_of_segments
- if step_index == number_of_segments:
- land_x, land_y = _final_screen_xy_with_optional_micro_jitter(
- target_screen_x,
- target_screen_y,
- screen_width_pixels=screen_width_pixels,
- screen_height_pixels=screen_height_pixels,
- )
- next_pointer_x = float(land_x)
- next_pointer_y = float(land_y)
- else:
- next_pointer_x = (
- current_pointer_x
- + delta_x_to_target * fraction_along_line_from_start_to_target
- )
- next_pointer_y = (
- current_pointer_y
- + delta_y_to_target * fraction_along_line_from_start_to_target
- )
- jx, jy = _jitter_offset_tangent_and_perpendicular(
- delta_x_to_target,
- delta_y_to_target,
- distance_to_target_pixels,
- along_max_abs=3.2,
- perp_max_abs=6.0,
- )
- next_pointer_x += jx
- next_pointer_y += jy
- next_pointer_x = max(
- 0, min(screen_width_pixels - 1, next_pointer_x)
- )
- next_pointer_y = max(
- 0, min(screen_height_pixels - 1, next_pointer_y)
- )
- pa.move_to(
- int(round(next_pointer_x)),
- int(round(next_pointer_y)),
- duration=random.uniform(0.14, 0.30),
- )
- if step_index < number_of_segments:
- time.sleep(random.uniform(0.045, 0.075))
|