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


import time

import pygame as pg
import random

pg.init()
pg.mixer.init()

# константы
# здесь записан порядок нот и их длительность для каждой песни
SIZE = (400, 600)

CHRISTMAS_TREE_NOTES = ["c4", "a4", "a4", "g4", "a4", "f4", "c4", "c4", "c4", "a4", "a4", "a-4", "g4", "c5",
                        "c5", "d4", "d4", "a-4", "a-4", "a4", "g4", "f4", "c4", "a4", "a4", "g4", "a4", "f4"]
CHRISTMAS_TREE_DURATION = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]

BIRCH_NOTES = ["a4", "a4", "a4", "a4", "g4", "f4", "f4", "e4", "d4",
               "a4", "a4", "c5", "a4", "g4", "g4", "f4", "f4", "e4", "d4",
               "e4", "f4", "g4", "f4", "f4", "e4", "d4",
               "e4", "f4", "g4", "f4", "f4", "e4", "d4"]
BIRCH_DURATION = [1, 1, 1, 1, 2, 1, 1, 2, 2,
                  1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
                  2, 1, 2, 1, 1, 2, 2,
                  2, 1, 2, 1, 1, 2, 2]

MORNING_NOTES = ["c5", "a4", "g4", "f4", "g4", "a4", "c5", "a4", "f4", "g4", "a4", "g4", "a4", "c5", "a4",
                 "c5", "d5", "a4", "d5", "c5", "a4", "f4", "c4", "a3", "g3", "f3", ]
MORNING_DURATION = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]


