diff options
-rw-r--r-- | lib/CMakeLists.txt | 8 | ||||
-rw-r--r-- | lib/ctre/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/bootstrap.cpp | 8 | ||||
-rw-r--r-- | src/errors/RLE_reader.hpp | 19 | ||||
-rw-r--r-- | src/errors/io.hpp | 55 | ||||
-rw-r--r-- | src/game/RLE_reader.cpp | 133 | ||||
-rw-r--r-- | src/game/RLE_reader.hpp | 24 | ||||
-rw-r--r-- | src/game/game.cpp | 77 | ||||
-rw-r--r-- | src/game/game.hpp | 5 | ||||
-rw-r--r-- | src/interfaces/RLE_reader.hpp | 19 | ||||
-rw-r--r-- | src/util/algorithm.hpp | 7 | ||||
-rw-r--r-- | src/util/algorithm_impl.hpp | 10 | ||||
-rw-r--r-- | src/util/fs.cpp | 29 | ||||
-rw-r--r-- | src/util/fs.hpp | 22 | ||||
-rw-r--r-- | src/util/io.hpp | 14 | ||||
-rw-r--r-- | src/util/io_impl.hpp | 55 |
17 files changed, 482 insertions, 11 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 84c5a77..9a18dbe 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -24,6 +24,14 @@ FetchContent_Declare( add_subdirectory(yacppdic) +FetchContent_Declare( + ctre + GIT_REPOSITORY "https://github.com/hanickadot/compile-time-regular-expressions" + GIT_TAG v3.7 +) + +add_subdirectory(ctre) + if(test) FetchContent_Declare( gtest diff --git a/lib/ctre/CMakeLists.txt b/lib/ctre/CMakeLists.txt new file mode 100644 index 0000000..3f2aced --- /dev/null +++ b/lib/ctre/CMakeLists.txt @@ -0,0 +1,3 @@ +message(STATUS "Fetching ctre...") + +FetchContent_MakeAvailable(ctre) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 52c5679..f6b27cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,10 +11,12 @@ endfunction(target_link_libraries_system) file(GLOB SOURCES bootstrap.cpp util/color.cpp + util/fs.cpp game/game.cpp game/status_manager.cpp game/generation_tracker.cpp game/components/statusline.cpp + game/RLE_reader.cpp engine/engine.cpp engine/main.cpp engine/data/vector2.cpp @@ -47,7 +49,7 @@ target_compile_options( -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror - -pedantic -fsanitize=address -fno-exceptions -fno-omit-frame-pointer + -pedantic -fsanitize=address -fno-omit-frame-pointer ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") @@ -65,6 +67,7 @@ target_link_libraries_system( fmt::fmt-header-only GSL yacppdic + ctre ) target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address) diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp index 3554183..16d38a3 100644 --- a/src/bootstrap.cpp +++ b/src/bootstrap.cpp @@ -1,6 +1,7 @@ #include "bootstrap.hpp" // Interfaces +#include "interfaces/RLE_reader.hpp" #include "interfaces/cell_helper.hpp" #include "interfaces/component.hpp" #include "interfaces/component_renderer.hpp" @@ -23,6 +24,7 @@ #include "engine/graphics/scene.hpp" #include "engine/user/cursor.hpp" #include "engine/user/input.hpp" +#include "game/RLE_reader.hpp" #include "game/cell_helper.hpp" #include "game/components/statusline.hpp" #include "game/game.hpp" @@ -40,6 +42,7 @@ auto bootstrap() noexcept -> yacppdic::Container container.bind<ICLIGameEngine>().to<CLIGameEngine>(); container.bind<IStatusManager>().to<StatusManager>(); container.bind<IScene>().to<Scene>(); + container.bind<IRLEReader>().to<RLEReader>(); container.bind<IGameFactory>().to_factory( [&container]( @@ -57,6 +60,8 @@ auto bootstrap() noexcept -> yacppdic::Container const auto cell_helper_factory = container.get<ICellHelperFactory<char>>(); + std::shared_ptr<IRLEReader> rle_reader = container.get<IRLEReader>(); + return std::make_unique<Game>( statusline_factory, scene, @@ -64,7 +69,8 @@ auto bootstrap() noexcept -> yacppdic::Container generation_tracker, status_manager, user_input_observer, - cell_helper_factory(*(scene->get_matrix()))); + cell_helper_factory(*(scene->get_matrix())), + rle_reader); }); container.bind<IMatrixFactory<char>>().to_factory( diff --git a/src/errors/RLE_reader.hpp b/src/errors/RLE_reader.hpp new file mode 100644 index 0000000..39b4ddb --- /dev/null +++ b/src/errors/RLE_reader.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <fmt/core.h> + +#include <filesystem> +#include <stdexcept> +#include <string_view> + +class InvalidRLEFileError : public std::runtime_error +{ +public: + explicit InvalidRLEFileError( + const std::filesystem::path &path, + const std::string_view &reason) noexcept + : std::runtime_error( + fmt::format("Invalid RLE file '{}'. {}", path.string(), reason)) + { + } +}; diff --git a/src/errors/io.hpp b/src/errors/io.hpp new file mode 100644 index 0000000..1d0aec3 --- /dev/null +++ b/src/errors/io.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "util/fs.hpp" + +#include <fmt/core.h> + +#include <filesystem> +#include <stdexcept> +#include <string> + +class IOError : public std::runtime_error +{ +public: + IOError() noexcept : std::runtime_error("Unknown IO error") {} + + explicit IOError(const std::string &error_message) noexcept + : std::runtime_error(error_message) + { + } +}; + +class NoSuchFileOrDirectoryError : public IOError +{ +public: + explicit NoSuchFileOrDirectoryError(const std::filesystem::path &path) noexcept + : IOError( + fmt::format("No file or directory exists with path '{}'", path.string())) + { + } +}; + +class WrongFileTypeError : public IOError +{ +public: + explicit WrongFileTypeError( + const std::filesystem::path &path, + std::filesystem::file_type expected_file_type, + std::filesystem::file_type actual_file_type) noexcept + : IOError(fmt::format( + "Wrong file type of file with path '{}'. Expected {}, is {}", + path.string(), + file_type_names.at(expected_file_type), + file_type_names.at(actual_file_type))) + { + } +}; + +class FileNotOpenError : public IOError +{ +public: + explicit FileNotOpenError(const std::filesystem::path &path) noexcept + : IOError(fmt::format("Failed to open file with path '{}'", path.string())) + { + } +}; diff --git a/src/game/RLE_reader.cpp b/src/game/RLE_reader.cpp new file mode 100644 index 0000000..f971966 --- /dev/null +++ b/src/game/RLE_reader.cpp @@ -0,0 +1,133 @@ +#include "RLE_reader.hpp" + +#include "engine/data/bounds.hpp" +#include "engine/data/vector2.hpp" +#include "errors/RLE_reader.hpp" +#include "util/algorithm.hpp" +#include "util/io.hpp" +#include "util/string.hpp" + +#include <ctre.hpp> + +#include <cctype> +#include <fstream> +#include <string> +#include <vector> + +#include <iostream> + +RLEReader::RLEReader(const IMatrixFactory<MatrixElement> &matrix_factory) noexcept + : _matrix_factory(matrix_factory) +{ +} + +auto RLEReader::read_RLE_file(const std::filesystem::path &path) const + -> std::unique_ptr<IMatrix<MatrixElement>> +{ + auto content_lines = read_file_lines<std::vector<std::string>>(path); + + if (content_lines.empty()) + { + throw InvalidRLEFileError(path, "File is empty"); + } + + if (content_lines.back().empty()) + { + content_lines.pop_back(); + } + + const auto header_line_iter = container_find( + content_lines, + [](const std::string &line) + { + return ctre::starts_with<"x = \\d+, y = \\d+">(line); + // return ctre::match<"x = \\d+, y = \\d+(, rule = [a-zA-Z0-9/]+)?">(line); + }); + + if (header_line_iter == content_lines.end()) + { + throw InvalidRLEFileError(path, "No header line"); + } + + const auto &header_line = *header_line_iter; + + const auto header_parts = split_string<std::vector<std::string>>(header_line, ','); + + const auto &header_x_part = header_parts[0]; + const auto &header_y_part = header_parts[1].substr(1); + + const auto pattern_width = + static_cast<Bounds::Value>(std::stoul(header_x_part.substr(4))); + + const auto pattern_height = + static_cast<Bounds::Value>(std::stoul(header_y_part.substr(4))); + + const auto pattern_size = Bounds({.width = pattern_width, .height = pattern_height}); + + auto pattern_matrix = _matrix_factory(pattern_size); + + auto pattern_pos = Vector2({.x = 0, .y = 0}); + + const auto first_pattern_line_iter = header_line_iter + 1; + + for (auto pattern_line_iter = first_pattern_line_iter; + pattern_line_iter != content_lines.end(); + ++pattern_line_iter) + { + if (!ctre::match<"[bo$0-9!]+">(*pattern_line_iter)) + { + throw InvalidRLEFileError(path, "Invalid pattern line"); + } + } + + auto pattern = std::string(); + + for (auto pattern_line_iter = first_pattern_line_iter; + pattern_line_iter != content_lines.end(); + ++pattern_line_iter) + { + pattern.append(*pattern_line_iter); + } + + auto run_count_str = std::string(); + + for (const auto &character : pattern) + { + if (static_cast<bool>(isdigit(character))) + { + run_count_str += character; + continue; + } + + if (character == '!') + { + break; + } + + const auto run_count = run_count_str.empty() ? 1 : std::stoi(run_count_str); + + run_count_str.clear(); + + if (character == '$') + { + pattern_pos.set_x(0); + pattern_pos += Vector2({.x = 0, .y = run_count}); + continue; + } + + for (auto run_index = 0; run_index < run_count; run_index++) + { + if (pattern_size.validate_coords(pattern_pos) != CoordsValidation::VALID) + { + throw InvalidRLEFileError(path, "Pattern goes out of bounds"); + } + + pattern_matrix->set(pattern_pos, character == 'o' ? 'x' : ' '); + + pattern_pos += Vector2::right(); + } + } + + return pattern_matrix; +} + diff --git a/src/game/RLE_reader.hpp b/src/game/RLE_reader.hpp new file mode 100644 index 0000000..797575e --- /dev/null +++ b/src/game/RLE_reader.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "interfaces/RLE_reader.hpp" +#include "interfaces/matrix.hpp" + +#include <yacppdic/auto_wirable.hpp> + +#include <filesystem> +#include <memory> + +class RLEReader + : public IRLEReader, + public yacppdic:: + AutoWirable<IRLEReader, RLEReader, IMatrixFactory<IRLEReader::MatrixElement>> +{ +public: + explicit RLEReader(const IMatrixFactory<MatrixElement> &matrix_factory) noexcept; + + [[nodiscard]] auto read_RLE_file(const std::filesystem::path &path) const + -> std::unique_ptr<IMatrix<MatrixElement>> override; + +private: + IMatrixFactory<MatrixElement> _matrix_factory; +}; diff --git a/src/game/game.cpp b/src/game/game.cpp index 73ab7f5..8d15b86 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3,7 +3,10 @@ #include "engine/data/bounds.hpp" #include "engine/escape.hpp" #include "engine/keycodes.hpp" +#include "errors/RLE_reader.hpp" +#include "errors/io.hpp" #include "util/algorithm.hpp" +#include "util/fs.hpp" #include "util/string.hpp" #include <fmt/color.h> @@ -21,7 +24,8 @@ Game::Game( 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) noexcept + std::shared_ptr<ICellHelper> cell_helper, + std::shared_ptr<IRLEReader> rle_reader) noexcept : _statusline_factory(std::move(statusline_factory)), _scene(std::move(scene)), _cursor_controller(std::move(cursor_controller)), @@ -29,6 +33,7 @@ Game::Game( _status_manager(std::move(status_manager)), _user_input_observer(std::move(user_input_observer)), _cell_helper(std::move(cell_helper)), + _rle_reader(std::move(rle_reader)), _current_mode(Mode::NORMAL), _minimum_cursor_pos_y(0) { @@ -89,18 +94,74 @@ void Game::on_start() noexcept scene_size.get_width(), scene_size.get_height())); - _commands["ping"] = CommandInfo( - {.option_cnt = 0, - .function = [this](CommandInfo::Options /*options*/) + _commands["open"] = CommandInfo( + {.option_cnt = 1U, + .function = [this](CommandInfo::Options options) { - _erase_entire_line(); + const auto rle_file_path = + expand_path_home(std::filesystem::path(options[0])); - const auto position = _cursor_controller->where(); + std::unique_ptr<IMatrix<char>> rle_matrix; - _cursor_controller->move_to(Vector2({.x = 0, .y = position.get_y()})); + try + { + rle_matrix = _rle_reader->read_RLE_file(rle_file_path); + } + catch (const InvalidRLEFileError &error) + { + _show_command_error(fmt::format("Error: {}", error.what())); + return; + } + catch (const IOError &error) + { + _show_command_error(fmt::format("Error: {}", error.what())); + return; + } + + if (rle_matrix == nullptr) + { + _show_command_error(fmt::format( + "A unknown error occurred while reading RLE file with path '{}'", + rle_file_path.string())); + return; + } + + _return_to_normal_mode(); + + const auto previous_pos = _cursor_controller->where(); + + auto scene_matrix = _scene->get_matrix(); + + for (auto row : *rle_matrix) + { + for (auto &col : row) + { + const auto col_pos = _cursor_controller->where(); + + std::cout.put(col); + + _cursor_controller->move_to(col_pos); + + scene_matrix->set(col_pos, col); + + if (!container_has(_living_cell_positions, col_pos)) + { + _living_cell_positions.push_back(col_pos); + } + + _cursor_controller->move(Vector2::right(), 1U); + } + + fmt::print("\n"); + const auto current_pos = _cursor_controller->where(); + + _cursor_controller->move_to( + Vector2({.x = previous_pos.get_x(), .y = current_pos.get_y() - 1})); + } - std::cout << "pong!"; std::cout.flush(); + + _cursor_controller->move_to(previous_pos); }}); } diff --git a/src/game/game.hpp b/src/game/game.hpp index 4d83fe5..abee6ec 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -1,5 +1,6 @@ #pragma once +#include "interfaces/RLE_reader.hpp" #include "interfaces/cell_helper.hpp" #include "interfaces/cursor.hpp" #include "interfaces/game.hpp" @@ -59,7 +60,8 @@ public: 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) noexcept; + std::shared_ptr<ICellHelper> cell_helper, + std::shared_ptr<IRLEReader> rle_reader) noexcept; void on_start() noexcept override; @@ -76,6 +78,7 @@ private: std::shared_ptr<IStatusManager> _status_manager; std::shared_ptr<IUserInputObserver> _user_input_observer; std::shared_ptr<ICellHelper> _cell_helper; + std::shared_ptr<IRLEReader> _rle_reader; Mode _current_mode; diff --git a/src/interfaces/RLE_reader.hpp b/src/interfaces/RLE_reader.hpp new file mode 100644 index 0000000..a3266a8 --- /dev/null +++ b/src/interfaces/RLE_reader.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "interfaces/matrix.hpp" + +#include <filesystem> +#include <memory> + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) +class IRLEReader +{ +public: + virtual ~IRLEReader() noexcept = default; + + using MatrixElement = char; + + [[nodiscard]] virtual auto read_RLE_file(const std::filesystem::path &path) const + -> std::unique_ptr<IMatrix<MatrixElement>> = 0; +}; + diff --git a/src/util/algorithm.hpp b/src/util/algorithm.hpp index e3d8b6f..60c68e2 100644 --- a/src/util/algorithm.hpp +++ b/src/util/algorithm.hpp @@ -9,6 +9,13 @@ requires Container<ContainerType> constexpr auto container_find(const ContainerType &container, const Value &value) noexcept -> typename ContainerType::const_iterator; +template <typename ContainerType, typename Predicate> +requires Container<ContainerType> && + std::predicate<Predicate, typename ContainerType::value_type> +constexpr auto +container_find(const ContainerType &container, Predicate predicate) noexcept -> + typename ContainerType::const_iterator; + template <typename ContainerType, typename Value> requires Container<ContainerType> constexpr auto container_has(const ContainerType &container, const Value &value) noexcept diff --git a/src/util/algorithm_impl.hpp b/src/util/algorithm_impl.hpp index 00269ed..d7c5e3b 100644 --- a/src/util/algorithm_impl.hpp +++ b/src/util/algorithm_impl.hpp @@ -12,6 +12,16 @@ constexpr auto container_find(const ContainerType &container, const Value &value return std::find(container.begin(), container.end(), value); } +template <typename ContainerType, typename Predicate> +requires Container<ContainerType> && + std::predicate<Predicate, typename ContainerType::value_type> +constexpr auto +container_find(const ContainerType &container, Predicate predicate) noexcept -> + typename ContainerType::const_iterator +{ + return std::find_if(container.begin(), container.end(), predicate); +} + template <typename ContainerType, typename Value> requires Container<ContainerType> constexpr auto container_has(const ContainerType &container, const Value &value) noexcept diff --git a/src/util/fs.cpp b/src/util/fs.cpp new file mode 100644 index 0000000..68c3dd9 --- /dev/null +++ b/src/util/fs.cpp @@ -0,0 +1,29 @@ +#include "fs.hpp" + +#include <cstdlib> +#include <pwd.h> +#include <unistd.h> + +auto get_current_user_home_path() noexcept -> std::filesystem::path +{ + const auto *home_path_env = std::getenv("HOME"); + + if (home_path_env == nullptr) + { + return getpwuid(getuid())->pw_dir; + } + + return home_path_env; +} + +auto expand_path_home(const std::filesystem::path &path) noexcept -> std::filesystem::path +{ + const auto path_str = path.string(); + + if (!path_str.starts_with("~/")) + { + return path; + } + + return get_current_user_home_path() / std::filesystem::path(path_str.substr(2)); +} diff --git a/src/util/fs.hpp b/src/util/fs.hpp new file mode 100644 index 0000000..860c055 --- /dev/null +++ b/src/util/fs.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <filesystem> +#include <string_view> +#include <unordered_map> + +const std::unordered_map<std::filesystem::file_type, std::string_view> file_type_names = { + {std::filesystem::file_type::none, "none"}, + {std::filesystem::file_type::not_found, "not_found"}, + {std::filesystem::file_type::regular, "regular"}, + {std::filesystem::file_type::directory, "directory"}, + {std::filesystem::file_type::symlink, "symlink"}, + {std::filesystem::file_type::block, "block"}, + {std::filesystem::file_type::character, "character"}, + {std::filesystem::file_type::fifo, "fifo"}, + {std::filesystem::file_type::socket, "socket"}, + {std::filesystem::file_type::unknown, "unknown"}}; + +auto get_current_user_home_path() noexcept -> std::filesystem::path; + +auto expand_path_home(const std::filesystem::path &path) noexcept + -> std::filesystem::path; diff --git a/src/util/io.hpp b/src/util/io.hpp new file mode 100644 index 0000000..2c248f7 --- /dev/null +++ b/src/util/io.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "util/concepts.hpp" + +#include <concepts> +#include <filesystem> +#include <string> + +template <typename ContainerType> +requires Container<ContainerType> && HasPushBack<ContainerType> && + std::same_as<typename ContainerType::value_type, std::string> +auto read_file_lines(const std::filesystem::path &path) -> ContainerType; + +#include "io_impl.hpp" diff --git a/src/util/io_impl.hpp b/src/util/io_impl.hpp new file mode 100644 index 0000000..14f4ded --- /dev/null +++ b/src/util/io_impl.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "io.hpp" + +#include "errors/io.hpp" + +#include <fstream> + +template <typename ContainerType> +requires Container<ContainerType> && HasPushBack<ContainerType> && + std::same_as<typename ContainerType::value_type, std::string> +auto read_file_lines(const std::filesystem::path &path) -> ContainerType +{ + if (!std::filesystem::exists(path)) + { + throw NoSuchFileOrDirectoryError(path); + } + + const auto file_status = std::filesystem::status(path); + + const auto file_type = file_status.type(); + + if (file_type != std::filesystem::file_type::regular) + { + throw WrongFileTypeError(path, std::filesystem::file_type::regular, file_type); + } + + auto file = std::ifstream(); + + file.open(path); + + if (!file.is_open()) + { + throw FileNotOpenError(path); + } + + auto content_lines = ContainerType(); + + while (file) + { + auto line = std::string(); + std::getline(file, line); + + std::erase(line, '\r'); + + content_lines.push_back(line); + } + + if (!content_lines.empty() && content_lines.back().empty()) + { + content_lines.pop_back(); + } + + return content_lines; +} |