aboutsummaryrefslogtreecommitdiff
path: root/src/game
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 /src/game
parent87f55120f96d0f4f80b497dc9006d89df2dda125 (diff)
feat: implement game of lifev0.1.0
Diffstat (limited to 'src/game')
-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
5 files changed, 247 insertions, 17 deletions
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();
}