3
\$\begingroup\$

this is the second version of this project, you can check previous post here: Terminal Graphical Visualizer, using a queue of different matrices
I implemented bare-bones version of ncurses library (without using it obviously), and using it I implemented 3 visualizations:

  1. falling sand simulation - drop a line of sand particles of random length, if it encounters a particle below or the floor it stops, i used links of the send being dropped and removing the sand particle from the link if it encounters a position where it can't keep falling, it got 2 version of simulation, one it falls only in y axis and the second it can fall diagonally if encounters a particle below but diagonally down to the left or right is not occupied it falls there.
  2. sorting algorithm visualization - sorts columns based on height and visualizing each step of the sort, I used the matrix of the falling sand and sorted it.
  3. game of life - well it is the game of life I won't be posting the rules of it but you can search it up the rules are very simple, I added a function that configures the matrix to the glider gun.
    Code:
    graphical_visualizer.hpp
#pragma once

#include <chrono>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>

const std::unordered_map<std::string, char const *> colors = {{"red", "\033[31m"}, {"green", "\033[32m"}, {"blue", "\033[34m"}, {"yellow", "\033[33m"}, {"reset", "\033[0m"}};

void gotoxy(size_t x, size_t y);

struct Pixel { // represents every single character on terminal
    char character;
    char const *color_code;

    bool operator==(Pixel const &compare);
    bool operator!=(Pixel const &compare);
};

using frame_matrix = std::vector<std::vector<Pixel>>;

class Frame { // consists of a pixel matrix, methods to manipulate and print the frame and consts for more flexebility
private:
    void initialize_frame();

    bool is_valid_input(std::string const &height_start, std::string const &height_length, std::string const &range_start, std::string const &range_length, std::string const &printable_char, std::string const &color);

public:
    frame_matrix current_frame;
    const static size_t AMOUNT_OF_INPUT_OPTIONS = 6;
    const static size_t FRAME_WIDTH = 40;
    const static size_t FRAME_HEIGHT = 20;
    const static char INPUT_DELIMITER = ',';
    const static char BACKGROUND = ' ';

    Frame();
    Frame(std::string const &input);

    frame_matrix get_current_frame() const;
    void alter_frame(std::string const &input);
    void set_current_frame(frame_matrix const &new_current_frame);
    void print_frame();
    void print_frame(Frame const &prev_frame);
    bool parse_input(std::string const &input);
};

class GraphicalVisualizer { // consists of a queue of frames and methods that manipulate the queue and prints the frames in order
private:
    std::queue<Frame> frame_queue;

public:
    GraphicalVisualizer();

    std::queue<Frame> get_frame_queue() const;
    void add_frame(Frame frame);
    void print_sequence(const std::chrono::milliseconds millis);
    void print_sequence_no_clear(const std::chrono::milliseconds millis);
};

graphical_visualizer.cpp

#include "graphical_visualizer.hpp"

#include <cstring>
#include <iostream>
#include <sstream>
#include <thread>

void gotoxy(size_t x, size_t y) {
    std::cout << "\033[" << y << ";" << x << "H";
}

bool Pixel::operator==(Pixel const &compare) {
    return character == compare.character && std::strcmp(color_code, compare.color_code);
}

bool Pixel::operator!=(Pixel const &compare) {
    return character != compare.character || !std::strcmp(color_code, compare.color_code);
}

Frame::Frame() { initialize_frame(); }   // without parameters initializes the frame to an empty frame of FRAME::HEIGHT * FRAME::WIDTH filled with white FRAME::BACKGROUND characters
Frame::Frame(const std::string &input) { // with input from user initilizes the frame and sets it to according to the input
    initialize_frame();
    alter_frame(input);
}

frame_matrix Frame::get_current_frame() const { return current_frame; }

void Frame::alter_frame(std::string const &input) { // if parse_input found invalid input prints the message otherwise parse_input just does the job of actually altering the frame
    if (!parse_input(input))
        std::cout << "Invalid input \n";
}

