Visualiseur d'algorithme de tri en C ++ et SDL2 amélioré

Aug 15 2020

Article original: Visualiseur d'algorithmes de tri en C ++ et SDL2
J'ai suivi les conseils que vous m'avez donnés et amélioré mon code. J'ai également ajouté deux autres algorithmes de tri. Je recherche principalement des conseils sur la lisibilité du code (construit sur Windows avec cmake et ninja)

Manifestation


accessoires à @Zeta pour la démonstration

main.cpp

#include "Engine.h"
#undef main

int main()
{
    try
    {
        // If the max number is higher than the window width it draws nothing other than a black screen :^)
        // Default draw method is line
        // Default sorting algorithm is bubble sort
        SortVis::Engine visualization(
            { 1024, 768 },
            1024,
            SortVis::Engine::SortAlgorithm::insertionSort,
            SortVis::Engine::DrawMethod::point
        );
        visualization.run();
    }
    catch (std::runtime_error& error)
    {
        std::cerr << error.what() << "\n";
    }
}

Engine.h

#ifndef ENGINE_H
#define ENGINE_H

#include "Coord.h"
#include <SDL.h>
#include <vector>
#include <iostream>

namespace SortVis
{
    class Engine
    {
    public:

        enum class DrawMethod
        {
            line,
            point
        };

        enum class SortAlgorithm
        {
            selectionSort,
            insertionSort,
            bubbleSort
        };

        // Random number generation
        Engine() = delete;
        Engine(Coord windowSize, int maxNumber);
        Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm);
        Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm, DrawMethod method);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method);

        // Load from file
        Engine(Coord windowSize, const char* pathToNumbersFile);
        Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm);
        Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm, DrawMethod method);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method);

        ~Engine();

        void run();     

    private:

        const Coord windowSize;
        const SortAlgorithm selectedSortAlgorithm = SortAlgorithm::bubbleSort;
        const DrawMethod selectedDrawMethod = DrawMethod::line;

        SDL_Window* window = nullptr;
        SDL_Renderer* renderer = nullptr;
        
        std::vector<int> numbers = { };
        int columnWidth = 0;
        int maxValue = 0;
        bool running = true;

        void initWindow(Coord windowSize, const char* windowTitle);
        void initRenderer();
        void calculateNumbers();        
        void loadFile(const char* pathToNumbersFile);
        
        void handleEvents();

        void draw();
        void drawSelection();

        void drawColumns();
        void drawPoints();

        void step();
        void stepBubbleSort();
        void stepInsertionSort();
        void stepSelectionSort();

        std::vector<int> generateRandom(int maxNumber);     
    };
}

#endif // ENGINE_H

Engine.cpp

#include "Engine.h"

#include <filesystem>
#include <fstream>
#include <random>
#include <utility>
#include <algorithm>
#include <numeric>
#include <string>

// --- CONSTRUCTORS --- (fml)

SortVis::Engine::Engine(Coord windowSize, int maxNumber)
    : windowSize(windowSize), numbers(generateRandom(maxNumber))
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer(); 
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)), selectedSortAlgorithm(algorithm)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)),
    selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle)
    : windowSize(windowSize), numbers(generateRandom(maxNumber))
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)), selectedSortAlgorithm(algorithm)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)),
    selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile)
    : windowSize(windowSize)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle)
    : windowSize(windowSize)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

// --- END OF CONSTRUCTORS ---

SortVis::Engine::~Engine()
{
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();
}

void SortVis::Engine::run()
{
    // Sets render draw color to black
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

    while (running)
    {
        handleEvents();
        if (!std::is_sorted(numbers.begin(), numbers.end()))
        {
            step();
        }
        draw();
    }
}

void SortVis::Engine::step()
{
    switch (selectedSortAlgorithm)
    {
    case SortAlgorithm::bubbleSort:
        stepBubbleSort();
        break;

    case SortAlgorithm::insertionSort:
        stepInsertionSort();
        break;

    case SortAlgorithm::selectionSort:
        stepSelectionSort();
        break;

    default:
        break;
    }
}

