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


import tkinter as tk
from tkinter import ttk
import threading
import time

# --- ИМПОРТЫ МОДУЛЕЙ БОТА (с безопасным фоллбэком, если модули еще не подключены) ---
try:
    import crop
    import analysis
    import logic
    import move
    MODULES_AVAILABLE = True
except ImportError:
    MODULES_AVAILABLE = False
    print("[GUI Warning] Один или несколько модулей (crop, analysis, logic, move) не найдены. Работает в демо-режиме GUI.")


class GradientFrame(tk.Canvas):
    """Кастомный фрейм с вертикальным градиентом"""
    def __init__(self, parent, color1="#2E3239", color2="#1C1E22", **kwargs):
        super().__init__(parent, highlightthickness=0, **kwargs)
        self.color1 = color1
        self.color2 = color2
        self.bind('<Configure>', self._draw_gradient)

    def _draw_gradient(self, event=None):
        self.delete("gradient")
        width = self.winfo_width()
        height = self.winfo_height()
        if width <= 1 or height <= 1:
            return

        r1, g1, b1 = self.winfo_rgb(self.color1)
        r2, g2, b2 = self.winfo_rgb(self.color2)

        r_ratio = (r2 - r1) / height
        g_ratio = (g2 - g1) / height
        b_ratio = (b2 - b1) / height

        # Рисуем градиент горизонтальными линиями
        for i in range(height):
            nr = int((r1 + (r_ratio * i)) / 256)
            ng = int((g1 + (g_ratio * i)) / 256)
            nb = int((b1 + (b_ratio * i)) / 256)
            color = f"#{nr:02x}{ng:02x}{nb:02x}"
            self.create_line(0, i, width, i, fill=color, tags="gradient")
        self.tag_lower("gradient")


