Implement basic ClassicNote

This commit is contained in:
NaiJi ✨ 2021-06-07 21:19:58 +03:00
parent 3c733cd490
commit f66951bcec
18 changed files with 130 additions and 250 deletions

View File

@ -8,6 +8,7 @@
#include "debughelper.h" #include "debughelper.h"
#include "timeline.h" #include "timeline.h"
#include "note.h" #include "note.h"
#include "game.h"
class Application class Application
{ {
@ -28,9 +29,9 @@ private:
std::unique_ptr<Timeline> _timeline; std::unique_ptr<Timeline> _timeline;
DebugHelper _debug; DebugHelper _debug;
std::unique_ptr<Game> _game;
void startGameLoop(); void startGameLoop();
void onKeyPressed(const sf::Keyboard::Key& key);
void onTap(const Note::Arrow& arrow);
}; };
#endif // APPLICATION_H #endif // APPLICATION_H

17
include/note.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <vector>
#include <SFML/System/Clock.hpp>
using microsec = sf::Int64;
class Note
{
public:
explicit Note(microsec perfect_offset) :
_perfect_offset(perfect_offset) {}
virtual ~Note() = 0;
protected:
microsec _perfect_offset;
};

View File

@ -1,31 +0,0 @@
#ifndef NOTEGRAPHICSENTITY_H
#define NOTEGRAPHICSENTITY_H
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Transformable.hpp>
class NoteGraphicsEntity : public sf::Drawable, public sf::Transformable
{
public:
explicit NoteGraphicsEntity();
virtual ~NoteGraphicsEntity() = 0;
virtual void update() = 0;
virtual void attach() noexcept final;
virtual void detach() noexcept final;
virtual void onKeyPressed() = 0;
virtual void onKeyReleased() = 0;
virtual void show() = 0;
virtual void killAsExpired() = 0;
virtual void reset() = 0;
virtual bool isActive() const = 0;
protected:
bool _attached;
};
#endif

View File