void Frame::set_current_frame(frame_matrix const &new_current_frame) {
    current_frame = new_current_frame;
}

void Frame::print_frame() { // prints frame char by char first sets the color of the char then prints the char itself and then resets the color back to white
    for (auto const &line : current_frame) {
        for (auto const &current_pixel : line) {
            std::cout << current_pixel.color_code << current_pixel.character;
            std::cout << colors.at("reset");
        }

        std::cout << std::endl;
    }
}

void Frame::print_frame(Frame const &prev_frame) { // prints the frame using goto, used to print the frame without calling system("clear") every frame
    for (size_t i = 0; i < FRAME_HEIGHT; ++i) {
        for (size_t j = 0; j < FRAME_WIDTH; ++j) {
            if (current_frame[i][j] != prev_frame.current_frame[i][j]) {
                gotoxy(j, i);
                std::cout << current_frame[i][j].color_code << current_frame[i][j].character;
                std::cout << colors.at("reset");
            }
        }
    }
}

bool Frame::parse_input(std::string const &input) { // parses the input that consists of "heigh_start,height_length,range_start,range_length,shape,color"
    std::stringstream input_stream(input);

    std::string input_sections[AMOUNT_OF_INPUT_OPTIONS];

    size_t input_sections_index = 0;
    while (std::getline(input_stream, input_sections[input_sections_index], INPUT_DELIMITER)) // inserts the input into sections of an array
        ++input_sections_index;

    if (!is_valid_input(input_sections[0], input_sections[1], input_sections[2], input_sections[3], input_sections[4], input_sections[5]))
        return false;

    int height_start = std::stoi(input_sections[0]); // convers input into usable types

    int height_length = std::stoi(input_sections[1]);

    int range_start = std::stoi(input_sections[2]);

    int range_length = std::stoi(input_sections[3]);

    char const *color = colors.at(input_sections[5]);

    char printable_char = input_sections[4][0]; // should always be a string of length 1

    for (size_t i = height_start; i <= height_start + height_length - 1; ++i) { // alters the frame based on the input
        for (size_t j = range_start; j <= range_start + range_length - 1; ++j) {
            current_frame[i][j].character = printable_char;
            current_frame[i][j].color_code = color;
        }
    }

    return true;
}

void Frame::initialize_frame() { // initializes the frame with Frame::BACKGROUND and white color
    for (size_t i = 0; i < FRAME_HEIGHT; ++i) {
        std::vector<Pixel> line;
        line.reserve(FRAME_WIDTH);
        for (size_t j = 0; j < FRAME_WIDTH; ++j)
            line.push_back({BACKGROUND, colors.at("reset")});

        current_frame.push_back(line);
    }
}

bool is_a_number(std::string const &number) {
    for (char const &ch : number) {
        if (!std::isdigit(ch))
            return false;
    }

    return true;
}

bool Frame::is_valid_input(std::string const &height_start, std::string const &height_length, std::string const &range_start, std::string const &range_length, std::string const &printable_char, std::string const &color) {

    auto pos = colors.find(color);

    if (pos == colors.end())
        return false;

    if (!is_a_number(height_start) || !is_a_number(height_start) || !is_a_number(height_start) || !is_a_number(height_start)) { // checks if all passed numbers are actuall numbers
        return false;
    }
    if (printable_char.size() != 1)
        return false;

    int height_start_int = std::stoi(height_start);
    int height_length_int = std::stoi(height_length);
    int range_start_int = std::stoi(range_start);
    int range_length_int = std::stoi(range_length);

    // checks if the numbers passed are in range
    if (height_start_int < 0 ||
        height_length_int + height_length_int - 1 >= FRAME_HEIGHT ||
        range_start_int < 0 ||
        range_start_int + range_length_int - 1 >= FRAME_WIDTH)
        return false;
    return true;
}

GraphicalVisualizer::GraphicalVisualizer() { frame_queue = {}; } // initializes an empty queue

