commit 65dd60a6
Move Confetti to its own class
Changed files
| A | src/Confetti.cpp |
| A | src/Confetti.hpp |
| M | src/PuzzleSingleImageState.cpp before |
| M | src/PuzzleSingleImageState.hpp before |
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 <SDL2/SDL2_gfxPrimitives.h>
+#include <cmath>
+#include <random>
+
+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<float> xDist(0.0f, static_cast<float>(screenWidth));
+ std::uniform_real_distribution<float> yDist(-100.0f, 0.0f);
+ std::uniform_real_distribution<float> vxDist(-100.0f, 100.0f);
+ std::uniform_real_distribution<float> vyDist(50.0f, 200.0f);
+ std::uniform_real_distribution<float> rotDist(0.0f, 360.0f);
+ std::uniform_real_distribution<float> rotSpeedDist(-360.0f, 360.0f);
+ std::uniform_real_distribution<float> sizeDist(4.0f, 12.0f);
+ std::uniform_real_distribution<float> 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<Uint8>(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<Sint16>(particle.x - halfSize * std::cos(angle));
+ Sint16 y1 = static_cast<Sint16>(particle.y - halfSize * std::sin(angle));
+ Sint16 x2 = static_cast<Sint16>(particle.x + halfSize * std::cos(angle));
+ Sint16 y2 = static_cast<Sint16>(particle.y + halfSize * std::sin(angle));
+ Sint16 x3 = static_cast<Sint16>(particle.x + halfSize * std::cos(angle) - halfSize * std::sin(angle));
+ Sint16 y3 = static_cast<Sint16>(particle.y + halfSize * std::sin(angle) + halfSize * std::cos(angle));
+ Sint16 x4 = static_cast<Sint16>(particle.x - halfSize * std::cos(angle) - halfSize * std::sin(angle));
+ Sint16 y4 = static_cast<Sint16>(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 <SDL.h>
+#include <vector>
+
+/**
+ * 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<Particle> 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 <SDL2/SDL2_gfxPrimitives.h>
#include <time.h>
-#include <cmath>
-#include <random>
#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<float> xDist(0.0f, static_cast<float>(globalData.xsize));
- std::uniform_real_distribution<float> yDist(-100.0f, 0.0f);
- std::uniform_real_distribution<float> vxDist(-100.0f, 100.0f);
- std::uniform_real_distribution<float> vyDist(50.0f, 200.0f);
- std::uniform_real_distribution<float> rotDist(0.0f, 360.0f);
- std::uniform_real_distribution<float> rotSpeedDist(-360.0f, 360.0f);
- std::uniform_real_distribution<float> sizeDist(4.0f, 12.0f);
- std::uniform_real_distribution<float> gravityDist(100.0f, 300.0f);
- std::uniform_int_distribution<int> 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<Uint8>(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<Sint16>(particle.x - halfSize * std::cos(angle));
- Sint16 y1 = static_cast<Sint16>(particle.y - halfSize * std::sin(angle));
- Sint16 x2 = static_cast<Sint16>(particle.x + halfSize * std::cos(angle));
- Sint16 y2 = static_cast<Sint16>(particle.y + halfSize * std::sin(angle));
- Sint16 x3 = static_cast<Sint16>(particle.x + halfSize * std::cos(angle) - halfSize * std::sin(angle));
- Sint16 y3 = static_cast<Sint16>(particle.y + halfSize * std::sin(angle) + halfSize * std::cos(angle));
- Sint16 x4 = static_cast<Sint16>(particle.x - halfSize * std::cos(angle) - halfSize * std::sin(angle));
- Sint16 y4 = static_cast<Sint16>(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 <string>
#include <vector>
#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<ConfettiParticle> confetti;
+ Confetti confetti;
SDL_Texture* pictureTex = NULL;
int source_image_height = 1;
int source_image_width = 1;