Загрузка данных


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