aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-06-02 19:51:54 +0200
committerHampusM <hampus@hampusmat.com>2022-06-13 17:57:00 +0200
commiteecf4b1e666211a13afa56f93477c55e8fd01621 (patch)
tree410510d6e058995174d5a5b0f535fb457a0c3542
parent87f55120f96d0f4f80b497dc9006d89df2dda125 (diff)
feat: implement game of lifev0.1.0
-rw-r--r--CMakeLists.txt4
-rw-r--r--lib/CMakeLists.txt10
-rw-r--r--lib/doctest/CMakeLists.txt4
-rw-r--r--lib/gtest/CMakeLists.txt4
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/bootstrap.cpp15
-rw-r--r--src/engine/data/vector2.cpp7
-rw-r--r--src/engine/data/vector2.hpp2
-rw-r--r--src/engine/graphics/scene.cpp2
-rw-r--r--src/engine/user/input.hpp6
-rw-r--r--src/game/cell_helper.hpp34
-rw-r--r--src/game/cell_helper.tpp106
-rw-r--r--src/game/game.cpp103
-rw-r--r--src/game/game.hpp19
-rw-r--r--src/game/status_manager.cpp2
-rw-r--r--src/interfaces/cell_helper.hpp37
-rw-r--r--src/interfaces/game.hpp2
-rw-r--r--src/interfaces/input.hpp4
-rw-r--r--src/interfaces/matrix.hpp11
-rw-r--r--src/interfaces/statusline.hpp2
-rw-r--r--src/util/algorithm.hpp31
-rw-r--r--src/util/algorithm.tpp57
-rw-r--r--src/util/concepts.hpp78
-rw-r--r--test/CMakeLists.txt41
-rw-r--r--test/cell_helper_test.cpp99
-rw-r--r--test/function.test.cpp52
-rw-r--r--test/main.cpp2
-rw-r--r--test/mocks/matrix.hpp31
-rw-r--r--test/string_matrix.test.cpp53
29 files changed, 651 insertions, 175 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5afa219..b617393 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,13 +4,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS off)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-
project(game-of-life CXX)
add_subdirectory(lib)
add_subdirectory(src)
-if("${TESTING}")
+if(test)
add_subdirectory(test)
endif()
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 7fdf83c..df9b253 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -32,12 +32,12 @@ FetchContent_Declare(
add_subdirectory(yacppdic)
-if("${TESTING}")
+if(test)
FetchContent_Declare(
- doctest
- GIT_REPOSITORY "https://github.com/doctest/doctest"
- GIT_TAG v2.4.8
+ gtest
+ GIT_REPOSITORY "https://github.com/google/googletest"
+ GIT_TAG v1.11.0
)
- add_subdirectory(doctest)
+ add_subdirectory(gtest)
endif()
diff --git a/lib/doctest/CMakeLists.txt b/lib/doctest/CMakeLists.txt
deleted file mode 100644
index 0ca5267..0000000
--- a/lib/doctest/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-message(STATUS "Fetching doctest...")
-
-FetchContent_MakeAvailable(doctest)
-
diff --git a/lib/gtest/CMakeLists.txt b/lib/gtest/CMakeLists.txt
new file mode 100644
index 0000000..f28b693
--- /dev/null
+++ b/lib/gtest/CMakeLists.txt
@@ -0,0 +1,4 @@
+message(STATUS "Fetching gtest...")
+
+FetchContent_MakeAvailable(gtest)
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a566c12..0c576e3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -30,6 +30,12 @@ file(GLOB SOURCES
add_executable(${PROJECT_NAME} ${SOURCES})
+set_target_properties(
+ ${PROJECT_NAME}
+ PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
target_compile_options(
@@ -44,7 +50,7 @@ target_compile_options(
-Wsign-conversion -Wsign-promo
-Wstrict-overflow=5 -Wswitch-default
-Wundef -Werror
- -pedantic -fsanitize=address -fno-exceptions
+ -pedantic -fsanitize=address -fno-exceptions -fno-omit-frame-pointer
)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp
index 02ec92d..adb53a2 100644
--- a/src/bootstrap.cpp
+++ b/src/bootstrap.cpp
@@ -2,6 +2,7 @@
// Interfaces
#include "interfaces/argument_parser.hpp"
+#include "interfaces/cell_helper.hpp"
#include "interfaces/cursor.hpp"
#include "interfaces/engine.hpp"
#include "interfaces/game.hpp"
@@ -23,6 +24,7 @@
#include "engine/graphics/statusline.hpp"
#include "engine/user/cursor.hpp"
#include "engine/user/input.hpp"
+#include "game/cell_helper.hpp"
#include "game/game.hpp"
#include "game/generation_tracker.hpp"
#include "game/status_manager.hpp"
@@ -51,7 +53,7 @@ auto bootstrap() noexcept -> yacppdic::Container
[&container](
const std::shared_ptr<IScene> &scene,
const std::shared_ptr<ICursorController> &cursor_controller,
- const std::shared_ptr<IUserInputObserver> user_input_observer)
+ const std::shared_ptr<IUserInputObserver> &user_input_observer)
{
std::shared_ptr<IStatusLine> statusline =
container.get<IStatusLineFactory>()(cursor_controller, scene);
@@ -62,12 +64,15 @@ auto bootstrap() noexcept -> yacppdic::Container
std::shared_ptr<IGenerationTracker> generation_tracker =
container.get<IGenerationTrackerFactory>()(true);
+ const auto cell_helper_factory = container.get<ICellHelperFactory<char>>();
+
return std::make_unique<Game>(
scene,
cursor_controller,
generation_tracker,
status_manager,
- user_input_observer);
+ user_input_observer,
+ cell_helper_factory(*(scene->get_matrix())));
});
container.bind<IRandomNumberGeneratorFactory>().to_factory(
@@ -109,5 +114,11 @@ auto bootstrap() noexcept -> yacppdic::Container
return std::make_unique<GenerationTracker>(is_paused);
});
+ container.bind<ICellHelperFactory<char>>().to_factory(
+ [](const IMatrix<char> &matrix)
+ {
+ return std::make_unique<CellHelper<char>>(matrix);
+ });
+
return container;
}
diff --git a/src/engine/data/vector2.cpp b/src/engine/data/vector2.cpp
index 0f203d0..cb81947 100644
--- a/src/engine/data/vector2.cpp
+++ b/src/engine/data/vector2.cpp
@@ -2,6 +2,8 @@
#include "util/hash.hpp"
+#include <tuple>
+
auto Vector2::get_x() const noexcept -> Vector2::Value
{
return _x;
@@ -79,6 +81,11 @@ auto Vector2::operator==(const Vector2 &rhs) const noexcept -> bool
return _x == rhs._x && _y == rhs._y;
}
+auto Vector2::operator<(const Vector2 &rhs) const noexcept -> bool
+{
+ return std::tie(_x, _y) < std::tie(rhs._x, rhs._y);
+}
+
auto Vector2Hasher::operator()(const Vector2 &vector2) const noexcept -> std::size_t
{
std::size_t result_hash = 0;
diff --git a/src/engine/data/vector2.hpp b/src/engine/data/vector2.hpp
index 620b41c..96ebf87 100644
--- a/src/engine/data/vector2.hpp
+++ b/src/engine/data/vector2.hpp
@@ -43,6 +43,8 @@ public:
auto operator==(const Vector2 &rhs) const noexcept -> bool;
+ auto operator<(const Vector2 &rhs) const noexcept -> bool;
+
/**
* Returns Vector2({.x = 0, .y = 1})
*/
diff --git a/src/engine/graphics/scene.cpp b/src/engine/graphics/scene.cpp
index 7a0e960..9f561f0 100644
--- a/src/engine/graphics/scene.cpp
+++ b/src/engine/graphics/scene.cpp
@@ -13,7 +13,7 @@
Scene::Scene(
IMatrixFactory<MatrixElement> matrix_factory,
std::shared_ptr<ICursorController> cursor_controller) noexcept
- : _matrix(matrix_factory(size() - Bounds({.width = 0U, .height = 1U}))),
+ : _matrix(matrix_factory(size())),
_cursor_controller(std::move(cursor_controller)),
_is_shown(false)
{
diff --git a/src/engine/user/input.hpp b/src/engine/user/input.hpp
index 29534e8..9f565c1 100644
--- a/src/engine/user/input.hpp
+++ b/src/engine/user/input.hpp
@@ -16,13 +16,13 @@ public:
void listen() noexcept override;
- bool is_key_pressed(Key key) noexcept override;
+ auto is_key_pressed(Key key) noexcept -> bool override;
- Key get_currently_pressed_key() const noexcept override;
+ [[nodiscard]] auto get_currently_pressed_key() const noexcept -> Key override;
void clear_currently_pressed() noexcept override;
private:
- Key _currently_pressed;
+ Key _currently_pressed{};
std::mutex _currently_pressed_mutex;
};
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 <list>
+#include <memory>
+
+template <typename MatrixElement>
+class CellHelper : public ICellHelper
+{
+public:
+ explicit CellHelper(const IMatrix<MatrixElement> &matrix) noexcept;
+
+ [[nodiscard]] auto is_cell_dying(const Vector2 &cell_pos) const noexcept
+ -> bool override;
+
+ [[nodiscard]] auto
+ get_birth_cell_positions(const std::list<Vector2> &cell_positions) const noexcept
+ -> std::list<Vector2> override;
+
+ [[nodiscard]] auto find_neighbours(const Vector2 &cell_pos) const noexcept
+ -> std::list<Vector2> override;
+
+private:
+ const IMatrix<MatrixElement> &_matrix;
+
+ static auto _get_position_neighbours(const Vector2 &position) noexcept
+ -> std::list<Vector2>;
+};
+
+#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 <typename MatrixElement>
+constexpr auto has_matrix_value(
+ const IMatrix<MatrixElement> &matrix,
+ const MatrixElement &value) noexcept
+{
+ return [&matrix, &value](const Vector2 &pos)
+ {
+ return matrix.get(pos) == value;
+ };
+}
+
+template <typename MatrixElement>
+CellHelper<MatrixElement>::CellHelper(const IMatrix<MatrixElement> &matrix) noexcept
+ : _matrix(matrix)
+{
+}
+
+template <typename MatrixElement>
+auto CellHelper<MatrixElement>::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 <typename MatrixElement>
+auto CellHelper<MatrixElement>::get_birth_cell_positions(
+ const std::list<Vector2> &cell_positions) const noexcept -> std::list<Vector2>
+{
+ auto all_empty_neighbour_positions = std::list<Vector2>();
+
+ for (const auto &cell_pos : cell_positions)
+ {
+ const std::list<Vector2> 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 <typename MatrixElement>
+auto CellHelper<MatrixElement>::find_neighbours(const Vector2 &cell_pos) const noexcept
+ -> std::list<Vector2>
+{
+ std::list<Vector2> 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 <typename MatrixElement>
+auto CellHelper<MatrixElement>::_get_position_neighbours(
+ const Vector2 &position) noexcept -> std::list<Vector2>
+{
+ 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 <fmt/core.h>
+#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <utility>
@@ -11,13 +14,14 @@ Game::Game(
std::shared_ptr<ICursorController> cursor_controller,
std::shared_ptr<IGenerationTracker> generation_tracker,
std::shared_ptr<IStatusManager> status_manager,
- std::shared_ptr<IUserInputObserver> user_input_observer) noexcept
+ std::shared_ptr<IUserInputObserver> user_input_observer,
+ std::shared_ptr<ICellHelper> 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<std::chrono::milliseconds>(
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<IMatrix<IScene::MatrixElement>> &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 <chrono>
+#include <cstddef>
+#include <list>
#include <memory>
+constexpr auto GENERATION_UPDATE_SPEED_MILLIS = 1000;
+
class Game : public IGame
{
public:
@@ -18,7 +26,8 @@ public:
std::shared_ptr<ICursorController> cursor_controller,
std::shared_ptr<IGenerationTracker> generation_tracker,
std::shared_ptr<IStatusManager> status_manager,
- std::shared_ptr<IUserInputObserver> user_input_observer) noexcept;
+ std::shared_ptr<IUserInputObserver> user_input_observer,
+ std::shared_ptr<ICellHelper> cell_helper) noexcept;
void on_start() noexcept override;
@@ -32,15 +41,19 @@ private:
std::shared_ptr<IGenerationTracker> _generation_tracker;
std::shared_ptr<IStatusManager> _status_manager;
std::shared_ptr<IUserInputObserver> _user_input_observer;
+ std::shared_ptr<ICellHelper> _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<Vector2> _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<IMatrix<IScene::MatrixElement>> &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();
}
diff --git a/src/interfaces/cell_helper.hpp b/src/interfaces/cell_helper.hpp
new file mode 100644
index 0000000..141d905
--- /dev/null
+++ b/src/interfaces/cell_helper.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "interfaces/matrix.hpp"
+
+#include "engine/data/vector2.hpp"
+
+#include <yacppdic/factory.hpp>
+
+#include <list>
+#include <memory>
+
+// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
+class ICellHelper
+{
+public:
+ virtual ~ICellHelper() noexcept = default;
+
+ [[nodiscard]] virtual auto is_cell_dying(const Vector2 &cell_pos) const noexcept
+ -> bool = 0;
+
+ [[nodiscard]] virtual auto
+ get_birth_cell_positions(const std::list<Vector2> &cell_positions) const noexcept
+ -> std::list<Vector2> = 0;
+
+ /*
+ [[nodiscard]] virtual auto
+ find_neighbour_cells(const Vector2 &cell_pos) const noexcept
+ -> std::list<Vector2> = 0;
+ */
+
+ [[nodiscard]] virtual auto find_neighbours(const Vector2 &cell_pos) const noexcept
+ -> std::list<Vector2> = 0;
+};
+
+template <typename MatrixElement>
+using ICellHelperFactory =
+ yacppdic::Factory<std::unique_ptr<ICellHelper>(const IMatrix<MatrixElement> &matrix)>;
diff --git a/src/interfaces/game.hpp b/src/interfaces/game.hpp
index e205fae..b6d7f36 100644
--- a/src/interfaces/game.hpp
+++ b/src/interfaces/game.hpp
@@ -24,4 +24,4 @@ public:
using IGameFactory = yacppdic::Factory<std::unique_ptr<IGame>(
const std::shared_ptr<IScene> &scene,
const std::shared_ptr<ICursorController> &cursor_controller,
- const std::shared_ptr<IUserInputObserver> user_input_observer)>;
+ const std::shared_ptr<IUserInputObserver> &user_input_observer)>;
diff --git a/src/interfaces/input.hpp b/src/interfaces/input.hpp
index c2ecefb..3558363 100644
--- a/src/interfaces/input.hpp
+++ b/src/interfaces/input.hpp
@@ -13,9 +13,9 @@ public:
virtual void listen() noexcept = 0;
- virtual bool is_key_pressed(Key key) noexcept = 0;
+ virtual auto is_key_pressed(Key key) noexcept -> bool = 0;
- virtual Key get_currently_pressed_key() const noexcept = 0;
+ [[nodiscard]] virtual auto get_currently_pressed_key() const noexcept -> Key = 0;
virtual void clear_currently_pressed() noexcept = 0;
};
diff --git a/src/interfaces/matrix.hpp b/src/interfaces/matrix.hpp
index 5a01943..010138f 100644
--- a/src/interfaces/matrix.hpp
+++ b/src/interfaces/matrix.hpp
@@ -9,15 +9,10 @@
#include <memory>
template <typename Element>
+// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
class IMatrix
{
public:
- IMatrix() noexcept = default;
-
- IMatrix(const IMatrix &matrix) noexcept = default;
-
- IMatrix(IMatrix &&matrix) noexcept = default;
-
virtual ~IMatrix() noexcept = default;
virtual void fill(Element element) noexcept = 0;
@@ -33,10 +28,6 @@ public:
[[nodiscard]] virtual auto begin() const noexcept -> MatrixIterator<Element> = 0;
[[nodiscard]] virtual auto end() const noexcept -> MatrixIterator<Element> = 0;
-
- auto operator=(const IMatrix &matrix) noexcept -> IMatrix & = default;
-
- auto operator=(IMatrix &&matrix) noexcept -> IMatrix & = default;
};
template <typename Element>
diff --git a/src/interfaces/statusline.hpp b/src/interfaces/statusline.hpp
index 2fefa6d..a71699e 100644
--- a/src/interfaces/statusline.hpp
+++ b/src/interfaces/statusline.hpp
@@ -15,6 +15,8 @@ enum StatusLineSection
D = 3,
E = 4,
F = 5,
+ G = 6,
+ H = 7
};
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
diff --git a/src/util/algorithm.hpp b/src/util/algorithm.hpp
new file mode 100644
index 0000000..71a1724
--- /dev/null
+++ b/src/util/algorithm.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "util/concepts.hpp"
+
+#include <concepts>
+
+template <typename ContainerType, typename Value>
+requires Container<ContainerType>
+constexpr auto container_find(const ContainerType &container, const Value &value) noexcept
+ -> typename ContainerType::const_iterator;
+
+template <typename ContainerType, typename Value>
+requires Container<ContainerType>
+constexpr auto container_has(const ContainerType &container, const Value &value) noexcept
+ -> bool;
+
+template <typename ContainerType, typename Predicate>
+requires Container<ContainerType> && HasPushBack<ContainerType> &&
+ std::predicate<Predicate, typename ContainerType::value_type>
+constexpr auto
+container_filter(const ContainerType &container, Predicate predicate) noexcept
+ -> ContainerType;
+
+template <typename ContainerType, typename Predicate>
+requires Container<ContainerType> &&
+ std::predicate<Predicate, typename ContainerType::value_type>
+constexpr auto
+container_filter(const ContainerType &container, Predicate predicate) noexcept
+ -> ContainerType;
+
+#include "algorithm.tpp"
diff --git a/src/util/algorithm.tpp b/src/util/algorithm.tpp
new file mode 100644
index 0000000..00269ed
--- /dev/null
+++ b/src/util/algorithm.tpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "algorithm.hpp"
+
+#include <algorithm>
+
+template <typename ContainerType, typename Value>
+requires Container<ContainerType>
+constexpr auto container_find(const ContainerType &container, const Value &value) noexcept
+ -> typename ContainerType::const_iterator
+{
+ return std::find(container.begin(), container.end(), value);
+}
+
+template <typename ContainerType, typename Value>
+requires Container<ContainerType>
+constexpr auto container_has(const ContainerType &container, const Value &value) noexcept
+ -> bool
+{
+ return container_find(container, value) != container.end();
+}
+
+template <typename ContainerType, typename Predicate>
+requires Container<ContainerType> && HasPushBack<ContainerType> &&
+ std::predicate<Predicate, typename ContainerType::value_type>
+constexpr auto
+container_filter(const ContainerType &container, Predicate predicate) noexcept
+ -> ContainerType
+{
+ ContainerType filtered_container;
+
+ std::copy_if(
+ std::begin(container),
+ std::end(container),
+ std::back_inserter(filtered_container),
+ predicate);
+
+ return filtered_container;
+}
+
+template <typename ContainerType, typename Predicate>
+requires Container<ContainerType> &&
+ std::predicate<Predicate, typename ContainerType::value_type>
+constexpr auto
+container_filter(const ContainerType &container, Predicate predicate) noexcept
+ -> ContainerType
+{
+ ContainerType filtered_container;
+
+ std::copy_if(
+ std::begin(container),
+ std::end(container),
+ std::inserter(filtered_container, filtered_container.begin()),
+ predicate);
+
+ return filtered_container;
+}
diff --git a/src/util/concepts.hpp b/src/util/concepts.hpp
new file mode 100644
index 0000000..928ee39
--- /dev/null
+++ b/src/util/concepts.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <concepts>
+#include <iterator>
+
+/**
+ * Concept for the Container named requirement.
+ *
+ * https://en.cppreference.com/w/cpp/named_req/Container
+ */
+template <class ContainerType>
+concept Container = requires(ContainerType container_a, const ContainerType container_b)
+{
+ typename ContainerType::value_type;
+ typename ContainerType::reference;
+ typename ContainerType::const_reference;
+ typename ContainerType::iterator;
+ typename ContainerType::const_iterator;
+ typename ContainerType::difference_type;
+ typename ContainerType::size_type;
+
+ requires std::regular<ContainerType>;
+ requires std::swappable<ContainerType>;
+ requires std::destructible<typename ContainerType::value_type>;
+ requires std::
+ same_as<typename ContainerType::reference, typename ContainerType::value_type &>;
+ requires std::same_as<
+ typename ContainerType::const_reference,
+ const typename ContainerType::value_type &>;
+ requires std::forward_iterator<typename ContainerType::iterator>;
+ requires std::forward_iterator<typename ContainerType::const_iterator>;
+ requires std::signed_integral<typename ContainerType::difference_type>;
+ requires std::same_as<
+ typename ContainerType::difference_type,
+ typename std::iterator_traits<typename ContainerType::iterator>::difference_type>;
+ requires std::same_as<
+ typename ContainerType::difference_type,
+ typename std::iterator_traits<
+ typename ContainerType::const_iterator>::difference_type>;
+ requires std::convertible_to<
+ typename ContainerType::iterator,
+ typename ContainerType::const_iterator>;
+
+ {
+ container_a.begin()
+ } -> std::same_as<typename ContainerType::iterator>;
+ {
+ container_a.end()
+ } -> std::same_as<typename ContainerType::iterator>;
+ {
+ container_b.begin()
+ } -> std::same_as<typename ContainerType::const_iterator>;
+ {
+ container_b.end()
+ } -> std::same_as<typename ContainerType::const_iterator>;
+ {
+ container_a.cbegin()
+ } -> std::same_as<typename ContainerType::const_iterator>;
+ {
+ container_a.cend()
+ } -> std::same_as<typename ContainerType::const_iterator>;
+ {
+ container_a.size()
+ } -> std::same_as<typename ContainerType::size_type>;
+ {
+ container_a.max_size()
+ } -> std::same_as<typename ContainerType::size_type>;
+ {
+ container_a.empty()
+ } -> std::same_as<bool>;
+};
+
+template <typename ContainerType>
+concept HasPushBack =
+ requires(ContainerType container, typename ContainerType::value_type value)
+{
+ container.push_back(value);
+};
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 243c1dd..0c74bf2 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,18 +1,20 @@
+project(tests CXX)
+
+enable_testing()
+
file(GLOB SOURCES
- main.cpp
- string_matrix.test.cpp
- "function.test.cpp"
+ cell_helper_test.cpp
+ ${CMAKE_SOURCE_DIR}/src/game/cell_helper.cpp
${CMAKE_SOURCE_DIR}/src/engine/data/vector2.cpp
${CMAKE_SOURCE_DIR}/src/engine/data/bounds.cpp
- ${CMAKE_SOURCE_DIR}/src/engine/graphics/string_matrix.cpp
)
-add_executable(tests ${SOURCES})
+add_executable(${PROJECT_NAME} ${SOURCES})
-target_compile_features(tests PUBLIC cxx_std_20)
+target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
target_compile_options(
- tests
+ ${PROJECT_NAME}
PRIVATE
-Wall -Wextra -Wpedantic -Wshadow
-Wold-style-cast -Wcast-align -Wno-unused
@@ -26,21 +28,22 @@ target_compile_options(
-pedantic -fsanitize=address -fno-exceptions
)
-if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
- target_compile_options(
- tests
- PRIVATE
- -Wlogical-op -Wnoexcept -Wstrict-null-sentinel
- )
-endif()
-
target_include_directories(
- tests
+ ${PROJECT_NAME}
PRIVATE
"${CMAKE_SOURCE_DIR}/src"
- "${CMAKE_SOURCE_DIR}/test"
)
-target_link_libraries(tests GSL doctest)
+target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address)
+
+target_link_libraries(
+ ${PROJECT_NAME}
+ GSL
+ yacppdic
+ gtest_main
+ gmock
+)
+
+include(GoogleTest)
-target_link_options(tests PRIVATE -fsanitize=address)
+gtest_discover_tests(${PROJECT_NAME})
diff --git a/test/cell_helper_test.cpp b/test/cell_helper_test.cpp
new file mode 100644
index 0000000..b570aba
--- /dev/null
+++ b/test/cell_helper_test.cpp
@@ -0,0 +1,99 @@
+#include "engine/data/vector2.hpp"
+#include "game/cell_helper.hpp"
+
+#include "mocks/matrix.hpp"
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <memory>
+
+class CellHelperTest : public testing::Test
+{
+protected:
+};
+
+TEST_F(CellHelperTest, IsCellDying)
+{
+ const auto mock_matrix = MockMatrix<char>();
+
+ const auto rows = 8U;
+ const auto columns = 6U;
+
+ const auto position = Vector2({.x = 3, .y = 5});
+
+ EXPECT_CALL(mock_matrix, get_row_cnt()).WillRepeatedly(testing::Return(rows));
+
+ EXPECT_CALL(mock_matrix, get_column_cnt()).WillRepeatedly(testing::Return(columns));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::up()))
+ .WillOnce(testing::Return('x'))
+ .WillOnce(testing::Return(' '))
+ .WillOnce(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::down()))
+ .WillRepeatedly(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::left()))
+ .WillOnce(testing::Return('x'))
+ .WillOnce(testing::Return('x'))
+ .WillOnce(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::right()))
+ .WillOnce(testing::Return('x'))
+ .WillOnce(testing::Return(' '))
+ .WillOnce(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::up() + Vector2::left()))
+ .WillRepeatedly(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::up() + Vector2::right()))
+ .WillRepeatedly(testing::Return('x'));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::down() + Vector2::left()))
+ .WillRepeatedly(testing::Return(' '));
+
+ EXPECT_CALL(mock_matrix, get(position + Vector2::down() + Vector2::right()))
+ .WillRepeatedly(testing::Return(' '));
+
+ auto cell_helper = CellHelper<char>(mock_matrix);
+
+ EXPECT_EQ(cell_helper.is_cell_dying(position), true);
+
+ EXPECT_EQ(cell_helper.is_cell_dying(position), false);
+
+ EXPECT_EQ(cell_helper.is_cell_dying(position), true);
+}
+
+TEST_F(CellHelperTest, FindNeighbours)
+{
+ const auto mock_matrix = MockMatrix<char>();
+
+ const auto rows = 8U;
+ const auto columns = 6U;
+
+ const auto position = Vector2({.x = 3, .y = 5});
+
+ EXPECT_CALL(mock_matrix, get_row_cnt()).WillOnce(testing::Return(rows));
+
+ EXPECT_CALL(mock_matrix, get_column_cnt()).WillOnce(testing::Return(columns));
+
+ auto cell_helper = CellHelper<char>(mock_matrix);
+
+ const auto living_neighbour_cells = cell_helper.find_neighbours(position);
+
+ EXPECT_EQ(living_neighbour_cells.size(), 8U);
+
+ EXPECT_THAT(
+ living_neighbour_cells,
+ testing::ContainerEq(std::list<Vector2>(
+ {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/test/function.test.cpp b/test/function.test.cpp
deleted file mode 100644
index 9aca0eb..0000000
--- a/test/function.test.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include "util/function.hpp"
-
-#include <cstdint>
-#include <doctest/doctest.h>
-
-TEST_CASE("normalize_lamda")
-{
- SUBCASE("Can return a function that returns a int")
- {
- const int number = 58;
-
- CHECK(normalize_lambda(
- [number]()
- {
- return number;
- })() == number);
- }
-
- SUBCASE("Can preserve object state")
- {
- class Book
- {
- public:
- Book() noexcept = default;
-
- void read_page() noexcept
- {
- _pages_read++;
- }
-
- [[nodiscard]] uint32_t pages_read() const noexcept
- {
- return _pages_read;
- }
-
- private:
- uint32_t _pages_read{0U};
- };
-
- auto book = Book();
-
- book.read_page();
- book.read_page();
- book.read_page();
-
- CHECK(normalize_lambda(
- [book]()
- {
- return book.pages_read();
- })() == 3);
- }
-}
diff --git a/test/main.cpp b/test/main.cpp
deleted file mode 100644
index 0a3f254..0000000
--- a/test/main.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
-#include <doctest/doctest.h>
diff --git a/test/mocks/matrix.hpp b/test/mocks/matrix.hpp
new file mode 100644
index 0000000..1602ebf
--- /dev/null
+++ b/test/mocks/matrix.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "interfaces/matrix.hpp"
+
+#include <gmock/gmock.h>
+
+template <typename Element>
+class MockMatrix : public IMatrix<Element>
+{
+public:
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(void, fill, (Element element), (noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(Element, get, (const Vector2 &pos), (const, noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(void, set, (const Vector2 &pos, Element element), (noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(uint32_t, get_row_cnt, (), (const, noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(uint32_t, get_column_cnt, (), (const, noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(MatrixIterator<Element>, begin, (), (const, noexcept, override));
+
+ // NOLINTNEXTLINE(modernize-use-trailing-return-type)
+ MOCK_METHOD(MatrixIterator<Element>, end, (), (const, noexcept));
+};
diff --git a/test/string_matrix.test.cpp b/test/string_matrix.test.cpp
deleted file mode 100644
index 522a274..0000000
--- a/test/string_matrix.test.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "engine/graphics/string_matrix.hpp"
-#include "engine/data/bounds.hpp"
-
-#include <doctest/doctest.h>
-#include <type_traits>
-
-constexpr uint32_t MATRIX_WIDTH = 76;
-constexpr uint32_t MATRIX_HEIGHT = 31;
-
-TEST_CASE("String matrix")
-{
- auto string_matrix =
- StringMatrix(Bounds({.width = MATRIX_WIDTH, .height = MATRIX_HEIGHT}));
-
- SUBCASE("Can set & get elements")
- {
- // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
- const auto position = Vector2({.x = 56, .y = 20});
-
- string_matrix.set(position, "#");
-
- CHECK(string_matrix.get(position) == "#");
- }
-
- SUBCASE("Can iterate")
- {
- CHECK(std::is_same_v<decltype(string_matrix.begin()),
- MatrixIterator<std::string_view>>);
-
- CHECK(std::is_same_v<decltype(string_matrix.end()),
- MatrixIterator<std::string_view>>);
-
- uint32_t row_iter_cnt = 0;
-
- for (auto row : string_matrix)
- {
- row_iter_cnt++;
-
- CHECK(std::is_same_v<decltype(row), MatrixRow<std::string_view>>);
-
- uint32_t col_iter_cnt = 0;
-
- for (auto &col : row)
- {
- col_iter_cnt++;
- }
-
- CHECK(col_iter_cnt == MATRIX_WIDTH);
- }
-
- CHECK(row_iter_cnt == MATRIX_HEIGHT);
- }
-}