std::queue<Frame> GraphicalVisualizer::get_frame_queue() const {
    return frame_queue;
}

void GraphicalVisualizer::add_frame(Frame frame) { frame_queue.push(frame); } 

void GraphicalVisualizer::print_sequence(const std::chrono::milliseconds millis) { // accepts a parameter of time between each frame
    std::queue<Frame> local_temp_queue = frame_queue;

    while (local_temp_queue.size() > 1) { // prints the frame then pops it from the queue, sleeps for provided time and clears the terminal for the next frame

        local_temp_queue.front().print_frame();
        local_temp_queue.pop();

        std::this_thread::sleep_for(millis);
        system("clear");
    }

    local_temp_queue.front().print_frame(); // prints last frame without clearing it from the terminal
    local_temp_queue.pop();
}

// prints the frames same as print_sequence but without using system("clear") insted providing the previous frame so it can just update the pixels that are different
void GraphicalVisualizer::print_sequence_no_clear(const std::chrono::milliseconds millis) {
    std::queue<Frame> local_temp_queue = frame_queue;
    local_temp_queue.front().print_frame();
    while (local_temp_queue.size() > 1) {
        Frame last_frame = local_temp_queue.front();
        local_temp_queue.pop();
        local_temp_queue.front().print_frame(last_frame);

        std::this_thread::sleep_for(millis);
        // system("clear");
    }

    local_temp_queue.front().print_frame();
    local_temp_queue.pop();
    system("clear");
}

falling_sand.hpp

#pragma once

#include "graphical_visualizer.hpp"

#include <chrono>
#include <queue>
#include <string>
#include <vector>

using namespace std::chrono_literals;

const char SAND_SHAPE = '@';
const std::string SAND_DEFAULT_COLOR = "red";
const int SAND_BLOCK_AMOUNT = 15;
const int MIN_SAND_LENGTH = 5;
const int MAX_SAND_LENGTH = 15;
const std::chrono::milliseconds millis_per_frame_falling_sand = 20ms;

struct SandBlock { // represent a line of falling sand, each line consists of vector of pairs that represent the position of each sand particle within the block
    int length;
    int starting_position;
    char shape;
    std::string color;
    std::vector<std::pair<size_t, size_t>> links;

    SandBlock();
    SandBlock(std::string color);
};

class FallingSand { // consists of a frame that changes, the visualizer that prints the frames and a queue of sand blocks that after reaching a stop the next in queue falls
private:
    Frame field;
    GraphicalVisualizer visualizer;
    std::queue<SandBlock> sand_blocks;

public:
    FallingSand();
    void generate_sand_blocks();
    void simulate_fall();
    void simualte_diag_fall();

    Frame get_field() const;
};

falling_sand.cpp

#include "falling_sand.hpp"

#include <random>

int generate_random_length() {
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist(MIN_SAND_LENGTH, MAX_SAND_LENGTH); // distribution in range [MIN_SAND_LENGTH, MAX_SAND_LENGTH]

    return dist(rng);
}

int generate_random_start_pos(int sand_length) {
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist(0, Frame::FRAME_WIDTH - sand_length);

    return dist(rng);
}

SandBlock::SandBlock() { // generate random starting possition and length of the block and the pushes the particles into the vector of links
    length = generate_random_length();
    starting_position = generate_random_start_pos(length);
    shape = SAND_SHAPE;
    color = SAND_DEFAULT_COLOR;
    for (size_t i = starting_position; i <= starting_position + length - 1; ++i) {
        links.push_back({0, i});
    }
}

SandBlock::SandBlock(std::string color) : color(color) { // same as without params but sets the color to the provided one instead of default const
    length = generate_random_length();
    starting_position = generate_random_start_pos(length);
    shape = SAND_SHAPE;
    for (size_t i = starting_position; i <= starting_position + length - 1; ++i) {
        links.push_back({0, i});
    }
}

