1
0
mirror of synced 2024-11-12 01:40:47 +01:00

More Music Select Preparation

This commit is contained in:
Stepland 2019-11-02 01:24:45 +01:00
parent 6a4f704856
commit a111820c9d
20 changed files with 517 additions and 141 deletions

View File

@ -15,20 +15,23 @@ filesystem = cpp.find_library('stdc++fs')
sources = [
'src/Main.cpp',
'src/Data/Chart.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/MusicSelect/MusicSelect.hpp',
'src/Screens/MusicSelect/MusicSelect.cpp',
'src/Screens/Result.hpp',
'src/Resources/CoverAtlas.hpp',
'src/Resources/CoverAtlas.cpp',
]
executable(
'jujube',
'src/Test.cpp',
'src/Textures/CoverPack.hpp',
'src/Textures/CoverPack.cpp',
dependencies: [sfml, filesystem]
sources,
dependencies: [sfml, filesystem],
include_directories : include_directories('include')
)

54
src/Data/KeyMaping.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "KeyMaping.hpp"
PanelCoords toCoord(PanelEnum panel) {
auto num = static_cast<int>(panel);
return {num % 4, num / 4};
}
KeyMaping::KeyMaping() {
m_key_to_panel[sf::Keyboard::Num1] = PanelEnum::P1;
m_key_to_panel[sf::Keyboard::Num2] = PanelEnum::P2;
m_key_to_panel[sf::Keyboard::Num3] = PanelEnum::P3;
m_key_to_panel[sf::Keyboard::Num4] = PanelEnum::P4;
m_key_to_panel[sf::Keyboard::A] = PanelEnum::P5;
m_key_to_panel[sf::Keyboard::Z] = PanelEnum::P6;
m_key_to_panel[sf::Keyboard::E] = PanelEnum::P7;
m_key_to_panel[sf::Keyboard::R] = PanelEnum::P8;
m_key_to_panel[sf::Keyboard::Q] = PanelEnum::P9;
m_key_to_panel[sf::Keyboard::S] = PanelEnum::P10;
m_key_to_panel[sf::Keyboard::D] = PanelEnum::P11;
m_key_to_panel[sf::Keyboard::F] = PanelEnum::P12;
m_key_to_panel[sf::Keyboard::W] = PanelEnum::P13;
m_key_to_panel[sf::Keyboard::X] = PanelEnum::P14;
m_key_to_panel[sf::Keyboard::C] = PanelEnum::P15;
m_key_to_panel[sf::Keyboard::V] = PanelEnum::P16;
for (const auto& [key, panel] : m_key_to_panel) {
m_panel_to_key[panel] = key;
}
}
void KeyMaping::setPanelToKey(const PanelEnum& panel, const sf::Keyboard::Key& key) {
if (m_key_to_panel.find(key) != m_key_to_panel.end()) {
m_panel_to_key.erase(m_key_to_panel[key]);
m_key_to_panel.erase(key);
}
m_panel_to_key[panel] = key;
m_key_to_panel[key] = panel;
}
std::optional<PanelEnum> KeyMaping::key_to_panel(const sf::Keyboard::Key& key) {
try {
return m_key_to_panel.at(key);
} catch(const std::exception& e) {
return {};
}
}
std::optional<sf::Keyboard::Key> KeyMaping::panel_to_key(const PanelEnum& panel) {
try {
return m_panel_to_key.at(panel);
} catch(const std::exception& e) {
return {};
}
}

43
src/Data/KeyMaping.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <optional>
#include <unordered_map>
#include <SFML/Window.hpp>
enum class PanelEnum {
P1,
P2,
P3,
P4,
P5,
P6,
P7,
P8,
P9,
P10,
P11,
P12,
P13,
P14,
P15,
P16,
};
struct PanelCoords {
unsigned int x;
unsigned int y;
};
PanelCoords toCoord(PanelEnum panel);
class KeyMaping {
public:
KeyMaping();
void setPanelToKey(const PanelEnum& panel, const sf::Keyboard::Key& key);
std::optional<PanelEnum> key_to_panel(const sf::Keyboard::Key& key);
std::optional<sf::Keyboard::Key> panel_to_key(const PanelEnum& panel);
private:
std::unordered_map<sf::Keyboard::Key, PanelEnum> m_key_to_panel;
std::unordered_map<PanelEnum, sf::Keyboard::Key> m_panel_to_key;
};

View File

