Загрузка данных
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()