FallingSand::FallingSand() {
    generate_sand_blocks();
    // simulate_fall();
    simualte_diag_fall();
    system("clear");
    visualizer.print_sequence(millis_per_frame_falling_sand);
}

void FallingSand::generate_sand_blocks() {
    for (size_t i = 1; i <= SAND_BLOCK_AMOUNT; ++i) {

        sand_blocks.push(SandBlock());
    }
}

void FallingSand::simulate_fall() { // the simulation itself this is version without daig falls of sand particles only in the y axis

    while (!sand_blocks.empty()) {
        SandBlock current_block = sand_blocks.front();

        bool did_move = true;
        while (did_move) {
            did_move = false;
            visualizer.add_frame(field); // adds the current state of frame to the visualizer queue

            frame_matrix new_frame = field.get_current_frame();

            for (size_t i = 0; i < current_block.links.size(); ++i) {
                std::pair<size_t, size_t> link = current_block.links[i]; // tracks the current sand particle

                // checks for bounds
                if (link.first != Frame::FRAME_HEIGHT - 1 && field.get_current_frame()[link.first + 1][link.second].character != current_block.shape) {
                    did_move = true;
                    // resets the color and shape of current place, then updates the color and shape of the one below
                    new_frame[current_block.links[i].first][current_block.links[i].second] = {Frame::BACKGROUND, colors.at("reset")};
                    current_block.links[i].first += 1;
                    new_frame[current_block.links[i].first][current_block.links[i].second] = {current_block.shape, colors.at(current_block.color)};
                }
            }

            field.set_current_frame(new_frame);
        }
        sand_blocks.pop();
    }
}

int generate_random_direction() { // generates random direction for diag fall either fall left or right
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist(0, 1);

    return dist(rng);
}

void FallingSand::simualte_diag_fall() {
    while (!sand_blocks.empty()) {
        SandBlock current_block = sand_blocks.front();

        bool did_move = true;
        while (did_move) {
            did_move = false;
            visualizer.add_frame(field);
            frame_matrix new_frame = field.get_current_frame();

            for (size_t i = 0; i < current_block.links.size(); ++i) {
                std::pair<size_t, size_t> link = current_block.links[i];
                // same as without diag fall but it does not stop when encounters anothe particle below
                if (link.first != Frame::FRAME_HEIGHT - 1 && field.get_current_frame()[link.first + 1][link.second].character != current_block.shape) {
                    did_move = true;
                    new_frame[current_block.links[i].first][current_block.links[i].second] = {Frame::BACKGROUND, colors.at("reset")};
                    current_block.links[i].first += 1;

                    new_frame[current_block.links[i].first][current_block.links[i].second] = {current_block.shape, colors.at(current_block.color)};
                } else if (link.first != Frame::FRAME_HEIGHT - 1 && field.get_current_frame()[link.first + 1][link.second].character == current_block.shape) {

                    int direction = generate_random_direction();

                    // checks if generated 1 in direction and also for bounds and posibility to fall diagonally to the left else checks right
                    if (link.second != Frame::FRAME_WIDTH - 1 && direction == 1 && field.get_current_frame()[link.first + 1][link.second + 1].character != current_block.shape) {
                        new_frame[current_block.links[i].first][current_block.links[i].second] = {Frame::BACKGROUND, colors.at("reset")};
                        current_block.links[i].first += 1;
                        current_block.links[i].second += 1;
                        new_frame[current_block.links[i].first][current_block.links[i].second] = {current_block.shape, colors.at(current_block.color)};
                    } else if (link.second != 0 && field.get_current_frame()[link.first + 1][link.second - 1].character != current_block.shape) {
                        new_frame[current_block.links[i].first][current_block.links[i].second] = {Frame::BACKGROUND, colors.at("reset")};
                        current_block.links[i].first += 1;
                        current_block.links[i].second -= 1;
                        new_frame[current_block.links[i].first][current_block.links[i].second] = {current_block.shape, colors.at(current_block.color)};
                    }
                }
            }

            field.set_current_frame(new_frame);
        }
        sand_blocks.pop();
    }
}

