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


import time
import keyboard
import chess
import chess.engine
import os
from analysis import ChessAnalyzer
import crop
from move import ChessMover


class ChessLogic:
    def __init__(self, engine_path="stockfish.exe"):
        self.analyzer = ChessAnalyzer()
        self.mover = ChessMover()  # Модуль управления мышью
        self.engine_path = engine_path

        # Флаги управления
        self.trigger_analysis = False
        self.trigger_toggle_color = False
        self.is_analyzed = False

        # Состояние доски 8х8
        self.board_state = None

        # Мой цвет ('w' или 'b') и активный ход ('w' или 'b')
        self.my_color = 'w'
        self.active_color = 'w'
        self.en_passant_target = '-'

    def on_start_pressed(self):
        self.trigger_analysis = True

    def on_color_toggle_pressed(self):
        self.trigger_toggle_color = True

    def on_auto_move_toggled(self):
        """Переключение авто-хода с немедленной проверкой текущей позиции"""
        self.mover.toggle_auto_move()
        # Если авто-ход ВКЛЮЧИЛИ, доска уже проанализирована и сейчас наш ход — сходить сразу
        if self.mover.auto_move_enabled and self.is_analyzed:
            print("[AUTO-MOVE TRIGGER]: Авто-ход включён, выполняем ход...")
            self.evaluate_current_turn()

    def toggle_my_color(self):
        """Переключение цвета игрока ('w' <-> 'b')"""
        self.my_color = 'b' if self.my_color == 'w' else 'w'
        color_str = "ЧЁРНЫЕ (b)" if self.my_color == 'b' else "БЕЛЫЕ (w)"
        print(f"\n[COLOR CHANGED]: Вы играете за {color_str}")

    def convert_coords_to_notation(self, row, col):
        files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
        ranks = ['8', '7', '6', '5', '4', '3', '2', '1']
        return f"{files[col]}{ranks[row]}"

    def get_castling_rights(self):
        """Проверка прав на рокировку"""
        if not self.board_state or len(self.board_state) != 8:
            return "KQkq"

        rights = ""
        if self.board_state[7][4] == 'K':
            if self.board_state[7][7] == 'R':
                rights += 'K'
            if self.board_state[7][0] == 'R':
                rights += 'Q'

        if self.board_state[0][4] == 'k':
            if self.board_state[0][7] == 'r':
                rights += 'k'
            if self.board_state[0][0] == 'r':
                rights += 'q'

        return rights if rights else '-'

    def board_to_fen(self):
        """Конвертация матрицы доски в FEN"""
        if not self.board_state:
            return ""

        working_board = self.board_state
        if self.my_color == 'b':
            working_board = [row[::-1] for row in self.board_state[::-1]]

        fen_rows = []
        for row in working_board:
            empty_count = 0
            row_fen = ""
            for cell in row:
                if cell == "":
                    empty_count += 1
                else:
                    if empty_count > 0:
                        row_fen += str(empty_count)
                        empty_count = 0
                    row_fen += cell
            if empty_count > 0:
                row_fen += str(empty_count)
            fen_rows.append(row_fen)

        board_fen = "/".join(fen_rows)
        castling = self.get_castling_rights()
        en_passant = self.en_passant_target if self.en_passant_target else '-'

        return f"{board_fen} {self.active_color} {castling} {en_passant} 0 1"

    def print_board(self):
        """Вывод состояния доски"""
        if not self.board_state:
            print("[!] Доска пуста")
            return

        print("\n--- ТЕКУЩАЯ ДОСКА ---")
        for row in self.board_state:
            print(" ".join([cell if cell != "" else "." for cell in row]))
        print("---------------------\n")

    def restore_missing_kings(self, new_board):
        """Восстановление перекрытого курсором короля"""
        if not self.board_state:
            return new_board

        has_white_king = any('K' in row for row in new_board)
        has_black_king = any('k' in row for row in new_board)

        restored_board = [list(row) for row in new_board]

        if not has_white_king:
            for r in range(8):
                for c in range(8):
                    if self.board_state[r][c] == 'K':
                        restored_board[r][c] = 'K'

        if not has_black_king:
            for r in range(8):
                for c in range(8):
                    if self.board_state[r][c] == 'k':
                        restored_board[r][c] = 'k'

        return restored_board

    def detect_who_moved(self, old_board, new_board):
        """Определяет, кто сходил, по факту перемещения фигуры между кадрами"""
        if not old_board or not new_board:
            return None

        white_moved = False
        black_moved = False

        for r in range(8):
            for c in range(8):
                p_old = old_board[r][c]
                p_new = new_board[r][c]

                if p_old != p_new:
                    # Если фигура покинула свою клетку
                    if p_old != "":
                        if p_old.isupper():
                            white_moved = True
                        elif p_old.islower():
                            black_moved = True

        if white_moved and not black_moved:
            return 'b'  # Сходили Белые -> следующий ход ЧЁРНЫХ
        elif black_moved and not white_moved:
            return 'w'  # Сходили Чёрные -> следующий ход БЕЛЫХ

        return None

    def is_valid_board_transition(self, old_board, new_board):
        """Проверка шахматной логики исчезновения фигур"""
        if not old_board:
            return True

        for r in range(8):
            for c in range(8):
                p_old = old_board[r][c]
                if not p_old:
                    continue

                p_new = new_board[r][c]
                if p_new == p_old:
                    continue

                if p_new != "":
                    is_old_white = p_old.isupper()
                    is_new_white = p_new.isupper()
                    if is_old_white != is_new_white:
                        continue

                moved = False
                for nr in range(8):
                    for nc in range(8):
                        if old_board[nr][nc] != p_old and new_board[nr][nc] == p_old:
                            moved = True
                            break
                    if moved:
                        break
                if moved:
                    continue

                if p_old in ('P', 'p'):
                    prom_rank = 0 if p_old == 'P' else 7
                    promoted = False
                    for nc in range(8):
                        if new_board[prom_rank][nc] in ('Q', 'R', 'B', 'N', 'q', 'r', 'b', 'n') and old_board[prom_rank][nc] != new_board[prom_rank][nc]:
                            promoted = True
                            break
                    if promoted:
                        continue

                print(f"[FRAME DROPPED]: Фигура '{p_old}' пропала не по правилам. Кадр пропущен.")
                return False

        return True

    def evaluate_current_turn(self):
        """Вызов Stockfish и передача хода в move.py (ТОЛЬКО ЕСЛИ НАШ ХОД)"""
        turn_str = "БЕЛЫЕ (w)" if self.active_color == 'w' else "ЧЁРНЫЕ (b)"
        my_color_str = "БЕЛЫЕ (w)" if self.my_color == 'w' else "ЧЁРНЫЕ (b)"

        print(f"[STATUS]: Ход на доске: {turn_str} | Мы играем за: {my_color_str}")

        # СТРОГАЯ ПРОВЕРКА: Если сейчас ход соперника — БОТ ИГНОРИРУЕТ И НИЧЕГО НЕ ДЕЛАЕТ!
        if self.active_color != self.my_color:
            print(f"[WAITING]: Сейчас ход соперника ({turn_str}). Ждём его ход...")
            return

        print("[ANALYZING]: Наш ход! Вызываем Stockfish...")
        fen = self.board_to_fen()

        if not os.path.exists(self.engine_path):
            print(f"[!] Ошибка: Движок Stockfish не найден по пути '{self.engine_path}'!")
            return

        try:
            board = chess.Board(fen)
            with chess.engine.SimpleEngine.popen_uci(self.engine_path) as engine:
                result = engine.play(board, chess.engine.Limit(time=0.5))
                best_move = result.move.uci()
                print(f"\n==========================================")
                print(f" >>> ЛУЧШИЙ ХОД (Stockfish): {best_move} <<<")
                print(f"==========================================\n")

                # Выполняем ход мышью (если включен режим 'L')
                self.mover.execute_move(best_move, self.my_color)

        except Exception as e:
            print(f"[!] Ошибка при вызове Stockfish: {e}")

    def process_initial_analysis(self):
        """Первичный анализ по кнопке 'S'"""
        print("\n[!] Нажата 'S'. Первичный анализ доски...")
        board_img = crop.get_board_image() if hasattr(crop, 'get_board_image') else crop.find_chessboard_strictly_1to1()[1]

        if board_img is None:
            print("[!] Ошибка: Не удалось получить изображение доски!")
            return

        self.board_state, detected_turn = self.analyzer.get_board_state_with_color(board_img)

        if self.board_state:
            self.is_analyzed = True
            print("[√] Позиция успешно проанализирована!")
            self.print_board()

            # В шахматах право первого хода ВСЕГДА у Белых ('w')
            if detected_turn:
                self.active_color = detected_turn
            else:
                self.active_color = 'w'

            self.evaluate_current_turn()

    def track_board_changes(self):
        """Отслеживание изменений на доске"""
        board_img = crop.get_board_image() if hasattr(crop, 'get_board_image') else crop.find_chessboard_strictly_1to1()[1]

        if board_img is None:
            return

        raw_board_state, detected_turn = self.analyzer.get_board_state_with_color(board_img)

        if not raw_board_state:
            return

        new_board_state = self.restore_missing_kings(raw_board_state)

        if new_board_state != self.board_state:
            if not self.is_valid_board_transition(self.board_state, new_board_state):
                return

            print("\n[CHANGE DETECTED]: Фигуры передвинулись!")

            # 1. Сначала определяем, кто сходил, по реальному перемещению фигур
            next_turn = self.detect_who_moved(self.board_state, new_board_state)
            
            # 2. Если не удалось определить по фигурам — используем цвет подветки
            if not next_turn:
                next_turn = detected_turn

            self.board_state = new_board_state

            if next_turn:
                self.active_color = next_turn
                print(f"[TURN UPDATED]: Очередь хода сменилась на: {'БЕЛЫЕ' if self.active_color == 'w' else 'ЧЁРНЫЕ'}")
            else:
                print("[TURN KEEP]: Очередь хода не изменилась (сомнительный кадр).")

            self.print_board()
            self.evaluate_current_turn()

        time.sleep(0.5)

    def run(self):
        print("==========================================")
        print(" Шахматный бот запущен.")
        print(" 'S' — старт анализа доски.")
        print(" 'C' — сменить цвет (Белые/Чёрные).")
        print(" 'L' — ВКЛ/ВЫКЛ автоход мыши.")
        print(" 'H' — сменить режим (Humanly drag / Click).")
        print(" '+' / '-' — изменить скорость автохода.")
        print("==========================================")

        keyboard.add_hotkey('s', self.on_start_pressed)
        keyboard.add_hotkey('c', self.on_color_toggle_pressed)
        keyboard.add_hotkey('l', self.on_auto_move_toggled)  # Обновленный обработчик L
        keyboard.add_hotkey('h', self.mover.toggle_humanly_mode)
        keyboard.add_hotkey('+', self.mover.speed_up)
        keyboard.add_hotkey('=', self.mover.speed_up)
        keyboard.add_hotkey('-', self.mover.speed_down)

        try:
            while True:
                if self.trigger_toggle_color:
                    self.trigger_toggle_color = False
                    self.toggle_my_color()
                    if self.is_analyzed:
                        self.evaluate_current_turn()

                if self.trigger_analysis:
                    self.trigger_analysis = False
                    self.process_initial_analysis()

                if self.is_analyzed:
                    self.track_board_changes()

                time.sleep(0.05)

        except KeyboardInterrupt:
            print("\n[!] Работа бота завершена.")


if __name__ == "__main__":
    logic = ChessLogic(engine_path="stockfish.exe")
    logic.run()