#include "ApplicationManager.h" #include "../state/StateManager.h" #include "../input/InputManager.h" #include "../interfaces/IAudioSystem.h" #include "../interfaces/IRenderer.h" #include "../interfaces/IAssetLoader.h" #include "../interfaces/IInputHandler.h" #include #include "../../audio/Audio.h" #include "../../audio/SoundEffect.h" #include "../../persistence/Scores.h" #include "../../states/State.h" #include "../../states/LoadingState.h" #include "../../states/MenuState.h" #include "../../states/OptionsState.h" #include "../../states/LevelSelectorState.h" #include "../../states/PlayingState.h" #include "../assets/AssetManager.h" #include "../Config.h" #include "../GlobalState.h" #include "../../graphics/renderers/RenderManager.h" #include "../../graphics/ui/Font.h" #include "../../graphics/effects/Starfield3D.h" #include "../../graphics/effects/Starfield.h" #include "../../graphics/renderers/GameRenderer.h" #include "../../gameplay/core/Game.h" #include "../../gameplay/effects/LineEffect.h" #include #include #include #include "../../utils/ImagePathResolver.h" #include #include #include #include ApplicationManager::ApplicationManager() = default; static void traceFile(const char* msg) { std::ofstream f("tetris_trace.log", std::ios::app); if (f) f << msg << "\n"; } // Helper: extracted from inline lambda to avoid MSVC parsing issues with complex lambdas void ApplicationManager::renderLoading(ApplicationManager* app, RenderManager& renderer) { // Clear background first renderer.clear(0, 0, 0, 255); // Use 3D starfield for loading screen (full screen) if (app->m_starfield3D) { int winW_actual = 0, winH_actual = 0; if (app->m_renderManager) app->m_renderManager->getWindowSize(winW_actual, winH_actual); if (winW_actual > 0 && winH_actual > 0) app->m_starfield3D->resize(winW_actual, winH_actual); app->m_starfield3D->draw(renderer.getSDLRenderer()); } SDL_Rect logicalVP = {0,0,0,0}; float logicalScale = 1.0f; if (app->m_renderManager) { logicalVP = app->m_renderManager->getLogicalViewport(); logicalScale = app->m_renderManager->getLogicalScale(); } SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP); SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale); float contentOffsetX = 0.0f; float contentOffsetY = 0.0f; auto drawRectOriginal = [&](float x, float y, float w, float h, SDL_Color c) { SDL_SetRenderDrawColor(renderer.getSDLRenderer(), c.r, c.g, c.b, c.a); SDL_FRect fr; fr.x = x + contentOffsetX; fr.y = y + contentOffsetY; fr.w = w; fr.h = h; SDL_RenderFillRect(renderer.getSDLRenderer(), &fr); }; // Compute dynamic logical width/height based on the RenderManager's // computed viewport and scale so the loading UI sizes itself to the // actual content area rather than a hardcoded design size. float LOGICAL_W = static_cast(Config::Logical::WIDTH); float LOGICAL_H = static_cast(Config::Logical::HEIGHT); if (logicalScale > 0.0f && logicalVP.w > 0 && logicalVP.h > 0) { // logicalVP is in window pixels; divide by scale to get logical units LOGICAL_W = static_cast(logicalVP.w) / logicalScale; LOGICAL_H = static_cast(logicalVP.h) / logicalScale; } const bool isLimitedHeight = LOGICAL_H < 450.0f; SDL_Texture* logoTex = app->m_assetManager->getTexture("logo"); const float logoHeight = logoTex ? (isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f) : 0; const float loadingTextHeight = 20; const float barHeight = 20; const float barPaddingVertical = isLimitedHeight ? 15 : 35; const float percentTextHeight = 24; const float spacingBetweenElements = isLimitedHeight ? 5 : 15; const float totalContentHeight = logoHeight + (logoHeight > 0 ? spacingBetweenElements : 0) + loadingTextHeight + barPaddingVertical + barHeight + spacingBetweenElements + percentTextHeight; float currentY = (LOGICAL_H - totalContentHeight) / 2.0f; if (logoTex) { const int lw = 872, lh = 273; const float maxLogoWidth = std::min(LOGICAL_W * 0.9f, 600.0f); const float availableHeight = isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f; const float availableWidth = maxLogoWidth; const float scaleFactorWidth = availableWidth / static_cast(lw); const float scaleFactorHeight = availableHeight / static_cast(lh); const float scaleFactor = std::min(scaleFactorWidth, scaleFactorHeight); const float displayWidth = lw * scaleFactor; const float displayHeight = lh * scaleFactor; const float logoX = (LOGICAL_W - displayWidth) / 2.0f; SDL_FRect dst{logoX + contentOffsetX, currentY + contentOffsetY, displayWidth, displayHeight}; SDL_RenderTexture(renderer.getSDLRenderer(), logoTex, nullptr, &dst); currentY += displayHeight + spacingBetweenElements; } FontAtlas* pixelFont = (FontAtlas*)app->m_assetManager->getFont("pixel_font"); FontAtlas* fallbackFont = (FontAtlas*)app->m_assetManager->getFont("main_font"); FontAtlas* loadingFont = pixelFont ? pixelFont : fallbackFont; if (loadingFont) { const std::string loadingText = "LOADING"; int tW=0, tH=0; loadingFont->measure(loadingText, 1.0f, tW, tH); float textX = (LOGICAL_W - (float)tW) * 0.5f; loadingFont->draw(renderer.getSDLRenderer(), textX + contentOffsetX, currentY + contentOffsetY, loadingText, 1.0f, {255,204,0,255}); } currentY += loadingTextHeight + barPaddingVertical; const int barW = 400, barH = 20; const int bx = (LOGICAL_W - barW) / 2; float loadingProgress = app->m_assetManager->getLoadingProgress(); drawRectOriginal(bx - 3, currentY - 3, barW + 6, barH + 6, {68,68,80,255}); drawRectOriginal(bx, currentY, barW, barH, {34,34,34,255}); drawRectOriginal(bx, currentY, int(barW * loadingProgress), barH, {255,204,0,255}); currentY += barH + spacingBetweenElements; if (loadingFont) { int percentage = int(loadingProgress * 100); char percentText[16]; std::snprintf(percentText, sizeof(percentText), "%d%%", percentage); std::string pStr(percentText); int pW=0, pH=0; loadingFont->measure(pStr, 1.5f, pW, pH); float percentX = (LOGICAL_W - (float)pW) * 0.5f; loadingFont->draw(renderer.getSDLRenderer(), percentX + contentOffsetX, currentY + contentOffsetY, pStr, 1.5f, {255,204,0,255}); } SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr); SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f); } ApplicationManager::~ApplicationManager() { if (m_initialized) { shutdown(); } } bool ApplicationManager::initialize(int argc, char* argv[]) { if (m_initialized) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager already initialized"); return true; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initializing ApplicationManager..."); // Initialize GlobalState GlobalState::instance().initialize(); // Set initial logical dimensions GlobalState::instance().updateLogicalDimensions(m_windowWidth, m_windowHeight); // Initialize SDL first if (!initializeSDL()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL"); return false; } // Initialize managers if (!initializeManagers()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize managers"); cleanupSDL(); return false; } // Register services for dependency injection registerServices(); // Initialize game systems if (!initializeGame()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize game systems"); cleanupManagers(); cleanupSDL(); return false; } m_initialized = true; m_running = true; m_lastFrameTime = SDL_GetTicks(); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager initialized successfully"); return true; } void ApplicationManager::run() { if (!m_initialized) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager not initialized"); return; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting main application loop"); traceFile("Main loop starting"); while (m_running) { SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Main loop iteration start: m_running=%d", m_running ? 1 : 0); traceFile("Main loop iteration"); // Calculate delta time uint64_t currentTime = SDL_GetTicks(); float deltaTime = (currentTime - m_lastFrameTime) / 1000.0f; m_lastFrameTime = currentTime; // Limit delta time to prevent spiral of death if (deltaTime > Config::Performance::MIN_FRAME_TIME) { deltaTime = Config::Performance::MIN_FRAME_TIME; } // Main loop phases processEvents(); if (m_running) { update(deltaTime); traceFile("about to call render"); render(); } } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Main application loop ended"); } void ApplicationManager::shutdown() { if (!m_initialized) { return; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shutting down ApplicationManager..."); m_running = false; // Stop audio systems before tearing down SDL to avoid aborts/asserts Audio::instance().shutdown(); SoundEffectManager::instance().shutdown(); // Cleanup in reverse order of initialization cleanupManagers(); cleanupSDL(); // Shutdown GlobalState last GlobalState::instance().shutdown(); m_initialized = false; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager shutdown complete"); } bool ApplicationManager::initializeSDL() { // Initialize SDL subsystems int sdlResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); if (sdlResult < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed: %s", SDL_GetError()); return false; } // Initialize SDL_ttf int ttfResult = TTF_Init(); if (ttfResult < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TTF_Init failed: %s", SDL_GetError()); SDL_Quit(); return false; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL initialized successfully"); return true; } bool ApplicationManager::initializeManagers() { // Create and initialize RenderManager m_renderManager = std::make_unique(); if (!m_renderManager->initialize(m_windowWidth, m_windowHeight, m_windowTitle)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize RenderManager"); return false; } m_isFullscreen = m_renderManager->isFullscreen(); // Create InputManager m_inputManager = std::make_unique(); if (!m_inputManager) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create InputManager"); return false; } // Create and initialize AssetManager m_assetManager = std::make_unique(); if (!m_assetManager->initialize(m_renderManager->getSDLRenderer())) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize AssetManager"); return false; } // Ensure SoundEffectManager is initialized early so SFX loads work SoundEffectManager::instance().init(); // Create StateManager (will be enhanced in next steps) m_stateManager = std::make_unique(AppState::Loading); // Create and initialize starfields m_starfield3D = std::make_unique(); m_starfield3D->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 200); m_starfield = std::make_unique(); m_starfield->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 50); // Register InputManager handlers to forward events to StateManager so // state-specific event handlers receive SDL_Event objects just like main.cpp. if (m_inputManager && m_stateManager) { m_inputManager->registerKeyHandler([this](SDL_Scancode sc, bool pressed){ if (!m_stateManager) return; bool consume = false; // Global hotkeys (handled across all states) if (pressed) { // Toggle fullscreen on F, F11 or Alt+Enter (or Alt+KP_Enter) if (sc == SDL_SCANCODE_F || sc == SDL_SCANCODE_F11 || ((sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_RETURN2 || sc == SDL_SCANCODE_KP_ENTER) && (SDL_GetModState() & SDL_KMOD_ALT))) { if (m_renderManager) { bool fs = m_renderManager->isFullscreen(); m_renderManager->setFullscreen(!fs); m_isFullscreen = m_renderManager->isFullscreen(); } consume = true; } // M: Toggle/mute music; start playback if unmuting and not started yet if (!consume && sc == SDL_SCANCODE_M) { Audio::instance().toggleMute(); m_musicEnabled = !m_musicEnabled; if (m_musicEnabled && !m_musicStarted && Audio::instance().getLoadedTrackCount() > 0) { Audio::instance().shuffle(); Audio::instance().start(); m_musicStarted = true; } consume = true; } // N: Play a test sound effect if (!consume && sc == SDL_SCANCODE_N) { SoundEffectManager::instance().playSound("lets_go", 1.0f); consume = true; } } // Forward to current state unless consumed if (!consume) { SDL_Event ev{}; ev.type = pressed ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP; ev.key.scancode = sc; ev.key.repeat = 0; m_stateManager->handleEvent(ev); } }); m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){ if (!m_stateManager) return; SDL_Event ev{}; ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; ev.button.button = button; ev.button.x = int(x); ev.button.y = int(y); m_stateManager->handleEvent(ev); }); m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){ if (!m_stateManager) return; SDL_Event ev{}; ev.type = SDL_EVENT_MOUSE_MOTION; ev.motion.x = int(x); ev.motion.y = int(y); ev.motion.xrel = int(dx); ev.motion.yrel = int(dy); m_stateManager->handleEvent(ev); }); m_inputManager->registerWindowEventHandler([this](const SDL_WindowEvent& we){ // Handle window resize events for RenderManager if (we.type == SDL_EVENT_WINDOW_RESIZED && m_renderManager) { m_renderManager->handleWindowResize(we.data1, we.data2); // Update GlobalState logical dimensions when window resizes GlobalState::instance().updateLogicalDimensions(we.data1, we.data2); } // Forward all window events to StateManager if (!m_stateManager) return; SDL_Event ev{}; ev.type = we.type; ev.window = we; m_stateManager->handleEvent(ev); }); m_inputManager->registerQuitHandler([this](){ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[QUIT] InputManager quit handler invoked - setting running=false"); traceFile("ApplicationManager: quit handler -> m_running=false"); m_running = false; }); } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers initialized successfully"); return true; } void ApplicationManager::registerServices() { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registering services for dependency injection..."); // Register concrete implementations as interface singletons if (m_renderManager) { std::shared_ptr renderPtr(m_renderManager.get(), [](RenderManager*) { // Custom deleter that does nothing since the unique_ptr manages lifetime }); m_serviceContainer.registerSingleton(renderPtr); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IRenderer service"); } if (m_assetManager) { std::shared_ptr assetPtr(m_assetManager.get(), [](AssetManager*) { // Custom deleter that does nothing since the unique_ptr manages lifetime }); m_serviceContainer.registerSingleton(assetPtr); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAssetLoader service"); } if (m_inputManager) { std::shared_ptr inputPtr(m_inputManager.get(), [](InputManager*) { // Custom deleter that does nothing since the unique_ptr manages lifetime }); m_serviceContainer.registerSingleton(inputPtr); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IInputHandler service"); } // Register Audio system singleton auto& audioInstance = Audio::instance(); auto audioPtr = std::shared_ptr