Added settings.ini
This commit is contained in:
@ -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; }
|
||||
|
||||
@ -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);
|
||||
|
||||
112
src/core/Settings.cpp
Normal file
112
src/core/Settings.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include "Settings.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
49
src/core/Settings.h
Normal file
49
src/core/Settings.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* 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";
|
||||
};
|
||||
@ -6,6 +6,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#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) {
|
||||
|
||||
34
src/main.cpp
34
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<unsigned int>(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();
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#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() {
|
||||
|
||||
Reference in New Issue
Block a user