diff --git a/src/Confetti.cpp b/src/Confetti.cpp new file mode 100644 index 0000000..32a6080 --- /dev/null +++ b/src/Confetti.cpp @@ -0,0 +1,140 @@ +/* +Copyright (c) 2025 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "Confetti.hpp" +#include +#include +#include + +Confetti::Confetti() { +} + +Confetti::~Confetti() { +} + +void Confetti::Burst(int screenWidth, int screenHeight) { + particles.clear(); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution xDist(0.0f, static_cast(screenWidth)); + std::uniform_real_distribution yDist(-100.0f, 0.0f); + std::uniform_real_distribution vxDist(-100.0f, 100.0f); + std::uniform_real_distribution vyDist(50.0f, 200.0f); + std::uniform_real_distribution rotDist(0.0f, 360.0f); + std::uniform_real_distribution rotSpeedDist(-360.0f, 360.0f); + std::uniform_real_distribution sizeDist(4.0f, 12.0f); + std::uniform_real_distribution gravityDist(100.0f, 300.0f); + + // Create colorful confetti particles + const int numParticles = 200; + for (int i = 0; i < numParticles; ++i) { + Particle particle; + particle.x = xDist(gen); + particle.y = yDist(gen); + particle.vx = vxDist(gen); + particle.vy = vyDist(gen); + particle.rotation = rotDist(gen); + particle.rotationSpeed = rotSpeedDist(gen); + particle.size = sizeDist(gen); + particle.lifetime = 5.0f; // 5 seconds + particle.gravity = gravityDist(gen); + + // Bright, saturated colors + int colorChoice = i % 6; + switch (colorChoice) { + case 0: particle.r = 255; particle.g = 0; particle.b = 0; break; // Red + case 1: particle.r = 0; particle.g = 255; particle.b = 0; break; // Green + case 2: particle.r = 0; particle.g = 0; particle.b = 255; break; // Blue + case 3: particle.r = 255; particle.g = 255; particle.b = 0; break; // Yellow + case 4: particle.r = 255; particle.g = 0; particle.b = 255; break; // Magenta + case 5: particle.r = 0; particle.g = 255; particle.b = 255; break; // Cyan + } + + particles.push_back(particle); + } +} + +void Confetti::Update(float deltaTime) { + // Update all confetti particles + for (auto it = particles.begin(); it != particles.end();) { + it->lifetime -= deltaTime; + + if (it->lifetime <= 0.0f) { + it = particles.erase(it); + } else { + // Update position + it->x += it->vx * deltaTime; + it->y += it->vy * deltaTime; + + // Apply gravity + it->vy += it->gravity * deltaTime; + + // Update rotation + it->rotation += it->rotationSpeed * deltaTime; + + // Add some air resistance + it->vx *= 0.99f; + + ++it; + } + } +} + +void Confetti::Draw(SDL_Renderer* target) { + for (const auto& particle : particles) { + // Calculate alpha based on lifetime (fade out in last second) + Uint8 alpha = 255; + if (particle.lifetime < 1.0f) { + alpha = static_cast(255 * particle.lifetime); + } + + // Draw confetti as small filled rectangles + float halfSize = particle.size / 2.0f; + float angle = particle.rotation * M_PI / 180.0f; + + // Simple rectangle for confetti pieces + Sint16 x1 = static_cast(particle.x - halfSize * std::cos(angle)); + Sint16 y1 = static_cast(particle.y - halfSize * std::sin(angle)); + Sint16 x2 = static_cast(particle.x + halfSize * std::cos(angle)); + Sint16 y2 = static_cast(particle.y + halfSize * std::sin(angle)); + Sint16 x3 = static_cast(particle.x + halfSize * std::cos(angle) - halfSize * std::sin(angle)); + Sint16 y3 = static_cast(particle.y + halfSize * std::sin(angle) + halfSize * std::cos(angle)); + Sint16 x4 = static_cast(particle.x - halfSize * std::cos(angle) - halfSize * std::sin(angle)); + Sint16 y4 = static_cast(particle.y - halfSize * std::sin(angle) + halfSize * std::cos(angle)); + + // Draw a filled polygon (quad) + Sint16 vx[] = {x1, x2, x3, x4}; + Sint16 vy[] = {y1, y2, y3, y4}; + filledPolygonRGBA(target, vx, vy, 4, particle.r, particle.g, particle.b, alpha); + } +} + +void Confetti::Clear() { + particles.clear(); +} + +bool Confetti::IsActive() const { + return !particles.empty(); +} diff --git a/src/Confetti.hpp b/src/Confetti.hpp new file mode 100644 index 0000000..94c04c1 --- /dev/null +++ b/src/Confetti.hpp @@ -0,0 +1,85 @@ +/* +Copyright (c) 2025 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef CONFETTI_HPP +#define CONFETTI_HPP + +#include +#include + +/** + * Confetti particle system for celebrating puzzle completion. + * Creates and animates colorful particles with physics simulation. + */ +class Confetti { +public: + Confetti(); + ~Confetti(); + + /** + * Trigger a confetti burst + * @param screenWidth Width of the screen for particle distribution + * @param screenHeight Height of the screen (particles spawn from top) + */ + void Burst(int screenWidth, int screenHeight); + + /** + * Update particle physics + * @param deltaTime Time elapsed since last update in seconds + */ + void Update(float deltaTime); + + /** + * Render all active confetti particles + * @param target SDL renderer to draw to + */ + void Draw(SDL_Renderer* target); + + /** + * Clear all active particles + */ + void Clear(); + + /** + * Check if there are active particles + * @return true if confetti is still animating + */ + bool IsActive() const; + +private: + struct Particle { + float x, y; // Position + float vx, vy; // Velocity + float rotation; // Rotation angle + float rotationSpeed; // Rotation speed + Uint8 r, g, b; // Color + float size; // Size of the confetti + float lifetime; // Time to live + float gravity; // Gravity multiplier + }; + + std::vector particles; +}; + +#endif // CONFETTI_HPP diff --git a/src/PuzzleSingleImageState.cpp b/src/PuzzleSingleImageState.cpp index 080b036..f4960d9 100644 --- a/src/PuzzleSingleImageState.cpp +++ b/src/PuzzleSingleImageState.cpp @@ -27,8 +27,6 @@ https://github.com/sago007/saland #include "globals.hpp" #include #include -#include -#include #include "SagoImGui.hpp" #include "rhash.hpp" @@ -128,18 +126,16 @@ void PuzzleSingleImageState::Draw(SDL_Renderer* target) { } ImGui::EndMainMenuBar(); - // Draw confetti on top of everything - DrawConfetti(target); + confetti.Draw(target); } void PuzzleSingleImageState::Update() { - // Update confetti animation static Uint32 lastTime = SDL_GetTicks(); Uint32 currentTime = SDL_GetTicks(); float deltaTime = (currentTime - lastTime) / 1000.0f; lastTime = currentTime; - UpdateConfetti(deltaTime); + confetti.Update(deltaTime); // If the mouse button is released, make bMouseUp equal true if ( !(SDL_GetMouseState(nullptr, nullptr)&SDL_BUTTON(1)) ) { @@ -324,7 +320,7 @@ void PuzzleSingleImageState::Shuffle() { } shuffeled = true; puzzleSolved = false; // Reset solved state when shuffling - confetti.clear(); // Clear any existing confetti + confetti.Clear(); // Clear any existing confetti } @@ -343,105 +339,6 @@ void PuzzleSingleImageState::CheckSolved() { // If puzzle just became solved, trigger confetti if (!wasSolved && !puzzleSolved) { puzzleSolved = true; - InitConfetti(); - } -} - -void PuzzleSingleImageState::InitConfetti() { - confetti.clear(); - - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution xDist(0.0f, static_cast(globalData.xsize)); - std::uniform_real_distribution yDist(-100.0f, 0.0f); - std::uniform_real_distribution vxDist(-100.0f, 100.0f); - std::uniform_real_distribution vyDist(50.0f, 200.0f); - std::uniform_real_distribution rotDist(0.0f, 360.0f); - std::uniform_real_distribution rotSpeedDist(-360.0f, 360.0f); - std::uniform_real_distribution sizeDist(4.0f, 12.0f); - std::uniform_real_distribution gravityDist(100.0f, 300.0f); - std::uniform_int_distribution colorDist(0, 255); - - // Create colorful confetti particles - const int numParticles = 200; - for (int i = 0; i < numParticles; ++i) { - ConfettiParticle particle; - particle.x = xDist(gen); - particle.y = yDist(gen); - particle.vx = vxDist(gen); - particle.vy = vyDist(gen); - particle.rotation = rotDist(gen); - particle.rotationSpeed = rotSpeedDist(gen); - particle.size = sizeDist(gen); - particle.lifetime = 5.0f; // 5 seconds - particle.gravity = gravityDist(gen); - - // Bright, saturated colors - int colorChoice = i % 6; - switch (colorChoice) { - case 0: particle.r = 255; particle.g = 0; particle.b = 0; break; // Red - case 1: particle.r = 0; particle.g = 255; particle.b = 0; break; // Green - case 2: particle.r = 0; particle.g = 0; particle.b = 255; break; // Blue - case 3: particle.r = 255; particle.g = 255; particle.b = 0; break; // Yellow - case 4: particle.r = 255; particle.g = 0; particle.b = 255; break; // Magenta - case 5: particle.r = 0; particle.g = 255; particle.b = 255; break; // Cyan - } - - confetti.push_back(particle); - } -} - -void PuzzleSingleImageState::UpdateConfetti(float deltaTime) { - // Update all confetti particles - for (auto it = confetti.begin(); it != confetti.end();) { - it->lifetime -= deltaTime; - - if (it->lifetime <= 0.0f) { - it = confetti.erase(it); - } else { - // Update position - it->x += it->vx * deltaTime; - it->y += it->vy * deltaTime; - - // Apply gravity - it->vy += it->gravity * deltaTime; - - // Update rotation - it->rotation += it->rotationSpeed * deltaTime; - - // Add some air resistance - it->vx *= 0.99f; - - ++it; - } - } -} - -void PuzzleSingleImageState::DrawConfetti(SDL_Renderer* target) { - for (const auto& particle : confetti) { - // Calculate alpha based on lifetime (fade out in last second) - Uint8 alpha = 255; - if (particle.lifetime < 1.0f) { - alpha = static_cast(255 * particle.lifetime); - } - - // Draw confetti as small filled rectangles - float halfSize = particle.size / 2.0f; - float angle = particle.rotation * M_PI / 180.0f; - - // Simple rectangle for confetti pieces - Sint16 x1 = static_cast(particle.x - halfSize * std::cos(angle)); - Sint16 y1 = static_cast(particle.y - halfSize * std::sin(angle)); - Sint16 x2 = static_cast(particle.x + halfSize * std::cos(angle)); - Sint16 y2 = static_cast(particle.y + halfSize * std::sin(angle)); - Sint16 x3 = static_cast(particle.x + halfSize * std::cos(angle) - halfSize * std::sin(angle)); - Sint16 y3 = static_cast(particle.y + halfSize * std::sin(angle) + halfSize * std::cos(angle)); - Sint16 x4 = static_cast(particle.x - halfSize * std::cos(angle) - halfSize * std::sin(angle)); - Sint16 y4 = static_cast(particle.y - halfSize * std::sin(angle) + halfSize * std::cos(angle)); - - // Draw a filled polygon (quad) - Sint16 vx[] = {x1, x2, x3, x4}; - Sint16 vy[] = {y1, y2, y3, y4}; - filledPolygonRGBA(target, vx, vy, 4, particle.r, particle.g, particle.b, alpha); + confetti.Burst(globalData.xsize, globalData.ysize); } } \ No newline at end of file diff --git a/src/PuzzleSingleImageState.hpp b/src/PuzzleSingleImageState.hpp index a609def..6556ac7 100644 --- a/src/PuzzleSingleImageState.hpp +++ b/src/PuzzleSingleImageState.hpp @@ -26,6 +26,7 @@ https://github.com/sago007/saland #include #include #include "rhash.h" +#include "Confetti.hpp" class PuzzleSingleImageState : public sago::GameStateInterface { public: @@ -53,25 +54,11 @@ public: bool flipMode = false; private: - struct ConfettiParticle { - float x, y; // Position - float vx, vy; // Velocity - float rotation; // Rotation angle - float rotationSpeed; // Rotation speed - Uint8 r, g, b; // Color - float size; // Size of the confetti - float lifetime; // Time to live - float gravity; // Gravity multiplier - }; - void ClearPicture(); - void InitConfetti(); - void UpdateConfetti(float deltaTime); - void DrawConfetti(SDL_Renderer* target); bool isActive = true; bool puzzleSolved = false; - std::vector confetti; + Confetti confetti; SDL_Texture* pictureTex = NULL; int source_image_height = 1; int source_image_width = 1;