void SortVis::Engine::stepBubbleSort()
{   
    static int i = 0;
    static int size = numbers.size();
    for (int j = 0; j < size - i - 1; ++j)
    {
        if (numbers[j] > numbers[j + 1])
        {
            std::swap(numbers[j], numbers[j + 1]);
        }           
    }
    ++i;
}

void SortVis::Engine::stepInsertionSort()
{
    static int i = 1;   
    for (int j = i; j > 0 && numbers[j - 1] > numbers[j]; --j)
    {
        std::swap(numbers[j - 1], numbers[j]);
    }
    ++i;
}

void SortVis::Engine::stepSelectionSort()
{
    static int i = 0;
    std::swap(numbers[i], numbers[std::min_element(numbers.begin() + i, numbers.end()) - numbers.begin()]);
    ++i;
}

void SortVis::Engine::draw()
{
    SDL_RenderClear(renderer);

    drawSelection();

    SDL_RenderPresent(renderer);
}

void SortVis::Engine::drawSelection()
{
    switch (selectedDrawMethod)
    {
    case DrawMethod::line:
        drawColumns();
        break;

    case DrawMethod::point:
        drawPoints();
        break;

    default:
        break;
    }
}

void SortVis::Engine::drawColumns()
{
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    SDL_Rect column;
    for (int i = numbers.size(); i > 0; --i)
    {
        column.x = (i-1) * columnWidth;
        column.w = columnWidth;
        column.h = (numbers[i - 1] * windowSize.Y) / maxValue;
        column.y = windowSize.Y - column.h;
        SDL_RenderFillRect(renderer, &column);
    }

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}

void SortVis::Engine::drawPoints()
{
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    // SDL_Point point;
    for (int i = numbers.size(); i > 0; --i)
    {
        // point.x = i - 1;
        // point.y = windowSize.Y - ((numbers[i - 1] * windowSize.Y) / maxValue);
        SDL_RenderDrawPoint(renderer, i - 1, windowSize.Y - ((numbers[i - 1] * windowSize.Y) / maxValue));
    }
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}

void SortVis::Engine::handleEvents()
{
    SDL_Event Event;

    while (SDL_PollEvent(&Event))
    {
        switch (Event.type)
        {
        case SDL_QUIT:
            running = false;
            break;

        default:
            break;
        }
    }
}

std::vector<int> SortVis::Engine::generateRandom(int maxNumber)
{
    std::mt19937 seed(std::random_device{}());
    std::vector<int> num(maxNumber);
    std::iota(num.begin(), num.end(), 0);
    std::shuffle(num.begin(), num.end(), seed);

    std::cout << "Generated random number sequence.\n";

    return num;
}

void SortVis::Engine::calculateNumbers()
{
    columnWidth = windowSize.X / numbers.size();
    maxValue = *std::max_element(numbers.begin(), numbers.end());
}

void SortVis::Engine::loadFile(const char* pathToNumbersFile)
{
    std::ifstream NumbersFile(pathToNumbersFile);
    if (NumbersFile.is_open())
    {
        std::string Number;
        while (std::getline(NumbersFile, Number))
        {
            numbers.push_back(std::stoi(Number));
        }
    }
    else
    {
        throw std::runtime_error("Couldn't open numbers file.");
    }

    if (numbers.empty())
    {
        throw std::runtime_error("Numbers file is empty.");
    }

    std::cout << "Loaded numbers file.\n";
}

void SortVis::Engine::initWindow(Coord windowSize, const char* windowTitle)
{
    window = SDL_CreateWindow(
        windowTitle,
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        windowSize.X,
        windowSize.Y,
        SDL_WINDOW_SHOWN
    );

    if (window == nullptr)
    {
        throw std::runtime_error("Could not initialize SDL window");
    }
}

void SortVis::Engine::initRenderer()
{
    renderer = SDL_CreateRenderer(
        window,
        -1,
        SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED
    );

    if (renderer == nullptr)
    {
        throw std::runtime_error("Could not initialize SDL renderer");
    }
}

Coord.h

#ifndef COORD_H
#define COORD_H

namespace SortVis
{
    struct Coord
    {
        int X;
        int Y;
    };
}

#endif // COORD_H

Réponses

