Initial release: SDL Windows Tetris

This commit is contained in:
2025-08-15 11:01:48 +02:00
commit d161b2c550
196 changed files with 5944 additions and 0 deletions

297
src/LineEffect.cpp Normal file
View File

@ -0,0 +1,297 @@
// LineEffect.cpp - Implementation of line clearing visual and audio effects
#include "LineEffect.h"
#include <algorithm>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
LineEffect::Particle::Particle(float px, float py)
: x(px), y(py), size(1.5f + static_cast<float>(rand()) / RAND_MAX * 4.0f), alpha(1.0f) {
// Random velocity for explosive effect
float angle = static_cast<float>(rand()) / RAND_MAX * 2.0f * M_PI;
float speed = 80.0f + static_cast<float>(rand()) / RAND_MAX * 150.0f; // More explosive speed
vx = std::cos(angle) * speed;
vy = std::sin(angle) * speed - 30.0f; // Less upward bias for more spread
// Bright explosive colors (oranges, yellows, whites, reds)
int colorType = rand() % 4;
switch (colorType) {
case 0: // Orange/Fire
color = {255, static_cast<Uint8>(140 + rand() % 100), static_cast<Uint8>(30 + rand() % 50), 255};
break;
case 1: // Yellow/Gold
color = {255, 255, static_cast<Uint8>(100 + rand() % 155), 255};
break;
case 2: // White
color = {255, 255, 255, 255};
break;
case 3: // Red/Pink
color = {255, static_cast<Uint8>(100 + rand() % 100), static_cast<Uint8>(100 + rand() % 100), 255};
break;
}
}
void LineEffect::Particle::update() {
x += vx * 0.016f; // Assume ~60 FPS
y += vy * 0.016f;
vy += 250.0f * 0.016f; // Stronger gravity for explosive effect
vx *= 0.98f; // Air resistance
alpha -= 0.12f; // Fast fade for explosive burst
if (alpha < 0.0f) alpha = 0.0f;
// Shrink particles as they fade
if (size > 0.5f) size -= 0.05f;
}
void LineEffect::Particle::render(SDL_Renderer* renderer) {
if (alpha <= 0.0f) return;
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
Uint8 adjustedAlpha = static_cast<Uint8>(alpha * 255.0f);
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, adjustedAlpha);
// Draw particle as a small circle
for (int i = 0; i < static_cast<int>(size); ++i) {
for (int j = 0; j < static_cast<int>(size); ++j) {
float dx = i - size/2.0f;
float dy = j - size/2.0f;
if (dx*dx + dy*dy <= (size/2.0f)*(size/2.0f)) {
SDL_RenderPoint(renderer, x + dx, y + dy);
}
}
}
}
LineEffect::LineEffect() : renderer(nullptr), state(AnimationState::IDLE), timer(0.0f),
rng(std::random_device{}()), audioStream(nullptr) {
}
LineEffect::~LineEffect() {
shutdown();
}
bool LineEffect::init(SDL_Renderer* r) {
renderer = r;
initAudio();
return true;
}
void LineEffect::shutdown() {
if (audioStream) {
SDL_DestroyAudioStream(audioStream);
audioStream = nullptr;
}
}
void LineEffect::initAudio() {
// For now, we'll generate simple beep sounds procedurally
// In a full implementation, you'd load WAV files
// Generate a simple line clear beep (440Hz for 0.2 seconds)
int sampleRate = 44100;
int duration = static_cast<int>(0.2f * sampleRate);
lineClearSample.resize(duration * 2); // Stereo
for (int i = 0; i < duration; ++i) {
float t = static_cast<float>(i) / sampleRate;
float wave = std::sin(2.0f * M_PI * 440.0f * t) * 0.3f; // 440Hz sine wave
int16_t sample = static_cast<int16_t>(wave * 32767.0f);
lineClearSample[i * 2] = sample; // Left channel
lineClearSample[i * 2 + 1] = sample; // Right channel
}
// Generate a higher pitched tetris sound (880Hz for 0.4 seconds)
duration = static_cast<int>(0.4f * sampleRate);
tetrisSample.resize(duration * 2);
for (int i = 0; i < duration; ++i) {
float t = static_cast<float>(i) / sampleRate;
float wave = std::sin(2.0f * M_PI * 880.0f * t) * 0.4f; // 880Hz sine wave
int16_t sample = static_cast<int16_t>(wave * 32767.0f);
tetrisSample[i * 2] = sample; // Left channel
tetrisSample[i * 2 + 1] = sample; // Right channel
}
}
void LineEffect::startLineClear(const std::vector<int>& rows, int gridX, int gridY, int blockSize) {
if (rows.empty()) return;
clearingRows = rows;
state = AnimationState::FLASH_WHITE;
timer = 0.0f;
particles.clear();
// Create particles for each clearing row
for (int row : rows) {
createParticles(row, gridX, gridY, blockSize);
}
// Play appropriate sound
playLineClearSound(static_cast<int>(rows.size()));
}
void LineEffect::createParticles(int row, int gridX, int gridY, int blockSize) {
// Create particles spread across the row with explosive pattern
int particlesPerRow = 35; // More particles for dramatic explosion effect
for (int i = 0; i < particlesPerRow; ++i) {
// Create particles along the entire row width
float x = gridX + (static_cast<float>(i) / (particlesPerRow - 1)) * (10 * blockSize);
float y = gridY + row * blockSize + blockSize / 2.0f;
// Add some randomness to position
x += (static_cast<float>(rand()) / RAND_MAX - 0.5f) * blockSize * 0.8f;
y += (static_cast<float>(rand()) / RAND_MAX - 0.5f) * blockSize * 0.6f;
particles.emplace_back(x, y);
}
}
bool LineEffect::update(float deltaTime) {
if (state == AnimationState::IDLE) return true;
timer += deltaTime;
switch (state) {
case AnimationState::FLASH_WHITE:
if (timer >= FLASH_DURATION) {
state = AnimationState::EXPLODE_BLOCKS;
timer = 0.0f;
}
break;
case AnimationState::EXPLODE_BLOCKS:
updateParticles();
if (timer >= EXPLODE_DURATION) {
state = AnimationState::BLOCKS_DROP;
timer = 0.0f;
}
break;
case AnimationState::BLOCKS_DROP:
updateParticles();
if (timer >= DROP_DURATION) {
state = AnimationState::IDLE;
clearingRows.clear();
particles.clear();
return true; // Effect complete
}
break;
case AnimationState::IDLE:
return true;
}
return false; // Effect still running
}
void LineEffect::updateParticles() {
// Update all particles
for (auto& particle : particles) {
particle.update();
}
// Remove dead particles
particles.erase(
std::remove_if(particles.begin(), particles.end(),
[](const Particle& p) { return !p.isAlive(); }),
particles.end()
);
}
void LineEffect::render(SDL_Renderer* renderer, int gridX, int gridY, int blockSize) {
if (state == AnimationState::IDLE) return;
switch (state) {
case AnimationState::FLASH_WHITE:
renderFlash(gridX, gridY, blockSize);
break;
case AnimationState::EXPLODE_BLOCKS:
renderExplosion();
break;
case AnimationState::BLOCKS_DROP:
renderExplosion();
break;
case AnimationState::IDLE:
break;
}
}
void LineEffect::renderFlash(int gridX, int gridY, int blockSize) {
// Create a flashing white effect with varying opacity
float progress = timer / FLASH_DURATION;
float flashIntensity = std::sin(progress * M_PI * 6.0f) * 0.5f + 0.5f; // Fewer flashes for quicker effect
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
Uint8 alpha = static_cast<Uint8>(flashIntensity * 180.0f); // Slightly less intense
for (int row : clearingRows) {
// Draw white rectangle covering the entire row with blue glow effect
SDL_SetRenderDrawColor(renderer, 255, 255, 255, alpha);
SDL_FRect flashRect = {
static_cast<float>(gridX - 4),
static_cast<float>(gridY + row * blockSize - 4),
static_cast<float>(10 * blockSize + 8),
static_cast<float>(blockSize + 8)
};
SDL_RenderFillRect(renderer, &flashRect);
// Add blue glow border
SDL_SetRenderDrawColor(renderer, 100, 150, 255, alpha / 2);
for (int i = 1; i <= 3; ++i) {
SDL_FRect glowRect = {
flashRect.x - i,
flashRect.y - i,
flashRect.w + 2*i,
flashRect.h + 2*i
};
SDL_RenderRect(renderer, &glowRect);
}
}
}
void LineEffect::renderExplosion() {
for (auto& particle : particles) {
particle.render(renderer);
}
}
void LineEffect::playLineClearSound(int lineCount) {
if (!audioStream) {
// Create audio stream for sound effects
SDL_AudioSpec spec = {};
spec.format = SDL_AUDIO_S16;
spec.channels = 2;
spec.freq = 44100;
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");
return;
}
}
// Choose appropriate sound based on line count
const std::vector<int16_t>* sample = nullptr;
if (lineCount == 4) {
sample = &tetrisSample; // Special sound for Tetris
printf("TETRIS! 4 lines cleared!\n");
} else {
sample = &lineClearSample; // Regular line clear sound
printf("Line clear: %d lines\n", lineCount);
}
if (sample && !sample->empty()) {
SDL_PutAudioStreamData(audioStream, sample->data(),
static_cast<int>(sample->size() * sizeof(int16_t)));
}
}