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


import pygame
import requests
import json
import threading
import re
import sys

# ================= КОНФИГУРАЦИЯ =================
# Вставь сюда свой ключ от OpenRouter
OPENROUTER_API_KEY = "ТВОЙ_OPENROUTER_API_KEY"
MODEL_NAME = "google/gemini-pro-1.5"

# Настройки окна и графики
WIDTH, HEIGHT = 940, 640
BOARD_SIZE = 640
SQUARE_SIZE = BOARD_SIZE // 8
LOG_WIDTH = WIDTH - BOARD_SIZE
FPS = 60

# Цвета
COLOR_LIGHT = (240, 217, 181)
COLOR_DARK = (181, 136, 99)
COLOR_BG_LOG = (40, 40, 40)
COLOR_TEXT = (220, 220, 220)
COLOR_ERROR = (255, 100, 100)

# ================= ИГРОВАЯ ЛОГИКА =================
# Начальная доска. Заглавные - белые, строчные - черные
board_matrix = [
    ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
    ['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
    ['', '', '', '', '', '', '', ''],
    ['', '', '', '', '', '', '', ''],
    ['', '', '', '', '', '', '', ''],
    ['', '', '', '', '', '', '', ''],
    ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
    ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]

# Глобальные переменные состояния
current_turn = 'white'
logs = ["Нажми ПРОБЕЛ для хода белых."]
api_loading = False
animating = False

# Данные для анимации
anim_piece = ''
anim_start_pos = (0, 0)
anim_target_pos = (0, 0)
anim_current_pos = [0.0, 0.0]
anim_progress = 0.0
anim_speed = 0.05 # Скорость интерполяции (0.01 - 1.0)
target_board_update = None # (row, col, piece) для обновления матрицы после анимации

def chess_coord_to_grid(move_str):
    """
    Переводит строку формата 'e2e4' в координаты матрицы (start_col, start_row, end_col, end_row).
    """
    move_str = move_str.lower().strip()
    if len(move_str) < 4:
        raise ValueError(f"Неверный формат хода: {move_str}")
    
    files = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7}
    
    try:
        start_col = files[move_str[0]]
        start_row = 8 - int(move_str[1]) # В матрице индекс 0 - это 8-я горизонталь
        end_col = files[move_str[2]]
        end_row = 8 - int(move_str[3])
        return start_col, start_row, end_col, end_row
    except (KeyError, ValueError):
        raise ValueError(f"Не удалось расшифровать координаты: {move_str}")

def board_to_string():
    """Простая конвертация доски в текст для передачи в промпт."""
    res = ""
    for r in range(8):
        for c in range(8):
            p = board_matrix[r][c]
            res += p if p else '.'
        res += '\n'
    return res

# ================= СЕТЕВОЙ МОДУЛЬ =================
def fetch_ai_move(color):
    """
    Синхронный запрос к OpenRouter.
    """
    system_prompt = (
        "Ты — шахматный движок. Идет обычная игра. "
        "Оцени текущую доску и сделай свой следующий ход. "
        "Твой ответ должен быть СТРОГО в формате валидного JSON на русском языке, без дополнительного текста. "
        "Ожидаемые ключи: 'thought' (твои мысли о ходе), 'piece' (буква фигуры: P, N, B, R, Q, K), "
        "'move' (координаты хода, например 'e2e4' или 'g8f6')."
    )
    
    user_prompt = f"Твой цвет: {color}\nТекущая доска (сверху черные, снизу белые):\n{board_to_string()}\nСделай ход."
    
    headers = {
        "Authorization": f"Bearer {OPENROUTER_API_KEY}",
        "Content-Type": "application/json"
    }
    
    data = {
        "model": MODEL_NAME,
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
    }
    
    response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=data)
    response.raise_for_status()
    
    response_data = response.json()
    raw_content = response_data['choices'][0]['message']['content']
    
    # Очистка от маркдауна (частая галлюцинация LLM при генерации JSON)
    clean_json = re.sub(r'```json\n|\n```|```', '', raw_content).strip()
    return json.loads(clean_json)