Frame FallingSand::get_field() const {
    return field;
}

algorithm_visualizer.hpp

#pragma once

#include "falling_sand.hpp"
#include "graphical_visualizer.hpp"
#include <chrono>
#include <iterator>
#include <queue>
#include <string>

using namespace std::chrono_literals;

using grid = std::vector<std::pair<int, char const *>>; // represents the frame in heights of columns and remembers the color of the column
const std::chrono::milliseconds millis_per_frame_algo_vis = 50ms;

class AlgorithmVisualizer { // consists of the visualizer to print the frames, a frame that is changing and different kind of sorting algos that works on grid
private:
    GraphicalVisualizer visualizer;
    Frame to_sort;

public:
    AlgorithmVisualizer(Frame to_sort, char symbol);

    std::vector<std::pair<int, char const *>> frame_matrix_to_num(frame_matrix convert_from, char symbol_to_count);
    frame_matrix num_to_frame_matrix(std::vector<std::pair<int, char const *>> convert_from, char symbol_to_insert);

    void bubble_sort(grid &vect_to_sort, char symbol);
    void merge_sort(grid &vect_to_sort, char symbol);
    void merge_sort(grid &vect_to_sort, char symbol, size_t start, size_t end, std::vector<std::pair<int, char const *>> &printable_frame);
    void quick_sort(grid &vect_to_sort, char symbol, size_t start, size_t end);
};

algorithm_visualizer.cpp

#include "algorithm_visualizer.hpp"
#include <algorithm>
#include <iostream>
#include <random>

char const *generate_random_color() {
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist(0, colors.size() - 1);
    auto it = colors.begin();
    for (size_t i = 0; i < dist(rng); ++i) {
        it++;
    }
    if (it->first == "reset")
        return colors.at("red");
    return it->second;
}

void change_frame_colors(Frame &frame_to_color, char symbol_to_color) {

    for (size_t j = 0; j < frame_to_color.current_frame[0].size(); ++j) {

        char const *color = generate_random_color();
        for (size_t i = 0; i < frame_to_color.current_frame.size(); ++i) {
            if (frame_to_color.current_frame[i][j].character == symbol_to_color)
                frame_to_color.current_frame[i][j].color_code = color;
        }
    }
}

AlgorithmVisualizer::AlgorithmVisualizer(Frame to_sort, char symbol) : to_sort(to_sort) { // place the algorithm to visualize in the body remove or comment the others

    change_frame_colors(to_sort, SAND_SHAPE);
    grid frame_to_vec = frame_matrix_to_num(to_sort.current_frame, symbol);
    // bubble_sort(frame_to_vec, symbol);
    //  merge_sort(frame_to_vec, symbol);
    quick_sort(frame_to_vec, symbol, 0, frame_to_vec.size());
    system("clear");
    visualizer.print_sequence(millis_per_frame_algo_vis);
}

grid AlgorithmVisualizer::frame_matrix_to_num(frame_matrix convert_from, char symbol_to_count) { // converts a frame matrix into a vector of numbers and colors so the soring algos can work on it
    grid convert_to;
    // calculate the height of each column
    for (size_t i = 0; i < convert_from[0].size(); ++i) {
        int symbol_counter = 0;
        char const *color = colors.at("reset");
        for (size_t j = 0; j < convert_from.size(); ++j) {
            if (convert_from[j][i].character == symbol_to_count) {
                symbol_counter++;
                color = convert_from[j][i].color_code;
            }
        }
        convert_to.push_back({symbol_counter, color});
    }
    return convert_to;
}

