Загрузка данных
import time
import random
import math
import pyautogui
import crop
pyautogui.PAUSE = 0.001 # Минимальная системная задержка для точного контроля
pyautogui.FAILSAFE = False
class ChessMover:
def __init__(self):
self.auto_move_enabled = False
self.humanly_mode = True # По умолчанию включен человечный режим
self.base_move_duration = 0.35 # Базовое время движения (сек)
self.is_moving = False # Блокировка повторных вызовов
def toggle_auto_move(self):
"""Включение / Выключение авто-хода (Клавиша L)"""
self.auto_move_enabled = not self.auto_move_enabled
status = "ВКЛЮЧЁН" if self.auto_move_enabled else "ВЫКЛЮЧЕН"
print(f"\n[AUTO-MOVE]: Автоход -> {status}")
def toggle_humanly_mode(self):
"""Переключение между плавной человечной дугой и точным кликом (Клавиша H)"""
self.humanly_mode = not self.humanly_mode
mode_str = "HUMANLY (Дуга Безье + Гауссов разброс)" if self.humanly_mode else "CLICK (Точный центр + Прямые клики)"
print(f"\n[MOVE MODE]: Режим движения -> {mode_str}")
def speed_up(self):
"""Увеличить скорость движения мыши (Клавиша '+')"""
self.base_move_duration = max(0.10, self.base_move_duration - 0.05)
print(f"\n[SPEED +]: Базовое время уменьшено до {self.base_move_duration:.2f} сек")
def speed_down(self):
"""Уменьшить скорость движения мыши (Клавиша '-')"""
self.base_move_duration = min(1.5, self.base_move_duration + 0.05)
print(f"\n[SPEED -]: Базовое время увеличено до {self.base_move_duration:.2f} сек")
def get_absolute_board_rect(self):
"""Абсолютные координаты доски на всем экране из crop.py"""
if crop._tracker.last_board_rect is None:
return None
bx, by, size = crop._tracker.last_board_rect
mon = crop._tracker.monitor
abs_x = mon["left"] + bx
abs_y = mon["top"] + by
return (abs_x, abs_y, size)
def square_to_screen_coords(self, square_str, board_rect, my_color='w', use_human_jitter=True):
"""
Преобразует шахматную клетку в координаты экрана.
Если use_human_jitter=True — применяет Гауссово смещение от центра клетки.
Если use_human_jitter=False — возвращает СТРОГИЙ ЦЕНТР клетки.
"""
if not board_rect or len(square_str) < 2:
return None
abs_x, abs_y, board_size = board_rect
cell_size = board_size / 8.0
file_char = square_str[0].lower()
rank_char = square_str[1]
file_idx = ord(file_char) - ord('a') # 0..7
rank_idx = int(rank_char) # 1..8
if my_color == 'w':
col = file_idx
row = 8 - rank_idx
else:
col = 7 - file_idx
row = rank_idx - 1
center_x = abs_x + (col + 0.5) * cell_size
center_y = abs_y + (row + 0.5) * cell_size
if use_human_jitter:
# Гауссово распределение от центра (применяется ТОЛЬКО в humanly_mode)
sigma = cell_size * 0.12
offset_x = random.gauss(0, sigma)
offset_y = random.gauss(0, sigma)
max_offset = cell_size * 0.30
offset_x = max(-max_offset, min(max_offset, offset_x))
offset_y = max(-max_offset, min(max_offset, offset_y))
return int(center_x + offset_x), int(center_y + offset_y)
else:
# Точный центр клетки без смещений
return int(center_x), int(center_y)
def generate_bezier_path(self, start, end, num_steps=20):
"""Генерирует кривую Безье 3-й степени (для humanly_mode)"""
sx, sy = start
ex, ey = end
dx = ex - sx
dy = ey - sy
dist = math.hypot(dx, dy)
if dist < 5:
return [end]
deviation = random.uniform(0.12, 0.32) * dist
side = random.choice([-1, 1])
nx = -dy / dist
ny = dx / dist
ctrl1_x = sx + dx * random.uniform(0.2, 0.4) + nx * deviation * side * random.uniform(0.7, 1.1)
ctrl1_y = sy + dy * random.uniform(0.2, 0.4) + ny * deviation * side * random.uniform(0.7, 1.1)
ctrl2_x = sx + dx * random.uniform(0.6, 0.8) + nx * deviation * side * random.uniform(0.5, 0.9)
ctrl2_y = sy + dy * random.uniform(0.6, 0.8) + ny * deviation * side * random.uniform(0.5, 0.9)
path = []
for i in range(1, num_steps + 1):
t_raw = i / float(num_steps)
t = 3 * (t_raw ** 2) - 2 * (t_raw ** 3) # Ease-In-Out
u = 1 - t
x = (u**3)*sx + 3*(u**2)*t*ctrl1_x + 3*u*(t**2)*ctrl2_x + (t**3)*ex
y = (u**3)*sy + 3*(u**2)*t*ctrl1_y + 3*u*(t**2)*ctrl2_y + (t**3)*ey
if i < num_steps:
x += random.uniform(-1.2, 1.2)
y += random.uniform(-1.2, 1.2)
path.append((int(x), int(y)))
return path
def human_move_along_path(self, path, total_duration):
"""Плавное перемещение по точкам траектории"""
if not path:
return
step_delay = total_duration / float(len(path))
for x, y in path:
pyautogui.moveTo(x, y)
time.sleep(step_delay * random.uniform(0.75, 1.25))
def execute_move(self, uci_move, my_color='w'):
"""Выполнение хода"""
if not self.auto_move_enabled:
return
if self.is_moving:
print("[!] Физический ход уже выполняется, пропуск повторного вызова.")
return
board_rect = self.get_absolute_board_rect()
if not board_rect:
print("[!] Ошибка движения: Координаты доски не найдены!")
return
from_sq = uci_move[:2]
to_sq = uci_move[2:4]
# Гауссов разброс передается только если включен humanly_mode
start_pos = self.square_to_screen_coords(from_sq, board_rect, my_color, use_human_jitter=self.humanly_mode)
end_pos = self.square_to_screen_coords(to_sq, board_rect, my_color, use_human_jitter=self.humanly_mode)
if not start_pos or not end_pos:
print(f"[!] Ошибка расчета координат для хода: {uci_move}")
return
start_x, start_y = start_pos
end_x, end_y = end_pos
self.is_moving = True
try:
if self.humanly_mode:
# =========================================================
# 1. HUMANLY MODE (Все симуляции человечности включены)
# =========================================================
duration = self.base_move_duration * random.uniform(0.8, 1.2)
print(f"[PHYSICAL MOVE]: {uci_move} | Humanly Drag | Время: {duration:.2f}s")
# Подводка мыши к фигуре
approach_path = self.generate_bezier_path(pyautogui.position(), start_pos, num_steps=10)
self.human_move_along_path(approach_path, duration * 0.35)
time.sleep(random.uniform(0.03, 0.07))
pyautogui.mouseDown(button='left')
time.sleep(random.uniform(0.02, 0.05))
# Перетаскивание
drag_steps = max(12, int(duration * 60))
# Микро-перелёт цели (овершут) с вероятностью 30%
if random.random() < 0.30:
overshoot_x = end_x + random.randint(-4, 4)
overshoot_y = end_y + random.randint(-4, 4)
drag_path = self.generate_bezier_path(start_pos, (overshoot_x, overshoot_y), num_steps=drag_steps)
self.human_move_along_path(drag_path, duration * 0.9)
time.sleep(random.uniform(0.01, 0.03))
pyautogui.moveTo(end_x, end_y)
else:
drag_path = self.generate_bezier_path(start_pos, end_pos, num_steps=drag_steps)
self.human_move_along_path(drag_path, duration)
time.sleep(random.uniform(0.03, 0.06))
pyautogui.mouseUp(button='left')
else:
# =========================================================
# 2. CLICK MODE (Строгий, точный, быстрый режим)
# =========================================================
print(f"[PHYSICAL MOVE]: {uci_move} | Exact Click-Click")
# Переход к точному центру первой клетки
pyautogui.moveTo(start_x, start_y, duration=0.08)
time.sleep(0.03)
pyautogui.click()
# Переход к точному центру второй клетки
time.sleep(0.05)
pyautogui.moveTo(end_x, end_y, duration=0.08)
time.sleep(0.03)
pyautogui.click()
# При превращении пешки (например, e7e8q)
if len(uci_move) == 5:
time.sleep(0.15)
pyautogui.click()
finally:
self.is_moving = False