#include <SFML/Graphics.hpp>
#include <vector>
#include <cmath>
#include <random>
#include <chrono>
struct Particle {
sf::Vector2f position;
sf::Vector2f velocity;
sf::Color color;
float lifetime;
float maxLifetime;
float size;
bool isTrail;
};
struct Firework {
sf::Vector2f position;
sf::Vector2f velocity;
sf::Color color;
float timer;
float explosionHeight;
bool exploded;
std::vector<Particle> particles;
int type;
float sizeFactor;
int pattern;
float speedMultiplier;
};
struct TextParticle {
sf::Vector2f position;
sf::Vector2f targetPosition;
sf::Vector2f velocity;
sf::Color color;
float lifetime;
float maxLifetime;
float size;
};
class FireworksDisplay {
private:
sf::RenderWindow window;
sf::Font font;
sf::Text titleText;
sf::Text infoText;
sf::Text blessingText[3];
sf::Clock clock;
std::vector<Firework> fireworks;
std::vector<TextParticle> blessingParticles;
std::mt19937 rng;
std::uniform_real_distribution<float> dist;
std::uniform_real_distribution<float> sizeDist;
std::uniform_real_distribution<float> speedDist;
bool showBlessing;
float blessingTimer;
bool fontLoaded;
float timeSinceStart;
bool initialShowDone;
public:
FireworksDisplay()
: window(sf::VideoMode(1200, 800), "2026 新春快乐 - 烟花庆祝"),
rng(std::chrono::steady_clock::now().time_since_epoch().count()),
dist(0.0f, 1.0f),
sizeDist(0.8f, 2.0f),
speedDist(0.2f, 3.0f),
showBlessing(false),
blessingTimer(0.0f),
fontLoaded(false),
timeSinceStart(0.0f),
initialShowDone(false)
{
window.setVerticalSyncEnabled(false);
window.setFramerateLimit(60);
loadFont();
initializeOpeningFireworks();
setupText();
}
void loadFont() {
std::vector<std::string> fontPaths = {
"/mnt/c/Windows/Fonts/msyh.ttc",
"/mnt/c/Windows/Fonts/msyhbd.ttc",
"/mnt/c/Windows/Fonts/simhei.ttf",
"/mnt/c/Windows/Fonts/simkai.ttf",
"/mnt/c/Windows/Fonts/simsun.ttc",
"msyh.ttc",
"simhei.ttf"
};
for (const auto& path : fontPaths) {
if (font.loadFromFile(path)) {
fontLoaded = true;
break;
}
}
}
void setupText() {
if (!fontLoaded) return;
std::string titleUtf8 = "2026 新春快乐 - 烟花庆祝";
titleText.setFont(font);
titleText.setString(sf::String::fromUtf8(titleUtf8.begin(), titleUtf8.end()));
titleText.setCharacterSize(48);
titleText.setFillColor(sf::Color::Yellow);
titleText.setStyle(sf::Text::Bold);
titleText.setOutlineColor(sf::Color::Red);
titleText.setOutlineThickness(2);
sf::FloatRect titleBounds = titleText.getLocalBounds();
titleText.setOrigin(titleBounds.left + titleBounds.width / 2.0f,
titleBounds.top + titleBounds.height / 2.0f);
titleText.setPosition(window.getSize().x / 2.0f, 40);
std::string infoUtf8 = "空格键:更多烟花 | 鼠标点击:指定位置爆炸 | 回车键:显示祝福 | ESC:退出";
infoText.setFont(font);
infoText.setString(sf::String::fromUtf8(infoUtf8.begin(), infoUtf8.end()));
infoText.setCharacterSize(20);
infoText.setFillColor(sf::Color(200, 200, 255));
infoText.setPosition(10, window.getSize().y - 30);
std::string blessingLines[3] = {"新的一年,愿你", "马不停蹄奔赴山海,", "一马当先奔向热爱!"};
for (int i = 0; i < 3; ++i) {
blessingText[i].setFont(font);
blessingText[i].setString(sf::String::fromUtf8(blessingLines[i].begin(), blessingLines[i].end()));
blessingText[i].setCharacterSize(48);
blessingText[i].setStyle(sf::Text::Bold);
blessingText[i].setOutlineThickness(2);
switch (i) {
case 0: blessingText[i].setFillColor(sf::Color::Red); blessingText[i].setOutlineColor(sf::Color::Yellow); break;
case 1: blessingText[i].setFillColor(sf::Color::Green); blessingText[i].setOutlineColor(sf::Color::White); break;
case 2: blessingText[i].setFillColor(sf::Color::Blue); blessingText[i].setOutlineColor(sf::Color::Yellow); break;
}
sf::FloatRect bounds = blessingText[i].getLocalBounds();
blessingText[i].setOrigin(bounds.left + bounds.width / 2.0f, bounds.top + bounds.height / 2.0f);
blessingText[i].setPosition(window.getSize().x / 2.0f, 200 + i * 60);
}
}
void initializeOpeningFireworks() {
fireworks.clear();
blessingParticles.clear();
createFirework(window.getSize().x * 0.4f, window.getSize().y, 1, 0, 3.5f, speedDist(rng));
createFirework(window.getSize().x * 0.6f, window.getSize().y, 1, 0, 3.5f, speedDist(rng));
}
void createFirework(float x, float y, int type, int pattern, float customSizeFactor = -1.0f, float speedMultiplier = 1.0f) {
Firework fw;
fw.position = sf::Vector2f(x, y);
fw.pattern = pattern;
fw.speedMultiplier = speedMultiplier;
float baseSpeed = 100.0f;
switch (pattern) {
case 0: fw.velocity = sf::Vector2f((dist(rng) - 0.5f) * 10.0f, -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
case 1: fw.velocity = sf::Vector2f(-(dist(rng) * 10.0f + 5.0f), -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
case 2: fw.velocity = sf::Vector2f(dist(rng) * 10.0f + 5.0f, -(dist(rng) * 20.0f + baseSpeed) * speedMultiplier); break;
}
fw.explosionHeight = window.getSize().y * (0.05f + dist(rng) * 0.3f);
fw.timer = 0.0f;
fw.exploded = false;
fw.type = type;
if (customSizeFactor > 0) fw.sizeFactor = customSizeFactor;
else fw.sizeFactor = sizeDist(rng);
switch (type) {
case 0: fw.color = sf::Color(255, 255, 255); break;
case 1: fw.color = sf::Color(255, 50, 50); break;
case 2: fw.color = sf::Color(0, 255, 100); break;
case 3: fw.color = sf::Color(100, 100, 255); break;
case 4: fw.color = sf::Color(255, 100, 255); break;
case 5: fw.color = sf::Color(100, 255, 255); break;
}
fireworks.push_back(fw);
}
void explodeFirework(Firework& fw) {
int particleCount = 0;
switch (fw.type) {
case 0: particleCount = 2000 + static_cast<int>(1000 * fw.sizeFactor); createTinyScatteringExplosion(fw, particleCount); break;
case 1: particleCount = 400 + static_cast<int>(300 * fw.sizeFactor); createHeartExplosion(fw, particleCount); break;
case 2: particleCount = 600 + static_cast<int>(600 * fw.sizeFactor); createSpiralExplosion(fw, particleCount); break;
case 3: particleCount = 700 + static_cast<int>(600 * fw.sizeFactor); createStarExplosion(fw, particleCount); break;
case 4: particleCount = 800 + static_cast<int>(800 * fw.sizeFactor); createMultiRingExplosion(fw, particleCount); break;
case 5: particleCount = 600 + static_cast<int>(600 * fw.sizeFactor); createTextExplosion(fw, particleCount); break;
}
}
void createTinyScatteringExplosion(Firework& fw, int particleCount) {
for (int i = 0; i < particleCount; ++i) {
float angle = dist(rng) * 2.0f * M_PI;
float speed = dist(rng) * 40.0f + 20.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);
int colorType = static_cast<int>(dist(rng) * 6);
switch (colorType) {
case 0: p.color = sf::Color(255, 255, 255); break;
case 1: p.color = sf::Color(255, 200, 100); break;
case 2: p.color = sf::Color(100, 255, 100); break;
case 3: p.color = sf::Color(100, 100, 255); break;
case 4: p.color = sf::Color(255, 100, 255); break;
default: p.color = sf::Color(255, 150, 50); break;
}
p.lifetime = dist(rng) * 1.2f + 0.8f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 0.8f + 0.3f) * fw.sizeFactor * 0.1f;
p.isTrail = false;
fw.particles.push_back(p);
}
createTrailParticles(fw, 200 + static_cast<int>(200 * fw.sizeFactor));
}
void createHeartExplosion(Firework& fw, int particleCount) {
float sizeMultiplier = 0.8f;
for (int i = 0; i < particleCount; ++i) {
float t = dist(rng) * 2.0f * M_PI;
float x = 16 * pow(sin(t), 3);
float y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t);
float scale = (dist(rng) * 0.8f + 0.2f) * fw.sizeFactor;
x *= scale * 1.5f;
y *= -scale * 1.5f;
float speed = dist(rng) * 6.0f + 3.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(x * speed, y * speed);
float colorFactor = dist(rng);
p.color = sf::Color(255, 50 + static_cast<int>(205 * colorFactor), 50 + static_cast<int>(130 * colorFactor));
p.lifetime = dist(rng) * 2.0f + 1.0f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 2.0f + 1.0f) * fw.sizeFactor * sizeMultiplier;
p.isTrail = false;
fw.particles.push_back(p);
}
createTrailParticles(fw, 80 + static_cast<int>(80 * fw.sizeFactor));
}
void createSpiralExplosion(Firework& fw, int particleCount) {
for (int i = 0; i < particleCount; ++i) {
float angle = dist(rng) * 10.0f * M_PI;
float radius = angle * 0.1f;
float speed = dist(rng) * 15.0f + 7.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(cos(angle) * radius * speed, sin(angle) * radius * speed);
p.color = hsvToRgb(i % 360, 1.0f, 1.0f);
p.lifetime = dist(rng) * 2.0f + 1.0f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 2.5f + 1.0f) * fw.sizeFactor;
p.isTrail = false;
fw.particles.push_back(p);
}
createTrailParticles(fw, 100 + static_cast<int>(100 * fw.sizeFactor));
}
void createStarExplosion(Firework& fw, int particleCount) {
int points = 5 + static_cast<int>(dist(rng) * 3);
for (int i = 0; i < particleCount; ++i) {
float angle = (static_cast<float>(i % points) / points) * 2.0f * M_PI;
float radius = (1.0f + 0.3f * sin(points * angle)) * 8.0f * fw.sizeFactor;
float speed = dist(rng) * 18.0f + 9.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(cos(angle) * radius * speed, sin(angle) * radius * speed);
p.color = sf::Color(255, 140 + static_cast<int>(115 * dist(rng)), 50);
p.lifetime = dist(rng) * 2.0f + 1.0f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 3.0f + 1.5f) * fw.sizeFactor;
p.isTrail = false;
fw.particles.push_back(p);
}
createTrailParticles(fw, 120 + static_cast<int>(120 * fw.sizeFactor));
}
void createMultiRingExplosion(Firework& fw, int particleCount) {
int rings = 3 + static_cast<int>(fw.sizeFactor);
int particlesPerRing = particleCount / rings;
for (int ring = 0; ring < rings; ++ring) {
float ringRadius = (ring + 1) * 3.0f * fw.sizeFactor;
for (int i = 0; i < particlesPerRing; ++i) {
float angle = (static_cast<float>(i) / particlesPerRing) * 2.0f * M_PI;
float speed = dist(rng) * 12.0f + 6.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(cos(angle) * ringRadius * speed, sin(angle) * ringRadius * speed);
p.color = hsvToRgb((ring * 60 + i * 2) % 360, 1.0f, 1.0f);
p.lifetime = dist(rng) * 2.0f + 1.0f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 2.5f + 1.0f) * fw.sizeFactor;
p.isTrail = false;
fw.particles.push_back(p);
}
}
createTrailParticles(fw, 150 + static_cast<int>(150 * fw.sizeFactor));
}
void createTextExplosion(Firework& fw, int particleCount) {
for (int i = 0; i < particleCount; ++i) {
float angle = dist(rng) * 2.0f * M_PI;
float speed = dist(rng) * 36.0f + 18.0f;
Particle p;
p.position = fw.position;
p.velocity = sf::Vector2f(cos(angle) * speed, sin(angle) * speed);
p.color = sf::Color(255, 215, 0);
p.lifetime = dist(rng) * 2.5f + 1.5f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 4.0f + 2.0f) * fw.sizeFactor;
p.isTrail = false;
fw.particles.push_back(p);
}
createTrailParticles(fw, 180 + static_cast<int>(180 * fw.sizeFactor));
}
void createTrailParticles(Firework& fw, int count) {
for (int i = 0; i < count; ++i) {
Particle p;
p.position = fw.position;
float angle = (dist(rng) - 0.5f) * M_PI * 0.5f;
float speed = dist(rng) * 25.0f + 12.0f;
p.velocity = sf::Vector2f(sin(angle) * speed, -cos(angle) * speed);
p.color = sf::Color(std::min(255, fw.color.r + 50), std::min(255, fw.color.g + 50), std::min(255, fw.color.b + 50), 200);
p.lifetime = dist(rng) * 0.4f + 0.2f;
p.maxLifetime = p.lifetime;
p.size = (dist(rng) * 2.0f + 1.0f) * fw.sizeFactor * 0.5f;
p.isTrail = true;
fw.particles.push_back(p);
}
}
void createBlessingTextEffect() {
blessingParticles.clear();
for (int i = 0; i < 1500; ++i) {
TextParticle tp;
tp.position = sf::Vector2f(dist(rng) * window.getSize().x, window.getSize().y + dist(rng) * 100.0f);
int row = i % 3;
int col = (i / 3) % 50;
tp.targetPosition = sf::Vector2f(
window.getSize().x * 0.3f + col * 15.0f + (dist(rng) - 0.5f) * 20.0f,
200 + row * 60 + (dist(rng) - 0.5f) * 30.0f
);
sf::Vector2f direction = tp.targetPosition - tp.position;
float distance = sqrt(direction.x * direction.x + direction.y * direction.y);
float speed = 8.0f + dist(rng) * 6.0f;
tp.velocity = (direction / distance) * speed;
int hue = (i * 10) % 360;
tp.color = hsvToRgb(hue, 1.0f, 1.0f);
tp.lifetime = 8.0f;
tp.maxLifetime = 8.0f;
tp.size = dist(rng) * 4.0f + 2.0f;
blessingParticles.push_back(tp);
}
showBlessing = true;
blessingTimer = 8.0f;
}
sf::Color hsvToRgb(int h, float s, float v) {
float c = v * s;
float x = c * (1 - abs(fmod(h / 60.0f, 2) - 1));
float m = v - c;
float r, g, b;
if (h >= 0 && h < 60) { r = c; g = x; b = 0; }
else if (h >= 60 && h < 120) { r = x; g = c; b = 0; }
else if (h >= 120 && h < 180) { r = 0; g = c; b = x; }
else if (h >= 180 && h < 240) { r = 0; g = x; b = c; }
else if (h >= 240 && h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return sf::Color(static_cast<int>((r + m) * 255), static_cast<int>((g + m) * 255), static_cast<int>((b + m) * 255));
}
void update(float deltaTime) {
timeSinceStart += deltaTime;
if (!initialShowDone && timeSinceStart > 3.0f) initialShowDone = true;
if (initialShowDone && !showBlessing && dist(rng) < 0.08f && fireworks.size() < 25) {
int pattern = static_cast<int>(dist(rng) * 3);
int type = static_cast<int>(dist(rng) * 6);
float speedMultiplier = speedDist(rng);
createFirework(dist(rng) * window.getSize().x, window.getSize().y, type, pattern, -1.0f, speedMultiplier);
}
if (showBlessing) {
blessingTimer -= deltaTime;
if (blessingTimer <= 0.0f) {
showBlessing = false;
blessingParticles.clear();
}
}
for (auto& fw : fireworks) {
if (!fw.exploded) {
fw.position += fw.velocity * deltaTime;
fw.timer += deltaTime;
if (fw.timer > 0.005f) {
for (int i = 0; i < 5; ++i) {
Particle trail;
trail.position = fw.position;
trail.velocity = sf::Vector2f(fw.velocity.x * 0.1f + (dist(rng) - 0.5f) * 6.0f, fw.velocity.y * 0.1f + (dist(rng) - 0.5f) * 6.0f);
trail.color = sf::Color(std::min(255, fw.color.r + 80), std::min(255, fw.color.g + 80), std::min(255, fw.color.b + 80), 240);
trail.lifetime = 0.2f + dist(rng) * 0.2f;
trail.maxLifetime = trail.lifetime;
trail.size = (dist(rng) * 1.5f + 0.5f) * fw.sizeFactor * 0.6f;
trail.isTrail = true;
fw.particles.push_back(trail);
}
fw.timer = 0.0f;
}
if (fw.position.y <= fw.explosionHeight || fw.velocity.y >= 0) {
fw.exploded = true;
explodeFirework(fw);
}
}
for (auto it = fw.particles.begin(); it != fw.particles.end();) {
it->position += it->velocity * deltaTime;
if (it->isTrail) it->velocity.y += 9.8f * deltaTime * 0.02f;
else it->velocity.y += 9.8f * deltaTime * 0.1f;
it->velocity *= 0.98f;
it->lifetime -= deltaTime;
if (it->lifetime <= 0.0f) it = fw.particles.erase(it);
else ++it;
}
}
for (auto& tp : blessingParticles) {
sf::Vector2f direction = tp.targetPosition - tp.position;
float distance = sqrt(direction.x * direction.x + direction.y * direction.y);
if (distance > 1.0f) {
float speed = std::min(12.0f, distance * 0.2f);
tp.velocity = (direction / distance) * speed;
tp.position += tp.velocity * deltaTime;
} else {
tp.velocity.x += (dist(rng) - 0.5f) * 3.0f * deltaTime;
tp.velocity.y += (dist(rng) - 0.5f) * 3.0f * deltaTime;
tp.position += tp.velocity * deltaTime;
if (abs(tp.position.x - tp.targetPosition.x) > 10.0f) tp.velocity.x = -tp.velocity.x * 0.1f;
if (abs(tp.position.y - tp.targetPosition.y) > 10.0f) tp.velocity.y = -tp.velocity.y * 0.1f;
}
tp.lifetime -= deltaTime * 0.02f;
}
fireworks.erase(std::remove_if(fireworks.begin(), fireworks.end(), [](const Firework& fw) { return fw.exploded && fw.particles.empty(); }), fireworks.end());
}
void draw() {
window.clear(sf::Color(5, 5, 25));
for (const auto& fw : fireworks) {
for (const auto& particle : fw.particles) {
float alpha = (particle.lifetime / particle.maxLifetime) * 255;
sf::Color particleColor = particle.color;
particleColor.a = static_cast<sf::Uint8>(alpha);
sf::CircleShape shape(particle.size);
shape.setFillColor(particleColor);
shape.setPosition(particle.position);
shape.setOrigin(particle.size, particle.size);
if (particle.isTrail) {
sf::CircleShape glow(particle.size * 2.5f);
glow.setFillColor(sf::Color(particleColor.r, particleColor.g, particleColor.b, particleColor.a / 3));
glow.setPosition(particle.position);
glow.setOrigin(particle.size * 2.5f, particle.size * 2.5f);
window.draw(glow);
}
window.draw(shape);
}
if (!fw.exploded) {
sf::CircleShape head(4.0f * fw.sizeFactor);
head.setFillColor(fw.color);
head.setPosition(fw.position);
head.setOrigin(4.0f * fw.sizeFactor, 4.0f * fw.sizeFactor);
window.draw(head);
sf::CircleShape glow(6.0f * fw.sizeFactor);
glow.setFillColor(sf::Color(fw.color.r, fw.color.g, fw.color.b, 180));
glow.setPosition(fw.position);
glow.setOrigin(6.0f * fw.sizeFactor, 6.0f * fw.sizeFactor);
window.draw(glow);
}
}
for (const auto& tp : blessingParticles) {
float alpha = (tp.lifetime / tp.maxLifetime) * 255;
sf::Color particleColor = tp.color;
particleColor.a = static_cast<sf::Uint8>(alpha);
sf::CircleShape shape(tp.size);
shape.setFillColor(particleColor);
shape.setPosition(tp.position);
shape.setOrigin(tp.size, tp.size);
sf::CircleShape glow(tp.size * 1.8f);
glow.setFillColor(sf::Color(particleColor.r, particleColor.g, particleColor.b, particleColor.a / 2));
glow.setPosition(tp.position);
glow.setOrigin(tp.size * 1.8f, tp.size * 1.8f);
window.draw(glow);
window.draw(shape);
}
if (fontLoaded) {
window.draw(titleText);
window.draw(infoText);
if (showBlessing) {
for (int i = 0; i < 3; ++i) window.draw(blessingText[i]);
}
}
window.display();
}
void run() {
sf::Clock deltaClock;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) window.close();
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space) {
for (int i = 0; i < 5; ++i) {
int pattern = static_cast<int>(dist(rng) * 3);
int type = static_cast<int>(dist(rng) * 6);
float speedMultiplier = speedDist(rng);
createFirework(dist(rng) * window.getSize().x, window.getSize().y, type, pattern, -1.0f, speedMultiplier);
}
}
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter) createBlessingTextEffect();
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) window.close();
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::R) {
initializeOpeningFireworks();
timeSinceStart = 0.0f;
initialShowDone = false;
showBlessing = false;
blessingParticles.clear();
}
if (event.type == sf::Event::MouseButtonPressed) {
int type = static_cast<int>(dist(rng) * 6);
int pattern = static_cast<int>(dist(rng) * 3);
float speedMultiplier = speedDist(rng);
createFirework(event.mouseButton.x, event.mouseButton.y, type, pattern, -1.0f, speedMultiplier);
}
}
float deltaTime = deltaClock.restart().asSeconds();
update(deltaTime);
draw();
}
}
};
int main() {
FireworksDisplay display;
display.run();
return 0;
}