frame_matrix AlgorithmVisualizer::num_to_frame_matrix(grid convert_from, char symbol_to_insert) { // converts the grid back into a frame_matrix so it could be inserted into the visualizer queue
    frame_matrix convert_to;
    // initializes a frame
    for (size_t i = 0; i < Frame::FRAME_HEIGHT; ++i) {
        std::vector<Pixel> line;
        for (size_t j = 0; j < Frame::FRAME_WIDTH; ++j)
            line.push_back({Frame::BACKGROUND, colors.at("reset")});

        convert_to.push_back(line);
    }

    int matrix_index = 0;
    // places the shape and color to insert into the frame goes column by column
    for (auto height : convert_from) {
        if (height.first >= Frame::FRAME_HEIGHT)
            return {};
        for (int j = 0; j < height.first; ++j) {
            convert_to[convert_to.size() - j - 1][matrix_index] = {symbol_to_insert, height.second};
        }
        matrix_index++;
    }
    return convert_to;
}

void AlgorithmVisualizer::bubble_sort(grid &vect_to_sort, char symbol) {

    for (size_t i = 0; i < vect_to_sort.size() - 1; ++i) {
        bool is_swapped = false;
        for (size_t j = 0; j < vect_to_sort.size() - i - 1; ++j) {
            to_sort.set_current_frame(num_to_frame_matrix(vect_to_sort, symbol));
            visualizer.add_frame(to_sort);
            if (vect_to_sort[j].first >= vect_to_sort[j + 1].first) {
                std::swap(vect_to_sort[j], vect_to_sort[j + 1]);
                is_swapped = true;
            }
        }
        if (!is_swapped)
            break;
    }
}

void AlgorithmVisualizer::merge_sort(grid &vect_to_sort, char symbol) {
    if (vect_to_sort.size() <= 1)
        return;

    size_t mid = (vect_to_sort.size() / 2);
    grid left(vect_to_sort.cbegin(), vect_to_sort.cbegin() + mid);
    grid right(vect_to_sort.cbegin() + mid, vect_to_sort.cend());
    vect_to_sort.clear();
    merge_sort(left, symbol);
    merge_sort(right, symbol);

    size_t left_index = 0;
    size_t right_index = 0;
    while (vect_to_sort.size() < left.size() + right.size()) {
        if (left_index == left.size())
            vect_to_sort.insert(vect_to_sort.end(), right.begin() + right_index, right.end());
        else if (right_index == right.size())
            vect_to_sort.insert(vect_to_sort.end(), left.begin() + left_index, left.end());
        else {
            if (left[left_index].first > right[right_index].first) {
                vect_to_sort.push_back(left[left_index]);
                left_index++;
            } else {
                vect_to_sort.push_back(right[right_index]);
                right_index++;
            }
        }
    }

    to_sort.set_current_frame(num_to_frame_matrix(vect_to_sort, symbol));
    visualizer.add_frame(to_sort);
}

// same as the default merge_sort but with saving the new vector shape so the frame can show the whole vector changing
void AlgorithmVisualizer::merge_sort(grid &vect_to_sort, char symbol, size_t start, size_t end, grid &printable_frame) {
    if (vect_to_sort.size() <= 1)
        return;

    size_t mid = (vect_to_sort.size() / 2);
    grid left(vect_to_sort.cbegin(), vect_to_sort.cbegin() + mid);
    grid right(vect_to_sort.cbegin() + mid, vect_to_sort.cend());
    vect_to_sort.clear();
    merge_sort(left, symbol, start, mid, printable_frame);
    merge_sort(right, symbol, mid, end, printable_frame);

    size_t left_index = 0;
    size_t right_index = 0;
    while (vect_to_sort.size() < left.size() + right.size()) {
        if (left_index == left.size())
            vect_to_sort.insert(vect_to_sort.end(), right.begin() + right_index, right.end());
        else if (right_index == right.size())
            vect_to_sort.insert(vect_to_sort.end(), left.begin() + left_index, left.end());
        else {
            if (left[left_index].first > right[right_index].first) {
                vect_to_sort.push_back(left[left_index]);
                left_index++;
            } else {
                vect_to_sort.push_back(right[right_index]);
                right_index++;
            }
        }
    }

    for (size_t i = 0; i < vect_to_sort.size(); ++i) {
        if (start < end) {
            printable_frame[start] = vect_to_sort[i];
            start++;
        }
    }

    to_sort.set_current_frame(num_to_frame_matrix(printable_frame, symbol));
    visualizer.add_frame(to_sort);
}