class GradientButton(tk.Canvas):
    """Кастомная кнопка с градиентом, скруглением и переключением состояний"""
    def __init__(self, parent, width=120, height=42, text="Start", command=None, **kwargs):
        super().__init__(parent, width=width, height=height, highlightthickness=0, bg="#2a2d32", **kwargs)
        self.btn_width = width
        self.btn_height = height
        self.text_str = text
        self.command = command
        self.is_active = False  # False = Синяя (Start), True = Красная (Stop/Active)

        # Синий градиент (выключен)
        self.blue_top = "#3B82F6"
        self.blue_bottom = "#1D4ED8"

        # Красный градиент (включен)
        self.red_top = "#EF4444"
        self.red_bottom = "#B91C1C"

        self.bind("<Button-1>", self._on_click)
        self.bind("<Enter>", self._on_hover)
        self.bind("<Leave>", self._on_leave)
        self.draw_button()

    def draw_button(self, hover=False):
        self.delete("all")
        c_top = self.red_top if self.is_active else self.blue_top
        c_bottom = self.red_bottom if self.is_active else self.blue_bottom

        if hover:
            # Чуть более светлый тон при наведении
            c_top = "#60A5FA" if not self.is_active else "#F87171"

        # Отрисовка скругленного градиентного прямоугольника через срезCanvas
        radius = 10
        w, h = self.btn_width, self.btn_height

        # Фон с градиентной имитацией
        r1, g1, b1 = self.winfo_rgb(c_top)
        r2, g2, b2 = self.winfo_rgb(c_bottom)

        for i in range(h):
            nr = int((r1 + ((r2 - r1) / h * i)) / 256)
            ng = int((g1 + ((g2 - g1) / h * i)) / 256)
            nb = int((b1 + ((b2 - b1) / h * i)) / 256)
            color = f"#{nr:02x}{ng:02x}{nb:02x}"
            self.create_line(radius, i, w - radius, i, fill=color)

        # Закругление углов (верхний и нижний дуги)
        self.create_arc(0, 0, radius*2, radius*2, start=90, extent=90, fill=c_top, outline="")
        self.create_arc(w-radius*2, 0, w, radius*2, start=0, extent=90, fill=c_top, outline="")
        self.create_arc(0, h-radius*2, radius*2, h, start=180, extent=90, fill=c_bottom, outline="")
        self.create_arc(w-radius*2, h-radius*2, w, h, start=270, extent=90, fill=c_bottom, outline="")

        # Заполнение боковин
        self.create_rectangle(0, radius, radius, h-radius, fill=c_top, outline="")
        self.create_rectangle(w-radius, radius, w, h-radius, fill=c_top, outline="")

        # Текст
        lbl_text = "STOP" if self.is_active else self.text_str
        self.create_text(w // 2, h // 2, text=lbl_text, fill="white", font=("Segoe UI", 11, "bold"))

    def _on_hover(self, event):
        self.draw_button(hover=True)

    def _on_leave(self, event):
        self.draw_button(hover=False)

    def _on_click(self, event):
        self.is_active = not self.is_active
        self.draw_button()
        if self.command:
            self.command(self.is_active)


class ChessBotApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Chess Bot Control")
        self.root.geometry("460x360")
        self.root.resizable(False, False)

        # Главный фоновый градиентный фрейм
        self.bg_frame = GradientFrame(self.root, color1="#32363E", color2="#1A1C20")
        self.bg_frame.pack(fill="both", expand=True)

        # Состояния галочек и настроек
        self.show_moves_var = tk.BooleanVar(value=False)
        self.auto_move_var = tk.BooleanVar(value=False)
        self.humanize_var = tk.BooleanVar(value=False)
        self.stockfish_lvl_var = tk.IntVar(value=10)

        self.bot_running = False

        self._build_ui()

    def _build_ui(self):
        # Контейнер для верхнего блока (Кнопка слева, Галочки справа)
        top_container = tk.Frame(self.bg_frame, bg="", bd=0)
        top_container.place(x=20, y=20, width=420, height=180)

        # СЛЕВА: Кнопка Start/Stop с отступом
        self.start_btn = GradientButton(
            top_container, 
            width=130, 
            height=46, 
            text="Start", 
            command=self.toggle_bot
        )
        self.start_btn.pack(side="left", anchor="nw", padx=(5, 10), pady=10)

        # СПРАВА: Блок галочек
        chk_frame = tk.Frame(top_container, bg="#25282E", bd=1, relief="solid")
        chk_frame.pack(side="right", anchor="ne", padx=(0, 5), pady=5, fill="both", expand=True)

        # Стилизация ttk Checkbutton
        style = ttk.Style()
        style.theme_use('default')
        style.configure(
            "Dark.TCheckbutton", 
            background="#25282E", 
            foreground="#E2E8F0", 
            font=("Segoe UI", 10),
            focuscolor=""
        )
        style.map(
            "Dark.TCheckbutton",
            foreground=[('disabled', '#64748B'), ('active', '#FFFFFF')],
            background=[('disabled', '#25282E'), ('active', '#25282E')]
        )

        # 1. Галочка "Показ ходов"
        self.chk_show_moves = ttk.Checkbutton(
            chk_frame, 
            text="Показ ходов", 
            variable=self.show_moves_var,
            style="Dark.TCheckbutton",
            command=self.on_show_moves_toggle
        )
        self.chk_show_moves.pack(anchor="w", padx=15, pady=(15, 8))

        # 2. Галочка "Авто-ход"
        self.chk_auto_move = ttk.Checkbutton(
            chk_frame, 
            text="Авто-ход", 
            variable=self.auto_move_var,
            style="Dark.TCheckbutton",
            command=self.on_auto_move_toggle
        )
        self.chk_auto_move.pack(anchor="w", padx=15, pady=8)

        # 3. Галочка "Хуманизация" (Снизу от авто-хода, изначально НЕДОСТУПНА)
        self.chk_humanize = ttk.Checkbutton(
            chk_frame, 
            text="Хуманизация", 
            variable=self.humanize_var,
            style="Dark.TCheckbutton",
            state="disabled",
            command=self.on_humanize_toggle
        )
        self.chk_humanize.pack(anchor="w", padx=30, pady=(2, 12)) # Отступ чуть правее для визуальной иерархии

        # СНИЗУ: Блок слайдера Stockfish
        slider_container = tk.Frame(self.bg_frame, bg="#25282E", bd=1, relief="solid")
        slider_container.place(x=20, y=210, width=420, height=130)

        # Заголовок слайдера
        self.lbl_slider_title = tk.Label(
            slider_container, 
            text="Уровень Stockfish: 10", 
            bg="#25282E", 
            fg="#F1F5F9", 
            font=("Segoe UI", 10, "bold")
        )
        self.lbl_slider_title.pack(anchor="w", padx=15, pady=(10, 2))

        # Сам ползунок (1-20)
        self.slider = tk.Scale(
            slider_container,
            from_=1,
            to=20,
            orient="horizontal",
            variable=self.stockfish_lvl_var,
            bg="#25282E",
            fg="#E2E8F0",
            highlightthickness=0,
            troughcolor="#1E293B",
            activebackground="#EF4444",
            sliderrelief="flat",
            showvalue=False,
            command=self.on_slider_move
        )
        self.slider.pack(fill="x", padx=15, pady=(0, 0))

        # Кастомная шкала со сносками и красными отметками
        self.ticks_canvas = tk.Canvas(slider_container, height=55, bg="#25282E", highlightthickness=0)
        self.ticks_canvas.pack(fill="x", padx=15)
        self.ticks_canvas.bind("<Configure>", self._draw_slider_ticks)

    def _draw_slider_ticks(self, event=None):
        canvas = self.ticks_canvas
        canvas.delete("all")
        w = canvas.winfo_width()
        if w <= 1:
            w = 390  # Дефолтная ширина фрейма за вычетом padx

        # Позиции от 1 до 20
        padding = 10
        usable_w = w - 2 * padding

        # Определение 4 основных зон (Easy, Medium, Hard, Max)
        # Уровень 1..5 -> Easy, 6..10 -> Medium, 11..15 -> Hard, 16..20 -> Max
        sections = [
            (1, 5, "EASY", 3),
            (6, 10, "MEDIUM", 8),
            (11, 15, "HARD", 13),
            (16, 20, "MAX", 18)
        ]

        # Рисуем деления 1-20
        for lvl in range(1, 21):
            x = padding + (lvl - 1) * (usable_w / 19.0)
            
            # Проверяем, является ли уровень ключевым красным риской (1, 5, 10, 15, 20)
            if lvl in [1, 5, 10, 15, 20]:
                # Большая красная сноска
                canvas.create_line(x, 0, x, 14, fill="#EF4444", width=2)
                canvas.create_text(x, 22, text=str(lvl), fill="#EF4444", font=("Segoe UI", 8, "bold"))
            else:
                # Маленькая обычная риска
                canvas.create_line(x, 0, x, 8, fill="#64748B", width=1)

        # Добавляем красные подписи подразделов (Easy, Medium, Hard, Max)
        for start_lvl, end_lvl, label, mid_lvl in sections:
            x_mid = padding + (mid_lvl - 1) * (usable_w / 19.0)
            canvas.create_text(
                x_mid, 40, 
                text=label, 
                fill="#F87171", 
                font=("Segoe UI", 9, "bold")
            )

    def on_slider_move(self, val):
        lvl = int(float(val))
        self.lbl_slider_title.config(text=f"Уровень Stockfish: {lvl}")

    def on_show_moves_toggle(self):
        state = self.show_moves_var.get()
        print(f"[GUI] Показ ходов: {state}")

    def on_auto_move_toggle(self):
        is_auto = self.auto_move_var.get()
        print(f"[GUI] Авто-ход: {is_auto}")
        
        # Разблокировка/Блокировка галочки Хуманизация
        if is_auto:
            self.chk_humanize.config(state="normal")
        else:
            self.chk_humanize.config(state="disabled")
            self.humanize_var.set(False) # Автоматически снимаем галочку, если авто-ход выключен

    def on_humanize_toggle(self):
        state = self.humanize_var.get()
        print(f"[GUI] Хуманизация авто-хода: {state}")

    def toggle_bot(self, active):
        self.bot_running = active
        if active:
            print("[GUI] >>> БОТ ЗАПУЩЕН! Поиск доски и распознавание фигур (crop.py & analysis.py)...")
            # Запуск логики в отдельном потоке, чтобы GUI не зависал
            threading.Thread(target=self._run_bot_logic, daemon=True).start()
        else:
            print("[GUI] <<< БОТ ОСТАНОВЛЕН.")

    def _run_bot_logic(self):
        """Фоновый поток работы бота"""
        while self.bot_running:
            # Здесь будет обращение к crop.py, analysis.py, logic.py и move.py
            if MODULES_AVAILABLE:
                # Пример вызова функций при подключении:
                # board_img = crop.get_board_image()
                pass
            time.sleep(0.5)


if __name__ == "__main__":
    root = tk.Tk()
    app = ChessBotApp(root)
    root.mainloop()