Загрузка данных
import time
import random
import keyboard
import chess
import chess.engine
import os
from analysis import ChessAnalyzer
import crop
class ChessLogic:
def __init__(self, engine_path="stockfish.exe", mode="medium"):
self.analyzer = ChessAnalyzer()
self.engine_path = engine_path
# --- НАСТРОЙКИ СИЛЫ И ДВИЖКА ---
self.mode = mode.lower() # Режимы: 'easy', 'medium', 'hard', 'max'
self.target_elo = 1650 # Значение Elo по умолчанию
self.multipv_count = 3 # Количество лучших ходов для выборки
# Глубина ходов выведена отдельно (фиксированная)
self.fixed_depth = 12
# Флаги и состояние
self.trigger_analysis = False
self.trigger_toggle_color = False
self.is_analyzed = False
self.board_state = []
self.my_color = 'w'
self.active_color = 'w'
self.en_passant_target = None
# Инициализация параметров сложности
self.set_difficulty_mode(self.mode)
def set_difficulty_mode(self, mode):
"""Конфигурация параметров под выбранный режим"""
self.mode = mode.lower()
if self.mode == 'easy':
self.target_elo = 1350
self.multipv_count = 3
print(f"[ENGINE CONFIG]: Режим 'EASY' | Elo: {self.target_elo} | MultiPV: Top-3 (Random)")
elif self.mode == 'medium':
self.target_elo = 1650
self.multipv_count = 3
print(f"[ENGINE CONFIG]: Режим 'MEDIUM' | Elo: {self.target_elo} | MultiPV: Top-3 (Random)")
elif self.mode == 'hard':
self.target_elo = 2000
self.multipv_count = 3
print(f"[ENGINE CONFIG]: Режим 'HARD' | Elo: {self.target_elo} | MultiPV: Top-3 (Random)")
elif self.mode == 'max':
self.target_elo = None # Без ограничения по Elo
self.multipv_count = 1 # Без MultiPV (только 1 лучший ход)
print(f"[ENGINE CONFIG]: Режим 'MAX' | Без лимита Elo | Строго лучший ход (PV1)")
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 find_king_position(self, board_matrix, king_symbol):
for r in range(8):
for c in range(8):
if board_matrix[r][c] == king_symbol:
return r, c
return None
def restore_missing_kings(self, new_board_state):
if not self.board_state:
return new_board_state
restored_state = [row[:] for row in new_board_state]
for king_symbol in ['K', 'k']:
new_pos = self.find_king_position(restored_state, king_symbol)
if new_pos is None:
old_pos = self.find_king_position(self.board_state, king_symbol)
if old_pos:
r, c = old_pos
restored_state[r][c] = king_symbol
print(f"[INERTIA] Король '{king_symbol}' перекрыт! Восстановлен на {self.convert_coords_to_notation(r, c)}")
return restored_state
def get_oriented_board_state(self):
if not self.board_state:
return None
if self.my_color == 'w':
return self.board_state
return [row[::-1] for row in self.board_state[::-1]]
def get_fen_string(self):
oriented_board = self.get_oriented_board_state()
if not oriented_board:
return ""
fen_rows = []
for row in oriented_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)
position_fen = "/".join(fen_rows)
ep_str = "-"
if self.en_passant_target:
ep_str = self.convert_coords_to_notation(*self.en_passant_target['target_pos'])
castling_rights = self.get_castling_rights()
return f"{position_fen} {self.active_color} {castling_rights} {ep_str} 0 1"
def on_start_pressed(self):
self.trigger_analysis = True
def on_color_toggle_pressed(self):
self.trigger_toggle_color = True
def toggle_my_color(self):
self.my_color = 'b' if self.my_color == 'w' else 'w'
color_name = "БЕЛЫЕ (w)" if self.my_color == 'w' else "ЧЁРНЫЕ (b) [Доска развернута]"
print(f"\n[COLOR SET]: Вы играете за -> {color_name}")
if self.is_analyzed and self.board_state:
self.evaluate_current_turn()
def process_initial_analysis(self):
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 = self.analyzer.get_board_state(board_img)
if self.board_state:
self.is_analyzed = True
print("[√] Позиция успешно проанализирована!")
self.print_board()
self.active_color = self.my_color
self.evaluate_current_turn()
else:
print("[!] Ошибка: Не удалось распознать фигуры на доске.")
def analyze_with_engine(self, fen_string, time_limit=0.5):
"""Запрос хода у Stockfish с учётом Elo, MultiPV и выведенной глубиной"""
if not fen_string:
return None
try:
board = chess.Board(fen_string)
if not board.is_valid():
print("[! WARN] Позиция на доске невалидна, пропускаем вызов движка...")
return None
with chess.engine.SimpleEngine.popen_uci(self.engine_path) as engine:
# 1. Настройка Elo в UCI
uci_options = {}
if self.target_elo is not None:
uci_options["UCI_LimitStrength"] = True
uci_options["UCI_Elo"] = max(1320, min(3190, self.target_elo))
else:
uci_options["UCI_LimitStrength"] = False
engine.configure(uci_options)
# 2. Формируем ограничение с отдельным выводом фиксированной глубины
limit = chess.engine.Limit(time=time_limit, depth=self.fixed_depth)
# 3. Выбор количества линий для MultiPV
pv_count = 1 if self.mode == 'max' else self.multipv_count
print(f"[ENGINE THINKING] Глубина: {self.fixed_depth} | Лимит времени: {time_limit}s | Mode: {self.mode.upper()}")
# Анализ позиции
analysis = engine.analyse(board, limit, multipv=pv_count)
# Извлекаем кандидатов на ход
if isinstance(analysis, list):
candidate_moves = [info["pv"][0] for info in analysis if "pv" in info and len(info["pv"]) > 0]
else:
candidate_moves = [analysis["pv"][0]] if "pv" in analysis and len(analysis["pv"]) > 0 else []
if not candidate_moves:
print("[!] Ошибка: Движок не предложил ни одного хода!")
return None
# 4. Логика выборки хода
if self.mode == 'max':
best_move = candidate_moves[0] # Строго 1-й (лучший) ход
choice_info = "PV1 (Идеальный ход)"
else:
best_move = random.choice(candidate_moves) # Рандом из Top-3
chosen_idx = candidate_moves.index(best_move) + 1
choice_info = f"Случайный из Top-{len(candidate_moves)} (Выбран PV{chosen_idx})"
is_castling = board.is_castling(best_move)
san_move = board.san(best_move)
print(f" [ВАШ ХОД]: {san_move} (UCI: {best_move.uci()}) | {choice_info}")
if is_castling:
print(" [INFO] Рекомендуемый ход - РОКИРОВКА!")
return best_move.uci()
except Exception as e:
print(f"[!] Ошибка Stockfish: {e}")
return None
def evaluate_current_turn(self):
current_fen = self.get_fen_string()
if not current_fen:
return
print(f"\nТекущий FEN: {current_fen}")
if self.active_color == self.my_color:
print(" Твой ход! Анализируем позицию через Stockfish...")
self.analyze_with_engine(current_fen)
else:
print(" Сейчас ход противника. Ожидаем ответа на доске...\n")
def print_board(self):
if not self.board_state:
return
print("\n a b c d e f g h")
print(" +-----------------+")
for i, row in enumerate(self.board_state):
rank = 8 - i
row_str = " | ".join([p if p != "" else "." for p in row])
print(f"{rank} | {row_str} | {rank}")
print(" +-----------------+")
print(" a b c d e f g h\n")
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 = self.analyzer.get_board_state(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:
print("[CHANGE DETECTED]: Фигуры передвинулись!")
self.board_state = new_board_state
self.active_color = 'b' if self.active_color == 'w' else 'w'
self.print_board()
self.evaluate_current_turn()
time.sleep(0.5)
def run(self):
print("================================================")
print(" Шахматный бот запущен.")
print(" Нажмите 'S' - старт анализа.")
print(" Нажмите 'C' - изменить свой цвет (Белые/Чёрные).")
print("================================================")
keyboard.add_hotkey('s', self.on_start_pressed)
keyboard.add_hotkey('c', self.on_color_toggle_pressed)
try:
while True:
if self.trigger_analysis:
self.trigger_analysis = False
self.process_initial_analysis()
if self.trigger_toggle_color:
self.trigger_toggle_color = False
self.toggle_my_color()
if self.is_analyzed:
self.track_board_changes()
time.sleep(0.5)
except KeyboardInterrupt:
print("\nПрограмма завершена.")
if __name__ == '__main__':
# Можно передать 'easy', 'medium', 'hard' или 'max'
logic = ChessLogic(engine_path="stockfish.exe", mode="medium")
logic.run()