From f0a6b0d974b5a7811a78d6c04db9abcddff4fcd7 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 23 Nov 2025 19:08:35 +0100 Subject: [PATCH] Added settings.ini --- CMakeLists.txt | 1 + settings.ini | 15 ++++ src/audio/Audio.cpp | 1 + src/audio/Audio.h | 2 + src/core/Settings.cpp | 112 ++++++++++++++++++++++++ src/core/Settings.h | 49 +++++++++++ src/graphics/renderers/GameRenderer.cpp | 51 ++++++----- src/main.cpp | 34 ++++++- src/states/OptionsState.cpp | 19 ++++ 9 files changed, 258 insertions(+), 26 deletions(-) create mode 100644 settings.ini create mode 100644 src/core/Settings.cpp create mode 100644 src/core/Settings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 46e4b36..18b9efc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(tetris src/core/input/InputManager.cpp src/core/assets/AssetManager.cpp src/core/GlobalState.cpp + src/core/Settings.cpp src/graphics/renderers/RenderManager.cpp src/persistence/Scores.cpp src/graphics/effects/Starfield.cpp diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..e96b861 --- /dev/null +++ b/settings.ini @@ -0,0 +1,15 @@ +; Tetris Game Settings +; This file is auto-generated + +[Display] +Fullscreen=1 + +[Audio] +Music=1 +Sound=0 + +[Player] +Name=Player + +[Debug] +Enabled=0 diff --git a/src/audio/Audio.cpp b/src/audio/Audio.cpp index e530762..ca666d0 100644 --- a/src/audio/Audio.cpp +++ b/src/audio/Audio.cpp @@ -85,6 +85,7 @@ void Audio::start(){ } void Audio::toggleMute(){ muted=!muted; } +void Audio::setMuted(bool m){ muted=m; } void Audio::nextTrack(){ if(tracks.empty()) { current = -1; return; } diff --git a/src/audio/Audio.h b/src/audio/Audio.h index 2d4b032..0541d03 100644 --- a/src/audio/Audio.h +++ b/src/audio/Audio.h @@ -43,6 +43,8 @@ public: void shuffle(); // randomize order void start(); // begin playback void toggleMute(); + void setMuted(bool m); + bool isMuted() const { return muted; } // Menu music support void setMenuTrack(const std::string& path); diff --git a/src/core/Settings.cpp b/src/core/Settings.cpp new file mode 100644 index 0000000..2ff17b3 --- /dev/null +++ b/src/core/Settings.cpp @@ -0,0 +1,112 @@ +#include "Settings.h" +#include +#include +#include + +// Singleton instance +Settings& Settings::instance() { + static Settings s_instance; + return s_instance; +} + +Settings::Settings() { + // Constructor - defaults already set in header +} + +std::string Settings::getSettingsPath() { + // Save settings.ini in the game's directory + return "settings.ini"; +} + +bool Settings::load() { + std::ifstream file(getSettingsPath()); + if (!file.is_open()) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Settings file not found, using defaults"); + return false; + } + + std::string line; + std::string currentSection; + + while (std::getline(file, line)) { + // Trim whitespace + size_t start = line.find_first_not_of(" \t\r\n"); + size_t end = line.find_last_not_of(" \t\r\n"); + if (start == std::string::npos) continue; // Empty line + line = line.substr(start, end - start + 1); + + // Skip comments + if (line[0] == ';' || line[0] == '#') continue; + + // Check for section header + if (line[0] == '[' && line[line.length() - 1] == ']') { + currentSection = line.substr(1, line.length() - 2); + continue; + } + + // Parse key=value + size_t equalsPos = line.find('='); + if (equalsPos == std::string::npos) continue; + + std::string key = line.substr(0, equalsPos); + std::string value = line.substr(equalsPos + 1); + + // Trim key and value + key.erase(key.find_last_not_of(" \t") + 1); + value.erase(0, value.find_first_not_of(" \t")); + + // Parse settings + if (currentSection == "Display") { + if (key == "Fullscreen") { + m_fullscreen = (value == "1" || value == "true" || value == "True"); + } + } else if (currentSection == "Audio") { + if (key == "Music") { + m_musicEnabled = (value == "1" || value == "true" || value == "True"); + } else if (key == "Sound") { + m_soundEnabled = (value == "1" || value == "true" || value == "True"); + } + } else if (currentSection == "Player") { + if (key == "Name") { + m_playerName = value; + } + } else if (currentSection == "Debug") { + if (key == "Enabled") { + m_debugEnabled = (value == "1" || value == "true" || value == "True"); + } + } + } + + file.close(); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Settings loaded from %s", getSettingsPath().c_str()); + return true; +} + +bool Settings::save() { + std::ofstream file(getSettingsPath()); + if (!file.is_open()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to save settings to %s", getSettingsPath().c_str()); + return false; + } + + // Write settings in INI format + file << "; Tetris Game Settings\n"; + file << "; This file is auto-generated\n\n"; + + file << "[Display]\n"; + file << "Fullscreen=" << (m_fullscreen ? "1" : "0") << "\n\n"; + + file << "[Audio]\n"; + file << "Music=" << (m_musicEnabled ? "1" : "0") << "\n"; + file << "Sound=" << (m_soundEnabled ? "1" : "0") << "\n\n"; + + file << "[Player]\n"; + file << "Name=" << m_playerName << "\n\n"; + + file << "[Debug]\n"; + file << "Enabled=" << (m_debugEnabled ? "1" : "0") << "\n"; + + file.close(); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Settings saved to %s", getSettingsPath().c_str()); + return true; +} diff --git a/src/core/Settings.h b/src/core/Settings.h new file mode 100644 index 0000000..ce78740 --- /dev/null +++ b/src/core/Settings.h @@ -0,0 +1,49 @@ +#pragma once +#include + +/** + * Settings - Persistent game settings manager + * Handles loading/saving settings to settings.ini + */ +class Settings { +public: + // Singleton access + static Settings& instance(); + + // Load settings from file (returns true if file existed) + bool load(); + + // Save settings to file + bool save(); + + // Settings accessors + bool isFullscreen() const { return m_fullscreen; } + void setFullscreen(bool value) { m_fullscreen = value; } + + bool isMusicEnabled() const { return m_musicEnabled; } + void setMusicEnabled(bool value) { m_musicEnabled = value; } + + bool isSoundEnabled() const { return m_soundEnabled; } + void setSoundEnabled(bool value) { m_soundEnabled = value; } + + bool isDebugEnabled() const { return m_debugEnabled; } + void setDebugEnabled(bool value) { m_debugEnabled = value; } + + const std::string& getPlayerName() const { return m_playerName; } + void setPlayerName(const std::string& name) { m_playerName = name; } + + // Get the settings file path + static std::string getSettingsPath(); + +private: + Settings(); // Private constructor for singleton + Settings(const Settings&) = delete; + Settings& operator=(const Settings&) = delete; + + // Settings values + bool m_fullscreen = false; + bool m_musicEnabled = true; + bool m_soundEnabled = true; + bool m_debugEnabled = false; + std::string m_playerName = "Player"; +}; diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index 992f5fe..50b0c54 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "../../core/Settings.h" // Color constants (copied from main.cpp) static const SDL_Color COLORS[] = { @@ -413,30 +414,32 @@ void GameRenderer::renderPlayingState( pixelFont->draw(renderer, scoreX, baseY + 290, timeStr, 0.9f, {255, 255, 255, 255}); // Debug: Gravity timing info - pixelFont->draw(renderer, scoreX, baseY + 330, "GRAVITY", 0.8f, {150, 150, 150, 255}); - double gravityMs = game->getGravityMs(); - double fallAcc = game->getFallAccumulator(); - - // Calculate effective gravity (accounting for soft drop) - bool isSoftDrop = game->isSoftDropping(); - double effectiveGravityMs = isSoftDrop ? (gravityMs / 2.0) : gravityMs; - double timeUntilDrop = std::max(0.0, effectiveGravityMs - fallAcc); - - char gravityStr[32]; - snprintf(gravityStr, sizeof(gravityStr), "%.0f ms%s", gravityMs, isSoftDrop ? " (SD)" : ""); - pixelFont->draw(renderer, scoreX, baseY + 350, gravityStr, 0.7f, {180, 180, 180, 255}); - - char dropStr[32]; - snprintf(dropStr, sizeof(dropStr), "Drop: %.0f", timeUntilDrop); - SDL_Color dropColor = isSoftDrop ? SDL_Color{255, 200, 100, 255} : SDL_Color{100, 255, 100, 255}; - pixelFont->draw(renderer, scoreX, baseY + 370, dropStr, 0.7f, dropColor); - - // Gravity HUD - char gms[64]; - double gms_val = game->getGravityMs(); - double gfps = gms_val > 0.0 ? (1000.0 / gms_val) : 0.0; - snprintf(gms, sizeof(gms), "GRAV: %.0f ms (%.2f fps)", gms_val, gfps); - pixelFont->draw(renderer, logicalW - 260, 10, gms, 0.9f, {200, 200, 220, 255}); + if (Settings::instance().isDebugEnabled()) { + pixelFont->draw(renderer, scoreX, baseY + 330, "GRAVITY", 0.8f, {150, 150, 150, 255}); + double gravityMs = game->getGravityMs(); + double fallAcc = game->getFallAccumulator(); + + // Calculate effective gravity (accounting for soft drop) + bool isSoftDrop = game->isSoftDropping(); + double effectiveGravityMs = isSoftDrop ? (gravityMs / 2.0) : gravityMs; + double timeUntilDrop = std::max(0.0, effectiveGravityMs - fallAcc); + + char gravityStr[32]; + snprintf(gravityStr, sizeof(gravityStr), "%.0f ms%s", gravityMs, isSoftDrop ? " (SD)" : ""); + pixelFont->draw(renderer, scoreX, baseY + 350, gravityStr, 0.7f, {180, 180, 180, 255}); + + char dropStr[32]; + snprintf(dropStr, sizeof(dropStr), "Drop: %.0f", timeUntilDrop); + SDL_Color dropColor = isSoftDrop ? SDL_Color{255, 200, 100, 255} : SDL_Color{100, 255, 100, 255}; + pixelFont->draw(renderer, scoreX, baseY + 370, dropStr, 0.7f, dropColor); + + // Gravity HUD (Top) + char gms[64]; + double gms_val = game->getGravityMs(); + double gfps = gms_val > 0.0 ? (1000.0 / gms_val) : 0.0; + snprintf(gms, sizeof(gms), "GRAV: %.0f ms (%.2f fps)", gms_val, gfps); + pixelFont->draw(renderer, logicalW - 260, 10, gms, 0.9f, {200, 200, 220, 255}); + } // Hold piece (if implemented) if (game->held().type < PIECE_COUNT) { diff --git a/src/main.cpp b/src/main.cpp index 846b21c..59b1f71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,7 @@ #include "utils/ImagePathResolver.h" #include "graphics/renderers/GameRenderer.h" #include "core/Config.h" +#include "core/Settings.h" // Debug logging removed: no-op in this build (previously LOG_DEBUG) @@ -534,6 +535,17 @@ int main(int, char **) // Initialize random seed for fireworks srand(static_cast(SDL_GetTicks())); + // Load settings + Settings::instance().load(); + + // Sync static variables with settings + musicEnabled = Settings::instance().isMusicEnabled(); + playerName = Settings::instance().getPlayerName(); + if (playerName.empty()) playerName = "Player"; + + // Apply sound settings to manager + SoundEffectManager::instance().setEnabled(Settings::instance().isSoundEnabled()); + int sdlInitRes = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); if (sdlInitRes < 0) { @@ -547,7 +559,13 @@ int main(int, char **) SDL_Quit(); return 1; } - SDL_Window *window = SDL_CreateWindow("Tetris (SDL3)", LOGICAL_W, LOGICAL_H, SDL_WINDOW_RESIZABLE); + + SDL_WindowFlags windowFlags = SDL_WINDOW_RESIZABLE; + if (Settings::instance().isFullscreen()) { + windowFlags |= SDL_WINDOW_FULLSCREEN; + } + + SDL_Window *window = SDL_CreateWindow("Tetris (SDL3)", LOGICAL_W, LOGICAL_H, windowFlags); if (!window) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed: %s", SDL_GetError()); @@ -711,7 +729,8 @@ int main(int, char **) AppState state = AppState::Loading; double loadingProgress = 0.0; Uint64 loadStart = SDL_GetTicks(); - bool running = true, isFullscreen = false; + bool running = true; + bool isFullscreen = Settings::instance().isFullscreen(); bool leftHeld = false, rightHeld = false; double moveTimerMs = 0; const double DAS = 170.0, ARR = 40.0; @@ -869,11 +888,13 @@ int main(int, char **) { Audio::instance().toggleMute(); musicEnabled = !musicEnabled; + Settings::instance().setMusicEnabled(musicEnabled); } if (e.key.scancode == SDL_SCANCODE_S) { // Toggle sound effects SoundEffectManager::instance().setEnabled(!SoundEffectManager::instance().isEnabled()); + Settings::instance().setSoundEnabled(SoundEffectManager::instance().isEnabled()); } if (e.key.scancode == SDL_SCANCODE_N) { @@ -884,6 +905,7 @@ int main(int, char **) { isFullscreen = !isFullscreen; SDL_SetWindowFullscreen(window, isFullscreen ? SDL_WINDOW_FULLSCREEN : 0); + Settings::instance().setFullscreen(isFullscreen); } } @@ -901,6 +923,7 @@ int main(int, char **) } else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) { if (playerName.empty()) playerName = "PLAYER"; scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName); + Settings::instance().setPlayerName(playerName); isNewHighScore = false; SDL_StopTextInput(window); } @@ -1169,6 +1192,9 @@ int main(int, char **) // Initialize audio system and start background loading on first frame if (!musicLoaded && currentTrackLoading == 0) { Audio::instance().init(); + // Apply audio settings + Audio::instance().setMuted(!Settings::instance().isMusicEnabled()); + // Note: SoundEffectManager doesn't have a global mute yet, but we can add it or handle it in playSound // Count actual music files first totalTracks = 0; @@ -1686,6 +1712,10 @@ int main(int, char **) SDL_DestroyTexture(blocksTex); if (logoSmallTex) SDL_DestroyTexture(logoSmallTex); + + // Save settings on exit + Settings::instance().save(); + lineEffect.shutdown(); Audio::instance().shutdown(); SoundEffectManager::instance().shutdown(); diff --git a/src/states/OptionsState.cpp b/src/states/OptionsState.cpp index 0b4a657..bcd0bf4 100644 --- a/src/states/OptionsState.cpp +++ b/src/states/OptionsState.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "../core/Settings.h" OptionsState::OptionsState(StateContext& ctx) : State(ctx) {} @@ -233,18 +234,36 @@ void OptionsState::toggleFullscreen() { if (ctx.fullscreenFlag) { *ctx.fullscreenFlag = nextState; } + // Save setting + Settings::instance().setFullscreen(nextState); + Settings::instance().save(); } void OptionsState::toggleMusic() { Audio::instance().toggleMute(); + // If muted, music is disabled. If not muted, music is enabled. + // Note: Audio::instance().isMuted() returns true if muted. + // But Audio class doesn't expose isMuted directly in header usually? + // Let's assume toggleMute toggles internal state. + // We can track it via ctx.musicEnabled if it's synced. + + bool enabled = true; if (ctx.musicEnabled) { *ctx.musicEnabled = !*ctx.musicEnabled; + enabled = *ctx.musicEnabled; } + + // Save setting + Settings::instance().setMusicEnabled(enabled); + Settings::instance().save(); } void OptionsState::toggleSoundFx() { bool next = !SoundEffectManager::instance().isEnabled(); SoundEffectManager::instance().setEnabled(next); + // Save setting + Settings::instance().setSoundEnabled(next); + Settings::instance().save(); } void OptionsState::exitToMenu() {