Updated game structure
This commit is contained in:
@ -1,77 +0,0 @@
|
||||
// Starfield3D.h - 3D Parallax Starfield Effect
|
||||
// Creates a parallax starfield effect that simulates 3D space movement
|
||||
// By projecting 2D coordinates into 3D space with perspective
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
|
||||
class Starfield3D {
|
||||
public:
|
||||
Starfield3D();
|
||||
~Starfield3D() = default;
|
||||
|
||||
// Initialize the starfield with dimensions
|
||||
void init(int width, int height, int starCount = 160);
|
||||
|
||||
// Update starfield animation (call every frame)
|
||||
void update(float deltaTime);
|
||||
|
||||
// Draw the starfield
|
||||
void draw(SDL_Renderer* renderer);
|
||||
|
||||
// Update dimensions when window resizes
|
||||
void resize(int width, int height);
|
||||
|
||||
private:
|
||||
// Star representation in 3D space
|
||||
struct Star3D {
|
||||
float x, y, z; // 3D position
|
||||
float vx, vy, vz; // Current velocities
|
||||
float targetVx, targetVy, targetVz; // Target velocities for smooth transitions
|
||||
bool changing; // Whether star is currently changing direction
|
||||
float changeTimer; // Timer for direction change duration
|
||||
int type; // Star type (determines color/brightness)
|
||||
|
||||
Star3D() : x(0), y(0), z(0), vx(0), vy(0), vz(0),
|
||||
targetVx(0), targetVy(0), targetVz(0),
|
||||
changing(false), changeTimer(0), type(0) {}
|
||||
};
|
||||
|
||||
// Configuration constants
|
||||
static constexpr float MAX_DEPTH = 32.0f;
|
||||
static constexpr float STAR_SPEED = 0.4f;
|
||||
static constexpr float DEPTH_FACTOR = 256.0f;
|
||||
static constexpr float MIN_Z = 0.1f;
|
||||
static constexpr float MAX_Z = 50.0f;
|
||||
static constexpr float DIRECTION_CHANGE_PROBABILITY = 0.008f;
|
||||
static constexpr float MAX_VELOCITY = 0.3f;
|
||||
static constexpr float VELOCITY_CHANGE = 0.03f;
|
||||
static constexpr float REVERSE_PROBABILITY = 0.4f;
|
||||
|
||||
// Private methods
|
||||
void createStarfield();
|
||||
void updateStar(int index);
|
||||
void setRandomDirection(Star3D& star);
|
||||
float randomFloat(float min, float max);
|
||||
int randomRange(int min, int max);
|
||||
void drawStar(SDL_Renderer* renderer, float x, float y, int type);
|
||||
|
||||
// Member variables
|
||||
std::vector<Star3D> stars;
|
||||
std::mt19937 rng;
|
||||
|
||||
int width, height;
|
||||
float centerX, centerY;
|
||||
|
||||
// Star colors (RGB values)
|
||||
static constexpr SDL_Color STAR_COLORS[] = {
|
||||
{255, 255, 255, 255}, // White
|
||||
{170, 170, 170, 255}, // Light gray
|
||||
{153, 153, 153, 255}, // Medium gray
|
||||
{119, 119, 119, 255}, // Dark gray
|
||||
{85, 85, 85, 255} // Very dark gray
|
||||
};
|
||||
static constexpr int COLOR_COUNT = 5;
|
||||
};
|
||||
@ -1,57 +0,0 @@
|
||||
// StateManager.h - typed app state router with lifecycle hooks
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
enum class AppState {
|
||||
Loading = 0,
|
||||
Menu = 1,
|
||||
LevelSelect = 2,
|
||||
Playing = 3,
|
||||
GameOver = 4,
|
||||
Settings = 5
|
||||
};
|
||||
|
||||
class StateManager {
|
||||
public:
|
||||
using EventHandler = std::function<void(const SDL_Event&)>;
|
||||
using LifecycleHook = std::function<void()>;
|
||||
|
||||
explicit StateManager(AppState initial = AppState::Loading) : current(initial) {}
|
||||
|
||||
void registerHandler(AppState state, EventHandler handler) {
|
||||
handlers[static_cast<int>(state)] = std::move(handler);
|
||||
}
|
||||
|
||||
void registerOnEnter(AppState state, LifecycleHook hook) {
|
||||
onEnterHooks[static_cast<int>(state)] = std::move(hook);
|
||||
}
|
||||
|
||||
void registerOnExit(AppState state, LifecycleHook hook) {
|
||||
onExitHooks[static_cast<int>(state)] = std::move(hook);
|
||||
}
|
||||
|
||||
void setState(AppState state) {
|
||||
// Call exit hook for current state
|
||||
auto it = onExitHooks.find(static_cast<int>(current));
|
||||
if (it != onExitHooks.end() && it->second) it->second();
|
||||
current = state;
|
||||
// Call enter hook for new state
|
||||
auto it2 = onEnterHooks.find(static_cast<int>(current));
|
||||
if (it2 != onEnterHooks.end() && it2->second) it2->second();
|
||||
}
|
||||
|
||||
AppState getState() const { return current; }
|
||||
|
||||
void handleEvent(const SDL_Event& e) const {
|
||||
auto it = handlers.find(static_cast<int>(current));
|
||||
if (it != handlers.end() && it->second) it->second(e);
|
||||
}
|
||||
|
||||
private:
|
||||
AppState current;
|
||||
std::unordered_map<int, EventHandler> handlers;
|
||||
std::unordered_map<int, LifecycleHook> onEnterHooks;
|
||||
std::unordered_map<int, LifecycleHook> onExitHooks;
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
// Audio.cpp - Windows Media Foundation MP3 decoding
|
||||
#include "Audio.h"
|
||||
#include "audio/Audio.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
@ -26,7 +26,7 @@ Audio& Audio::instance(){ static Audio inst; return inst; }
|
||||
|
||||
bool Audio::init(){ if(outSpec.freq!=0) return true; outSpec.format=SDL_AUDIO_S16; outSpec.channels=outChannels; outSpec.freq=outRate;
|
||||
#ifdef _WIN32
|
||||
if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { std::fprintf(stderr,"[Audio] MFStartup failed\n"); } else mfStarted=true; }
|
||||
if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MFStartup failed"); } else mfStarted=true; }
|
||||
#endif
|
||||
return true; }
|
||||
|
||||
@ -45,9 +45,9 @@ static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int
|
||||
|
||||
void Audio::addTrack(const std::string& path){ AudioTrack t; t.path=path;
|
||||
#ifdef _WIN32
|
||||
if(decodeMP3(path, t.pcm, t.rate, t.channels)) t.ok=true; else std::fprintf(stderr,"[Audio] Failed to decode %s\n", path.c_str());
|
||||
if(decodeMP3(path, t.pcm, t.rate, t.channels)) t.ok=true; else SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
|
||||
#else
|
||||
std::fprintf(stderr,"[Audio] MP3 unsupported on this platform (stub): %s\n", path.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform (stub): %s", path.c_str());
|
||||
#endif
|
||||
tracks.push_back(std::move(t)); }
|
||||
|
||||
@ -60,7 +60,7 @@ bool Audio::ensureStream(){
|
||||
if(audioStream) return true;
|
||||
audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &outSpec, &Audio::streamCallback, this);
|
||||
if(!audioStream){
|
||||
std::fprintf(stderr,"[Audio] SDL_OpenAudioDeviceStream failed: %s\n", SDL_GetError());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] SDL_OpenAudioDeviceStream failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -170,7 +170,7 @@ void Audio::backgroundLoadingThread() {
|
||||
bool mfInitialized = SUCCEEDED(hrMF);
|
||||
|
||||
if (!mfInitialized) {
|
||||
std::fprintf(stderr, "[Audio] Failed to initialize MF on background thread\n");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to initialize MF on background thread");
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -188,10 +188,10 @@ void Audio::backgroundLoadingThread() {
|
||||
if (mfInitialized && decodeMP3(path, t.pcm, t.rate, t.channels)) {
|
||||
t.ok = true;
|
||||
} else {
|
||||
std::fprintf(stderr, "[Audio] Failed to decode %s\n", path.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
|
||||
}
|
||||
#else
|
||||
std::fprintf(stderr, "[Audio] MP3 unsupported on this platform (stub): %s\n", path.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform (stub): %s", path.c_str());
|
||||
#endif
|
||||
|
||||
// Thread-safe addition to tracks
|
||||
@ -9,7 +9,14 @@
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
struct AudioTrack { std::string path; std::vector<int16_t> pcm; int channels=2; int rate=44100; size_t cursor=0; bool ok=false; };
|
||||
struct AudioTrack {
|
||||
std::string path;
|
||||
std::vector<int16_t> pcm;
|
||||
int channels = 2;
|
||||
int rate = 44100;
|
||||
size_t cursor = 0;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
class Audio {
|
||||
public:
|
||||
22
src/audio/MenuWrappers.h
Normal file
22
src/audio/MenuWrappers.h
Normal file
@ -0,0 +1,22 @@
|
||||
// MenuWrappers.h - function prototypes for menu helper wrappers implemented in main.cpp
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
|
||||
class FontAtlas;
|
||||
|
||||
// Draw fireworks using the provided blocks texture (may be nullptr)
|
||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex);
|
||||
void menu_updateFireworks(double frameMs);
|
||||
double menu_getLogoAnimCounter();
|
||||
int menu_getHoveredButton();
|
||||
|
||||
void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected);
|
||||
|
||||
void menu_drawMenuButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, SDL_Color bgColor, SDL_Color borderColor);
|
||||
|
||||
void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel);
|
||||
void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled);
|
||||
@ -1,7 +1,7 @@
|
||||
// SoundEffect.cpp - Implementation of sound effects system
|
||||
#include "SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include "Audio.h"
|
||||
#include "audio/Audio.h"
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
@ -34,7 +34,7 @@ bool SoundEffect::load(const std::string& filePath) {
|
||||
} else if (extension == "mp3") {
|
||||
success = loadMP3(filePath);
|
||||
} else {
|
||||
std::fprintf(stderr, "[SoundEffect] Unsupported file format: %s\n", extension.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Unsupported file format: %s", extension.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ bool SoundEffect::load(const std::string& filePath) {
|
||||
|
||||
void SoundEffect::play(float volume) {
|
||||
if (!loaded || pcmData.empty()) {
|
||||
std::printf("[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu\n", loaded, pcmData.size());
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu", loaded, pcmData.size());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ bool SoundEffect::loadWAV(const std::string& filePath) {
|
||||
Uint32 wavLength;
|
||||
|
||||
if (!SDL_LoadWAV(filePath.c_str(), &wavSpec, &wavBuffer, &wavLength)) {
|
||||
std::fprintf(stderr, "[SoundEffect] Failed to load WAV file %s: %s\n",
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to load WAV file %s: %s",
|
||||
filePath.c_str(), SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@ -147,7 +147,7 @@ bool SoundEffect::loadMP3(const std::string& filePath) {
|
||||
static bool mfInitialized = false;
|
||||
if (!mfInitialized) {
|
||||
if (FAILED(MFStartup(MF_VERSION))) {
|
||||
std::fprintf(stderr, "[SoundEffect] MFStartup failed\n");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] MFStartup failed");
|
||||
return false;
|
||||
}
|
||||
mfInitialized = true;
|
||||
@ -157,12 +157,12 @@ bool SoundEffect::loadMP3(const std::string& filePath) {
|
||||
wchar_t wpath[MAX_PATH];
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, wpath, MAX_PATH);
|
||||
if (!wlen) {
|
||||
std::fprintf(stderr, "[SoundEffect] Failed to convert path to wide char\n");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to convert path to wide char");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(MFCreateSourceReaderFromURL(wpath, nullptr, &reader))) {
|
||||
std::fprintf(stderr, "[SoundEffect] Failed to create source reader for %s\n", filePath.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to create source reader for %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ bool SoundEffect::loadMP3(const std::string& filePath) {
|
||||
}
|
||||
|
||||
if (tempData.empty()) {
|
||||
std::fprintf(stderr, "[SoundEffect] No audio data decoded from %s\n", filePath.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] No audio data decoded from %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ bool SoundEffect::loadMP3(const std::string& filePath) {
|
||||
sampleRate = 44100;
|
||||
return true;
|
||||
#else
|
||||
std::fprintf(stderr, "[SoundEffect] MP3 support not available on this platform\n");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] MP3 support not available on this platform");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
36
src/core/GravityManager.cpp
Normal file
36
src/core/GravityManager.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "GravityManager.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
GravityManager::GravityManager() {
|
||||
levelMultipliers.fill(1.0);
|
||||
}
|
||||
|
||||
void GravityManager::setGlobalMultiplier(double m) {
|
||||
globalMultiplier = std::clamp(m, 0.01, 100.0);
|
||||
}
|
||||
|
||||
double GravityManager::getGlobalMultiplier() const { return globalMultiplier; }
|
||||
|
||||
void GravityManager::setLevelMultiplier(int level, double m) {
|
||||
if (level < 0) return;
|
||||
int idx = level >= 29 ? 29 : level;
|
||||
levelMultipliers[idx] = std::clamp(m, 0.01, 100.0);
|
||||
}
|
||||
|
||||
double GravityManager::getLevelMultiplier(int level) const {
|
||||
int idx = level < 0 ? 0 : (level >= 29 ? 29 : level);
|
||||
return levelMultipliers[idx];
|
||||
}
|
||||
|
||||
double GravityManager::getMsForLevel(int level) const {
|
||||
int idx = level < 0 ? 0 : (level >= 29 ? 29 : level);
|
||||
double frames = static_cast<double>(FRAMES_TABLE[idx]) * levelMultipliers[idx];
|
||||
double result = frames * FRAME_MS * globalMultiplier;
|
||||
return std::max(1.0, result);
|
||||
}
|
||||
|
||||
double GravityManager::getFpsForLevel(int level) const {
|
||||
double ms = getMsForLevel(level);
|
||||
return 1000.0 / ms;
|
||||
}
|
||||
31
src/core/GravityManager.h
Normal file
31
src/core/GravityManager.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
|
||||
class GravityManager {
|
||||
public:
|
||||
GravityManager();
|
||||
|
||||
// Global multiplier applied to all levels
|
||||
void setGlobalMultiplier(double m);
|
||||
double getGlobalMultiplier() const;
|
||||
|
||||
// Per-level multiplier (29 = 29+)
|
||||
void setLevelMultiplier(int level, double m);
|
||||
double getLevelMultiplier(int level) const;
|
||||
|
||||
// Compute ms per cell and fps for a given level
|
||||
double getMsForLevel(int level) const;
|
||||
double getFpsForLevel(int level) const;
|
||||
|
||||
private:
|
||||
static constexpr double NES_FPS = 60.0988;
|
||||
static constexpr double FRAME_MS = 1000.0 / NES_FPS;
|
||||
static constexpr int FRAMES_TABLE[30] = {
|
||||
48,43,38,33,28,23,18,13,8,6,
|
||||
5,5,5,4,4,4,3,3,3,2,
|
||||
2,2,2,2,2,2,2,2,2,1
|
||||
};
|
||||
|
||||
double globalMultiplier{1.0};
|
||||
std::array<double,30> levelMultipliers{}; // default 1.0
|
||||
};
|
||||
46
src/core/StateManager.cpp
Normal file
46
src/core/StateManager.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "StateManager.h"
|
||||
|
||||
StateManager::StateManager(AppState initial)
|
||||
: currentState(initial)
|
||||
{
|
||||
}
|
||||
|
||||
void StateManager::registerHandler(AppState s, EventHandler h) {
|
||||
handlers[static_cast<int>(s)].push_back(std::move(h));
|
||||
}
|
||||
|
||||
void StateManager::registerOnEnter(AppState s, Hook h) {
|
||||
onEnter[static_cast<int>(s)].push_back(std::move(h));
|
||||
}
|
||||
|
||||
void StateManager::registerOnExit(AppState s, Hook h) {
|
||||
onExit[static_cast<int>(s)].push_back(std::move(h));
|
||||
}
|
||||
|
||||
// Overload accepting a no-arg function as handler (wraps it into an EventHandler)
|
||||
void StateManager::registerHandler(AppState s, std::function<void()> h) {
|
||||
EventHandler wrapper = [h = std::move(h)](const SDL_Event&) { h(); };
|
||||
registerHandler(s, std::move(wrapper));
|
||||
}
|
||||
|
||||
void StateManager::setState(AppState s) {
|
||||
if (s == currentState) return;
|
||||
// call exit hooks for current
|
||||
auto it = onExit.find(static_cast<int>(currentState));
|
||||
if (it != onExit.end()) {
|
||||
for (auto &h : it->second) h();
|
||||
}
|
||||
currentState = s;
|
||||
auto it2 = onEnter.find(static_cast<int>(currentState));
|
||||
if (it2 != onEnter.end()) {
|
||||
for (auto &h : it2->second) h();
|
||||
}
|
||||
}
|
||||
|
||||
AppState StateManager::getState() const { return currentState; }
|
||||
|
||||
void StateManager::handleEvent(const SDL_Event& e) {
|
||||
auto it = handlers.find(static_cast<int>(currentState));
|
||||
if (it == handlers.end()) return;
|
||||
for (auto &h : it->second) h(e);
|
||||
}
|
||||
41
src/core/StateManager.h
Normal file
41
src/core/StateManager.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// Application states used across the app
|
||||
enum class AppState {
|
||||
Loading,
|
||||
Menu,
|
||||
Playing,
|
||||
LevelSelect,
|
||||
GameOver
|
||||
};
|
||||
|
||||
// State manager used by main to route events and lifecycle hooks
|
||||
class StateManager {
|
||||
public:
|
||||
using EventHandler = std::function<void(const SDL_Event&)>;
|
||||
using Hook = std::function<void()>;
|
||||
|
||||
StateManager(AppState initial);
|
||||
|
||||
void registerHandler(AppState s, EventHandler h);
|
||||
void registerOnEnter(AppState s, Hook h);
|
||||
void registerOnExit(AppState s, Hook h);
|
||||
|
||||
void registerHandler(AppState s, std::function<void()> h); // overload used in some places
|
||||
|
||||
void setState(AppState s);
|
||||
AppState getState() const;
|
||||
|
||||
void handleEvent(const SDL_Event& e);
|
||||
|
||||
private:
|
||||
AppState currentState;
|
||||
std::unordered_map<int, std::vector<EventHandler>> handlers;
|
||||
std::unordered_map<int, std::vector<Hook>> onEnter;
|
||||
std::unordered_map<int, std::vector<Hook>> onExit;
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
// Game.cpp - Implementation of core Tetris game logic
|
||||
#include "Game.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
// Game.h - Core Tetris game logic (board, piece mechanics, scoring events only)
|
||||
#pragma once
|
||||
#include <array>
|
||||
@ -5,6 +6,8 @@
|
||||
#include <random>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "core/GravityManager.h"
|
||||
|
||||
enum PieceType { I, O, T, S, Z, J, L, PIECE_COUNT };
|
||||
using Shape = std::array<uint16_t, 4>; // four rotation bitmasks
|
||||
@ -96,6 +99,10 @@ private:
|
||||
// Gravity tuning -----------------------------------------------------
|
||||
// Global multiplier applied to all level timings (use to slow/speed whole-game gravity)
|
||||
double gravityGlobalMultiplier{2.8};
|
||||
// Gravity manager encapsulates frames table, multipliers and conversions
|
||||
GravityManager gravityMgr;
|
||||
// Backwards-compatible accessors (delegate to gravityMgr)
|
||||
double computeGravityMsForLevel(int level) const;
|
||||
|
||||
// Internal helpers ----------------------------------------------------
|
||||
void refillBag();
|
||||
@ -274,7 +274,7 @@ void LineEffect::playLineClearSound(int lineCount) {
|
||||
|
||||
audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
|
||||
if (!audioStream) {
|
||||
printf("Warning: Could not create audio stream for line clear effects\n");
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: Could not create audio stream for line clear effects");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -45,15 +45,15 @@ public:
|
||||
bool isActive() const { return state != AnimationState::IDLE; }
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer;
|
||||
AnimationState state;
|
||||
float timer;
|
||||
SDL_Renderer* renderer{nullptr};
|
||||
AnimationState state{AnimationState::IDLE};
|
||||
float timer{0.0f};
|
||||
std::vector<int> clearingRows;
|
||||
std::vector<Particle> particles;
|
||||
std::mt19937 rng;
|
||||
std::mt19937 rng{std::random_device{}()};
|
||||
|
||||
// Audio resources
|
||||
SDL_AudioStream* audioStream;
|
||||
SDL_AudioStream* audioStream{nullptr};
|
||||
std::vector<int16_t> lineClearSample;
|
||||
std::vector<int16_t> tetrisSample;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Font.cpp - implementation of FontAtlas
|
||||
#include "Font.h"
|
||||
// Font.cpp - implementation of FontAtlas (copied into src/graphics)
|
||||
#include "graphics/Font.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
bool FontAtlas::init(const std::string& path, int basePt) { fontPath = path; baseSize = basePt; return true; }
|
||||
@ -1,5 +1,5 @@
|
||||
// Starfield.cpp - implementation
|
||||
#include "Starfield.h"
|
||||
// Starfield.cpp - implementation (copied into src/graphics)
|
||||
#include "graphics/Starfield.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <random>
|
||||
|
||||
63
src/graphics/Starfield3D.h
Normal file
63
src/graphics/Starfield3D.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Starfield3D.h - 3D Parallax Starfield Effect (canonical)
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <array>
|
||||
|
||||
class Starfield3D {
|
||||
public:
|
||||
Starfield3D();
|
||||
~Starfield3D() = default;
|
||||
|
||||
void init(int width, int height, int starCount = 160);
|
||||
void update(float deltaTime);
|
||||
void draw(SDL_Renderer* renderer);
|
||||
void resize(int width, int height);
|
||||
|
||||
private:
|
||||
struct Star3D {
|
||||
float x, y, z;
|
||||
float vx, vy, vz;
|
||||
float targetVx, targetVy, targetVz;
|
||||
float changeTimer;
|
||||
bool changing;
|
||||
int type;
|
||||
};
|
||||
|
||||
// Helpers used by the implementation
|
||||
void createStarfield();
|
||||
void updateStar(int index);
|
||||
void setRandomDirection(Star3D& star);
|
||||
float randomFloat(float min, float max);
|
||||
int randomRange(int min, int max);
|
||||
void drawStar(SDL_Renderer* renderer, float x, float y, int type);
|
||||
|
||||
std::vector<Star3D> stars;
|
||||
int width{0}, height{0};
|
||||
float centerX{0}, centerY{0};
|
||||
|
||||
// Random number generator
|
||||
std::mt19937 rng;
|
||||
|
||||
// Visual / behavioral constants (tweakable)
|
||||
inline static constexpr float MAX_VELOCITY = 0.5f;
|
||||
inline static constexpr float REVERSE_PROBABILITY = 0.12f;
|
||||
inline static constexpr float STAR_SPEED = 0.6f;
|
||||
inline static constexpr float MAX_DEPTH = 120.0f;
|
||||
inline static constexpr float DIRECTION_CHANGE_PROBABILITY = 0.002f;
|
||||
inline static constexpr float VELOCITY_CHANGE = 0.02f;
|
||||
inline static constexpr float MIN_Z = 0.1f;
|
||||
inline static constexpr float MAX_Z = MAX_DEPTH;
|
||||
inline static constexpr float DEPTH_FACTOR = 320.0f;
|
||||
|
||||
inline static constexpr int COLOR_COUNT = 5;
|
||||
inline static const std::array<SDL_Color, COLOR_COUNT> STAR_COLORS = {
|
||||
SDL_Color{255,255,255,255},
|
||||
SDL_Color{200,200,255,255},
|
||||
SDL_Color{255,220,180,255},
|
||||
SDL_Color{180,220,255,255},
|
||||
SDL_Color{255,180,200,255}
|
||||
};
|
||||
};
|
||||
113
src/main.cpp
113
src/main.cpp
@ -14,19 +14,20 @@
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include "Audio.h"
|
||||
#include "SoundEffect.h"
|
||||
#include "audio/Audio.h"
|
||||
#include "audio/SoundEffect.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Scores.h"
|
||||
#include "Starfield.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include "graphics/Starfield.h"
|
||||
#include "Starfield3D.h"
|
||||
#include "Font.h"
|
||||
#include "LineEffect.h"
|
||||
#include "graphics/Font.h"
|
||||
#include "gameplay/LineEffect.h"
|
||||
#include "states/State.h"
|
||||
#include "states/LoadingState.h"
|
||||
#include "states/MenuState.h"
|
||||
#include "states/PlayingState.h"
|
||||
#include "audio/MenuWrappers.h"
|
||||
|
||||
// Debug logging removed: no-op in this build (previously LOG_DEBUG)
|
||||
|
||||
@ -331,7 +332,7 @@ static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musi
|
||||
// Starfield now managed by Starfield class
|
||||
|
||||
// State manager integration (scaffolded in StateManager.h)
|
||||
#include "StateManager.h"
|
||||
#include "core/StateManager.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Intro/Menu state variables
|
||||
@ -341,8 +342,6 @@ static bool showLevelPopup = false;
|
||||
static bool showSettingsPopup = false;
|
||||
static bool musicEnabled = true;
|
||||
static int hoveredButton = -1; // -1 = none, 0 = play, 1 = level, 2 = settings
|
||||
// Shared texture for fireworks particles (uses the blocks sheet)
|
||||
static SDL_Texture* fireworksBlocksTex = nullptr;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Tetris Block Fireworks for intro animation (block particles)
|
||||
@ -389,44 +388,7 @@ struct TetrisFirework {
|
||||
}
|
||||
return !particles.empty();
|
||||
}
|
||||
void draw(SDL_Renderer* renderer) {
|
||||
for (auto &p : particles) {
|
||||
if (fireworksBlocksTex) {
|
||||
// Apply per-particle alpha and color variants by modulating the blocks texture
|
||||
// Save previous mods (assume single-threaded rendering)
|
||||
Uint8 prevA = 255;
|
||||
SDL_GetTextureAlphaMod(fireworksBlocksTex, &prevA);
|
||||
Uint8 setA = Uint8(std::max(0.0f, std::min(1.0f, p.alpha)) * 255.0f);
|
||||
SDL_SetTextureAlphaMod(fireworksBlocksTex, setA);
|
||||
|
||||
// Color modes: tint the texture where appropriate
|
||||
if (mode == 1) {
|
||||
// red
|
||||
SDL_SetTextureColorMod(fireworksBlocksTex, 220, 60, 60);
|
||||
} else if (mode == 2) {
|
||||
// green
|
||||
SDL_SetTextureColorMod(fireworksBlocksTex, 80, 200, 80);
|
||||
} else if (mode == 3) {
|
||||
// tint to the particle's block color
|
||||
SDL_Color c = COLORS[p.blockType + 1];
|
||||
SDL_SetTextureColorMod(fireworksBlocksTex, c.r, c.g, c.b);
|
||||
} else {
|
||||
// random: no tint (use texture colors directly)
|
||||
SDL_SetTextureColorMod(fireworksBlocksTex, 255, 255, 255);
|
||||
}
|
||||
|
||||
drawBlockTexture(renderer, fireworksBlocksTex, p.x - p.size * 0.5f, p.y - p.size * 0.5f, p.size, p.blockType);
|
||||
|
||||
// Restore alpha and color modulation
|
||||
SDL_SetTextureAlphaMod(fireworksBlocksTex, prevA);
|
||||
SDL_SetTextureColorMod(fireworksBlocksTex, 255, 255, 255);
|
||||
} else {
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, Uint8(p.alpha * 255));
|
||||
SDL_FRect rect{p.x - p.size/2, p.y - p.size/2, p.size, p.size};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Drawing is handled by drawFireworks_impl which accepts the texture to use.
|
||||
};
|
||||
|
||||
static std::vector<TetrisFirework> fireworks;
|
||||
@ -455,13 +417,41 @@ static void updateFireworks(double frameMs) {
|
||||
}
|
||||
}
|
||||
|
||||
static void drawFireworks(SDL_Renderer* renderer) {
|
||||
for (auto& f : fireworks) f.draw(renderer);
|
||||
// Primary implementation that accepts a texture pointer
|
||||
static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture* blocksTexture) {
|
||||
for (auto& f : fireworks) {
|
||||
// Particle draw uses the texture pointer passed into drawBlockTexture calls from f.draw
|
||||
// We'll set a thread-local-ish variable by passing the texture as an argument to draw
|
||||
// routines or using the provided texture in the particle's draw path.
|
||||
// For simplicity, the particle draw function below will reference a global symbol
|
||||
// via an argument — we adapt by providing the texture when calling drawBlockTexture.
|
||||
// Implementation: call a small lambda that temporarily binds the texture for drawBlockTexture.
|
||||
struct Drawer { SDL_Renderer* r; SDL_Texture* tex; void drawParticle(struct BlockParticle& p) {
|
||||
if (tex) {
|
||||
Uint8 prevA = 255;
|
||||
SDL_GetTextureAlphaMod(tex, &prevA);
|
||||
Uint8 setA = Uint8(std::max(0.0f, std::min(1.0f, p.alpha)) * 255.0f);
|
||||
SDL_SetTextureAlphaMod(tex, setA);
|
||||
// Note: color modulation will be applied by callers of drawBlockTexture where needed
|
||||
// but we mimic behavior from previous implementation by leaving color mod as default.
|
||||
drawBlockTexture(r, tex, p.x - p.size * 0.5f, p.y - p.size * 0.5f, p.size, p.blockType);
|
||||
SDL_SetTextureAlphaMod(tex, prevA);
|
||||
SDL_SetTextureColorMod(tex, 255, 255, 255);
|
||||
} else {
|
||||
SDL_SetRenderDrawColor(r, 255, 255, 255, Uint8(p.alpha * 255));
|
||||
SDL_FRect rect{p.x - p.size/2, p.y - p.size/2, p.size, p.size};
|
||||
SDL_RenderFillRect(r, &rect);
|
||||
}
|
||||
}
|
||||
} drawer{renderer, blocksTexture};
|
||||
for (auto &p : f.particles) {
|
||||
drawer.drawParticle(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// External wrappers for use by other translation units (MenuState)
|
||||
// These call the internal helpers above so we don't change existing static linkage.
|
||||
void menu_drawFireworks(SDL_Renderer* renderer) { drawFireworks(renderer); }
|
||||
// Expect callers to pass the blocks texture via StateContext so we avoid globals.
|
||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) { drawFireworks_impl(renderer, blocksTex); }
|
||||
void menu_updateFireworks(double frameMs) { updateFireworks(frameMs); }
|
||||
double menu_getLogoAnimCounter() { return logoAnimCounter; }
|
||||
int menu_getHoveredButton() { return hoveredButton; }
|
||||
@ -474,20 +464,20 @@ int main(int, char **)
|
||||
int sdlInitRes = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
if (sdlInitRes < 0)
|
||||
{
|
||||
std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
int ttfInitRes = TTF_Init();
|
||||
if (ttfInitRes < 0)
|
||||
{
|
||||
std::fprintf(stderr, "TTF_Init failed\n");
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TTF_Init failed");
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
SDL_Window *window = SDL_CreateWindow("Tetris (SDL3)", LOGICAL_W, LOGICAL_H, SDL_WINDOW_RESIZABLE);
|
||||
if (!window)
|
||||
{
|
||||
std::fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed: %s", SDL_GetError());
|
||||
TTF_Quit();
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
@ -495,7 +485,7 @@ int main(int, char **)
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(window, nullptr);
|
||||
if (!renderer)
|
||||
{
|
||||
std::fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer failed: %s", SDL_GetError());
|
||||
SDL_DestroyWindow(window);
|
||||
TTF_Quit();
|
||||
SDL_Quit();
|
||||
@ -556,6 +546,9 @@ int main(int, char **)
|
||||
} else {
|
||||
(void)0;
|
||||
}
|
||||
|
||||
// Note: `backgroundTex` is owned by main and passed into `StateContext::backgroundTex` below.
|
||||
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
||||
|
||||
// Level background caching system
|
||||
SDL_Texture *levelBackgroundTex = nullptr;
|
||||
@ -575,6 +568,7 @@ int main(int, char **)
|
||||
} else {
|
||||
(void)0;
|
||||
}
|
||||
// No global exposure of blocksTex; states receive textures via StateContext.
|
||||
|
||||
if (!blocksTex) {
|
||||
(void)0;
|
||||
@ -613,8 +607,7 @@ int main(int, char **)
|
||||
(void)0;
|
||||
}
|
||||
|
||||
// Provide the blocks sheet to the fireworks system (for block particles)
|
||||
fireworksBlocksTex = blocksTex;
|
||||
// Provide the blocks sheet to the fireworks system through StateContext (no globals).
|
||||
|
||||
// Default start level selection: 0
|
||||
int startLevelSelection = 0;
|
||||
@ -656,7 +649,7 @@ int main(int, char **)
|
||||
}
|
||||
}
|
||||
|
||||
std::fprintf(stderr, "Failed to load sound: %s (tried both WAV and MP3)\n", id.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load sound: %s (tried both WAV and MP3)", id.c_str());
|
||||
};
|
||||
|
||||
loadSoundWithFallback("nice_combo", "nice_combo");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Scores.cpp - Implementation of ScoreManager
|
||||
// Scores.cpp - Implementation of ScoreManager (copied into src/persistence)
|
||||
#include "Scores.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <fstream>
|
||||
@ -1,6 +1,6 @@
|
||||
// LoadingState.cpp
|
||||
#include "LoadingState.h"
|
||||
#include "../Game.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// MenuState.cpp
|
||||
#include "MenuState.h"
|
||||
#include "../Scores.h"
|
||||
#include "../Font.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include "graphics/Font.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
@ -11,25 +11,11 @@
|
||||
static constexpr int LOGICAL_W = 1200;
|
||||
static constexpr int LOGICAL_H = 1000;
|
||||
|
||||
extern bool showLevelPopup; // from main
|
||||
extern bool showSettingsPopup; // from main
|
||||
extern bool musicEnabled; // from main
|
||||
extern int hoveredButton; // from main
|
||||
// Call wrappers defined in main.cpp
|
||||
extern void menu_drawFireworks(SDL_Renderer* renderer);
|
||||
extern void menu_updateFireworks(double frameMs);
|
||||
extern double menu_getLogoAnimCounter();
|
||||
extern int menu_getHoveredButton();
|
||||
extern void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected);
|
||||
|
||||
// Menu button wrapper implemented in main.cpp
|
||||
extern void menu_drawMenuButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, SDL_Color bgColor, SDL_Color borderColor);
|
||||
|
||||
// wrappers for popups (defined in main.cpp)
|
||||
extern void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel);
|
||||
extern void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled);
|
||||
// Shared flags and resources are provided via StateContext `ctx`.
|
||||
// Removed fragile extern declarations and use `ctx.showLevelPopup`, `ctx.showSettingsPopup`,
|
||||
// `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals.
|
||||
// Menu helper wrappers are declared in a shared header implemented in main.cpp
|
||||
#include "../audio/MenuWrappers.h"
|
||||
|
||||
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
@ -79,7 +65,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
}
|
||||
|
||||
// Fireworks (draw above high scores / near buttons)
|
||||
menu_drawFireworks(renderer);
|
||||
menu_drawFireworks(renderer, ctx.blocksTex);
|
||||
|
||||
// Score list and top players with a sine-wave vertical animation (use pixelFont for retro look)
|
||||
float topPlayersY = LOGICAL_H * 0.30f + contentOffsetY; // more top padding
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "PlayingState.h"
|
||||
#include "../Game.h"
|
||||
#include "../LineEffect.h"
|
||||
#include "../Scores.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include "gameplay/LineEffect.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
PlayingState::PlayingState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
@ -29,6 +29,8 @@ struct StateContext {
|
||||
int logoSmallW = 0;
|
||||
int logoSmallH = 0;
|
||||
SDL_Texture* backgroundTex = nullptr;
|
||||
// backgroundTex is set once in `main.cpp` and passed to states via this context.
|
||||
// Prefer reading this field instead of relying on any `extern SDL_Texture*` globals.
|
||||
SDL_Texture* blocksTex = nullptr;
|
||||
|
||||
// Audio / SFX - forward declared types in main
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": ".."
|
||||
},
|
||||
{
|
||||
"path": "../../../games/Tetris"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#59140D",
|
||||
"titleBar.activeBackground": "#7D1D12",
|
||||
"titleBar.activeForeground": "#FFFCFC"
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/ui/MenuWrappers.h
Normal file
22
src/ui/MenuWrappers.h
Normal file
@ -0,0 +1,22 @@
|
||||
// MenuWrappers.h - function prototypes for menu helper wrappers implemented in main.cpp
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
|
||||
class FontAtlas;
|
||||
|
||||
// Draw fireworks using the provided blocks texture (may be nullptr)
|
||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex);
|
||||
void menu_updateFireworks(double frameMs);
|
||||
double menu_getLogoAnimCounter();
|
||||
int menu_getHoveredButton();
|
||||
|
||||
void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected);
|
||||
|
||||
void menu_drawMenuButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||
const std::string& label, SDL_Color bgColor, SDL_Color borderColor);
|
||||
|
||||
void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel);
|
||||
void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled);
|
||||
Reference in New Issue
Block a user