aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/CMakeLists.txt8
-rw-r--r--lib/ctre/CMakeLists.txt3
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/bootstrap.cpp8
-rw-r--r--src/errors/RLE_reader.hpp19
-rw-r--r--src/errors/io.hpp55
-rw-r--r--src/game/RLE_reader.cpp133
-rw-r--r--src/game/RLE_reader.hpp24
-rw-r--r--src/game/game.cpp77
-rw-r--r--src/game/game.hpp5
-rw-r--r--src/interfaces/RLE_reader.hpp19
-rw-r--r--src/util/algorithm.hpp7
-rw-r--r--src/util/algorithm_impl.hpp10
-rw-r--r--src/util/fs.cpp29
-rw-r--r--src/util/fs.hpp22
-rw-r--r--src/util/io.hpp14
-rw-r--r--src/util/io_impl.hpp55
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;
+}