diff --git a/application.cpp b/application.cpp index 343d3e8..355d024 100644 --- a/application.cpp +++ b/application.cpp @@ -1,4 +1,5 @@ #include "application.h" +#include "note.h" #include #include @@ -17,27 +18,7 @@ Application::Application() : void Application::run() { - // BPM of METEOR is 170. - // Length is 1:14 - // I calculated that the time between beats is about 1412162 microseconds - std::string song_filename = "/home/naiji/METEOR.flac"; - microsec starting_beat_offset = 372162; - int amount_of_beats = 209; - microsec time_between_beats = 1412162; - microsec note_input_offset = 412162; - sf::Int64 iter = starting_beat_offset + (time_between_beats * amount_of_beats); - - Note::setPrecisionQualifier(note_input_offset / 2); - - while (iter > 0) - { - Note note(iter, note_input_offset); - _timeline.push(note); - iter -= time_between_beats; - } - - // // // // // // // // _music.openFromFile(song_filename); _music.play(); @@ -163,48 +144,31 @@ void Application::onKeyPressed(const sf::Keyboard::Key &key) const auto arrow = keyToArrow(key); if (arrow != Note::Arrow::NONE) - { + { // TODO: SHIT BLOCK. _debug.spawnGreenPulse(); - - if (!_timeline.empty()) + const auto note = _timeline.getActiveNote(); + if (note) { - const auto current_note = _timeline.top(); - const auto grade_result = current_note.onTap(arrow, _music.getPlayingOffset().asMicroseconds()); - _grade = makeGradeString(grade_result.rating); + // This is obscure. Active note on timeline gets received by last ::update() call. + // there can be 100-200 microseconds delay between onKeyPressed and update... + // Also the problem is that we get music offset by CURRENT music time, + // when active note is activated by music time of last ::update call + // anyway gotta think on it, smh smh smh + const auto music_offset = _music.getPlayingOffset().asMicroseconds(); + const auto tap_result = note->onTap(arrow, music_offset); + _grade = makeGradeString(tap_result.rating); } } } void Application::update() { - const auto microseconds = _music.getPlayingOffset().asMicroseconds(); + const auto music_offset = _music.getPlayingOffset().asMicroseconds(); - // To Do: Here we notice when next note becomes active and ready for user input. - // Here I explicitly calculate its birth time for now. - if (!_timeline.empty() && _timeline.top().offset() - 412162 <= microseconds) - { - _debug.spawnBluePulse(); - } + _timeline.update(music_offset); + _debug.update(music_offset); - // To do: Actual note offset should pulse only once and the note shouldn't die right after it, - // because there is also "after pulse" offset, like, you know, player can be a little late - if (!_timeline.empty() && _timeline.top().offset() <= microseconds) - { - _timeline.pop(); - _debug.spawnRedPulse(); - } - - // To do: Here should be the end of "after pulse" time. When user fucked up all the time and the - // note dies with "Missed" grade. - // - // if ( . . . _timeline.top().offset() + 412162 <= microseconds) - // { - // - // } - - _debug.update(microseconds); - - if (_grade.getFillColor().a > 0) + if (_grade.getFillColor().a > 0) // TODO: Encapsulate { const auto alpha = _grade.getFillColor().a - 20; _grade.setFillColor(sf::Color(255, 255, 255, alpha < 0 ? 0 : alpha)); @@ -215,7 +179,7 @@ void Application::update() void Application::draw() { _game_window.clear(); - _debug.drawOn(_game_window); + _game_window.draw(_debug); _game_window.draw(_grade); _game_window.display(); } diff --git a/application.h b/application.h index 3e67278..91bc93d 100644 --- a/application.h +++ b/application.h @@ -5,10 +5,8 @@ #include #include -#include - #include "debughelper.h" -#include "note.h" +#include "timeline.h" class Application { @@ -23,12 +21,10 @@ private: sf::RenderWindow _game_window; sf::Music _music; - std::stack _timeline; - sf::Int64 _time_since_last_tick; - sf::Int64 _last_stamp; sf::Font _font; sf::Text _grade; + Timeline _timeline; DebugHelper _debug; void startGameLoop(); diff --git a/debughelper.cpp b/debughelper.cpp index 480c441..2905979 100644 --- a/debughelper.cpp +++ b/debughelper.cpp @@ -28,14 +28,14 @@ void DebugHelper::update(const microsec µseconds) _blue_pulse.fade(); } -void DebugHelper::drawOn(sf::RenderWindow &game_window) const +void DebugHelper::draw(sf::RenderTarget& target, sf::RenderStates states) const { if (_toggled) { - _red_pulse.drawOn(game_window); - _green_pulse.drawOn(game_window); - _blue_pulse.drawOn(game_window); - game_window.draw(_time_print); + target.draw(_green_pulse, states); + target.draw(_red_pulse, states); + target.draw(_blue_pulse, states); + target.draw(_time_print, states); } } @@ -83,7 +83,7 @@ void DebugHelper::Pulse::fade() _pulse_shape.setFillColor(fill_color); } -void DebugHelper::Pulse::drawOn(sf::RenderWindow &game_window) const +void DebugHelper::Pulse::draw(sf::RenderTarget& target, sf::RenderStates states) const { - game_window.draw(_pulse_shape); + target.draw(_pulse_shape, states); } diff --git a/debughelper.h b/debughelper.h index f0f0b0a..1148642 100644 --- a/debughelper.h +++ b/debughelper.h @@ -8,14 +8,14 @@ using microsec = sf::Int64; -class DebugHelper +class DebugHelper : public sf::Drawable { public: DebugHelper(bool init = true); void toggle(); void update(const microsec& microseconds); - void drawOn(sf::RenderWindow &game_window) const; + virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override; void spawnGreenPulse(); void spawnRedPulse(); @@ -26,14 +26,14 @@ private: sf::Font _font; sf::Text _time_print; - class Pulse + class Pulse : public sf::Drawable { public: Pulse(sf::Vector2f position, sf::Color fill_color); void appear(); void fade(); - void drawOn(sf::RenderWindow &game_window) const; + virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override; private: sf::RectangleShape _pulse_shape; diff --git a/note.cpp b/note.cpp index 922d438..cc59023 100644 --- a/note.cpp +++ b/note.cpp @@ -2,8 +2,8 @@ #include Note::Note(microsec offset, microsec life_span_offset, Note::Arrow type) : - _offset(offset), - _start_handling_offset(_offset + life_span_offset), + _offset(offset), // TODO: Move to struct NoteData + _start_handling_offset(_offset + life_span_offset), // so Note::Note(NoteData&& data) : . . . _end_handling_offset(_offset - life_span_offset), _type(type) {} @@ -50,7 +50,7 @@ bool Note::isActive(microsec music_play_offset) const noexcept && music_play_offset < _end_handling_offset; } -void Note::setPrecisionQualifier(microsec qualifier) +void Note::resetPrecisionQualifier(microsec qualifier) { _precision_qualifier = qualifier; } diff --git a/note.h b/note.h index a0484f9..c2a3682 100644 --- a/note.h +++ b/note.h @@ -47,7 +47,7 @@ public: NoteGrade onTap(Arrow arrow_type, microsec tap_time_stamp) const; bool isActive(microsec music_play_offset) const noexcept; - static void setPrecisionQualifier(microsec qualifier); + static void resetPrecisionQualifier(microsec qualifier = 500000); private: coordinates _position; diff --git a/timeline.cpp b/timeline.cpp new file mode 100644 index 0000000..eb270d0 --- /dev/null +++ b/timeline.cpp @@ -0,0 +1,66 @@ +#include "timeline.h" +#include "note.h" + +Timeline::Timeline() +{ + // BPM of METEOR is 170. + // Length is 1:14 + // I calculated that the time between beats is about 1412162 microseconds + + microsec starting_beat_offset = 372162; + int amount_of_beats = 209; + microsec time_between_beats = 1412162; + microsec note_input_offset = 412162; + microsec interval = starting_beat_offset + (time_between_beats * amount_of_beats); + + Note::resetPrecisionQualifier(note_input_offset / 2); + + while (interval > 0) + { + _timeline.emplace_back(new Note(interval, note_input_offset)); + interval -= time_between_beats; + } + + _active_note = nullptr; + _top_note = _timeline.begin(); +} + +Timeline::~Timeline() +{ + for (auto note : _timeline) + delete note; + + _timeline.clear(); + _top_note = _timeline.end(); + _active_note = nullptr; + + Note::resetPrecisionQualifier(); +} + +void Timeline::update(const microsec µseconds) +{ + checkCurrentActiveNote(microseconds); + checkForNextActiveNote(microseconds); +} + +void Timeline::checkCurrentActiveNote(const microsec µseconds) +{ + if (_active_note && !_active_note->isActive(microseconds)) + { + _active_note = nullptr; + ++_top_note; + } +} + +void Timeline::checkForNextActiveNote(const microsec µseconds) +{ + if (!_active_note && (*_top_note)->isActive(microseconds)) + { + _active_note = *_top_note; + } +} + +const Note* Timeline::getActiveNote() const noexcept +{ + return _active_note; +} diff --git a/timeline.h b/timeline.h new file mode 100644 index 0000000..f2bda8a --- /dev/null +++ b/timeline.h @@ -0,0 +1,44 @@ +#ifndef TIMELINE_H +#define TIMELINE_H + +#include + +#include +#include + +using microsec = sf::Int64; +class Note; + +class Timeline +{ +public: + explicit Timeline(); + ~Timeline(); + + void update(const microsec& microseconds); + const Note* getActiveNote() const noexcept; + +private: + std::vector _timeline; + std::vector::iterator _top_note; + Note* _active_note; + + void checkCurrentActiveNote(const microsec µseconds); + void checkForNextActiveNote(const microsec µseconds); + + /* Difference between top and active note is that + * top note is the note handling input right now + * OR it's the closest note from current music offset + * position, not necessarily active. A note stops being top only + * after dying or being tapped by player, even if it's already + * past her perfect offset. + * + * Meanwhile active note is the note which is currently handling + * player input for grade. + * + * An active note is always top note but a top note + * is not always active note. + * */ +}; + +#endif // TIMELINE_H