Compare commits
11 Commits
5fd3febd8e
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 516aa16737 | |||
| 735e966608 | |||
| 68b35ea57b | |||
| 938988c876 | |||
| 03bdc82dc1 | |||
| 17cb64c9d4 | |||
| 6ef93e4c9c | |||
| e2dd768faf | |||
| 0b546ce25c | |||
| 45086e58d8 | |||
| b1f2033880 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -18,6 +18,7 @@
|
|||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
Makefile
|
Makefile
|
||||||
|
settings.ini
|
||||||
|
|
||||||
# vcpkg
|
# vcpkg
|
||||||
/vcpkg_installed/
|
/vcpkg_installed/
|
||||||
@ -70,7 +71,4 @@ dist_package/
|
|||||||
# Local environment files (if any)
|
# Local environment files (if any)
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Ignore local settings file
|
|
||||||
settings.ini
|
|
||||||
|
|
||||||
# End of .gitignore
|
# End of .gitignore
|
||||||
|
|||||||
@ -57,6 +57,8 @@ set(TETRIS_SOURCES
|
|||||||
src/graphics/renderers/SyncLineRenderer.cpp
|
src/graphics/renderers/SyncLineRenderer.cpp
|
||||||
src/graphics/renderers/UIRenderer.cpp
|
src/graphics/renderers/UIRenderer.cpp
|
||||||
src/audio/Audio.cpp
|
src/audio/Audio.cpp
|
||||||
|
src/audio/AudioManager.cpp
|
||||||
|
src/renderer/SDLRenderer.cpp
|
||||||
src/gameplay/effects/LineEffect.cpp
|
src/gameplay/effects/LineEffect.cpp
|
||||||
src/audio/SoundEffect.cpp
|
src/audio/SoundEffect.cpp
|
||||||
src/video/VideoPlayer.cpp
|
src/video/VideoPlayer.cpp
|
||||||
@ -66,6 +68,7 @@ set(TETRIS_SOURCES
|
|||||||
src/app/Fireworks.cpp
|
src/app/Fireworks.cpp
|
||||||
src/app/AssetLoader.cpp
|
src/app/AssetLoader.cpp
|
||||||
src/app/TextureLoader.cpp
|
src/app/TextureLoader.cpp
|
||||||
|
src/resources/ResourceManager.cpp
|
||||||
src/states/LoadingManager.cpp
|
src/states/LoadingManager.cpp
|
||||||
# State implementations (new)
|
# State implementations (new)
|
||||||
src/states/LoadingState.cpp
|
src/states/LoadingState.cpp
|
||||||
@ -201,6 +204,20 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
|||||||
target_include_directories(spacetris_tests PRIVATE "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
target_include_directories(spacetris_tests PRIVATE "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# GoogleTest-based board unit tests
|
||||||
|
find_package(GTest CONFIG REQUIRED)
|
||||||
|
add_executable(test_board
|
||||||
|
tests/test_board.cpp
|
||||||
|
src/logic/Board.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_board PRIVATE ${CMAKE_SOURCE_DIR}/src)
|
||||||
|
target_link_libraries(test_board PRIVATE GTest::gtest_main)
|
||||||
|
add_test(NAME BoardTests COMMAND test_board)
|
||||||
|
|
||||||
|
if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||||
|
target_include_directories(test_board PRIVATE "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Add new src subfolders to include path so old #includes continue to work
|
# Add new src subfolders to include path so old #includes continue to work
|
||||||
target_include_directories(spacetris PRIVATE
|
target_include_directories(spacetris PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
#include "app/AssetLoader.h"
|
#include "app/AssetLoader.h"
|
||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include "app/TextureLoader.h"
|
||||||
|
|
||||||
|
#include "utils/ImagePathResolver.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
AssetLoader::AssetLoader() = default;
|
AssetLoader::AssetLoader() = default;
|
||||||
|
|
||||||
@ -37,6 +41,10 @@ void AssetLoader::shutdown() {
|
|||||||
m_renderer = nullptr;
|
m_renderer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetLoader::setResourceManager(resources::ResourceManager* mgr) {
|
||||||
|
m_resourceManager = mgr;
|
||||||
|
}
|
||||||
|
|
||||||
void AssetLoader::setBasePath(const std::string& basePath) {
|
void AssetLoader::setBasePath(const std::string& basePath) {
|
||||||
m_basePath = basePath;
|
m_basePath = basePath;
|
||||||
}
|
}
|
||||||
@ -65,24 +73,25 @@ bool AssetLoader::performStep() {
|
|||||||
|
|
||||||
std::string fullPath = m_basePath.empty() ? path : (m_basePath + "/" + path);
|
std::string fullPath = m_basePath.empty() ? path : (m_basePath + "/" + path);
|
||||||
|
|
||||||
SDL_Surface* surf = IMG_Load(fullPath.c_str());
|
// Diagnostic: resolve path and check file existence
|
||||||
if (!surf) {
|
const std::string resolved = AssetPath::resolveImagePath(path);
|
||||||
std::lock_guard<std::mutex> lk(m_errorsMutex);
|
bool exists = false;
|
||||||
m_errors.push_back(std::string("IMG_Load failed: ") + fullPath + " -> " + SDL_GetError());
|
try { if (!resolved.empty()) exists = std::filesystem::exists(std::filesystem::u8path(resolved)); } catch (...) { exists = false; }
|
||||||
|
|
||||||
|
// Use TextureLoader to centralize loading and ResourceManager caching
|
||||||
|
TextureLoader loader(m_loadedTasks, m_currentLoading, m_currentLoadingMutex, m_errors, m_errorsMutex);
|
||||||
|
loader.setResourceManager(m_resourceManager);
|
||||||
|
// Pass the original queued path (not the full resolved path) so caching keys stay consistent
|
||||||
|
SDL_Texture* tex = loader.loadFromImage(m_renderer, path);
|
||||||
|
if (!tex) {
|
||||||
|
// errors have been recorded by TextureLoader
|
||||||
} else {
|
} else {
|
||||||
SDL_Texture* tex = SDL_CreateTextureFromSurface(m_renderer, surf);
|
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
||||||
SDL_DestroySurface(surf);
|
auto& slot = m_textures[path];
|
||||||
if (!tex) {
|
if (slot && slot != tex) {
|
||||||
std::lock_guard<std::mutex> lk(m_errorsMutex);
|
SDL_DestroyTexture(slot);
|
||||||
m_errors.push_back(std::string("CreateTexture failed: ") + fullPath);
|
|
||||||
} else {
|
|
||||||
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
|
||||||
auto& slot = m_textures[path];
|
|
||||||
if (slot && slot != tex) {
|
|
||||||
SDL_DestroyTexture(slot);
|
|
||||||
}
|
|
||||||
slot = tex;
|
|
||||||
}
|
}
|
||||||
|
slot = tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_loadedTasks.fetch_add(1, std::memory_order_relaxed);
|
m_loadedTasks.fetch_add(1, std::memory_order_relaxed);
|
||||||
@ -104,12 +113,17 @@ void AssetLoader::adoptTexture(const std::string& path, SDL_Texture* texture) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// register in local map and resource manager
|
||||||
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
||||||
auto& slot = m_textures[path];
|
auto& slot = m_textures[path];
|
||||||
if (slot && slot != texture) {
|
if (slot && slot != texture) {
|
||||||
SDL_DestroyTexture(slot);
|
SDL_DestroyTexture(slot);
|
||||||
}
|
}
|
||||||
slot = texture;
|
slot = texture;
|
||||||
|
if (m_resourceManager) {
|
||||||
|
std::shared_ptr<void> sp(texture, [](void* t){ SDL_DestroyTexture(static_cast<SDL_Texture*>(t)); });
|
||||||
|
m_resourceManager->put(path, sp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float AssetLoader::getProgress() const {
|
float AssetLoader::getProgress() const {
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "../resources/ResourceManager.h"
|
||||||
|
|
||||||
// Lightweight AssetLoader scaffold.
|
// Lightweight AssetLoader scaffold.
|
||||||
// Responsibilities:
|
// Responsibilities:
|
||||||
@ -22,6 +23,7 @@ public:
|
|||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
void setBasePath(const std::string& basePath);
|
void setBasePath(const std::string& basePath);
|
||||||
|
void setResourceManager(resources::ResourceManager* mgr);
|
||||||
|
|
||||||
// Queue a texture path (relative to base path) for loading.
|
// Queue a texture path (relative to base path) for loading.
|
||||||
void queueTexture(const std::string& path);
|
void queueTexture(const std::string& path);
|
||||||
@ -49,6 +51,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
SDL_Renderer* m_renderer = nullptr;
|
SDL_Renderer* m_renderer = nullptr;
|
||||||
std::string m_basePath;
|
std::string m_basePath;
|
||||||
|
resources::ResourceManager* m_resourceManager = nullptr;
|
||||||
|
|
||||||
// queued paths (simple FIFO)
|
// queued paths (simple FIFO)
|
||||||
std::vector<std::string> m_queue;
|
std::vector<std::string> m_queue;
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
#include "audio/Audio.h"
|
#include "audio/Audio.h"
|
||||||
#include "audio/MenuWrappers.h"
|
#include "audio/MenuWrappers.h"
|
||||||
#include "audio/SoundEffect.h"
|
#include "audio/SoundEffect.h"
|
||||||
|
#include "audio/AudioManager.h"
|
||||||
|
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "core/Settings.h"
|
#include "core/Settings.h"
|
||||||
@ -68,6 +69,7 @@
|
|||||||
#include "ui/MenuLayout.h"
|
#include "ui/MenuLayout.h"
|
||||||
|
|
||||||
#include "utils/ImagePathResolver.h"
|
#include "utils/ImagePathResolver.h"
|
||||||
|
#include "../resources/ResourceManager.h"
|
||||||
|
|
||||||
// ---------- Game config ----------
|
// ---------- Game config ----------
|
||||||
static constexpr int LOGICAL_W = 1200;
|
static constexpr int LOGICAL_W = 1200;
|
||||||
@ -187,6 +189,7 @@ struct TetrisApp::Impl {
|
|||||||
AssetLoader assetLoader;
|
AssetLoader assetLoader;
|
||||||
std::unique_ptr<LoadingManager> loadingManager;
|
std::unique_ptr<LoadingManager> loadingManager;
|
||||||
std::unique_ptr<TextureLoader> textureLoader;
|
std::unique_ptr<TextureLoader> textureLoader;
|
||||||
|
resources::ResourceManager resourceManager;
|
||||||
|
|
||||||
FontAtlas pixelFont;
|
FontAtlas pixelFont;
|
||||||
FontAtlas font;
|
FontAtlas font;
|
||||||
@ -427,6 +430,8 @@ int TetrisApp::Impl::init()
|
|||||||
|
|
||||||
// Asset loader (creates SDL_Textures on the main thread)
|
// Asset loader (creates SDL_Textures on the main thread)
|
||||||
assetLoader.init(renderer);
|
assetLoader.init(renderer);
|
||||||
|
// Wire resource manager into loader so textures are cached and reused
|
||||||
|
assetLoader.setResourceManager(&resourceManager);
|
||||||
loadingManager = std::make_unique<LoadingManager>(&assetLoader);
|
loadingManager = std::make_unique<LoadingManager>(&assetLoader);
|
||||||
|
|
||||||
// Legacy image loader (used only as a fallback when AssetLoader misses)
|
// Legacy image loader (used only as a fallback when AssetLoader misses)
|
||||||
@ -436,6 +441,8 @@ int TetrisApp::Impl::init()
|
|||||||
currentLoadingMutex,
|
currentLoadingMutex,
|
||||||
assetLoadErrors,
|
assetLoadErrors,
|
||||||
assetLoadErrorsMutex);
|
assetLoadErrorsMutex);
|
||||||
|
// Let legacy TextureLoader access the same resource cache
|
||||||
|
textureLoader->setResourceManager(&resourceManager);
|
||||||
|
|
||||||
// Load scores asynchronously but keep the worker alive until shutdown
|
// Load scores asynchronously but keep the worker alive until shutdown
|
||||||
scoreLoader = std::jthread([this]() {
|
scoreLoader = std::jthread([this]() {
|
||||||
@ -841,17 +848,19 @@ void TetrisApp::Impl::runLoop()
|
|||||||
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
||||||
if (e.key.scancode == SDL_SCANCODE_M)
|
if (e.key.scancode == SDL_SCANCODE_M)
|
||||||
{
|
{
|
||||||
Audio::instance().toggleMute();
|
if (auto sys = AudioManager::get()) sys->toggleMute();
|
||||||
musicEnabled = !musicEnabled;
|
musicEnabled = !musicEnabled;
|
||||||
Settings::instance().setMusicEnabled(musicEnabled);
|
Settings::instance().setMusicEnabled(musicEnabled);
|
||||||
}
|
}
|
||||||
if (e.key.scancode == SDL_SCANCODE_N)
|
if (e.key.scancode == SDL_SCANCODE_N)
|
||||||
{
|
{
|
||||||
Audio::instance().skipToNextTrack();
|
if (auto sys = AudioManager::get()) {
|
||||||
if (!musicStarted && Audio::instance().getLoadedTrackCount() > 0) {
|
sys->skipToNextTrack();
|
||||||
musicStarted = true;
|
if (!musicStarted && sys->getLoadedTrackCount() > 0) {
|
||||||
musicEnabled = true;
|
musicStarted = true;
|
||||||
Settings::instance().setMusicEnabled(true);
|
musicEnabled = true;
|
||||||
|
Settings::instance().setMusicEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// K: Toggle sound effects (S is reserved for co-op movement)
|
// K: Toggle sound effects (S is reserved for co-op movement)
|
||||||
@ -1316,10 +1325,14 @@ void TetrisApp::Impl::runLoop()
|
|||||||
game->softDropBoost(frameMs);
|
game->softDropBoost(frameMs);
|
||||||
|
|
||||||
if (musicLoadingStarted && !musicLoaded) {
|
if (musicLoadingStarted && !musicLoaded) {
|
||||||
currentTrackLoading = Audio::instance().getLoadedTrackCount();
|
if (auto sys = AudioManager::get()) {
|
||||||
if (Audio::instance().isLoadingComplete() || (totalTracks > 0 && currentTrackLoading >= totalTracks)) {
|
currentTrackLoading = sys->getLoadedTrackCount();
|
||||||
Audio::instance().shuffle();
|
if (sys->isLoadingComplete() || (totalTracks > 0 && currentTrackLoading >= totalTracks)) {
|
||||||
musicLoaded = true;
|
sys->shuffle();
|
||||||
|
musicLoaded = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentTrackLoading = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1706,21 +1719,27 @@ void TetrisApp::Impl::runLoop()
|
|||||||
currentLoadingFile.clear();
|
currentLoadingFile.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Audio::instance().init();
|
if (auto sys = AudioManager::get()) {
|
||||||
totalTracks = 0;
|
sys->init();
|
||||||
for (int i = 1; i <= 100; ++i) {
|
totalTracks = 0;
|
||||||
char base[128];
|
for (int i = 1; i <= 100; ++i) {
|
||||||
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
char base[128];
|
||||||
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||||
if (path.empty()) break;
|
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||||
Audio::instance().addTrackAsync(path);
|
if (path.empty()) break;
|
||||||
totalTracks++;
|
sys->addTrackAsync(path);
|
||||||
}
|
totalTracks++;
|
||||||
totalLoadingTasks.store(baseTasks + totalTracks);
|
}
|
||||||
if (totalTracks > 0) {
|
totalLoadingTasks.store(baseTasks + totalTracks);
|
||||||
Audio::instance().startBackgroundLoading();
|
if (totalTracks > 0) {
|
||||||
musicLoadingStarted = true;
|
sys->startBackgroundLoading();
|
||||||
|
musicLoadingStarted = true;
|
||||||
|
} else {
|
||||||
|
musicLoaded = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
totalTracks = 0;
|
||||||
|
totalLoadingTasks.store(baseTasks + totalTracks);
|
||||||
musicLoaded = true;
|
musicLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1785,6 +1804,8 @@ void TetrisApp::Impl::runLoop()
|
|||||||
nextPanelTex = assetLoader.getTexture(Assets::NEXT_PANEL);
|
nextPanelTex = assetLoader.getTexture(Assets::NEXT_PANEL);
|
||||||
holdPanelTex = assetLoader.getTexture(Assets::HOLD_PANEL);
|
holdPanelTex = assetLoader.getTexture(Assets::HOLD_PANEL);
|
||||||
|
|
||||||
|
// texture retrieval diagnostics removed
|
||||||
|
|
||||||
auto ensureTextureSize = [&](SDL_Texture* tex, int& outW, int& outH) {
|
auto ensureTextureSize = [&](SDL_Texture* tex, int& outW, int& outH) {
|
||||||
if (!tex) return;
|
if (!tex) return;
|
||||||
if (outW > 0 && outH > 0) return;
|
if (outW > 0 && outH > 0) return;
|
||||||
@ -1871,10 +1892,20 @@ void TetrisApp::Impl::runLoop()
|
|||||||
if (totalTracks > 0) {
|
if (totalTracks > 0) {
|
||||||
musicProgress = musicLoaded ? 0.7 : std::min(0.7, (double)currentTrackLoading / totalTracks * 0.7);
|
musicProgress = musicLoaded ? 0.7 : std::min(0.7, (double)currentTrackLoading / totalTracks * 0.7);
|
||||||
} else {
|
} else {
|
||||||
if (Audio::instance().isLoadingComplete()) {
|
if (auto sys = AudioManager::get()) {
|
||||||
musicProgress = 0.7;
|
if (sys->isLoadingComplete()) {
|
||||||
} else if (Audio::instance().getLoadedTrackCount() > 0) {
|
musicProgress = 0.7;
|
||||||
musicProgress = 0.35;
|
} else if (sys->getLoadedTrackCount() > 0) {
|
||||||
|
musicProgress = 0.35;
|
||||||
|
} else {
|
||||||
|
Uint32 elapsedMs = SDL_GetTicks() - static_cast<Uint32>(loadStart);
|
||||||
|
if (elapsedMs > 1500) {
|
||||||
|
musicProgress = 0.7;
|
||||||
|
musicLoaded = true;
|
||||||
|
} else {
|
||||||
|
musicProgress = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Uint32 elapsedMs = SDL_GetTicks() - static_cast<Uint32>(loadStart);
|
Uint32 elapsedMs = SDL_GetTicks() - static_cast<Uint32>(loadStart);
|
||||||
if (elapsedMs > 1500) {
|
if (elapsedMs > 1500) {
|
||||||
@ -1916,7 +1947,7 @@ void TetrisApp::Impl::runLoop()
|
|||||||
menuTrackLoader = std::jthread([]() {
|
menuTrackLoader = std::jthread([]() {
|
||||||
std::string menuTrack = AssetPath::resolveWithExtensions("assets/music/Every Block You Take", { ".mp3" });
|
std::string menuTrack = AssetPath::resolveWithExtensions("assets/music/Every Block You Take", { ".mp3" });
|
||||||
if (!menuTrack.empty()) {
|
if (!menuTrack.empty()) {
|
||||||
Audio::instance().setMenuTrack(menuTrack);
|
if (auto sys = AudioManager::get()) sys->setMenuTrack(menuTrack);
|
||||||
} else {
|
} else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Menu track not found (Every Block You Take)");
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Menu track not found (Every Block You Take)");
|
||||||
}
|
}
|
||||||
@ -1925,9 +1956,9 @@ void TetrisApp::Impl::runLoop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == AppState::Menu) {
|
if (state == AppState::Menu) {
|
||||||
Audio::instance().playMenuMusic();
|
if (auto sys = AudioManager::get()) sys->playMenuMusic();
|
||||||
} else {
|
} else {
|
||||||
Audio::instance().playGameMusic();
|
if (auto sys = AudioManager::get()) sys->playGameMusic();
|
||||||
}
|
}
|
||||||
musicStarted = true;
|
musicStarted = true;
|
||||||
}
|
}
|
||||||
@ -1936,9 +1967,9 @@ void TetrisApp::Impl::runLoop()
|
|||||||
static AppState previousState = AppState::Loading;
|
static AppState previousState = AppState::Loading;
|
||||||
if (state != previousState && musicStarted) {
|
if (state != previousState && musicStarted) {
|
||||||
if (state == AppState::Menu && previousState == AppState::Playing) {
|
if (state == AppState::Menu && previousState == AppState::Playing) {
|
||||||
Audio::instance().playMenuMusic();
|
if (auto sys = AudioManager::get()) sys->playMenuMusic();
|
||||||
} else if (state == AppState::Playing && previousState == AppState::Menu) {
|
} else if (state == AppState::Playing && previousState == AppState::Menu) {
|
||||||
Audio::instance().playGameMusic();
|
if (auto sys = AudioManager::get()) sys->playGameMusic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previousState = state;
|
previousState = state;
|
||||||
@ -2712,7 +2743,7 @@ void TetrisApp::Impl::shutdown()
|
|||||||
}
|
}
|
||||||
|
|
||||||
lineEffect.shutdown();
|
lineEffect.shutdown();
|
||||||
Audio::instance().shutdown();
|
if (auto sys = AudioManager::get()) sys->shutdown();
|
||||||
SoundEffectManager::instance().shutdown();
|
SoundEffectManager::instance().shutdown();
|
||||||
|
|
||||||
// Destroy textures before tearing down the renderer/window.
|
// Destroy textures before tearing down the renderer/window.
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#include "utils/ImagePathResolver.h"
|
#include "utils/ImagePathResolver.h"
|
||||||
|
|
||||||
TextureLoader::TextureLoader(
|
TextureLoader::TextureLoader(
|
||||||
@ -45,6 +47,18 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
|
|||||||
const std::string resolvedPath = AssetPath::resolveImagePath(path);
|
const std::string resolvedPath = AssetPath::resolveImagePath(path);
|
||||||
setCurrentLoadingFile(resolvedPath.empty() ? path : resolvedPath);
|
setCurrentLoadingFile(resolvedPath.empty() ? path : resolvedPath);
|
||||||
|
|
||||||
|
// Check filesystem existence for diagnostics (no console log)
|
||||||
|
bool fileExists = false;
|
||||||
|
try { if (!resolvedPath.empty()) fileExists = std::filesystem::exists(std::filesystem::u8path(resolvedPath)); } catch (...) { fileExists = false; }
|
||||||
|
// If resource manager provided, check cache first using the original asset key (path)
|
||||||
|
if (resourceManager_) {
|
||||||
|
if (auto sp = resourceManager_->get<SDL_Texture>(path)) {
|
||||||
|
clearCurrentLoadingFile();
|
||||||
|
loadedTasks_.fetch_add(1);
|
||||||
|
return sp.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Surface* surface = IMG_Load(resolvedPath.c_str());
|
SDL_Surface* surface = IMG_Load(resolvedPath.c_str());
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
{
|
{
|
||||||
@ -54,7 +68,7 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
|
|||||||
}
|
}
|
||||||
loadedTasks_.fetch_add(1);
|
loadedTasks_.fetch_add(1);
|
||||||
clearCurrentLoadingFile();
|
clearCurrentLoadingFile();
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image %s (resolved: %s): %s", path.c_str(), resolvedPath.c_str(), SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image %s (resolved: %s) exists=%s: %s", path.c_str(), resolvedPath.c_str(), fileExists ? "yes" : "no", SDL_GetError());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +80,7 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||||
|
// surface size preserved in outW/outH; no console log
|
||||||
SDL_DestroySurface(surface);
|
SDL_DestroySurface(surface);
|
||||||
|
|
||||||
if (!texture) {
|
if (!texture) {
|
||||||
@ -80,6 +95,15 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No texture-size console diagnostics here
|
||||||
|
|
||||||
|
// cache in resource manager if present
|
||||||
|
if (resourceManager_) {
|
||||||
|
std::shared_ptr<void> sp(texture, [](void* t){ SDL_DestroyTexture(static_cast<SDL_Texture*>(t)); });
|
||||||
|
// store under original asset key (path) so callers using logical asset names find them
|
||||||
|
resourceManager_->put(path, sp);
|
||||||
|
}
|
||||||
|
|
||||||
loadedTasks_.fetch_add(1);
|
loadedTasks_.fetch_add(1);
|
||||||
clearCurrentLoadingFile();
|
clearCurrentLoadingFile();
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "../resources/ResourceManager.h"
|
||||||
|
|
||||||
class TextureLoader {
|
class TextureLoader {
|
||||||
public:
|
public:
|
||||||
@ -16,6 +17,8 @@ public:
|
|||||||
std::vector<std::string>& assetLoadErrors,
|
std::vector<std::string>& assetLoadErrors,
|
||||||
std::mutex& assetLoadErrorsMutex);
|
std::mutex& assetLoadErrorsMutex);
|
||||||
|
|
||||||
|
void setResourceManager(resources::ResourceManager* mgr) { resourceManager_ = mgr; }
|
||||||
|
|
||||||
SDL_Texture* loadFromImage(SDL_Renderer* renderer, const std::string& path, int* outW = nullptr, int* outH = nullptr);
|
SDL_Texture* loadFromImage(SDL_Renderer* renderer, const std::string& path, int* outW = nullptr, int* outH = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -28,4 +31,6 @@ private:
|
|||||||
void setCurrentLoadingFile(const std::string& filename);
|
void setCurrentLoadingFile(const std::string& filename);
|
||||||
void clearCurrentLoadingFile();
|
void clearCurrentLoadingFile();
|
||||||
void recordAssetLoadError(const std::string& message);
|
void recordAssetLoadError(const std::string& message);
|
||||||
|
|
||||||
|
resources::ResourceManager* resourceManager_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -118,6 +118,7 @@ static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int
|
|||||||
outCh = static_cast<int>(clientFormat.mChannelsPerFrame);
|
outCh = static_cast<int>(clientFormat.mChannelsPerFrame);
|
||||||
return !outPCM.empty();
|
return !outPCM.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
|
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
|
||||||
(void)outPCM; (void)outRate; (void)outCh; (void)path;
|
(void)outPCM; (void)outRate; (void)outCh; (void)path;
|
||||||
@ -184,6 +185,8 @@ void Audio::skipToNextTrack(){
|
|||||||
void Audio::toggleMute(){ muted=!muted; }
|
void Audio::toggleMute(){ muted=!muted; }
|
||||||
void Audio::setMuted(bool m){ muted=m; }
|
void Audio::setMuted(bool m){ muted=m; }
|
||||||
|
|
||||||
|
bool Audio::isMuted() const { return muted; }
|
||||||
|
|
||||||
void Audio::nextTrack(){
|
void Audio::nextTrack(){
|
||||||
if(tracks.empty()) { current = -1; return; }
|
if(tracks.empty()) { current = -1; return; }
|
||||||
// Try every track once to find a decodable one
|
// Try every track once to find a decodable one
|
||||||
|
|||||||
@ -32,29 +32,27 @@ public:
|
|||||||
void setSoundVolume(float volume) override;
|
void setSoundVolume(float volume) override;
|
||||||
bool isMusicPlaying() const override;
|
bool isMusicPlaying() const override;
|
||||||
|
|
||||||
// Existing Audio class methods
|
// Additional IAudioSystem methods (forwarded to concrete implementation)
|
||||||
bool init(); // initialize backend (MF on Windows)
|
bool init() override;
|
||||||
void addTrack(const std::string& path); // decode MP3 -> PCM16 stereo 44100
|
void shutdown() override;
|
||||||
void addTrackAsync(const std::string& path); // add track for background loading
|
void addTrack(const std::string& path) override;
|
||||||
void startBackgroundLoading(); // start background thread for loading
|
void addTrackAsync(const std::string& path) override;
|
||||||
void waitForLoadingComplete(); // wait for all tracks to finish loading
|
void startBackgroundLoading() override;
|
||||||
bool isLoadingComplete() const; // check if background loading is done
|
bool isLoadingComplete() const override;
|
||||||
int getLoadedTrackCount() const; // get number of tracks loaded so far
|
int getLoadedTrackCount() const override;
|
||||||
void shuffle(); // randomize order
|
void start() override;
|
||||||
void start(); // begin playback
|
void skipToNextTrack() override;
|
||||||
void skipToNextTrack(); // advance to the next music track
|
void shuffle() override;
|
||||||
void toggleMute();
|
void toggleMute() override;
|
||||||
|
bool isMuted() const override;
|
||||||
void setMuted(bool m);
|
void setMuted(bool m);
|
||||||
bool isMuted() const { return muted; }
|
void setMenuTrack(const std::string& path) override;
|
||||||
|
void playMenuMusic() override;
|
||||||
|
void playGameMusic() override;
|
||||||
|
void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume) override;
|
||||||
|
|
||||||
// Menu music support
|
// Existing Audio class helper methods
|
||||||
void setMenuTrack(const std::string& path);
|
void waitForLoadingComplete(); // wait for all tracks to finish loading
|
||||||
void playMenuMusic();
|
|
||||||
void playGameMusic();
|
|
||||||
|
|
||||||
// Queue a sound effect to mix over the music (pcm can be mono/stereo, any rate; will be converted)
|
|
||||||
void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume);
|
|
||||||
void shutdown();
|
|
||||||
private:
|
private:
|
||||||
Audio()=default; ~Audio()=default; Audio(const Audio&)=delete; Audio& operator=(const Audio&)=delete;
|
Audio()=default; ~Audio()=default; Audio(const Audio&)=delete; Audio& operator=(const Audio&)=delete;
|
||||||
static void SDLCALL streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total);
|
static void SDLCALL streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total);
|
||||||
|
|||||||
15
src/audio/AudioManager.cpp
Normal file
15
src/audio/AudioManager.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "AudioManager.h"
|
||||||
|
#include "Audio.h"
|
||||||
|
|
||||||
|
static IAudioSystem* g_audioSystem = nullptr;
|
||||||
|
|
||||||
|
IAudioSystem* AudioManager::get() {
|
||||||
|
if (!g_audioSystem) {
|
||||||
|
g_audioSystem = &Audio::instance();
|
||||||
|
}
|
||||||
|
return g_audioSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioManager::set(IAudioSystem* sys) {
|
||||||
|
g_audioSystem = sys;
|
||||||
|
}
|
||||||
11
src/audio/AudioManager.h
Normal file
11
src/audio/AudioManager.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../core/interfaces/IAudioSystem.h"
|
||||||
|
|
||||||
|
class AudioManager {
|
||||||
|
public:
|
||||||
|
// Get the currently registered audio system (may return Audio::instance())
|
||||||
|
static IAudioSystem* get();
|
||||||
|
// Replace the audio system (for tests or different backends)
|
||||||
|
static void set(IAudioSystem* sys);
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#include "SoundEffect.h"
|
#include "SoundEffect.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include "audio/Audio.h"
|
#include "audio/Audio.h"
|
||||||
|
#include "audio/AudioManager.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
@ -93,7 +94,9 @@ void SimpleAudioPlayer::playSound(const std::vector<int16_t>& pcmData, int chann
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Route through shared Audio mixer so SFX always play over music
|
// Route through shared Audio mixer so SFX always play over music
|
||||||
Audio::instance().playSfx(pcmData, channels, sampleRate, volume);
|
if (auto sys = AudioManager::get()) {
|
||||||
|
sys->playSfx(pcmData, channels, sampleRate, volume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SoundEffect::loadWAV(const std::string& filePath) {
|
bool SoundEffect::loadWAV(const std::string& filePath) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "../interfaces/IInputHandler.h"
|
#include "../interfaces/IInputHandler.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "../../audio/Audio.h"
|
#include "../../audio/Audio.h"
|
||||||
|
#include "../../audio/AudioManager.h"
|
||||||
#include "../../audio/SoundEffect.h"
|
#include "../../audio/SoundEffect.h"
|
||||||
#include "../../persistence/Scores.h"
|
#include "../../persistence/Scores.h"
|
||||||
#include "../../states/State.h"
|
#include "../../states/State.h"
|
||||||
@ -267,7 +268,7 @@ void ApplicationManager::shutdown() {
|
|||||||
m_running = false;
|
m_running = false;
|
||||||
|
|
||||||
// Stop audio systems before tearing down SDL to avoid aborts/asserts
|
// Stop audio systems before tearing down SDL to avoid aborts/asserts
|
||||||
Audio::instance().shutdown();
|
if (auto sys = AudioManager::get()) sys->shutdown();
|
||||||
SoundEffectManager::instance().shutdown();
|
SoundEffectManager::instance().shutdown();
|
||||||
|
|
||||||
// Cleanup in reverse order of initialization
|
// Cleanup in reverse order of initialization
|
||||||
@ -381,11 +382,11 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
|
|
||||||
// M: Toggle/mute music; start playback if unmuting and not started yet
|
// M: Toggle/mute music; start playback if unmuting and not started yet
|
||||||
if (!consume && sc == SDL_SCANCODE_M) {
|
if (!consume && sc == SDL_SCANCODE_M) {
|
||||||
Audio::instance().toggleMute();
|
if (auto sys = AudioManager::get()) sys->toggleMute();
|
||||||
m_musicEnabled = !m_musicEnabled;
|
m_musicEnabled = !m_musicEnabled;
|
||||||
if (m_musicEnabled && !m_musicStarted && Audio::instance().getLoadedTrackCount() > 0) {
|
if (m_musicEnabled && !m_musicStarted && AudioManager::get() && AudioManager::get()->getLoadedTrackCount() > 0) {
|
||||||
Audio::instance().shuffle();
|
AudioManager::get()->shuffle();
|
||||||
Audio::instance().start();
|
AudioManager::get()->start();
|
||||||
m_musicStarted = true;
|
m_musicStarted = true;
|
||||||
}
|
}
|
||||||
consume = true;
|
consume = true;
|
||||||
@ -393,11 +394,7 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
|
|
||||||
// N: Skip to next song in the playlist (or restart menu track)
|
// N: Skip to next song in the playlist (or restart menu track)
|
||||||
if (!consume && sc == SDL_SCANCODE_N) {
|
if (!consume && sc == SDL_SCANCODE_N) {
|
||||||
Audio::instance().skipToNextTrack();
|
if (auto sys = AudioManager::get()) { sys->skipToNextTrack(); if (!m_musicStarted && sys->getLoadedTrackCount() > 0) { m_musicStarted = true; m_musicEnabled = true; } }
|
||||||
if (!m_musicStarted && Audio::instance().getLoadedTrackCount() > 0) {
|
|
||||||
m_musicStarted = true;
|
|
||||||
m_musicEnabled = true;
|
|
||||||
}
|
|
||||||
consume = true;
|
consume = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,13 +512,13 @@ void ApplicationManager::registerServices() {
|
|||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IInputHandler service");
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IInputHandler service");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Audio system singleton
|
// Register Audio system singleton (via AudioManager)
|
||||||
auto& audioInstance = Audio::instance();
|
IAudioSystem* audioInstance = AudioManager::get();
|
||||||
auto audioPtr = std::shared_ptr<Audio>(&audioInstance, [](Audio*) {
|
if (audioInstance) {
|
||||||
// Custom deleter that does nothing since Audio is a singleton
|
std::shared_ptr<IAudioSystem> audioPtr(audioInstance, [](IAudioSystem*){});
|
||||||
});
|
m_serviceContainer.registerSingleton<IAudioSystem>(audioPtr);
|
||||||
m_serviceContainer.registerSingleton<IAudioSystem>(audioPtr);
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAudioSystem service");
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAudioSystem service");
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Service registration completed successfully");
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Service registration completed successfully");
|
||||||
}
|
}
|
||||||
@ -618,7 +615,7 @@ bool ApplicationManager::initializeGame() {
|
|||||||
// as lambdas that reference members here.
|
// as lambdas that reference members here.
|
||||||
|
|
||||||
// Start background music loading similar to main.cpp: Audio init + file discovery
|
// Start background music loading similar to main.cpp: Audio init + file discovery
|
||||||
Audio::instance().init();
|
if (auto sys = AudioManager::get()) sys->init();
|
||||||
// Discover available tracks (up to 100) and queue for background loading
|
// Discover available tracks (up to 100) and queue for background loading
|
||||||
m_totalTracks = 0;
|
m_totalTracks = 0;
|
||||||
std::vector<std::string> trackPaths;
|
std::vector<std::string> trackPaths;
|
||||||
@ -634,15 +631,15 @@ bool ApplicationManager::initializeGame() {
|
|||||||
}
|
}
|
||||||
m_totalTracks = static_cast<int>(trackPaths.size());
|
m_totalTracks = static_cast<int>(trackPaths.size());
|
||||||
for (const auto& path : trackPaths) {
|
for (const auto& path : trackPaths) {
|
||||||
Audio::instance().addTrackAsync(path);
|
if (auto sys = AudioManager::get()) sys->addTrackAsync(path);
|
||||||
}
|
}
|
||||||
if (m_totalTracks > 0) {
|
if (m_totalTracks > 0) {
|
||||||
Audio::instance().startBackgroundLoading();
|
if (auto sys = AudioManager::get()) sys->startBackgroundLoading();
|
||||||
// Kick off playback now; Audio will pick a track once decoded.
|
// Kick off playback now; Audio will pick a track once decoded.
|
||||||
// Do not mark as started yet; we'll flip the flag once a track is actually loaded.
|
// Do not mark as started yet; we'll flip the flag once a track is actually loaded.
|
||||||
if (m_musicEnabled) {
|
if (m_musicEnabled) {
|
||||||
Audio::instance().shuffle();
|
if (auto sys = AudioManager::get()) { sys->shuffle(); sys->start(); }
|
||||||
Audio::instance().start();
|
m_musicStarted = true;
|
||||||
}
|
}
|
||||||
m_currentTrackLoading = 1; // mark started
|
m_currentTrackLoading = 1; // mark started
|
||||||
}
|
}
|
||||||
@ -941,15 +938,15 @@ void ApplicationManager::setupStateHandlers() {
|
|||||||
// Start music as soon as at least one track has decoded (don’t wait for all)
|
// Start music as soon as at least one track has decoded (don’t wait for all)
|
||||||
// Start music as soon as at least one track has decoded (don't wait for all)
|
// Start music as soon as at least one track has decoded (don't wait for all)
|
||||||
if (m_musicEnabled && !m_musicStarted) {
|
if (m_musicEnabled && !m_musicStarted) {
|
||||||
if (Audio::instance().getLoadedTrackCount() > 0) {
|
if (auto sys = AudioManager::get()) {
|
||||||
Audio::instance().shuffle();
|
if (sys->getLoadedTrackCount() > 0) { sys->shuffle(); sys->start(); m_musicStarted = true; }
|
||||||
Audio::instance().start();
|
|
||||||
m_musicStarted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Track completion status for UI
|
// Track completion status for UI
|
||||||
if (!m_musicLoaded && Audio::instance().isLoadingComplete()) {
|
if (!m_musicLoaded) {
|
||||||
m_musicLoaded = true;
|
if (auto sys = AudioManager::get()) {
|
||||||
|
if (sys->isLoadingComplete()) m_musicLoaded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
#include "AssetManager.h"
|
#include "AssetManager.h"
|
||||||
#include "../../graphics/ui/Font.h"
|
#include "../../graphics/ui/Font.h"
|
||||||
#include "../../audio/Audio.h"
|
#include "../../audio/Audio.h"
|
||||||
|
#include "../../audio/AudioManager.h"
|
||||||
#include "../../audio/SoundEffect.h"
|
#include "../../audio/SoundEffect.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "../../utils/ImagePathResolver.h"
|
#include "../../utils/ImagePathResolver.h"
|
||||||
|
#include "../../core/Config.h"
|
||||||
|
#include "../../resources/AssetPaths.h"
|
||||||
|
|
||||||
AssetManager::AssetManager()
|
AssetManager::AssetManager()
|
||||||
: m_renderer(nullptr)
|
: m_renderer(nullptr)
|
||||||
@ -38,7 +41,7 @@ bool AssetManager::initialize(SDL_Renderer* renderer) {
|
|||||||
m_renderer = renderer;
|
m_renderer = renderer;
|
||||||
|
|
||||||
// Get references to singleton systems
|
// Get references to singleton systems
|
||||||
m_audioSystem = &Audio::instance();
|
m_audioSystem = AudioManager::get();
|
||||||
m_soundSystem = &SoundEffectManager::instance();
|
m_soundSystem = &SoundEffectManager::instance();
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
@ -103,7 +106,34 @@ SDL_Texture* AssetManager::loadTexture(const std::string& id, const std::string&
|
|||||||
|
|
||||||
SDL_Texture* AssetManager::getTexture(const std::string& id) const {
|
SDL_Texture* AssetManager::getTexture(const std::string& id) const {
|
||||||
auto it = m_textures.find(id);
|
auto it = m_textures.find(id);
|
||||||
return (it != m_textures.end()) ? it->second : nullptr;
|
if (it != m_textures.end()) return it->second;
|
||||||
|
|
||||||
|
// Lazy fallback: attempt to load well-known short ids from configured asset paths.
|
||||||
|
std::vector<std::string> candidates;
|
||||||
|
if (id == "logo") {
|
||||||
|
candidates.push_back(std::string(Assets::LOGO));
|
||||||
|
candidates.push_back(Config::Assets::LOGO_BMP);
|
||||||
|
} else if (id == "logo_small") {
|
||||||
|
candidates.push_back(Config::Assets::LOGO_SMALL_BMP);
|
||||||
|
candidates.push_back(std::string(Assets::LOGO));
|
||||||
|
} else if (id == "background") {
|
||||||
|
candidates.push_back(std::string(Assets::MAIN_SCREEN));
|
||||||
|
candidates.push_back(Config::Assets::BACKGROUND_BMP);
|
||||||
|
} else if (id == "blocks") {
|
||||||
|
candidates.push_back(std::string(Assets::BLOCKS_SPRITE));
|
||||||
|
candidates.push_back(Config::Assets::BLOCKS_BMP);
|
||||||
|
} else if (id == "asteroids") {
|
||||||
|
candidates.push_back(std::string(Assets::ASTEROID_SPRITE));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &candidatePath : candidates) {
|
||||||
|
if (candidatePath.empty()) continue;
|
||||||
|
AssetManager* self = const_cast<AssetManager*>(this);
|
||||||
|
SDL_Texture* tex = self->loadTexture(id, candidatePath);
|
||||||
|
if (tex) return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AssetManager::unloadTexture(const std::string& id) {
|
bool AssetManager::unloadTexture(const std::string& id) {
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "../interfaces/IAssetLoader.h"
|
#include "../interfaces/IAssetLoader.h"
|
||||||
#include "../interfaces/IAssetLoader.h"
|
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class FontAtlas;
|
class FontAtlas;
|
||||||
class Audio;
|
class Audio;
|
||||||
class SoundEffectManager;
|
class SoundEffectManager;
|
||||||
|
class IAudioSystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AssetManager - Centralized resource management following SOLID principles
|
* AssetManager - Centralized resource management following SOLID principles
|
||||||
@ -121,7 +121,7 @@ private:
|
|||||||
|
|
||||||
// System references
|
// System references
|
||||||
SDL_Renderer* m_renderer;
|
SDL_Renderer* m_renderer;
|
||||||
Audio* m_audioSystem; // Pointer to singleton
|
IAudioSystem* m_audioSystem; // Pointer to audio system (IAudioSystem)
|
||||||
SoundEffectManager* m_soundSystem; // Pointer to singleton
|
SoundEffectManager* m_soundSystem; // Pointer to singleton
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Abstract interface for audio system operations
|
* @brief Abstract interface for audio system operations
|
||||||
@ -52,4 +54,28 @@ public:
|
|||||||
* @return true if music is playing, false otherwise
|
* @return true if music is playing, false otherwise
|
||||||
*/
|
*/
|
||||||
virtual bool isMusicPlaying() const = 0;
|
virtual bool isMusicPlaying() const = 0;
|
||||||
|
|
||||||
|
// Extended control methods used by the application
|
||||||
|
virtual bool init() = 0;
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
virtual void addTrack(const std::string& path) = 0;
|
||||||
|
virtual void addTrackAsync(const std::string& path) = 0;
|
||||||
|
virtual void startBackgroundLoading() = 0;
|
||||||
|
virtual bool isLoadingComplete() const = 0;
|
||||||
|
virtual int getLoadedTrackCount() const = 0;
|
||||||
|
|
||||||
|
virtual void start() = 0;
|
||||||
|
virtual void skipToNextTrack() = 0;
|
||||||
|
virtual void shuffle() = 0;
|
||||||
|
|
||||||
|
virtual void toggleMute() = 0;
|
||||||
|
virtual bool isMuted() const = 0;
|
||||||
|
|
||||||
|
virtual void setMenuTrack(const std::string& path) = 0;
|
||||||
|
virtual void playMenuMusic() = 0;
|
||||||
|
virtual void playGameMusic() = 0;
|
||||||
|
|
||||||
|
// Low-level SFX path (raw PCM) used by internal SFX mixer
|
||||||
|
virtual void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume) = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "audio/Audio.h"
|
#include "audio/Audio.h"
|
||||||
|
#include "audio/AudioManager.h"
|
||||||
|
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
@ -266,6 +267,6 @@ void LineEffect::playLineClearSound(int lineCount) {
|
|||||||
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
||||||
if (sample && !sample->empty()) {
|
if (sample && !sample->empty()) {
|
||||||
// Mix via shared Audio device so it layers with music
|
// Mix via shared Audio device so it layers with music
|
||||||
Audio::instance().playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
if (auto sys = AudioManager::get()) sys->playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "audio/Audio.h"
|
#include "audio/Audio.h"
|
||||||
|
#include "audio/AudioManager.h"
|
||||||
#include "gameplay/core/Game.h"
|
#include "gameplay/core/Game.h"
|
||||||
|
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
@ -461,7 +462,7 @@ void LineEffect::playLineClearSound(int lineCount) {
|
|||||||
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
||||||
if (sample && !sample->empty()) {
|
if (sample && !sample->empty()) {
|
||||||
// Mix via shared Audio device so it layers with music
|
// Mix via shared Audio device so it layers with music
|
||||||
Audio::instance().playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
if (auto sys = AudioManager::get()) sys->playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "GameRenderer.h"
|
#include "GameRenderer.h"
|
||||||
|
#include "../../renderer/Renderer_iface.h"
|
||||||
|
|
||||||
#include "SyncLineRenderer.h"
|
#include "SyncLineRenderer.h"
|
||||||
#include "../../gameplay/core/Game.h"
|
#include "../../gameplay/core/Game.h"
|
||||||
@ -248,23 +249,25 @@ static void updateAndDrawTransport(SDL_Renderer* renderer, SDL_Texture* blocksTe
|
|||||||
Uint8 gridAlpha = static_cast<Uint8>(std::lround(255.0f * t));
|
Uint8 gridAlpha = static_cast<Uint8>(std::lround(255.0f * t));
|
||||||
Uint8 nextAlpha = gridAlpha; // fade new NEXT preview in at same rate as grid
|
Uint8 nextAlpha = gridAlpha; // fade new NEXT preview in at same rate as grid
|
||||||
|
|
||||||
// Draw preview fade-out
|
// Create renderer wrapper
|
||||||
if (previewAlpha > 0) {
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, previewAlpha);
|
// Draw preview fade-out
|
||||||
for (int cy = 0; cy < 4; ++cy) {
|
if (previewAlpha > 0) {
|
||||||
for (int cx = 0; cx < 4; ++cx) {
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, previewAlpha);
|
||||||
if (!Game::cellFilled(s_transport.piece, cx, cy)) continue;
|
for (int cy = 0; cy < 4; ++cy) {
|
||||||
float px = s_transport.startX + static_cast<float>(cx) * s_transport.tileSize;
|
for (int cx = 0; cx < 4; ++cx) {
|
||||||
float py = s_transport.startY + static_cast<float>(cy) * s_transport.tileSize;
|
if (!Game::cellFilled(s_transport.piece, cx, cy)) continue;
|
||||||
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, px, py, s_transport.tileSize, s_transport.piece.type);
|
float px = s_transport.startX + static_cast<float>(cx) * s_transport.tileSize;
|
||||||
|
float py = s_transport.startY + static_cast<float>(cy) * s_transport.tileSize;
|
||||||
|
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, px, py, s_transport.tileSize, s_transport.piece.type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
}
|
}
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw grid fade-in (same intensity as next preview fade-in)
|
// Draw grid fade-in (same intensity as next preview fade-in)
|
||||||
if (gridAlpha > 0) {
|
if (gridAlpha > 0) {
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, gridAlpha);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, gridAlpha);
|
||||||
for (int cy = 0; cy < 4; ++cy) {
|
for (int cy = 0; cy < 4; ++cy) {
|
||||||
for (int cx = 0; cx < 4; ++cx) {
|
for (int cx = 0; cx < 4; ++cx) {
|
||||||
if (!Game::cellFilled(s_transport.piece, cx, cy)) continue;
|
if (!Game::cellFilled(s_transport.piece, cx, cy)) continue;
|
||||||
@ -273,12 +276,12 @@ static void updateAndDrawTransport(SDL_Renderer* renderer, SDL_Texture* blocksTe
|
|||||||
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, gx, gy, s_transport.tileSize, s_transport.piece.type);
|
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, gx, gy, s_transport.tileSize, s_transport.piece.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw new NEXT preview fade-in (simultaneous)
|
// Draw new NEXT preview fade-in (simultaneous)
|
||||||
if (nextAlpha > 0) {
|
if (nextAlpha > 0) {
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, nextAlpha);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, nextAlpha);
|
||||||
for (int cy = 0; cy < 4; ++cy) {
|
for (int cy = 0; cy < 4; ++cy) {
|
||||||
for (int cx = 0; cx < 4; ++cx) {
|
for (int cx = 0; cx < 4; ++cx) {
|
||||||
if (!Game::cellFilled(s_transport.nextPiece, cx, cy)) continue;
|
if (!Game::cellFilled(s_transport.nextPiece, cx, cy)) continue;
|
||||||
@ -287,7 +290,7 @@ static void updateAndDrawTransport(SDL_Renderer* renderer, SDL_Texture* blocksTe
|
|||||||
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, nx, ny, s_transport.tileSize, s_transport.nextPiece.type);
|
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, nx, ny, s_transport.tileSize, s_transport.nextPiece.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t >= 1.0f) {
|
if (t >= 1.0f) {
|
||||||
@ -308,16 +311,18 @@ static const SDL_Color COLORS[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void GameRenderer::drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c) {
|
void GameRenderer::drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c) {
|
||||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->setDrawColor(c);
|
||||||
SDL_FRect fr{x, y, w, h};
|
SDL_FRect fr{x, y, w, h};
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawAsteroid(SDL_Renderer* renderer, SDL_Texture* asteroidTex, float x, float y, float size, const AsteroidCell& cell) {
|
static void drawAsteroid(SDL_Renderer* renderer, SDL_Texture* asteroidTex, float x, float y, float size, const AsteroidCell& cell) {
|
||||||
auto outlineGravity = [&](float inset, SDL_Color color) {
|
auto outlineGravity = [&](float inset, SDL_Color color) {
|
||||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->setDrawColor(color);
|
||||||
SDL_FRect glow{ x + inset, y + inset, size - inset * 2.0f, size - inset * 2.0f };
|
SDL_FRect glow{ x + inset, y + inset, size - inset * 2.0f, size - inset * 2.0f };
|
||||||
SDL_RenderRect(renderer, &glow);
|
rwrap->drawRectF(&glow);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (asteroidTex) {
|
if (asteroidTex) {
|
||||||
@ -330,9 +335,10 @@ static void drawAsteroid(SDL_Renderer* renderer, SDL_Texture* asteroidTex, float
|
|||||||
case AsteroidType::Core: col = 3; break;
|
case AsteroidType::Core: col = 3; break;
|
||||||
}
|
}
|
||||||
int row = std::clamp<int>(cell.visualState, 0, 2);
|
int row = std::clamp<int>(cell.visualState, 0, 2);
|
||||||
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
SDL_FRect src{ col * SPRITE_SIZE, row * SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE };
|
SDL_FRect src{ col * SPRITE_SIZE, row * SPRITE_SIZE, SPRITE_SIZE, SPRITE_SIZE };
|
||||||
SDL_FRect dst{ x, y, size, size };
|
SDL_FRect dst{ x, y, size, size };
|
||||||
SDL_RenderTexture(renderer, asteroidTex, &src, &dst);
|
rwrap->renderTexture(asteroidTex, &src, &dst);
|
||||||
|
|
||||||
if (cell.gravityEnabled) {
|
if (cell.gravityEnabled) {
|
||||||
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
||||||
@ -355,15 +361,16 @@ static void drawAsteroid(SDL_Renderer* renderer, SDL_Texture* asteroidTex, float
|
|||||||
static_cast<Uint8>(base.b * hpScale + 40 * (1.0f - hpScale)),
|
static_cast<Uint8>(base.b * hpScale + 40 * (1.0f - hpScale)),
|
||||||
255
|
255
|
||||||
};
|
};
|
||||||
SDL_SetRenderDrawColor(renderer, fill.r, fill.g, fill.b, fill.a);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->setDrawColor(fill);
|
||||||
SDL_FRect body{x, y, size - 1.0f, size - 1.0f};
|
SDL_FRect body{x, y, size - 1.0f, size - 1.0f};
|
||||||
SDL_RenderFillRect(renderer, &body);
|
rwrap->fillRectF(&body);
|
||||||
|
|
||||||
SDL_Color outline = base;
|
SDL_Color outline = base;
|
||||||
outline.a = 220;
|
outline.a = 220;
|
||||||
SDL_FRect border{x + 1.0f, y + 1.0f, size - 2.0f, size - 2.0f};
|
SDL_FRect border{x + 1.0f, y + 1.0f, size - 2.0f, size - 2.0f};
|
||||||
SDL_SetRenderDrawColor(renderer, outline.r, outline.g, outline.b, outline.a);
|
rwrap->setDrawColor(outline);
|
||||||
SDL_RenderRect(renderer, &border);
|
rwrap->drawRectF(&border);
|
||||||
if (cell.gravityEnabled) {
|
if (cell.gravityEnabled) {
|
||||||
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
outlineGravity(2.0f, SDL_Color{255, 230, 120, 180});
|
||||||
}
|
}
|
||||||
@ -387,7 +394,8 @@ void GameRenderer::drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksT
|
|||||||
|
|
||||||
SDL_FRect srcRect = {srcX, srcY, srcW, srcH};
|
SDL_FRect srcRect = {srcX, srcY, srcW, srcH};
|
||||||
SDL_FRect dstRect = {x, y, size, size};
|
SDL_FRect dstRect = {x, y, size, size};
|
||||||
SDL_RenderTexture(renderer, blocksTex, &srcRect, &dstRect);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->renderTexture(blocksTex, &srcRect, &dstRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost, float pixelOffsetX, float pixelOffsetY) {
|
void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost, float pixelOffsetX, float pixelOffsetY) {
|
||||||
@ -403,14 +411,17 @@ void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, con
|
|||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
// Draw ghost piece as barely visible gray outline
|
// Draw ghost piece as barely visible gray outline
|
||||||
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 20); // Very faint gray
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
// Draw ghost fill
|
||||||
|
SDL_Color ghostFill{180,180,180,20};
|
||||||
|
rwrap->setDrawColor(ghostFill);
|
||||||
SDL_FRect rect = {px + 2, py + 2, tileSize - 4, tileSize - 4};
|
SDL_FRect rect = {px + 2, py + 2, tileSize - 4, tileSize - 4};
|
||||||
SDL_RenderFillRect(renderer, &rect);
|
rwrap->fillRectF(&rect);
|
||||||
|
|
||||||
// Draw thin gray border
|
// Draw thin gray border
|
||||||
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 30);
|
SDL_Color ghostBorder{180,180,180,30};
|
||||||
|
rwrap->setDrawColor(ghostBorder);
|
||||||
SDL_FRect border = {px + 1, py + 1, tileSize - 2, tileSize - 2};
|
SDL_FRect border = {px + 1, py + 1, tileSize - 2, tileSize - 2};
|
||||||
SDL_RenderRect(renderer, &border);
|
rwrap->drawRectF(&border);
|
||||||
} else {
|
} else {
|
||||||
drawBlockTexture(renderer, blocksTex, px, py, tileSize, piece.type);
|
drawBlockTexture(renderer, blocksTex, px, py, tileSize, piece.type);
|
||||||
}
|
}
|
||||||
@ -426,6 +437,7 @@ void GameRenderer::drawBlockTexturePublic(SDL_Renderer* renderer, SDL_Texture* b
|
|||||||
|
|
||||||
void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) {
|
void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) {
|
||||||
if (pieceType >= PIECE_COUNT) return;
|
if (pieceType >= PIECE_COUNT) return;
|
||||||
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
|
||||||
// Use the first rotation (index 0) for preview
|
// Use the first rotation (index 0) for preview
|
||||||
Game::Piece previewPiece;
|
Game::Piece previewPiece;
|
||||||
@ -461,7 +473,7 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
|
|||||||
// Use semi-transparent alpha for preview blocks
|
// Use semi-transparent alpha for preview blocks
|
||||||
Uint8 previewAlpha = 180;
|
Uint8 previewAlpha = 180;
|
||||||
if (blocksTex) {
|
if (blocksTex) {
|
||||||
SDL_SetTextureAlphaMod(blocksTex, previewAlpha);
|
rwrap->setTextureAlphaMod(blocksTex, previewAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int cy = 0; cy < 4; ++cy) {
|
for (int cy = 0; cy < 4; ++cy) {
|
||||||
@ -476,7 +488,7 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
|
|||||||
|
|
||||||
// Reset alpha
|
// Reset alpha
|
||||||
if (blocksTex) {
|
if (blocksTex) {
|
||||||
SDL_SetTextureAlphaMod(blocksTex, 255);
|
rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,6 +508,8 @@ void GameRenderer::renderNextPanel(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
|
||||||
const SDL_Color gridBorderColor{60, 80, 160, 255}; // matches main grid outline
|
const SDL_Color gridBorderColor{60, 80, 160, 255}; // matches main grid outline
|
||||||
const SDL_Color bayColor{8, 12, 24, 235};
|
const SDL_Color bayColor{8, 12, 24, 235};
|
||||||
const SDL_Color bayOutline{25, 62, 86, 220};
|
const SDL_Color bayOutline{25, 62, 86, 220};
|
||||||
@ -505,25 +519,24 @@ void GameRenderer::renderNextPanel(
|
|||||||
// the panel rectangle and skip the custom background/frame drawing.
|
// the panel rectangle and skip the custom background/frame drawing.
|
||||||
if (nextPanelTex) {
|
if (nextPanelTex) {
|
||||||
SDL_FRect dst{panelX, panelY, panelW, panelH};
|
SDL_FRect dst{panelX, panelY, panelW, panelH};
|
||||||
SDL_RenderTexture(renderer, nextPanelTex, nullptr, &dst);
|
rwrap->renderTexture(nextPanelTex, nullptr, &dst);
|
||||||
// Draw the panel label over the texture — user requested visible label
|
|
||||||
const float labelPad = tileSize * 0.25f;
|
const float labelPad = tileSize * 0.25f;
|
||||||
pixelFont->draw(renderer, panelX + labelPad, panelY + labelPad * 0.5f, "NEXT", 0.9f, labelColor);
|
pixelFont->draw(renderer, panelX + labelPad, panelY + labelPad * 0.5f, "NEXT", 0.9f, labelColor);
|
||||||
} else {
|
} else {
|
||||||
SDL_FRect bayRect{panelX, panelY, panelW, panelH};
|
SDL_FRect bayRect{panelX, panelY, panelW, panelH};
|
||||||
SDL_SetRenderDrawColor(renderer, bayColor.r, bayColor.g, bayColor.b, bayColor.a);
|
rwrap->setDrawColor(bayColor);
|
||||||
SDL_RenderFillRect(renderer, &bayRect);
|
rwrap->fillRectF(&bayRect);
|
||||||
|
|
||||||
SDL_FRect thinOutline{panelX - 1.0f, panelY - 1.0f, panelW + 2.0f, panelH + 2.0f};
|
SDL_FRect thinOutline{panelX - 1.0f, panelY - 1.0f, panelW + 2.0f, panelH + 2.0f};
|
||||||
auto drawOutlineNoBottom = [&](const SDL_FRect& rect, SDL_Color color) {
|
auto drawOutlineNoBottom = [&](const SDL_FRect& rect, SDL_Color color) {
|
||||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
rwrap->setDrawColor(color);
|
||||||
const float left = rect.x;
|
const float left = rect.x;
|
||||||
const float top = rect.y;
|
const float top = rect.y;
|
||||||
const float right = rect.x + rect.w;
|
const float right = rect.x + rect.w;
|
||||||
const float bottom = rect.y + rect.h;
|
const float bottom = rect.y + rect.h;
|
||||||
SDL_RenderLine(renderer, left, top, right, top); // top edge
|
rwrap->renderLine(left, top, right, top); // top edge
|
||||||
SDL_RenderLine(renderer, left, top, left, bottom); // left edge
|
rwrap->renderLine(left, top, left, bottom); // left edge
|
||||||
SDL_RenderLine(renderer, right, top, right, bottom); // right edge
|
rwrap->renderLine(right, top, right, bottom); // right edge
|
||||||
};
|
};
|
||||||
|
|
||||||
drawOutlineNoBottom(thinOutline, gridBorderColor);
|
drawOutlineNoBottom(thinOutline, gridBorderColor);
|
||||||
@ -641,11 +654,12 @@ void GameRenderer::renderPlayingState(
|
|||||||
float contentOffsetX = (winW - contentW) * 0.5f / contentScale;
|
float contentOffsetX = (winW - contentW) * 0.5f / contentScale;
|
||||||
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
||||||
|
|
||||||
// Helper lambda for drawing rectangles with content offset
|
// Renderer wrapper and helper lambda for drawing rectangles with content offset
|
||||||
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
|
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
|
||||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
rwrap->setDrawColor(c);
|
||||||
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Responsive layout that scales with window size while maintaining margins
|
// Responsive layout that scales with window size while maintaining margins
|
||||||
@ -747,28 +761,28 @@ void GameRenderer::renderPlayingState(
|
|||||||
scaledW,
|
scaledW,
|
||||||
scaledH
|
scaledH
|
||||||
};
|
};
|
||||||
SDL_RenderTexture(renderer, statisticsPanelTex, nullptr, &dstF);
|
rwrap->renderTexture(statisticsPanelTex, nullptr, &dstF);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: render entire texture stretched to panel
|
// Fallback: render entire texture stretched to panel
|
||||||
SDL_RenderTexture(renderer, statisticsPanelTex, nullptr, &blocksPanelBg);
|
rwrap->renderTexture(statisticsPanelTex, nullptr, &blocksPanelBg);
|
||||||
}
|
}
|
||||||
} else if (scorePanelTex) {
|
} else if (scorePanelTex) {
|
||||||
SDL_RenderTexture(renderer, scorePanelTex, nullptr, &blocksPanelBg);
|
rwrap->renderTexture(scorePanelTex, nullptr, &blocksPanelBg);
|
||||||
} else {
|
} else {
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 18, 32, 205);
|
rwrap->setDrawColor(SDL_Color{12, 18, 32, 205});
|
||||||
SDL_RenderFillRect(renderer, &blocksPanelBg);
|
rwrap->fillRectF(&blocksPanelBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw grid lines
|
// Draw grid lines
|
||||||
SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255);
|
rwrap->setDrawColor(SDL_Color{40, 45, 60, 255});
|
||||||
for (int x = 1; x < Game::COLS; ++x) {
|
for (int x = 1; x < Game::COLS; ++x) {
|
||||||
float lineX = gridX + x * finalBlockSize;
|
float lineX = gridX + x * finalBlockSize;
|
||||||
SDL_RenderLine(renderer, lineX, gridY, lineX, gridY + GRID_H);
|
rwrap->renderLine(lineX, gridY, lineX, gridY + GRID_H);
|
||||||
}
|
}
|
||||||
for (int y = 1; y < Game::ROWS; ++y) {
|
for (int y = 1; y < Game::ROWS; ++y) {
|
||||||
float lineY = gridY + y * finalBlockSize;
|
float lineY = gridY + y * finalBlockSize;
|
||||||
SDL_RenderLine(renderer, gridX, lineY, gridX + GRID_W, lineY);
|
rwrap->renderLine(gridX, lineY, gridX + GRID_W, lineY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!s_starfieldInitialized) {
|
if (!s_starfieldInitialized) {
|
||||||
@ -817,6 +831,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
SDL_BlendMode oldBlend = SDL_BLENDMODE_NONE;
|
SDL_BlendMode oldBlend = SDL_BLENDMODE_NONE;
|
||||||
SDL_GetRenderDrawBlendMode(renderer, &oldBlend);
|
SDL_GetRenderDrawBlendMode(renderer, &oldBlend);
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
// rwrap already declared near function start; reuse it here.
|
||||||
// Add a small, smooth sub-pixel jitter to the starfield origin so the
|
// Add a small, smooth sub-pixel jitter to the starfield origin so the
|
||||||
// brightest star doesn't permanently sit exactly at the visual center.
|
// brightest star doesn't permanently sit exactly at the visual center.
|
||||||
{
|
{
|
||||||
@ -933,10 +948,10 @@ void GameRenderer::renderPlayingState(
|
|||||||
float pulse = 0.5f + 0.5f * std::sin(sp.pulse);
|
float pulse = 0.5f + 0.5f * std::sin(sp.pulse);
|
||||||
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio * pulse, 0.0f, 1.0f) * 255.0f);
|
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio * pulse, 0.0f, 1.0f) * 255.0f);
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetRenderDrawColor(renderer, sp.color.r, sp.color.g, sp.color.b, alpha);
|
rwrap->setDrawColor(SDL_Color{sp.color.r, sp.color.g, sp.color.b, alpha});
|
||||||
float half = sp.size * 0.5f;
|
float half = sp.size * 0.5f;
|
||||||
SDL_FRect fr{gridX + sp.x - half, gridY + sp.y - half, sp.size, sp.size};
|
SDL_FRect fr{gridX + sp.x - half, gridY + sp.y - half, sp.size, sp.size};
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
|
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@ -949,11 +964,11 @@ void GameRenderer::renderPlayingState(
|
|||||||
// Draw a small filled connector to visually merge NEXT panel and grid border
|
// Draw a small filled connector to visually merge NEXT panel and grid border
|
||||||
// If an external NEXT panel texture is used, skip the connector to avoid
|
// If an external NEXT panel texture is used, skip the connector to avoid
|
||||||
// drawing a visible seam under the image/artwork.
|
// drawing a visible seam under the image/artwork.
|
||||||
if (!nextPanelTex) {
|
if (!nextPanelTex) {
|
||||||
SDL_SetRenderDrawColor(renderer, 60, 80, 160, 255); // same as grid border
|
rwrap->setDrawColor(SDL_Color{60, 80, 160, 255}); // same as grid border
|
||||||
float connectorY = NEXT_PANEL_Y + NEXT_PANEL_HEIGHT; // bottom of next panel (near grid top)
|
float connectorY = NEXT_PANEL_Y + NEXT_PANEL_HEIGHT; // bottom of next panel (near grid top)
|
||||||
SDL_FRect connRect{ NEXT_PANEL_X, connectorY - 1.0f, NEXT_PANEL_WIDTH, 2.0f };
|
SDL_FRect connRect{ NEXT_PANEL_X, connectorY - 1.0f, NEXT_PANEL_WIDTH, 2.0f };
|
||||||
SDL_RenderFillRect(renderer, &connRect);
|
rwrap->fillRectF(&connRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw transport effect if active (renders the moving piece and trail)
|
// Draw transport effect if active (renders the moving piece and trail)
|
||||||
@ -1164,27 +1179,27 @@ void GameRenderer::renderPlayingState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asteroidsTex && spawnAlpha < 1.0f) {
|
if (asteroidsTex && spawnAlpha < 1.0f) {
|
||||||
SDL_SetTextureAlphaMod(asteroidsTex, static_cast<Uint8>(std::clamp(spawnAlpha, 0.0f, 1.0f) * 255.0f));
|
rwrap->setTextureAlphaMod(asteroidsTex, static_cast<Uint8>(std::clamp(spawnAlpha, 0.0f, 1.0f) * 255.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
float size = finalBlockSize * spawnScale * clearScale;
|
float size = finalBlockSize * spawnScale * clearScale;
|
||||||
float offset = (finalBlockSize - size) * 0.5f;
|
float offset = (finalBlockSize - size) * 0.5f;
|
||||||
if (asteroidsTex && clearAlpha < 1.0f) {
|
if (asteroidsTex && clearAlpha < 1.0f) {
|
||||||
Uint8 alpha = static_cast<Uint8>(std::clamp(spawnAlpha * clearAlpha, 0.0f, 1.0f) * 255.0f);
|
Uint8 alpha = static_cast<Uint8>(std::clamp(spawnAlpha * clearAlpha, 0.0f, 1.0f) * 255.0f);
|
||||||
SDL_SetTextureAlphaMod(asteroidsTex, alpha);
|
rwrap->setTextureAlphaMod(asteroidsTex, alpha);
|
||||||
}
|
}
|
||||||
drawAsteroid(renderer, asteroidsTex, bx + offset, by + offset, size, cell);
|
drawAsteroid(renderer, asteroidsTex, bx + offset, by + offset, size, cell);
|
||||||
|
|
||||||
if (asteroidsTex && (spawnAlpha < 1.0f || clearAlpha < 1.0f)) {
|
if (asteroidsTex && (spawnAlpha < 1.0f || clearAlpha < 1.0f)) {
|
||||||
SDL_SetTextureAlphaMod(asteroidsTex, 255);
|
rwrap->setTextureAlphaMod(asteroidsTex, 255);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (blocksTex && clearAlpha < 1.0f) {
|
if (blocksTex && clearAlpha < 1.0f) {
|
||||||
SDL_SetTextureAlphaMod(blocksTex, static_cast<Uint8>(std::clamp(clearAlpha, 0.0f, 1.0f) * 255.0f));
|
rwrap->setTextureAlphaMod(blocksTex, static_cast<Uint8>(std::clamp(clearAlpha, 0.0f, 1.0f) * 255.0f));
|
||||||
}
|
}
|
||||||
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize * clearScale, v - 1);
|
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize * clearScale, v - 1);
|
||||||
if (blocksTex && clearAlpha < 1.0f) {
|
if (blocksTex && clearAlpha < 1.0f) {
|
||||||
SDL_SetTextureAlphaMod(blocksTex, 255);
|
rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1209,7 +1224,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
s.y += s.vy * sparkDeltaMs;
|
s.y += s.vy * sparkDeltaMs;
|
||||||
float lifeRatio = std::clamp(static_cast<float>(s.lifeMs / s.maxLifeMs), 0.0f, 1.0f);
|
float lifeRatio = std::clamp(static_cast<float>(s.lifeMs / s.maxLifeMs), 0.0f, 1.0f);
|
||||||
Uint8 alpha = static_cast<Uint8>(lifeRatio * 200.0f);
|
Uint8 alpha = static_cast<Uint8>(lifeRatio * 200.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, s.color.r, s.color.g, s.color.b, alpha);
|
rwrap->setDrawColor(SDL_Color{s.color.r, s.color.g, s.color.b, alpha});
|
||||||
float size = s.size * (0.7f + (1.0f - lifeRatio) * 0.8f);
|
float size = s.size * (0.7f + (1.0f - lifeRatio) * 0.8f);
|
||||||
SDL_FRect shardRect{
|
SDL_FRect shardRect{
|
||||||
s.x - size * 0.5f,
|
s.x - size * 0.5f,
|
||||||
@ -1217,7 +1232,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
size,
|
size,
|
||||||
size * 1.4f
|
size * 1.4f
|
||||||
};
|
};
|
||||||
SDL_RenderFillRect(renderer, &shardRect);
|
rwrap->fillRectF(&shardRect);
|
||||||
++shardIt;
|
++shardIt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1238,14 +1253,14 @@ void GameRenderer::renderPlayingState(
|
|||||||
|
|
||||||
SDL_Color c = b.color;
|
SDL_Color c = b.color;
|
||||||
Uint8 a = static_cast<Uint8>(alpha * 220.0f);
|
Uint8 a = static_cast<Uint8>(alpha * 220.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, a);
|
rwrap->setDrawColor(SDL_Color{c.r, c.g, c.b, a});
|
||||||
SDL_FRect outer{
|
SDL_FRect outer{
|
||||||
b.x - radius + jitter,
|
b.x - radius + jitter,
|
||||||
b.y - radius + jitter,
|
b.y - radius + jitter,
|
||||||
radius * 2.0f,
|
radius * 2.0f,
|
||||||
radius * 2.0f
|
radius * 2.0f
|
||||||
};
|
};
|
||||||
SDL_RenderRect(renderer, &outer);
|
rwrap->drawRectF(&outer);
|
||||||
|
|
||||||
SDL_FRect inner{
|
SDL_FRect inner{
|
||||||
b.x - (radius - thickness),
|
b.x - (radius - thickness),
|
||||||
@ -1253,8 +1268,8 @@ void GameRenderer::renderPlayingState(
|
|||||||
(radius - thickness) * 2.0f,
|
(radius - thickness) * 2.0f,
|
||||||
(radius - thickness) * 2.0f
|
(radius - thickness) * 2.0f
|
||||||
};
|
};
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, static_cast<Uint8>(a * 0.9f));
|
rwrap->setDrawColor(SDL_Color{255, 255, 255, static_cast<Uint8>(a * 0.9f)});
|
||||||
SDL_RenderRect(renderer, &inner);
|
rwrap->drawRectF(&inner);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
@ -1275,14 +1290,14 @@ void GameRenderer::renderPlayingState(
|
|||||||
}
|
}
|
||||||
float lifeRatio = spark.lifeMs / spark.maxLifeMs;
|
float lifeRatio = spark.lifeMs / spark.maxLifeMs;
|
||||||
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio, 0.0f, 1.0f) * 160.0f);
|
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio, 0.0f, 1.0f) * 160.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, spark.color.r, spark.color.g, spark.color.b, alpha);
|
rwrap->setDrawColor(SDL_Color{spark.color.r, spark.color.g, spark.color.b, alpha});
|
||||||
SDL_FRect sparkRect{
|
SDL_FRect sparkRect{
|
||||||
spark.x - spark.size * 0.5f,
|
spark.x - spark.size * 0.5f,
|
||||||
spark.y - spark.size * 0.5f,
|
spark.y - spark.size * 0.5f,
|
||||||
spark.size,
|
spark.size,
|
||||||
spark.size * 1.4f
|
spark.size * 1.4f
|
||||||
};
|
};
|
||||||
SDL_RenderFillRect(renderer, &sparkRect);
|
rwrap->fillRectF(&sparkRect);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1526,9 +1541,9 @@ void GameRenderer::renderPlayingState(
|
|||||||
float barW = numbersW;
|
float barW = numbersW;
|
||||||
float barY = numbersY + numbersH + 8.0f;
|
float barY = numbersY + numbersH + 8.0f;
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 24, 80, 120, 220);
|
rwrap->setDrawColor(SDL_Color{24, 80, 120, 220});
|
||||||
SDL_FRect track{barX, barY, barW, barHeight};
|
SDL_FRect track{barX, barY, barW, barHeight};
|
||||||
SDL_RenderFillRect(renderer, &track);
|
rwrap->fillRectF(&track);
|
||||||
|
|
||||||
// Fill color brightness based on usage and highlight for top piece
|
// Fill color brightness based on usage and highlight for top piece
|
||||||
float strength = (totalBlocks > 0) ? (float(blockCounts[i]) / float(totalBlocks)) : 0.0f;
|
float strength = (totalBlocks > 0) ? (float(blockCounts[i]) / float(totalBlocks)) : 0.0f;
|
||||||
@ -1542,9 +1557,9 @@ void GameRenderer::renderPlayingState(
|
|||||||
};
|
};
|
||||||
|
|
||||||
float fillW = barW * std::clamp(strength, 0.0f, 1.0f);
|
float fillW = barW * std::clamp(strength, 0.0f, 1.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, fillC.r, fillC.g, fillC.b, fillC.a);
|
rwrap->setDrawColor(SDL_Color{fillC.r, fillC.g, fillC.b, fillC.a});
|
||||||
SDL_FRect fill{barX, barY, fillW, barHeight};
|
SDL_FRect fill{barX, barY, fillW, barHeight};
|
||||||
SDL_RenderFillRect(renderer, &fill);
|
rwrap->fillRectF(&fill);
|
||||||
|
|
||||||
// Advance cursor to next row: after bar + gap (leave more space between blocks)
|
// Advance cursor to next row: after bar + gap (leave more space between blocks)
|
||||||
yCursor = barY + barHeight + rowGap + 6.0f;
|
yCursor = barY + barHeight + rowGap + 6.0f;
|
||||||
@ -1719,10 +1734,10 @@ void GameRenderer::renderPlayingState(
|
|||||||
|
|
||||||
SDL_FRect statsBg{statsPanelLeft, statsPanelTop, statsPanelWidth, statsPanelHeight};
|
SDL_FRect statsBg{statsPanelLeft, statsPanelTop, statsPanelWidth, statsPanelHeight};
|
||||||
if (scorePanelTex) {
|
if (scorePanelTex) {
|
||||||
SDL_RenderTexture(renderer, scorePanelTex, nullptr, &statsBg);
|
rwrap->renderTexture(scorePanelTex, nullptr, &statsBg);
|
||||||
} else {
|
} else {
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 18, 32, 205);
|
rwrap->setDrawColor(SDL_Color{12, 18, 32, 205});
|
||||||
SDL_RenderFillRect(renderer, &statsBg);
|
rwrap->fillRectF(&statsBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
scorePanelMetricsValid = true;
|
scorePanelMetricsValid = true;
|
||||||
@ -1810,12 +1825,12 @@ void GameRenderer::renderPlayingState(
|
|||||||
SDL_FRect panelDst{panelX, panelY, panelW, panelH};
|
SDL_FRect panelDst{panelX, panelY, panelW, panelH};
|
||||||
SDL_SetTextureBlendMode(holdPanelTex, SDL_BLENDMODE_BLEND);
|
SDL_SetTextureBlendMode(holdPanelTex, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetTextureScaleMode(holdPanelTex, SDL_SCALEMODE_LINEAR);
|
SDL_SetTextureScaleMode(holdPanelTex, SDL_SCALEMODE_LINEAR);
|
||||||
SDL_RenderTexture(renderer, holdPanelTex, nullptr, &panelDst);
|
rwrap->renderTexture(holdPanelTex, nullptr, &panelDst);
|
||||||
} else {
|
} else {
|
||||||
// fallback: draw a dark panel rect so UI is visible even without texture
|
// fallback: draw a dark panel rect so UI is visible even without texture
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 18, 32, 220);
|
rwrap->setDrawColor(SDL_Color{12, 18, 32, 220});
|
||||||
SDL_FRect panelDst{panelX, panelY, panelW, panelH};
|
SDL_FRect panelDst{panelX, panelY, panelW, panelH};
|
||||||
SDL_RenderFillRect(renderer, &panelDst);
|
rwrap->fillRectF(&panelDst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display "HOLD" label on right side
|
// Display "HOLD" label on right side
|
||||||
@ -1854,6 +1869,8 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
) {
|
) {
|
||||||
if (!renderer || !game || !pixelFont) return;
|
if (!renderer || !game || !pixelFont) return;
|
||||||
|
|
||||||
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
|
||||||
static SyncLineRenderer s_syncLine;
|
static SyncLineRenderer s_syncLine;
|
||||||
static bool s_lastHadCompletedLines = false;
|
static bool s_lastHadCompletedLines = false;
|
||||||
|
|
||||||
@ -1892,9 +1909,9 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
|
||||||
|
|
||||||
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
|
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
|
||||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
rwrap->setDrawColor(c);
|
||||||
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr float COOP_GAP_PX = 20.0f;
|
static constexpr float COOP_GAP_PX = 20.0f;
|
||||||
@ -1967,19 +1984,19 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Grid lines (draw per-half so the gap is clean)
|
// Grid lines (draw per-half so the gap is clean)
|
||||||
SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255);
|
rwrap->setDrawColor(SDL_Color{40, 45, 60, 255});
|
||||||
for (int x = 1; x < 10; ++x) {
|
for (int x = 1; x < 10; ++x) {
|
||||||
float lineX = gridX + x * finalBlockSize;
|
float lineX = gridX + x * finalBlockSize;
|
||||||
SDL_RenderLine(renderer, lineX, gridY, lineX, gridY + GRID_H);
|
rwrap->renderLine(lineX, gridY, lineX, gridY + GRID_H);
|
||||||
}
|
}
|
||||||
for (int x = 1; x < 10; ++x) {
|
for (int x = 1; x < 10; ++x) {
|
||||||
float lineX = gridX + HALF_W + COOP_GAP_PX + x * finalBlockSize;
|
float lineX = gridX + HALF_W + COOP_GAP_PX + x * finalBlockSize;
|
||||||
SDL_RenderLine(renderer, lineX, gridY, lineX, gridY + GRID_H);
|
rwrap->renderLine(lineX, gridY, lineX, gridY + GRID_H);
|
||||||
}
|
}
|
||||||
for (int y = 1; y < CoopGame::ROWS; ++y) {
|
for (int y = 1; y < CoopGame::ROWS; ++y) {
|
||||||
float lineY = gridY + y * finalBlockSize;
|
float lineY = gridY + y * finalBlockSize;
|
||||||
SDL_RenderLine(renderer, gridX, lineY, gridX + HALF_W, lineY);
|
rwrap->renderLine(gridX, lineY, gridX + HALF_W, lineY);
|
||||||
SDL_RenderLine(renderer, gridX + HALF_W + COOP_GAP_PX, lineY, gridX + HALF_W + COOP_GAP_PX + HALF_W, lineY);
|
rwrap->renderLine(gridX + HALF_W + COOP_GAP_PX, lineY, gridX + HALF_W + COOP_GAP_PX + HALF_W, lineY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-grid 3D starfield + ambient sparkles (match classic feel, per-half)
|
// In-grid 3D starfield + ambient sparkles (match classic feel, per-half)
|
||||||
@ -2164,10 +2181,10 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
|
|
||||||
float pulse = 0.5f + 0.5f * std::sin(sp.pulse);
|
float pulse = 0.5f + 0.5f * std::sin(sp.pulse);
|
||||||
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio * pulse, 0.0f, 1.0f) * 255.0f);
|
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio * pulse, 0.0f, 1.0f) * 255.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, sp.color.r, sp.color.g, sp.color.b, alpha);
|
rwrap->setDrawColor(SDL_Color{sp.color.r, sp.color.g, sp.color.b, alpha});
|
||||||
float half = sp.size * 0.5f;
|
float half = sp.size * 0.5f;
|
||||||
SDL_FRect fr{ originX + sp.x - half, gridY + sp.y - half, sp.size, sp.size };
|
SDL_FRect fr{ originX + sp.x - half, gridY + sp.y - half, sp.size, sp.size };
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2186,14 +2203,14 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
}
|
}
|
||||||
float lifeRatio = spark.lifeMs / spark.maxLifeMs;
|
float lifeRatio = spark.lifeMs / spark.maxLifeMs;
|
||||||
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio, 0.0f, 1.0f) * 160.0f);
|
Uint8 alpha = static_cast<Uint8>(std::clamp(lifeRatio, 0.0f, 1.0f) * 160.0f);
|
||||||
SDL_SetRenderDrawColor(renderer, spark.color.r, spark.color.g, spark.color.b, alpha);
|
rwrap->setDrawColor(SDL_Color{spark.color.r, spark.color.g, spark.color.b, alpha});
|
||||||
SDL_FRect sparkRect{
|
SDL_FRect sparkRect{
|
||||||
spark.x - spark.size * 0.5f,
|
spark.x - spark.size * 0.5f,
|
||||||
spark.y - spark.size * 0.5f,
|
spark.y - spark.size * 0.5f,
|
||||||
spark.size,
|
spark.size,
|
||||||
spark.size * 1.4f
|
spark.size * 1.4f
|
||||||
};
|
};
|
||||||
SDL_RenderFillRect(renderer, &sparkRect);
|
rwrap->fillRectF(&sparkRect);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2225,17 +2242,17 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rs.leftFull && rs.rightFull) {
|
if (rs.leftFull && rs.rightFull) {
|
||||||
SDL_SetRenderDrawColor(renderer, 140, 210, 255, 45);
|
rwrap->setDrawColor(SDL_Color{140, 210, 255, 45});
|
||||||
SDL_FRect frL{gridX, rowY, HALF_W, finalBlockSize};
|
SDL_FRect frL{gridX, rowY, HALF_W, finalBlockSize};
|
||||||
SDL_RenderFillRect(renderer, &frL);
|
rwrap->fillRectF(&frL);
|
||||||
SDL_FRect frR{gridX + HALF_W + COOP_GAP_PX, rowY, HALF_W, finalBlockSize};
|
SDL_FRect frR{gridX + HALF_W + COOP_GAP_PX, rowY, HALF_W, finalBlockSize};
|
||||||
SDL_RenderFillRect(renderer, &frR);
|
rwrap->fillRectF(&frR);
|
||||||
} else if (rs.leftFull ^ rs.rightFull) {
|
} else if (rs.leftFull ^ rs.rightFull) {
|
||||||
SDL_SetRenderDrawColor(renderer, 90, 140, 220, 35);
|
rwrap->setDrawColor(SDL_Color{90, 140, 220, 35});
|
||||||
float w = HALF_W;
|
float w = HALF_W;
|
||||||
float x = rs.leftFull ? gridX : (gridX + HALF_W + COOP_GAP_PX);
|
float x = rs.leftFull ? gridX : (gridX + HALF_W + COOP_GAP_PX);
|
||||||
SDL_FRect fr{x, rowY, w, finalBlockSize};
|
SDL_FRect fr{x, rowY, w, finalBlockSize};
|
||||||
SDL_RenderFillRect(renderer, &fr);
|
rwrap->fillRectF(&fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
|
||||||
@ -2432,7 +2449,7 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
float elapsed = static_cast<float>(nowTicks - sf.startTick);
|
float elapsed = static_cast<float>(nowTicks - sf.startTick);
|
||||||
float t = sf.durationMs <= 0.0f ? 1.0f : std::clamp(elapsed / sf.durationMs, 0.0f, 1.0f);
|
float t = sf.durationMs <= 0.0f ? 1.0f : std::clamp(elapsed / sf.durationMs, 0.0f, 1.0f);
|
||||||
Uint8 alpha = static_cast<Uint8>(std::lround(255.0f * t));
|
Uint8 alpha = static_cast<Uint8>(std::lround(255.0f * t));
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, alpha);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, alpha);
|
||||||
|
|
||||||
int minCy = 4;
|
int minCy = 4;
|
||||||
int maxCy = -1;
|
int maxCy = -1;
|
||||||
@ -2479,7 +2496,7 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
drawBlockTexturePublic(renderer, blocksTex, px, py, sf.tileSize, livePiece.type);
|
drawBlockTexturePublic(renderer, blocksTex, px, py, sf.tileSize, livePiece.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
|
if (blocksTex) rwrap->setTextureAlphaMod(blocksTex, 255);
|
||||||
|
|
||||||
// End fade after duration, but never stop while we are pinning (otherwise
|
// End fade after duration, but never stop while we are pinning (otherwise
|
||||||
// I can briefly disappear until it becomes visible in the real grid).
|
// I can briefly disappear until it becomes visible in the real grid).
|
||||||
@ -2499,12 +2516,12 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
float py = gridY + (float)pyIdx * finalBlockSize + offsets.second;
|
float py = gridY + (float)pyIdx * finalBlockSize + offsets.second;
|
||||||
if (isGhost) {
|
if (isGhost) {
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 20);
|
rwrap->setDrawColor(SDL_Color{180, 180, 180, 20});
|
||||||
SDL_FRect rect = {px + 2.0f, py + 2.0f, finalBlockSize - 4.0f, finalBlockSize - 4.0f};
|
SDL_FRect rect = {px + 2.0f, py + 2.0f, finalBlockSize - 4.0f, finalBlockSize - 4.0f};
|
||||||
SDL_RenderFillRect(renderer, &rect);
|
rwrap->fillRectF(&rect);
|
||||||
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 30);
|
rwrap->setDrawColor(SDL_Color{180, 180, 180, 30});
|
||||||
SDL_FRect border = {px + 1.0f, py + 1.0f, finalBlockSize - 2.0f, finalBlockSize - 2.0f};
|
SDL_FRect border = {px + 1.0f, py + 1.0f, finalBlockSize - 2.0f, finalBlockSize - 2.0f};
|
||||||
SDL_RenderRect(renderer, &border);
|
rwrap->drawRectF(&border);
|
||||||
} else {
|
} else {
|
||||||
drawBlockTexturePublic(renderer, blocksTex, px, py, finalBlockSize, p.type);
|
drawBlockTexturePublic(renderer, blocksTex, px, py, finalBlockSize, p.type);
|
||||||
}
|
}
|
||||||
@ -2579,7 +2596,7 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
auto drawNextPanel = [&](float panelX, float panelY, const CoopGame::Piece& piece) {
|
auto drawNextPanel = [&](float panelX, float panelY, const CoopGame::Piece& piece) {
|
||||||
SDL_FRect panel{ panelX, panelY, nextPanelW, nextPanelH };
|
SDL_FRect panel{ panelX, panelY, nextPanelW, nextPanelH };
|
||||||
if (nextPanelTex) {
|
if (nextPanelTex) {
|
||||||
SDL_RenderTexture(renderer, nextPanelTex, nullptr, &panel);
|
rwrap->renderTexture(nextPanelTex, nullptr, &panel);
|
||||||
} else {
|
} else {
|
||||||
drawRectWithOffset(panel.x - contentOffsetX, panel.y - contentOffsetY, panel.w, panel.h, SDL_Color{18,22,30,200});
|
drawRectWithOffset(panel.x - contentOffsetX, panel.y - contentOffsetY, panel.w, panel.h, SDL_Color{18,22,30,200});
|
||||||
}
|
}
|
||||||
@ -2707,10 +2724,10 @@ void GameRenderer::renderCoopPlayingState(
|
|||||||
float panelX = (side == CoopGame::PlayerSide::Right) ? (columnRightX - panelW) : columnLeftX;
|
float panelX = (side == CoopGame::PlayerSide::Right) ? (columnRightX - panelW) : columnLeftX;
|
||||||
SDL_FRect panelBg{ panelX, panelY, panelW, panelH };
|
SDL_FRect panelBg{ panelX, panelY, panelW, panelH };
|
||||||
if (scorePanelTex) {
|
if (scorePanelTex) {
|
||||||
SDL_RenderTexture(renderer, scorePanelTex, nullptr, &panelBg);
|
rwrap->renderTexture(scorePanelTex, nullptr, &panelBg);
|
||||||
} else {
|
} else {
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 18, 32, 205);
|
rwrap->setDrawColor(SDL_Color{12, 18, 32, 205});
|
||||||
SDL_RenderFillRect(renderer, &panelBg);
|
rwrap->fillRectF(&panelBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
float textDrawX = panelX + statsPanelPadLeft;
|
float textDrawX = panelX + statsPanelPadLeft;
|
||||||
@ -2777,9 +2794,10 @@ void GameRenderer::renderExitPopup(
|
|||||||
SDL_SetRenderViewport(renderer, nullptr);
|
SDL_SetRenderViewport(renderer, nullptr);
|
||||||
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
|
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 2, 4, 12, 210);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->setDrawColor(SDL_Color{2, 4, 12, 210});
|
||||||
SDL_FRect fullWin{0.0f, 0.0f, winW, winH};
|
SDL_FRect fullWin{0.0f, 0.0f, winW, winH};
|
||||||
SDL_RenderFillRect(renderer, &fullWin);
|
rwrap->fillRectF(&fullWin);
|
||||||
|
|
||||||
const float scale = std::max(0.8f, logicalScale);
|
const float scale = std::max(0.8f, logicalScale);
|
||||||
const float panelW = 740.0f * scale;
|
const float panelW = 740.0f * scale;
|
||||||
@ -2797,8 +2815,8 @@ void GameRenderer::renderExitPopup(
|
|||||||
panel.w + 4.0f * scale,
|
panel.w + 4.0f * scale,
|
||||||
panel.h + 4.0f * scale
|
panel.h + 4.0f * scale
|
||||||
};
|
};
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
|
rwrap->setDrawColor(SDL_Color{0, 0, 0, 140});
|
||||||
SDL_RenderFillRect(renderer, &shadow);
|
rwrap->fillRectF(&shadow);
|
||||||
|
|
||||||
const std::array<SDL_Color, 3> panelLayers{
|
const std::array<SDL_Color, 3> panelLayers{
|
||||||
SDL_Color{7, 10, 22, 255},
|
SDL_Color{7, 10, 22, 255},
|
||||||
@ -2814,12 +2832,12 @@ void GameRenderer::renderExitPopup(
|
|||||||
panel.h - inset * 2.0f
|
panel.h - inset * 2.0f
|
||||||
};
|
};
|
||||||
SDL_Color c = panelLayers[i];
|
SDL_Color c = panelLayers[i];
|
||||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
rwrap->setDrawColor(c);
|
||||||
SDL_RenderFillRect(renderer, &layer);
|
rwrap->fillRectF(&layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 60, 90, 150, 255);
|
rwrap->setDrawColor(SDL_Color{60, 90, 150, 255});
|
||||||
SDL_RenderRect(renderer, &panel);
|
rwrap->drawRectF(&panel);
|
||||||
|
|
||||||
SDL_FRect insetFrame{
|
SDL_FRect insetFrame{
|
||||||
panel.x + 10.0f * scale,
|
panel.x + 10.0f * scale,
|
||||||
@ -2827,8 +2845,8 @@ void GameRenderer::renderExitPopup(
|
|||||||
panel.w - 20.0f * scale,
|
panel.w - 20.0f * scale,
|
||||||
panel.h - 20.0f * scale
|
panel.h - 20.0f * scale
|
||||||
};
|
};
|
||||||
SDL_SetRenderDrawColor(renderer, 24, 45, 84, 255);
|
rwrap->setDrawColor(SDL_Color{24, 45, 84, 255});
|
||||||
SDL_RenderRect(renderer, &insetFrame);
|
rwrap->drawRectF(&insetFrame);
|
||||||
|
|
||||||
const float contentPad = 44.0f * scale;
|
const float contentPad = 44.0f * scale;
|
||||||
float textX = panel.x + contentPad;
|
float textX = panel.x + contentPad;
|
||||||
@ -2842,9 +2860,9 @@ void GameRenderer::renderExitPopup(
|
|||||||
pixelFont->draw(renderer, textX, cursorY, title, titleScale, SDL_Color{255, 224, 130, 255});
|
pixelFont->draw(renderer, textX, cursorY, title, titleScale, SDL_Color{255, 224, 130, 255});
|
||||||
cursorY += titleH + 18.0f * scale;
|
cursorY += titleH + 18.0f * scale;
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 32, 64, 110, 210);
|
rwrap->setDrawColor(SDL_Color{32, 64, 110, 210});
|
||||||
SDL_FRect divider{textX, cursorY, contentWidth, 2.0f * scale};
|
SDL_FRect divider{textX, cursorY, contentWidth, 2.0f * scale};
|
||||||
SDL_RenderFillRect(renderer, ÷r);
|
rwrap->fillRectF(÷r);
|
||||||
cursorY += 26.0f * scale;
|
cursorY += 26.0f * scale;
|
||||||
|
|
||||||
const std::array<const char*, 2> lines{
|
const std::array<const char*, 2> lines{
|
||||||
@ -2885,29 +2903,29 @@ void GameRenderer::renderExitPopup(
|
|||||||
SDL_Color border = selected ? SDL_Color{255, 225, 150, 255} : SDL_Color{90, 120, 170, 255};
|
SDL_Color border = selected ? SDL_Color{255, 225, 150, 255} : SDL_Color{90, 120, 170, 255};
|
||||||
SDL_Color topEdge = SDL_Color{Uint8(std::min(255, body.r + 20)), Uint8(std::min(255, body.g + 20)), Uint8(std::min(255, body.b + 20)), 255};
|
SDL_Color topEdge = SDL_Color{Uint8(std::min(255, body.r + 20)), Uint8(std::min(255, body.g + 20)), Uint8(std::min(255, body.b + 20)), 255};
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 110);
|
rwrap->setDrawColor(SDL_Color{0, 0, 0, 110});
|
||||||
SDL_FRect btnShadow{btn.x + 6.0f * scale, btn.y + 8.0f * scale, btn.w, btn.h};
|
SDL_FRect btnShadow{btn.x + 6.0f * scale, btn.y + 8.0f * scale, btn.w, btn.h};
|
||||||
SDL_RenderFillRect(renderer, &btnShadow);
|
rwrap->fillRectF(&btnShadow);
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, body.r, body.g, body.b, body.a);
|
rwrap->setDrawColor(body);
|
||||||
SDL_RenderFillRect(renderer, &btn);
|
rwrap->fillRectF(&btn);
|
||||||
|
|
||||||
SDL_FRect topStrip{btn.x, btn.y, btn.w, 6.0f * scale};
|
SDL_FRect topStrip{btn.x, btn.y, btn.w, 6.0f * scale};
|
||||||
SDL_SetRenderDrawColor(renderer, topEdge.r, topEdge.g, topEdge.b, topEdge.a);
|
rwrap->setDrawColor(topEdge);
|
||||||
SDL_RenderFillRect(renderer, &topStrip);
|
rwrap->fillRectF(&topStrip);
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, border.r, border.g, border.b, border.a);
|
rwrap->setDrawColor(border);
|
||||||
SDL_RenderRect(renderer, &btn);
|
rwrap->drawRectF(&btn);
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 230, 160, 90);
|
rwrap->setDrawColor(SDL_Color{255, 230, 160, 90});
|
||||||
SDL_FRect glow{
|
SDL_FRect glow{
|
||||||
btn.x - 6.0f * scale,
|
btn.x - 6.0f * scale,
|
||||||
btn.y - 6.0f * scale,
|
btn.y - 6.0f * scale,
|
||||||
btn.w + 12.0f * scale,
|
btn.w + 12.0f * scale,
|
||||||
btn.h + 12.0f * scale
|
btn.h + 12.0f * scale
|
||||||
};
|
};
|
||||||
SDL_RenderRect(renderer, &glow);
|
rwrap->drawRectF(&glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float labelScale = 1.35f * scale;
|
const float labelScale = 1.35f * scale;
|
||||||
@ -2948,9 +2966,10 @@ void GameRenderer::renderPauseOverlay(
|
|||||||
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
|
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
|
||||||
|
|
||||||
// Draw full screen overlay (darken)
|
// Draw full screen overlay (darken)
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
auto rwrap = renderer::MakeSDLRenderer(renderer);
|
||||||
|
rwrap->setDrawColor(SDL_Color{0, 0, 0, 180});
|
||||||
SDL_FRect pauseOverlay{0, 0, winW, winH};
|
SDL_FRect pauseOverlay{0, 0, winW, winH};
|
||||||
SDL_RenderFillRect(renderer, &pauseOverlay);
|
rwrap->fillRectF(&pauseOverlay);
|
||||||
|
|
||||||
// Draw centered text
|
// Draw centered text
|
||||||
const char* pausedText = "PAUSED";
|
const char* pausedText = "PAUSED";
|
||||||
|
|||||||
59
src/logic/Board.cpp
Normal file
59
src/logic/Board.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include "Board.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace logic {
|
||||||
|
|
||||||
|
Board::Board()
|
||||||
|
: grid_(Width * Height, Cell::Empty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Board::clear()
|
||||||
|
{
|
||||||
|
std::fill(grid_.begin(), grid_.end(), Cell::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Board::inBounds(int x, int y) const
|
||||||
|
{
|
||||||
|
return x >= 0 && x < Width && y >= 0 && y < Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
Board::Cell Board::at(int x, int y) const
|
||||||
|
{
|
||||||
|
if (!inBounds(x, y)) return Cell::Empty;
|
||||||
|
return grid_[y * Width + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Board::set(int x, int y, Cell c)
|
||||||
|
{
|
||||||
|
if (!inBounds(x, y)) return;
|
||||||
|
grid_[y * Width + x] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Board::clearFullLines()
|
||||||
|
{
|
||||||
|
int cleared = 0;
|
||||||
|
// scan from bottom to top
|
||||||
|
for (int y = Height - 1; y >= 0; --y) {
|
||||||
|
bool full = true;
|
||||||
|
for (int x = 0; x < Width; ++x) {
|
||||||
|
if (at(x, y) == Cell::Empty) { full = false; break; }
|
||||||
|
}
|
||||||
|
if (full) {
|
||||||
|
// remove row y: move all rows above down by one
|
||||||
|
for (int yy = y; yy > 0; --yy) {
|
||||||
|
for (int x = 0; x < Width; ++x) {
|
||||||
|
grid_[yy * Width + x] = grid_[(yy - 1) * Width + x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear top row
|
||||||
|
for (int x = 0; x < Width; ++x) grid_[x] = Cell::Empty;
|
||||||
|
++cleared;
|
||||||
|
// stay on same y to re-check the row that fell into place
|
||||||
|
++y; // because next iteration decrements y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleared;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace logic
|
||||||
32
src/logic/Board.h
Normal file
32
src/logic/Board.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace logic {
|
||||||
|
|
||||||
|
class Board {
|
||||||
|
public:
|
||||||
|
static constexpr int Width = 10;
|
||||||
|
static constexpr int Height = 20;
|
||||||
|
|
||||||
|
enum class Cell : uint8_t { Empty = 0, Filled = 1 };
|
||||||
|
|
||||||
|
Board();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
Cell at(int x, int y) const;
|
||||||
|
void set(int x, int y, Cell c);
|
||||||
|
bool inBounds(int x, int y) const;
|
||||||
|
|
||||||
|
// Remove and return number of full lines cleared. Rows above fall down.
|
||||||
|
int clearFullLines();
|
||||||
|
|
||||||
|
const std::vector<Cell>& data() const { return grid_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Cell> grid_; // row-major: y*Width + x
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace logic
|
||||||
38
src/renderer/Renderer.h
Normal file
38
src/renderer/Renderer.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Renderer abstraction (minimal scaffold)
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace renderer {
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
virtual ~Renderer() = default;
|
||||||
|
|
||||||
|
// Create/destroy textures
|
||||||
|
virtual SDL_Texture* createTextureFromSurface(SDL_Surface* surf) = 0;
|
||||||
|
virtual void destroyTexture(SDL_Texture* tex) = 0;
|
||||||
|
|
||||||
|
// Draw operations (minimal)
|
||||||
|
// Copy a texture (integer rects)
|
||||||
|
virtual void copy(SDL_Texture* tex, const SDL_Rect* src, const SDL_Rect* dst) = 0;
|
||||||
|
// Copy a texture using floating-point rects (SDL_FRect)
|
||||||
|
virtual void renderTexture(SDL_Texture* tex, const SDL_FRect* src, const SDL_FRect* dst) = 0;
|
||||||
|
// Set alpha modulation on a texture
|
||||||
|
virtual void setTextureAlphaMod(SDL_Texture* tex, Uint8 a) = 0;
|
||||||
|
// Draw a line (floating-point coordinates)
|
||||||
|
virtual void renderLine(float x1, float y1, float x2, float y2) = 0;
|
||||||
|
// Set draw color and draw filled/floating rects
|
||||||
|
virtual void clear(const SDL_Color& color) = 0;
|
||||||
|
virtual void setDrawColor(const SDL_Color& color) = 0;
|
||||||
|
virtual void fillRectF(const SDL_FRect* rect) = 0;
|
||||||
|
virtual void drawRectF(const SDL_FRect* rect) = 0;
|
||||||
|
virtual void present() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Factory helper implemented by SDL-specific backend
|
||||||
|
std::unique_ptr<Renderer> MakeSDLRenderer(SDL_Renderer* rdr);
|
||||||
|
|
||||||
|
} // namespace renderer
|
||||||
|
|
||||||
27
src/renderer/Renderer_iface.h
Normal file
27
src/renderer/Renderer_iface.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Clean renderer interface for local use
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace renderer {
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
virtual ~Renderer() = default;
|
||||||
|
virtual SDL_Texture* createTextureFromSurface(SDL_Surface* surf) = 0;
|
||||||
|
virtual void destroyTexture(SDL_Texture* tex) = 0;
|
||||||
|
virtual void copy(SDL_Texture* tex, const SDL_Rect* src, const SDL_Rect* dst) = 0;
|
||||||
|
virtual void renderTexture(SDL_Texture* tex, const SDL_FRect* src, const SDL_FRect* dst) = 0;
|
||||||
|
virtual void setTextureAlphaMod(SDL_Texture* tex, Uint8 a) = 0;
|
||||||
|
virtual void renderLine(float x1, float y1, float x2, float y2) = 0;
|
||||||
|
virtual void clear(const SDL_Color& color) = 0;
|
||||||
|
virtual void setDrawColor(const SDL_Color& color) = 0;
|
||||||
|
virtual void fillRectF(const SDL_FRect* rect) = 0;
|
||||||
|
virtual void drawRectF(const SDL_FRect* rect) = 0;
|
||||||
|
virtual void present() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Renderer> MakeSDLRenderer(SDL_Renderer* rdr);
|
||||||
|
|
||||||
|
} // namespace renderer
|
||||||
80
src/renderer/SDLRenderer.cpp
Normal file
80
src/renderer/SDLRenderer.cpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#include "Renderer_iface.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace renderer {
|
||||||
|
|
||||||
|
class SDLRendererImpl : public Renderer {
|
||||||
|
public:
|
||||||
|
explicit SDLRendererImpl(SDL_Renderer* rdr) : rdr_(rdr) {}
|
||||||
|
~SDLRendererImpl() override = default;
|
||||||
|
|
||||||
|
SDL_Texture* createTextureFromSurface(SDL_Surface* surf) override {
|
||||||
|
if (!rdr_ || !surf) return nullptr;
|
||||||
|
return SDL_CreateTextureFromSurface(rdr_, surf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyTexture(SDL_Texture* tex) override {
|
||||||
|
if (tex) SDL_DestroyTexture(tex);
|
||||||
|
}
|
||||||
|
void copy(SDL_Texture* tex, const SDL_Rect* src, const SDL_Rect* dst) override {
|
||||||
|
if (!rdr_ || !tex) return;
|
||||||
|
// Convert integer rects to float rects and call SDL_RenderTexture (SDL3 API)
|
||||||
|
SDL_FRect fs{}; SDL_FRect fd{};
|
||||||
|
const SDL_FRect* ps = nullptr;
|
||||||
|
const SDL_FRect* pd = nullptr;
|
||||||
|
if (src) { fs.x = static_cast<float>(src->x); fs.y = static_cast<float>(src->y); fs.w = static_cast<float>(src->w); fs.h = static_cast<float>(src->h); ps = &fs; }
|
||||||
|
if (dst) { fd.x = static_cast<float>(dst->x); fd.y = static_cast<float>(dst->y); fd.w = static_cast<float>(dst->w); fd.h = static_cast<float>(dst->h); pd = &fd; }
|
||||||
|
SDL_RenderTexture(rdr_, tex, ps, pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderTexture(SDL_Texture* tex, const SDL_FRect* src, const SDL_FRect* dst) override {
|
||||||
|
if (!rdr_ || !tex) return;
|
||||||
|
SDL_RenderTexture(rdr_, tex, src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTextureAlphaMod(SDL_Texture* tex, Uint8 a) override {
|
||||||
|
if (!tex) return;
|
||||||
|
SDL_SetTextureAlphaMod(tex, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(const SDL_Color& color) override {
|
||||||
|
if (!rdr_) return;
|
||||||
|
SDL_SetRenderDrawColor(rdr_, color.r, color.g, color.b, color.a);
|
||||||
|
SDL_RenderClear(rdr_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDrawColor(const SDL_Color& color) override {
|
||||||
|
if (!rdr_) return;
|
||||||
|
SDL_SetRenderDrawColor(rdr_, color.r, color.g, color.b, color.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillRectF(const SDL_FRect* rect) override {
|
||||||
|
if (!rdr_ || !rect) return;
|
||||||
|
SDL_RenderFillRect(rdr_, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawRectF(const SDL_FRect* rect) override {
|
||||||
|
if (!rdr_ || !rect) return;
|
||||||
|
SDL_RenderRect(rdr_, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderLine(float x1, float y1, float x2, float y2) override {
|
||||||
|
if (!rdr_) return;
|
||||||
|
SDL_RenderLine(rdr_, x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void present() override {
|
||||||
|
if (!rdr_) return;
|
||||||
|
SDL_RenderPresent(rdr_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Renderer* rdr_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Factory helper
|
||||||
|
std::unique_ptr<Renderer> MakeSDLRenderer(SDL_Renderer* rdr) {
|
||||||
|
return std::make_unique<SDLRendererImpl>(rdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace renderer
|
||||||
41
src/resources/ResourceManager.cpp
Normal file
41
src/resources/ResourceManager.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include "ResourceManager.h"
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
namespace resources {
|
||||||
|
|
||||||
|
ResourceManager::ResourceManager() = default;
|
||||||
|
ResourceManager::~ResourceManager() = default;
|
||||||
|
|
||||||
|
std::future<std::shared_ptr<void>> ResourceManager::loadAsync(const std::string& key, std::function<std::shared_ptr<void>(const std::string&)> loader)
|
||||||
|
{
|
||||||
|
// Quick check for existing cached resource
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
auto it = cache_.find(key);
|
||||||
|
if (it != cache_.end()) {
|
||||||
|
// Return already-available resource (keep strong ref)
|
||||||
|
auto sp = it->second;
|
||||||
|
if (sp) {
|
||||||
|
return std::async(std::launch::deferred, [sp]() { return sp; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch async loader
|
||||||
|
return std::async(std::launch::async, [this, key, loader]() {
|
||||||
|
auto res = loader(key);
|
||||||
|
if (res) {
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
cache_[key] = res; // store strong reference
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceManager::put(const std::string& key, std::shared_ptr<void> resource)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
cache_[key] = resource; // store strong reference so callers using raw pointers stay valid
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace resources
|
||||||
43
src/resources/ResourceManager.h
Normal file
43
src/resources/ResourceManager.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <future>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace resources {
|
||||||
|
|
||||||
|
class ResourceManager {
|
||||||
|
public:
|
||||||
|
ResourceManager();
|
||||||
|
~ResourceManager();
|
||||||
|
|
||||||
|
// Return cached resource if available and of the right type
|
||||||
|
template<typename T>
|
||||||
|
std::shared_ptr<T> get(const std::string& key)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
auto it = cache_.find(key);
|
||||||
|
if (it == cache_.end()) return nullptr;
|
||||||
|
auto sp = it->second;
|
||||||
|
if (!sp) { cache_.erase(it); return nullptr; }
|
||||||
|
return std::static_pointer_cast<T>(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronously load a resource using the provided loader function.
|
||||||
|
// The loader must return a shared_ptr to the concrete resource (boxed as void).
|
||||||
|
std::future<std::shared_ptr<void>> loadAsync(const std::string& key, std::function<std::shared_ptr<void>(const std::string&)> loader);
|
||||||
|
|
||||||
|
// Insert a resource into the cache (thread-safe)
|
||||||
|
void put(const std::string& key, std::shared_ptr<void> resource);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Keep strong ownership of cached resources so they remain valid
|
||||||
|
// while present in the cache.
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<void>> cache_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace resources
|
||||||
@ -8,6 +8,7 @@
|
|||||||
#include "../core/Settings.h"
|
#include "../core/Settings.h"
|
||||||
#include "../core/state/StateManager.h"
|
#include "../core/state/StateManager.h"
|
||||||
#include "../audio/Audio.h"
|
#include "../audio/Audio.h"
|
||||||
|
#include "../audio/AudioManager.h"
|
||||||
#include "../audio/SoundEffect.h"
|
#include "../audio/SoundEffect.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_render.h>
|
#include <SDL3/SDL_render.h>
|
||||||
@ -180,7 +181,7 @@ void MenuState::showCoopSetupPanel(bool show, bool resumeMusic) {
|
|||||||
coopSetupStep = CoopSetupStep::ChoosePartner;
|
coopSetupStep = CoopSetupStep::ChoosePartner;
|
||||||
// Resume menu music only when requested (ESC should pass resumeMusic=false)
|
// Resume menu music only when requested (ESC should pass resumeMusic=false)
|
||||||
if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) {
|
if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) {
|
||||||
Audio::instance().playMenuMusic();
|
if (auto sys = AudioManager::get()) sys->playMenuMusic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "../core/state/StateManager.h"
|
#include "../core/state/StateManager.h"
|
||||||
#include "../graphics/ui/Font.h"
|
#include "../graphics/ui/Font.h"
|
||||||
#include "../audio/Audio.h"
|
#include "../audio/Audio.h"
|
||||||
|
#include "../audio/AudioManager.h"
|
||||||
#include "../audio/SoundEffect.h"
|
#include "../audio/SoundEffect.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -220,7 +221,7 @@ void OptionsState::toggleFullscreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OptionsState::toggleMusic() {
|
void OptionsState::toggleMusic() {
|
||||||
Audio::instance().toggleMute();
|
if (auto sys = AudioManager::get()) sys->toggleMute();
|
||||||
// If muted, music is disabled. If not muted, music is enabled.
|
// If muted, music is disabled. If not muted, music is enabled.
|
||||||
// Note: Audio::instance().isMuted() returns true if muted.
|
// Note: Audio::instance().isMuted() returns true if muted.
|
||||||
// But Audio class doesn't expose isMuted directly in header usually?
|
// But Audio class doesn't expose isMuted directly in header usually?
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "../video/VideoPlayer.h"
|
#include "../video/VideoPlayer.h"
|
||||||
#include "../audio/Audio.h"
|
#include "../audio/Audio.h"
|
||||||
|
#include "../audio/AudioManager.h"
|
||||||
#include "../core/state/StateManager.h"
|
#include "../core/state/StateManager.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
@ -104,7 +105,7 @@ void VideoState::startAudioIfReady() {
|
|||||||
if (m_audioPcm.empty()) return;
|
if (m_audioPcm.empty()) return;
|
||||||
|
|
||||||
// Use the existing audio output path (same device as music/SFX).
|
// Use the existing audio output path (same device as music/SFX).
|
||||||
Audio::instance().playSfx(m_audioPcm, m_audioChannels, m_audioRate, 1.0f);
|
if (auto sys = AudioManager::get()) sys->playSfx(m_audioPcm, m_audioChannels, m_audioRate, 1.0f);
|
||||||
m_audioStarted = true;
|
m_audioStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
tests/test_board.cpp
Normal file
38
tests/test_board.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "../src/logic/Board.h"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using logic::Board;
|
||||||
|
|
||||||
|
TEST(BoardTests, InitiallyEmpty)
|
||||||
|
{
|
||||||
|
Board b;
|
||||||
|
for (int y = 0; y < Board::Height; ++y)
|
||||||
|
for (int x = 0; x < Board::Width; ++x)
|
||||||
|
EXPECT_EQ(b.at(x, y), Board::Cell::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BoardTests, ClearSingleFullLine)
|
||||||
|
{
|
||||||
|
Board b;
|
||||||
|
int y = Board::Height - 1;
|
||||||
|
for (int x = 0; x < Board::Width; ++x) b.set(x, y, Board::Cell::Filled);
|
||||||
|
int cleared = b.clearFullLines();
|
||||||
|
EXPECT_EQ(cleared, 1);
|
||||||
|
for (int x = 0; x < Board::Width; ++x) EXPECT_EQ(b.at(x, Board::Height - 1), Board::Cell::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BoardTests, ClearTwoNonAdjacentLines)
|
||||||
|
{
|
||||||
|
Board b;
|
||||||
|
int y1 = Board::Height - 1;
|
||||||
|
int y2 = Board::Height - 3;
|
||||||
|
for (int x = 0; x < Board::Width; ++x) { b.set(x, y1, Board::Cell::Filled); b.set(x, y2, Board::Cell::Filled); }
|
||||||
|
int cleared = b.clearFullLines();
|
||||||
|
EXPECT_EQ(cleared, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"enet",
|
"enet",
|
||||||
"catch2",
|
"catch2",
|
||||||
|
"gtest",
|
||||||
"cpr",
|
"cpr",
|
||||||
"nlohmann-json",
|
"nlohmann-json",
|
||||||
"ffmpeg"
|
"ffmpeg"
|
||||||
|
|||||||
Reference in New Issue
Block a user