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


"""
Snake — классическая Змейка на Pygame
Группа 3: Калашников, Венчинский, Мисюра, Мартюгов

Управление:
    стрелки/WASD  — поворот
    R             — рестарт после Game Over
    ESC           — выход из игры (везде)
    Q             — отмена в меню сложности
    C             — открыть меню сложности / подтвердить выбор
"""

import pygame
import random
import sys


# ==========================================================
# НАСТРОЙКИ
# ==========================================================
WIDTH = 600
HEIGHT = 600
CELL = 20
COLS = WIDTH // CELL
ROWS = HEIGHT // CELL

BG_COLOR = (20, 20, 30)
GRID_COLOR = (30, 30, 45)
SNAKE_COLOR = (80, 200, 120)
SNAKE_HEAD_COLOR = (150, 255, 170)
FOOD_COLOR = (240, 80, 80)
TEXT_COLOR = (230, 230, 230)


pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Змейка")
clock = pygame.time.Clock()
font = pygame.font.SysFont("Comic Sans MS", 22, bold=True)
big_font = pygame.font.SysFont("Comic Sans MS", 50, bold=True)
small_font = pygame.font.SysFont("Comic Sans MS", 18, bold=True)


def difficulty_screen():
    screen.fill(BG_COLOR)
    
    title = big_font.render("ВЫБЕРИТЕ СЛОЖНОСТЬ", True, TEXT_COLOR)
    screen.blit(title, title.get_rect(center=(WIDTH // 2, 120)))
    
    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
    hint = small_font.render("C - подтвердить выбор", True, TEXT_COLOR)
    exit_text = small_font.render("ESC - ВЫХОД ИЗ ИГРЫ", True, (200, 80, 80))
    
    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 250)))
    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 310)))
    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 370)))
    screen.blit(hint, hint.get_rect(center=(WIDTH // 2, 450)))
    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 520)))
    
    pygame.display.flip()
    
    selected = 10
    confirmed = False
    
    while not confirmed:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_1:
                    selected = 8
                    easy = font.render("1 - ЛЕГКО (8 FPS) ✓", True, (80, 200, 80))
                    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
                    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
                    screen.fill(BG_COLOR)
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 120)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 250)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 310)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 370)))
                    screen.blit(hint, hint.get_rect(center=(WIDTH // 2, 450)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 520)))
                    pygame.display.flip()
                elif event.key == pygame.K_2:
                    selected = 10
                    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
                    medium = font.render("2 - СРЕДНЕ (10 FPS) ✓", True, (80, 200, 80))
                    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
                    screen.fill(BG_COLOR)
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 120)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 250)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 310)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 370)))
                    screen.blit(hint, hint.get_rect(center=(WIDTH // 2, 450)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 520)))
                    pygame.display.flip()
                elif event.key == pygame.K_3:
                    selected = 15
                    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
                    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
                    hard = font.render("3 - СЛОЖНО (15 FPS) ✓", True, (80, 200, 80))
                    screen.fill(BG_COLOR)
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 120)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 250)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 310)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 370)))
                    screen.blit(hint, hint.get_rect(center=(WIDTH // 2, 450)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 520)))
                    pygame.display.flip()
                elif event.key == pygame.K_c:
                    confirmed = True
                elif event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
    
    return selected


def show_difficulty_menu():
    screen.fill(BG_COLOR)
    
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill((0, 0, 0, 200))
    screen.blit(overlay, (0, 0))
    
    title = big_font.render("СМЕНА СЛОЖНОСТИ", True, TEXT_COLOR)
    screen.blit(title, title.get_rect(center=(WIDTH // 2, 100)))
    
    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
    confirm = font.render("C - ПОДТВЕРДИТЬ", True, (80, 200, 80))
    cancel = font.render("Q - ОТМЕНА", True, (200, 80, 80))
    exit_text = font.render("ESC - ВЫХОД ИЗ ИГРЫ", True, (200, 80, 80))
    pause = small_font.render("ИГРА НА ПАУЗЕ", True, TEXT_COLOR)
    
    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 220)))
    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 280)))
    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 340)))
    screen.blit(confirm, confirm.get_rect(center=(WIDTH // 2, 410)))
    screen.blit(cancel, cancel.get_rect(center=(WIDTH // 2, 460)))
    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 510)))
    screen.blit(pause, pause.get_rect(center=(WIDTH // 2, 560)))
    
    pygame.display.flip()
    
    selected = 10
    confirmed = False
    
    while not confirmed:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_1:
                    selected = 8
                    easy = font.render("1 - ЛЕГКО (8 FPS) ✓", True, (80, 200, 80))
                    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
                    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
                    screen.fill(BG_COLOR)
                    screen.blit(overlay, (0, 0))
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 100)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 220)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 280)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 340)))
                    screen.blit(confirm, confirm.get_rect(center=(WIDTH // 2, 410)))
                    screen.blit(cancel, cancel.get_rect(center=(WIDTH // 2, 460)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 510)))
                    screen.blit(pause, pause.get_rect(center=(WIDTH // 2, 560)))
                    pygame.display.flip()
                elif event.key == pygame.K_2:
                    selected = 10
                    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
                    medium = font.render("2 - СРЕДНЕ (10 FPS) ✓", True, (80, 200, 80))
                    hard = font.render("3 - СЛОЖНО (15 FPS)", True, TEXT_COLOR)
                    screen.fill(BG_COLOR)
                    screen.blit(overlay, (0, 0))
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 100)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 220)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 280)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 340)))
                    screen.blit(confirm, confirm.get_rect(center=(WIDTH // 2, 410)))
                    screen.blit(cancel, cancel.get_rect(center=(WIDTH // 2, 460)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 510)))
                    screen.blit(pause, pause.get_rect(center=(WIDTH // 2, 560)))
                    pygame.display.flip()
                elif event.key == pygame.K_3:
                    selected = 15
                    easy = font.render("1 - ЛЕГКО (8 FPS)", True, TEXT_COLOR)
                    medium = font.render("2 - СРЕДНЕ (10 FPS)", True, TEXT_COLOR)
                    hard = font.render("3 - СЛОЖНО (15 FPS) ✓", True, (80, 200, 80))
                    screen.fill(BG_COLOR)
                    screen.blit(overlay, (0, 0))
                    screen.blit(title, title.get_rect(center=(WIDTH // 2, 100)))
                    screen.blit(easy, easy.get_rect(center=(WIDTH // 2, 220)))
                    screen.blit(medium, medium.get_rect(center=(WIDTH // 2, 280)))
                    screen.blit(hard, hard.get_rect(center=(WIDTH // 2, 340)))
                    screen.blit(confirm, confirm.get_rect(center=(WIDTH // 2, 410)))
                    screen.blit(cancel, cancel.get_rect(center=(WIDTH // 2, 460)))
                    screen.blit(exit_text, exit_text.get_rect(center=(WIDTH // 2, 510)))
                    screen.blit(pause, pause.get_rect(center=(WIDTH // 2, 560)))
                    pygame.display.flip()
                elif event.key == pygame.K_c:
                    confirmed = True
                elif event.key == pygame.K_q:
                    return None
                elif event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
    
    return selected


# ==========================================================
# РОЛЬ 1 — Калашников: Food
# ==========================================================
class Food:
    def __init__(self):
        self.position = (5, 5)

    def spawn(self, snake_body):
        while True:
            col = random.randint(0, COLS - 1)
            row = random.randint(0, ROWS - 1)
            
            if (col, row) not in snake_body:
                self.position = (col, row)
                break
        
        while True:
            col = random.randint(0, COLS - 1)
            row = random.randint(0, ROWS - 1)
            
            if (col, row) not in snake_body:
                self.position = (col, row)
                return

    def draw(self, surface):
        x = self.position[0] * CELL
        y = self.position[1] * CELL
        pygame.draw.rect(surface, FOOD_COLOR, (x + 2, y + 2, CELL - 4, CELL - 4), border_radius=6)
        pygame.draw.circle(surface, (255, 200, 200), (x + CELL // 3, y + CELL // 3), 2)


# ==========================================================
# РОЛЬ 2 — Венчинский: Renderer
# ==========================================================
class Renderer:
    def draw_grid(self, surface):
        surface.fill(BG_COLOR)
        for x in range(0, WIDTH, CELL):
            pygame.draw.line(surface, GRID_COLOR, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, CELL):
            pygame.draw.line(surface, GRID_COLOR, (0, y), (WIDTH, y))

    def draw_snake(self, surface, body):
        for i, (col, row) in enumerate(body):
            x = col * CELL
            y = row * CELL
            
            color = SNAKE_HEAD_COLOR if i == 0 else SNAKE_COLOR
            
            pygame.draw.rect(surface, color, (x + 1, y + 1, CELL - 2, CELL - 2), border_radius=5)
            
            if i == 0:
                eye_x1 = x + CELL // 4
                eye_y = y + CELL // 4
                pygame.draw.circle(surface, (0, 0, 0), (eye_x1, eye_y), 3)
                
                eye_x2 = x + 3 * CELL // 4
                pygame.draw.circle(surface, (0, 0, 0), (eye_x2, eye_y), 3)
                
                pygame.draw.circle(surface, (255, 255, 255), (eye_x1 - 1, eye_y - 1), 1)
                pygame.draw.circle(surface, (255, 255, 255), (eye_x2 - 1, eye_y - 1), 1)

    def draw_score(self, surface, score, fps):
        text = font.render(f"Счёт: {score}", True, TEXT_COLOR)
        surface.blit(text, (10, 10))
        
        if fps == 8:
            difficulty_text = small_font.render("Сложность: ЛЕГКО", True, TEXT_COLOR)
        elif fps == 10:
            difficulty_text = small_font.render("Сложность: СРЕДНЕ", True, TEXT_COLOR)
        else:
            difficulty_text = small_font.render("Сложность: СЛОЖНО", True, TEXT_COLOR)
        surface.blit(difficulty_text, (10, 40))
        
        hint_text = small_font.render("C - сменить сложность", True, TEXT_COLOR)
        surface.blit(hint_text, (WIDTH - 180, 10))

    def draw_game_over(self, surface, score):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 180))
        surface.blit(overlay, (0, 0))

        text1 = big_font.render("GAME OVER", True, (220, 80, 80))
        text2 = font.render(f"Твой счёт: {score}", True, TEXT_COLOR)
        text3 = font.render("R — заново   |   ESC — выход   |   C — сложность", True, TEXT_COLOR)
        surface.blit(text1, text1.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 50)))
        surface.blit(text2, text2.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 10)))
        surface.blit(text3, text3.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50)))


# ==========================================================
# РОЛЬ 3 — Мисюра: Snake
# ==========================================================
class Snake:
    def __init__(self):
        mid_c, mid_r = COLS // 2, ROWS // 2
        self.body = [(mid_c, mid_r), (mid_c - 1, mid_r), (mid_c - 2, mid_r)]
        self.direction = (1, 0)
        self.grow_pending = False

    def set_direction(self, new_dir):
        if new_dir[0] == -self.direction[0] and new_dir[1] == -self.direction[1]:
            return
        self.direction = new_dir

    def move(self):
        head = self.body[0]
        new_head = (head[0] + self.direction[0], head[1] + self.direction[1])
        self.body.insert(0, new_head)
        if not self.grow_pending:
            self.body.pop()
        self.grow_pending = False

    def grow(self):
        self.grow_pending = True

    def head(self):
        return self.body[0]

    def check_wall_collision(self):
        c, r = self.head()
        return c < 0 or c >= COLS or r < 0 or r >= ROWS

    def check_self_collision(self):
        return self.head() in self.body[1:]


# ==========================================================
# РОЛЬ 4 — Мартюгов: Game loop
# ==========================================================
def handle_input(event, snake, restart_callback, game_over, change_difficulty_callback):
    if event.type != pygame.KEYDOWN:
        return
    
    if event.key == pygame.K_UP or event.key == pygame.K_w:
        snake.set_direction((0, -1))
    elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
        snake.set_direction((0, 1))
    elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
        snake.set_direction((-1, 0))
    elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
        snake.set_direction((1, 0))
    elif event.key == pygame.K_r and game_over:
        restart_callback()
    elif event.key == pygame.K_c:
        change_difficulty_callback()


def new_game():
    snake = Snake()
    food = Food()
    food.spawn(snake.body)
    return snake, food, 0


def main():
    current_fps = difficulty_screen()
    snake, food, score = new_game()
    renderer = Renderer()
    game_over = False

    def restart():
        nonlocal snake, food, score, game_over
        snake, food, score = new_game()
        game_over = False

    def change_difficulty():
        nonlocal current_fps, snake, food, score, game_over
    
        new_fps = show_difficulty_menu()
        if new_fps is not None and new_fps != current_fps:
            current_fps = new_fps
            snake, food, score = new_game()
            game_over = False

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                pygame.quit()
                sys.exit()
            handle_input(event, snake, restart, game_over, change_difficulty)

        if not game_over:
            snake.move()
            if snake.check_wall_collision() or snake.check_self_collision():
                game_over = True
            elif snake.head() == food.position:
                snake.grow()
                food.spawn(snake.body)
                score += 1

        renderer.draw_grid(screen)
        food.draw(screen)
        renderer.draw_snake(screen, snake.body)
        
        if game_over:
            renderer.draw_game_over(screen, score)
        else:
            renderer.draw_score(screen, score, current_fps)

        pygame.display.flip()
        clock.tick(current_fps)


if __name__ == "__main__":
    main()