import pygame
import sys
import random
import math
import os
import json

# --- Конфигурационный Файл ---
CONFIG_FILE = "config.json"

# Дефолтные настройки
DEFAULT_CONFIG = {
    "controls": {
        "UP": "K_UP",
        "DOWN": "K_DOWN",
        "LEFT": "K_LEFT",
        "RIGHT": "K_RIGHT"
    },
    "debug": {
        "movement": True,
        "collision": True,
        "food_spawn": True
    },
    "high_score": 0
}

def load_config():
    script_dir = os.path.dirname(os.path.abspath(__file__))  # Определяем директорию скрипта
    config_path = os.path.join(script_dir, CONFIG_FILE)
    if not os.path.exists(config_path):
        with open(config_path, 'w') as f:
            json.dump(DEFAULT_CONFIG, f, indent=4)
        print("[DEBUG] Создан дефолтный конфигурационный файл.")
        return DEFAULT_CONFIG.copy()
    else:
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
            # Проверка и исправление всех необходимых настроек
            for key in DEFAULT_CONFIG:
                if key not in config:
                    config[key] = DEFAULT_CONFIG[key]
                    print(f"[DEBUG] Ключ '{key}' отсутствовал в конфиге и был добавлен.")
                elif isinstance(DEFAULT_CONFIG[key], dict):
                    if not isinstance(config[key], dict):
                        config[key] = DEFAULT_CONFIG[key]
                        print(f"[DEBUG] Ключ '{key}' имел неправильный тип и был переопределён на дефолтный.")
                        continue
                    for subkey in DEFAULT_CONFIG[key]:
                        if subkey not in config[key]:
                            config[key][subkey] = DEFAULT_CONFIG[key][subkey]
                            print(f"[DEBUG] Ключ '{subkey}' в '{key}' отсутствовал и был добавлен.")
            print("[DEBUG] Конфигурационный файл загружен и проверен.")
            return config
        except (json.JSONDecodeError, IOError) as e:
            print(f"[WARNING] Ошибка загрузки конфигурационного файла: {e}. Используются дефолтные настройки.")
            return DEFAULT_CONFIG.copy()

def save_config(config):
    script_dir = os.path.dirname(os.path.abspath(__file__))  # Определяем директорию скрипта
    config_path = os.path.join(script_dir, CONFIG_FILE)
    try:
        with open(config_path, 'w') as f:
            json.dump(config, f, indent=4)
        print("[DEBUG] Конфигурационный файл сохранён.")
    except IOError as e:
        print(f"[WARNING] Ошибка сохранения конфигурационного файла: {e}.")

# --- Функция для условного вывода дебаг-сообщений ---
def debug_print(message, debug_enabled):
    if debug_enabled:
        print(f"[DEBUG] {message}")

# --- Инициализация Pygame ---
pygame.init()
pygame.mixer.init()

# Размеры экрана
WIDTH, HEIGHT = 800, 600
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Игра Змейка")

# Цвета
BACKGROUND_COLOR = (20, 21, 31)
SNAKE_COLOR = (50, 205, 50)
SNAKE_OUTLINE_COLOR = (64, 255, 64)
FOOD_COLOR = (255, 69, 0)
FOOD_OUTLINE_COLOR = (255, 99, 71)
PARTICLE_COLOR = (255, 69, 0)
CHECKBOX_COLOR = (255, 255, 255)
CHECKBOX_BORDER = (200, 200, 200)
BUTTON_COLOR = (100, 100, 255)
BUTTON_HOVER_COLOR = (150, 150, 255)
TEXT_COLOR = (255, 255, 255)

# Цвета для свечения
FOOD_GLOW_COLOR = (255, 100, 50, 76)

# Шрифты
FONT = pygame.font.SysFont('Arial', 20)
BIG_FONT = pygame.font.SysFont('Arial', 50)

# FPS
FPS = 60
CLOCK = pygame.time.Clock()

# Размер клетки (grid size)
GRID_SIZE = 20

# Определение событий для движения змейки
MOVE_SNAKE = pygame.USEREVENT + 1
pygame.time.set_timer(MOVE_SNAKE, 150)  # Змейка двигается каждые 150 мс

# --- Функция для загрузки звуков с обработкой ошибок ---
def load_sound(file_path):
    try:
        sound = pygame.mixer.Sound(file_path)
        return sound
    except FileNotFoundError:
        print(f"[WARNING] Звуковой файл '{file_path}' не найден. Звуки будут отключены.")
    except pygame.error as e:
        print(f"[WARNING] Ошибка загрузки звукового файла '{file_path}': {e}. Звуки будут отключены.")
    return None

