From e22685d04f11bb085a8167929628f54e01e3e501 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Sun, 7 Jul 2024 22:39:37 +0200 Subject: [PATCH 1/3] add simulation for recording files add tests for the simulation --- src/game/meson.build | 2 + src/game/simulation.cpp | 121 +++++++++++++++++++++ src/game/simulation.hpp | 38 +++++++ src/input/guid.hpp | 102 ++++++++--------- tests/graphics/helper/service_provider.cpp | 87 +++++++++++++++ tests/graphics/helper/service_provider.hpp | 51 +++++++++ tests/graphics/meson.build | 7 +- tests/graphics/tetrion_simulation.cpp | 20 ++++ tests/utils/helper.hpp | 8 ++ tests/utils/meson.build | 7 +- 10 files changed, 392 insertions(+), 51 deletions(-) create mode 100644 src/game/simulation.cpp create mode 100644 src/game/simulation.hpp create mode 100644 tests/graphics/helper/service_provider.cpp create mode 100644 tests/graphics/helper/service_provider.hpp create mode 100644 tests/graphics/tetrion_simulation.cpp diff --git a/src/game/meson.build b/src/game/meson.build index 0399f90a..7bba33ec 100644 --- a/src/game/meson.build +++ b/src/game/meson.build @@ -11,6 +11,8 @@ graphics_src_files += files( 'grid.hpp', 'rotation.cpp', 'rotation.hpp', + 'simulation.cpp', + 'simulation.hpp', 'tetrion.cpp', 'tetrion.hpp', 'tetromino.cpp', diff --git a/src/game/simulation.cpp b/src/game/simulation.cpp new file mode 100644 index 00000000..be4919f2 --- /dev/null +++ b/src/game/simulation.cpp @@ -0,0 +1,121 @@ + +#include +#include + +#include "core/helper/expected.hpp" +#include "input/replay_input.hpp" +#include "simulation.hpp" +#include "ui/layout.hpp" + + +namespace { + + const auto dummy_layout = ui::AbsolutLayout{ 0, 0, 0, 0 }; + +} + + +Simulation::Simulation( + ServiceProvider* const service_provider, + const std::shared_ptr& input, + const tetrion::StartingParameters& starting_parameters +) + : ui::Widget{ dummy_layout, ui::WidgetType::Component, false }, + m_input{ input } { + + + spdlog::info("[simulation] starting level for tetrion {}", starting_parameters.starting_level); + + m_tetrion = std::make_unique( + starting_parameters.tetrion_index, starting_parameters.seed, starting_parameters.starting_level, + service_provider, starting_parameters.recording_writer, dummy_layout, false + ); + + m_tetrion->spawn_next_tetromino(0); + + m_input->set_target_tetrion(m_tetrion.get()); + if (starting_parameters.recording_writer.has_value()) { + const auto recording_writer = starting_parameters.recording_writer.value(); + const auto tetrion_index = starting_parameters.tetrion_index; + m_input->set_event_callback([recording_writer, + tetrion_index](InputEvent event, SimulationStep simulation_step_index) { + spdlog::debug("event: {} (step {})", magic_enum::enum_name(event), simulation_step_index); + + //TODO(Totto): Remove all occurrences of std::ignore, where we shouldn't ignore this return value + std::ignore = recording_writer->add_record(tetrion_index, simulation_step_index, event); + }); + } +} + +helper::expected +Simulation::get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path) { + + //TODO(Totto): Support multiple tetrions to be in the recorded file and simulated + + auto maybe_recording_reader = recorder::RecordingReader::from_path(recording_path); + + if (not maybe_recording_reader.has_value()) { + return helper::unexpected{ + fmt::format("an error occurred while reading recording: {}", maybe_recording_reader.error()) + }; + } + + const auto recording_reader = + std::make_shared(std::move(maybe_recording_reader.value())); + + + const auto tetrion_headers = recording_reader->tetrion_headers(); + + if (tetrion_headers.size() != 1) { + return helper::unexpected{ + fmt::format("Expected 1 recording in the recording file, but got : {}", tetrion_headers.size()) + }; + } + + const auto tetrion_index = 0; + + auto input = std::make_shared(recording_reader, nullptr); + + const auto& header = tetrion_headers.at(tetrion_index); + + const auto seed = header.seed; + const auto starting_level = header.starting_level; + + const tetrion::StartingParameters starting_parameters = { 0, seed, starting_level, tetrion_index, std::nullopt }; + + return Simulation{ service_provider, input, starting_parameters }; +} + + +void Simulation::update() { + if (is_game_finished()) { + return; + } + + ++m_simulation_step_index; + m_input->update(m_simulation_step_index); + m_tetrion->update_step(m_simulation_step_index); + m_input->late_update(m_simulation_step_index); +} + +void Simulation::render(const ServiceProvider&) const { + utils::unreachable(); +} + +[[nodiscard]] helper::BoolWrapper> +Simulation::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { + return false; +} + +[[nodiscard]] bool Simulation::is_game_finished() const { + if (m_tetrion->is_game_over()) { + return true; + }; + + const auto input_as_replay = utils::is_child_class(m_input); + if (input_as_replay.has_value()) { + return input_as_replay.value()->is_end_of_recording(); + } + + return false; +} diff --git a/src/game/simulation.hpp b/src/game/simulation.hpp new file mode 100644 index 00000000..e53784da --- /dev/null +++ b/src/game/simulation.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "core/helper/expected.hpp" +#include "input/input_creator.hpp" +#include "input/replay_input.hpp" +#include "tetrion.hpp" +#include "ui/widget.hpp" + +struct Simulation : public ui::Widget { +private: + using TetrionHeaders = std::vector; + + SimulationStep m_simulation_step_index{ 0 }; + std::unique_ptr m_tetrion; + std::shared_ptr m_input; + +public: + explicit Simulation( + ServiceProvider* service_provider, + const std::shared_ptr& input, + const tetrion::StartingParameters& starting_parameters + ); + + + static helper::expected + get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path); + + void update() override; + + void render(const ServiceProvider& service_provider) const override; + [[nodiscard]] Widget::EventHandleResult + + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; + + [[nodiscard]] bool is_game_finished() const; +}; diff --git a/src/input/guid.hpp b/src/input/guid.hpp index c0c7b208..5b83eab3 100644 --- a/src/input/guid.hpp +++ b/src/input/guid.hpp @@ -52,78 +52,82 @@ struct fmt::formatter : formatter { namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces) - // decode a single_hex_number - [[nodiscard]] constexpr const_utils::Expected single_hex_number(char input) { - if (input >= '0' && input <= '9') { - return const_utils::Expected::good_result(static_cast(input - '0')); - } + namespace guid { - if (input >= 'A' && input <= 'F') { - return const_utils::Expected::good_result(static_cast(input - 'A' + 10)); - } + // decode a single_hex_number + [[nodiscard]] constexpr const_utils::Expected single_hex_number(char input) { + if (input >= '0' && input <= '9') { + return const_utils::Expected::good_result(static_cast(input - '0')); + } + + if (input >= 'A' && input <= 'F') { + return const_utils::Expected::good_result(static_cast(input - 'A' + 10)); + } - if (input >= 'a' && input <= 'f') { - return const_utils::Expected::good_result(static_cast(input - 'a' + 10)); + if (input >= 'a' && input <= 'f') { + return const_utils::Expected::good_result(static_cast(input - 'a' + 10)); + } + + return const_utils::Expected::error_result("the input must be a valid hex character"); } - return const_utils::Expected::error_result("the input must be a valid hex character"); - } + // decode a single 2 digit color value in hex + [[nodiscard]] constexpr const_utils::Expected single_hex_color_value(const char* input) { - // decode a single 2 digit color value in hex - [[nodiscard]] constexpr const_utils::Expected single_hex_color_value(const char* input) { + const auto first = single_hex_number(input[0]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - const auto first = single_hex_number(input[0]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + PROPAGATE(first, u8, std::string); - PROPAGATE(first, u8, std::string); + const auto second = single_hex_number(input[1]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - const auto second = single_hex_number(input[1]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + PROPAGATE(second, u8, std::string); - PROPAGATE(second, u8, std::string); + return const_utils::Expected::good_result((first.value() << 4) | second.value()); + } - return const_utils::Expected::good_result((first.value() << 4) | second.value()); - } + [[nodiscard]] constexpr const_utils::Expected + get_guid_from_string_impl(const char* input, std::size_t size) { - [[nodiscard]] constexpr const_utils::Expected - get_guid_from_string_impl(const char* input, std::size_t size) { + if (size == 0) { + return const_utils::Expected::error_result( + "not enough data to determine the literal type" + ); + } - if (size == 0) { - return const_utils::Expected::error_result( - "not enough data to determine the literal type" - ); - } + constexpr std::size_t amount = 16; - constexpr std::size_t amount = 16; + size_t width = 2; - size_t width = 2; + if (size == amount * 2) { + width = 2; + } else if (size == (amount * 2 + (amount - 1))) { + width = 3; + } else { - if (size == amount * 2) { - width = 2; - } else if (size == (amount * 2 + (amount - 1))) { - width = 3; - } else { + return const_utils::Expected::error_result("Unrecognized guid literal"); + } - return const_utils::Expected::error_result("Unrecognized guid literal"); - } + sdl::GUID::ArrayType result{}; - sdl::GUID::ArrayType result{}; + for (size_t i = 0; i < amount; ++i) { + const size_t offset = i * width; - for (size_t i = 0; i < amount; ++i) { - const size_t offset = i * width; + const auto temp_result = single_hex_color_value( + input + offset + ); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - const auto temp_result = - single_hex_color_value(input + offset); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + PROPAGATE(temp_result, sdl::GUID, std::string); - PROPAGATE(temp_result, sdl::GUID, std::string); + const auto value = temp_result.value(); - const auto value = temp_result.value(); + result.at(i) = value; + } - result.at(i) = value; + return const_utils::Expected::good_result(sdl::GUID{ result }); } - - return const_utils::Expected::good_result(sdl::GUID{ result }); - } + } // namespace guid } // namespace @@ -132,14 +136,14 @@ namespace detail { [[nodiscard]] constexpr const_utils::Expected get_guid_from_string(const std::string& input ) { - return get_guid_from_string_impl(input.c_str(), input.size()); + return guid::get_guid_from_string_impl(input.c_str(), input.size()); } } // namespace detail consteval sdl::GUID operator""_guid(const char* input, std::size_t size) { - const auto result = get_guid_from_string_impl(input, size); + const auto result = guid::get_guid_from_string_impl(input, size); CONSTEVAL_STATIC_ASSERT(result.has_value(), "incorrect guid literal"); diff --git a/tests/graphics/helper/service_provider.cpp b/tests/graphics/helper/service_provider.cpp new file mode 100644 index 00000000..2aca567d --- /dev/null +++ b/tests/graphics/helper/service_provider.cpp @@ -0,0 +1,87 @@ + +#include "./service_provider.hpp" +#include "utils/helper.hpp" + +#include + +#if defined(__GNUC__) || defined(__clang__) +#define FUNCT __FUNCTION__ +#elif +#define FUNCT "" +#endif + +DummyServiceProvider::DummyServiceProvider() = default; + +// implementation of ServiceProvider +[[nodiscard]] EventDispatcher& DummyServiceProvider::event_dispatcher() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const EventDispatcher& DummyServiceProvider::event_dispatcher() const { + ASSERT_FAIL(FUNCT); +} + +FontManager& DummyServiceProvider::font_manager() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const FontManager& DummyServiceProvider::font_manager() const { + ASSERT_FAIL(FUNCT); +} + +CommandLineArguments& DummyServiceProvider::command_line_arguments() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const CommandLineArguments& DummyServiceProvider::command_line_arguments() const { + ASSERT_FAIL(FUNCT); +} + +SettingsManager& DummyServiceProvider::settings_manager() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const SettingsManager& DummyServiceProvider::settings_manager() const { + ASSERT_FAIL(FUNCT); +} + +MusicManager& DummyServiceProvider::music_manager() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const MusicManager& DummyServiceProvider::music_manager() const { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const Renderer& DummyServiceProvider::renderer() const { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const Window& DummyServiceProvider::window() const { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] Window& DummyServiceProvider::window() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] input::InputManager& DummyServiceProvider::input_manager() { + ASSERT_FAIL(FUNCT); +} + +[[nodiscard]] const input::InputManager& DummyServiceProvider::input_manager() const { + ASSERT_FAIL(FUNCT); +} + + +#if defined(_HAVE_DISCORD_SDK) + +[[nodiscard]] std::optional& DummyServiceProvider::discord_instance() { + ASSERT_FAIL(FUNCT); +} +[[nodiscard]] const std::optional& DummyServiceProvider::discord_instance() const { + ASSERT_FAIL(FUNCT); +} + + +#endif diff --git a/tests/graphics/helper/service_provider.hpp b/tests/graphics/helper/service_provider.hpp new file mode 100644 index 00000000..1afb168b --- /dev/null +++ b/tests/graphics/helper/service_provider.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "manager/event_dispatcher.hpp" +#include "manager/music_manager.hpp" +#include "manager/service_provider.hpp" +#include "manager/settings_manager.hpp" + +struct DummyServiceProvider final : public ServiceProvider { +public: + DummyServiceProvider(); + + // implementation of ServiceProvider + [[nodiscard]] EventDispatcher& event_dispatcher() override; + + [[nodiscard]] const EventDispatcher& event_dispatcher() const override; + + FontManager& font_manager() override; + + [[nodiscard]] const FontManager& font_manager() const override; + + CommandLineArguments& command_line_arguments() override; + + [[nodiscard]] const CommandLineArguments& command_line_arguments() const override; + + SettingsManager& settings_manager() override; + + [[nodiscard]] const SettingsManager& settings_manager() const override; + + MusicManager& music_manager() override; + + [[nodiscard]] const MusicManager& music_manager() const override; + + [[nodiscard]] const Renderer& renderer() const override; + + [[nodiscard]] const Window& window() const override; + + [[nodiscard]] Window& window() override; + + [[nodiscard]] input::InputManager& input_manager() override; + + [[nodiscard]] const input::InputManager& input_manager() const override; + + +#if defined(_HAVE_DISCORD_SDK) + + [[nodiscard]] std::optional& discord_instance() override; + [[nodiscard]] const std::optional& discord_instance() const override; + + +#endif +}; diff --git a/tests/graphics/meson.build b/tests/graphics/meson.build index c1538b2d..1f978bf3 100644 --- a/tests/graphics/meson.build +++ b/tests/graphics/meson.build @@ -1 +1,6 @@ -graphics_test_src += files('sdl_key.cpp') +graphics_test_src += files( + 'sdl_key.cpp', + 'helper/service_provider.cpp', + 'helper/service_provider.hpp', + 'tetrion_simulation.cpp', +) diff --git a/tests/graphics/tetrion_simulation.cpp b/tests/graphics/tetrion_simulation.cpp new file mode 100644 index 00000000..530d30a6 --- /dev/null +++ b/tests/graphics/tetrion_simulation.cpp @@ -0,0 +1,20 @@ + + +#include "game/simulation.hpp" +#include "utils/helper.hpp" +#include "./helper/service_provider.hpp" + +#include +#include + + +TEST(Simulation, InvalidFilePath) { + + auto service_provider = std::make_shared(); + + std::filesystem::path path = "__INVALID_PATH"; + + auto maybe_simulation = Simulation::get_replay_simulation(service_provider.get(), path); + + ASSERT_THAT(maybe_simulation, ExpectedHasError()) << "Path was: " << path << "Error: " << maybe_simulation.error(); +} diff --git a/tests/utils/helper.hpp b/tests/utils/helper.hpp index 15c3de0d..0ef51d10 100644 --- a/tests/utils/helper.hpp +++ b/tests/utils/helper.hpp @@ -2,6 +2,7 @@ #pragma once +#include "core/helper/utils.hpp" #include "printer.hpp" #include @@ -22,3 +23,10 @@ MATCHER(OptionalHasValue, "optional has value") { MATCHER(OptionalHasNoValue, "optional has no value") { return not arg.has_value(); } + + +#define ASSERT_FAIL(x) \ + do { \ + std::cerr << "assertion fail in" << __FILE__ << ":" << __LINE__ << ": " << (x); \ + utils::unreachable(); \ + } while (false) diff --git a/tests/utils/meson.build b/tests/utils/meson.build index 95435d7f..b2a61873 100644 --- a/tests/utils/meson.build +++ b/tests/utils/meson.build @@ -1 +1,6 @@ -test_src += files('files.cpp', 'files.hpp', 'helper.hpp', 'printer.hpp') +test_src += files( + 'files.cpp', + 'files.hpp', + 'helper.hpp', + 'printer.hpp' +) From 9be68bce44e487a060316b04ddce355562cb6610 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 8 Jul 2024 22:41:28 +0200 Subject: [PATCH 2/3] improve simulation by splitting tetrion into two separate classes, one handles non graphical simulation, the other graphical simulation --- src/game/meson.build | 2 + src/game/simulated_tetrion.cpp | 535 +++++++++++++++++++++ src/game/simulated_tetrion.hpp | 288 +++++++++++ src/game/simulation.cpp | 25 +- src/game/simulation.hpp | 17 +- src/game/tetrion.cpp | 517 +------------------- src/game/tetrion.hpp | 254 +--------- src/input/game_input.hpp | 11 +- tests/files/test_rec_valid.rec | Bin 0 -> 11726 bytes tests/graphics/helper/service_provider.cpp | 87 ---- tests/graphics/helper/service_provider.hpp | 51 -- tests/graphics/meson.build | 2 - tests/graphics/tetrion_simulation.cpp | 22 +- tests/utils/helper.hpp | 8 +- 14 files changed, 877 insertions(+), 942 deletions(-) create mode 100644 src/game/simulated_tetrion.cpp create mode 100644 src/game/simulated_tetrion.hpp create mode 100644 tests/files/test_rec_valid.rec delete mode 100644 tests/graphics/helper/service_provider.cpp delete mode 100644 tests/graphics/helper/service_provider.hpp diff --git a/src/game/meson.build b/src/game/meson.build index 7bba33ec..9b5f0f6c 100644 --- a/src/game/meson.build +++ b/src/game/meson.build @@ -11,6 +11,8 @@ graphics_src_files += files( 'grid.hpp', 'rotation.cpp', 'rotation.hpp', + 'simulated_tetrion.cpp', + 'simulated_tetrion.hpp', 'simulation.cpp', 'simulation.hpp', 'tetrion.cpp', diff --git a/src/game/simulated_tetrion.cpp b/src/game/simulated_tetrion.cpp new file mode 100644 index 00000000..57a90617 --- /dev/null +++ b/src/game/simulated_tetrion.cpp @@ -0,0 +1,535 @@ +#include +#include +#include + +#include "helper/constants.hpp" +#include "helper/graphic_utils.hpp" +#include "helper/music_utils.hpp" +#include "manager/music_manager.hpp" +#include "simulated_tetrion.hpp" + +#include +#include + + +SimulatedTetrion::SimulatedTetrion( + const u8 tetrion_index, + const Random::Seed random_seed, + const u32 starting_level, + ServiceProvider* const service_provider, + std::optional> recording_writer +) + : m_lock_delay_step_index{ lock_delay }, + m_level{ starting_level }, + m_random{ random_seed }, + m_tetrion_index{ tetrion_index }, + m_next_gravity_simulation_step_index{ get_gravity_delay_frames() }, + m_recording_writer{ std::move(recording_writer) }, + m_service_provider{ service_provider } { } + +SimulatedTetrion::~SimulatedTetrion() = default; + +void SimulatedTetrion::update_step(const SimulationStep simulation_step_index) { + switch (m_game_state) { + case GameState::Playing: { + if (simulation_step_index >= m_next_gravity_simulation_step_index) { + assert(simulation_step_index == m_next_gravity_simulation_step_index and "frame skipped?!"); + if (m_is_accelerated_down_movement and not m_down_key_pressed) { + assert(m_next_gravity_simulation_step_index >= get_gravity_delay_frames() and "overflow"); + m_next_gravity_simulation_step_index -= get_gravity_delay_frames(); + m_is_accelerated_down_movement = false; + } else { + if (move_tetromino_down( + m_is_accelerated_down_movement ? MovementType::Forced : MovementType::Gravity, + simulation_step_index + )) { + reset_lock_delay(simulation_step_index); + } + } + m_next_gravity_simulation_step_index += get_gravity_delay_frames(); + } + + refresh_ghost_tetromino(); + break; + } + case GameState::GameOver: + default: + break; + } +} + +bool SimulatedTetrion::handle_input_command( + const input::GameInputCommand command, + const SimulationStep simulation_step_index +) { + switch (command) { + case input::GameInputCommand::RotateLeft: + if (rotate_tetromino_left()) { + reset_lock_delay(simulation_step_index); + return true; + } + return false; + case input::GameInputCommand::RotateRight: + if (rotate_tetromino_right()) { + reset_lock_delay(simulation_step_index); + return true; + } + return false; + case input::GameInputCommand::MoveLeft: + if (move_tetromino_left()) { + reset_lock_delay(simulation_step_index); + return true; + } + return false; + case input::GameInputCommand::MoveRight: + if (move_tetromino_right()) { + reset_lock_delay(simulation_step_index); + return true; + } + return false; + case input::GameInputCommand::MoveDown: + //TODO(Totto): use input_type() != InputType:Touch +#if not defined(__ANDROID__) + m_down_key_pressed = true; + m_is_accelerated_down_movement = true; + m_next_gravity_simulation_step_index = simulation_step_index + get_gravity_delay_frames(); +#endif + if (move_tetromino_down(MovementType::Forced, simulation_step_index)) { + reset_lock_delay(simulation_step_index); + return true; + } + return false; + case input::GameInputCommand::Drop: + m_lock_delay_step_index = simulation_step_index; // lock instantly + return drop_tetromino(simulation_step_index); + case input::GameInputCommand::ReleaseMoveDown: { + m_down_key_pressed = false; + return false; + } + case input::GameInputCommand::Hold: + if (m_allowed_to_hold) { + hold_tetromino(simulation_step_index); + reset_lock_delay(simulation_step_index); + m_allowed_to_hold = false; + return true; + } + return false; + default: + assert(false and "unknown GameInput"); + return false; + } +} + +void SimulatedTetrion::spawn_next_tetromino(const SimulationStep simulation_step_index) { + spawn_next_tetromino(get_next_tetromino_type(), simulation_step_index); +} + +void SimulatedTetrion::spawn_next_tetromino( + const helper::TetrominoType type, + const SimulationStep simulation_step_index +) { + constexpr GridPoint spawn_position{ 3, 0 }; + m_active_tetromino = Tetromino{ spawn_position, type }; + refresh_previews(); + if (not is_active_tetromino_position_valid()) { + m_game_state = GameState::GameOver; + + auto current_pieces = m_active_tetromino.value().minos(); + + bool all_valid{ false }; + u8 move_up = 0; + while (not all_valid) { + all_valid = true; + for (auto& mino : current_pieces) { + if (mino.position().y != 0) { + mino.position() = mino.position() - GridPoint{ 0, 1 }; + if (not is_valid_mino_position(mino.position())) { + all_valid = false; + } + } + } + + ++move_up; + } + + for (const Mino& mino : m_active_tetromino->minos()) { + auto position = mino.position(); + if (mino.position().y >= move_up && move_up != 0) { + position -= GridPoint{ 0, move_up }; + m_mino_stack.set(position, mino.type()); + } + } + + spdlog::info("game over"); + if (m_recording_writer.has_value()) { + spdlog::info("writing snapshot"); + std::ignore = m_recording_writer.value()->add_snapshot(simulation_step_index, core_information()); + } + m_active_tetromino = {}; + m_ghost_tetromino = {}; + return; + } + + m_next_gravity_simulation_step_index = simulation_step_index + get_gravity_delay_frames(); + refresh_ghost_tetromino(); +} + +bool SimulatedTetrion::rotate_tetromino_right() { + return with_lock_delay([&]() { return rotate(RotationDirection::Right); }); +} + +bool SimulatedTetrion::rotate_tetromino_left() { + return with_lock_delay([&]() { return rotate(RotationDirection::Left); }); +} + +bool SimulatedTetrion::move_tetromino_down(MovementType movement_type, const SimulationStep simulation_step_index) { + if (not m_active_tetromino.has_value()) { + return false; + } + if (movement_type == MovementType::Forced) { + m_score += 4; + } + + + if (tetromino_can_move_down(m_active_tetromino.value())) { + m_active_tetromino->move_down(); + return true; + } + + m_is_in_lock_delay = true; + if ((m_is_in_lock_delay and m_num_executed_lock_delays >= num_lock_delays) + or simulation_step_index >= m_lock_delay_step_index) { + lock_active_tetromino(simulation_step_index); + reset_lock_delay(simulation_step_index); + } else { + m_next_gravity_simulation_step_index = simulation_step_index + 1; + } + return false; +} + +bool SimulatedTetrion::move_tetromino_left() { + return with_lock_delay([&]() { return move(MoveDirection::Left); }); +} + +bool SimulatedTetrion::move_tetromino_right() { + return with_lock_delay([&]() { return move(MoveDirection::Right); }); +} + +bool SimulatedTetrion::drop_tetromino(const SimulationStep simulation_step_index) { + if (not m_active_tetromino.has_value()) { + return false; + } + u64 num_movements = 0; + while (tetromino_can_move_down(m_active_tetromino.value())) { + ++num_movements; + m_active_tetromino->move_down(); + } + + m_score += static_cast(4) * num_movements; + lock_active_tetromino(simulation_step_index); + return num_movements > 0; +} + +void SimulatedTetrion::hold_tetromino(const SimulationStep simulation_step_index) { + if (not m_active_tetromino.has_value()) { + return; + } + + if (not m_tetromino_on_hold.has_value()) { + m_tetromino_on_hold = Tetromino{ grid::hold_tetromino_position, m_active_tetromino->type() }; + spawn_next_tetromino(simulation_step_index); + } else { + const auto on_hold = m_tetromino_on_hold->type(); + m_tetromino_on_hold = Tetromino{ grid::hold_tetromino_position, m_active_tetromino->type() }; + spawn_next_tetromino(on_hold, simulation_step_index); + } +} + +[[nodiscard]] u8 SimulatedTetrion::tetrion_index() const { + return m_tetrion_index; +} + +[[nodiscard]] u32 SimulatedTetrion::level() const { + return m_level; +} + +[[nodiscard]] u64 SimulatedTetrion::score() const { + return m_score; +} + +[[nodiscard]] u32 SimulatedTetrion::lines_cleared() const { + return m_lines_cleared; +} + +[[nodiscard]] const MinoStack& SimulatedTetrion::mino_stack() const { + return m_mino_stack; +} + +[[nodiscard]] std::unique_ptr SimulatedTetrion::core_information() const { + + return std::make_unique(m_tetrion_index, m_level, m_score, m_lines_cleared, m_mino_stack); +} + +[[nodiscard]] bool SimulatedTetrion::is_game_over() const { + return m_game_state == GameState::GameOver; +} + +void SimulatedTetrion::reset_lock_delay(const SimulationStep simulation_step_index) { + m_lock_delay_step_index = simulation_step_index + lock_delay; +} + +void SimulatedTetrion::refresh_texts() { } + +void SimulatedTetrion::clear_fully_occupied_lines() { + bool cleared = false; + const u32 lines_cleared_before = m_lines_cleared; + do { // NOLINT(cppcoreguidelines-avoid-do-while) + cleared = false; + for (u8 row = 0; row < grid::height_in_tiles; ++row) { + bool fully_occupied = true; + for (u8 column = 0; column < grid::width_in_tiles; ++column) { + if (m_mino_stack.is_empty(GridPoint{ column, row })) { + fully_occupied = false; + break; + } + } + + if (fully_occupied) { + ++m_lines_cleared; + const auto level = m_lines_cleared / 10; + if (level > m_level) { + m_level = level; + spdlog::info("new level: {}", m_level); + if (level == constants::music_change_level) { + if (m_service_provider != nullptr) { + m_service_provider->music_manager() + .load_and_play_music( + utils::get_assets_folder() / "music" + / utils::get_supported_music_extension("03. Game Theme (50 Left)") + ) + .and_then(utils::log_error); + } + } + } + m_mino_stack.clear_row_and_let_sink(static_cast(row)); + cleared = true; + break; + } + } + } while (cleared); + const u32 num_lines_cleared = m_lines_cleared - lines_cleared_before; + static constexpr std::array score_per_line_multiplier{ 0, 40, 100, 300, 1200 }; + m_score += static_cast(score_per_line_multiplier.at(num_lines_cleared)) * static_cast(m_level + 1); +} + +void SimulatedTetrion::lock_active_tetromino(const SimulationStep simulation_step_index) { + assert(m_active_tetromino.has_value()); + for (const Mino& mino : m_active_tetromino->minos()) { + m_mino_stack.set(mino.position(), mino.type()); + } + m_allowed_to_hold = true; + m_is_in_lock_delay = false; + m_num_executed_lock_delays = 0; + clear_fully_occupied_lines(); + spawn_next_tetromino(simulation_step_index); + refresh_texts(); + reset_lock_delay(simulation_step_index); + + // save a snapshot on every freeze (only in debug builds) +#if !defined(NDEBUG) + if (m_recording_writer) { + spdlog::debug("adding snapshot at step {}", simulation_step_index); + std::ignore = (*m_recording_writer)->add_snapshot(simulation_step_index, core_information()); + } +#endif +} + +bool SimulatedTetrion::is_active_tetromino_position_valid() const { + if (not m_active_tetromino) { + return false; + } + return is_tetromino_position_valid(m_active_tetromino.value()); +} + +bool SimulatedTetrion::is_valid_mino_position(GridPoint position) const { + return position.x < grid::width_in_tiles and position.y < grid::height_in_tiles and m_mino_stack.is_empty(position); +} + +bool SimulatedTetrion::mino_can_move_down(GridPoint position) const { + if (position.y == (grid::height_in_tiles - 1)) { + return false; + } + + return is_valid_mino_position(position + GridPoint{ 0, 1 }); +} + + +void SimulatedTetrion::refresh_ghost_tetromino() { + if (not m_active_tetromino.has_value()) { + m_ghost_tetromino = {}; + return; + } + m_ghost_tetromino = m_active_tetromino.value(); + while (tetromino_can_move_down(m_ghost_tetromino.value())) { + m_ghost_tetromino->move_down(); + } +} + +void SimulatedTetrion::refresh_previews() { + auto sequence_index = m_sequence_index; + auto bag_index = usize{ 0 }; + for (std::remove_cvref_t i = 0; i < num_preview_tetrominos; ++i) { + m_preview_tetrominos.at(static_cast(i)) = Tetromino{ + grid::preview_tetromino_position + shapes::UPoint{ 0, static_cast(grid::preview_padding * i) }, + m_sequence_bags.at(bag_index)[sequence_index] + }; + ++sequence_index; + static constexpr auto bag_size = decltype(m_sequence_bags)::value_type::size(); + if (sequence_index >= bag_size) { + assert(sequence_index == bag_size); + sequence_index = 0; + ++bag_index; + assert(bag_index < m_sequence_bags.size()); + } + } +} + +helper::TetrominoType SimulatedTetrion::get_next_tetromino_type() { + const helper::TetrominoType next_type = m_sequence_bags[0][m_sequence_index]; + m_sequence_index = (m_sequence_index + 1) % Bag::size(); + if (m_sequence_index == 0) { + // we had a wrap-around + m_sequence_bags[0] = m_sequence_bags[1]; + m_sequence_bags[1] = Bag{ m_random }; + } + return next_type; +} + +bool SimulatedTetrion::tetromino_can_move_down(const Tetromino& tetromino) const { + return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { + return not mino_can_move_down(mino.position()); + }); +} + + +[[nodiscard]] u64 SimulatedTetrion::get_gravity_delay_frames() const { + const auto frames = (m_level >= frames_per_tile.size() ? frames_per_tile.back() : frames_per_tile.at(m_level)); + if (m_is_accelerated_down_movement) { + return std::max(u64{ 1 }, static_cast(std::round(static_cast(frames) / 20.0))); + } + return frames; +} + +u8 SimulatedTetrion::rotation_to_index(const Rotation from, const Rotation rotation_to) { + if (from == Rotation::North and rotation_to == Rotation::East) { + return 0; + } + if (from == Rotation::East and rotation_to == Rotation::North) { + return 1; + } + if (from == Rotation::East and rotation_to == Rotation::South) { + return 2; + } + if (from == Rotation::South and rotation_to == Rotation::East) { + return 3; + } + if (from == Rotation::South and rotation_to == Rotation::West) { + return 4; + } + if (from == Rotation::West and rotation_to == Rotation::South) { + return 5; + } + if (from == Rotation::West and rotation_to == Rotation::North) { + return 6; + } + if (from == Rotation::North and rotation_to == Rotation::West) { + return 7; + } + UNREACHABLE(); +} + +bool SimulatedTetrion::is_tetromino_position_valid(const Tetromino& tetromino) const { + return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { + return not is_valid_mino_position(mino.position()); + }); +} + +bool SimulatedTetrion::rotate(SimulatedTetrion::RotationDirection rotation_direction) { + if (not m_active_tetromino) { + return false; + } + + const auto wall_kick_table = get_wall_kick_table(); + if (not wall_kick_table.has_value()) { + return false; + } + + const auto from_rotation = m_active_tetromino->rotation(); + const auto to_rotation = from_rotation + static_cast(rotation_direction == RotationDirection::Left ? -1 : 1); + const auto table_index = rotation_to_index(from_rotation, to_rotation); + + if (rotation_direction == RotationDirection::Left) { + m_active_tetromino->rotate_left(); + } else { + m_active_tetromino->rotate_right(); + } + + for (const auto& translation : (*wall_kick_table)->at(table_index)) { + m_active_tetromino->move(translation); + if (is_active_tetromino_position_valid()) { + return true; + } + m_active_tetromino->move(-translation); + } + + if (rotation_direction == RotationDirection::Left) { + m_active_tetromino->rotate_right(); + } else { + m_active_tetromino->rotate_left(); + } + return false; +} + +bool SimulatedTetrion::move(const SimulatedTetrion::MoveDirection move_direction) { + if (not m_active_tetromino) { + return false; + } + + switch (move_direction) { + case MoveDirection::Left: + m_active_tetromino->move_left(); + if (not is_active_tetromino_position_valid()) { + m_active_tetromino->move_right(); + return false; + } + return true; + case MoveDirection::Right: + m_active_tetromino->move_right(); + if (not is_active_tetromino_position_valid()) { + m_active_tetromino->move_left(); + return false; + } + return true; + } + + UNREACHABLE(); +} + +std::optional SimulatedTetrion::get_wall_kick_table() const { + assert(m_active_tetromino.has_value() and "no active tetromino"); + const auto type = m_active_tetromino->type(); // NOLINT(bugprone-unchecked-optional-access) + switch (type) { + case helper::TetrominoType::J: + case helper::TetrominoType::L: + case helper::TetrominoType::T: + case helper::TetrominoType::S: + case helper::TetrominoType::Z: + return &wall_kick_data_jltsz; + case helper::TetrominoType::I: + return &wall_kick_data_i; + case helper::TetrominoType::O: + return {}; + default: + UNREACHABLE(); + } +} diff --git a/src/game/simulated_tetrion.hpp b/src/game/simulated_tetrion.hpp new file mode 100644 index 00000000..0a4bbf45 --- /dev/null +++ b/src/game/simulated_tetrion.hpp @@ -0,0 +1,288 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "bag.hpp" +#include "grid.hpp" +#include "input/game_input.hpp" +#include "manager/service_provider.hpp" +#include "tetromino.hpp" +#include "ui/layouts/grid_layout.hpp" +#include "ui/widget.hpp" + +#include + + +enum class GameState : u8 { + Playing, + GameOver, +}; + +enum class MovementType : u8 { + Gravity, + Forced, +}; + +struct SimulatedTetrion { +private: + using WallKickPoint = shapes::AbstractPoint; + using WallKickTable = std::array, 8>; + using GridPoint = Mino::GridPoint; + + static constexpr SimulationStep lock_delay = 30; + static constexpr int num_lock_delays = 30; + + enum class RotationDirection : u8 { + Left, + Right, + }; + + enum class MoveDirection : u8 { + Left, + Right, + }; + + + static constexpr u8 num_preview_tetrominos = 6; + + bool m_is_accelerated_down_movement = false; + bool m_down_key_pressed = false; + bool m_allowed_to_hold = true; + bool m_is_in_lock_delay = false; + + u32 m_num_executed_lock_delays = 0; + u64 m_lock_delay_step_index; + +protected: + MinoStack m_mino_stack; + u32 m_level; + u32 m_lines_cleared = 0; + u64 m_score = 0; + + + std::optional m_active_tetromino; + std::optional m_ghost_tetromino; + std::optional m_tetromino_on_hold; + std::array, num_preview_tetrominos> m_preview_tetrominos{}; + +private: + Random m_random; + GameState m_game_state = GameState::Playing; + int m_sequence_index = 0; + std::array m_sequence_bags{ Bag{ m_random }, Bag{ m_random } }; + u8 m_tetrion_index; + u64 m_next_gravity_simulation_step_index; + std::optional> m_recording_writer; + +protected: + ServiceProvider* const m_service_provider; + +public: + SimulatedTetrion( + u8 tetrion_index, + Random::Seed random_seed, + u32 starting_level, + ServiceProvider* const service_provider, + std::optional> recording_writer + ); + + virtual ~SimulatedTetrion(); + + void update_step(SimulationStep simulation_step_index); + + // returns if the input event lead to a movement + bool handle_input_command(input::GameInputCommand command, SimulationStep simulation_step_index); + void spawn_next_tetromino(SimulationStep simulation_step_index); + void spawn_next_tetromino(helper::TetrominoType type, SimulationStep simulation_step_index); + bool rotate_tetromino_right(); + bool rotate_tetromino_left(); + bool move_tetromino_down(MovementType movement_type, SimulationStep simulation_step_index); + bool move_tetromino_left(); + bool move_tetromino_right(); + bool drop_tetromino(SimulationStep simulation_step_index); + void hold_tetromino(SimulationStep simulation_step_index); + + [[nodiscard]] u8 tetrion_index() const; + [[nodiscard]] u32 level() const; + [[nodiscard]] u64 score() const; + [[nodiscard]] u32 lines_cleared() const; + [[nodiscard]] const MinoStack& mino_stack() const; + [[nodiscard]] std::unique_ptr core_information() const; + + [[nodiscard]] bool is_game_over() const; + +private: + template + bool with_lock_delay(Callable movement) { + const auto result = movement(); + if (result and m_is_in_lock_delay) { + ++m_num_executed_lock_delays; + } + return result; + } + + bool rotate(RotationDirection rotation_direction); + bool move(MoveDirection move_direction); + [[nodiscard]] std::optional get_wall_kick_table() const; + void reset_lock_delay(SimulationStep simulation_step_index); + virtual void refresh_texts(); + void clear_fully_occupied_lines(); + void lock_active_tetromino(SimulationStep simulation_step_index); + [[nodiscard]] bool is_active_tetromino_position_valid() const; + [[nodiscard]] bool mino_can_move_down(GridPoint position) const; + [[nodiscard]] bool is_valid_mino_position(GridPoint position) const; + + void refresh_ghost_tetromino(); + void refresh_previews(); + helper::TetrominoType get_next_tetromino_type(); + + [[nodiscard]] bool is_tetromino_position_valid(const Tetromino& tetromino) const; + [[nodiscard]] bool tetromino_can_move_down(const Tetromino& tetromino) const; + + [[nodiscard]] u64 get_gravity_delay_frames() const; + + static u8 rotation_to_index(Rotation from, Rotation to); + + static constexpr auto wall_kick_data_jltsz = WallKickTable{ + // North -> East + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ -1, -1 }, + WallKickPoint{ 0, 2 }, + WallKickPoint{ -1, 2 }, + }, + // East -> North + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ 1, 1 }, + WallKickPoint{ 0, -2 }, + WallKickPoint{ 1, -2 }, + }, + // East -> South + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ 1, 1 }, + WallKickPoint{ 0, -2 }, + WallKickPoint{ 1, -2 }, + }, + // South -> East + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ -1, -1 }, + WallKickPoint{ 0, 2 }, + WallKickPoint{ -1, 2 }, + }, + // South -> West + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ 1, -1 }, + WallKickPoint{ 0, 2 }, + WallKickPoint{ 1, 2 }, + }, + // West -> South + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ -1, 1 }, + WallKickPoint{ 0, -2 }, + WallKickPoint{ -1, -2 }, + }, + // West -> North + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ -1, 1 }, + WallKickPoint{ 0, -2 }, + WallKickPoint{ -1, -2 }, + }, + // North -> West + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ 1, -1 }, + WallKickPoint{ 0, 2 }, + WallKickPoint{ 1, 2 }, + }, + }; + + static constexpr auto wall_kick_data_i = WallKickTable{ + // North -> East + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -2, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ -2, 1 }, + WallKickPoint{ 1, -2 }, + }, + // East -> North + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 2, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ 2, -1 }, + WallKickPoint{ -1, 2 }, + }, + // East -> South + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ 2, 0 }, + WallKickPoint{ -1, -2 }, + WallKickPoint{ 2, 1 }, + }, + // South -> East + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ -2, 0 }, + WallKickPoint{ 1, 2 }, + WallKickPoint{ -2, -1 }, + }, + // South -> West + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 2, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ 2, -1 }, + WallKickPoint{ -1, 2 }, + }, + // West -> South + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -2, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ -2, 1 }, + WallKickPoint{ 1, -2 }, + }, + // West -> North + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ 1, 0 }, + WallKickPoint{ -2, 0 }, + WallKickPoint{ 1, 2 }, + WallKickPoint{ -2, -1 }, + }, + // North -> West + std::array{ + WallKickPoint{ 0, 0 }, + WallKickPoint{ -1, 0 }, + WallKickPoint{ 2, 0 }, + WallKickPoint{ -1, -2 }, + WallKickPoint{ 2, 1 }, + }, + }; + + static constexpr auto frames_per_tile = std::array{ 48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, + 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1 }; + + friend struct TetrionSnapshot; +}; diff --git a/src/game/simulation.cpp b/src/game/simulation.cpp index be4919f2..f089ec63 100644 --- a/src/game/simulation.cpp +++ b/src/game/simulation.cpp @@ -7,6 +7,7 @@ #include "simulation.hpp" #include "ui/layout.hpp" +#include namespace { @@ -16,19 +17,17 @@ namespace { Simulation::Simulation( - ServiceProvider* const service_provider, const std::shared_ptr& input, const tetrion::StartingParameters& starting_parameters ) - : ui::Widget{ dummy_layout, ui::WidgetType::Component, false }, - m_input{ input } { + : m_input{ input } { spdlog::info("[simulation] starting level for tetrion {}", starting_parameters.starting_level); - m_tetrion = std::make_unique( - starting_parameters.tetrion_index, starting_parameters.seed, starting_parameters.starting_level, - service_provider, starting_parameters.recording_writer, dummy_layout, false + m_tetrion = std::make_unique( + starting_parameters.tetrion_index, starting_parameters.seed, starting_parameters.starting_level, nullptr, + starting_parameters.recording_writer ); m_tetrion->spawn_next_tetromino(0); @@ -47,8 +46,7 @@ Simulation::Simulation( } } -helper::expected -Simulation::get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path) { +helper::expected Simulation::get_replay_simulation(std::filesystem::path& recording_path) { //TODO(Totto): Support multiple tetrions to be in the recorded file and simulated @@ -83,7 +81,7 @@ Simulation::get_replay_simulation(ServiceProvider* service_provider, std::filesy const tetrion::StartingParameters starting_parameters = { 0, seed, starting_level, tetrion_index, std::nullopt }; - return Simulation{ service_provider, input, starting_parameters }; + return Simulation{ input, starting_parameters }; } @@ -98,15 +96,6 @@ void Simulation::update() { m_input->late_update(m_simulation_step_index); } -void Simulation::render(const ServiceProvider&) const { - utils::unreachable(); -} - -[[nodiscard]] helper::BoolWrapper> -Simulation::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { - return false; -} - [[nodiscard]] bool Simulation::is_game_finished() const { if (m_tetrion->is_game_over()) { return true; diff --git a/src/game/simulation.hpp b/src/game/simulation.hpp index e53784da..001c1e30 100644 --- a/src/game/simulation.hpp +++ b/src/game/simulation.hpp @@ -5,34 +5,27 @@ #include "core/helper/expected.hpp" #include "input/input_creator.hpp" #include "input/replay_input.hpp" -#include "tetrion.hpp" -#include "ui/widget.hpp" +#include "simulated_tetrion.hpp" -struct Simulation : public ui::Widget { +struct Simulation { private: using TetrionHeaders = std::vector; SimulationStep m_simulation_step_index{ 0 }; - std::unique_ptr m_tetrion; + std::unique_ptr m_tetrion; std::shared_ptr m_input; public: explicit Simulation( - ServiceProvider* service_provider, const std::shared_ptr& input, const tetrion::StartingParameters& starting_parameters ); - static helper::expected - get_replay_simulation(ServiceProvider* service_provider, std::filesystem::path& recording_path); + static helper::expected get_replay_simulation(std::filesystem::path& recording_path); - void update() override; + void update(); - void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult - - handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; [[nodiscard]] bool is_game_finished() const; }; diff --git a/src/game/tetrion.cpp b/src/game/tetrion.cpp index 8e2629b8..dab092e0 100644 --- a/src/game/tetrion.cpp +++ b/src/game/tetrion.cpp @@ -1,17 +1,13 @@ #include #include -#include -#include "helper/constants.hpp" -#include "helper/graphic_utils.hpp" -#include "helper/music_utils.hpp" + +#include "game/simulated_tetrion.hpp" #include "helper/platform.hpp" -#include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "tetrion.hpp" #include "ui/components/label.hpp" -#include #include #include @@ -26,13 +22,7 @@ Tetrion::Tetrion( bool is_top_level ) : ui::Widget{ layout , ui::WidgetType::Component ,is_top_level}, - m_lock_delay_step_index{ lock_delay }, - m_service_provider{ service_provider }, - m_recording_writer{ std::move(recording_writer) }, - m_random{ random_seed }, - m_level{ starting_level }, - m_tetrion_index{ tetrion_index }, - m_next_gravity_simulation_step_index{ get_gravity_delay_frames() }, + SimulatedTetrion{tetrion_index,random_seed,starting_level, service_provider,std::move(recording_writer)}, m_main_layout{ utils::size_t_identity<2>(), 0, @@ -49,7 +39,7 @@ Tetrion::Tetrion( 1, 3, ui::Direction::Vertical, ui::AbsolutMargin{ 0 }, std::pair{ 0.0, 0.1 } ); - + //TODO: auto* text_layout = get_text_layout(); constexpr auto text_size = utils::get_orientation() == utils::Orientation::Landscape @@ -75,34 +65,7 @@ Tetrion::Tetrion( refresh_texts(); } -void Tetrion::update_step(const SimulationStep simulation_step_index) { - switch (m_game_state) { - case GameState::Playing: { - if (simulation_step_index >= m_next_gravity_simulation_step_index) { - assert(simulation_step_index == m_next_gravity_simulation_step_index and "frame skipped?!"); - if (m_is_accelerated_down_movement and not m_down_key_pressed) { - assert(m_next_gravity_simulation_step_index >= get_gravity_delay_frames() and "overflow"); - m_next_gravity_simulation_step_index -= get_gravity_delay_frames(); - m_is_accelerated_down_movement = false; - } else { - if (move_tetromino_down( - m_is_accelerated_down_movement ? MovementType::Forced : MovementType::Gravity, - simulation_step_index - )) { - reset_lock_delay(simulation_step_index); - } - } - m_next_gravity_simulation_step_index += get_gravity_delay_frames(); - } - - refresh_ghost_tetromino(); - break; - } - case GameState::GameOver: - default: - break; - } -} +Tetrion::~Tetrion() = default; void Tetrion::render(const ServiceProvider& service_provider) const { @@ -116,13 +79,13 @@ void Tetrion::render(const ServiceProvider& service_provider) const { const shapes::UPoint& tile_size = grid->tile_size(); helper::graphics::render_minos(m_mino_stack, service_provider, original_scale, to_screen_coords, tile_size); - if (m_active_tetromino) { + if (m_active_tetromino.has_value()) { m_active_tetromino->render( service_provider, MinoTransparency::Solid, original_scale, to_screen_coords, tile_size, grid::grid_position ); } - if (m_ghost_tetromino) { + if (m_ghost_tetromino.has_value()) { m_ghost_tetromino->render( service_provider, MinoTransparency::Ghost, original_scale, to_screen_coords, tile_size, grid::grid_position @@ -153,187 +116,6 @@ Tetrion::handle_event(const std::shared_ptr& /*input_manage return false; } -bool Tetrion::handle_input_command(const input::GameInputCommand command, const SimulationStep simulation_step_index) { - switch (command) { - case input::GameInputCommand::RotateLeft: - if (rotate_tetromino_left()) { - reset_lock_delay(simulation_step_index); - return true; - } - return false; - case input::GameInputCommand::RotateRight: - if (rotate_tetromino_right()) { - reset_lock_delay(simulation_step_index); - return true; - } - return false; - case input::GameInputCommand::MoveLeft: - if (move_tetromino_left()) { - reset_lock_delay(simulation_step_index); - return true; - } - return false; - case input::GameInputCommand::MoveRight: - if (move_tetromino_right()) { - reset_lock_delay(simulation_step_index); - return true; - } - return false; - case input::GameInputCommand::MoveDown: - //TODO(Totto): use input_type() != InputType:Touch -#if not defined(__ANDROID__) - m_down_key_pressed = true; - m_is_accelerated_down_movement = true; - m_next_gravity_simulation_step_index = simulation_step_index + get_gravity_delay_frames(); -#endif - if (move_tetromino_down(MovementType::Forced, simulation_step_index)) { - reset_lock_delay(simulation_step_index); - return true; - } - return false; - case input::GameInputCommand::Drop: - m_lock_delay_step_index = simulation_step_index; // lock instantly - return drop_tetromino(simulation_step_index); - case input::GameInputCommand::ReleaseMoveDown: { - m_down_key_pressed = false; - return false; - } - case input::GameInputCommand::Hold: - if (m_allowed_to_hold) { - hold_tetromino(simulation_step_index); - reset_lock_delay(simulation_step_index); - m_allowed_to_hold = false; - return true; - } - return false; - default: - assert(false and "unknown GameInput"); - return false; - } -} - -void Tetrion::spawn_next_tetromino(const SimulationStep simulation_step_index) { - spawn_next_tetromino(get_next_tetromino_type(), simulation_step_index); -} - -void Tetrion::spawn_next_tetromino(const helper::TetrominoType type, const SimulationStep simulation_step_index) { - constexpr GridPoint spawn_position{ 3, 0 }; - m_active_tetromino = Tetromino{ spawn_position, type }; - refresh_previews(); - if (not is_active_tetromino_position_valid()) { - m_game_state = GameState::GameOver; - - auto current_pieces = m_active_tetromino.value().minos(); - - bool all_valid{ false }; - u8 move_up = 0; - while (not all_valid) { - all_valid = true; - for (auto& mino : current_pieces) { - if (mino.position().y != 0) { - mino.position() = mino.position() - GridPoint{ 0, 1 }; - if (not is_valid_mino_position(mino.position())) { - all_valid = false; - } - } - } - - ++move_up; - } - - for (const Mino& mino : m_active_tetromino->minos()) { - auto position = mino.position(); - if (mino.position().y >= move_up && move_up != 0) { - position -= GridPoint{ 0, move_up }; - m_mino_stack.set(position, mino.type()); - } - } - - spdlog::info("game over"); - if (m_recording_writer.has_value()) { - spdlog::info("writing snapshot"); - std::ignore = m_recording_writer.value()->add_snapshot(simulation_step_index, core_information()); - } - m_active_tetromino = {}; - m_ghost_tetromino = {}; - return; - } - - m_next_gravity_simulation_step_index = simulation_step_index + get_gravity_delay_frames(); - refresh_ghost_tetromino(); -} - -bool Tetrion::rotate_tetromino_right() { - return with_lock_delay([&]() { return rotate(RotationDirection::Right); }); -} - -bool Tetrion::rotate_tetromino_left() { - return with_lock_delay([&]() { return rotate(RotationDirection::Left); }); -} - -bool Tetrion::move_tetromino_down(MovementType movement_type, const SimulationStep simulation_step_index) { - if (not m_active_tetromino.has_value()) { - return false; - } - if (movement_type == MovementType::Forced) { - m_score += 4; - } - - - if (tetromino_can_move_down(m_active_tetromino.value())) { - m_active_tetromino->move_down(); - return true; - } - - m_is_in_lock_delay = true; - if ((m_is_in_lock_delay and m_num_executed_lock_delays >= num_lock_delays) - or simulation_step_index >= m_lock_delay_step_index) { - lock_active_tetromino(simulation_step_index); - reset_lock_delay(simulation_step_index); - } else { - m_next_gravity_simulation_step_index = simulation_step_index + 1; - } - return false; -} - -bool Tetrion::move_tetromino_left() { - return with_lock_delay([&]() { return move(MoveDirection::Left); }); -} - -bool Tetrion::move_tetromino_right() { - return with_lock_delay([&]() { return move(MoveDirection::Right); }); -} - -bool Tetrion::drop_tetromino(const SimulationStep simulation_step_index) { - if (not m_active_tetromino.has_value()) { - return false; - } - u64 num_movements = 0; - while (tetromino_can_move_down(m_active_tetromino.value())) { - ++num_movements; - m_active_tetromino->move_down(); - } - - m_score += static_cast(4) * num_movements; - lock_active_tetromino(simulation_step_index); - return num_movements > 0; -} - -void Tetrion::hold_tetromino(const SimulationStep simulation_step_index) { - if (not m_active_tetromino.has_value()) { - return; - } - - if (not m_tetromino_on_hold.has_value()) { - m_tetromino_on_hold = Tetromino{ grid::hold_tetromino_position, m_active_tetromino->type() }; - spawn_next_tetromino(simulation_step_index); - } else { - const auto on_hold = m_tetromino_on_hold->type(); - m_tetromino_on_hold = Tetromino{ grid::hold_tetromino_position, m_active_tetromino->type() }; - spawn_next_tetromino(on_hold, simulation_step_index); - } -} - [[nodiscard]] Grid* Tetrion::get_grid() { return m_main_layout.get(0); } @@ -350,39 +132,6 @@ void Tetrion::hold_tetromino(const SimulationStep simulation_step_index) { return m_main_layout.get(1); } -[[nodiscard]] u8 Tetrion::tetrion_index() const { - return m_tetrion_index; -} - -[[nodiscard]] u32 Tetrion::level() const { - return m_level; -} - -[[nodiscard]] u64 Tetrion::score() const { - return m_score; -} - -[[nodiscard]] u32 Tetrion::lines_cleared() const { - return m_lines_cleared; -} - -[[nodiscard]] const MinoStack& Tetrion::mino_stack() const { - return m_mino_stack; -} - -[[nodiscard]] std::unique_ptr Tetrion::core_information() const { - - return std::make_unique(m_tetrion_index, m_level, m_score, m_lines_cleared, m_mino_stack); -} - -[[nodiscard]] bool Tetrion::is_game_over() const { - return m_game_state == GameState::GameOver; -} - -void Tetrion::reset_lock_delay(const SimulationStep simulation_step_index) { - m_lock_delay_step_index = simulation_step_index + lock_delay; -} - void Tetrion::refresh_texts() { auto* text_layout = get_text_layout(); @@ -398,255 +147,3 @@ void Tetrion::refresh_texts() { stream << "lines: " << m_lines_cleared; text_layout->get(2)->set_text(*m_service_provider, stream.str()); } - -void Tetrion::clear_fully_occupied_lines() { - bool cleared = false; - const u32 lines_cleared_before = m_lines_cleared; - do { // NOLINT(cppcoreguidelines-avoid-do-while) - cleared = false; - for (u8 row = 0; row < grid::height_in_tiles; ++row) { - bool fully_occupied = true; - for (u8 column = 0; column < grid::width_in_tiles; ++column) { - if (m_mino_stack.is_empty(GridPoint{ column, row })) { - fully_occupied = false; - break; - } - } - - if (fully_occupied) { - ++m_lines_cleared; - const auto level = m_lines_cleared / 10; - if (level > m_level) { - m_level = level; - spdlog::info("new level: {}", m_level); - if (level == constants::music_change_level) { - m_service_provider->music_manager() - .load_and_play_music( - utils::get_assets_folder() / "music" - / utils::get_supported_music_extension("03. Game Theme (50 Left)") - ) - .and_then(utils::log_error); - } - } - m_mino_stack.clear_row_and_let_sink(static_cast(row)); - cleared = true; - break; - } - } - } while (cleared); - const u32 num_lines_cleared = m_lines_cleared - lines_cleared_before; - static constexpr std::array score_per_line_multiplier{ 0, 40, 100, 300, 1200 }; - m_score += static_cast(score_per_line_multiplier.at(num_lines_cleared)) * static_cast(m_level + 1); -} - -void Tetrion::lock_active_tetromino(const SimulationStep simulation_step_index) { - assert(m_active_tetromino.has_value()); - for (const Mino& mino : m_active_tetromino->minos()) { - m_mino_stack.set(mino.position(), mino.type()); - } - m_allowed_to_hold = true; - m_is_in_lock_delay = false; - m_num_executed_lock_delays = 0; - clear_fully_occupied_lines(); - spawn_next_tetromino(simulation_step_index); - refresh_texts(); - reset_lock_delay(simulation_step_index); - - // save a snapshot on every freeze (only in debug builds) -#if !defined(NDEBUG) - if (m_recording_writer) { - spdlog::debug("adding snapshot at step {}", simulation_step_index); - std::ignore = (*m_recording_writer)->add_snapshot(simulation_step_index, core_information()); - } -#endif -} - -bool Tetrion::is_active_tetromino_position_valid() const { - if (not m_active_tetromino) { - return false; - } - return is_tetromino_position_valid(m_active_tetromino.value()); -} - -bool Tetrion::is_valid_mino_position(GridPoint position) const { - return position.x < grid::width_in_tiles and position.y < grid::height_in_tiles and m_mino_stack.is_empty(position); -} - -bool Tetrion::mino_can_move_down(GridPoint position) const { - if (position.y == (grid::height_in_tiles - 1)) { - return false; - } - - return is_valid_mino_position(position + GridPoint{ 0, 1 }); -} - - -void Tetrion::refresh_ghost_tetromino() { - if (not m_active_tetromino.has_value()) { - m_ghost_tetromino = {}; - return; - } - m_ghost_tetromino = m_active_tetromino.value(); - while (tetromino_can_move_down(m_ghost_tetromino.value())) { - m_ghost_tetromino->move_down(); - } -} - -void Tetrion::refresh_previews() { - auto sequence_index = m_sequence_index; - auto bag_index = usize{ 0 }; - for (std::remove_cvref_t i = 0; i < num_preview_tetrominos; ++i) { - m_preview_tetrominos.at(static_cast(i)) = Tetromino{ - grid::preview_tetromino_position + shapes::UPoint{ 0, static_cast(grid::preview_padding * i) }, - m_sequence_bags.at(bag_index)[sequence_index] - }; - ++sequence_index; - static constexpr auto bag_size = decltype(m_sequence_bags)::value_type::size(); - if (sequence_index >= bag_size) { - assert(sequence_index == bag_size); - sequence_index = 0; - ++bag_index; - assert(bag_index < m_sequence_bags.size()); - } - } -} - -helper::TetrominoType Tetrion::get_next_tetromino_type() { - const helper::TetrominoType next_type = m_sequence_bags[0][m_sequence_index]; - m_sequence_index = (m_sequence_index + 1) % Bag::size(); - if (m_sequence_index == 0) { - // we had a wrap-around - m_sequence_bags[0] = m_sequence_bags[1]; - m_sequence_bags[1] = Bag{ m_random }; - } - return next_type; -} - -bool Tetrion::tetromino_can_move_down(const Tetromino& tetromino) const { - return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { - return not mino_can_move_down(mino.position()); - }); -} - - -[[nodiscard]] u64 Tetrion::get_gravity_delay_frames() const { - const auto frames = (m_level >= frames_per_tile.size() ? frames_per_tile.back() : frames_per_tile.at(m_level)); - if (m_is_accelerated_down_movement) { - return std::max(u64{ 1 }, static_cast(std::round(static_cast(frames) / 20.0))); - } - return frames; -} - -u8 Tetrion::rotation_to_index(const Rotation from, const Rotation rotation_to) { - if (from == Rotation::North and rotation_to == Rotation::East) { - return 0; - } - if (from == Rotation::East and rotation_to == Rotation::North) { - return 1; - } - if (from == Rotation::East and rotation_to == Rotation::South) { - return 2; - } - if (from == Rotation::South and rotation_to == Rotation::East) { - return 3; - } - if (from == Rotation::South and rotation_to == Rotation::West) { - return 4; - } - if (from == Rotation::West and rotation_to == Rotation::South) { - return 5; - } - if (from == Rotation::West and rotation_to == Rotation::North) { - return 6; - } - if (from == Rotation::North and rotation_to == Rotation::West) { - return 7; - } - UNREACHABLE(); -} - -bool Tetrion::is_tetromino_position_valid(const Tetromino& tetromino) const { - return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { - return not is_valid_mino_position(mino.position()); - }); -} - -bool Tetrion::rotate(Tetrion::RotationDirection rotation_direction) { - if (not m_active_tetromino) { - return false; - } - - const auto wall_kick_table = get_wall_kick_table(); - if (not wall_kick_table.has_value()) { - return false; - } - - const auto from_rotation = m_active_tetromino->rotation(); - const auto to_rotation = from_rotation + static_cast(rotation_direction == RotationDirection::Left ? -1 : 1); - const auto table_index = rotation_to_index(from_rotation, to_rotation); - - if (rotation_direction == RotationDirection::Left) { - m_active_tetromino->rotate_left(); - } else { - m_active_tetromino->rotate_right(); - } - - for (const auto& translation : (*wall_kick_table)->at(table_index)) { - m_active_tetromino->move(translation); - if (is_active_tetromino_position_valid()) { - return true; - } - m_active_tetromino->move(-translation); - } - - if (rotation_direction == RotationDirection::Left) { - m_active_tetromino->rotate_right(); - } else { - m_active_tetromino->rotate_left(); - } - return false; -} - -bool Tetrion::move(const Tetrion::MoveDirection move_direction) { - if (not m_active_tetromino) { - return false; - } - - switch (move_direction) { - case MoveDirection::Left: - m_active_tetromino->move_left(); - if (not is_active_tetromino_position_valid()) { - m_active_tetromino->move_right(); - return false; - } - return true; - case MoveDirection::Right: - m_active_tetromino->move_right(); - if (not is_active_tetromino_position_valid()) { - m_active_tetromino->move_left(); - return false; - } - return true; - } - - UNREACHABLE(); -} - -std::optional Tetrion::get_wall_kick_table() const { - assert(m_active_tetromino.has_value() and "no active tetromino"); - const auto type = m_active_tetromino->type(); // NOLINT(bugprone-unchecked-optional-access) - switch (type) { - case helper::TetrominoType::J: - case helper::TetrominoType::L: - case helper::TetrominoType::T: - case helper::TetrominoType::S: - case helper::TetrominoType::Z: - return &wall_kick_data_jltsz; - case helper::TetrominoType::I: - return &wall_kick_data_i; - case helper::TetrominoType::O: - return {}; - default: - UNREACHABLE(); - } -} diff --git a/src/game/tetrion.hpp b/src/game/tetrion.hpp index cbff250f..1cc32f08 100644 --- a/src/game/tetrion.hpp +++ b/src/game/tetrion.hpp @@ -5,77 +5,24 @@ #include #include -#include "bag.hpp" -#include "grid.hpp" #include "input/game_input.hpp" #include "manager/service_provider.hpp" -#include "tetromino.hpp" +#include "simulated_tetrion.hpp" #include "ui/layout.hpp" -#include "ui/layouts/grid_layout.hpp" #include "ui/layouts/tile_layout.hpp" #include "ui/widget.hpp" -#include namespace recorder { struct RecordingWriter; } -enum class GameState : u8 { - Playing, - GameOver, -}; - -enum class MovementType : u8 { - Gravity, - Forced, -}; -struct Tetrion final : public ui::Widget { +struct Tetrion final : public ui::Widget, SimulatedTetrion { private: - using WallKickPoint = shapes::AbstractPoint; - using WallKickTable = std::array, 8>; - using GridPoint = Mino::GridPoint; using ScreenCordsFunction = Mino::ScreenCordsFunction; + using GridPoint = Mino::GridPoint; - static constexpr SimulationStep lock_delay = 30; - static constexpr int num_lock_delays = 30; - - enum class RotationDirection : u8 { - Left, - Right, - }; - - enum class MoveDirection : u8 { - Left, - Right, - }; - - static constexpr u8 num_preview_tetrominos = 6; - - bool m_is_accelerated_down_movement = false; - bool m_down_key_pressed = false; - bool m_allowed_to_hold = true; - bool m_is_in_lock_delay = false; - - u32 m_num_executed_lock_delays = 0; - u64 m_lock_delay_step_index; - ServiceProvider* const m_service_provider; - std::optional> m_recording_writer; - MinoStack m_mino_stack; - Random m_random; - u32 m_level; - u32 m_lines_cleared = 0; - GameState m_game_state = GameState::Playing; - int m_sequence_index = 0; - u64 m_score = 0; - std::array m_sequence_bags{ Bag{ m_random }, Bag{ m_random } }; - std::optional m_active_tetromino; - std::optional m_ghost_tetromino; - std::optional m_tetromino_on_hold; - std::array, num_preview_tetrominos> m_preview_tetrominos{}; - u8 m_tetrion_index; - u64 m_next_gravity_simulation_step_index; ui::TileLayout m_main_layout; @@ -87,205 +34,18 @@ struct Tetrion final : public ui::Widget { std::optional> recording_writer, const ui::Layout& layout, bool is_top_level); - void update_step(SimulationStep simulation_step_index); + + ~Tetrion() override; + void render(const ServiceProvider& service_provider) const override; [[nodiscard]] Widget::EventHandleResult handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; - // returns if the input event lead to a movement - bool handle_input_command(input::GameInputCommand command, SimulationStep simulation_step_index); - void spawn_next_tetromino(SimulationStep simulation_step_index); - void spawn_next_tetromino(helper::TetrominoType type, SimulationStep simulation_step_index); - bool rotate_tetromino_right(); - bool rotate_tetromino_left(); - bool move_tetromino_down(MovementType movement_type, SimulationStep simulation_step_index); - bool move_tetromino_left(); - bool move_tetromino_right(); - bool drop_tetromino(SimulationStep simulation_step_index); - void hold_tetromino(SimulationStep simulation_step_index); - [[nodiscard]] Grid* get_grid(); [[nodiscard]] const Grid* get_grid() const; [[nodiscard]] ui::GridLayout* get_text_layout(); [[nodiscard]] const ui::GridLayout* get_text_layout() const; - [[nodiscard]] u8 tetrion_index() const; - [[nodiscard]] u32 level() const; - [[nodiscard]] u64 score() const; - [[nodiscard]] u32 lines_cleared() const; - [[nodiscard]] const MinoStack& mino_stack() const; - [[nodiscard]] std::unique_ptr core_information() const; - - [[nodiscard]] bool is_game_over() const; - private: - template - bool with_lock_delay(Callable movement) { - const auto result = movement(); - if (result and m_is_in_lock_delay) { - ++m_num_executed_lock_delays; - } - return result; - } - - bool rotate(RotationDirection rotation_direction); - bool move(MoveDirection move_direction); - [[nodiscard]] std::optional get_wall_kick_table() const; - void reset_lock_delay(SimulationStep simulation_step_index); - void refresh_texts(); - void clear_fully_occupied_lines(); - void lock_active_tetromino(SimulationStep simulation_step_index); - [[nodiscard]] bool is_active_tetromino_position_valid() const; - [[nodiscard]] bool mino_can_move_down(GridPoint position) const; - [[nodiscard]] bool is_valid_mino_position(GridPoint position) const; - - void refresh_ghost_tetromino(); - void refresh_previews(); - helper::TetrominoType get_next_tetromino_type(); - - [[nodiscard]] bool is_tetromino_position_valid(const Tetromino& tetromino) const; - [[nodiscard]] bool tetromino_can_move_down(const Tetromino& tetromino) const; - - [[nodiscard]] u64 get_gravity_delay_frames() const; - - static u8 rotation_to_index(Rotation from, Rotation to); - - static constexpr auto wall_kick_data_jltsz = WallKickTable{ - // North -> East - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ -1, -1 }, - WallKickPoint{ 0, 2 }, - WallKickPoint{ -1, 2 }, - }, - // East -> North - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ 1, 1 }, - WallKickPoint{ 0, -2 }, - WallKickPoint{ 1, -2 }, - }, - // East -> South - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ 1, 1 }, - WallKickPoint{ 0, -2 }, - WallKickPoint{ 1, -2 }, - }, - // South -> East - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ -1, -1 }, - WallKickPoint{ 0, 2 }, - WallKickPoint{ -1, 2 }, - }, - // South -> West - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ 1, -1 }, - WallKickPoint{ 0, 2 }, - WallKickPoint{ 1, 2 }, - }, - // West -> South - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ -1, 1 }, - WallKickPoint{ 0, -2 }, - WallKickPoint{ -1, -2 }, - }, - // West -> North - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ -1, 1 }, - WallKickPoint{ 0, -2 }, - WallKickPoint{ -1, -2 }, - }, - // North -> West - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ 1, -1 }, - WallKickPoint{ 0, 2 }, - WallKickPoint{ 1, 2 }, - }, - }; - - static constexpr auto wall_kick_data_i = WallKickTable{ - // North -> East - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -2, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ -2, 1 }, - WallKickPoint{ 1, -2 }, - }, - // East -> North - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 2, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ 2, -1 }, - WallKickPoint{ -1, 2 }, - }, - // East -> South - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ 2, 0 }, - WallKickPoint{ -1, -2 }, - WallKickPoint{ 2, 1 }, - }, - // South -> East - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ -2, 0 }, - WallKickPoint{ 1, 2 }, - WallKickPoint{ -2, -1 }, - }, - // South -> West - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 2, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ 2, -1 }, - WallKickPoint{ -1, 2 }, - }, - // West -> South - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -2, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ -2, 1 }, - WallKickPoint{ 1, -2 }, - }, - // West -> North - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ 1, 0 }, - WallKickPoint{ -2, 0 }, - WallKickPoint{ 1, 2 }, - WallKickPoint{ -2, -1 }, - }, - // North -> West - std::array{ - WallKickPoint{ 0, 0 }, - WallKickPoint{ -1, 0 }, - WallKickPoint{ 2, 0 }, - WallKickPoint{ -1, -2 }, - WallKickPoint{ 2, 1 }, - }, - }; - - static constexpr auto frames_per_tile = std::array{ 48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, - 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1 }; - - friend struct TetrionSnapshot; + void refresh_texts() override; }; diff --git a/src/input/game_input.hpp b/src/input/game_input.hpp index bda37d19..12b2ae8d 100644 --- a/src/input/game_input.hpp +++ b/src/input/game_input.hpp @@ -4,13 +4,12 @@ #include #include -#include "helper/clock_source.hpp" -#include "manager/event_listener.hpp" +#include #include #include -struct Tetrion; +struct SimulatedTetrion; namespace input { @@ -48,7 +47,7 @@ namespace input { std::unordered_map m_keys_hold; GameInputType m_input_type; - Tetrion* m_target_tetrion{}; + SimulatedTetrion* m_target_tetrion{}; OnEventCallback m_on_event_callback; protected: @@ -56,7 +55,7 @@ namespace input { void handle_event(InputEvent event, SimulationStep simulation_step_index); - [[nodiscard]] const Tetrion* target_tetrion() const { + [[nodiscard]] const SimulatedTetrion* target_tetrion() const { return m_target_tetrion; } @@ -90,7 +89,7 @@ namespace input { return m_input_type != GameInputType::Touch; } - void set_target_tetrion(Tetrion* target_tetrion) { + void set_target_tetrion(SimulatedTetrion* target_tetrion) { m_target_tetrion = target_tetrion; } diff --git a/tests/files/test_rec_valid.rec b/tests/files/test_rec_valid.rec new file mode 100644 index 0000000000000000000000000000000000000000..14e9ae3d1ba50522f8483052202bfd34e693275c GIT binary patch literal 11726 zcmdU#d9W456~;*z+;NQ{vLjfy3TN8^JfhMdp0n#VQ9Tq4H!3K)bMNT>+qe)^%kWL$7NZ?_LGfN zE>hW8ly(H*WmAz|-e%TvG%$B{M5Rp4m5@r!G^lg&6fEbj$MMQMAn3yYj4-nDs zvqUPM&i}Q4T$7y#`$;PfOFE2n21l&R-t$RW3MA(z|BOc-%5n!Mk0D>?kC)| z>|H@b?hlB_b%@+yk~@h=^{=r*`^Pm^G4@&Nbrc#LaTvy7*_*-+QOQgqD)|KwmE1{$ zp}!)+#Rnw!8Oi-0k?OMiUn|Hp6+eeM56LCZi##v(%HF5!4NBYDivu4hJ(~!n9f?rd znFyuBh^X}%Vs+WOo`@cPMnn&@iRfW25k1@^x%Wx#<3!BQS|aB1JtF3E4-s?esYI_% zu9AT%Uo@NS)oVY8GizyIR4GtVpyEKssKu~1C^?geTG|s)O9vwKb(Vx~lF&n{%wN z9u`%9B*L#1M40p(5!U^c2s_@Egm)ymoyfXYr0Q}_Est_treI9bghgX)jk_rTHSQ6G zIWEA^T}?!dMIxrL84)$MBVtCE5OLisCE~hymWZ#dl-!qy==XIZI$I~v^+bGalO${* zVzjN2`;jDkN~ELivbV;%rsmGk%6$b(bQs$uTE}*UJGgrA5~-XEs$_IzZG&fF$(YCl zu^&Xxu1(Sz{ELVX+?)uNrxB6chKM>l5plYFO%i%Z!gnM(P@;n*S|`!rM9kI*BCEfe z>xr%2HMKZu)n`h6Ci1h9pR-OhaA-I&m5AzQi?%sLc+3Xr^lGH`ahB|0|}Ve$J!)U=HV_4|nE!OtWzVNhElzSe<=uU$w)ZcoX*N}@lK z=p#g|RF4y}QavrXD<#(hrN?s+oo7EZSy;*4lbL=k5btz|?w-CYtc zm4tpox1OXlx+ZM3lFwml%tT=pM;zwtZt@Ba0lh2*zR^*?-1(|UD5nS#|XwMzVeM=HH5~+P{ zO1o=vYxj|B&lJNJ`vS@m57qQMMR+vS;r~7 zA=`3XlUs3)ZXMa8&X#lzM}!@AA1XpkR}rCpxab^B#CbK9i1X?eiO!SgQX*^mMye*) z)P|SAv8zc}D^sm>UYR^9d6be+-%28;AXh4^{0I{a%wq67)MntvNElO@1B5QpxRjX?%pRiiPya@{?Q?Ofc zTws)ny3Zw|mJ5lfyBiU8_aLI~UXt5aa{Ec{Ajus}#QEMp#QA=m3-E3s9 z-5mDfm6JSKY;ktr%EsA&D;ti%t_+@EO2&_Z0Ec~sT})wcZz&Pc$B3}(X(H-bNkr{0 z6IsvWtjF#-T$6jo){!!i(y=nJs+6JAWu&D;Ss0m2WN*=a?0YeWU&Dc*?;k|y+ayZ1 zOLVtH_YtYD9XCUE3*nmF4k1yNn3`yXm)Y2Q2{o170-jlvvhUFjuv@6fHcjRWYGNXM zaR}ut7=>}?6VXF=BF@V(MD#F@h_6i~qBDo+VJ@TSVIC1ZER?S;CL;Gq$z372&lBm$ zz?37d$?ZoD-;rz)=bpVC;#4eqf9DWz=MB;DCK0u5AVTC0BC6O$q_SI6%3PD%d3-Tt zxq_D~dbyI9i?GXUA<;`E+FznWi1ezK&qnr-YjV4)W9OAF z`U*!04^yam1SP?@1|oc$M1*e+k$VH9@a-nayE^(co35#*GSB!$sB#}WoV!JOZA`B~>FYIp=B3?Cp8dl@5ayvY z;H?VIW{HS4BSQbFM7VVZ5zclZ!mW#maH~$D!zDUKqEjUL7!hmxQ$#$`yhwzGSBV&S zEfE91MTD}CBzKQQ<3i#ldT2>RZkfngn4Ovh*Hk`XvylE302eT&Vn!~MTE0$%8$F3| zqZbhs))Czb#|rHuq-#333ry{ruo=NN1UFhd`{PE7P1M2LSuXaH7ps|M?}X<#9F>Ox zjq9>qPl1i7t}pVxn7x_$+MyxTab+ zW5=-`ub}*b@`}o@ylJ3F)V5rz_=8ljiiqoEH4)dzMj|G28xfQFf#mL#TzhiZ4?GKr znC&Ve-5Hc}$2Fakd=IavWRHBTXFKhV&WE}XDjY>z;}HeJsZ)sXq!kfHoJE8^4&g~> zN$5&M^qWL@(whiR1{2Zm2qHQgMZ^%3i0EO4M1L;PSw!@E8xj59A-M}A_kPKJSaSa+ z(N`q8o(N@Ih)}joqB|to7Qd6)4>XjC&~TnaFOcXDiKy)wB5Io?(J2!B7ZG#uE)nbP zheRlABtqFU5I&6ISamuQNCb*lX`& zA$G-y6UC^%84(7ZMuZiui14aLa?g>3^CfzbL@y!It92=_TvL}XfmgaZFgWtbZ$iCU z6nq;%gjrV+;m~Lz&b%o^7(0!KOY8bbzv!p`MNjgp3tns)`v&UQ90;{ML_{sWC!&@WL=^C6B2@oH za^H}IHznacB6`?G#Ay48u%Hd~W3??4vD$VdLPJ*~2EK%dfiIJ2Ux^MNVzlo|!VpOq zPo$rZru=kG2mc*L^782m`I#+ZqiC=j%tm-OI+=sQ+nb1}YYq{0%_pL+yNIY@ArXE( zLWEy05YfY{lDm$`x;Ccja!m*S9mY|rOXcnFqxh8++uE}C4h5jb4-eH+XOpsn%utsSnJYFlxd=LQ)QZB9zPL{s##(T z=W$R7GLE@-X^koyY8<)HKUA+<@a$Z>Cbzpjo}Q-ls?b*%UuS%i z!KGFb?WZ29eR|4k*W~s$CxdonjK+cg49@G8Qb(IZ>$o{phimF|g3p=s9Hi?|N^2Z} pHUah_HBwXO&#uWex&6Y)HaF>-w5N=xw6Bb>k~?^McD}5Y{2% - -#if defined(__GNUC__) || defined(__clang__) -#define FUNCT __FUNCTION__ -#elif -#define FUNCT "" -#endif - -DummyServiceProvider::DummyServiceProvider() = default; - -// implementation of ServiceProvider -[[nodiscard]] EventDispatcher& DummyServiceProvider::event_dispatcher() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const EventDispatcher& DummyServiceProvider::event_dispatcher() const { - ASSERT_FAIL(FUNCT); -} - -FontManager& DummyServiceProvider::font_manager() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const FontManager& DummyServiceProvider::font_manager() const { - ASSERT_FAIL(FUNCT); -} - -CommandLineArguments& DummyServiceProvider::command_line_arguments() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const CommandLineArguments& DummyServiceProvider::command_line_arguments() const { - ASSERT_FAIL(FUNCT); -} - -SettingsManager& DummyServiceProvider::settings_manager() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const SettingsManager& DummyServiceProvider::settings_manager() const { - ASSERT_FAIL(FUNCT); -} - -MusicManager& DummyServiceProvider::music_manager() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const MusicManager& DummyServiceProvider::music_manager() const { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const Renderer& DummyServiceProvider::renderer() const { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const Window& DummyServiceProvider::window() const { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] Window& DummyServiceProvider::window() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] input::InputManager& DummyServiceProvider::input_manager() { - ASSERT_FAIL(FUNCT); -} - -[[nodiscard]] const input::InputManager& DummyServiceProvider::input_manager() const { - ASSERT_FAIL(FUNCT); -} - - -#if defined(_HAVE_DISCORD_SDK) - -[[nodiscard]] std::optional& DummyServiceProvider::discord_instance() { - ASSERT_FAIL(FUNCT); -} -[[nodiscard]] const std::optional& DummyServiceProvider::discord_instance() const { - ASSERT_FAIL(FUNCT); -} - - -#endif diff --git a/tests/graphics/helper/service_provider.hpp b/tests/graphics/helper/service_provider.hpp deleted file mode 100644 index 1afb168b..00000000 --- a/tests/graphics/helper/service_provider.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include "manager/event_dispatcher.hpp" -#include "manager/music_manager.hpp" -#include "manager/service_provider.hpp" -#include "manager/settings_manager.hpp" - -struct DummyServiceProvider final : public ServiceProvider { -public: - DummyServiceProvider(); - - // implementation of ServiceProvider - [[nodiscard]] EventDispatcher& event_dispatcher() override; - - [[nodiscard]] const EventDispatcher& event_dispatcher() const override; - - FontManager& font_manager() override; - - [[nodiscard]] const FontManager& font_manager() const override; - - CommandLineArguments& command_line_arguments() override; - - [[nodiscard]] const CommandLineArguments& command_line_arguments() const override; - - SettingsManager& settings_manager() override; - - [[nodiscard]] const SettingsManager& settings_manager() const override; - - MusicManager& music_manager() override; - - [[nodiscard]] const MusicManager& music_manager() const override; - - [[nodiscard]] const Renderer& renderer() const override; - - [[nodiscard]] const Window& window() const override; - - [[nodiscard]] Window& window() override; - - [[nodiscard]] input::InputManager& input_manager() override; - - [[nodiscard]] const input::InputManager& input_manager() const override; - - -#if defined(_HAVE_DISCORD_SDK) - - [[nodiscard]] std::optional& discord_instance() override; - [[nodiscard]] const std::optional& discord_instance() const override; - - -#endif -}; diff --git a/tests/graphics/meson.build b/tests/graphics/meson.build index 1f978bf3..dcb37735 100644 --- a/tests/graphics/meson.build +++ b/tests/graphics/meson.build @@ -1,6 +1,4 @@ graphics_test_src += files( 'sdl_key.cpp', - 'helper/service_provider.cpp', - 'helper/service_provider.hpp', 'tetrion_simulation.cpp', ) diff --git a/tests/graphics/tetrion_simulation.cpp b/tests/graphics/tetrion_simulation.cpp index 530d30a6..e57b0cf5 100644 --- a/tests/graphics/tetrion_simulation.cpp +++ b/tests/graphics/tetrion_simulation.cpp @@ -2,7 +2,6 @@ #include "game/simulation.hpp" #include "utils/helper.hpp" -#include "./helper/service_provider.hpp" #include #include @@ -10,11 +9,24 @@ TEST(Simulation, InvalidFilePath) { - auto service_provider = std::make_shared(); - std::filesystem::path path = "__INVALID_PATH"; - auto maybe_simulation = Simulation::get_replay_simulation(service_provider.get(), path); + auto maybe_simulation = Simulation::get_replay_simulation(path); + + ASSERT_THAT(maybe_simulation, ExpectedHasError()) + << "Path was: " << path << "\nError: " << maybe_simulation.error(); + ASSERT_THAT( + maybe_simulation.error(), + ("an error occurred while reading recording: unable to load recording from file \"" + path.string() + "\"") + ); +} + +TEST(Simulation, ValidRecordingsFile) { + + std::filesystem::path path = "./test_rec_valid.rec"; + + auto maybe_simulation = Simulation::get_replay_simulation(path); - ASSERT_THAT(maybe_simulation, ExpectedHasError()) << "Path was: " << path << "Error: " << maybe_simulation.error(); + ASSERT_THAT(maybe_simulation, ExpectedHasValue()) + << "Path was: " << path << "\nError: " << maybe_simulation.error(); } diff --git a/tests/utils/helper.hpp b/tests/utils/helper.hpp index 0ef51d10..e8d39b68 100644 --- a/tests/utils/helper.hpp +++ b/tests/utils/helper.hpp @@ -25,8 +25,8 @@ MATCHER(OptionalHasNoValue, "optional has no value") { } -#define ASSERT_FAIL(x) \ - do { \ - std::cerr << "assertion fail in" << __FILE__ << ":" << __LINE__ << ": " << (x); \ - utils::unreachable(); \ +#define ASSERT_FAIL(x) \ + do { \ + std::cerr << "assertion fail in " << __FILE__ << ":" << __LINE__ << ": " << (x) << "\n"; \ + utils::unreachable(); \ } while (false) From 44baab2fe7df71b47b1b23442f1409fae6b7cf55 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 8 Jul 2024 22:57:03 +0200 Subject: [PATCH 3/3] fix clang-tidy errors --- src/game/simulated_tetrion.cpp | 6 ++++- src/game/simulated_tetrion.hpp | 40 +++++++++++++++++++++++----------- src/game/simulation.cpp | 7 ------ src/game/tetrion.cpp | 1 - src/game/tetrion.hpp | 6 +++++ src/input/guid.hpp | 4 ++-- tests/utils/helper.hpp | 2 +- 7 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/game/simulated_tetrion.cpp b/src/game/simulated_tetrion.cpp index 57a90617..1fbc7e72 100644 --- a/src/game/simulated_tetrion.cpp +++ b/src/game/simulated_tetrion.cpp @@ -29,6 +29,10 @@ SimulatedTetrion::SimulatedTetrion( SimulatedTetrion::~SimulatedTetrion() = default; +SimulatedTetrion::SimulatedTetrion(const SimulatedTetrion& other) = default; + +SimulatedTetrion::SimulatedTetrion(SimulatedTetrion&& other) noexcept = default; + void SimulatedTetrion::update_step(const SimulationStep simulation_step_index) { switch (m_game_state) { case GameState::Playing: { @@ -324,7 +328,7 @@ void SimulatedTetrion::clear_fully_occupied_lines() { void SimulatedTetrion::lock_active_tetromino(const SimulationStep simulation_step_index) { assert(m_active_tetromino.has_value()); - for (const Mino& mino : m_active_tetromino->minos()) { + for (const Mino& mino : m_active_tetromino->minos()) { // NOLINT(bugprone-unchecked-optional-access) m_mino_stack.set(mino.position(), mino.type()); } m_allowed_to_hold = true; diff --git a/src/game/simulated_tetrion.hpp b/src/game/simulated_tetrion.hpp index 0a4bbf45..5cd6ef98 100644 --- a/src/game/simulated_tetrion.hpp +++ b/src/game/simulated_tetrion.hpp @@ -58,16 +58,23 @@ struct SimulatedTetrion { u64 m_lock_delay_step_index; protected: - MinoStack m_mino_stack; - u32 m_level; - u32 m_lines_cleared = 0; - u64 m_score = 0; - - - std::optional m_active_tetromino; - std::optional m_ghost_tetromino; - std::optional m_tetromino_on_hold; - std::array, num_preview_tetrominos> m_preview_tetrominos{}; + MinoStack + m_mino_stack; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + u32 m_level; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + u32 m_lines_cleared = // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + 0; + u64 m_score = // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + 0; + + + std::optional + m_active_tetromino; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + std::optional + m_ghost_tetromino; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + std::optional + m_tetromino_on_hold; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) + std::array, num_preview_tetrominos> + m_preview_tetrominos{}; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) private: Random m_random; @@ -79,19 +86,26 @@ struct SimulatedTetrion { std::optional> m_recording_writer; protected: - ServiceProvider* const m_service_provider; + ServiceProvider* + m_service_provider; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-non-private-member-variables-in-classes) public: SimulatedTetrion( u8 tetrion_index, Random::Seed random_seed, u32 starting_level, - ServiceProvider* const service_provider, + ServiceProvider* service_provider, std::optional> recording_writer ); virtual ~SimulatedTetrion(); + SimulatedTetrion(const SimulatedTetrion& other); + SimulatedTetrion& operator=(const SimulatedTetrion& other) = delete; + + SimulatedTetrion(SimulatedTetrion&& other) noexcept; + SimulatedTetrion& operator=(SimulatedTetrion&& other) noexcept = delete; + void update_step(SimulationStep simulation_step_index); // returns if the input event lead to a movement @@ -145,7 +159,7 @@ struct SimulatedTetrion { [[nodiscard]] u64 get_gravity_delay_frames() const; - static u8 rotation_to_index(Rotation from, Rotation to); + static u8 rotation_to_index(Rotation from, Rotation rotation_to); static constexpr auto wall_kick_data_jltsz = WallKickTable{ // North -> East diff --git a/src/game/simulation.cpp b/src/game/simulation.cpp index f089ec63..548424a2 100644 --- a/src/game/simulation.cpp +++ b/src/game/simulation.cpp @@ -5,16 +5,9 @@ #include "core/helper/expected.hpp" #include "input/replay_input.hpp" #include "simulation.hpp" -#include "ui/layout.hpp" #include -namespace { - - const auto dummy_layout = ui::AbsolutLayout{ 0, 0, 0, 0 }; - -} - Simulation::Simulation( const std::shared_ptr& input, diff --git a/src/game/tetrion.cpp b/src/game/tetrion.cpp index dab092e0..177a775c 100644 --- a/src/game/tetrion.cpp +++ b/src/game/tetrion.cpp @@ -39,7 +39,6 @@ Tetrion::Tetrion( 1, 3, ui::Direction::Vertical, ui::AbsolutMargin{ 0 }, std::pair{ 0.0, 0.1 } ); - //TODO: auto* text_layout = get_text_layout(); constexpr auto text_size = utils::get_orientation() == utils::Orientation::Landscape diff --git a/src/game/tetrion.hpp b/src/game/tetrion.hpp index 1cc32f08..bd4f37cb 100644 --- a/src/game/tetrion.hpp +++ b/src/game/tetrion.hpp @@ -37,6 +37,12 @@ struct Tetrion final : public ui::Widget, SimulatedTetrion { ~Tetrion() override; + Tetrion(const Tetrion& other) = delete; + Tetrion& operator=(const Tetrion& other) = delete; + + Tetrion(Tetrion&& other) noexcept = delete; + Tetrion& operator=(Tetrion&& other) noexcept = delete; + void render(const ServiceProvider& service_provider) const override; [[nodiscard]] Widget::EventHandleResult handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; diff --git a/src/input/guid.hpp b/src/input/guid.hpp index 5b83eab3..4d853265 100644 --- a/src/input/guid.hpp +++ b/src/input/guid.hpp @@ -115,8 +115,8 @@ namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces) const auto temp_result = single_hex_color_value( - input + offset - ); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + input + offset //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ); PROPAGATE(temp_result, sdl::GUID, std::string); diff --git a/tests/utils/helper.hpp b/tests/utils/helper.hpp index e8d39b68..73645078 100644 --- a/tests/utils/helper.hpp +++ b/tests/utils/helper.hpp @@ -25,7 +25,7 @@ MATCHER(OptionalHasNoValue, "optional has no value") { } -#define ASSERT_FAIL(x) \ +#define ASSERT_FAIL(x) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ do { \ std::cerr << "assertion fail in " << __FILE__ << ":" << __LINE__ << ": " << (x) << "\n"; \ utils::unreachable(); \