3 Edward Aug 16 2020 at 13:29

Le programme est définitivement amélioré par rapport à la version précédente. Bon travail! Voici quelques éléments qui peuvent vous aider à l'améliorer davantage.

Ne vous répétez pas (DRY)

Il y a onze répétitions du constructeur, ce qui me paraît un peu excessif, surtout parce que le code est presque identique. Je les réduirais à exactement un:

Engine(
    Coord windowSize, 
    std::vector<int>&& numbers, 
    SortAlgorithm algorithm = SortAlgorithm::bubbleSort, 
    DrawMethod method = DrawMethod::point,
    const char* windowTitle = "Sort visualizer"
); 

En fournissant un seul constructeur avec des paramètres par défaut, les répétitions sont éliminées et la flexibilité est améliorée. Notez également que le tableau de nombres est passé en argument.

Réduisez la taille de l'interface à ce qui est nécessaire

Ces deux fonctions sont génériques et n'ont pas besoin d'être membres de la classe:

std::vector<int> SortVis::loadFile(std::istream& numberFile);
std::vector<int> SortVis::generateRandom(int maxNumber);

En réduisant la taille de l'interface au minimum nécessaire, la classe est plus petite et plus facile à lire, à comprendre, à tester, à utiliser, à maintenir et à adapter. Notez également que le premier argument prend un std::istream&au lieu d'un nom de fichier. Cela permet des choses pratiques comme le chargement depuis une socket ou un stringstream.

Préfère échouer tôt

Si l' SDL_InitSubSystemappel dans le constructeur échoue, il ne sert à rien de continuer. Pour cette raison, l' calculateNumbersappel le plus long devrait probablement venir après plutôt qu'avant.

Ne définissez la couleur que si vous allez dessiner quelque chose

Les seuls endroits qui SDL_SetRenderDrawColordevraient être utilisés sont juste avant que quelque chose ne soit réellement dessiné à l'écran. Pour cette raison, il peut apparaître exactement deux fois dans ce code: une fois en haut de drawSelectionet une fois en haut de draw.

Déplacer le travail en dehors des boucles lorsque cela est possible

Au lieu de recalculer les quatre parties de columnchaque fois dans la boucle, il est possible de simplement changer celles qui doivent changer:

SDL_Rect column{ 0, 0, columnWidth, 0 };
for (const auto n : numbers)
{
    column.h = n * windowSize.Y / maxValue;
    column.y = windowSize.Y - column.h;
    SDL_RenderFillRect(renderer, &column);
    column.x += columnWidth;
}

Utilisez des objets au lieu de switches

Le code contient actuellement ceci:

void SortVis::Engine::step()
{
    switch (selectedSortAlgorithm)
    {
    case SortAlgorithm::bubbleSort:
        stepBubbleSort();
        break;

    case SortAlgorithm::insertionSort:
        stepInsertionSort();
        break;

    case SortAlgorithm::selectionSort:
        stepSelectionSort();
        break;

    default:
        break;
    }
}

Cela nécessite l'évaluation de selectedSortAlgorithmchaque itération. La meilleure façon de procéder consiste à créer une classe de base virtuelle qui illustre l'interface, puis à permettre à l'utilisateur de créer une classe dérivée avec n'importe quel nouveau type de tri qu'il souhaite. Voici une façon de le faire:

using Collection = std::vector<int>;
struct Sorter {
    std::string_view name;
    virtual bool step(Collection &) = 0;
    Sorter(Sorter&) = delete;
    Sorter(std::string_view name) 
        : name{name}
    {
    }
    virtual void reset(Collection& numbers) {
        a = original_a = 0;
        original_b = numbers.size();
    }
    virtual ~Sorter() = default;
    std::size_t a;
    std::size_t original_a; 
    std::size_t original_b;
};

struct BubbleSorter : public Sorter {
    BubbleSorter() : Sorter{"Bubble Sort"} { }
    bool step(Collection& numbers) {
        auto lag{original_a};
        for (auto it{lag + 1}; it < original_b; ++it, ++lag) {
            if (numbers[lag] > numbers[it]) {
                std::swap(numbers[lag], numbers[it]);
            }           
        }
        return ++a != original_b;
    }
};