class Tile(pg.sprite.Sprite):
    # загрузка всех картинок для плиток
    print(1)
    short_tile = pg.image.load("short_tile.png")
    short_tile = pg.transform.scale(short_tile, (SIZE[0] // 4, SIZE[1] // 3.5))
    short_tile_pressed = pg.image.load("short_tile_pressed.png")
    short_tile_pressed = pg.transform.scale(short_tile_pressed, (SIZE[0] // 4, SIZE[1] // 3.5))

    long_tile = pg.image.load("long_tile.png")
    long_tile = pg.transform.scale(long_tile, (SIZE[0] // 4, SIZE[1] // 2.2))
    long_tile_pressed = pg.image.load("long_tile_pressed.png")
    long_tile_pressed = pg.transform.scale(long_tile_pressed, (SIZE[0] // 4, SIZE[1] // 2.2))

    def __init__(self, long=False):
        '''
        Класс плитки может создавать их двух разных размеров: короткие и длинные.
        Если нужно создать длинную плитку, параметр long должен иметь значение True. По умолчанию плитки короткие.

        Длинная плитка имеет дополнительную переменную count - это сколько тиков уже прошло до того,
        как она посчитается сыгранной. Пока это время не прошло, длинную плитку нужно зажимать мышкой.
        '''
        super().__init__()

        if long:
            self.long = True
            self.image = Tile.long_tile
            self.count = 0
        else:
            self.long = False
            self.image = Tile.short_tile

        self.rect = self.image.get_rect()
        self.rect.x = random.randint(0, 3) * SIZE[0] // 4  # создаём плитку на случайной дорожке
        self.rect.y = -SIZE[1]

        # если плитка соприкосается с другими плитками, переставить её на свободную дорожку
        while pg.sprite.spritecollide(self, screen_notes, False):
            self.rect.x = random.randint(0, 3) * SIZE[0] // 4

        self.played = False

    def update(self):
        self.rect.y += 1

        # ставим длинные плитки в начальный цвет, чтобы если их перестали нажимать слишком рано,
        # они не застыли в нажатом положении
        if self.long and self.count > 0 and not self.played:
            self.image = Tile.long_tile

        if self.rect.y >= SIZE[1]:  # если плитка за пределами окна, она удаляется
            self.kill()

    def press(self):
        # Нота плитки проигрывается если
        # 1. она не была проиграна раньше
        # 2. это длинная плитка и она ещё не отыграла ни одного тика
        # 3. это короткая плитка
        if not self.played:
            if self.long and self.count == 0:
                play_note()
            if not self.long:
                play_note()

        if self.long:
            # каждые несколько тиков при нажатии на длинную плитку её цвет меняется
            # когда нужное количество тиков пройдёт, то она навсегда поменяет цвет
            # и будет считаться нажатой

            self.count += 1
            if self.count % 5 == 0 and self.count * 2 < 255:
                self.image = Tile.long_tile_pressed
            else:
                self.image = Tile.long_tile
            if self.count >= 120:
                self.image = Tile.long_tile_pressed
                self.played = True
        else:
            # короткие плитки сразу меняют цвет и считаются сыгранными
            self.image = Tile.short_tile_pressed
            self.played = True


class Song:
    songs = []  # здесь будет храниться список всех доступных песен

    def __init__(self, name, notes, duration, rect, interval=0.5):
        self.name = name
        self.notes = notes
        self.duration = duration
        self.color = "red"
        self.rect = pg.Rect(rect)
        self.interval = interval

        self.text = f1.render(self.name, True, pg.Color("white"))
        Song.songs.append(self)


screen = pg.display.set_mode(SIZE)

fps = 500
clock = pg.time.Clock()

next_note = 0  # номер ноты, которую нужно сыграть следующей
sound = None  # звук, который сейчас играет

pg.mixer.music.set_volume(0.3)


def play_note():
    ...


f1 = pg.font.Font(None, 38)

# создание песен
song1 = Song("В лесу родилась ёлочка", CHRISTMAS_TREE_NOTES, CHRISTMAS_TREE_DURATION, (20, 100, 360, 45))
song2 = Song("Во поле береза стояла", BIRCH_NOTES, BIRCH_DURATION, (20, 200, 360, 45), interval=0.7)
song3 = Song("Утро", MORNING_NOTES, MORNING_DURATION, (20, 300, 360, 45))

background_menu = pg.image.load("menu.png")
background_menu = pg.transform.scale(background_menu, SIZE)

is_play = True

# ВРЕМЕННЫЙ КОД для запуска без меню
screen_notes = pg.sprite.Group()
created_notes = 0
next_note = 0
timer = time.time()
mode = "play"
playing_song = song1


while True:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            quit()
        if event.type == pg.MOUSEBUTTONDOWN:
            # если игра не идёт, при клике переход в меню
            if not is_play and mode == "play":
                if event.button == 1:
                    mode = "menu"

    if mode == "menu":
        is_play = True

        # выключить музыку при выходе в меню
        if isinstance(sound, pg.mixer.Sound):
            sound.stop()

        # место для отрисовки меню

    if mode == "play":
        if is_play:   # только если сейчас идёт игра - нет проигрыша или победы
            print(2)
            if created_notes < len(playing_song.notes) and \
                    time.time() > timer >= playing_song.interval * playing_song.duration[created_notes]:

                if playing_song.duration[created_notes] == 1:
                    screen_notes.add(Tile())
                else:
                    screen_notes.add(Tile(long=True))
                timer = time.time()
                created_notes += 1
            screen_notes.update()

        screen.fill(pg.Color("white"))

        for i in range(4):  # отрисовка дорожек
            pg.draw.line(screen, pg.Color("black"), (i * 100, 0), (i * 100, 600))

        pg.draw.line(screen, pg.Color("red"), (0, 500), (400, 500), 5)

        screen_notes.draw(screen)

        # если была нажата последняя нота в песне и она сыграна до конца, то это победа
        if next_note == len(playing_song.notes):
            for note in screen_notes:
                if not note.played:
                    break
            else:
                text = f1.render("ПОБЕДА", True, pg.Color("green"))
                text_rect = text.get_rect(center=(SIZE[0] // 2, SIZE[1] // 2))
                screen.blit(text, text_rect)
                is_play = False

    pg.display.flip()
    clock.tick(fps)