#include "App.hpp" #include "sorters/BubbleSorter.hpp" #include "sorters/SelectionSorter.hpp" #include "sorters/InsertionSorter.hpp" #include "sorters/MergeSorter.hpp" #include "sorters/QuickSorter.hpp" App::App() : window_(sf::VideoMode(1600, 900), "SortLab") , array_(100) , currentSorter_(std::make_unique()) , ui_() , opsHistory_(400) , progressMap_() , isPlaying_(false) , timeSinceLastStep_(0.0f) , stepsPerFrame_(1) , isSweeping_(false) , sweepIndex_(0) , sweepTimer_(0.0f) , sweepDelay_(0.005f) , lastComparisons_(0) , lastSwaps_(0) , bottomPanelFontLoaded_(false) , showInfo_(false) { window_.setFramerateLimit(60); generateBeepSound(); beepSound_.setBuffer(beepBuffer_); beepSound_.setVolume(100.0f); if (bottomPanelFont_.loadFromFile("assets/fonts/JetBrainsMono-Regular.ttf")) { bottomPanelFontLoaded_ = true; } } void App::run() { sf::Clock clock; while (window_.isOpen()) { float dt = clock.restart().asSeconds(); handleEvents(); update(dt); render(); } } void App::handleEvents() { sf::Event event; while (window_.pollEvent(event)) { if (event.type == sf::Event::Closed) { window_.close(); } if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::Space: isPlaying_ = !isPlaying_; break; case sf::Keyboard::Right: if (!isPlaying_ && !currentSorter_->isFinished()) { currentSorter_->step(array_); } break; case sf::Keyboard::Up: stepsPerFrame_ = (stepsPerFrame_ < 500) ? stepsPerFrame_ * 2 : 500; if (stepsPerFrame_ < 1) stepsPerFrame_ = 1; break; case sf::Keyboard::Down: stepsPerFrame_ = (stepsPerFrame_ > 1) ? stepsPerFrame_ / 2 : 1; if (stepsPerFrame_ < 1) stepsPerFrame_ = 1; break; case sf::Keyboard::Num1: switchSorter(std::make_unique()); break; case sf::Keyboard::Num2: switchSorter(std::make_unique()); break; case sf::Keyboard::Num3: switchSorter(std::make_unique()); break; case sf::Keyboard::Num4: switchSorter(std::make_unique()); break; case sf::Keyboard::Num5: switchSorter(std::make_unique()); break; case sf::Keyboard::R: array_.shuffle(); array_.resetStates(); array_.resetCounters(); currentSorter_->reset(); opsHistory_.reset(); isPlaying_ = false; timeSinceLastStep_ = 0.0f; isSweeping_ = false; sweepIndex_ = 0; lastComparisons_ = 0; lastSwaps_ = 0; stepsPerFrame_ = 1; break; case sf::Keyboard::Q: window_.close(); break; case sf::Keyboard::I: showInfo_ = !showInfo_; break; default: break; } } } } void App::update(float dt) { ui_.update(*currentSorter_, isPlaying_, currentSorter_->isFinished(), stepsPerFrame_, array_); if (isSweeping_) { sweepTimer_ += dt; if (sweepTimer_ >= sweepDelay_) { if (sweepIndex_ < array_.getSize()) { array_.setState(sweepIndex_, Array::State::SORTED); float normalizedValue = array_.getValue(sweepIndex_) / static_cast(array_.getSize()); float pitch = 0.5f + normalizedValue * 1.5f; playBeep(pitch); sweepIndex_++; sweepTimer_ = 0.0f; } else { isSweeping_ = false; } } return; } if (isPlaying_ && !currentSorter_->isFinished()) { size_t beforeComparisons = array_.getComparisons(); size_t beforeSwaps = array_.getSwaps(); for (int step = 0; step < stepsPerFrame_; ++step) { if (currentSorter_->isFinished()) { break; } currentSorter_->step(array_); opsHistory_.record(array_.getComparisons()); } size_t afterComparisons = array_.getComparisons(); size_t afterSwaps = array_.getSwaps(); if (afterComparisons > beforeComparisons || afterSwaps > beforeSwaps) { float avgPitch = 1.0f; for (int i = 0; i < array_.getSize(); ++i) { if (array_.getState(i) == Array::State::COMPARE || array_.getState(i) == Array::State::SWAP) { float normalizedValue = array_.getValue(i) / static_cast(array_.getSize()); avgPitch = 0.5f + normalizedValue * 1.5f; break; } } playBeep(avgPitch); } if (currentSorter_->isFinished()) { array_.resetStates(); isSweeping_ = true; sweepIndex_ = 0; sweepTimer_ = 0.0f; } } } void App::render() { window_.clear(sf::Color(10, 12, 20)); const float topUIHeight = 80.0f; const float bottomPanelHeight = 140.0f; const float mainAreaTop = topUIHeight; const float mainAreaBottom = window_.getSize().y - bottomPanelHeight; const float mainAreaHeight = mainAreaBottom - mainAreaTop; float width = window_.getSize().x / static_cast(array_.getSize()); float maxVal = static_cast(array_.getSize()); sf::VertexArray vertices(sf::Quads); for (int i = 0; i < array_.getSize(); ++i) { float height = (array_.getValue(i) / maxVal) * (mainAreaHeight * 0.9f); float x = i * width; float y = mainAreaBottom - height; sf::Color barColor; bool useGradient = false; switch (array_.getState(i)) { case Array::State::NORMAL: useGradient = true; break; case Array::State::COMPARE: barColor = sf::Color(0, 220, 255); break; case Array::State::SWAP: barColor = sf::Color(255, 120, 30); break; case Array::State::SORTED: barColor = sf::Color(50, 220, 120); break; } if (useGradient) { float normalizedValue = array_.getValue(i) / maxVal; sf::Color bottomColor(60, 80, 140); sf::Color topColor(160, 180, 255); sf::Color gradientTop( static_cast(bottomColor.r + (topColor.r - bottomColor.r) * normalizedValue), static_cast(bottomColor.g + (topColor.g - bottomColor.g) * normalizedValue), static_cast(bottomColor.b + (topColor.b - bottomColor.b) * normalizedValue) ); sf::Color gradientBottom( static_cast(bottomColor.r + (topColor.r - bottomColor.r) * normalizedValue * 0.6f), static_cast(bottomColor.g + (topColor.g - bottomColor.g) * normalizedValue * 0.6f), static_cast(bottomColor.b + (topColor.b - bottomColor.b) * normalizedValue * 0.6f) ); vertices.append(sf::Vertex(sf::Vector2f(x, mainAreaBottom), gradientBottom)); vertices.append(sf::Vertex(sf::Vector2f(x + width - 1.0f, mainAreaBottom), gradientBottom)); vertices.append(sf::Vertex(sf::Vector2f(x + width - 1.0f, y), gradientTop)); vertices.append(sf::Vertex(sf::Vector2f(x, y), gradientTop)); } else { vertices.append(sf::Vertex(sf::Vector2f(x, mainAreaBottom), barColor)); vertices.append(sf::Vertex(sf::Vector2f(x + width - 1.0f, mainAreaBottom), barColor)); vertices.append(sf::Vertex(sf::Vector2f(x + width - 1.0f, y), barColor)); vertices.append(sf::Vertex(sf::Vector2f(x, y), barColor)); } } window_.draw(vertices); sf::RectangleShape separator(sf::Vector2f(window_.getSize().x, 1.0f)); separator.setPosition(0.0f, mainAreaBottom); separator.setFillColor(sf::Color(40, 45, 60)); window_.draw(separator); renderDeltaHistogram(); progressMap_.draw(window_, array_, bottomPanelFont_, bottomPanelFontLoaded_); sf::RectangleShape verticalSeparator(sf::Vector2f(1.0f, bottomPanelHeight)); verticalSeparator.setPosition(1066.0f, mainAreaBottom); verticalSeparator.setFillColor(sf::Color(40, 45, 60)); window_.draw(verticalSeparator); ui_.draw(window_); if (showInfo_) { ui_.drawInfoOverlay(window_); } window_.display(); } void App::renderDeltaHistogram() { const float startX = 0.0f; const float startY = 760.0f; const float width = 1066.0f; const float height = 140.0f; sf::RectangleShape background(sf::Vector2f(width, height)); background.setPosition(startX, startY); background.setFillColor(sf::Color(12, 15, 25)); window_.draw(background); const auto& deltaHistory = opsHistory_.getDeltaHistory(); if (!deltaHistory.empty()) { size_t maxDelta = opsHistory_.getMaxDelta(); if (maxDelta == 0) maxDelta = 1; float barWidth = width / 400.0f; float availableHeight = height - 25.0f; sf::VertexArray histogramBars(sf::Quads); for (size_t i = 0; i < deltaHistory.size(); ++i) { float barHeight = (static_cast(deltaHistory[i]) / static_cast(maxDelta)) * availableHeight; float x = startX + i * barWidth; float y = startY + height - barHeight; float normalizedValue = static_cast(deltaHistory[i]) / static_cast(maxDelta); sf::Color lowColor(0, 80, 200); sf::Color highColor(0, 240, 255); sf::Color barColor( static_cast(lowColor.r + (highColor.r - lowColor.r) * normalizedValue), static_cast(lowColor.g + (highColor.g - lowColor.g) * normalizedValue), static_cast(lowColor.b + (highColor.b - lowColor.b) * normalizedValue) ); histogramBars.append(sf::Vertex(sf::Vector2f(x, startY + height), barColor)); histogramBars.append(sf::Vertex(sf::Vector2f(x + barWidth, startY + height), barColor)); histogramBars.append(sf::Vertex(sf::Vector2f(x + barWidth, y), barColor)); histogramBars.append(sf::Vertex(sf::Vector2f(x, y), barColor)); } window_.draw(histogramBars); } sf::RectangleShape baseline(sf::Vector2f(width, 1.0f)); baseline.setPosition(startX, startY + height - 1.0f); baseline.setFillColor(sf::Color(60, 70, 90)); window_.draw(baseline); if (bottomPanelFontLoaded_) { sf::Text label; label.setFont(bottomPanelFont_); label.setString("Delta Comparisons / step"); label.setCharacterSize(13); label.setFillColor(sf::Color(180, 180, 180)); label.setPosition(startX + 10.0f, startY + 5.0f); window_.draw(label); } } void App::switchSorter(std::unique_ptr newSorter) { currentSorter_ = std::move(newSorter); array_.shuffle(); array_.resetStates(); array_.resetCounters(); opsHistory_.reset(); isPlaying_ = false; timeSinceLastStep_ = 0.0f; isSweeping_ = false; sweepIndex_ = 0; lastComparisons_ = 0; lastSwaps_ = 0; stepsPerFrame_ = 1; }