@ -14,9 +14,9 @@ template<typename GRADE, typename = std::enable_if_t<std::is_enum<GRADE>::value>
class PrecisionEvaluator class PrecisionEvaluator
{ {
public: public:
PrecisionEvaluator(std::vector<microsec>&& intervals, microsec offset) : PrecisionEvaluator(const std::vector<microsec>& intervals, microsec offset) :
_offset(offset), _offset(offset),
_intervals(std::move(intervals)) _intervals(intervals)
{ {
microsec&& handling_offset = std::accumulate(intervals.begin(), intervals.end(), 0); microsec&& handling_offset = std::accumulate(intervals.begin(), intervals.end(), 0);
_start_handling_offset = _offset - handling_offset; _start_handling_offset = _offset - handling_offset;
@ -41,7 +41,7 @@ public:
std::size_t raw_grade; std::size_t raw_grade;
for (raw_grade = 0; raw_grade < _intervals.size(); ++raw_grade) for (raw_grade = 0; raw_grade < _intervals.size(); ++raw_grade)
{ {
if (shift_from_perfect <= _intervals[raw_grade]) if (shift_from_perfect <= _intervals.at(raw_grade))
break; break;
} }
@ -60,7 +60,7 @@ private:
* V0 is PERFECT SCORE and the last V represents the worst * V0 is PERFECT SCORE and the last V represents the worst
* grades which is death of note by expiration */ * grades which is death of note by expiration */
std::vector<microsec> _intervals; const std::vector<microsec>& _intervals;
}; };
#endif // PRECISIONEVALUATOR_H #endif // PRECISIONEVALUATOR_H

6
include/sprite.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
class Sprite
{
};

View File

@ -2,56 +2,18 @@
#define TIMELINE_H #define TIMELINE_H
#include <SFML/Config.hpp> #include <SFML/Config.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <vector>
#include <memory> #include <memory>
using microsec = sf::Int64; using microsec = sf::Int64;
class Note;
class TimelineViewManager;
class Timeline : public sf::Drawable // Probably it's bad class Timeline
{ {
public: public:
explicit Timeline(std::unique_ptr<TimelineViewManager> view_manager); virtual ~Timeline() = default;
virtual ~Timeline();
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override; virtual void update(const microsec& music_offset) = 0;
virtual void init() = 0;
void update(const microsec& music_offset); virtual void clear() = 0;
Note *fetchActiveNote(const microsec &music_offset) noexcept;
/* void init(); */
void clear();
private:
std::vector<Note*> _timeline;
std::vector<Note*>::const_iterator _top_note;
Note* _active_note;
std::vector<Note*>::const_iterator _last_visible_note;
microsec _visibility_offset;
std::unique_ptr<TimelineViewManager> _view_manager;
void checkCurrentActiveNote(const microsec &music_offset);
void checkForNextActiveNote(const microsec &music_offset);
void prepareNotesToDraw(const microsec &music_offset);
/* 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 #endif // TIMELINE_H

View File

@ -6,8 +6,7 @@ class Note;
class TimelineViewManager class TimelineViewManager
{ {
public: public:
explicit TimelineViewManager(); virtual ~TimelineViewManager() = default;
virtual ~TimelineViewManager() = 0;
virtual void initNoteGraphics(Note *note) = 0; virtual void initNoteGraphics(Note *note) = 0;
}; };

View File

@ -1,16 +1,15 @@
#include "application.h" #include "application.h"
#include "classicgame/classicgame.h"
#include <SFML/Graphics/Color.hpp> #include <SFML/Graphics/Color.hpp>
#include <SFML/Window/Event.hpp> #include <SFML/Window/Event.hpp>
#include "classicviewmanager.h"
const sf::Time TIME_PER_FRAME = sf::seconds(1.f / 60.f); const sf::Time TIME_PER_FRAME = sf::seconds(1.f / 60.f);
Application::Application() : Application::Application() :
_game_window({1280, 720}, "Test"), _game_window({1280, 720}, "Test"),
_debug(true) _debug(true),
_game(std::make_unique<ClassicGame>())
{ {
_timeline = std::make_unique<Timeline>(std::make_unique<ClassicViewManager>());
_font.loadFromFile("/usr/share/qtcreator/fonts/SourceCodePro-Regular.ttf"); _font.loadFromFile("/usr/share/qtcreator/fonts/SourceCodePro-Regular.ttf");
_grade.setFont(_font); _grade.setFont(_font);
_grade.setPosition(160, 160); _grade.setPosition(160, 160);
@ -53,121 +52,17 @@ void Application::startGameLoop()
} }
} }
static void makeGradeString(const NoteGrade::Rating& rating, sf::Text& text)
{
switch (rating)
{
case (NoteGrade::Rating::BAD):
text.setString("BAD");
text.setFillColor(sf::Color(255, 255, 255, 255));
break;
case (NoteGrade::Rating::GREAT):
text.setString("GREAT");
text.setFillColor(sf::Color(255, 255, 0, 255));
break;
case (NoteGrade::Rating::WRONG):
text.setString("WRONG");
text.setFillColor(sf::Color(120, 120, 120, 255));
break;
case (NoteGrade::Rating::GOOD):
text.setString("GOOD");
text.setFillColor(sf::Color(255, 100, 120, 255));
break;
}
}
void Application::input() void Application::input()
{ {
sf::Event event; sf::Event event;
while (_game_window.pollEvent(event)) while (_game_window.pollEvent(event))
{ {
switch (event.type) _game->input(event);
{
default:
break;
case (sf::Event::Closed):
_game_window.close();
break;
case (sf::Event::KeyPressed):
onKeyPressed(event.key.code);
break;
}
}
}
static Note::Arrow keyToArrow(const sf::Keyboard::Key &key)
{
switch (key)
{
case sf::Keyboard::A:
case sf::Keyboard::Left:
case sf::Keyboard::Num4:
return Note::Arrow::LEFT;
case sf::Keyboard::W:
case sf::Keyboard::Up:
case sf::Keyboard::Num8:
return Note::Arrow::UP;
case sf::Keyboard::D:
case sf::Keyboard::Right:
case sf::Keyboard::Num6:
return Note::Arrow::RIGHT;
case sf::Keyboard::S:
case sf::Keyboard::Down:
case sf::Keyboard::Num2:
return Note::Arrow::DOWN;
default:
return Note::Arrow::NONE;
}
}
void Application::onKeyPressed(const sf::Keyboard::Key &key)
{
if (key == sf::Keyboard::D)
{
_debug.toggle();
return;
}
onTap(keyToArrow(key));
}
void Application::onTap(const Note::Arrow &arrow)
{
if (arrow == Note::Arrow::NONE)
return;
const auto music_offset = _music.getPlayingOffset().asMicroseconds();
auto note = _timeline->fetchActiveNote(music_offset);
if (note)
{
auto tap_result = note->onTap(arrow, music_offset);
makeGradeString(tap_result.rating, _grade);
_grade.setFillColor(sf::Color(255, 255, 255, 255));
} }
} }
void Application::update() void Application::update()
{ {
const auto music_offset = _music.getPlayingOffset().asMicroseconds();
_timeline->update(music_offset);
_debug.update(music_offset);
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));
}
} }

View File

@ -1,7 +1,9 @@
#include "classicgame.h" #include "classicgame.h"
#include "classicinputtype.h" #include "classicinputtype.h"
#include "classictimeline.h"
ClassicGame::ClassicGame() ClassicGame::ClassicGame() :
_timeline(std::make_unique<ClassicTimeline>())
{ {
_keys_to_buttons = _keys_to_buttons =
{ {
@ -71,7 +73,8 @@ void ClassicGame::input(const sf::Event& event)
} }
ClassicInputType input(timestamp, new_action); ClassicInputType input(timestamp, new_action);
/* Here get active Note from timeline and pass the input object to it */ auto note = _timeline->getActiveNote(timestamp);
note->
} }
Action ClassicGame::getActionKeyPressed(Button button) const Action ClassicGame::getActionKeyPressed(Button button) const

View File

@ -6,6 +6,8 @@
#include "game.h" #include "game.h"
#include "classicactions.h" #include "classicactions.h"
class ClassicTimeline;
class ClassicGame final : public Game class ClassicGame final : public Game
{ {
public: public:
@ -25,6 +27,9 @@ private:
Action getActionKeyPressed(Button button) const; Action getActionKeyPressed(Button button) const;
Action getActionKeyReleased(Button button) const; Action getActionKeyReleased(Button button) const;
std::unique_ptr<ClassicTimeline> _timeline;
}; };
#endif // CLASSICGAME_H #endif // CLASSICGAME_H

View File

@ -0,0 +1,8 @@
#include "classicnote.h"
ClassicNote::ClassicNote(const std::vector<microsec>& intervals, microsec perfect_offset) :
Note(perfect_offset),
_evaluator(intervals, _perfect_offset)
{
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "note.h"
#include "precisionevaluator.h"
class ClassicNote : public Note
{
public:
enum class GRADE
{
PERFECT,
GOOD,
BAD
};
explicit ClassicNote(const std::vector<microsec>& intervals, microsec perfect_offset);
virtual ~ClassicNote() = default;
private:
PrecisionEvaluator<GRADE> _evaluator;
};

View File

View File

View File

@ -1,12 +1,9 @@
#include "timeline.h"
#include "note.h"
#include "timelineviewmanager.h"
#include <SFML/Graphics/RenderTarget.hpp>
#include <iostream> #include <iostream>
#include "classicactions.h"
#include "classictimeline.h"
#include "note.h"
Timeline::Timeline(std::unique_ptr<TimelineViewManager> view_manager) : ClassicTimeline::ClassicTimeline()
_view_manager(std::move(view_manager))
{ {
// BPM of METEOR is 170. // BPM of METEOR is 170.
// Length is 1:14 // Length is 1:14
@ -22,15 +19,13 @@ Timeline::Timeline(std::unique_ptr<TimelineViewManager> view_manager) :
microsec bpm_end = starting_beat_offset + (interval * amount_of_beats); microsec bpm_end = starting_beat_offset + (interval * amount_of_beats);
_visibility_offset = note_input_offset * 12; _visibility_offset = note_input_offset * 12;
Note::resetPrecisionQualifier(note_input_offset / 3); _timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Button::DOWN));
_timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Note::Arrow::DOWN));
bpm_iterator += interval; bpm_iterator += interval;
_timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Note::Arrow::LEFT)); _timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Button::LEFT));
bpm_iterator += interval; bpm_iterator += interval;
_timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Note::Arrow::LEFT)); _timeline.emplace_back(new Note(bpm_iterator, note_input_offset, Button::LEFT));
bpm_iterator += interval; bpm_iterator += interval;
while (bpm_iterator < bpm_end) while (bpm_iterator < bpm_end)
@ -39,19 +34,6 @@ Timeline::Timeline(std::unique_ptr<TimelineViewManager> view_manager) :
bpm_iterator += interval; bpm_iterator += interval;
} }
_timeline[0]->setPosition({200, 200});
_timeline[1]->setPosition({250, 200});
_timeline[2]->setPosition({300, 200});
_timeline[3]->setPosition({350, 200});
_timeline[4]->setPosition({400, 200});
_timeline[5]->setPosition({450, 200});
_timeline[6]->setPosition({200, 300});
_timeline[7]->setPosition({250, 300});
_timeline[8]->setPosition({300, 300});
_timeline[9]->setPosition({350, 300});
_timeline[10]->setPosition({400, 300});
_timeline[11]->setPosition({450, 300});
_active_note = nullptr; _active_note = nullptr;
_last_visible_note = _timeline.end(); _last_visible_note = _timeline.end();
_top_note = _timeline.begin(); _top_note = _timeline.begin();
@ -93,19 +75,6 @@ void Timeline::clear()
Note::resetPrecisionQualifier(); Note::resetPrecisionQualifier();
} }
void Timeline::draw(sf::RenderTarget& target, sf::RenderStates states) const // Temporary solution
{
if (_last_visible_note == _timeline.end() || _top_note > _last_visible_note)
return;
auto note_to_draw = _top_note;
while (note_to_draw != (_last_visible_note + 1))
{
target.draw(*(*note_to_draw), states);
++note_to_draw;
}
}
void Timeline::update(const microsec &music_offset) void Timeline::update(const microsec &music_offset)
{ {
checkCurrentActiveNote(music_offset); checkCurrentActiveNote(music_offset);

View File

@ -0,0 +1,43 @@
#pragma once
#include <vector>
#include "timeline.h"
class Note;
class ClassicTimeline : public Timeline
{
public:
explicit ClassicTimeline();
virtual void update(const microsec& music_offset) override;
virtual void init() override;
virtual void clear() override;
Note *getActiveNote(const microsec &music_offset) noexcept;
private:
std::vector<Note*> _timeline;
std::vector<Note*>::const_iterator _top_note;
Note* _active_note;
std::vector<Note*>::const_iterator _last_visible_note;
microsec _visibility_offset;
void checkCurrentActiveNote(const microsec &music_offset);
void checkForNextActiveNote(const microsec &music_offset);
void prepareNotesToDraw(const microsec &music_offset);
/* 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.
* */
};

View File

@ -1,15 +0,0 @@
#include "notegraphicsentity.h"
NoteGraphicsEntity::NoteGraphicsEntity() :
_attached(false)
{}
void NoteGraphicsEntity::attach() noexcept
{
_attached = true;
}
void NoteGraphicsEntity::detach() noexcept
{
_attached = false;
}

View File

@ -1,4 +0,0 @@
#include "timelineviewmanager.h"
TimelineViewManager::TimelineViewManager()
{}