Загрузка данных
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