void AlgorithmVisualizer::quick_sort(grid &vect_to_sort, char symbol, size_t start, size_t end) {
    if (end - start <= 0)
        return;

    size_t pivot = end - 1;
    size_t first_bigger_than_pivot = start;
    bool is_bigger_found = vect_to_sort[first_bigger_than_pivot].first > vect_to_sort[pivot].first;
    for (size_t i = start; i < pivot; ++i) {
        if (!is_bigger_found && vect_to_sort[i].first > vect_to_sort[pivot].first) {
            is_bigger_found = true;
            first_bigger_than_pivot = i;
        }
        if (is_bigger_found && vect_to_sort[i].first <= vect_to_sort[pivot].first) {
            std::rotate(vect_to_sort.begin() + first_bigger_than_pivot, vect_to_sort.begin() + i, vect_to_sort.begin() + i + 1);
            to_sort.set_current_frame(num_to_frame_matrix(vect_to_sort, symbol));
            visualizer.add_frame(to_sort);
            first_bigger_than_pivot += 1;
        }
    }

    if (is_bigger_found) {
        std::swap(vect_to_sort[pivot], vect_to_sort[first_bigger_than_pivot]);
        pivot = first_bigger_than_pivot;
        to_sort.set_current_frame(num_to_frame_matrix(vect_to_sort, symbol));
        visualizer.add_frame(to_sort);
    }

    quick_sort(vect_to_sort, symbol, start, pivot);
    quick_sort(vect_to_sort, symbol, pivot + 1, end);
}

game_of_life.hpp

#pragma once

#include "graphical_visualizer.hpp"
#include <string>

using namespace std::chrono_literals;

namespace game_of_life {
    char const CELL_SHAPE = '*';
    std::string const CELL_COLOR = "blue";
    const std::chrono::milliseconds millis_per_frame_game_of_life = 100ms;
}

bool is_cell_alive(frame_matrix const &grid, size_t x, size_t y);

void simulate_game_of_life(Frame &frame, size_t amount_of_cycles, void (*game_conf)(frame_matrix &));

void glider_gun_conf(frame_matrix &grid);

game_of_life.cpp

#include "game_of_life.hpp"

#include <set>