# Загрузка звуков (опционально)
EAT_SOUND = load_sound('eat.wav')
COLLISION_SOUND = load_sound('collision.wav')

# --- Определение состояний игры ---
class GameState:
    MAIN_MENU = 'главное_меню'
    PLAYING = 'игра'
    GAME_OVER = 'конец_игры'
    SETTINGS = 'настройки'
    PAUSED = 'пауза'

# --- Класс Змейки ---
class Snake:
    DIRECTIONS = ['UP', 'DOWN', 'LEFT', 'RIGHT']

    def __init__(self, x, y, config):
        self.direction = 'UP'  # Начальное направление
        self.next_direction = 'UP'  # Следующее направление
        self.speed = GRID_SIZE  # Скорость змейки равна размеру клетки
        self.radius = GRID_SIZE // 2
        self.length = 7  # Начальная длина змейки
        self.config = config
        # Инициализация сегментов змейки
        self.segments = []
        for i in range(self.length):
            # Располагаем сегменты вниз от головы
            self.segments.append((x, y + i * self.speed))
        debug_print(f"Змейка инициализирована с сегментами: {self.segments}", config["debug"])

    def turn(self, direction):
        # Предотвращаем обратный поворот
        opposite_directions = {'UP':'DOWN', 'DOWN':'UP', 'LEFT':'RIGHT', 'RIGHT':'LEFT'}
        if direction == opposite_directions.get(self.direction):
            debug_print(f"Попытка обратного поворота игнорируется: {direction}", self.config["debug"])
            return
        if direction in self.DIRECTIONS:
            self.next_direction = direction
            debug_print(f"Внутреннее направление змейки установлено на {self.next_direction}", self.config["debug"])

    def grow(self):
        self.length += 1
        debug_print(f"Змейка выросла до длины {self.length}", self.config["debug"])
        if EAT_SOUND:
            EAT_SOUND.play()

    def update(self):
        # Обновляем направление
        self.direction = self.next_direction
        head_x, head_y = self.segments[0]

        if self.direction == 'UP':
            new_head = (head_x, head_y - self.speed)
        elif self.direction == 'DOWN':
            new_head = (head_x, head_y + self.speed)
        elif self.direction == 'LEFT':
            new_head = (head_x - self.speed, head_y)
        elif self.direction == 'RIGHT':
            new_head = (head_x + self.speed, head_y)

        # Вставляем новый сегмент головы
        self.segments.insert(0, new_head)
        debug_print(f"Голова змейки переместилась в {new_head}", self.config["debug"])

        # Удаляем последний сегмент, если не выросли
        if len(self.segments) > self.length:
            removed_segment = self.segments.pop()
            debug_print(f"Удалён сегмент из хвоста: {removed_segment}", self.config["debug"])

        return new_head  # Возвращаем новую позицию головы для проверки столкновений

    def draw(self, surface):
        for idx, (x, y) in enumerate(self.segments):
            if idx == 0:
                # Головка змейки
                pygame.draw.circle(surface, SNAKE_COLOR, (x, y), self.radius)
                pygame.draw.circle(surface, SNAKE_OUTLINE_COLOR, (x, y), self.radius, 2)
            else:
                # Тело змейки
                pygame.draw.circle(surface, SNAKE_COLOR, (x, y), self.radius)
                pygame.draw.circle(surface, SNAKE_OUTLINE_COLOR, (x, y), self.radius, 2)

    def check_self_collision(self, new_head):
        # Проверяем столкновение головы с телом, начиная с 5-го сегмента
        for idx, segment in enumerate(self.segments[4:], start=4):
            distance = math.hypot(new_head[0] - segment[0], new_head[1] - segment[1])
            debug_print(f"Проверка коллизии с сегментом {idx}: {segment} | Расстояние: {distance:.2f}", self.config["debug"])
            if distance < self.radius * 2:
                debug_print("Змейка столкнулась сама с собой", self.config["debug"])
                if COLLISION_SOUND:
                    COLLISION_SOUND.play()
                return True
        return False

