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


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        # По умолчанию включен человечный режим (WindMouse)
        self.base_move_duration = 0.35  # Базовое время движения для WindMouse (сек)
        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):
        """Переключение между человечным WindMouse и мгновенным кликом (Клавиша H)"""
        self.humanly_mode = not self.humanly_mode
        mode_str = "HUMANLY (Физика WindMouse + Jitter)" 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

        # Смещение применяется ТОЛЬКО если включен humanly_mode
        if use_human_jitter:
            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 wind_mouse_move(self, start_pos, end_pos, speed_factor=1.0):
        """
        Симуляция физики WindMouse. Работает ИСКЛЮЧИТЕЛЬНО при self.humanly_mode == True.
        """
        dest_x, dest_y = end_pos

        # Жесткая проверка: если humanly_mode выключен — мгновенный переход без физики
        if not self.humanly_mode:
            pyautogui.moveTo(dest_x, dest_y)
            return

        start_x, start_y = start_pos
        current_x, current_y = float(start_x), float(start_y)
        dist = math.hypot(dest_x - current_x, dest_y - current_y)
        
        if dist < 2:
            pyautogui.moveTo(dest_x, dest_y)
            return

        # Настройки физики (только для humanly_mode)
        G_0 = random.uniform(8.0, 11.0)   # Гравитация (притяжение к цели)
        W_0 = random.uniform(2.5, 4.0)    # Ветер (хаотическое сбивание курса)
        D_0 = random.uniform(10.0, 15.0)  # Дистанция начала замедления у цели

        target_steps = max(12, int((self.base_move_duration * 90) / speed_factor))
        M_0 = max(4.0, dist / target_steps)

        v_x, v_y = 0.0, 0.0
        w_x, w_y = 0.0, 0.0
        
        sqrt_3 = math.sqrt(3.0)
        sqrt_5 = math.sqrt(5.0)

        while True:
            dist = math.hypot(dest_x - current_x, dest_y - current_y)
            if dist < 1.5:
                break

            if dist < D_0:
                W_0 /= sqrt_5
                if M_0 < 3.0:
                    M_0 = 3.0
                else:
                    M_0 /= 2.5
            
            w_x = w_x / sqrt_3 + (random.random() * 2.0 - 1.0) * W_0 / sqrt_5
            w_y = w_y / sqrt_3 + (random.random() * 2.0 - 1.0) * W_0 / sqrt_5

            v_x += w_x + G_0 * (dest_x - current_x) / dist
            v_y += w_y + G_0 * (dest_y - current_y) / dist

            v_mag = math.hypot(v_x, v_y)
            if v_mag > M_0:
                v_clip = M_0 / 2.0 + random.random() * M_0 / 2.0
                v_x = (v_x / v_mag) * v_clip
                v_y = (v_y / v_mag) * v_clip

            current_x += v_x
            current_y += v_y

            pyautogui.moveTo(int(round(current_x)), int(round(current_y)))
            time.sleep(random.uniform(0.001, 0.003))

        pyautogui.moveTo(dest_x, dest_y)

    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]

        # Разброс (jitter) передается ТОЛЬКО если self.humanly_mode равен True
        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 (Полная симуляция: WindMouse + Jitter + Drag)
                # =========================================================
                print(f"[PHYSICAL MOVE]: {uci_move} | WindMouse Drag")

                current_mouse_pos = pyautogui.position()
                self.wind_mouse_move(current_mouse_pos, start_pos, speed_factor=1.4)

                time.sleep(random.uniform(0.03, 0.07))
                pyautogui.mouseDown(button='left')
                time.sleep(random.uniform(0.02, 0.05))

                self.wind_mouse_move(start_pos, end_pos, speed_factor=1.0)

                time.sleep(random.uniform(0.03, 0.06))
                pyautogui.mouseUp(button='left')

            else:
                # =========================================================
                # 2. CLICK MODE (Мгновенный режим: Без WindMouse, без задержек)
                # =========================================================
                print(f"[PHYSICAL MOVE]: {uci_move} | Instant Exact Click")

                # Прямые мгновенные клики в строгий центр без симуляции траекторий
                pyautogui.click(start_x, start_y)
                pyautogui.click(end_x, end_y)

            # При превращении пешки (например, e7e8q)
            if len(uci_move) == 5:
                time.sleep(0.05)
                pyautogui.click()

        finally:
            self.is_moving = False