diff options
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/game.cpp | 248 | ||||
-rw-r--r-- | src/game/game.hpp | 52 | ||||
-rw-r--r-- | src/game/keycodes.hpp | 15 |
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 |