diff --git a/src/core/Config.h b/src/core/Config.h index b8f02ab..538ba90 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -30,6 +30,9 @@ namespace Config { constexpr double ARR_RATE = 40.0; // Auto Repeat Rate in ms constexpr float LEVEL_FADE_DURATION = 3500.0f; // Level background fade time in ms constexpr int MAX_LEVELS = 20; // Maximum selectable starting level + + // Gravity speed multiplier: 1.0 = normal, 2.0 = 2x slower, 0.5 = 2x faster + constexpr double GRAVITY_SPEED_MULTIPLIER = 1; } // UI Layout constants diff --git a/src/core/application/ApplicationManager.cpp b/src/core/application/ApplicationManager.cpp index b8f8d32..fd282d0 100644 --- a/src/core/application/ApplicationManager.cpp +++ b/src/core/application/ApplicationManager.cpp @@ -512,6 +512,11 @@ bool ApplicationManager::initializeGame() { m_game = std::make_unique(m_startLevelSelection); // Wire up sound callbacks as main.cpp did if (m_game) { + // Apply global gravity speed multiplier from config + m_game->setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER); + // Reset to recalculate gravity with the new multiplier + m_game->reset(m_startLevelSelection); + m_game->setSoundCallback([&](int linesCleared){ SoundEffectManager::instance().playSound("clear_line", 1.0f); // voice lines handled via asset manager loaded sounds diff --git a/src/gameplay/core/Game.cpp b/src/gameplay/core/Game.cpp index b3433d2..9360ba9 100644 --- a/src/gameplay/core/Game.cpp +++ b/src/gameplay/core/Game.cpp @@ -56,11 +56,53 @@ void Game::reset(int startLevel_) { _score = 0; _lines = 0; _level = startLevel_; startLevel = startLevel_; // Initialize gravity using NES timing table (ms per cell by level) gravityMs = gravityMsForLevel(_level, gravityGlobalMultiplier); - fallAcc = 0; _elapsedSec = 0; gameOver=false; paused=false; + fallAcc = 0; gameOver=false; paused=false; + _startTime = SDL_GetPerformanceCounter(); + _pausedTime = 0; + _lastPauseStart = 0; hold = Piece{}; hold.type = PIECE_COUNT; canHold=true; refillBag(); spawn(); } +double Game::elapsed() const { + if (!_startTime) return 0.0; + + Uint64 currentTime = SDL_GetPerformanceCounter(); + Uint64 totalPausedTime = _pausedTime; + + // If currently paused, add time since pause started + if (paused && _lastPauseStart > 0) { + totalPausedTime += (currentTime - _lastPauseStart); + } + + Uint64 activeTime = currentTime - _startTime - totalPausedTime; + double seconds = (double)activeTime / (double)SDL_GetPerformanceFrequency(); + return seconds; +} + +void Game::updateElapsedTime() { + // This method is now just for API compatibility + // Actual elapsed time is calculated on-demand in elapsed() +} + +void Game::setPaused(bool p) { + if (p == paused) return; // No change + + if (p) { + // Pausing - record when pause started + _lastPauseStart = SDL_GetPerformanceCounter(); + } else { + // Unpausing - add elapsed pause time to total + if (_lastPauseStart > 0) { + Uint64 currentTime = SDL_GetPerformanceCounter(); + _pausedTime += (currentTime - _lastPauseStart); + _lastPauseStart = 0; + } + } + + paused = p; +} + void Game::refillBag() { bag.clear(); for (int i=0;i(i)); @@ -241,13 +283,15 @@ bool Game::tryMoveDown() { void Game::tickGravity(double frameMs) { if (paused) return; // Don't tick gravity when paused + // Soft drop: 20x faster for rapid continuous dropping + double effectiveGravityMs = softDropping ? (gravityMs / 5.0) : gravityMs; + fallAcc += frameMs; - while (fallAcc >= gravityMs) { + while (fallAcc >= effectiveGravityMs) { // Attempt to move down by one row if (tryMoveDown()) { // Award soft drop points only if player is actively holding Down - // JS: POINTS.SOFT_DROP = 1 per cell for soft drop if (softDropping) { _score += 1; } @@ -256,13 +300,14 @@ void Game::tickGravity(double frameMs) { lockPiece(); if (gameOver) break; } - fallAcc -= gravityMs; + fallAcc -= effectiveGravityMs; } } void Game::softDropBoost(double frameMs) { - // Reduce soft drop speed multiplier from 10.0 to 3.0 to make it less aggressive - if (!paused) fallAcc += frameMs * 3.0; + // This method is now deprecated - soft drop is handled in tickGravity + // Kept for API compatibility but does nothing + (void)frameMs; } void Game::hardDrop() { diff --git a/src/gameplay/core/Game.h b/src/gameplay/core/Game.h index 1424f15..07a8aeb 100644 --- a/src/gameplay/core/Game.h +++ b/src/gameplay/core/Game.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "../../core/GravityManager.h" enum PieceType { I, O, T, S, Z, J, L, PIECE_COUNT }; @@ -40,13 +41,14 @@ public: bool canHoldPiece() const { return canHold; } bool isGameOver() const { return gameOver; } bool isPaused() const { return paused; } - void setPaused(bool p) { paused = p; } + void setPaused(bool p); int score() const { return _score; } int lines() const { return _lines; } int level() const { return _level; } int startLevelBase() const { return startLevel; } - double elapsed() const { return _elapsedSec; } - void addElapsed(double frameMs) { if (!paused) _elapsedSec += frameMs/1000.0; } + double elapsed() const; // Now calculated from start time + void updateElapsedTime(); // Update elapsed time from system clock + bool isSoftDropping() const { return softDropping; } // Block statistics const std::array& getBlockCounts() const { return blockCounts; } @@ -69,6 +71,7 @@ public: void setGravityGlobalMultiplier(double m) { gravityGlobalMultiplier = m; } double getGravityGlobalMultiplier() const; double getGravityMs() const; + double getFallAccumulator() const { return fallAcc; } // Debug: time accumulated toward next drop void setLevelGravityMultiplier(int level, double m); private: @@ -85,7 +88,9 @@ private: int _level{1}; double gravityMs{800.0}; double fallAcc{0.0}; - double _elapsedSec{0.0}; + Uint64 _startTime{0}; // Performance counter at game start + Uint64 _pausedTime{0}; // Time spent paused (in performance counter ticks) + Uint64 _lastPauseStart{0}; // When the current pause started bool gameOver{false}; int startLevel{0}; bool softDropping{false}; // true while player holds Down key diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index 560fa2a..992f5fe 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -412,6 +412,25 @@ void GameRenderer::renderPlayingState( snprintf(timeStr, sizeof(timeStr), "%02d:%02d", mins, secs); 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(); diff --git a/src/main.cpp b/src/main.cpp index 25475ac..846b21c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,6 +33,7 @@ #include "audio/MenuWrappers.h" #include "utils/ImagePathResolver.h" #include "graphics/renderers/GameRenderer.h" +#include "core/Config.h" // Debug logging removed: no-op in this build (previously LOG_DEBUG) @@ -629,6 +630,9 @@ int main(int, char **) } Game game(startLevelSelection); + // Apply global gravity speed multiplier from config + game.setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER); + game.reset(startLevelSelection); // Initialize sound effects system SoundEffectManager::instance().init(); @@ -713,7 +717,7 @@ int main(int, char **) const double DAS = 170.0, ARR = 40.0; SDL_Rect logicalVP{0, 0, LOGICAL_W, LOGICAL_H}; float logicalScale = 1.f; - Uint64 lastMs = SDL_GetTicks(); + Uint64 lastMs = SDL_GetPerformanceCounter(); bool musicStarted = false; bool musicLoaded = false; int currentTrackLoading = 0; @@ -1084,9 +1088,12 @@ int main(int, char **) } // --- Timing --- - Uint64 now = SDL_GetTicks(); - double frameMs = double(now - lastMs); + Uint64 now = SDL_GetPerformanceCounter(); + double frameMs = double(now - lastMs) * 1000.0 / double(SDL_GetPerformanceFrequency()); lastMs = now; + + // Cap frame time to avoid spiral of death (max 100ms) + if (frameMs > 100.0) frameMs = 100.0; const bool *ks = SDL_GetKeyboardState(nullptr); bool left = state == AppState::Playing && ks[SDL_SCANCODE_LEFT]; bool right = state == AppState::Playing && ks[SDL_SCANCODE_RIGHT]; @@ -1132,7 +1139,7 @@ int main(int, char **) { if (!game.isPaused()) { game.tickGravity(frameMs); - game.addElapsed(frameMs); + game.updateElapsedTime(); // Update line effect and clear lines when animation completes if (lineEffect.isActive()) { diff --git a/src/states/PlayingState.cpp b/src/states/PlayingState.cpp index 7838221..37ef13e 100644 --- a/src/states/PlayingState.cpp +++ b/src/states/PlayingState.cpp @@ -128,16 +128,10 @@ void PlayingState::handleEvent(const SDL_Event& e) { void PlayingState::update(double frameMs) { if (!ctx.game) return; - static bool debugPrinted = false; - if (!debugPrinted) { - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[PLAYING] Starting updates, frameMs=%.2f, paused=%d", frameMs, ctx.game->isPaused()); - debugPrinted = true; - } - - // forward per-frame gameplay updates (gravity, elapsed) + // forward per-frame gameplay updates (gravity, line effects) if (!ctx.game->isPaused()) { ctx.game->tickGravity(frameMs); - ctx.game->addElapsed(frameMs); + ctx.game->updateElapsedTime(); if (ctx.lineEffect && ctx.lineEffect->isActive()) { if (ctx.lineEffect->update(frameMs / 1000.0f)) {