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


import os
import time
import cv2
import numpy as np
import mss
import keyboard  # Глобальный перехват клавиш

from board_detector import crop_chess_board
from hog_scanner import get_hog_features, extract_dominant_piece_color, scan_frame
from engine_adapter import ChessEngine

# ANSI-коды для красной подсветки
RED_TEXT = "\033[91m\033[1m"
RESET_TEXT = "\033[0m"


def select_board_region(sct, monitor):
    """Открывает окно для выделения области мышкой."""
    print("\n-----------------------------------------------------------")
    print(" ИНСТРУКЦИЯ: Выделите мышкой шахматную доску на экране.")
    print(" После выделения нажмите ENTER или ПРОБЕЛ.")
    print("-----------------------------------------------------------\n")

    sct_img = sct.grab(monitor)
    frame = cv2.cvtColor(np.array(sct_img), cv2.COLOR_BGRA2BGR)

    window_name = "Выделите шахматную доску (Enter - подтвердить)"
    r = cv2.selectROI(window_name, frame, showCrosshair=True, fromCenter=False)
    cv2.destroyWindow(window_name)

    x, y, w, h = map(int, r)
    
    if w == 0 or h == 0:
        return monitor

    return {
        "top": monitor["top"] + y,
        "left": monitor["left"] + x,
        "width": w,
        "height": h
    }


def parse_move_to_coords(move_uci):
    if not move_uci or len(move_uci) < 4:
        return None, None
    
    col_map = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7}
    
    col_from = col_map.get(move_uci[0])
    row_from = 8 - int(move_uci[1])
    
    col_to = col_map.get(move_uci[2])
    row_to = 8 - int(move_uci[3])
    
    return (row_from, col_from), (row_to, col_to)


def print_board(board_matrix, fen, best_move=None, evaluation="", engine_status="ВЫКЛ"):
    os.system('')
    os.system('cls' if os.name == 'nt' else 'clear')

    from_square, to_square = parse_move_to_coords(best_move)

    divider = "  +" + "---+" * 8
    print("\n    a   b   c   d   e   f   g   h")
    print(divider)

    for r_idx, row in enumerate(board_matrix):
        rank = 8 - r_idx
        row_cells = []
        
        for c_idx, symbol in enumerate(row):
            cell_char = symbol if symbol != '.' else ' '
            
            if from_square and (r_idx, c_idx) == from_square:
                cell_str = f"{RED_TEXT}[{cell_char}]{RESET_TEXT}"
            elif to_square and (r_idx, c_idx) == to_square:
                cell_str = f"{RED_TEXT}*{cell_char}*{RESET_TEXT}" if cell_char != ' ' else f"{RED_TEXT} X {RESET_TEXT}"
            else:
                cell_str = f" {cell_char} "
                
            row_cells.append(cell_str)

        print(f"{rank} |" + "|".join(row_cells) + f"| {rank}")
        print(divider)

    print("    a   b   c   d   e   f   g   h")
    print(f"\nFEN: {fen}")
    print(f"Статус Stockfish: [{engine_status}]")
    print("Горячие клавиши (работают в ЛЮБОМ окне):")
    print("  • [ S ]  — Включить / Выключить бот")
    print("  • [ H ]  — Выделить доску мышкой")
    print(f"\nОценка: {evaluation}")
    print(f"ЛУЧШИЙ ХОД: {RED_TEXT}{best_move if best_move else 'Ожидание активации бота (жми S)...'}{RESET_TEXT}\n")


def main():
    TEMPLATES_DIR = "extracted_pieces"
    ALL_PIECES = ['K', 'Q', 'R', 'B', 'N', 'P', 'k', 'q', 'r', 'b', 'n', 'p']
    
    PIECES_CONFIG = {
        "K": "white_K.png", "Q": "white_Q.png", "R": "white_R.png",
        "B": "white_B.png", "N": "white_N.png", "P": "white_P.png",
        "k": "black_k.png", "q": "black_q.png", "r": "black_r.png",
        "b": "black_b.png", "n": "black_n.png", "p": "black_p.png"
    }

    if not os.path.exists(TEMPLATES_DIR):
        print(f"[ОШИБКА] Папка '{TEMPLATES_DIR}' не найдена!")
        return

    print("[1/2] Загрузка шаблонов...")
    templates_hog = {}
    color_database = {}

    for sym, filename in PIECES_CONFIG.items():
        path = os.path.join(TEMPLATES_DIR, filename)
        if not os.path.exists(path):
            continue

        img = cv2.imread(path)
        if img is None:
            continue

        img_50 = cv2.resize(img, (50, 50))
        crop_center = img_50[7:43, 7:43]
        
        templates_hog[sym] = get_hog_features(crop_center)
        color_database[sym] = extract_dominant_piece_color(img_50)

    print("[2/2] Запуск Stockfish...")
    engine = ChessEngine(engine_path="stockfish.exe", time_limit=0.1)

    sct = mss.mss()
    full_monitor = sct.monitors[1]
    current_roi = full_monitor

    pending_fen = ""
    change_timestamp = 0.0
    stockfish_sent = False
    
    current_matrix = []
    best_move = None
    evaluation = ""

    print("\n[ЗАПУСК] Сканирование запущенно! Нажми 'S' в игре для включения бота.")

    try:
        while True:
            start_time = time.time()

            # 1. Глобальная проверка нажатия клавиши 'S' (работает даже из браузера!)
            if keyboard.is_pressed('s'):
                is_active = engine.toggle()
                stockfish_sent = False
                status_str = "ВКЛ" if is_active else "ВЫКЛ"
                print_board(current_matrix, pending_fen, best_move, evaluation, engine_status=status_str)
                time.sleep(0.3)  # Защита от дребезга (чтобы не переключалось 10 раз за секунду)

            # 2. Глобальная проверка нажатия клавиши 'H' (ручная обрезка)
            if keyboard.is_pressed('h'):
                current_roi = select_board_region(sct, full_monitor)
                time.sleep(0.3)

            # 3. Захват кадра
            sct_img = sct.grab(current_roi)
            frame = cv2.cvtColor(np.array(sct_img), cv2.COLOR_BGRA2BGR)

            cropped_board = crop_chess_board(frame)
            matrix, current_fen = scan_frame(cropped_board, color_database, templates_hog, ALL_PIECES)

            # 4. Фиксация изменений
            if current_fen != pending_fen:
                pending_fen = current_fen
                change_timestamp = time.time()
                stockfish_sent = False
                current_matrix = matrix
                status_str = "ВКЛ" if engine.enabled else "ВЫКЛ"
                print_board(current_matrix, current_fen, None, "Ожидание 333 мс...", engine_status=status_str)

            # 5. Пауза 333 мс перед отсылкой в Stockfish
            if not stockfish_sent and (time.time() - change_timestamp) >= 0.333:
                if engine.enabled:
                    best_move, evaluation = engine.analyze_fen(pending_fen)
                else:
                    best_move, evaluation = None, "Движок отключен [Жми S]"
                    
                status_str = "ВКЛ" if engine.enabled else "ВЫКЛ"
                print_board(current_matrix, pending_fen, best_move, evaluation, engine_status=status_str)
                stockfish_sent = True

            elapsed = time.time() - start_time
            time.sleep(max(0.001, 0.1 - elapsed))

    finally:
        engine.close()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n[ИНФО] Работа программы завершена.")
    except Exception as e:
        print(f"\n[КРИТИЧЕСКАЯ ОШИБКА]: {e}")
        import traceback
        traceback.print_exc()
        input("\nНажмите Enter, чтобы закрыть...")