bool is_cell_alive(frame_matrix const &grid, size_t x, size_t y) {
    int amount_of_neighbors = 0;
    if (x > 0) {
        if (grid[y][x - 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
        if (y > 0 && grid[y - 1][x - 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
        if (y < Frame::FRAME_HEIGHT - 1 && grid[y + 1][x - 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
    }
    if (x < Frame::FRAME_WIDTH - 1) {
        if (grid[y][x + 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
        if (y > 0 && grid[y - 1][x + 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
        if (y < Frame::FRAME_HEIGHT - 1 && grid[y + 1][x + 1].character == game_of_life::CELL_SHAPE)
            ++amount_of_neighbors;
    }

    if (y > 0 && grid[y - 1][x].character == game_of_life::CELL_SHAPE)
        ++amount_of_neighbors;
    if (y < Frame::FRAME_HEIGHT - 1 && grid[y + 1][x].character == game_of_life::CELL_SHAPE)
        ++amount_of_neighbors;

    if (grid[y][x].character == game_of_life::CELL_SHAPE && amount_of_neighbors < 2)
        return false;
    else if (grid[y][x].character == game_of_life::CELL_SHAPE && amount_of_neighbors > 3)
        return false;
    else if (grid[y][x].character == Frame::BACKGROUND && amount_of_neighbors != 3)
        return false;

    return true;
}

void simulate_game_of_life(Frame &frame, size_t amount_of_cycles, void (*game_conf)(frame_matrix &)) {
    GraphicalVisualizer visualizer;
    game_conf(frame.current_frame);
    visualizer.add_frame(frame);
    for (size_t cycle = 1; cycle <= amount_of_cycles; ++cycle) {
        std::set<std::pair<size_t, size_t>> alive_cells;
        for (size_t i = 0; i < Frame::FRAME_HEIGHT; ++i) {
            for (size_t j = 0; j < Frame::FRAME_WIDTH; ++j) {
                if (is_cell_alive(frame.current_frame, j, i))
                    alive_cells.insert({j, i});
            }
        }
        for (size_t i = 0; i < Frame::FRAME_WIDTH; ++i) {
            for (size_t j = 0; j < Frame::FRAME_HEIGHT; ++j) {
                auto find_cell = alive_cells.find({i, j});
                if (find_cell != alive_cells.cend())
                    frame.current_frame[j][i] = {game_of_life::CELL_SHAPE, colors.at(game_of_life::CELL_COLOR)};
                else
                    frame.current_frame[j][i] = {Frame::BACKGROUND, colors.at("reset")};
            }
        }
        visualizer.add_frame(frame);
    }
    visualizer.print_sequence(game_of_life::millis_per_frame_game_of_life);
}

void glider_gun_conf(frame_matrix &grid) {
    size_t ref_point_y = Frame::FRAME_HEIGHT / 4;
    size_t ref_point_x = Frame::FRAME_WIDTH / 4;

    using namespace game_of_life;
    // left square
    grid[ref_point_y][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    // left square

    // middle part 10 to right
    ref_point_x += 10;

    grid[ref_point_y][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 2][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 1][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 3][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 2][ref_point_x + 2] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 4][ref_point_x + 2] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 2][ref_point_x + 3] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 4][ref_point_x + 3] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x + 4] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 1][ref_point_x + 5] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 3][ref_point_x + 5] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y][ref_point_x + 6] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x + 6] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 2][ref_point_x + 6] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x + 7] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    // middle part

    // right part
    ref_point_x += 10;
    ref_point_y -= 2;

    grid[ref_point_y][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 2][ref_point_x] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 1][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 2][ref_point_x + 1] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 1][ref_point_x + 2] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 3][ref_point_x + 2] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 1][ref_point_x + 4] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y - 2][ref_point_x + 4] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 3][ref_point_x + 4] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    grid[ref_point_y + 4][ref_point_x + 4] = {CELL_SHAPE, colors.at(CELL_COLOR)};
    // right part
}

main.cpp

#include <chrono>
#include <iostream>
#include <string>

#include "algorithm_visualizer.hpp"
#include "falling_sand.hpp"
#include "game_of_life.hpp"

using namespace std::chrono_literals;

int main(int, char **) {

    auto start = std::chrono::high_resolution_clock::now();

    FallingSand simualtion;
    AlgorithmVisualizer algo_vis(simualtion.get_field(), SAND_SHAPE);

    Frame frame;
    simulate_game_of_life(frame, 100, &glider_gun_conf);

    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> duration = end - start;
    std::cout << "Execution time: " << duration.count() << " seconds." << std::endl;

    return 0;
}

couple of notes: in graphical_visualizer I tried adding another version of printing the sequence without using clear it semi-works and I still prefer the system("clear") because I got annoyed trying to solve stupid bugs with it.
second in algorithm_visualizer I have 2 versions of merge sort, one is prints sequence of sorted sub arrays, the second shows the whole array getting sorted.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Overall seems really cute, but here are some possible points for improvements:

  • generate_random_direction is a free function inside some cpp file of a class. I might be a better practice to make it a private function (maybe even private static function) of the class.

  • simualte_diag_fall seems like a really big function. I think it'd be better to split it into multiple functions.

  • millis_per_frame_game_of_life is out of naming convention.

  • For free functions, consider using a namespace to make it more encapsulated.

Overall seems good! Good luck!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.