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


import os
import time
import cv2
import numpy as np
import mss
import keyboard
from colorama import init, Fore, Style

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
from auto_mover import execute_move, change_speed

OPPONENT_MOVE_DELAY = 0.5
init(autoreset=True)


def parse_move_to_coords(move_uci, is_black=False):
    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])
    
    if is_black:
        row_from, col_from = 7 - row_from, 7 - col_from
        row_to, col_to = 7 - row_to, 7 - col_to
        
    return (row_from, col_from), (row_to, col_to)


def flip_matrix_180(matrix):
    return [row[::-1] for row in matrix[::-1]]


def matrix_to_fen(matrix):
    fen_rows = []
    for row in matrix:
        empty_count = 0
        row_str = ""
        for cell in row:
            if cell in ('.', ' '):
                empty_count += 1
            else:
                if empty_count > 0:
                    row_str += str(empty_count)
                    empty_count = 0
                row_str += cell
        if empty_count > 0:
            row_str += str(empty_count)
        fen_rows.append(row_str)
    return "/".join(fen_rows)


def extract_opponent_pieces(matrix, player_color_code):
    opponent_matrix = []
    for row in matrix:
        op_row = []
        for cell in row:
            if player_color_code == 'w':
                op_row.append(cell if cell.islower() else '.')
            else:
                op_row.append(cell if cell.isupper() else '.')
        opponent_matrix.append(op_row)
    return opponent_matrix


def count_opponent_pieces(opponent_matrix):
    return sum(1 for row in opponent_matrix for cell in row if cell != '.')


def is_valid_position(matrix):
    flat = [cell for row in matrix for cell in row]
    return ('K' in flat) and ('k' in flat)