@ -1,9 +1,12 @@
#include "SongList.hpp"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <list>
#include <memon.hpp>
namespace fs = std::filesystem;
Data::SongList::SongList::SongList() {
@ -12,25 +15,12 @@ Data::SongList::SongList::SongList() {
for (const auto& folder : getSongFolders()) {
try {
songs.emplace_back(folder);
} catch (const std::invalid_argument& e) {
} catch (const std::exception& 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';
}
}
}
}
@ -82,13 +72,26 @@ Data::Song::Song(fs::path song_folder) {
if (memon_files.size() > 1) {
throw std::invalid_argument("Multiple .memon files");
} else {
stepland::memon m;
std::ifstream file(memon_files.back());
file >> m;
this->title = m.song_title;
this->artist = m.artist;
if (not m.album_cover_path.empty()) {
this->cover.emplace(m.album_cover_path);
}
if (not m.music_path.empty()) {
this->audio.emplace(m.music_path);
}
for (const auto& [difficulty, chart] : m.charts) {
this->chart_levels[difficulty] = chart.level;
}
}
} else {
// .memo ?
auto memo_files = getMemoFiles(song_folder);
if (not memo_files.empty()) {
throw std::exception("jujube does not support .memo files for now ...");
} else {
throw std::invalid_argument("No valid file found in song folder");
}

View File

@ -1,12 +1,13 @@
#pragma once
#include <filesystem>
#include <iterator>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "../Textures/CoverPack.hpp"
#include "../Resources/CoverAtlas.hpp"
namespace fs = std::filesystem;
@ -26,15 +27,17 @@ namespace Data {
// the level is stored multiplied by 10 and displayed divided by 10
// to allow for decimal levels (introduced in jubeat ... festo ?)
std::unordered_map<std::string, unsigned int> chart_levels;
static bool sort_by_title(const Data::Song& a, const Data::Song& b) {
return a.title < b.title;
}
};
// Class holding all the necessary data to run the Music Select screen
// Class holding all the necessary song data to run the Music Select screen
class SongList {
public:
SongList();
private:
Textures::CoverPack cover_previews;
std::vector<const Song> songs;
std::vector<Song> songs;
};
// Returns the folders conscidered to contain a valid song

View File

@ -1,10 +1,9 @@
#include <SFML/Graphics.hpp>
#include "Model/Chart.hpp"
#include "Model/MusicList.hpp"
#include "Model/Score.hpp"
#include "Data/Chart.hpp"
#include "Data/Score.hpp"
#include "Screens/MusicSelect.hpp"
#include "Screens/MusicSelect/MusicSelect.hpp"
#include "Screens/Gameplay.hpp"
#include "Screens/Result.hpp"
@ -13,7 +12,7 @@ int main(int argc, char const *argv[]) {
sf::RenderWindow window(sf::VideoMode(800,600), "jujube");
window.setVerticalSyncEnabled(true);
Screen::MusicSelect music_select;
MusicSelect::Screen music_select;
while (true) {

View File

@ -0,0 +1,93 @@
#include "CoverAtlas.hpp"
#include <algorithm>
#include <cassert>
#include <sstream>
namespace fs = std::filesystem;
Textures::CoverAltas::CoverAltas() :
path_to_index(),
index_to_path(),
textures()
{
this->emplace_back("assets/textures/fallback_cover.png");
}
sf::Sprite Textures::CoverAltas::at(const fs::path& path) const {
unsigned int index;
if (path_to_index.find(path) != path_to_index.end()) {
index = path_to_index.at(path);
} else {
index = 0;
}
auto location = get_detailed_location(path_to_index.at(path));
sf::IntRect rect(location.column*256, location.row*256, 256, 256);
sf::Sprite cover;
cover.setTexture(textures.at(location.texture_index)->getTexture());
cover.setTextureRect(rect);
return cover;
}
void Textures::CoverAltas::emplace_back(const fs::path& cover) {
if (path_to_index.find(cover) != path_to_index.end()) {
return;
}
sf::Texture cover_texture;
if (!cover_texture.loadFromFile(cover)) {
throw std::invalid_argument("Unable to load cover image : "+cover.string());
}
unsigned int next_index = next_available_index();
auto size = cover_texture.getSize();
auto location = get_detailed_location(next_index);
sf::Sprite new_cover;
new_cover.setTexture(cover_texture);
new_cover.setScale(256.0f/size.x, 256.0f/size.y);
new_cover.setPosition(256.0f * location.column, 256.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<sf::RenderTexture>());
textures.back()->create(1024, 1024);
}
textures.at(location.texture_index)->draw(new_cover);
textures.at(location.texture_index)->display();
path_to_index[cover] = next_index;
index_to_path[next_index] = cover;
}
void Textures::CoverAltas::efficient_reload(const std::vector<fs::path>& covers) {
// Remove the paths that are not present in covers
auto it = path_to_index.begin();
while (it != path_to_index.end()) {
if (std::find(covers.begin(), covers.end(), it->first) == covers.end()) {
index_to_path.erase(it->second);
it = path_to_index.erase(it);
} else {
++it;
}
}
// Add the covers that are not already in the altas
for (const auto& cover : covers) {
if (path_to_index.find(cover) == path_to_index.end()) {
this->emplace_back(cover);
}
}
}
Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) {
return {location / 16, (location % 16) / 4, location % 4};
}
unsigned int Textures::CoverAltas::next_available_index() {
unsigned int index = 0;
while (index_to_path.find(index) != index_to_path.end()) {
++index;
}
return index;
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <filesystem>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <SFML/Graphics.hpp>
namespace fs = std::filesystem;
// Define the way we hash fs::path for use in unordered maps
namespace std {
template <>
struct hash<fs::path> {
std::size_t operator()(const fs::path& p) const {
return std::hash<std::string>()(p.string());
}
};
};
namespace Textures {
/*
A CoverAltas stores 256x256 cover previews in a vector of 1024x1024 textures
*/
class CoverAltas {
public:
CoverAltas();
// returns the sprite corresponding to the path
// returns a fallback texture if the path is unknown
sf::Sprite at(const fs::path& path) const;
void emplace_back(const fs::path& cover);
// only loads the images not already present in the atlas
// empties the slots not present in the vector
void efficient_reload(const std::vector<fs::path>& covers);
private:
std::unordered_map<fs::path, unsigned int> path_to_index;
std::unordered_map<unsigned int, fs::path> index_to_path;
std::vector<std::shared_ptr<sf::RenderTexture>> textures;
unsigned int next_available_index();
};
struct DetailedLocation {
unsigned int texture_index;
unsigned int row;
unsigned int column;
};
DetailedLocation get_detailed_location(unsigned int location);
};

View File

@ -1,17 +0,0 @@
#pragma once
#include <SFML/Window.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
class MusicSelect {
Data::SongList music_list;
public:
MusicSelect() = default;
Chart& select_chart(sf::Window& window) const;
};
};

View File

@ -0,0 +1,53 @@
#include "MusicSelect.hpp"
#include <iostream>
#include "../../Data/KeyMaping.hpp"
MusicSelect::Screen::Screen(const Data::SongList& t_song_list) :
song_list(t_song_list),
resources(),
state(t_song_list)
{
for (const auto& song : song_list.songs) {
if (song.cover) {
try {
resources.cover_previews.emplace_back(song.cover.value());
} catch(const std::exception& e) {
std::cerr << e.what() << '\n';
}
}
}
}
Chart& MusicSelect::Screen::select_chart(sf::Window& window) {
bool chart_selected = false;
while (not chart_selected) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::KeyPressed:
handle_key(event.key);
break;
default:
break;
}
}
}
}
void MusicSelect::Screen::handle_key(const sf::Event::KeyEvent& key_event) {
auto panel = key_mapping.key_to_panel(key_event.code);
if (panel) {
if (PanelEnum::P2 <= *panel and *panel <= PanelEnum::P12) {
auto coord = toCoord(*panel);
auto ribbon_size = state.ribbon.get_layout().size();
state
.ribbon
.get_layout()
.at((state.ribbon_position + coord.x) % ribbon_size)
.at(coord.y)
->click(state);
}
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <map>
#include <SFML/Window.hpp>
#include "../../Data/SongList.hpp"
#include "../../Data/Chart.hpp"
#include "../../Data/KeyMaping.hpp"
#include "State.hpp"
#include "Resources.hpp"
namespace MusicSelect {
// The music select screen is created only once
// it loads a cache of available songs in the song_list attribute
class Screen {
public:
Screen(const Data::SongList& t_song_list);
Chart& select_chart(sf::Window& window);
private:
const Data::SongList& song_list;
Resources resources;
State state;
KeyMaping key_mapping;
void handle_key(const sf::Event::KeyEvent& key_event);
};
};

View File

@ -0,0 +1,6 @@
#include "Panel.hpp"
void MusicSelect::CategoryPanel::display(State& state, sf::Window& window, sf::FloatRect area) {
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "State.hpp"
namespace MusicSelect {
// A Panel holds anything that can go under a panel on the moving part
// of the music select screen, be it nothing, a sort indicator, or a song
class Panel {
public:
// What happens when you click on the panel
virtual void click(State& state) = 0;
// How the panel should be displayed
virtual void display(State& state, sf::Window& window, sf::FloatRect area) = 0;
virtual ~Panel() = default;
};
class EmptyPanel final : public Panel {
public:
void click(State& state) override {return;};
void display(State& state, sf::Window& window, sf::FloatRect area) override {return;};
};
class CategoryPanel final : public Panel {
public:
CategoryPanel(const std::string& t_label) : label(t_label) {};
void click(State& state) override;
void display(State& state, sf::Window& window, sf::FloatRect area) override;
private:
std::string label;
};
class SongPanel final : public Panel {
public:
SongPanel(const Data::Song& t_song) : song(t_song) {};
void click(State& state) override;
void display(State& state, sf::Window& window, sf::FloatRect area) override;
private:
const Data::Song& song;
};
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <unordered_map>
#include <SFML/Graphics.hpp>
#include "../../Resources/CoverAtlas.hpp"
namespace MusicSelect {
// Holds the fonts, graphics and sounds needed by all of the MusicSelect Screen
struct Resources {
Textures::CoverAltas cover_previews;
std::unordered_map<std::string, sf::Font> fonts;
};
}

View File

@ -0,0 +1,44 @@
#include "Ribbon.hpp"
#include <map>
#include <vector>
#include "Panel.hpp"
MusicSelect::Ribbon MusicSelect::Ribbon::title_sort(const Data::SongList& song_list) {
std::map<char,std::vector<Data::Song>> categories;
for (const auto& song : song_list.songs) {
if (song.title.size > 0) {
char letter = song.title[0];
if ('A' <= letter and letter <= 'Z') {
categories[letter].push_back(song);
} else if ('a' <= letter and letter <= 'z') {
categories['A' + (letter - 'a')].push_back(song);
} else {
categories['?'].push_back(song);
}
} else {
categories['?'].push_back(song);
}
}
Ribbon ribbon;
for (const auto& [letter, songs] : categories) {
std::vector<std::unique_ptr<Panel>> panels = {std::make_unique<CategoryPanel>(letter)};
std::sort(songs.begin(), songs.end(), Data::Song::sort_by_title);
for (const auto& song : songs) {
panels.push_back(std::make_unique<SongPanel>(song));
}
while (panels.size % 3 != 0) {
panels.push_back(std::make_unique<EmptyPanel>());
}
for (size_t i = 0; i < panels.size; i += 3) {
std::array<std::unique_ptr<Panel>,3> column = {
std::move(panels[i]),
std::move(panels[i+1]),
std::move(panels[i+2])
};
ribbon.layout.push_back(column);
}
}
return ribbon;
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "Panel.hpp"
#include "../../Data/SongList.hpp"
namespace MusicSelect {
// The Ribbon is the moving part of the Music Select Screen
// It can be sorted in a number of ways
class Ribbon {
public:
Ribbon();
static Ribbon title_sort(const Data::SongList& song_list);
const auto& get_layout() {return layout;};
private:
std::vector<std::array<std::unique_ptr<Panel>,3>> layout;
};
}

View File

@ -0,0 +1,8 @@
#include "State.hpp"
#include <algorithm>
#include <map>
MusicSelect::State::State(const Data::SongList& song_list) {
ribbon = MusicSelect::Ribbon::title_sort(song_list);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <array>
#include <functional>
#include <optional>
#include <vector>
#include "../Data/SongList.hpp"
#include "Panel.hpp"
#include "Ribbon.hpp"
namespace MusicSelect {
// Holds everything related to the displaying state of the MusicSelect screen
struct State {
State(const Data::SongList& song_list);
// The ribbon is the moving part or the music select screen
Ribbon ribbon;
unsigned int ribbon_position;
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
};
}

View File

@ -1,48 +0,0 @@
#include "CoverPack.hpp"
#include <algorithm>
#include <cassert>
#include <sstream>
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<sf::RenderTexture>());
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<std::shared_ptr<sf::RenderTexture>>& Textures::CoverPack::getTextures() const {
return textures;
}

View File

@ -1,45 +0,0 @@
#pragma once
#include <filesystem>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <SFML/Graphics.hpp>
// Define the way we hash std::filesystem::path for use in unordered maps
namespace std {
template <>
struct hash<std::filesystem::path> {
std::size_t operator()(const std::filesystem::path& p) const {
return std::hash<std::string>()(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<std::shared_ptr<sf::RenderTexture>>& getTextures() const;
private:
std::unordered_map<std::filesystem::path, unsigned int> locations;
std::vector<std::shared_ptr<sf::RenderTexture>> 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);
};