๊ณต๋ถ€/์ฝ”๋”ฉ

์•„์ฃผ ์œ ์šฉํ•˜๊ณ  ํŽธ๋ฆฌํ•œ ์ƒ๋…„์›”์ผ ์ž…๋ ฅ ํ”„๋กœ๊ทธ๋žจ

sourceoftax 2025. 12. 8. 22:10

 

๋™์˜์ƒ ์„œ๋น„์Šค๊ฐ€ ์ข…๋ฃŒ๋˜์–ด ํ•ด๋‹น ์ฝ˜ํ…์ธ ๋ฅผ ์žฌ์ƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์˜ ์‹ค๋ ฅ์„ ๋ฝ๋‚ด๋ณด์„ธ์š”

 

์‚ฌ์šฉ ์ฝ”๋“œ 1(์Œ์•… ๋„ฃ๊ณ  ๋…ธ๋“œ ๋ฝ‘๋Š” ํ”„๋กœ๊ทธ๋žจ)

import librosa
import numpy as np
import json
import os


WAV_FILE = "fantaisie.wav"
OUTPUT_FILE = "beatmap.json"

def create_beatmap():
    if not os.path.exists(WAV_FILE):
        print(f"์˜ค๋ฅ˜. {WAV_FILE} ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค")
        return

    print("์Œ์•… ๋ถ„์„ ์ค‘")
    
    y, sr = librosa.load(WAV_FILE, sr=22050)

    onset_env = librosa.onset.onset_strength(y=y, sr=sr)
    onset_frames = librosa.onset.onset_detect(
        onset_envelope=onset_env, 
        sr=sr, 
        wait=3,      
        delta=0.04   
    )
    
    timestamps = librosa.frames_to_time(onset_frames, sr=sr)
    
    timestamps_list = timestamps.tolist()

    with open(OUTPUT_FILE, 'w') as f:
        json.dump(timestamps_list, f)

    print(f"{len(timestamps_list)}๊ฐœ์˜ ๋…ธํŠธ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค")
    print(f"๊ฒฐ๊ณผ๊ฐ€ {OUTPUT_FILE}์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค")

if __name__ == "__main__":
    create_beatmap()

์‚ฌ์šฉ ์ฝ”๋“œ 2(๋ฉ”์ธ ์ฝ”๋“œ)

import sys
import os
import time
import json
import random
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QPushButton, 
                             QVBoxLayout, QHBoxLayout, QStackedWidget)
from PyQt5.QtCore import QTimer, Qt, QUrl
from PyQt5.QtGui import QPainter, QColor, QFont, QPen
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 600
NOTE_SPEED = 8
HIT_Y = 500
HIT_RANGE = 70


