aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2022-03-23 19:41:31 +0100
committerHampusM <hampus@hampusmat.com>2022-06-13 17:56:57 +0200
commit486ca3846b46dc229e5807968578809766ec1991 (patch)
tree65a4b7a746d6305666af06f8a1975c76244085a7
parentb8e86ce397dc07320c02f6a5f592c7c6a4421c86 (diff)
feat: implement generations & multithreading
-rw-r--r--README.md1
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/bootstrap.cpp45
-rw-r--r--src/commands/toggle_pause.cpp22
-rw-r--r--src/commands/toggle_pause.hpp20
-rw-r--r--src/engine/engine.cpp12
-rw-r--r--src/engine/graphics/scene.cpp20
-rw-r--r--src/engine/graphics/scene.hpp6
-rw-r--r--src/engine/user/input.hpp4
-rw-r--r--src/game/cursor_listener.cpp14
-rw-r--r--src/game/cursor_listener.hpp19
-rw-r--r--src/game/game.cpp26
-rw-r--r--src/game/game.hpp21
-rw-r--r--src/game/generation_tracker.cpp18
-rw-r--r--src/game/generation_tracker.hpp22
-rw-r--r--src/game/status_updater.cpp25
-rw-r--r--src/game/status_updater.hpp24
-rw-r--r--src/game/statusline.cpp102
-rw-r--r--src/game/statusline.hpp41
-rw-r--r--src/interfaces/game.hpp6
-rw-r--r--src/interfaces/generation_tracker.hpp18
-rw-r--r--src/interfaces/scene.hpp2
-rw-r--r--src/interfaces/status_updater.hpp19
-rw-r--r--src/interfaces/statusline.hpp27
24 files changed, 437 insertions, 82 deletions
diff --git a/README.md b/README.md
index 6dc2bea..083f4f4 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,6 @@ This is a c++ Linux CLI implementation of John Conway's game of life.
# Todo
- Implement the actual game
-- Multithreading
- Dynamic terminal window size. Redraw everything when the window size changes
- Cells can be eaten by other cells twice as large
- Creatures with different abilities. Speed for example
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c603b37..4aa4a40 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -15,7 +15,9 @@ file(GLOB SOURCES
argument_parser.cpp
util/color.cpp
game/game.cpp
- game/cursor_listener.cpp
+ game/status_updater.cpp
+ game/generation_tracker.cpp
+ game/statusline.cpp
engine/engine.cpp
engine/data/vector2.cpp
engine/data/bounds.cpp
@@ -29,6 +31,7 @@ file(GLOB SOURCES
commands/insert_cell.cpp
commands/move_cursor.cpp
commands/quit.cpp
+ commands/toggle_pause.cpp
DI/object_type.cpp)
add_executable(${PROJECT_NAME} ${SOURCES})
diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp
index 713991d..8dec609 100644
--- a/src/bootstrap.cpp
+++ b/src/bootstrap.cpp
@@ -5,10 +5,13 @@
#include "interfaces/cursor.hpp"
#include "interfaces/engine.hpp"
#include "interfaces/game.hpp"
+#include "interfaces/generation_tracker.hpp"
#include "interfaces/input.hpp"
#include "interfaces/matrix.hpp"
#include "interfaces/randomization.hpp"
#include "interfaces/scene.hpp"
+#include "interfaces/status_updater.hpp"
+#include "interfaces/statusline.hpp"
#include "interfaces/window.hpp"
// Implementations
@@ -22,6 +25,9 @@
#include "engine/user/cursor.hpp"
#include "engine/user/input.hpp"
#include "game/game.hpp"
+#include "game/generation_tracker.hpp"
+#include "game/status_updater.hpp"
+#include "game/statusline.hpp"
#include "randomization/generator.hpp"
#include "randomization/seed_generator.hpp"
@@ -42,11 +48,21 @@ Container bootstrap() noexcept
container.bind<IWindow>().to<Window>();
container.bind<IGameFactory>().to_factory(normalize_lambda(
- [](const std::shared_ptr<IWindow> &window, const std::shared_ptr<IScene> &scene,
- const std::shared_ptr<ICursorController> &cursor_controller)
+ [&container](const std::shared_ptr<IWindow> &window,
+ const std::shared_ptr<IScene> &scene,
+ const std::shared_ptr<ICursorController> &cursor_controller)
{
+ auto statusline =
+ container.get<IStatusLineFactory>()(cursor_controller, window);
+
+ auto generation_tracker = container.get<IGenerationTrackerFactory>()(true);
+
+ auto status_updater =
+ container.get<IStatusUpdaterFactory>()(statusline, generation_tracker);
+
return std::dynamic_pointer_cast<IGame>(
- std::make_shared<Game>(window, scene, cursor_controller));
+ std::make_shared<Game>(window, scene, cursor_controller, statusline,
+ generation_tracker, status_updater));
}));
container.bind<IRandomNumberGeneratorFactory>().to_factory(
@@ -79,5 +95,28 @@ Container bootstrap() noexcept
cursor_controller, window));
}));
+ container.bind<IStatusLineFactory>().to_factory(
+ [](const std::shared_ptr<ICursorController> &cursor_controller,
+ const std::shared_ptr<IWindow> &window)
+ {
+ return std::dynamic_pointer_cast<IStatusLine>(
+ std::make_shared<StatusLine>(cursor_controller, window));
+ });
+
+ container.bind<IStatusUpdaterFactory>().to_factory(
+ [](const std::shared_ptr<IStatusLine> &status_line,
+ const std::shared_ptr<IGenerationTracker> &generation_tracker)
+ {
+ return std::dynamic_pointer_cast<IStatusUpdater>(
+ std::make_shared<StatusUpdater>(status_line, generation_tracker));
+ });
+
+ container.bind<IGenerationTrackerFactory>().to_factory(
+ [](bool is_paused)
+ {
+ return std::dynamic_pointer_cast<IGenerationTracker>(
+ std::make_shared<GenerationTracker>(is_paused));
+ });
+
return container;
}
diff --git a/src/commands/toggle_pause.cpp b/src/commands/toggle_pause.cpp
new file mode 100644
index 0000000..8a3f44c
--- /dev/null
+++ b/src/commands/toggle_pause.cpp
@@ -0,0 +1,22 @@
+#include "toggle_pause.hpp"
+
+#include <fmt/core.h>
+#include <utility>
+
+TogglePauseCommand::TogglePauseCommand(
+ std::shared_ptr<IGenerationTracker> generation_tracker,
+ std::shared_ptr<IStatusLine> statusline) noexcept
+ : _generation_tracker(std::move(generation_tracker)),
+ _statusline(std::move(statusline))
+{
+}
+
+void TogglePauseCommand::execute() noexcept
+{
+ auto onoff = !_generation_tracker->get_is_paused();
+
+ _generation_tracker->set_is_paused(onoff);
+
+ _statusline->set_status(StatusLineSection::B,
+ fmt::format("Paused: {}", onoff ? "yes" : "no"));
+}
diff --git a/src/commands/toggle_pause.hpp b/src/commands/toggle_pause.hpp
new file mode 100644
index 0000000..4e05323
--- /dev/null
+++ b/src/commands/toggle_pause.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "interfaces/command.hpp"
+#include "interfaces/generation_tracker.hpp"
+#include "interfaces/statusline.hpp"
+
+#include <memory>
+
+class TogglePauseCommand : public ICommand
+{
+public:
+ explicit TogglePauseCommand(std::shared_ptr<IGenerationTracker> generation_tracker,
+ std::shared_ptr<IStatusLine> statusline) noexcept;
+
+ void execute() noexcept override;
+
+private:
+ std::shared_ptr<IGenerationTracker> _generation_tracker;
+ std::shared_ptr<IStatusLine> _statusline;
+};
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
index e463f28..c988c33 100644
--- a/src/engine/engine.cpp
+++ b/src/engine/engine.cpp
@@ -2,6 +2,7 @@
#include "util/function.hpp"
+#include <thread>
#include <utility>
CLIGameEngine::CLIGameEngine(IGameFactory game_factory, ISceneFactory scene_factory,
@@ -38,7 +39,16 @@ void CLIGameEngine::start() noexcept
_configure_input(game->get_input_config());
- _input_handler->listen();
+ std::thread listen_input_thread(normalize_lambda(
+ [this]()
+ {
+ _input_handler->listen();
+ }));
+
+ while (true)
+ {
+ game->on_update();
+ }
}
void CLIGameEngine::_configure_input(
diff --git a/src/engine/graphics/scene.cpp b/src/engine/graphics/scene.cpp
index 4e382fe..24c174f 100644
--- a/src/engine/graphics/scene.cpp
+++ b/src/engine/graphics/scene.cpp
@@ -45,26 +45,6 @@ void Scene::leave() noexcept
_is_shown = false;
}
-void Scene::write_status(const std::string_view &str) noexcept
-{
- const auto previous_position = _cursor_controller->where();
-
- const auto window_size = _window->size();
-
- _cursor_controller->move_to(
- Vector2({.x = 2, .y = static_cast<Vector2::Value>(window_size.get_height())}),
- true);
-
- auto background_color = get_background_esc_seq(STATUSBAR_COLOR);
-
- fmt::print("{}", background_color);
- fmt::print(ERASE_ENTIRE_LINE, fmt::arg("esc", ESC));
- fmt::print(fmt::runtime(str.data()));
- fmt::print(RESET_ALL_MODES, fmt::arg("esc", ESC));
-
- _cursor_controller->move_to(previous_position, true);
-}
-
const std::shared_ptr<IMatrix<std::string_view>> &Scene::get_matrix() const noexcept
{
return _matrix;
diff --git a/src/engine/graphics/scene.hpp b/src/engine/graphics/scene.hpp
index 1f1c51c..c4f6d67 100644
--- a/src/engine/graphics/scene.hpp
+++ b/src/engine/graphics/scene.hpp
@@ -12,10 +12,6 @@
constexpr fmt::string_view ENABLE_ALT_BUFFER = "{esc}[?1049h";
constexpr fmt::string_view DISABLE_ALT_BUFFER = "{esc}[?1049l";
-constexpr fmt::string_view ERASE_ENTIRE_LINE = "{esc}[2K";
-
-constexpr uint32_t STATUSBAR_COLOR = 0x1A1A1AU;
-
class Scene : public IScene
{
public:
@@ -27,8 +23,6 @@ public:
void leave() noexcept override;
- void write_status(const std::string_view &str) noexcept override;
-
[[nodiscard]] const std::shared_ptr<IMatrix<std::string_view>> &
get_matrix() const noexcept override;
diff --git a/src/engine/user/input.hpp b/src/engine/user/input.hpp
index 6a9edfd..51f3fcb 100644
--- a/src/engine/user/input.hpp
+++ b/src/engine/user/input.hpp
@@ -29,7 +29,9 @@ public:
void leave_raw_mode() noexcept override;
private:
- std::array<std::vector<std::shared_ptr<ISubscriber<Context>>>, CHAR_MAX> _subscribers;
+ std::array<std::vector<std::shared_ptr<ISubscriber<Context>>>,
+ static_cast<std::size_t>(CHAR_MAX * 2U)>
+ _subscribers;
std::shared_ptr<termios> _original_termios = nullptr;
diff --git a/src/game/cursor_listener.cpp b/src/game/cursor_listener.cpp
deleted file mode 100644
index c815f08..0000000
--- a/src/game/cursor_listener.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "cursor_listener.hpp"
-
-#include <fmt/core.h>
-#include <utility>
-
-CursorListener::CursorListener(std::shared_ptr<IScene> scene) noexcept
- : _scene(std::move(scene))
-{
-}
-
-void CursorListener::update(const Vector2 &context) noexcept
-{
- _scene->write_status(fmt::format("X: {} Y {}", context.get_x(), context.get_y()));
-}
diff --git a/src/game/cursor_listener.hpp b/src/game/cursor_listener.hpp
deleted file mode 100644
index c74bb3d..0000000
--- a/src/game/cursor_listener.hpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include "interfaces/scene.hpp"
-#include "interfaces/subscriber.hpp"
-
-#include "engine/data/vector2.hpp"
-
-#include <memory>
-
-class CursorListener : public ISubscriber<Vector2>
-{
-public:
- explicit CursorListener(std::shared_ptr<IScene> scene) noexcept;
-
- void update(const Vector2 &context) noexcept override;
-
-private:
- std::shared_ptr<IScene> _scene;
-};
diff --git a/src/game/game.cpp b/src/game/game.cpp
index f788d15..6632c08 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -3,22 +3,31 @@
#include "commands/insert_cell.hpp"
#include "commands/move_cursor.hpp"
#include "commands/quit.hpp"
-#include "game/cursor_listener.hpp"
+#include "commands/toggle_pause.hpp"
#include <fmt/core.h>
#include <iostream>
+#include <utility>
-Game::Game(const std::shared_ptr<IWindow> &window, const std::shared_ptr<IScene> &scene,
- const std::shared_ptr<ICursorController> &cursor_controller) noexcept
- : _window(window), _scene(scene), _cursor_controller(cursor_controller)
+Game::Game(std::shared_ptr<IWindow> window, std::shared_ptr<IScene> scene,
+ std::shared_ptr<ICursorController> cursor_controller,
+ std::shared_ptr<IStatusLine> statusline,
+ std::shared_ptr<IGenerationTracker> generation_tracker,
+ std::shared_ptr<IStatusUpdater> status_updater) noexcept
+ : _window(std::move(window)),
+ _scene(std::move(scene)),
+ _cursor_controller(std::move(cursor_controller)),
+ _statusline(std::move(statusline)),
+ _generation_tracker(std::move(generation_tracker)),
+ _status_updater(std::move(status_updater))
{
}
void Game::on_start() noexcept
{
- auto cursor_listener = std::make_shared<CursorListener>(_scene);
+ _statusline->initialize_background();
- _cursor_controller->subscribe(CursorEvent::POSITION_CHANGE, cursor_listener);
+ _cursor_controller->subscribe(CursorEvent::POSITION_CHANGE, _status_updater);
const auto window_size = _window->size();
@@ -28,9 +37,11 @@ void Game::on_start() noexcept
_cursor_controller->move_to(center_position);
- cursor_listener->update(center_position);
+ _status_updater->update(center_position);
}
+void Game::on_update() noexcept {}
+
void Game::on_exit() const noexcept
{
for (auto row : *_scene->get_matrix())
@@ -51,6 +62,7 @@ Game::get_input_config() const noexcept
{
return {{'q', std::make_shared<QuitCommand>()},
{'i', std::make_shared<InsertCellCommand>(_cursor_controller, _scene)},
+ {'p', std::make_shared<TogglePauseCommand>(_generation_tracker, _statusline)},
{'k', std::make_shared<MoveCursorCommand>(Vector2::up(), _cursor_controller,
_window)},
{'j', std::make_shared<MoveCursorCommand>(Vector2::down(), _cursor_controller,
diff --git a/src/game/game.hpp b/src/game/game.hpp
index 2493a42..5894a01 100644
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -2,7 +2,10 @@
#include "interfaces/cursor.hpp"
#include "interfaces/game.hpp"
+#include "interfaces/generation_tracker.hpp"
#include "interfaces/scene.hpp"
+#include "interfaces/status_updater.hpp"
+#include "interfaces/statusline.hpp"
#include "interfaces/window.hpp"
#include <memory>
@@ -10,18 +13,26 @@
class Game : public IGame
{
public:
- Game(const std::shared_ptr<IWindow> &window, const std::shared_ptr<IScene> &scene,
- const std::shared_ptr<ICursorController> &cursor_controller) noexcept;
+ Game(std::shared_ptr<IWindow> window, std::shared_ptr<IScene> scene,
+ std::shared_ptr<ICursorController> cursor_controller,
+ std::shared_ptr<IStatusLine> statusline,
+ std::shared_ptr<IGenerationTracker> generation_tracker,
+ std::shared_ptr<IStatusUpdater> status_updater) noexcept;
void on_start() noexcept override;
+ void on_update() noexcept override;
+
void on_exit() const noexcept override;
[[nodiscard]] std::unordered_map<char, std::shared_ptr<ICommand>>
get_input_config() const noexcept override;
private:
- const std::shared_ptr<IWindow> &_window;
- const std::shared_ptr<IScene> &_scene;
- const std::shared_ptr<ICursorController> &_cursor_controller;
+ std::shared_ptr<IWindow> _window;
+ std::shared_ptr<IScene> _scene;
+ std::shared_ptr<ICursorController> _cursor_controller;
+ std::shared_ptr<IStatusLine> _statusline;
+ std::shared_ptr<IGenerationTracker> _generation_tracker;
+ std::shared_ptr<IStatusUpdater> _status_updater;
};
diff --git a/src/game/generation_tracker.cpp b/src/game/generation_tracker.cpp
new file mode 100644
index 0000000..0e137cb
--- /dev/null
+++ b/src/game/generation_tracker.cpp
@@ -0,0 +1,18 @@
+#include "generation_tracker.hpp"
+
+GenerationTracker::GenerationTracker(bool is_paused) noexcept : _is_paused(is_paused) {}
+
+uint32_t GenerationTracker::get_current_generation() const noexcept
+{
+ return _current_generation;
+}
+
+bool GenerationTracker::get_is_paused() const noexcept
+{
+ return _is_paused;
+}
+
+void GenerationTracker::set_is_paused(bool is_paused) noexcept
+{
+ _is_paused = is_paused;
+}
diff --git a/src/game/generation_tracker.hpp b/src/game/generation_tracker.hpp
new file mode 100644
index 0000000..0e59751
--- /dev/null
+++ b/src/game/generation_tracker.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "interfaces/generation_tracker.hpp"
+
+#include <cstdint>
+
+class GenerationTracker : public IGenerationTracker
+{
+public:
+ explicit GenerationTracker(bool is_paused) noexcept;
+
+ [[nodiscard]] uint32_t get_current_generation() const noexcept override;
+
+ [[nodiscard]] bool get_is_paused() const noexcept override;
+
+ void set_is_paused(bool is_paused) noexcept override;
+
+private:
+ uint32_t _current_generation = 0U;
+
+ bool _is_paused;
+};
diff --git a/src/game/status_updater.cpp b/src/game/status_updater.cpp
new file mode 100644
index 0000000..18c535e
--- /dev/null
+++ b/src/game/status_updater.cpp
@@ -0,0 +1,25 @@
+#include "status_updater.hpp"
+
+#include <fmt/core.h>
+#include <utility>
+
+StatusUpdater::StatusUpdater(
+ std::shared_ptr<IStatusLine> statusline,
+ std::shared_ptr<IGenerationTracker> generation_tracker) noexcept
+ : _statusline(std::move(statusline)),
+ _generation_tracker(std::move(generation_tracker))
+{
+}
+
+void StatusUpdater::update(const Vector2 &context) noexcept
+{
+ _statusline->set_status(
+ StatusLineSection::A,
+ fmt::format("X: {} Y {}", context.get_x(), context.get_y()));
+
+ _statusline->set_status(
+ StatusLineSection::B,
+ fmt::format("Paused: {} Generation: {}",
+ _generation_tracker->get_is_paused() ? "yes" : "no",
+ _generation_tracker->get_current_generation()));
+}
diff --git a/src/game/status_updater.hpp b/src/game/status_updater.hpp
new file mode 100644
index 0000000..3b76d3b
--- /dev/null
+++ b/src/game/status_updater.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "interfaces/generation_tracker.hpp"
+#include "interfaces/status_updater.hpp"
+#include "interfaces/statusline.hpp"
+#include "interfaces/subscriber.hpp"
+
+#include "engine/data/vector2.hpp"
+
+#include <memory>
+
+class StatusUpdater : public IStatusUpdater
+{
+public:
+ explicit StatusUpdater(
+ std::shared_ptr<IStatusLine> statusline,
+ std::shared_ptr<IGenerationTracker> generation_tracker) noexcept;
+
+ void update(const Vector2 &context) noexcept override;
+
+private:
+ std::shared_ptr<IStatusLine> _statusline;
+ std::shared_ptr<IGenerationTracker> _generation_tracker;
+};
diff --git a/src/game/statusline.cpp b/src/game/statusline.cpp
new file mode 100644
index 0000000..377fa75
--- /dev/null
+++ b/src/game/statusline.cpp
@@ -0,0 +1,102 @@
+#include "statusline.hpp"
+
+#include "engine/escape.hpp"
+#include "util/color.hpp"
+
+#include <iostream>
+#include <string>
+#include <utility>
+
+StatusLine::StatusLine(std::shared_ptr<ICursorController> cursor_controller,
+ std::shared_ptr<IWindow> window) noexcept
+ : _cursor_controller(std::move(cursor_controller)), _window(std::move(window))
+{
+ constexpr uint32_t SECTION_A_LENGTH = 20;
+ constexpr uint32_t SECTION_B_LENGTH = 15;
+
+ _sections_lengths[StatusLineSection::A] = SECTION_A_LENGTH;
+ _sections_lengths[StatusLineSection::B] = SECTION_B_LENGTH;
+}
+
+void StatusLine::initialize_background() noexcept
+{
+ const auto previous_position = _move_to_statusline(0);
+
+ auto background_color = get_background_esc_seq(STATUSBAR_COLOR);
+
+ fmt::print("{}{}", background_color, std::string(_window->size().get_width(), ' '));
+ fmt::print(RESET_ALL_MODES, fmt::arg("esc", ESC));
+
+ _move_back(previous_position);
+}
+
+void StatusLine::set_status(StatusLineSection section,
+ const std::string_view &str) noexcept
+{
+ _clear_section(section);
+
+ int32_t section_start = _get_section_start_x(section);
+
+ const auto previous_position = _move_to_statusline(section_start);
+
+ auto background_color = get_background_esc_seq(STATUSBAR_COLOR);
+
+ fmt::print("{}{}", background_color, str);
+ fmt::print(RESET_ALL_MODES, fmt::arg("esc", ESC));
+
+ _move_back(previous_position);
+}
+
+Vector2 StatusLine::_move_to_statusline(int32_t x) noexcept
+{
+ const auto previous_position = _cursor_controller->where();
+
+ const auto window_size = _window->size();
+
+ _cursor_controller->hide();
+
+ auto window_height = static_cast<Vector2::Value>(window_size.get_height());
+
+ _cursor_controller->move_to(Vector2({.x = x, .y = window_height}), true);
+
+ return previous_position;
+}
+
+void StatusLine::_move_back(Vector2 previous_position) noexcept
+{
+ _cursor_controller->move_to(previous_position, true);
+ _cursor_controller->show();
+}
+
+int32_t StatusLine::_get_section_start_x(StatusLineSection section) const noexcept
+{
+ int32_t section_start = 0;
+
+ auto section_index = static_cast<int32_t>(section);
+
+ while (section_index > 0)
+ {
+ section_start += static_cast<int32_t>(
+ _sections_lengths.at(StatusLineSection(section_index - 1)));
+
+ section_index--;
+ }
+
+ return section_start;
+}
+
+void StatusLine::_clear_section(StatusLineSection section) noexcept
+{
+ auto section_start = _get_section_start_x(section);
+
+ const auto previous_position = _move_to_statusline(section_start);
+
+ auto background_color = get_background_esc_seq(STATUSBAR_COLOR);
+
+ auto section_length = _sections_lengths.at(section);
+
+ fmt::print("{}{}", background_color, std::string(section_length, ' '));
+ fmt::print(RESET_ALL_MODES, fmt::arg("esc", ESC));
+
+ _move_back(previous_position);
+}
diff --git a/src/game/statusline.hpp b/src/game/statusline.hpp
new file mode 100644
index 0000000..7db1e0b
--- /dev/null
+++ b/src/game/statusline.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "interfaces/cursor.hpp"
+#include "interfaces/statusline.hpp"
+#include "interfaces/window.hpp"
+
+#include "engine/data/vector2.hpp"
+
+#include <cstdint>
+#include <fmt/core.h>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+
+constexpr uint32_t STATUSBAR_COLOR = 0x1A1A1AU;
+
+class StatusLine : public IStatusLine
+{
+public:
+ StatusLine(std::shared_ptr<ICursorController> cursor_controller,
+ std::shared_ptr<IWindow> window) noexcept;
+
+ void initialize_background() noexcept override;
+
+ void set_status(StatusLineSection section,
+ const std::string_view &str) noexcept override;
+
+private:
+ std::unordered_map<StatusLineSection, uint32_t> _sections_lengths;
+
+ std::shared_ptr<ICursorController> _cursor_controller;
+ std::shared_ptr<IWindow> _window;
+
+ Vector2 _move_to_statusline(int32_t x) noexcept;
+
+ void _move_back(Vector2 previous_position) noexcept;
+
+ int32_t _get_section_start_x(StatusLineSection section) const noexcept;
+
+ void _clear_section(StatusLineSection section) noexcept;
+};
diff --git a/src/interfaces/game.hpp b/src/interfaces/game.hpp
index c99c01f..6836dbd 100644
--- a/src/interfaces/game.hpp
+++ b/src/interfaces/game.hpp
@@ -11,9 +11,11 @@
class IGame
{
public:
- virtual ~IGame() = default;
+ virtual ~IGame() noexcept = default;
- virtual void on_start() = 0;
+ virtual void on_start() noexcept = 0;
+
+ virtual void on_update() noexcept = 0;
virtual void on_exit() const noexcept = 0;
diff --git a/src/interfaces/generation_tracker.hpp b/src/interfaces/generation_tracker.hpp
new file mode 100644
index 0000000..135af00
--- /dev/null
+++ b/src/interfaces/generation_tracker.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+class IGenerationTracker
+{
+public:
+ virtual ~IGenerationTracker() noexcept = default;
+
+ [[nodiscard]] virtual uint32_t get_current_generation() const noexcept = 0;
+
+ [[nodiscard]] virtual bool get_is_paused() const noexcept = 0;
+
+ virtual void set_is_paused(bool is_paused) noexcept = 0;
+};
+
+using IGenerationTrackerFactory = std::shared_ptr<IGenerationTracker> (*)(bool is_paused);
diff --git a/src/interfaces/scene.hpp b/src/interfaces/scene.hpp
index 0443d41..3b5e037 100644
--- a/src/interfaces/scene.hpp
+++ b/src/interfaces/scene.hpp
@@ -15,8 +15,6 @@ public:
virtual void leave() noexcept = 0;
- virtual void write_status(const std::string_view &str) noexcept = 0;
-
[[nodiscard]] virtual const std::shared_ptr<IMatrix<std::string_view>> &
get_matrix() const noexcept = 0;
};
diff --git a/src/interfaces/status_updater.hpp b/src/interfaces/status_updater.hpp
new file mode 100644
index 0000000..ccfb2db
--- /dev/null
+++ b/src/interfaces/status_updater.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "interfaces/generation_tracker.hpp"
+#include "interfaces/statusline.hpp"
+#include "interfaces/subscriber.hpp"
+
+#include "engine/data/vector2.hpp"
+
+#include <memory>
+
+class IStatusUpdater : public ISubscriber<Vector2>
+{
+public:
+ void update(const Vector2 &context) noexcept override = 0;
+};
+
+using IStatusUpdaterFactory = std::shared_ptr<IStatusUpdater> (*)(
+ const std::shared_ptr<IStatusLine> &statusline,
+ const std::shared_ptr<IGenerationTracker> &generation_tracker);
diff --git a/src/interfaces/statusline.hpp b/src/interfaces/statusline.hpp
new file mode 100644
index 0000000..00da99b
--- /dev/null
+++ b/src/interfaces/statusline.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "interfaces/cursor.hpp"
+#include "interfaces/window.hpp"
+
+#include <string_view>
+
+enum StatusLineSection
+{
+ A,
+ B
+};
+
+class IStatusLine
+{
+public:
+ virtual ~IStatusLine() noexcept = default;
+
+ virtual void initialize_background() noexcept = 0;
+
+ virtual void set_status(StatusLineSection section,
+ const std::string_view &str) noexcept = 0;
+};
+
+using IStatusLineFactory = std::shared_ptr<IStatusLine> (*)(
+ const std::shared_ptr<ICursorController> &cursor_controller,
+ const std::shared_ptr<IWindow> &window);