Separate ClassicNote from its logic to ClassicNoteManager

This commit is contained in:
NaiJi ✨ 2021-08-09 21:59:40 +03:00
parent cf1119c742
commit a223b7253c
14 changed files with 236 additions and 272 deletions

View File

@ -11,7 +11,7 @@ set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_USE_WIN32_THREADS_INIT 0) set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1) set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h") file(GLOB_RECURSE SOURCES "include/*.h" "src/*.cpp" "src/*.h")
# STATIC # # STATIC #
# You need to build SFML from sources with cmake # You need to build SFML from sources with cmake

View File

@ -1,28 +0,0 @@
#pragma once
#include "game/inputtype.h"
class Note
{
public:
explicit Note(microsec perfect_offset) :
_perfect_offset(perfect_offset) {}
virtual ~Note() = default;
virtual bool isActive() const = 0;
virtual void update(const microsec& music_offset) = 0;
virtual void input(PlayerInput&& inputdata) = 0;
virtual void draw() const = 0;
virtual void putToGame(const microsec &offset) = 0;
virtual bool isInGame() const = 0;
virtual bool shouldRemove() const = 0;
microsec offset() const
{
return _perfect_offset;
}
protected:
microsec _perfect_offset;
};

View File

@ -37,7 +37,7 @@ public:
_sprite_dispatcher[action].push(sprite); _sprite_dispatcher[action].push(sprite);
} }
~SpriteContainer(){} ~SpriteContainer() = default;
private: private:
inline void reallocatePoll(Type sprite_type) inline void reallocatePoll(Type sprite_type)

View File

@ -5,8 +5,8 @@ using microsec = sf::Int64;
struct Coordinates struct Coordinates
{ {
float x; float x = 0.;
float y; float y = 0.;
constexpr inline Coordinates operator+(const Coordinates& right) const noexcept constexpr inline Coordinates operator+(const Coordinates& right) const noexcept
{ {

View File

@ -2,12 +2,14 @@
#include "classictimeline.h" #include "classictimeline.h"
#include "classicnote.h" #include "classicnote.h"
#include "classicmapcreator.h" #include "classicmapcreator.h"
#include "classicnotemanager.h"
#include "tools/music.h" #include "tools/music.h"
#include <iostream> #include <iostream>
ClassicGame::ClassicGame(std::unique_ptr<ClassicGraphicsManager>&& manager, std::unique_ptr<Music>&& music) : ClassicGame::ClassicGame(std::shared_ptr<ClassicGraphicsManager>&& manager, std::unique_ptr<Music>&& music) :
_timeline(std::make_unique<ClassicTimeline>()), _timeline(std::make_unique<ClassicTimeline>()),
_graphics_manager(std::move(manager)), _graphics_manager(std::move(manager)),
_note_manager(std::make_unique<ClassicNoteManager>(_graphics_manager)),
_music(std::move(music)), _music(std::move(music)),
_is_paused(false) _is_paused(false)
{ {
@ -100,10 +102,10 @@ void ClassicGame::input(PlayerInput&& inputdata)
if (!_timeline->isExpired(note_it)) if (!_timeline->isExpired(note_it))
{ {
auto note = (*note_it); auto note = (*note_it);
note->input(std::move(inputdata)); _note_manager->input(note, std::move(inputdata));
_slap.play(); _slap.play();
if (note->isHold() && note->allElementsPressed()) // also check for Type if (note->hold && _note_manager->allElementsPressed(note)) // also check for Type
{ {
_notes_on_hold.emplace_back(note); _notes_on_hold.emplace_back(note);
std::cout << "HOLD initited by " << inputdata.event.key.code << '\n'; std::cout << "HOLD initited by " << inputdata.event.key.code << '\n';

View File

@ -2,6 +2,7 @@
#define CLASSICGAME_H #define CLASSICGAME_H
#include <map> #include <map>
#include <memory>
#include "game/game.h" #include "game/game.h"
#include "classicactions.h" #include "classicactions.h"
#include <SFML/Audio/SoundBuffer.hpp> #include <SFML/Audio/SoundBuffer.hpp>
@ -11,11 +12,12 @@ class Music;
class ClassicNote; class ClassicNote;
class ClassicTimeline; class ClassicTimeline;
class ClassicGraphicsManager; class ClassicGraphicsManager;
class ClassicNoteManager;
class ClassicGame final : public Game class ClassicGame final : public Game
{ {
public: public:
explicit ClassicGame(std::unique_ptr<ClassicGraphicsManager>&& manager, std::unique_ptr<Music>&& music); explicit ClassicGame(std::shared_ptr<ClassicGraphicsManager>&& manager, std::unique_ptr<Music>&& music);
virtual ~ClassicGame() override; virtual ~ClassicGame() override;
virtual void run() override; virtual void run() override;
@ -32,7 +34,9 @@ private:
std::vector<ClassicNote*> _notes_on_hold; std::vector<ClassicNote*> _notes_on_hold;
std::unique_ptr<ClassicTimeline> _timeline; std::unique_ptr<ClassicTimeline> _timeline;
std::unique_ptr<ClassicGraphicsManager> _graphics_manager; std::shared_ptr<ClassicGraphicsManager> _graphics_manager;
std::shared_ptr<ClassicNoteManager> _note_manager;
sf::SoundBuffer _slap_buffer; sf::SoundBuffer _slap_buffer;
sf::Sound _slap; sf::Sound _slap;

View File

@ -1,7 +1,7 @@
#include "classicmapcreator.h" #include "classicmapcreator.h"
#include "classicnote.h" #include "classicnote.h"
ClassicMapCreator::ClassicMapCreator(const std::unique_ptr<ClassicGraphicsManager>& manager) : ClassicMapCreator::ClassicMapCreator(const std::shared_ptr<ClassicGraphicsManager>& manager) :
_graphics_manager(manager) _graphics_manager(manager)
{} {}

View File

@ -15,12 +15,12 @@ struct Beatmap
class ClassicMapCreator class ClassicMapCreator
{ {
public: public:
explicit ClassicMapCreator(const std::unique_ptr<ClassicGraphicsManager>& manager); explicit ClassicMapCreator(const std::shared_ptr<ClassicGraphicsManager>& manager);
Beatmap createBeatmap(const std::string& filepath) const; Beatmap createBeatmap(const std::string& filepath) const;
private: private:
const std::unique_ptr<ClassicGraphicsManager>& _graphics_manager; const std::shared_ptr<ClassicGraphicsManager> _graphics_manager;
}; };
#endif // CLASSICMAPCREATOR_H #endif // CLASSICMAPCREATOR_H

View File

@ -1,162 +0,0 @@
#include "classicnote.h"
#include "classicsprite.h"
#include "classicgraphicsmanager.h"
// Replace with interface by dependency injection
#include "classicflyinganimationscenario.h"
#include "classicdyinganimationscenario.h"
//
ClassicNote::ClassicNote(ClassicNoteInitializer &&init, const std::unique_ptr<ClassicGraphicsManager> &manager) :
Note(init.perfect_offset),
_evaluator(init.intervals, _perfect_offset),
_graphics_manager(manager),
_state(State::NONE)
{
_elements.resize(init.elements.size());
_isHold = init.hold;
for (std::size_t i = 0; i < _elements.size(); ++i)
{
_elements[i].keys = init.elements[i].keys;
_elements[i].coordinates = init.elements[i].coordinates;
_elements[i].type = init.elements[i].type;
// Animations will be injected into note.
_elements[i].animations[State::NONE] = nullptr;
_elements[i].animations[State::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>();
_elements[i].animations[State::ACTIVE] = _elements[i].animations[State::FLYING];
_elements[i].animations[State::DYING] = std::make_shared<ClassicDyingAnimationScenario>();
_elements[i].animations[State::DEAD] = nullptr;
}
}
bool ClassicNote::isActive() const
{
return _state == State::ACTIVE;
}
void ClassicNote::putToGame(const microsec &music_offset)
{
_state = State::FLYING;
for (auto& element : _elements)
{
element.sprite = _graphics_manager->getSprite(element.type);
element.sprite->setCoordinates(element.coordinates, 0., 9.);
element.animations[_state]->launch(element.sprite, music_offset, offset());
}
}
bool ClassicNote::isInGame() const
{
return _state == State::FLYING
|| _state == State::ACTIVE
|| _state == State::DYING;
}
bool ClassicNote::shouldRemove() const
{
return _state == State::DEAD;
}
void ClassicNote::update(const microsec& music_offset)
{
switch (_state)
{
default: return;
break;
case State::FLYING:
if (_evaluator.isActive(music_offset))
_state = State::ACTIVE;
break;
case State::DYING:
if (_elements[0].animations[_state]->isDone())
_state = State::DEAD;
break;
case State::ACTIVE:
if (!_evaluator.isActive(music_offset))
{
_state = State::DYING;
for (auto& element : _elements)
element.animations[_state]->launch(element.sprite, music_offset, offset());
}
break;
}
for (auto& element : _elements)
if (element.animations[_state])
element.animations[_state]->update(music_offset);
}
void ClassicNote::input(PlayerInput&& inputdata)
{
auto grade = ClassicNote::Grade::BAD;
bool input_valid = std::any_of(_elements.begin(), _elements.end(),
[inputdata=inputdata](auto& element)
{
if (element.pressed)
return false;
auto key_iterator = std::find(element.keys.begin(), element.keys.end(), inputdata.event.key.code);
bool found_key = key_iterator != element.keys.end();
if (found_key)
{
element.pressed = true;
element.pressed_as = inputdata.event.key.code;
}
return found_key;
});
bool all_pressed = allElementsPressed();
if (all_pressed)
grade = _evaluator.calculatePrecision(inputdata.timestamp);
if (all_pressed || !input_valid)
{
_state = State::DYING;
for (auto& element : _elements)
element.animations[_state]->launch(element.sprite, inputdata.timestamp, offset());
}
std::cout << "User input: " << static_cast<int>(grade) << "\n";
}
void ClassicNote::draw() const
{
for (std::size_t i = 0; i < _elements.size(); ++i)
{
if (i >= 1)
_graphics_manager->drawLine(_elements[i-1].sprite->trailCoordinates(), _elements[i].sprite->trailCoordinates());
_graphics_manager->draw(_elements[i].sprite);
}
}
bool ClassicNote::isHold() const
{
return _isHold;
}
bool ClassicNote::allElementsPressed() const
{
return std::all_of(_elements.begin(), _elements.end(),
[](const auto& element)
{
return element.pressed;
});
}
bool ClassicNote::isPressedAs(sf::Keyboard::Key key) const
{
return std::any_of(_elements.begin(), _elements.end(),
[key=key](const auto& element)
{
return key == element.pressed_as;
});
}

View File

@ -1,89 +1,56 @@
#pragma once #pragma once
#include "game/note.h"
#include "game/precisionevaluator.h"
#include "classicactions.h" #include "classicactions.h"
#include "game/precisionevaluator.h"
#include "SFML/Window/Keyboard.hpp"
#include <memory> #include <memory>
#include <vector>
#include <array> #include <array>
class ClassicSprite; enum class ClassicGrade
class ClassicGraphicsManager;
class ClassicAnimationScenario;
class ClassicNote : public Note
{ {
public:
enum class Grade
{
PERFECT, PERFECT,
GOOD, GOOD,
BAD BAD
}; };
enum State enum ClassicNoteState
{ {
NONE, NONE,
FLYING, FLYING,
ACTIVE, ACTIVE,
DYING, DYING,
DEAD DEAD
}; };
struct ClassicNoteInitializer class ClassicSprite;
{ class ClassicAnimationScenario;
struct ClassicNote
{
PrecisionEvaluator<ClassicGrade> evaluator;
std::vector<microsec> intervals; std::vector<microsec> intervals;
microsec perfect_offset; bool hold = false;
bool hold;
ClassicNoteState state = ClassicNoteState::NONE;
struct Element struct Element
{ {
Type type;
Coordinates coordinates;
std::vector<Coordinates> falling_curve_interpolation;
std::array<sf::Keyboard::Key, 2> keys;
};
std::vector<Element> elements;
};
explicit ClassicNote(ClassicNoteInitializer&& init, const std::unique_ptr<ClassicGraphicsManager>& manager);
virtual ~ClassicNote() = default;
virtual bool isActive() const override;
virtual void update(const microsec &music_offset) override;
virtual void input(PlayerInput&& inputdata) override;
virtual void putToGame(const microsec &music_offset) override;
virtual bool isInGame() const override;
virtual bool shouldRemove() const override;
virtual void draw() const override;
bool isHold() const;
bool allElementsPressed() const;
bool isPressedAs(sf::Keyboard::Key key) const;
private:
struct NoteElement
{
std::shared_ptr<ClassicSprite> sprite;
std::array<std::shared_ptr<ClassicAnimationScenario>, 5> animations;
std::array<sf::Keyboard::Key, 2> keys;
Coordinates coordinates; // Each note may consist of several buttons. Coordinates coordinates; // Each note may consist of several buttons.
Type type; // For example, ↑ → or ↓ → ← // For example, ↑ → or ↓ → ←
// Note Element represents this idea. // Note Element represents this idea.
bool pressed = false; // Each ending button in such sequence bool pressed = false; // Each ending button in such sequence
sf::Keyboard::Key pressed_as; // is an element. sf::Keyboard::Key pressed_as; // is an element.
Type type = Type::NONE;
std::vector<Coordinates> falling_curve_interpolation;
std::array<sf::Keyboard::Key, 2> available_keys;
std::shared_ptr<ClassicSprite> sprite;
std::array<std::shared_ptr<ClassicAnimationScenario>, 5> animations;
}; };
std::vector<NoteElement> _elements; std::vector<Element> elements;
const PrecisionEvaluator<Grade> _evaluator;
const std::unique_ptr<ClassicGraphicsManager>& _graphics_manager;
State _state;
bool _isHold;
}; };

View File

@ -0,0 +1,149 @@
#include "classicnotemanager.h"
#include "classicsprite.h"
#include "classicgraphicsmanager.h"
// Replace with interface by dependency injection
#include "classicflyinganimationscenario.h"
#include "classicdyinganimationscenario.h"
//
ClassicNoteManager::ClassicNoteManager(const std::shared_ptr<ClassicGraphicsManager>& manager) :
_graphics_manager(manager)
{}
ClassicNoteManager::~ClassicNoteManager()
{}
bool ClassicNoteManager::isActive(const ClassicNote* note) const
{
return note->state == ClassicNoteState::ACTIVE;
}
void ClassicNoteManager::putToGame(ClassicNote* note, const microsec &music_offset)
{
note->state = ClassicNoteState::FLYING;
for (auto& element : note->elements)
{
element.sprite = _graphics_manager->getSprite(element.type);
element.sprite->setCoordinates(element.coordinates, 0., 9.);
element.animations[note->state]->launch(element.sprite, music_offset, note->evaluator.offset());
}
}
bool ClassicNoteManager::isInGame(const ClassicNote* note) const
{
return note->state == ClassicNoteState::FLYING
|| note->state == ClassicNoteState::ACTIVE
|| note->state == ClassicNoteState::DYING;
}
bool ClassicNoteManager::shouldRemove(const ClassicNote* note) const
{
return note->state == ClassicNoteState::DEAD;
}
void ClassicNoteManager::update(ClassicNote* note, const microsec& music_offset)
{
switch (note->state)
{
default: return;
break;
case ClassicNoteState::FLYING:
if (note->evaluator.isActive(music_offset))
note->state = ClassicNoteState::ACTIVE;
break;
case ClassicNoteState::DYING:
if (note->elements[0].animations[note->state]->isDone())
note->state = ClassicNoteState::DEAD;
break;
case ClassicNoteState::ACTIVE:
if (!note->evaluator.isActive(music_offset))
{
note->state = ClassicNoteState::DYING;
for (auto& element : note->elements)
element.animations[note->state]->launch(element.sprite, music_offset, note->evaluator.offset());
}
break;
}
for (auto& element : note->elements)
if (element.animations[note->state])
element.animations[note->state]->update(music_offset);
}
void ClassicNoteManager::input(ClassicNote* note, PlayerInput&& inputdata)
{
auto grade = ClassicGrade::BAD;
bool input_valid = std::any_of(note->elements.begin(), note->elements.end(),
[inputdata=inputdata](auto& element)
{
if (element.pressed)
return false;
auto key_iterator = std::find(element.keys.begin(), element.keys.end(), inputdata.event.key.code);
bool found_key = key_iterator != element.keys.end();
if (found_key)
{
element.pressed = true;
element.pressed_as = inputdata.event.key.code;
}
return found_key;
});
bool all_pressed = allElementsPressed(note);
if (all_pressed)
grade = note->evaluator.calculatePrecision(inputdata.timestamp);
if (all_pressed || !input_valid)
{
note->state = ClassicNoteState::DYING;
for (auto& element : note->elements)
element.animations[note->state]->launch(element.sprite, inputdata.timestamp, note->evaluator.offset());
}
std::cout << "User input: " << static_cast<int>(grade) << "\n";
}
void ClassicNoteManager::draw(const ClassicNote* note) const
{
for (std::size_t i = 0; i < note->elements.size(); ++i)
{
if (i >= 1)
_graphics_manager->drawLine(note->elements[i-1].sprite->trailCoordinates(), note->elements[i].sprite->trailCoordinates());
_graphics_manager->draw(note->elements[i].sprite);
}
}
bool ClassicNoteManager::allElementsPressed(const ClassicNote* note) const
{
return std::all_of(note->elements.begin(), note->elements.end(),
[](const auto& element)
{
return element.pressed;
});
}
bool ClassicNoteManager::isPressedAs(const ClassicNote* note, sf::Keyboard::Key key) const
{
return std::any_of(note->elements.begin(), note->elements.end(),
[key=key](const auto& element)
{
return key == element.pressed_as;
});
}
void ClassicNoteManager::setDefaultAnimations(std::array<std::shared_ptr<ClassicAnimationScenario>, 5>& animations) const
{
animations[ClassicNoteState::NONE] = nullptr;
animations[ClassicNoteState::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>();
animations[ClassicNoteState::ACTIVE] = animations[ClassicNoteState::FLYING];
animations[ClassicNoteState::DYING] = std::make_shared<ClassicDyingAnimationScenario>();
animations[ClassicNoteState::DEAD] = nullptr;
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "game/inputtype.h"
#include "classicnote.h"
#include <memory>
class ClassicGraphicsManager;
class ClassicNoteManager
{
public:
explicit ClassicNoteManager(const std::shared_ptr<ClassicGraphicsManager>& manager);
~ClassicNoteManager();
bool isActive(const ClassicNote* note) const;
void update(ClassicNote* note, const microsec &music_offset);
void input(ClassicNote* note, PlayerInput&& inputdata);
void putToGame(ClassicNote* note, const microsec &music_offset);
bool isInGame(const ClassicNote* note) const;
bool shouldRemove(const ClassicNote* note) const;
void draw(const ClassicNote* note) const;
bool allElementsPressed(const ClassicNote* note) const;
bool isPressedAs(const ClassicNote* note, sf::Keyboard::Key key) const;
void setDefaultAnimations(std::array<std::shared_ptr<ClassicAnimationScenario>, 5>& animations) const;
private:
const std::shared_ptr<ClassicGraphicsManager> _graphics_manager;
};

View File

@ -94,7 +94,7 @@ void ClassicTimeline::expire(Iterator& iterator)
bool ClassicTimeline::isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const bool ClassicTimeline::isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const
{ {
return ((*iterator)->offset() - _visibility_offset) <= music_offset; return ((*iterator)->evaluator.offset() - _visibility_offset) <= music_offset;
} }
void ClassicTimeline::fetchVisibleNotes() void ClassicTimeline::fetchVisibleNotes()

View File

@ -6,6 +6,8 @@
#include <memory> #include <memory>
class ClassicNote; class ClassicNote;
class ClassicNoteManager;
class ClassicGraphicsManager;
class ClassicTimeline : public Timeline class ClassicTimeline : public Timeline
{ {