def print_board(board_matrix, fen, best_move=None, evaluation="", engine_status="ВЫКЛ", player_color="Белые (w)", is_black=False, status_msg=""):
    os.system('cls' if os.name == 'nt' else 'clear')

    from_square, to_square = parse_move_to_coords(best_move, is_black=is_black)
    files_hdr = "    h   g   f   e   d   c   b   a" if is_black else "    a   b   c   d   e   f   g   h"
    divider = "  +" + "---+" * 8
    print(f"\n{files_hdr}\n{divider}")

    for r_idx, row in enumerate(board_matrix):
        rank = r_idx + 1 if is_black else 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"{Fore.RED}{Style.BRIGHT}[{cell_char}]{Style.RESET_ALL}"
            elif to_square and (r_idx, c_idx) == to_square:
                cell_str = f"{Fore.RED}{Style.BRIGHT}*{cell_char}*{Style.RESET_ALL}" if cell_char != ' ' else f"{Fore.RED}{Style.BRIGHT} X {Style.RESET_ALL}"
            else:
                cell_str = f" {cell_char} "
            row_cells.append(cell_str)

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

    print(files_hdr)
    print(f"\nFEN: {fen}")
    print(f"Статус Stockfish: [{engine_status}] | Цвет: {Fore.YELLOW}{Style.BRIGHT}{player_color}{Style.RESET_ALL}")
    print(f"Состояние: {Fore.CYAN}{Style.BRIGHT}{status_msg}{Style.RESET_ALL}")
    print("\nГорячие клавиши: [S] - Вкл/Выкл | [C] - Цвет | [ ] ] - Быстрее | [ [ ] - Медленнее")
    print(f"\nОценка: {evaluation}")
    
    best_move_str = f"{Fore.RED}{Style.BRIGHT}{best_move}{Style.RESET_ALL}" if best_move else "Ожидание хода..."
    print(f"ЛУЧШИЙ ХОД: {best_move_str}\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]

    stable_fen = ""
    last_analyzed_fen = ""
    last_opponent_state = None
    change_timestamp = 0.0
    active_color_code = 'w'
    player_color_str = "Белые (w)"

    current_matrix = []
    best_move = None
    evaluation = ""
    status_msg = "Ожидание..."

    print("\n[ЗАПУСК] Клавиши: 'S' - Старт/Стоп, 'C' - Цвет, ']' - Быстрее мышь, '[' - Медленнее мышь.")

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

            # --- УПРАВЛЕНИЕ ГОРЯЧИМИ КЛАВИШАМИ ---
            if keyboard.is_pressed('s'):
                is_active = engine.toggle()
                last_analyzed_fen = ""
                last_opponent_state = None
                status_str = "ВКЛ" if is_active else "ВЫКЛ"
                print_board(current_matrix, stable_fen, best_move, evaluation, status_str, player_color_str, is_black=(active_color_code == 'b'), status_msg="Бот переключен")
                time.sleep(0.3)

            if keyboard.is_pressed('c'):
                active_color_code = 'b' if active_color_code == 'w' else 'w'
                player_color_str = "Чёрные (b)" if active_color_code == 'b' else "Белые (w)"
                last_analyzed_fen = ""
                last_opponent_state = None
                status_str = "ВКЛ" if engine.enabled else "ВЫКЛ"
                print_board(current_matrix, stable_fen, best_move, evaluation, status_str, player_color_str, is_black=(active_color_code == 'b'), status_msg="Цвет изменен")
                time.sleep(0.3)

            if keyboard.is_pressed('['):
                change_speed(0.05)
                time.sleep(0.2)

            if keyboard.is_pressed(']'):
                change_speed(-0.05)
                time.sleep(0.2)

            # --- ЗАХВАТ КАДРА И СКАН ИГРОВОГО ПОЛЯ ---
            sct_img = sct.grab(full_monitor)
            frame = cv2.cvtColor(np.array(sct_img), cv2.COLOR_BGRA2BGR)

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

            is_black = (active_color_code == 'b')
            standard_matrix = flip_matrix_180(matrix) if is_black else matrix
            current_matrix = matrix

            if not is_valid_position(standard_matrix):
                change_timestamp = time.time()
                time.sleep(0.05)
                continue

            base_fen = matrix_to_fen(standard_matrix)
            raw_current_fen = f"{base_fen} {active_color_code} - - 0 1"

            current_opponent_state = extract_opponent_pieces(standard_matrix, active_color_code)

            # --- ДЕТЕКТОР СТАБИЛЬНОСТИ ПОЗИЦИИ ---
            if raw_current_fen != stable_fen:
                stable_fen = raw_current_fen
                change_timestamp = time.time()

            # --- ТАЙМЕР И ВЫПОЛНЕНИЕ АВТО-ХОДА ---
            if (time.time() - change_timestamp) >= OPPONENT_MOVE_DELAY and stable_fen != last_analyzed_fen:
                if last_opponent_state is None:
                    opponent_moved = True
                else:
                    current_count = count_opponent_pieces(current_opponent_state)
                    last_count = count_opponent_pieces(last_opponent_state)
                    opponent_moved = (current_opponent_state != last_opponent_state) and (current_count >= last_count)

                last_opponent_state = current_opponent_state

                if opponent_moved:
                    if engine.enabled:
                        status_msg = "Противник сходил! Анализ Stockfish..."
                        status_str = "ВКЛ"
                        print_board(current_matrix, stable_fen, None, "Думает...", status_str, player_color_str, is_black=is_black, status_msg=status_msg)
                        
                        best_move, evaluation = engine.analyze_fen(stable_fen)
                        last_analyzed_fen = stable_fen
                        status_msg = "Ход найден! Выполняется авто-ход..."

                        # === ВЫПОЛНЕНИЕ ХОДА В ОКНЕ ИГРЫ ===
                        if best_move:
                            execute_move(best_move, board_roi, is_black=is_black)
                    else:
                        best_move, evaluation = None, "Движок отключен [S]"
                        last_analyzed_fen = stable_fen
                        status_msg = "Движок отключен"

                    status_str = "ВКЛ" if engine.enabled else "ВЫКЛ"
                    print_board(current_matrix, stable_fen, best_move, evaluation, status_str, player_color_str, is_black=is_black, status_msg=status_msg)

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

    finally:
        engine.close()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nПрограмма завершена.")