// LineEffect.cpp - Implementation of line clearing visual and audio effects #include "LineEffect.h" #include #include #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(rand()) / RAND_MAX * 4.0f), alpha(1.0f) { // Random velocity for explosive effect float angle = static_cast(rand()) / RAND_MAX * 2.0f * M_PI; float speed = 80.0f + static_cast(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(140 + rand() % 100), static_cast(30 + rand() % 50), 255}; break; case 1: // Yellow/Gold color = {255, 255, static_cast(100 + rand() % 155), 255}; break; case 2: // White color = {255, 255, 255, 255}; break; case 3: // Red/Pink color = {255, static_cast(100 + rand() % 100), static_cast(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(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(size); ++i) { for (int j = 0; j < static_cast(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(0.2f * sampleRate); lineClearSample.resize(duration * 2); // Stereo for (int i = 0; i < duration; ++i) { float t = static_cast(i) / sampleRate; float wave = std::sin(2.0f * M_PI * 440.0f * t) * 0.3f; // 440Hz sine wave int16_t sample = static_cast(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(0.4f * sampleRate); tetrisSample.resize(duration * 2); for (int i = 0; i < duration; ++i) { float t = static_cast(i) / sampleRate; float wave = std::sin(2.0f * M_PI * 880.0f * t) * 0.4f; // 880Hz sine wave int16_t sample = static_cast(wave * 32767.0f); tetrisSample[i * 2] = sample; // Left channel tetrisSample[i * 2 + 1] = sample; // Right channel } } void LineEffect::startLineClear(const std::vector& 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(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(i) / (particlesPerRow - 1)) * (10 * blockSize); float y = gridY + row * blockSize + blockSize / 2.0f; // Add some randomness to position x += (static_cast(rand()) / RAND_MAX - 0.5f) * blockSize * 0.8f; y += (static_cast(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(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(gridX - 4), static_cast(gridY + row * blockSize - 4), static_cast(10 * blockSize + 8), static_cast(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* 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(sample->size() * sizeof(int16_t))); } }