diff --git a/meson.build b/meson.build index e54e073..7622304 100644 --- a/meson.build +++ b/meson.build @@ -15,19 +15,20 @@ filesystem = cpp.find_library('stdc++fs') sources = [ 'src/Main.cpp', - 'src/Model/Chart.hpp', - 'src/Model/MusicList.hpp', - 'src/Model/MusicList.cpp', - 'src/Model/Score.hpp', + 'src/Data/Note.hpp', + 'src/Data/Note.cpp', + 'srx/Data/Score.hpp', + 'src/Data/SongList.hpp', + 'src/Data/SongList.cpp', 'src/Screens/Gameplay.hpp', 'src/Screens/MusicSelect.hpp', 'src/Screens/Result.hpp', - 'src/Textures/TexturePack.hpp', - 'src/Textures/TexturePack.cpp', ] executable( 'jujube', - sources, + 'src/Test.cpp', + 'src/Textures/CoverPack.hpp', + 'src/Textures/CoverPack.cpp', dependencies: [sfml, filesystem] ) \ No newline at end of file diff --git a/src/Data/Chart.hpp b/src/Data/Chart.hpp new file mode 100644 index 0000000..f2ac0dc --- /dev/null +++ b/src/Data/Chart.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace Data { + class Chart { + public: + Chart(/* args */); + }; +} \ No newline at end of file diff --git a/src/Data/Note.cpp b/src/Data/Note.cpp new file mode 100644 index 0000000..52d2bef --- /dev/null +++ b/src/Data/Note.cpp @@ -0,0 +1,73 @@ +#include "Note.hpp" + + +Data::Note::Note( + unsigned int t_position, + sf::Time t_timing, + sf::Time t_length, + unsigned int t_tail_position +) : + position(t_position), + timing(t_timing), + length(t_length), + tail_position(t_tail_position) +{ + if (t_position > 15) { + throw std::out_of_range("Tried creating a note with invalid position : "+std::to_string(t_position)); + } + + if (t_length.asMicroseconds < 0) { + throw std::out_of_range("Tried creating a long note with negative length : "+std::to_string(t_length.asSeconds)+ "s"); + + } + if (t_length.asMicroseconds > 0) { + if (t_tail_position > 5) { + throw std::out_of_range("Tried creating a long note with invalid tail position : "+std::to_string(t_tail_position)); + } + } +} + +bool Data::Note::operator==(const Data::Note &rhs) const { + return timing == rhs.timing && position == rhs.position; +} + +bool Data::Note::operator!=(const Data::Note &rhs) const { + return !(rhs == *this); +} + +bool Data::Note::operator<(const Data::Note &rhs) const { + if (timing < rhs.timing) { + return true; + } + if (rhs.timing < timing) { + return false; + } + return position < rhs.position; +} + +bool Data::Note::operator>(const Data::Note &rhs) const { + return rhs < *this; +} + +bool Data::Note::operator<=(const Data::Note &rhs) const { + return !(rhs < *this); +} + +bool Data::Note::operator>=(const Data::Note &rhs) const { + return !(*this < rhs); +} +const unsigned int Data::Note::getPosition() const { + return position; +} + +const sf::Time& Data::Note::getTiming() const { + return timing; +} + +const sf::Time& Data::Note::getLength() const { + return length; +} + +const unsigned int Data::Note::getTailPosition() const { + return tail_position; +} diff --git a/src/Data/Note.hpp b/src/Data/Note.hpp new file mode 100644 index 0000000..60f98cc --- /dev/null +++ b/src/Data/Note.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace Data { + class Note { + public: + Note(unsigned int t_position, sf::Time t_timing, sf::Time t_length, unsigned int t_tail_position); + + bool operator==(const Note &rhs) const; + bool operator!=(const Note &rhs) const; + bool operator<(const Note &rhs) const; + bool operator>(const Note &rhs) const; + bool operator<=(const Note &rhs) const; + bool operator>=(const Note &rhs) const; + + const unsigned int getPosition() const; + const sf::Time& getTiming() const; + const sf::Time& getLength() const; + const unsigned int getTailPosition() const; + + private: + + const unsigned int position; + const sf::Time timing; + const sf::Time length = sf::Time{}; + const unsigned int tail_position = 0; + + }; +}; \ No newline at end of file diff --git a/src/Data/Score.hpp b/src/Data/Score.hpp new file mode 100644 index 0000000..c617c49 --- /dev/null +++ b/src/Data/Score.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace Data { + class Score { + public: + Score(); + }; +}; \ No newline at end of file diff --git a/src/Data/SongList.cpp b/src/Data/SongList.cpp new file mode 100644 index 0000000..4818181 --- /dev/null +++ b/src/Data/SongList.cpp @@ -0,0 +1,116 @@ +#include "SongList.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +Data::SongList::SongList::SongList() { + + // Loading all song metadata + for (const auto& folder : getSongFolders()) { + try { + songs.emplace_back(folder); + } catch (const std::invalid_argument& e) { + std::cerr << "Exception while parsing song folder " + << folder.string() << " : " << e.what() << '\n'; + continue; + } + } + + // Loading all cover previews + for (const auto& song : songs) { + if (song.cover) { + try { + cover_previews.emplace_back(*song.cover); + } catch (const std::invalid_argument& e) { + std::cerr << "Exception while loading song cover " + << song.cover->string() << " : " << e.what() << '\n'; + } + } + } + +} + + +const std::vector Data::getSongFolders() { + + std::vector song_folders; + + for (const auto& dir_item : fs::directory_iterator("songs/")) { + if (dir_item.is_directory()) { + for (auto& path : recursiveSongSearch(dir_item.path())) { + song_folders.push_back(path); + } + } + } + + return song_folders; +} + +std::list Data::recursiveSongSearch(fs::path song_or_pack) { + std::list res; + fs::directory_iterator folder{song_or_pack}; + if ( + std::any_of( + fs::begin(folder), + fs::end(folder), + [](const fs::directory_entry& de) { + de.path().extension() == ".memo" or + de.path().extension() == ".memon"; + } + ) + ) { + res.push_back(song_or_pack); + return res; + } else { + for (auto& p : fs::directory_iterator(song_or_pack)) { + if (p.is_directory()) { + res.splice(res.end(), recursiveSongSearch(p)); + } + } + return res; + } +} + +Data::Song::Song(fs::path song_folder) { + + // .memon ? + auto memon_files = getMemonFiles(song_folder); + if (not memon_files.empty()) { + if (memon_files.size() > 1) { + throw std::invalid_argument("Multiple .memon files"); + } else { + + } + } else { + // .memo ? + auto memo_files = getMemoFiles(song_folder); + if (not memo_files.empty()) { + + } else { + throw std::invalid_argument("No valid file found in song folder"); + } + } +} + +const std::vector& Data::getMemoFiles(fs::path song_folder) { + std::vector res; + for (const auto& p : fs::directory_iterator(song_folder)) { + if (p.path().extension() == ".memo") { + res.push_back(p.path()); + } + } + return res; +} + +const std::vector& Data::getMemonFiles(fs::path song_folder) { + std::vector res; + for (const auto& p : fs::directory_iterator(song_folder)) { + if (p.path().extension() == ".memon") { + res.push_back(p.path()); + } + } + return res; +} diff --git a/src/Data/SongList.hpp b/src/Data/SongList.hpp new file mode 100644 index 0000000..181cc31 --- /dev/null +++ b/src/Data/SongList.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../Textures/CoverPack.hpp" + +namespace fs = std::filesystem; + +namespace Data { + + // Basic metadata about a song + struct Song { + Song() = default; + explicit Song(fs::path song_folder); + std::string title; + std::string artist; + // Path to the album cover + std::optional cover; + // Path the the audio file + std::optional audio; + // Mapping from chart difficulty (BSC, ADV, EXT ...) to the numeric level, + // the level is stored multiplied by 10 and displayed divided by 10 + // to allow for decimal levels (introduced in jubeat ... festo ?) + std::unordered_map chart_levels; + }; + + // Class holding all the necessary data to run the Music Select screen + class SongList { + public: + SongList(); + private: + Textures::CoverPack cover_previews; + std::vector songs; + }; + + // Returns the folders conscidered to contain a valid song + // classic memo files should have the .memo extension + // .memon files also accepted + const std::vector getSongFolders(); + std::list recursiveSongSearch(fs::path song_or_pack); + const std::vector& getMemoFiles(fs::path song_folder); + const std::vector& getMemonFiles(fs::path song_folder); +} diff --git a/src/Main.cpp b/src/Main.cpp index befca54..8005152 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -22,8 +22,8 @@ int main(int argc, char const *argv[]) { Screen::Gameplay gameplay(selected_chart); Score score = gameplay.play_chart(window); - Screen::Result result(score); - result.display(window); + Screen::Result result_screen(score); + result_screen.display(window); } diff --git a/src/Model/Chart.hpp b/src/Model/Chart.hpp deleted file mode 100644 index e68c52b..0000000 --- a/src/Model/Chart.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -class Chart { -public: - Chart(); -}; - -class ChartMetadata { -public: - ChartMetadata( - std::string title, - std::string artist - ); -private: - std::string title; - std::string artist; - -} \ No newline at end of file diff --git a/src/Model/MusicList.cpp b/src/Model/MusicList.cpp deleted file mode 100644 index 43bdfef..0000000 --- a/src/Model/MusicList.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "MusicList.hpp" - -MusicList::MusicList() \ No newline at end of file diff --git a/src/Model/MusicList.hpp b/src/Model/MusicList.hpp deleted file mode 100644 index 1d8de77..0000000 --- a/src/Model/MusicList.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#include "../Textures/JacketPack.hpp" - -class MusicList { -public: - MusicList(); -private: - Textures::JacketPack jacket_previews; -}; diff --git a/src/Model/Score.hpp b/src/Model/Score.hpp deleted file mode 100644 index a9af728..0000000 --- a/src/Model/Score.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -class Score { -public: - Score(); -}; \ No newline at end of file diff --git a/src/Screens/Gameplay.hpp b/src/Screens/Gameplay.hpp index cb86132..20c471b 100644 --- a/src/Screens/Gameplay.hpp +++ b/src/Screens/Gameplay.hpp @@ -1,14 +1,14 @@ #pragma once #include -#include "../Model/Chart.hpp" -#include "../Model/Score.hpp" +#include "../Data/Chart.hpp" +#include "../Data/Score.hpp" namespace Screen { class Gameplay { - const Chart& chart; + const Data::Chart& chart; public: - explicit Gameplay(const Chart& selected_chart); + explicit Gameplay(const Data::Chart& selected_chart); Score play_chart(sf::Window& window) const; }; }; diff --git a/src/Screens/MusicSelect.hpp b/src/Screens/MusicSelect.hpp index ade9fbf..b1152a7 100644 --- a/src/Screens/MusicSelect.hpp +++ b/src/Screens/MusicSelect.hpp @@ -1,16 +1,15 @@ #pragma once #include -#include "../Model/MusicList.hpp" -#include "../Model/Chart.hpp" +#include "../Data/SongList.hpp" +#include "../Data/Chart.hpp" namespace Screen { - /* - The music select screen is created only once and loads a cache of available songs - in the music_list attribute - */ + + // The music select screen is created only once and loads a cache of available songs + // in the music_list attribute class MusicSelect { - MusicList music_list; + Data::SongList music_list; public: MusicSelect() = default; Chart& select_chart(sf::Window& window) const; diff --git a/src/Textures/CoverPack.cpp b/src/Textures/CoverPack.cpp new file mode 100644 index 0000000..553799b --- /dev/null +++ b/src/Textures/CoverPack.cpp @@ -0,0 +1,48 @@ +#include "CoverPack.hpp" + +#include +#include +#include + +sf::Sprite Textures::CoverPack::at(const std::filesystem::path& path) const{ + auto location = get_detailed_location(locations.at(path)); + sf::IntRect rect(location.column, location.row, 128, 128); + sf::Sprite cover; + cover.setTexture(textures.at(location.texture_index)->getTexture()); + cover.setTextureRect(rect); + return cover; +} + +void Textures::CoverPack::emplace_back(const std::filesystem::path& cover) { + + sf::Texture cover_texture; + if (!cover_texture.loadFromFile(cover)) { + std::stringstream ss; + ss << "Unable to load cover image : " << cover; + throw std::invalid_argument(ss.str()); + } else { + locations[cover] = next_location; + } + auto size = cover_texture.getSize(); + auto location = get_detailed_location(next_location); + sf::Sprite new_cover; + new_cover.setTexture(cover_texture); + new_cover.setScale(128.0f/size.x, 128.0f/size.y); + new_cover.setPosition(128.0f * location.column, 128.0f * location.row); + if (location.column == 0 and location.row == 0) { + // first cover means it's a new texture + textures.push_back(std::make_shared()); + textures.back()->create(1024, 1024); + } + textures.at(location.texture_index)->draw(new_cover); + textures.at(location.texture_index)->display(); + next_location++; +} + +Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) { + return {location / 64, (location % 64) / 8, location % 8}; +} + +const std::vector>& Textures::CoverPack::getTextures() const { + return textures; +} diff --git a/src/Textures/CoverPack.hpp b/src/Textures/CoverPack.hpp new file mode 100644 index 0000000..f877c86 --- /dev/null +++ b/src/Textures/CoverPack.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +// Define the way we hash std::filesystem::path for use in unordered maps +namespace std { + template <> + struct hash { + std::size_t operator()(const std::filesystem::path& p) const { + return std::hash()(p.string()); + } + }; +}; + +namespace Textures { + + /* + A CoverPack stores 128x128 cover previews in a vector of 1024x1024 textures + */ + class CoverPack { + public: + CoverPack() = default; + sf::Sprite at(const std::filesystem::path& path) const; + void emplace_back(const std::filesystem::path& cover); + const std::vector>& getTextures() const; + private: + std::unordered_map locations; + std::vector> textures; + unsigned int next_location = 0; + }; + + struct DetailedLocation { + unsigned int texture_index; + unsigned int row; + unsigned int column; + }; + + DetailedLocation get_detailed_location(unsigned int location); +}; diff --git a/src/Textures/JacketPack.cpp b/src/Textures/JacketPack.cpp deleted file mode 100644 index f47f824..0000000 --- a/src/Textures/JacketPack.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "JacketPack.hpp" - -#include -#include -#include - -sf::Sprite Textures::JacketPack::operator[](const std::string& path) const{ - auto location = get_detailed_location(locations.at(path)); - sf::IntRect rect(location.column, location.row, 128, 128); - sf::Sprite jacket; - jacket.setTexture(textures.at(location.texture_index)->getTexture()); - jacket.setTextureRect(rect); - return jacket; -} - -void Textures::JacketPack::push_back(const std::filesystem::path& jacket) { - - sf::Texture jacket_texture; - if (!jacket_texture.loadFromFile(jacket)) { - std::stringstream ss; - ss << "Unable to load jacket image : " << jacket; - throw std::runtime_error(ss.str()); - } else { - locations[jacket] = next_location; - } - auto size = jacket_texture.getSize(); - auto location = get_detailed_location(next_location); - sf::Sprite new_jacket; - new_jacket.setTexture(jacket_texture); - new_jacket.setScale(128.0f/size.x, 128.0f/size.y); - new_jacket.setPosition(128.0f * location.column, 128.0f * location.row); - if (location.column == 0 and location.row == 0) { - // first jacket means it's a new texture - textures.push_back(std::make_shared()); - textures.back()->create(1024, 1024); - } - textures.at(location.texture_index)->draw(new_jacket); - textures.at(location.texture_index)->display(); - next_location++; -} - -Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) { - return {location / 64, (location % 64) / 8, location % 8}; -} diff --git a/src/Textures/JacketPack.hpp b/src/Textures/JacketPack.hpp deleted file mode 100644 index 540f382..0000000 --- a/src/Textures/JacketPack.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace Textures { - - /* - A JacketPack stores 128x128 jacket previews in 1024x1024 textures - */ - class JacketPack { - public: - JacketPack() = default; - sf::Sprite operator[](const std::string& path) const; - void push_back(const std::filesystem::path& jacket); - //private: - std::unordered_map locations; - std::vector> textures; - unsigned int next_location = 0; - }; - - struct DetailedLocation { - unsigned int texture_index; - unsigned int row; - unsigned int column; - }; - - DetailedLocation get_detailed_location(unsigned int location); -}