aboutsummaryrefslogtreecommitdiff
path: root/src/game
diff options
context:
space:
mode:
Diffstat (limited to 'src/game')
-rw-r--r--src/game/game.cpp248
-rw-r--r--src/game/game.hpp52
-rw-r--r--src/game/keycodes.hpp15
3 files changed, 299 insertions, 16 deletions
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 3127d40..0b3e85e 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -1,8 +1,12 @@
#include "game.hpp"
#include "engine/data/bounds.hpp"
+#include "engine/escape.hpp"
+#include "game/keycodes.hpp"
#include "util/algorithm.hpp"
+#include "util/string.hpp"
+#include <fmt/color.h>
#include <fmt/core.h>
#include <algorithm>
@@ -24,7 +28,8 @@ Game::Game(
_generation_tracker(std::move(generation_tracker)),
_status_manager(std::move(status_manager)),
_user_input_observer(std::move(user_input_observer)),
- _cell_helper(std::move(cell_helper))
+ _cell_helper(std::move(cell_helper)),
+ _current_mode(Mode::NORMAL)
{
}
@@ -35,7 +40,7 @@ void Game::on_start() noexcept
std::shared_ptr<IStatusLine> statusline =
_statusline_factory(Bounds({.width = scene_size.get_width(), .height = 1}));
- _scene->register_component(statusline, Vector2({0, 0}));
+ _scene->register_component(statusline, Vector2({.x = 0, .y = 1}));
_status_manager->bind(statusline);
@@ -81,11 +86,49 @@ void Game::on_start() noexcept
scene_size.get_width(),
scene_size.get_height()));
- _last_update_time = std::chrono::system_clock::now();
+ _commands["ping"] = CommandInfo(
+ {.option_cnt = 0,
+ .function = [this](CommandInfo::Options /*options*/)
+ {
+ _erase_entire_line();
+
+ const auto position = _cursor_controller->where();
+
+ _cursor_controller->move_to(Vector2({.x = 0, .y = position.get_y()}));
+
+ std::cout << "pong!";
+ std::cout.flush();
+ }});
}
void Game::on_update() noexcept
{
+ if (_current_mode == Mode::COMMAND)
+ {
+ _on_command_mode_update();
+ return;
+ }
+
+ _on_normal_mode_update();
+}
+
+void Game::on_exit() const noexcept
+{
+ for (auto row : *_scene->get_matrix())
+ {
+ for (auto &col : row)
+ {
+ fmt::print("{}", col);
+ }
+
+ fmt::print("\n");
+ }
+
+ std::cout.flush();
+}
+
+void Game::_on_normal_mode_update() noexcept
+{
const auto pressed_key = _user_input_observer->get_currently_pressed_key();
auto cursor_has_moved = false;
@@ -124,6 +167,26 @@ void Game::on_update() noexcept
case 'q':
std::exit(EXIT_SUCCESS);
+ case ':':
+ _last_pos_before_command_mode = _cursor_controller->where();
+
+ _cursor_controller->move_to(Vector2({.x = 0, .y = 0}));
+
+ _erase_entire_line();
+
+ std::cout << ":";
+ std::cout.flush();
+
+ _cursor_controller->update_position(
+ _cursor_controller->where() + Vector2::right());
+
+ _cursor_controller->set_cursor_style(CursorStyle::BlinkingBar);
+
+ _command_mode_input = "";
+
+ _current_mode = Mode::COMMAND;
+ return;
+
case 'i':
{
const auto position = _cursor_controller->where();
@@ -208,7 +271,6 @@ void Game::on_update() noexcept
if (_generation_tracker->get_is_paused() && !is_generation_stepping)
{
- _last_update_time = time_now;
return;
}
@@ -218,7 +280,6 @@ void Game::on_update() noexcept
if (time_since_last_gen.count() <= _min_time_since_last_gen_millis)
{
- _last_update_time = time_now;
return;
}
@@ -261,23 +322,171 @@ void Game::on_update() noexcept
_set_space(matrix, birth_cell_pos, 'x');
_living_cell_positions.push_back(birth_cell_pos);
}
+}
+
+void Game::_on_command_mode_update() noexcept
+{
+ const auto pressed_key = _user_input_observer->get_currently_pressed_key();
+
+ switch (pressed_key)
+ {
+ case keycodes::ESCAPE:
+ _erase_entire_line();
+ _return_to_normal_mode();
+ break;
+
+ case keycodes::ENTER:
+ if (!_command_mode_input.empty())
+ {
+ _run_command(_command_mode_input);
+ }
+
+ if (_current_mode != Mode::NORMAL)
+ {
+ _return_to_normal_mode();
+ }
+
+ break;
+
+ case keycodes::BACKSPACE:
+ {
+ const auto position = _cursor_controller->where();
+
+ const auto input_pos_x = static_cast<uint32_t>(position.get_x()) - 1U;
+
+ if (input_pos_x == 0U)
+ {
+ break;
+ }
+
+ _command_mode_input.erase(input_pos_x - 1U, 1U);
+
+ _cursor_controller->move(Vector2::left(), 1U);
+
+ std::cout.put(' ');
+ std::cout.flush();
+
+ _cursor_controller->update_position(
+ _cursor_controller->where() + Vector2::right());
+
+ _cursor_controller->move(Vector2::left(), 1U);
+
+ _erase_line_from_cursor();
+
+ std::cout << _command_mode_input.substr(input_pos_x - 1U);
+ std::cout.flush();
+
+ _cursor_controller->move_to(position);
+ _cursor_controller->move(Vector2::left(), 1U);
- _last_update_time = time_now;
+ break;
+ }
+
+ case keycodes::LEFT_ARROW:
+ if (_cursor_controller->where().get_x() >= 2)
+ {
+ _cursor_controller->move(Vector2::left(), 1U);
+ }
+ break;
+
+ case keycodes::RIGHT_ARROW:
+ if (static_cast<uint32_t>(_cursor_controller->where().get_x()) - 1U <
+ _command_mode_input.size())
+ {
+ _cursor_controller->move(Vector2::right(), 1U);
+ }
+ break;
+
+ case keycodes::UP_ARROW:
+ case keycodes::DOWN_ARROW:
+ case 0:
+ break;
+
+ default:
+ const auto position = _cursor_controller->where();
+
+ const auto input_pos_x = static_cast<uint32_t>(position.get_x()) - 1U;
+
+ if (input_pos_x < _command_mode_input.length())
+ {
+ _command_mode_input.insert(input_pos_x, 1, pressed_key);
+
+ _erase_line_from_cursor();
+
+ std::cout << pressed_key << _command_mode_input.substr(input_pos_x + 1U);
+ std::cout.flush();
+
+ _cursor_controller->move_to(position + Vector2::right());
+ break;
+ }
+
+ _command_mode_input += pressed_key;
+
+ std::cout << pressed_key;
+ std::cout.flush();
+
+ _cursor_controller->update_position(
+ _cursor_controller->where() + Vector2::right());
+
+ break;
+ }
}
-void Game::on_exit() const noexcept
+void Game::_return_to_normal_mode() noexcept
{
- for (auto row : *_scene->get_matrix())
+ _cursor_controller->move_to(_last_pos_before_command_mode.value_or(
+ Vector2({.x = CURSOR_FALLBACK_POS_X, .y = CURSOR_FALLBACK_POS_Y})));
+
+ _cursor_controller->set_cursor_style(CursorStyle::BlinkingBlock);
+
+ _current_mode = Mode::NORMAL;
+}
+
+void Game::_run_command(const std::string &command) noexcept
+{
+ const auto split_command = split_string<std::vector<std::string>>(command, ' ');
+
+ if (split_command.size() == 1)
{
- for (auto &col : row)
+ if (!_commands.contains(command))
{
- fmt::print("{}", col);
+ _show_command_error(fmt::format("Error: Not a command: {}", command));
+ return;
}
- fmt::print("\n");
+ if (_commands.at(command).option_cnt != 0U)
+ {
+ _show_command_error(
+ fmt::format("Error: Insufficient arguments for command: {}", command));
+ return;
+ }
}
- std::cout.flush();
+ const auto &command_name = split_command[0];
+
+ if (!_commands.contains(command_name))
+ {
+ _show_command_error(fmt::format("Error: Not a command: {}", command));
+ return;
+ }
+
+ const auto &command_info = _commands.at(command_name);
+
+ const auto args =
+ decltype(split_command)(split_command.begin() + 1, split_command.end());
+
+ command_info.function(args);
+}
+
+void Game::_show_command_error(const std::string_view &error_message) noexcept
+{
+ _erase_entire_line();
+
+ const auto position = _cursor_controller->where();
+
+ _cursor_controller->move_to(Vector2({.x = 0, .y = position.get_y()}));
+
+ fmt::print(fmt::fg(fmt::color::red), "{}", error_message);
}
auto Game::_move_cursor(const Vector2 &direction) noexcept -> bool
@@ -293,7 +502,7 @@ auto Game::_move_cursor(const Vector2 &direction) noexcept -> bool
return false;
}
- if (dest_position.get_y() < 1)
+ if (dest_position.get_y() <= 1)
{
return false;
}
@@ -319,3 +528,16 @@ void Game::_set_space(
_cursor_controller->move_to(prev_position);
}
+
+void Game::_erase_entire_line() noexcept
+{
+ fmt::print(ERASE_ENTIRE_LINE, fmt::arg("esc", ESC));
+ std::cout.flush();
+}
+
+void Game::_erase_line_from_cursor() noexcept
+{
+ fmt::print(ERASE_LINE_FROM_CURSOR, fmt::arg("esc", ESC));
+ std::cout.flush();
+}
+
diff --git a/src/game/game.hpp b/src/game/game.hpp
index 8363c4d..a35c0ce 100644
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -14,14 +14,41 @@
#include <chrono>
#include <cstddef>
+#include <functional>
#include <list>
#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
constexpr auto DEFAULT_MIN_TIME_SINCE_LAST_GEN_MILLIS = 200;
constexpr auto MIN_TIME_SINCE_LAST_GEN_INCREMENT = 50;
constexpr auto MIN_TIME_SINCE_LAST_GEN_DECREMENT = 50;
+constexpr auto CURSOR_FALLBACK_POS_X = 10;
+constexpr auto CURSOR_FALLBACK_POS_Y = 10;
+
+constexpr std::string_view ERASE_ENTIRE_LINE = "{esc}[2K";
+constexpr std::string_view ERASE_LINE_FROM_CURSOR = "{esc}[0K";
+
+enum Mode
+{
+ NORMAL,
+ COMMAND
+};
+
+class CommandInfo
+{
+public:
+ using Options = const std::vector<std::string> &;
+ using CommandFunction = std::function<void(Options)>;
+
+ std::size_t option_cnt;
+ CommandFunction function;
+};
+
class Game : public IGame
{
public:
@@ -50,19 +77,38 @@ private:
std::shared_ptr<IUserInputObserver> _user_input_observer;
std::shared_ptr<ICellHelper> _cell_helper;
- using TimePoint = std::chrono::system_clock::time_point;
+ Mode _current_mode;
- TimePoint _last_update_time;
- TimePoint _last_gen_update_time;
+ std::optional<Vector2> _last_pos_before_command_mode;
+
+ std::string _command_mode_input;
+
+ std::unordered_map<std::string, CommandInfo> _commands;
+
+ std::chrono::system_clock::time_point _last_gen_update_time;
int32_t _min_time_since_last_gen_millis = DEFAULT_MIN_TIME_SINCE_LAST_GEN_MILLIS;
std::list<Vector2> _living_cell_positions;
+ void _on_normal_mode_update() noexcept;
+
+ void _on_command_mode_update() noexcept;
+
+ void _return_to_normal_mode() noexcept;
+
+ void _run_command(const std::string &command) noexcept;
+
+ void _show_command_error(const std::string_view &error_message) noexcept;
+
auto _move_cursor(const Vector2 &direction) noexcept -> bool;
void _set_space(
const std::shared_ptr<IMatrix<IScene::MatrixElement>> &matrix,
const Vector2 &position,
char character) noexcept;
+
+ static void _erase_entire_line() noexcept;
+
+ static void _erase_line_from_cursor() noexcept;
};
diff --git a/src/game/keycodes.hpp b/src/game/keycodes.hpp
new file mode 100644
index 0000000..84ca5a7
--- /dev/null
+++ b/src/game/keycodes.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+namespace keycodes
+{
+
+constexpr auto ENTER = 10;
+constexpr auto ESCAPE = 27;
+constexpr auto BACKSPACE = 127;
+
+constexpr auto UP_ARROW = 65;
+constexpr auto DOWN_ARROW = 66;
+constexpr auto RIGHT_ARROW = 67;
+constexpr auto LEFT_ARROW = 68;
+
+} // namespace keycodes