Skip to content

Instantly share code, notes, and snippets.

@klevcsoo
Last active June 23, 2022 14:28
Show Gist options
  • Save klevcsoo/e8857459ca605c2aa30e2e7d92fce30f to your computer and use it in GitHub Desktop.
Save klevcsoo/e8857459ca605c2aa30e2e7d92fce30f to your computer and use it in GitHub Desktop.
A simple C++ sorting algorithm visualizer using SFML.
#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