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:
- 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.
- 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.
- 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 ¤t_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.