# --- Класс Еды ---
class Food:
    def __init__(self):
        self.radius = 6
        # Предварительное создание поверхности свечения
        self.glow_surface = pygame.Surface((self.radius * 2 + 8, self.radius * 2 + 8), pygame.SRCALPHA)
        pygame.draw.circle(self.glow_surface, FOOD_GLOW_COLOR, (self.radius + 4, self.radius + 4), self.radius + 4)
        self.x = None
        self.y = None
        debug_print(f"Еда инициализирована в ({self.x}, {self.y})", False)

    def respawn(self, snake_segments):
        while True:
            self.x = random.randint(1, (WIDTH // GRID_SIZE) - 2) * GRID_SIZE + GRID_SIZE // 2
            self.y = random.randint(1, (HEIGHT // GRID_SIZE) - 2) * GRID_SIZE + GRID_SIZE // 2
            # Убедимся, что еда не появляется на теле змейки
            if (self.x, self.y) not in snake_segments:
                break
        debug_print(f"Еда появилась в ({self.x}, {self.y})", False)

    def draw(self, surface):
        if self.x is not None and self.y is not None:
            # Эффект свечения
            surface.blit(self.glow_surface, (self.x - self.radius - 4, self.y - self.radius - 4))
            # Рисуем еду
            pygame.draw.circle(surface, FOOD_COLOR, (self.x, self.y), self.radius)
            pygame.draw.circle(surface, FOOD_OUTLINE_COLOR, (self.x, self.y), self.radius, 2)

# --- Класс Частиц ---
class ParticleSystem:
    MAX_PARTICLES = 500  # Максимальное количество частиц

    def __init__(self):
        self.particles = []
        debug_print("Система частиц инициализирована", False)

    def emit(self, x, y):
        # Ограничение общего количества частиц
        if len(self.particles) + 20 > self.MAX_PARTICLES:
            self.particles = self.particles[-self.MAX_PARTICLES:]
        for _ in range(20):
            angle = random.uniform(0, 2 * math.pi)
            speed = random.uniform(1, 4)
            self.particles.append({
                'x': x,
                'y': y,
                'dx': math.cos(angle) * speed,
                'dy': math.sin(angle) * speed,
                'life': 1.0,
                'color': PARTICLE_COLOR
            })
        debug_print(f"Выпущено 20 частиц в ({x}, {y})", False)

    def update(self):
        original_count = len(self.particles)
        self.particles = [p for p in self.particles if p['life'] > 0]
        removed_particles = original_count - len(self.particles)
        if removed_particles > 0:
            debug_print(f"Удалено {removed_particles} частиц", False)
        for p in self.particles:
            p['x'] += p['dx']
            p['y'] += p['dy']
            p['life'] -= 0.02

    def draw(self, surface):
        for p in self.particles:
            if p['life'] <= 0:
                continue
            alpha = max(int(255 * p['life']), 0)
            particle_surface = pygame.Surface((4, 4), pygame.SRCALPHA)
            particle_surface.fill((*p['color'], alpha))
            surface.blit(particle_surface, (int(p['x']), int(p['y'])))

# --- Функция для получения Pygame ключа из строки ---
def get_pygame_key(key_str):
    try:
        return getattr(pygame, key_str)
    except AttributeError:
        debug_print(f"Ключ '{key_str}' не найден в Pygame. Используется K_UNKNOWN.", False)
        return pygame.K_UNKNOWN

# --- Класс Меню ---
class Menu:
    def __init__(self, title, options, on_select, config=None):
        self.title = title
        self.options = options
        self.on_select = on_select
        self.selected_index = 0
        self.alpha = 0
        self.show_menu_surface = True
        self.fade_in = True
        self.fade_out = False
        self.menu_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        self.config = config  # Для получения рекорда в главном меню
        self.setup_buttons()
        debug_print(f"Меню '{self.title}' инициализировано", config["debug"] if config else False)

    def setup_buttons(self):
        # Расположение кнопок по вертикали
        self.buttons = []
        button_width = 200
        button_height = 50
        spacing = 20
        total_height = len(self.options) * (button_height + spacing)
        start_y = 250  # Установим фиксированную высоту от верхнего края
        for idx, option in enumerate(self.options):
            rect = pygame.Rect(WIDTH//2 - button_width//2, start_y + idx * (button_height + spacing), button_width, button_height)
            self.buttons.append(rect)
        debug_print(f"Кнопки для меню '{self.title}' настроены", self.config["debug"] if self.config else False)

    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            for idx, button in enumerate(self.buttons):
                if button.collidepoint(event.pos):
                    self.selected_index = idx  # Обновляем выбранный индекс на основе клика
                    debug_print(f"В меню '{self.title}' выбрана опция '{self.options[idx]}'", self.config["debug"] if self.config else False)
                    self.fade_out = True
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                self.selected_index = (self.selected_index - 1) % len(self.options)
                debug_print(f"В меню '{self.title}' выбор перемещён на {self.selected_index}", self.config["debug"] if self.config else False)
            elif event.key == pygame.K_DOWN:
                self.selected_index = (self.selected_index + 1) % len(self.options)
                debug_print(f"В меню '{self.title}' выбор перемещён на {self.selected_index}", self.config["debug"] if self.config else False)
            elif event.key in [pygame.K_RETURN, pygame.K_SPACE]:
                debug_print(f"В меню '{self.title}' выбрана опция '{self.options[self.selected_index]}' через клавиатуру", self.config["debug"] if self.config else False)
                self.fade_out = True

    def update(self):
        if self.fade_in and self.alpha < 255:
            self.alpha += 5
            if self.alpha >= 255:
                self.alpha = 255
                self.fade_in = False
                debug_print(f"В меню '{self.title}' затухание завершено", self.config["debug"] if self.config else False)
        elif self.fade_out and self.alpha > 0:
            self.alpha -= 5
            if self.alpha <= 0:
                self.alpha = 0
                self.show_menu_surface = False
                debug_print(f"В меню '{self.title}' затухание завершено", self.config["debug"] if self.config else False)
                # Вызов callback-функции после завершения анимации
                if self.on_select:
                    self.on_select(self.selected_index)
        # Заполнение поверхности меню
        self.menu_surface.fill((0, 0, 0, self.alpha))

    def draw(self, surface):
        if self.show_menu_surface:
            surface.blit(self.menu_surface, (0, 0))
            # Рисуем заголовок
            title_text = BIG_FONT.render(self.title, True, TEXT_COLOR)
            surface.blit(title_text, (WIDTH//2 - title_text.get_width()//2, 100))
            # В главном меню отображаем рекорд
            if self.title == "Главное меню" and self.config:
                high_score = self.config.get("high_score", 0)
                high_score_text = FONT.render(f"Рекорд: {high_score}", True, TEXT_COLOR)
                # Переместим текст рекорда ещё ниже
                surface.blit(high_score_text, (WIDTH//2 - high_score_text.get_width()//2, 160))
            # Рисуем кнопки
            for idx, button in enumerate(self.buttons):
                # Изменение цвета при наведении мышью
                mouse_pos = pygame.mouse.get_pos()
                if button.collidepoint(mouse_pos):
                    color = BUTTON_HOVER_COLOR
                else:
                    color = BUTTON_COLOR
                # Активная кнопка
                if idx == self.selected_index:
                    pygame.draw.rect(surface, BUTTON_HOVER_COLOR, button)
                else:
                    pygame.draw.rect(surface, color, button)
                # Рисуем текст кнопки
                option_text = FONT.render(self.options[idx], True, TEXT_COLOR)
                surface.blit(option_text, (button.x + button.width//2 - option_text.get_width()//2,
                                           button.y + button.height//2 - option_text.get_height()//2))

# --- Класс Меню Настроек ---
class SettingsMenu:
    def __init__(self, config, on_save, on_back):
        self.title = "Настройки"
        self.options = ["UP", "DOWN", "LEFT", "RIGHT"]  # Только управление
        self.on_save = on_save
        self.on_back = on_back
        self.config = config
        self.selected_index = 0
        self.alpha = 0
        self.show_menu_surface = True
        self.fade_in = True
        self.fade_out = False
        self.menu_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        self.setup_buttons()
        self.awaiting_key = None  # Для переназначения клавиш
        # Определение позиций чекбоксов для отдельных типов дебаг-сообщений
        self.checkbox_rects = {
            "movement": pygame.Rect(WIDTH//2 + 150, 300, 20, 20),
            "collision": pygame.Rect(WIDTH//2 + 150, 350, 20, 20),
            "food_spawn": pygame.Rect(WIDTH//2 + 150, 400, 20, 20)
        }
        debug_print("Меню настроек инициализировано", self.config["debug"]["movement"])
        self.debug_options = ["movement", "collision", "food_spawn"]

    def setup_buttons(self):
        # Расположение кнопок по вертикали (первая часть: управление)
        self.buttons = []
        button_width = 300
        button_height = 40
        spacing = 20
        total_height = len(self.options) * (button_height + spacing)
        start_y = 150  # Фиксированная позиция ниже заголовка
        for idx, option in enumerate(self.options):
            rect = pygame.Rect(WIDTH//2 - button_width//2, start_y + idx * (button_height + spacing), button_width, button_height)
            self.buttons.append(rect)
        # Добавим кнопку "Назад" отдельно
        self.back_button = pygame.Rect(WIDTH//2 - 100, 500, 200, 50)
        debug_print(f"Кнопки для меню '{self.title}' настроены", self.config["debug"]["movement"])

    def handle_event(self, event):
        if self.awaiting_key:
            if event.type == pygame.KEYDOWN:
                key_name = pygame.key.name(event.key).upper()
                # Проверка на уникальность
                if key_name in [v.replace("K_", "") for k, v in self.config["controls"].items()]:
                    debug_print(f"Клавиша '{key_name}' уже назначена. Выберите другую клавишу.", self.config["debug"]["movement"])
                else:
                    self.config["controls"][self.awaiting_key] = f"K_{key_name}"
                    debug_print(f"Клавиша для '{self.awaiting_key}' изменена на '{key_name}'", self.config["debug"]["movement"])
                self.awaiting_key = None
        else:
            if event.type == pygame.MOUSEBUTTONDOWN:
                # Проверка нажатия на кнопки переназначения клавиш
                for idx, button in enumerate(self.buttons):
                    if button.collidepoint(event.pos):
                        if self.options[idx] in ["UP", "DOWN", "LEFT", "RIGHT"]:
                            self.awaiting_key = self.options[idx]
                            debug_print(f"Ожидание переназначения клавиши для '{self.awaiting_key}'. Нажмите новую клавишу.", self.config["debug"]["movement"])
                # Проверка нажатия на чекбоксы дебаг-сообщений
                for debug_type, rect in self.checkbox_rects.items():
                    if rect.collidepoint(event.pos):
                        self.config["debug"][debug_type] = not self.config["debug"][debug_type]
                        debug_print(f"Дебаг-сообщения для '{debug_type}' {'включены' if self.config['debug'][debug_type] else 'отключены'}", self.config["debug"]["movement"])
                # Проверка нажатия на кнопку "Назад"
                if self.back_button.collidepoint(event.pos):
                    debug_print("Кнопка 'Назад' нажата в меню настроек", self.config["debug"]["movement"])
                    self.fade_out = True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.fade_out = True
                elif event.key in [pygame.K_RETURN, pygame.K_SPACE]:
                    self.fade_out = True

    def update(self):
        if self.fade_in and self.alpha < 255:
            self.alpha += 5
            if self.alpha >= 255:
                self.alpha = 255
                self.fade_in = False
                debug_print(f"В меню настроек затухание завершено", self.config["debug"]["movement"])
        elif self.fade_out and self.alpha > 0:
            self.alpha -= 5
            if self.alpha <= 0:
                self.alpha = 0
                self.show_menu_surface = False
                debug_print(f"В меню настроек затухание завершено", self.config["debug"]["movement"])
                # Вызов callback-функции после завершения анимации
                if self.awaiting_key is None:  # Убедимся, что не находимся в процессе переназначения клавиши
                    if self.on_save:
                        self.on_save()
        # Заполнение поверхности меню
        self.menu_surface.fill((0, 0, 0, self.alpha))

    def draw(self, surface):
        if self.show_menu_surface:
            surface.blit(self.menu_surface, (0, 0))
            # Рисуем заголовок
            title_text = BIG_FONT.render(self.title, True, TEXT_COLOR)
            surface.blit(title_text, (WIDTH//2 - title_text.get_width()//2, 50))
            # Рисуем кнопки для переназначения клавиш
            for idx, button in enumerate(self.buttons):
                # Изменение цвета при наведении мышью
                mouse_pos = pygame.mouse.get_pos()
                if button.collidepoint(mouse_pos):
                    color = BUTTON_HOVER_COLOR
                else:
                    color = BUTTON_COLOR
                # Активная кнопка (при выборе через клавиатуру не используется здесь, но оставлено на будущее)
                if idx == self.selected_index:
                    pygame.draw.rect(surface, BUTTON_HOVER_COLOR, button)
                else:
                    pygame.draw.rect(surface, color, button)
                # Рисуем текст кнопки
                option_text = FONT.render(self.options[idx], True, TEXT_COLOR)
                surface.blit(option_text, (button.x + 10, button.y + button.height//2 - option_text.get_height()//2))
                # Рисуем текущую привязанную клавишу
                bound_key = self.config["controls"][self.options[idx]]
                bound_key_display = bound_key.replace("K_", "")
                bound_text = FONT.render(f"Текущая клавиша: {bound_key_display}", True, TEXT_COLOR)
                surface.blit(bound_text, (button.x + button.width - bound_text.get_width() - 10, button.y + button.height//2 - bound_text.get_height()//2))
            # Рисуем чекбоксы для дебаг-сообщений
            for debug_type, rect in self.checkbox_rects.items():
                # Рисуем рамку чекбокса
                pygame.draw.rect(surface, CHECKBOX_BORDER, rect, 2)
                # Заполняем галочку, если включено
                if self.config["debug"][debug_type]:
                    pygame.draw.rect(surface, CHECKBOX_COLOR, rect.inflate(-4, -4))
                # Рисуем текст рядом с чекбоксом
                debug_text = FONT.render(f"Дебаг: {debug_type.capitalize()}", True, TEXT_COLOR)
                surface.blit(debug_text, (rect.right + 10, rect.y - 2))
            # Рисуем кнопку "Назад"
            # Изменение цвета при наведении мышью
            if self.back_button.collidepoint(pygame.mouse.get_pos()):
                back_color = BUTTON_HOVER_COLOR
            else:
                back_color = BUTTON_COLOR
            pygame.draw.rect(surface, back_color, self.back_button)
            back_text = FONT.render("Назад", True, TEXT_COLOR)
            surface.blit(back_text, (self.back_button.x + self.back_button.width//2 - back_text.get_width()//2,
                                     self.back_button.y + self.back_button.height//2 - back_text.get_height()//2))
            # Рисуем инструкцию для переназначения
            if self.awaiting_key:
                instruction = FONT.render(f"Нажмите новую клавишу для '{self.awaiting_key}'", True, (255, 0, 0))
                surface.blit(instruction, (WIDTH//2 - instruction.get_width()//2, 470))

# --- Класс Game Over Меню ---
class GameOverMenu(Menu):
    def __init__(self, score, high_score, on_restart, on_main_menu, config):
        self.score = score
        self.high_score = high_score
        self.on_restart = on_restart
        self.on_main_menu = on_main_menu
        self.config = config
        options = ["Перезапустить", "Главное меню"]
        super().__init__("Конец игры", options, self.handle_selection, config)
        debug_print("Меню 'Конец игры' инициализировано", self.config["debug"])

    def handle_selection(self, selected_index):
        if selected_index == 0:
            debug_print("Меню 'Конец игры': выбрано перезапуск", self.config["debug"])
            self.on_restart()
        elif selected_index == 1:
            debug_print("Меню 'Конец игры': выбрано главное меню", self.config["debug"])
            self.on_main_menu()

    def draw(self, surface):
        if self.show_menu_surface:
            # Super draw будет обрабатывать заголовок и кнопки
            super().draw(surface)
            # Рисуем счёт
            score_text = FONT.render(f"Ваш счёт: {self.score}", True, TEXT_COLOR)
            high_score_text = FONT.render(f"Рекорд: {self.high_score}", True, TEXT_COLOR)
            surface.blit(score_text, (WIDTH//2 - score_text.get_width()//2, 200))
            surface.blit(high_score_text, (WIDTH//2 - high_score_text.get_width()//2, 240))

# --- Класс Паузы ---
class PauseMenu:
    def __init__(self, on_resume, on_main_menu):
        self.on_resume = on_resume
        self.on_main_menu = on_main_menu
        self.show_menu = False
        self.menu_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        self.resume_button = pygame.Rect(WIDTH//2 - 100, 250, 200, 50)
        self.main_menu_button = pygame.Rect(WIDTH//2 - 100, 320, 200, 50)
        debug_print("Меню паузы инициализировано", False)

    def handle_event(self, event):
        if not self.show_menu:
            return
        if event.type == pygame.MOUSEBUTTONDOWN:
            if self.resume_button.collidepoint(event.pos):
                debug_print("Кнопка 'Продолжить' нажата в меню паузы", False)
                self.on_resume()
            elif self.main_menu_button.collidepoint(event.pos):
                debug_print("Кнопка 'Главное меню' нажата в меню паузы", False)
                self.on_main_menu()
        elif event.type == pygame.KEYDOWN:
            if event.key in [pygame.K_RETURN, pygame.K_SPACE]:
                debug_print("Кнопка 'Продолжить' выбрана через клавиатуру в меню паузы", False)
                self.on_resume()
            elif event.key == pygame.K_ESCAPE:
                self.on_resume()

    def show_menu_func(self, menu=True):
        self.show_menu = menu
        debug_print(f"Меню паузы show_menu установлено на {self.show_menu}", False)

    def hide(self):
        self.show_menu = False
        debug_print("Меню паузы скрыто", False)

    def update(self):
        pass  # Место для анимации или других обновлений паузы

    def draw(self, surface):
        if self.show_menu:
            # Полупрозрачный фон
            self.menu_surface.fill((0, 0, 0, 150))
            surface.blit(self.menu_surface, (0, 0))
            # Текст "Пауза"
            paused_text = BIG_FONT.render("Пауза", True, TEXT_COLOR)
            surface.blit(paused_text, (WIDTH//2 - paused_text.get_width()//2, 100))
            # Кнопки
            pygame.draw.rect(surface, BUTTON_COLOR, self.resume_button)
            pygame.draw.rect(surface, BUTTON_COLOR, self.main_menu_button)
            # Рисуем текст кнопок
            resume_text = FONT.render("Продолжить", True, TEXT_COLOR)
            main_menu_text = FONT.render("Главное меню", True, TEXT_COLOR)
            surface.blit(resume_text, (self.resume_button.x + self.resume_button.width//2 - resume_text.get_width()//2,
                                       self.resume_button.y + self.resume_button.height//2 - resume_text.get_height()//2))
            surface.blit(main_menu_text, (self.main_menu_button.x + self.main_menu_button.width//2 - main_menu_text.get_width()//2,
                                          self.main_menu_button.y + self.main_menu_button.height//2 - main_menu_text.get_height()//2))

# --- Класс Игры ---
class Game:
    def __init__(self, on_game_over, on_exit, config):
        self.config = config
        self.snake = Snake(WIDTH // 2, HEIGHT // 2, config)
        self.food = Food()
        self.food.respawn(self.snake.segments)
        self.particles = ParticleSystem()
        self.score = 0
        self.high_score = self.config.get("high_score", 0)
        self.on_game_over = on_game_over
        self.on_exit = on_exit
        self.is_paused = False
        self.game_over = False
        self.pause_menu = PauseMenu(self.resume, self.return_to_main_menu)
        debug_print("Игра инициализирована", self.config["debug"]["movement"])

    def save_high_score(self):
        if self.score > self.high_score:
            self.high_score = self.score
            self.config["high_score"] = self.high_score
            try:
                save_config(self.config)
                debug_print(f"Новый рекорд сохранен: {self.high_score}", self.config["debug"]["movement"])
            except IOError as e:
                debug_print(f"Ошибка при сохранении рекорда: {e}", self.config["debug"]["movement"])

    def move_snake(self):
        new_head = self.snake.update()
        # Проверка столкновения с границами экрана
        head_x, head_y = new_head
        if not (GRID_SIZE <= head_x <= WIDTH - GRID_SIZE and GRID_SIZE <= head_y <= HEIGHT - GRID_SIZE):
            debug_print("Змейка вышла за границы экрана", self.config["debug"]["collision"])
            if COLLISION_SOUND:
                COLLISION_SOUND.play()
            self.handle_game_over()
            return

        # Проверка столкновения с самим собой
        if self.snake.check_self_collision(new_head):
            self.handle_game_over()
            return

        # Проверка столкновения змейки с едой
        if math.hypot(new_head[0] - self.food.x, new_head[1] - self.food.y) < self.snake.radius + self.food.radius:
            debug_print("Змейка съела еду", self.config["debug"]["food_spawn"])
            self.snake.grow()
            self.food.respawn(self.snake.segments)
            self.particles.emit(new_head[0], new_head[1])
            self.score += 1
            debug_print(f"Счёт: {self.score}", self.config["debug"]["movement"])
            if self.score > self.high_score:
                self.save_high_score()

    def update(self):
        if not self.is_paused and not self.game_over:
            pass  # Все обновления происходят через событие MOVE_SNAKE
        # Обновляем систему частиц независимо от состояния паузы
        self.particles.update()

    def handle_game_over(self):
        self.game_over = True
        self.save_high_score()
        self.on_game_over(self.score, self.high_score, self.config)

    def handle_event(self, event):
        if self.is_paused:
            self.pause_menu.handle_event(event)
            return
        if event.type == pygame.KEYDOWN:
            up_key = get_pygame_key(self.config["controls"]["UP"])
            down_key = get_pygame_key(self.config["controls"]["DOWN"])
            left_key = get_pygame_key(self.config["controls"]["LEFT"])
            right_key = get_pygame_key(self.config["controls"]["RIGHT"])
            if event.key == up_key:
                self.snake.turn('UP')
                debug_print("Нажата клавиша 'Вверх'", self.config["debug"]["movement"])
            elif event.key == down_key:
                self.snake.turn('DOWN')
                debug_print("Нажата клавиша 'Вниз'", self.config["debug"]["movement"])
            elif event.key == left_key:
                self.snake.turn('LEFT')
                debug_print("Нажата клавиша 'Влево'", self.config["debug"]["movement"])
            elif event.key == right_key:
                self.snake.turn('RIGHT')
                debug_print("Нажата клавиша 'Вправо'", self.config["debug"]["movement"])
            elif event.key == pygame.K_ESCAPE:
                self.toggle_pause()

    def toggle_pause(self):
        self.is_paused = not self.is_paused
        self.pause_menu.show_menu_func(self.is_paused)
        debug_print(f"Пауза {'включена' if self.is_paused else 'выключена'}", self.config["debug"]["movement"])

    def resume(self):
        self.is_paused = False
        self.pause_menu.hide()
        debug_print("Игра возобновлена", self.config["debug"]["movement"])

    def return_to_main_menu(self):
        self.is_paused = False
        self.on_exit()
        debug_print("Возврат в главное меню из паузы", self.config["debug"]["movement"])

    def draw(self, surface):
        self.snake.draw(surface)
        self.food.draw(surface)
        self.particles.draw(surface)
        # Рисуем счёт
        score_text = FONT.render(f"Счёт: {self.score}", True, TEXT_COLOR)
        high_score_text = FONT.render(f"Рекорд: {self.high_score}", True, TEXT_COLOR)
        surface.blit(score_text, (10, 10))
        surface.blit(high_score_text, (10, 30))
        if self.is_paused:
            self.pause_menu.draw(surface)

# --- Класс Менеджера Игр ---
class GameManager:
    def __init__(self):
        self.config = load_config()
        self.high_score = self.config.get("high_score", 0)
        self.state = GameState.MAIN_MENU
        self.main_menu = Menu("Главное меню", ["Начать игру", "Настройки", "Выйти"], self.handle_main_menu_selection, self.config)
        self.game = None
        self.game_over_menu = None
        self.settings_menu = None
        print("[DEBUG] Менеджер игр инициализирован")

    def handle_main_menu_selection(self, selected_index):
        if selected_index == 0:
            self.start_game()
        elif selected_index == 1:
            self.open_settings()
        elif selected_index == 2:
            self.exit_game()

    def start_game(self):
        self.game = Game(self.game_over, self.show_main_menu, self.config)
        self.state = GameState.PLAYING
        print("[DEBUG] Игра начата")

    def open_settings(self):
        self.state = GameState.SETTINGS
        self.settings_menu = SettingsMenu(self.config, self.save_settings, self.show_main_menu)
        print("[DEBUG] Открыто меню настроек")

    def save_settings(self):
        # Обновление конфигурационного файла
        new_high_score = self.game.high_score if self.game else self.config.get("high_score", 0)
        self.config["high_score"] = max(self.config.get("high_score", 0), new_high_score)
        save_config(self.config)
        print("[DEBUG] Настройки сохранены")
        self.show_main_menu()

    def exit_game(self):
        print("[DEBUG] Выход из игры")
        if self.game:
            self.game.save_high_score()
        save_config(self.config)
        pygame.quit()
        sys.exit()

    def game_over(self, score, high_score, config):
        self.state = GameState.GAME_OVER
        # Обновление конфигурационного файла, если есть новый рекорд
        if high_score > config.get("high_score", 0):
            config["high_score"] = high_score
            save_config(config)
        self.game_over_menu = GameOverMenu(score, high_score, self.start_game, self.show_main_menu, config)
        print("[DEBUG] Переход в меню 'Конец игры'")

    def show_main_menu(self):
        self.state = GameState.MAIN_MENU
        self.main_menu = Menu("Главное меню", ["Начать игру", "Настройки", "Выйти"], self.handle_main_menu_selection, self.config)
        print("[DEBUG] Возврат в главное меню")

    def run(self):
        while True:
            self.update()
            self.handle_events()
            self.draw()
            CLOCK.tick(FPS)

    def update(self):
        if self.state == GameState.MAIN_MENU:
            self.main_menu.update()
        elif self.state == GameState.PLAYING:
            self.game.update()
        elif self.state == GameState.GAME_OVER:
            self.game_over_menu.update()
        elif self.state == GameState.SETTINGS:
            self.settings_menu.update()

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.exit_game()
            elif self.state == GameState.MAIN_MENU:
                self.main_menu.handle_event(event)
            elif self.state == GameState.PLAYING:
                if event.type == MOVE_SNAKE:
                    if not self.game.is_paused:
                        self.game.move_snake()
                else:
                    self.game.handle_event(event)
            elif self.state == GameState.GAME_OVER:
                self.game_over_menu.handle_event(event)
            elif self.state == GameState.SETTINGS:
                self.settings_menu.handle_event(event)

    def draw(self):
        SCREEN.fill(BACKGROUND_COLOR)
        if self.state == GameState.MAIN_MENU:
            self.main_menu.draw(SCREEN)
        elif self.state == GameState.PLAYING:
            self.game.draw(SCREEN)
        elif self.state == GameState.GAME_OVER:
            self.game_over_menu.draw(SCREEN)
        elif self.state == GameState.SETTINGS:
            self.settings_menu.draw(SCREEN)
        pygame.display.flip()

# --- Главный цикл ---
if __name__ == "__main__":
    manager = GameManager()
    manager.run()

1. В меню "конец игры" сдвинь кнопки чуть ниже чтобы текст на них не залезал
2. После изменения настроек управления в самой игре змейка не реагирует на нажатия заданных клавиш.