From eecf4b1e666211a13afa56f93477c55e8fd01621 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 2 Jun 2022 19:51:54 +0200 Subject: feat: implement game of life --- src/game/cell_helper.hpp | 34 ++++++++++++++ src/game/cell_helper.tpp | 106 ++++++++++++++++++++++++++++++++++++++++++++ src/game/game.cpp | 103 ++++++++++++++++++++++++++++++++++++------ src/game/game.hpp | 19 ++++++-- src/game/status_manager.cpp | 2 + 5 files changed, 247 insertions(+), 17 deletions(-) create mode 100644 src/game/cell_helper.hpp create mode 100644 src/game/cell_helper.tpp (limited to 'src/game') diff --git a/src/game/cell_helper.hpp b/src/game/cell_helper.hpp new file mode 100644 index 0000000..cf41c6f --- /dev/null +++ b/src/game/cell_helper.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "interfaces/cell_helper.hpp" +#include "interfaces/matrix.hpp" + +#include "engine/data/vector2.hpp" + +#include +#include + +template +class CellHelper : public ICellHelper +{ +public: + explicit CellHelper(const IMatrix &matrix) noexcept; + + [[nodiscard]] auto is_cell_dying(const Vector2 &cell_pos) const noexcept + -> bool override; + + [[nodiscard]] auto + get_birth_cell_positions(const std::list &cell_positions) const noexcept + -> std::list override; + + [[nodiscard]] auto find_neighbours(const Vector2 &cell_pos) const noexcept + -> std::list override; + +private: + const IMatrix &_matrix; + + static auto _get_position_neighbours(const Vector2 &position) noexcept + -> std::list; +}; + +#include "cell_helper.tpp" diff --git a/src/game/cell_helper.tpp b/src/game/cell_helper.tpp new file mode 100644 index 0000000..ebb35aa --- /dev/null +++ b/src/game/cell_helper.tpp @@ -0,0 +1,106 @@ +#pragma once + +#include "cell_helper.hpp" + +#include "util/algorithm.hpp" + +template +constexpr auto has_matrix_value( + const IMatrix &matrix, + const MatrixElement &value) noexcept +{ + return [&matrix, &value](const Vector2 &pos) + { + return matrix.get(pos) == value; + }; +} + +template +CellHelper::CellHelper(const IMatrix &matrix) noexcept + : _matrix(matrix) +{ +} + +template +auto CellHelper::is_cell_dying(const Vector2 &cell_pos) const noexcept + -> bool +{ + const auto neighbour_cell_positions = + container_filter(find_neighbours(cell_pos), has_matrix_value(_matrix, 'x')); + + const auto neighbour_cell_cnt = neighbour_cell_positions.size(); + + return neighbour_cell_cnt < 2 || neighbour_cell_cnt >= 4; +} + +template +auto CellHelper::get_birth_cell_positions( + const std::list &cell_positions) const noexcept -> std::list +{ + auto all_empty_neighbour_positions = std::list(); + + for (const auto &cell_pos : cell_positions) + { + const std::list empty_neighbour_positions = + container_filter(find_neighbours(cell_pos), has_matrix_value(_matrix, ' ')); + + all_empty_neighbour_positions.insert( + all_empty_neighbour_positions.end(), + empty_neighbour_positions.begin(), + empty_neighbour_positions.end()); + } + + // Remove duplicates + all_empty_neighbour_positions.sort(); + all_empty_neighbour_positions.unique(); + + auto birth_cell_positions = container_filter( + all_empty_neighbour_positions, + [this](const Vector2 &cell_pos) + { + const auto neighbour_cell_positions = container_filter( + find_neighbours(cell_pos), + has_matrix_value(_matrix, 'x')); + + return neighbour_cell_positions.size() == 3; + }); + + return birth_cell_positions; +} + +template +auto CellHelper::find_neighbours(const Vector2 &cell_pos) const noexcept + -> std::list +{ + std::list cell_positions = {}; + + const auto matrix_size = + Bounds({.width = _matrix.get_column_cnt(), .height = _matrix.get_row_cnt()}); + + const auto neighbours = _get_position_neighbours(cell_pos); + + for (const auto &neighbour_pos : neighbours) + { + if (matrix_size.validate_coords(neighbour_pos) == CoordsValidation::VALID) + { + cell_positions.push_back(neighbour_pos); + } + } + + return cell_positions; +} + +template +auto CellHelper::_get_position_neighbours( + const Vector2 &position) noexcept -> std::list +{ + return { + position + Vector2::up(), + position + Vector2::down(), + position + Vector2::left(), + position + Vector2::right(), + position + Vector2::up() + Vector2::left(), + position + Vector2::up() + Vector2::right(), + position + Vector2::down() + Vector2::left(), + position + Vector2::down() + Vector2::right()}; +} diff --git a/src/game/game.cpp b/src/game/game.cpp index 41bafdf..55877bb 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1,7 +1,10 @@ #include "game.hpp" +#include "util/algorithm.hpp" + #include +#include #include #include #include @@ -11,13 +14,14 @@ Game::Game( std::shared_ptr cursor_controller, std::shared_ptr generation_tracker, std::shared_ptr status_manager, - std::shared_ptr user_input_observer) noexcept + std::shared_ptr user_input_observer, + std::shared_ptr cell_helper) noexcept : _scene(std::move(scene)), _cursor_controller(std::move(cursor_controller)), _generation_tracker(std::move(generation_tracker)), _status_manager(std::move(status_manager)), _user_input_observer(std::move(user_input_observer)), - _gen_update_speed_millis(1000) + _cell_helper(std::move(cell_helper)) { } @@ -31,6 +35,8 @@ void Game::on_start() noexcept _status_manager->set_section_title(StatusLineSection::D, "Paused: "); _status_manager->set_section_title(StatusLineSection::E, "Generation: "); _status_manager->set_section_title(StatusLineSection::F, "Time since last frame: "); + _status_manager->set_section_title(StatusLineSection::G, "Living cells: "); + _status_manager->set_section_title(StatusLineSection::H, "Window size: "); const auto scene_size = _scene->size(); @@ -56,6 +62,15 @@ void Game::on_start() noexcept _status_manager->set_section_body(StatusLineSection::F, "0"); + _status_manager->set_section_body(StatusLineSection::G, "0"); + + _status_manager->set_section_body( + StatusLineSection::H, + fmt::format( + "Width {} Height {}", + scene_size.get_width(), + scene_size.get_height())); + _last_update_time = std::chrono::system_clock::now(); } @@ -64,6 +79,7 @@ void Game::on_update() noexcept const auto pressed_key = _user_input_observer->get_currently_pressed_key(); auto cursor_has_moved = false; + auto is_generation_stepping = false; switch (pressed_key) { @@ -91,8 +107,19 @@ void Game::on_update() noexcept std::exit(EXIT_SUCCESS); case 'i': - _insert_cell(_cursor_controller->where(), 'x'); + { + const auto position = _cursor_controller->where(); + const auto matrix = _scene->get_matrix(); + + if (matrix->get(position) == 'x') + { + break; + } + + _set_space(matrix, position, 'x'); + _living_cell_positions.push_back(position); break; + } case 'p': { @@ -104,6 +131,10 @@ void Game::on_update() noexcept break; } + case 's': + is_generation_stepping = true; + break; + default: break; } @@ -129,12 +160,21 @@ void Game::on_update() noexcept StatusLineSection::F, fmt::format("{} nanoseconds", time_since_last_update.count())); + _status_manager->set_section_body( + StatusLineSection::G, + fmt::format("{}", _living_cell_positions.size())); + + if (_generation_tracker->get_is_paused() && !is_generation_stepping) + { + _last_update_time = time_now; + return; + } + const auto time_since_last_gen_update = std::chrono::duration_cast( time_now - _last_gen_update_time); - if (!_generation_tracker->get_is_paused() && - time_since_last_gen_update.count() > _gen_update_speed_millis) + if (time_since_last_gen_update.count() > GENERATION_UPDATE_SPEED_MILLIS) { const auto new_current_gen = _generation_tracker->get_current_generation() + 1U; @@ -147,6 +187,36 @@ void Game::on_update() noexcept _last_gen_update_time = time_now; } + auto matrix = _scene->get_matrix(); + + const auto dying_cell_positions = container_filter( + _living_cell_positions, + [this](const Vector2 &cell_pos) + { + return _cell_helper->is_cell_dying(cell_pos); + }); + + auto birth_cell_positions = + _cell_helper->get_birth_cell_positions(_living_cell_positions); + + for (const auto &dying_cell_pos : dying_cell_positions) + { + _set_space(matrix, dying_cell_pos, ' '); + + const auto cell_found = container_find(_living_cell_positions, dying_cell_pos); + + if (cell_found != _living_cell_positions.end()) + { + _living_cell_positions.erase(cell_found); + } + } + + for (const auto &birth_cell_pos : birth_cell_positions) + { + _set_space(matrix, birth_cell_pos, 'x'); + _living_cell_positions.push_back(birth_cell_pos); + } + _last_update_time = time_now; } @@ -167,28 +237,33 @@ void Game::on_exit() const noexcept void Game::_move_cursor(const Vector2 &direction) noexcept { - const auto new_position = _cursor_controller->where().to_direction(direction, 1); + const auto current_position = _cursor_controller->where(); + + const auto dest_position = current_position + direction; const auto scene_size = _scene->size(); - if (scene_size.validate_coords(new_position) != CoordsValidation::VALID) + if (scene_size.validate_coords(dest_position) != CoordsValidation::VALID) { return; } - _cursor_controller->move_to(new_position); + _cursor_controller->move_to(dest_position); } -void Game::_insert_cell(const Vector2 &position, char cell) noexcept +void Game::_set_space( + const std::shared_ptr> &matrix, + const Vector2 &position, + char character) noexcept { - std::cout.put(cell); - std::cout.flush(); + const auto prev_position = _cursor_controller->where(); _cursor_controller->move_to(position); - auto matrix = _scene->get_matrix(); + std::cout.put(character); + std::cout.flush(); - const auto pos_offset = Vector2({.x = 0U, .y = 1U}); + matrix->set(position, character); - matrix->set(position - pos_offset, cell); + _cursor_controller->move_to(prev_position); } diff --git a/src/game/game.hpp b/src/game/game.hpp index 0ffa1d6..d29f4b6 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -1,15 +1,23 @@ #pragma once +#include "interfaces/cell_helper.hpp" #include "interfaces/cursor.hpp" #include "interfaces/game.hpp" #include "interfaces/generation_tracker.hpp" #include "interfaces/input.hpp" +#include "interfaces/matrix.hpp" #include "interfaces/scene.hpp" #include "interfaces/status_manager.hpp" +#include "engine/data/vector2.hpp" + #include +#include +#include #include +constexpr auto GENERATION_UPDATE_SPEED_MILLIS = 1000; + class Game : public IGame { public: @@ -18,7 +26,8 @@ public: std::shared_ptr cursor_controller, std::shared_ptr generation_tracker, std::shared_ptr status_manager, - std::shared_ptr user_input_observer) noexcept; + std::shared_ptr user_input_observer, + std::shared_ptr cell_helper) noexcept; void on_start() noexcept override; @@ -32,15 +41,19 @@ private: std::shared_ptr _generation_tracker; std::shared_ptr _status_manager; std::shared_ptr _user_input_observer; + std::shared_ptr _cell_helper; using TimePoint = std::chrono::system_clock::time_point; TimePoint _last_update_time; TimePoint _last_gen_update_time; - int _gen_update_speed_millis; + std::list _living_cell_positions; void _move_cursor(const Vector2 &direction) noexcept; - void _insert_cell(const Vector2 &position, char cell) noexcept; + void _set_space( + const std::shared_ptr> &matrix, + const Vector2 &position, + char character) noexcept; }; diff --git a/src/game/status_manager.cpp b/src/game/status_manager.cpp index 33174d1..4c189b9 100644 --- a/src/game/status_manager.cpp +++ b/src/game/status_manager.cpp @@ -18,6 +18,8 @@ void StatusManager::initialize() noexcept _statusline->set_section_length(StatusLineSection::D, 20U); _statusline->set_section_length(StatusLineSection::E, 25U); _statusline->set_section_length(StatusLineSection::F, 50U); + _statusline->set_section_length(StatusLineSection::G, 30U); + _statusline->set_section_length(StatusLineSection::H, 30U); _statusline->initialize_background(); } -- cgit v1.2.3-18-g5258