def api_worker():
    """Функция для запуска в отдельном потоке, чтобы не вешать Pygame."""
    global api_loading, logs, animating
    global anim_piece, anim_start_pos, anim_target_pos, anim_current_pos, anim_progress, target_board_update, current_turn
    
    try:
        data = fetch_ai_move(current_turn)
        
        thought = data.get("thought", "Нет мыслей.")
        piece = data.get("piece", "?")
        move_str = data.get("move", "0000")
        
        logs.append(f"[{current_turn.upper()}] Мысли: {thought}")
        logs.append(f"Ход: {piece} {move_str}")
        
        try:
            sc, sr, ec, er = chess_coord_to_grid(move_str)
            
            # Подготовка к анимации
            anim_piece = board_matrix[sr][sc]
            if not anim_piece: 
                # Если клетка пуста (галюцинация ИИ), берем заявленную фигуру
                anim_piece = piece if current_turn == 'white' else piece.lower()
                
            board_matrix[sr][sc] = '' # Стираем фигуру со старого места
            
            anim_start_pos = (sc * SQUARE_SIZE, sr * SQUARE_SIZE)
            anim_target_pos = (ec * SQUARE_SIZE, er * SQUARE_SIZE)
            anim_current_pos = list(anim_start_pos)
            anim_progress = 0.0
            
            target_board_update = (er, ec, anim_piece)
            animating = True
            
            # Смена хода
            current_turn = 'black' if current_turn == 'white' else 'white'
            logs.append(f"--- Ожидание: {current_turn.upper()} ---")
            
        except Exception as e:
            logs.append(f"ОШИБКА КООРДИНАТ: {str(e)}")
            
    except json.JSONDecodeError:
        logs.append("ОШИБКА API: ИИ вернул невалидный JSON.")
    except Exception as e:
        logs.append(f"ОШИБКА СЕТИ: {str(e)}")
        
    finally:
        api_loading = False

# ================= ГРАФИКА И UI =================
def draw_board(surface):
    for row in range(8):
        for col in range(8):
            color = COLOR_LIGHT if (row + col) % 2 == 0 else COLOR_DARK
            pygame.draw.rect(surface, color, (col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))

def draw_pieces(surface, font):
    for row in range(8):
        for col in range(8):
            piece = board_matrix[row][col]
            if piece:
                # Если в будущем будут png, здесь можно заменить render на blit
                text = font.render(piece, True, (0, 0, 0) if piece.islower() else (255, 255, 255))
                # Центрируем букву в клетке
                rect = text.get_rect(center=(col * SQUARE_SIZE + SQUARE_SIZE // 2, row * SQUARE_SIZE + SQUARE_SIZE // 2))
                surface.blit(text, rect)

def draw_logs(surface, font):
    surface.fill(COLOR_BG_LOG, (BOARD_SIZE, 0, LOG_WIDTH, HEIGHT))
    pygame.draw.line(surface, COLOR_TEXT, (BOARD_SIZE, 0), (BOARD_SIZE, HEIGHT), 2)
    
    y_offset = 10
    # Выводим последние логи снизу вверх или ограничиваем их количество
    for line in logs[-15:]: 
        # Простой перенос слов (Word Wrap) для логов
        words = line.split(' ')
        current_line = []
        for word in words:
            current_line.append(word)
            test_text = font.render(' '.join(current_line), True, COLOR_TEXT)
            if test_text.get_width() > LOG_WIDTH - 20:
                current_line.pop()
                render_text = font.render(' '.join(current_line), True, COLOR_TEXT if not "ОШИБКА" in line else COLOR_ERROR)
                surface.blit(render_text, (BOARD_SIZE + 10, y_offset))
                y_offset += 25
                current_line = [word]
        
        render_text = font.render(' '.join(current_line), True, COLOR_TEXT if not "ОШИБКА" in line else COLOR_ERROR)
        surface.blit(render_text, (BOARD_SIZE + 10, y_offset))
        y_offset += 30

def lerp(start, end, t):
    return start + (end - start) * t

# ================= ГЛАВНЫЙ ЦИКЛ =================
def main():
    global api_loading, animating, anim_progress, anim_current_pos
    
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("AI Chess Chaos")
    clock = pygame.time.Clock()
    
    font_pieces = pygame.font.SysFont("arial", int(SQUARE_SIZE * 0.6), bold=True)
    font_logs = pygame.font.SysFont("arial", 16)
    
    running = True
    while running:
        # 1. Обработка событий
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and not api_loading and not animating:
                    api_loading = True
                    logs.append("Отправка запроса к ИИ...")
                    # Запускаем API-запрос в фоне
                    threading.Thread(target=api_worker, daemon=True).start()

        # 2. Логика анимации
        if animating:
            anim_progress += anim_speed
            if anim_progress >= 1.0:
                anim_progress = 1.0
                animating = False
                # Фиксируем фигуру на доске
                r, c, p = target_board_update
                board_matrix[r][c] = p # Если там кто-то был, он автоматически "съедается" (перезаписывается)
            else:
                anim_current_pos[0] = lerp(anim_start_pos[0], anim_target_pos[0], anim_progress)
                anim_current_pos[1] = lerp(anim_start_pos[1], anim_target_pos[1], anim_progress)

        # 3. Отрисовка
        screen.fill((0, 0, 0))
        draw_board(screen)
        draw_pieces(screen, font_pieces)
        
        # Отрисовка анимируемой фигуры поверх остальных
        if animating:
            text = font_pieces.render(anim_piece, True, (0, 0, 0) if anim_piece.islower() else (255, 255, 255))
            rect = text.get_rect(center=(anim_current_pos[0] + SQUARE_SIZE // 2, anim_current_pos[1] + SQUARE_SIZE // 2))
            screen.blit(text, rect)
            
        draw_logs(screen, font_logs)
        
        pygame.display.flip()
        clock.tick(FPS)

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()