Maintenant, pour une flexibilité maximale, vous pouvez passer un tel objet au Enginedans son constructeur:

Engine visualization{
    { 1024, 768 },
    generateRandom(1024),
    std::make_unique<BubbleSorter>()
};

Utiliser un pointeur vers une fonction membre

Une chose similaire peut être faite pour la méthode de dessin si vous le souhaitez, mais c'est un peu plus délicat car elle utilise un pointeur vers une fonction membre qui a une syntaxe qui peut être difficile à obtenir. Nous pourrions déclarer la variable à l'intérieur Enginecomme ceci:

void (Engine::*drawSelection)();

J'ai réutilisé votre drawSelectionnom ici. Dans le constructeur, nous pouvons utiliser ceci:

drawSelection{method == DrawMethod::point ? &Engine::drawPoints : &Engine::drawColumns}

Et enfin pour l'appeler, nous devons utiliser l'opérateur d'accès pointeur vers membre. Le voici en contexte:

void SortVis::Engine::draw() {
    // Sets render draw color to black
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    (this->*(drawSelection))();
    SDL_RenderPresent(renderer);
}

Ne faites pas de travail qui n'est pas nécessaire

À l'heure actuelle, la boucle principale runest la suivante:

while (running)
{
    handleEvents();
    if (!std::is_sorted(numbers.begin(), numbers.end()))
    {
        step();
    }
    draw();
}

Cela n'a pas beaucoup de sens pour moi de faire un appel std::is_sorteddans un programme qui démontre le tri! Il m'a semblé que je voudrais fermer le programme une fois le tri terminé et j'ai modifié les routines de tri pour ne renvoyer boolavec la valeur falseque lorsque son exécution est terminée et que le vecteur est trié. Donc pour cela, la boucle se transforme en ceci:

while (running) {
    handleEvents();
    running &= sorter->step(numbers);
    draw();
}

Pensez à ajouter des fonctionnalités

Je suggérerais que ce serait peut-être bien d'ajouter quelques fonctionnalités. Voici ce que j'ai fait:

void SortVis::Engine::handleEvents() {
    SDL_Event Event;
    while (SDL_PollEvent(&Event)) {
        switch (Event.type) {
        case SDL_QUIT:
            running = false;
            break;
        case SDL_KEYDOWN:
            switch (Event.key.keysym.sym) {
                case SDLK_r:
                    numbers = generateRandom(maxValue);
                    sorter->reset(numbers);
                    break;
                case SDLK_b:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<BubbleSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_i:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<InsertionSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_s:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<SelectionSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_l:
                    drawSelection = &Engine::drawColumns;
                    break;
                case SDLK_p:
                    drawSelection = &Engine::drawPoints;
                    break;
                case SDLK_q:
                    running = false;
                    break;
            }

        default:
            break;
        }
    }
}
2 G.Sliepen Aug 16 2020 at 10:34

Utiliser namespace SortVis {...}dansEngine.cpp

Vous pouvez éviter de répéter l'espace de noms dans chaque définition de fonction en Engine.cppencapsulant tout le code à l'intérieur namespace SortVis, comme vous l'avez fait dans Engine.h. Il est également courant de ne pas indenter le code à l'intérieur d'un namespace {...}bloc qui couvre l'ensemble du fichier, pour éviter que le code ne s'échappe trop souvent de la main droite de l'écran.

Trop de constructeurs

Vous avez 12 constructeurs différents, c'est un peu beaucoup. Imaginez également que vous souhaitiez ajouter un autre paramètre facultatif à l'avenir, puis vous doublerez le nombre de constructeurs requis. Ce n'est pas maintenable à long terme. Je vois deux façons de réduire le nombre de constructeurs requis:

  1. Utilisez les valeurs d'argument par défaut, comme ceci:

    Engine(Coord windowSize, int maxNumber,
           const char *windowTitle = "Sort visualizer",
           SortAlgorithm algorithm = SortAlgorithm::bubbleSort,
           DrawMethod method = DrawMethod::line);
    

    Avec cette approche, vous n'avez besoin que de deux constructeurs. Cela peut être un peu plus ennuyeux à utiliser si vous ne voulez en spécifier qu'un autre DrawMethod, mais c'est un petit prix pour une maintenance beaucoup plus améliorée.

  2. Ne spécifiez pas les valeurs d'entrée, l'algorithme et la méthode de dessin dans le constructeur, autorisez-les à être définis par les fonctions membres, comme ceci:

    Engine(Coord windowSize, const char *windowTitle = "Sort visualizer");
    void generateNumbers(int maxNumber);
    void loadNumbers(const char *pathToNumbersFile);
    void setAlgorithm(SortAlgorithm algorithm);
    void setDrawMethod(DrawMethod method);
    