class RhythmGameWidget(QWidget):
    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window
        self.score = 0
        self.notes = []
        self.is_playing = False
        self.is_game_over = False

        self.timer = QTimer()
        self.timer.timeout.connect(self.game_loop)

        self.score_font = QFont("Arial", 24, QFont.Bold)
        self.gameover_font = QFont("Arial", 36, QFont.Bold)

        self.player = QMediaPlayer()
        self.hit_player = QMediaPlayer()

        bgm = QUrl.fromLocalFile(os.path.abspath("fantaisie.wav"))
        hit = QUrl.fromLocalFile(os.path.abspath("hit.mp3"))

        self.player.setMedia(QMediaContent(bgm))
        self.hit_player.setMedia(QMediaContent(hit))

        self.map_file = "beatmap.json"
        self.note_timestamps = []

        if os.path.exists(self.map_file):
            with open(self.map_file, 'r') as f:
                self.note_timestamps = json.load(f)

        self.current_note_index = 0
        self.start_time = 0

    def start_game(self):
        if not self.note_timestamps:
            self.main_window.show_status("์˜ค๋ฅ˜. beatmap.json ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค")
            self.main_window.back_to_form()
            return

        self.score = 0
        self.notes = []
        self.current_note_index = 0
        self.is_playing = True
        self.is_game_over = False

        self.start_time = time.time()

        self.timer.start(16)
        self.setFocus()

        self.player.stop()
        self.player.play()

    def stop_game(self):
        if not self.is_playing:
            return

        self.is_playing = False
        self.is_game_over = True

        QTimer.singleShot(20, self._finish_stop)

    def _finish_stop(self):
        self.timer.stop()
        self.player.stop()
        self.update()

        QTimer.singleShot(700, self._safe_return)

    def _safe_return(self):
        self.main_window.finish_game(self.score)

    def play_hit_sound(self):
        self.hit_player.stop()
        self.hit_player.play()

    def game_loop(self):
        if not self.is_playing:
            return

        now = time.time() - self.start_time

        while self.current_note_index < len(self.note_timestamps):
            t = self.note_timestamps[self.current_note_index]
            if t <= now:
                x = random.randint(0, WINDOW_WIDTH - 50)
                self.notes.append({'x': x, 'y': -20})
                self.current_note_index += 1
            else:
                break

        for note in self.notes:
            note['y'] += NOTE_SPEED
            if note['y'] > WINDOW_HEIGHT:  
                self.stop_game()
                return

        self.update()

    def paintEvent(self, event):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)

        p.fillRect(self.rect(), QColor("#222222"))

        if self.is_game_over:
            p.setPen(QColor("#FF4444"))
            p.setFont(self.gameover_font)
            p.drawText(self.rect(), Qt.AlignCenter, "์ž…๋ ฅ ์™„๋ฃŒ")

            p.setPen(QColor("white"))
            p.setFont(self.score_font)
            p.drawText(self.rect().adjusted(0, 90, 0, 0),
                       Qt.AlignCenter, f"์ž…๋ ฅ๋œ ์ •๋ณด: {self.score}")
            return

        pen = QPen(QColor(255, 255, 255))
        pen.setWidth(2)
        p.setPen(pen)
        p.drawLine(0, HIT_Y, WINDOW_WIDTH, HIT_Y)

        p.setBrush(QColor("#66CCFF"))
        p.setPen(Qt.NoPen)
        for note in self.notes:
            p.drawRect(note['x'], note['y'], 50, 20)

        p.setPen(QColor("#66CCFF"))
        p.setFont(self.score_font)
        p.drawText(self.rect(),
                   Qt.AlignTop | Qt.AlignHCenter,
                   f"COMBO: {self.score}")

    def keyPressEvent(self, event):
        if not self.is_playing:
            return

        if event.key() == Qt.Key_Space:
            for note in self.notes[:]:
                if HIT_Y - HIT_RANGE < note['y'] < HIT_Y + HIT_RANGE:
                    self.notes.remove(note)
                    self.score += 1
                    self.play_hit_sound()
                    break


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Input System")
        self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.setStyleSheet("background-color: #f9f9f9; font-family: 'Malgun Gothic';")

        self.stack = QStackedWidget(self)
        self.form_widget = QWidget()
        self.setup_form_ui()
        self.game_widget = RhythmGameWidget(self)

        self.stack.addWidget(self.form_widget)
        self.stack.addWidget(self.game_widget)

        layout = QVBoxLayout(self)
        layout.addWidget(self.stack)
        layout.setContentsMargins(0, 0, 0, 0)

        self.current_target = None

    def setup_form_ui(self):
        layout = QVBoxLayout()
        layout.setAlignment(Qt.AlignCenter)
        layout.setSpacing(20)

        title = QLabel("์ƒ๋…„์›”์ผ ์ž…๋ ฅ")
        title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
        title.setAlignment(Qt.AlignCenter)

        self.status_label = QLabel("์ž…๋ ฅ๋œ ์ •๋ณด๋Š” ์‚ฌ์šฉ ํ›„ ํŒŒ๊ธฐ๋ฉ๋‹ˆ๋‹ค")
        self.status_label.setStyleSheet("font-size: 14px; color: #666;")
        self.status_label.setAlignment(Qt.AlignCenter)

        input_layout = QHBoxLayout()
        self.btn_year = self.create_input_btn("YYYY")
        self.btn_month = self.create_input_btn("MM")
        self.btn_day = self.create_input_btn("DD")

        input_layout.addWidget(self.btn_year)
        input_layout.addWidget(self.btn_month)
        input_layout.addWidget(self.btn_day)

        btn_submit = QPushButton("์ œ์ถœํ•˜๊ธฐ")
        btn_submit.setStyleSheet("""
            background-color: #333; color: white; border-radius: 8px;
            padding: 12px; font-weight: bold;
        """)

        layout.addStretch()
        layout.addWidget(title)
        layout.addWidget(self.status_label)
        layout.addLayout(input_layout)
        layout.addWidget(btn_submit)
        layout.addStretch()

        self.form_widget.setLayout(layout)

    def create_input_btn(self, text):
        btn = QPushButton(text)
        btn.setFixedSize(90, 60)
        btn.setStyleSheet("""
            QPushButton {
                border: 2px solid #ddd;
                border-radius: 12px;
                background: white;
                font-size: 18px;
                color: #555;
            }
            QPushButton:hover { border-color: #66CCFF; }
        """)
        btn.clicked.connect(lambda: self.start_game_sequence(btn))
        return btn

    def start_game_sequence(self, btn):
        self.current_target = btn
        self.stack.setCurrentWidget(self.game_widget)
        self.game_widget.start_game()

    def back_to_form(self):
        self.stack.setCurrentWidget(self.form_widget)

    def show_status(self, msg):
        self.status_label.setText(msg)

    def finish_game(self, score):
        QTimer.singleShot(10, lambda: self._finish(score))

    def _finish(self, score):
        self.stack.setCurrentWidget(self.form_widget)

        if self.current_target:
            self.current_target.setText(str(score))
            self.current_target.setStyleSheet("""
                border: 2px solid #FF4444;
                border-radius: 12px;
                background: #FFF0F0;
                color: #FF4444;
                font-size: 20px;
                font-weight: bold;
            """)

        self.status_label.setText("์ž…๋ ฅ ์™„๋ฃŒ")
        self.status_label.setStyleSheet(
            "font-size: 16px; color: #FF4444; font-weight: bold;"
        )

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())