Проанализируй и оптимизируй этот код:
"
import pygame
import random
import time
import math
import numpy as np

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

# Константы
WIDTH, HEIGHT = 380, 600
FPS = 60

# Цвета
GREEN = (0, 100, 0)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
GRAY = (128, 128, 128)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
ORANGE = (255, 165, 0)
LIGHT_BLUE = (173, 216, 230)  # Светло-голубой
DARK_BLUE = (100, 149, 237)  # Темно-голубой

# Настройки игры
GAME_DURATION = 120
FREEZE_TIME = 4
CELL_SIZE = 40

# Классы для элементов
class FallingObject:
    def __init__(self, shape, color, size, score_value, speed, is_bomb=False):
        self.shape = shape
        self.color = color
        self.size = size
        self.score_value = score_value
        self.is_bomb = is_bomb
        self.x = random.randint(0 + size, WIDTH - size)
        self.y = 0
        self.speed = speed

    def fall(self):
        self.y += self.speed

    def draw(self, surface):
        draw_functions = {
            "snowflake": self.draw_snowflake,
            "crystal": self.draw_crystal,
            "bomb": self.draw_bomb,
        }
        draw_functions[self.shape](surface)

    def draw_snowflake(self, surface):
        center = (self.x, self.y)
        for i in range(6):
            angle = i * (360 / 6)
            x_offset = self.size * 0.5 * math.cos(math.radians(angle))
            y_offset = self.size * 0.5 * math.sin(math.radians(angle))
            pygame.draw.line(surface, WHITE, center, (center[0] + x_offset, center[1] + y_offset), 2)
            pygame.draw.line(surface, WHITE,
                             (center[0] + x_offset, center[1] + y_offset),
                             (center[0] + x_offset * 0.5, center[1] + y_offset * 0.5), 1)

    def draw_crystal(self, surface):
        points = [
            (self.x, self.y),
            (self.x + self.size // 2, self.y - self.size // 1.5),
            (self.x + self.size, self.y),
            (self.x + self.size // 1.5, self.y + self.size // 2),
            (self.x + self.size // 2, self.y + self.size),
            (self.x, self.y)
        ]
        pygame.draw.polygon(surface, self.color, points)
        pygame.draw.polygon(surface, WHITE, points, 2)

    def draw_bomb(self, surface):
        pygame.draw.circle(surface, GRAY, (self.x, self.y), self.size // 3)
        pygame.draw.line(surface, WHITE, (self.x, self.y - self.size // 3), (self.x, self.y - self.size // 3 - 10), 2)
        pygame.draw.circle(surface, YELLOW, (self.x, self.y - self.size // 3 - 10), 5)

class Button:
    def __init__(self, text, x, y, width, height, font_size=30):
        self.rect = pygame.Rect(x, y, width, height)
        self.text = text
        self.font = pygame.font.Font(None, font_size)

    def draw(self, surface):
        pygame.draw.rect(surface, WHITE, self.rect)
        text_surface = self.font.render(self.text, True, BLACK)
        text_rect = text_surface.get_rect(center=self.rect.center)
        surface.blit(text_surface, text_rect)

    def is_hovered(self, pos):
        return self.rect.collidepoint(pos)

def display_message(screen, message, size, color, y_offset=0):
    font = pygame.font.Font(None, size)
    text_surface = font.render(message, True, color)
    text_rect = text_surface.get_rect(center=(WIDTH // 2, HEIGHT // 2 + y_offset))
    screen.blit(text_surface, text_rect)

def draw_grid(screen):
    """Рисует сетку на фоне."""
    for x in range(0, WIDTH, CELL_SIZE):
        pygame.draw.line(screen, GREEN, (x, 0), (x, HEIGHT))
    for y in range(0, HEIGHT, CELL_SIZE):
        pygame.draw.line(screen, GREEN, (0, y), (WIDTH, y))

def game_over_screen(screen, score):
    screen.fill(BLACK)
    display_message(screen, "Game Over!", 48, WHITE, -30)
    display_message(screen, f"Score: {score}", 36, WHITE, 0)
    pygame.display.flip()

def main_menu(screen):
    screen.fill(BLACK)
    display_message(screen, "Drop Game", 36, WHITE, -50)
    start_button = Button("Start Game", 90, HEIGHT - 150, 200, 50)
    start_button.draw(screen)
    pygame.display.flip()

    waiting = True
    while waiting:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if start_button.is_hovered(event.pos):
                    waiting = False

def generate_falling_objects(falling_objects):
    size_snowflakes = random.choice([16, 28])
    size_crystal = random.choice([15, 20])
    size_bomb = random.choice([20, 25])
    
    # Генерируем случайную скорость от 3 до 4.5
    speed = random.uniform(3.0, 4.5)  # Скорость от 3 до 4.5 пикселей за кадр

    # Проверяем, чтобы избежать наложения
    overlapping = True
    while overlapping:
        # Случайный x
        x_position = random.randint(0 + size_snowflakes, WIDTH - size_snowflakes)
        # Проверяем, перекрывает ли новый объект старые
        overlapping = any(obj for obj in falling_objects if obj.x - obj.size < x_position < obj.x + obj.size)

    if random.random() < 0.025:
        falling_objects.append(FallingObject("bomb", GRAY, size_bomb, -100, speed, is_bomb=True))
    elif random.random() < 0.08:
        falling_objects.append(FallingObject("crystal", BLUE, size_crystal, 0, speed))
    else:
        falling_objects.append(FallingObject("snowflake", WHITE, size_snowflakes, 1, speed))

def handle_mouse_click(event, falling_objects):
    mouse_x, mouse_y = event.pos
    for obj in falling_objects:
        if obj.x - obj.size < mouse_x < obj.x + obj.size and obj.y - obj.size < mouse_y < obj.y + obj.size:
            return obj
    return None
    
def main():
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Clicker Game")
    clock = pygame.time.Clock()

    while True:  # Основной цикл игры
        score = 0
        freeze_mode = False
        start_time = time.time()
        falling_objects = []
        explosion_frame = 0  # Номер кадра для эффекта взрыва
        pickup_start_time = None
        pickup_text = ""
        pickup_position = None  # Позиция подбора
        explosion_active = False
        explosion_time = 0  
        shake_duration = 10  # Количество кадров для тряски
        shake_magnitude = 6  # Сила тряски

        while True:  # Основной игровой цикл
            screen.fill(BLACK)
            draw_grid(screen)

            # Инициализация переменной для отслеживания времени заморозки
            freeze_elapsed_time = 0

            # Проверяем, прошло ли время игры
            if not freeze_mode:
                elapsed_time = time.time() - start_time
                if elapsed_time >= GAME_DURATION:
                    break
            else:
                # Если в режиме заморозки, сохраняем текущее время
                freeze_elapsed_time = time.time() - freeze_start_time if 'freeze_start_time' in locals() else 0
                
            # Эффект взрыва
            shake_offset_x = 0
            shake_offset_y = 0
            if explosion_active:
                explosion_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)  # Создаем цвет с альфа-каналом
                explosion_surface.fill((255, 0, 0, 128))  # Полупрозрачный красный цвет
                screen.blit(explosion_surface, (0, 0))  # Накладываем эффект
                
                shake_frame = int((pygame.time.get_ticks() // (1000 // FPS)) % shake_duration)
                shake_offset_x = random.randint(-shake_magnitude, shake_magnitude) if shake_frame < shake_duration else 0
                shake_offset_y = random.randint(-shake_magnitude, shake_magnitude) if shake_frame < shake_duration else 0
                
                if time.time() - explosion_time > 1:  
                    explosion_active = False

            remaining_time = max(0, GAME_DURATION - int(elapsed_time))
            minutes, seconds = divmod(remaining_time, 60)
            time_text = f"{minutes:02}:{seconds:02}"

            font = pygame.font.Font(None, 36)
            frame_rect = pygame.Rect(5, 5, 110, 35)
            
            # Создаем поверхность для рамки с прозрачностью
            timer_border_surface = pygame.Surface((frame_rect.width, frame_rect.height), pygame.SRCALPHA)
            timer_border_surface.fill((0, 0, 0, 150))  # Полупрозрачный черный цвет для обводки
            pygame.draw.rect(timer_border_surface, (0, 0, 0, 0), (0, 0, frame_rect.width, frame_rect.height), border_radius=10)
            pygame.draw.rect(screen, YELLOW, frame_rect, 2, border_radius=10)  # Обводка рамки
            screen.blit(timer_border_surface, frame_rect.topleft)

            # Заполняем рамку таймера во время заморозки
            if freeze_mode:
                pygame.draw.rect(screen, (173, 216, 230, 150), frame_rect)  # Полупрозрачное голубое заполнение

            time_surface = font.render(time_text, True, YELLOW)
            screen.blit(time_surface, time_surface.get_rect(center=frame_rect.center))

            # Отображаем очки в верхнем правом углу
            score_color = RED if explosion_active else WHITE  # Меняем цвет текста на красный при взрыве
            score_surface = font.render(str(score), True, score_color)
            score_rect = score_surface.get_rect(topright=(WIDTH - 5, frame_rect.top + 5))
            screen.blit(score_surface, score_rect)

            # Генерация падающих объектов
            if not freeze_mode and random.random() < 0.06:
                generate_falling_objects(falling_objects)

            # Обработка событий
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    return
                
                if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    clicked_object = handle_mouse_click(event, falling_objects)
                    if clicked_object:
                        if clicked_object.is_bomb:
                            # Обработка взрыва бомбы
                            score = max(0, score - 100)  # Уменьшение на 100
                            pickup_start_time = time.time()
                            pickup_text = "-100"
                            pickup_position = (clicked_object.x, clicked_object.y)
                            explosion_active = True  
                            explosion_time = time.time()
                        elif clicked_object.color == BLUE:
                            freeze_mode = True
                            freeze_start_time = time.time()
                        else:
                            score += clicked_object.score_value
                            pickup_start_time = time.time()
                            pickup_text = "+1"
                            pickup_position = (clicked_object.x, clicked_object.y)

                        # Удаляем объект с экрана
                        falling_objects.remove(clicked_object)

            # Логика заморозки
            if freeze_mode:
                # Сохраняем начальное время заморозки, чтобы не увеличивать счётчик времени
                if 'freeze_start_time' not in locals():
                    freeze_start_time = time.time()
                if freeze_elapsed_time >= FREEZE_TIME:
                    freeze_mode = False
                    # Не обновляем start_time, чтобы время не шло
                    start_time += FREEZE_TIME  # Добавляем время заморозки к стартовому времени
            else:
                # Логика падения объектов
                for obj in falling_objects:
                    obj.fall()  # Используем измененную логику падения
                    if obj.y > HEIGHT + obj.size:
                        falling_objects.remove(obj)

            # Отрисовка объектов
            for obj in falling_objects:
                obj.draw(screen)

            # Эффект подбора снежинок и бомб
            if pickup_start_time and time.time() - pickup_start_time < 0.35:
                pickup_surface = font.render(pickup_text, True, RED if pickup_text == "-100" else ORANGE)
                pickup_rect = pickup_surface.get_rect(center=pickup_position)
                pickup_rect.y -= 15 * (time.time() - pickup_start_time) / 0.35  # Подъем текста
                screen.blit(pickup_surface, pickup_rect)
                
            # Применяем смещение для тряски экрана
            screen_offset = (shake_offset_x, shake_offset_y)
            screen_copy = screen.copy()
            screen.blit(screen_copy, (screen_offset[0], screen_offset[1]))

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

        # Окно окончания игры
        game_over_screen(screen, score)

        restart_button = Button("Restart Game", 90, HEIGHT - 150, 200, 50)
        restart_button.draw(screen)
        pygame.display.flip()

        waiting = True
        while waiting:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    return
                if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    if restart_button.is_hovered(event.pos):
                        waiting = False
                        break

if __name__ == "__main__":
    main_menu(pygame.display.set_mode((WIDTH, HEIGHT)))
    main()
"