En général, ne faites dans le constructeur que ce qui doit vraiment être fait au moment de la construction. L'initialisation de SDL et l'ouverture d'une fenêtre sont cruciales pour un moteur de visualisation fonctionnel, c'est donc une bonne chose à faire dans le constructeur.

Envisagez de ne pas générer / charger des nombres dans class Engine

Au lieu de Enginegénérer des nombres aléatoires ou de les charger à partir d'un fichier, vous pouvez le simplifier en ne le faisant pas du tout, mais plutôt en lui permettant simplement d'utiliser n'importe quel vecteur que vous lui donnez. Donc par exemple:

void run(const std::vector<int> &input) {
    numbers = input;
    ...
}

Vous pouvez même envisager de le passer comme non-const et avoir run()modifier le vecteur d'entrée donné.

Envisagez de séparer la visualisation du tri

Un gros problème est que vous devez diviser vos algorithmes de tri en étapes et avoir une switch()instruction step()pour choisir le bon algorithme. Vous devez également énumérer les algorithmes possibles. Au lieu de faire cela, envisagez de Enginesimplement visualiser un vecteur de nombres, et au lieu de Enginepiloter les étapes de l'algorithme, ayez un lecteur d'algorithme Enginepour montrer l'état du vecteur à chaque étape. Vous pouvez le faire en changeant Engine::draw()pour prendre une référence à un vecteur de nombres:

void Engine::draw(const std::vector<int> &numbers) {
     ...
}

Et un algorithme de tri peut devenir une seule fonction:

void bubbleSort(std::vector<int> &numbers, Engine &visualization) {
    for (size_t i = 0; i < numbers.size() - 1; ++i) {
        for (size_t j = 0; j < numbers.size() - 1; ++j) {
            if (numbers[j] > numbers[j + 1])) {
                std::swap(numbers[j], numbers[j + 1]);
            }
        }

        visualization.draw(numbers);
    }
}

Et puis votre main()pourrait ressembler à ceci:

int main() {
    std::vector<int> numbers = {...}; // generate some vector here

    SortVis::Engine visualization({1024, 768});
    SortVis::bubbleSort(numbers, visualization);
}

Les avantages de cette approche sont que vous séparez les préoccupations. Le Enginemaintenant n'a plus qu'à visualiser un vecteur (il devrait probablement être renommé en quelque chose comme Visualizer). Vous pouvez facilement ajouter de nouveaux algorithmes de tri sans avoir à changer Engine.

Un problème avec ce qui précède est qu'il ne gère plus les événements SDL. Vous pouvez le faire dans draw()et avoir draw()un retour boolindiquant si l'algorithme doit continuer ou non.

Vérifiez les erreurs après la lecture d'un fichier, pas avant

Dans Engine::loadFile(), vous vérifiez si le fichier est ouvert correctement, mais vous ne vérifiez jamais s'il y a eu une erreur lors de la lecture. Un moyen possible est:

std::ifstream NumbersFile(pathToNumbersFile);

std::string Number;
while (std::getline(NumbersFile, Number) {
    numbers.push_back(std::stoi(Number));
}

if (!NumbersFile.eof()) {
    throw std::runtime_error("Error while reading numbers file.");
}

Ici, nous utilisons le fait que le eofbitn'est défini que s'il a atteint la fin du fichier avec succès, il ne le sera pas si le fichier n'a pas pu s'ouvrir ou si une erreur s'est produite avant d'atteindre la fin du fichier.