Last active
June 23, 2022 14:28
-
-
Save klevcsoo/e8857459ca605c2aa30e2e7d92fce30f to your computer and use it in GitHub Desktop.
A simple C++ sorting algorithm visualizer using SFML.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <list> | |
#include <utility> | |
#include <random> | |
#include <algorithm> | |
#include <iterator> | |
#include <vector> | |
#include <SFML/System.hpp> | |
#include <SFML/Graphics.hpp> | |
#pragma clang diagnostic push | |
#pragma ide diagnostic ignored "UnreachableCode" | |
#pragma ide diagnostic ignored "cert-err58-cpp" | |
enum SortingMethod { | |
NONE, | |
BUBBLE, | |
SELECTION, | |
INSERTION | |
}; | |
class ScreenText : public sf::Text { | |
static sf::Font m_font; | |
public: | |
ScreenText(const std::string& text, int size, float x = 0.F, float y = 0.F) { | |
this->setString(text); | |
this->setCharacterSize(size); | |
this->setFont(ScreenText::m_font); | |
this->setPosition(x, y); | |
} | |
static void set_global_font(const sf::Font& font) { | |
ScreenText::m_font = font; | |
} | |
}; | |
sf::Font ScreenText::m_font; | |
template<typename T> | |
int* at(std::list<T>& list, int i) { | |
return &*std::next(list.begin(), i); | |
} | |
template<typename T> | |
std::ostream& operator<<(std::ostream& os, std::list<T>& list) { | |
os << "{ "; | |
for (int i = 0; i < list.size(); i++) { | |
if (i != 0) os << ", "; | |
os << *at(list, i); | |
} | |
os << " }"; | |
return os; | |
} | |
sf::Font load_app_font() { | |
std::string p = "/Users/klevcsoo/Developer/SortingAlgorithms/source-code-pro.ttf"; | |
sf::Font app_font; | |
if (!app_font.loadFromFile(p)) { | |
std::cerr << "Error: Failed to load application font." << std::endl; | |
throw std::exception(); | |
} | |
return app_font; | |
} | |
std::list<int> generate_int_list(unsigned int size) { | |
std::list<int> out; | |
for (int i = 1; i <= size; i++) out.push_back(i); | |
std::random_device rd; | |
std::mt19937 g(rd()); | |
std::vector<int> v(out.begin(), out.end()); | |
std::shuffle(v.begin(), v.end(), g); | |
out.assign(v.begin(), v.end()); | |
return out; | |
} | |
sf::Color generate_elem_color(float relative_elem_pos) { | |
const float h = relative_elem_pos, s = 1.F, l = 0.5F; | |
float result_r, result_g, result_b; | |
auto hue_to_rgb = [](float p, float q, float t) { | |
if (t < 0) t += 1; | |
if (t > 1) t -= 1; | |
if (t < 1.F / 6) return p + (q - p) * 6 * t; | |
if (t < 1.F / 2) return q; | |
if (t < 2.F / 3) return p + (q - p) * (2.F / 3.F - t) * 6; | |
return p; | |
}; | |
float q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
float p = 2 * l - q; | |
result_r = hue_to_rgb(p, q, h + 1.F / 3) * 255; | |
result_g = hue_to_rgb(p, q, h) * 255; | |
result_b = hue_to_rgb(p, q, h - 1.F / 3) * 255; | |
return { | |
static_cast<sf::Uint8>(result_r), | |
static_cast<sf::Uint8>(result_g), | |
static_cast<sf::Uint8>(result_b) | |
}; | |
} | |
int bubble_sort(std::list<int>& out, int step) { | |
int o = 0; | |
for (int i = 0; i < out.size() - step - 1; i++) { | |
auto first = at(out, i); | |
auto second = at(out, i + 1); | |
if (*first > *second) std::iter_swap(first, second); | |
o = *second; | |
} | |
return o; | |
} | |
int selection_sort(std::list<int>& out, int step) { | |
auto min_ptr = at(out, step); | |
for (int i = step + 1; i < out.size(); i++) { | |
auto n_ptr = at(out, i); | |
if (*n_ptr < *min_ptr) min_ptr = n_ptr; | |
} | |
std::iter_swap(min_ptr, at(out, step)); | |
return *min_ptr; | |
} | |
int insertion_sort(std::list<int>& out, int step) { | |
// Using C-style array, because its very inefficient | |
// and tricky to insert elements into std::list. | |
int array[out.size()]; | |
std::copy(out.begin(), out.end(), array); | |
auto key = array[step]; | |
int left = step - 1; | |
while (key < array[left] && left >= 0) { | |
array[left + 1] = array[left]; | |
left--; | |
} | |
array[left + 1] = key; | |
std::copy(array, array + out.size(), out.begin()); | |
return key; | |
} | |
int main() { | |
unsigned int list_size = 500; | |
std::list<int> sorted_list; | |
auto chosen_method = SortingMethod::NONE; | |
int active_algorithm_step = 0; | |
int active_algorithm_value = 0; | |
sf::RenderWindow win(sf::VideoMode(1920, 1080), "Sorting algorithms"); | |
win.setFramerateLimit(60); | |
win.setVerticalSyncEnabled(true); | |
ScreenText::set_global_font(load_app_font()); | |
std::map<std::string, ScreenText> screen_texts = { | |
{ | |
"main.title", | |
ScreenText("Select an algorithm:", 32, 50, 50) | |
}, | |
{ | |
"main.selection", | |
ScreenText("1: bubble, 2: selection, 3: insertion ", 28, 50, 150) | |
}, | |
{ | |
"main.selection.help", | |
ScreenText("(Press the associated button on the keyboard)", 24, 50, 200) | |
}, | |
{ | |
"main.list-size.counter", | |
ScreenText("current size: 500", 28, 50, 300) | |
}, | |
{ | |
"main.list-size.help", | |
ScreenText( | |
"To change the number of elements, use the arrow keys\n" | |
"(up / down -> +-1, right / left -> +-100)", | |
24, 50, 350 | |
) | |
}, | |
{ | |
"quit.help", | |
ScreenText("Press ESC to stop and quit", 28, 50, 50) | |
} | |
}; | |
while (win.isOpen()) { | |
sf::Event e{}; | |
while (win.pollEvent(e)) { | |
if (e.type == sf::Event::Closed) win.close(); | |
} | |
win.clear(); | |
if (chosen_method == SortingMethod::NONE) { | |
win.draw(screen_texts.at("main.title")); | |
win.draw(screen_texts.at("main.selection")); | |
win.draw(screen_texts.at("main.selection.help")); | |
win.draw(screen_texts.at("main.list-size.help")); | |
win.draw(screen_texts.at("main.list-size.counter")); | |
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num1)) { | |
chosen_method = SortingMethod::BUBBLE; | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num2)) { | |
chosen_method = SortingMethod::SELECTION; | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num3)) { | |
chosen_method = SortingMethod::INSERTION; | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { | |
screen_texts.at("main.list-size.counter").setString( | |
"current size: " + std::to_string(++list_size) | |
); | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { | |
screen_texts.at("main.list-size.counter").setString( | |
"current size: " + std::to_string(--list_size) | |
); | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { | |
screen_texts.at("main.list-size.counter").setString( | |
"current size: " + std::to_string(list_size += 100) | |
); | |
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { | |
screen_texts.at("main.list-size.counter").setString( | |
"current size: " + std::to_string(list_size -= 100) | |
); | |
} | |
} else { | |
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) { | |
sorted_list.clear(); | |
chosen_method = SortingMethod::NONE; | |
active_algorithm_step = 0; | |
active_algorithm_value = 0; | |
continue; | |
} | |
if (sorted_list.empty()) { | |
sorted_list = generate_int_list(list_size); | |
} | |
if (active_algorithm_step < sorted_list.size()) { | |
std::cout << "Job in progress...\r" << std::flush; | |
switch (chosen_method) { | |
case BUBBLE: | |
active_algorithm_value = bubble_sort( | |
sorted_list, active_algorithm_step | |
); | |
break; | |
case SELECTION: | |
active_algorithm_value = selection_sort( | |
sorted_list, active_algorithm_step | |
); | |
break; | |
case INSERTION: | |
active_algorithm_value = insertion_sort( | |
sorted_list, active_algorithm_step | |
); | |
break; | |
default: | |
std::cerr << "Error: Invalid sorting algorithm.\n"; | |
break; | |
} | |
active_algorithm_step++; | |
} else { | |
std::cout << "Finished job.\r" << std::flush; | |
} | |
const float screen_w_f = static_cast<float>(win.getSize().x), | |
screen_h_f = static_cast<float>(win.getSize().y), | |
list_size_f = static_cast<float>(sorted_list.size()); | |
float column_width = screen_w_f / list_size_f; | |
for (int i = 0; i < sorted_list.size(); i++) { | |
auto current_value = *at(sorted_list, i); | |
// Where the element is in the list (from 0 to 1). | |
// So for example: size is 100, the index of the element | |
// is 39, so the elem_pos is 0.4 (zero-indexes). | |
float elem_pos = (float) current_value / list_size_f; | |
float height = screen_h_f * elem_pos; | |
sf::RectangleShape rect(sf::Vector2f(column_width, height)); | |
rect.setPosition(column_width * ((float) i + 1.F), screen_h_f - height); | |
sf::Color c = active_algorithm_value == current_value ? | |
sf::Color::White : generate_elem_color(elem_pos); | |
rect.setFillColor(c); | |
win.draw(rect); | |
} | |
win.draw(screen_texts.at("quit.help")); | |
} | |
win.display(); | |
} | |
return 0; | |
} | |
#pragma clang diagnostic pop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment