diff --git a/CHANGELOG.md b/CHANGELOG.md index bd39095..9f94e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ # Changelog -#[Unreleased](https://github.com/bwhitchurch/CubeTimer/compare/0.1.0...HEAD) +#[Unreleased](https://github.com/bwhitchurch/CubeTimer/compare/0.1.1...HEAD) + +## New Features + +- feat: reflective enums through preprocessor shenanigans. [`7d34190`](https://github.com/bwhitchurch/CubeTimer/commit/7d34190747b18cabc922cf4bad7c1ce38c7e5b6a) + +## Fixes + +- Fix: dummy test in scramblerTest to ensure valid program [`b4b1ec5`](https://github.com/bwhitchurch/CubeTimer/commit/b4b1ec58fc2620da520f6bc95eb0f5b67333afbd) +- Fix: check for constexpr string support [`c1c9cda`](https://github.com/bwhitchurch/CubeTimer/commit/c1c9cdaa66722f1f56c57ed2d08b5dad9bc5418f) +- Fix: checking constexpr string support (again) [`2c1a04f`](https://github.com/bwhitchurch/CubeTimer/commit/2c1a04f1fab6c3f10914c418750da9827369b543) +- Fix: remove findpackage from subdirectory cmakelists [`e388169`](https://github.com/bwhitchurch/CubeTimer/commit/e388169d2ee39cc8a97ad0b35b9fd68975d36475) +- fix: constexpr string check changed to ifdef [`78bdc18`](https://github.com/bwhitchurch/CubeTimer/commit/78bdc184ab927b76b3fa0bf987f7267765917c52) + +## Miscellaneous + +- Change: cube header makes use of enum utils. [`9b0f1a6`](https://github.com/bwhitchurch/CubeTimer/commit/9b0f1a644d1720c5d9cd796d8867fd476183e103) +- Enum things [`8320a5e`](https://github.com/bwhitchurch/CubeTimer/commit/8320a5ee7d8ffa4e60959c8de8ed9dcadecc7703) +- Change: Finished reworking scrambler to use new enum and cubeMove classes [`e0edfb1`](https://github.com/bwhitchurch/CubeTimer/commit/e0edfb1b2f518fd5ef3a43b6c988eeb1a40dc062) +- enum.hpp is dead [`f268ef2`](https://github.com/bwhitchurch/CubeTimer/commit/f268ef27d5541603acb786a228f410548e4e52f0) +- Fix naming in ppUtils [`ccfba25`](https://github.com/bwhitchurch/CubeTimer/commit/ccfba2500685859fcfb39d08341ff165bf93febc) +#[0.1.1](https://github.com/bwhitchurch/CubeTimer/compare/0.1.0...0.1.1) ## New Features @@ -8,7 +29,7 @@ ## Minor Changes -- dev: some basic ci [`8573379`](https://github.com/bwhitchurch/CubeTimer/commit/8573379353ac600f34d8fe38a4d46ab47479b7aa) +- dev: some basic ci [`adde650`](https://github.com/bwhitchurch/CubeTimer/commit/adde6502800a7ca5425df555e5ffed572a7dab2f) ## Miscellaneous diff --git a/CMakeLists.txt b/CMakeLists.txt index 464ad34..b5034d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,9 @@ dynamic_project_options( --suppress=unmatchedSuppression --suppress=passedByValue --suppress=syntaxError + --suppress=duplicateExpression + --suppress=knownConditionTrueFalse + --suppress=preprocessorErrorDirective --inconclusive) if(ENABLE_INCLUDE_WHAT_YOU_USE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89f5b80..36e9de4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,11 +1,13 @@ -add_library(scrambler scrambler.cpp) +add_subdirectory(ppUtils) +add_subdirectory(EnumUtils) +add_library(scrambler scrambler.cpp cube.cpp) target_include_directories(scrambler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(scrambler PROPERTIES PUBLIC_HEADER - "scrambler.hpp;enum.hpp") + "scrambler.hpp;cube.cpp") target_link_libraries( scrambler - PUBLIC project_options project_warnings - PRIVATE fmt::fmt spdlog::spdlog) + PRIVATE project_options project_warnings + PUBLIC fmt::fmt spdlog::spdlog EnumUtils) target_include_directories( scrambler PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") diff --git a/src/EnumUtils/CMakeLists.txt b/src/EnumUtils/CMakeLists.txt new file mode 100644 index 0000000..5793631 --- /dev/null +++ b/src/EnumUtils/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(EnumUtils enumUtils.cpp) +target_include_directories(EnumUtils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries( + EnumUtils + PUBLIC ppUtils + PRIVATE project_options project_warnings) + +if(ENABLE_TESTING) + add_subdirectory(test) +endif() diff --git a/src/EnumUtils/enumUtils.hpp b/src/EnumUtils/enumUtils.hpp new file mode 100644 index 0000000..ce37c5f --- /dev/null +++ b/src/EnumUtils/enumUtils.hpp @@ -0,0 +1,118 @@ +#ifndef ENUM_UTILS_HPP +#define ENUM_UTILS_HPP +#include "ppUtils.hpp" + +#include +#include +#include +#include +using namespace std::string_view_literals; +#if __cpp_lib_constexpr_string >= 201907L + #define CUBE_TIMER_CONSTEXPR constexpr +#else + #define CUBE_TIMER_CONSTEXPR +#endif + +template < typename Type > struct IgnoreEquals { + Type value; + + template < typename Any > + constexpr IgnoreEquals& operator=([[maybe_unused]] Any t_val) { + return *this; + } + + constexpr explicit IgnoreEquals() : value() {} + + constexpr explicit IgnoreEquals(Type t_value) : value(t_value) {} + + // NOLINTNEXTLINE(hicpp-explicit-conversions) + constexpr operator Type() const { return value; } + + constexpr IgnoreEquals operator*(const Type& t_value) { + return IgnoreEquals(t_value); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define MAKE_ENUM(enum_name, enum_type, ...) \ + enum enum_name : enum_type { __VA_ARGS__ }; + +#define NAME_ENTRY(arg) #arg##sv.substr(0, #arg##sv.find_first_of('=') - 1) + +#define MAKE_NAMES(array_prefix, enum_name, ...) \ + constexpr static std:: \ + array< std::string_view, MM_COUNT_ARGS(__VA_ARGS__) > \ + array_prefix##_names{{MM_TRANSFORM(NAME_ENTRY, __VA_ARGS__)}}; + +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define VALUE_ENTRY(enum_name, arg) IgnoreEquals< enum_name >{} * arg +#define MAKE_VALUES(array_prefix, enum_name, ...) \ + constexpr static std::array< enum_name, MM_COUNT_ARGS(__VA_ARGS__) > \ + array_prefix##_values{ \ + {MM_TRANSFORM_1(VALUE_ENTRY, enum_name, __VA_ARGS__)}}; + +#define BETTER_ENUM(enum_name, type, ...) \ + class enum_name { \ + public: \ + MAKE_ENUM(m_enumeration, type, __VA_ARGS__) \ + private: \ + MAKE_VALUES(m, m_enumeration, __VA_ARGS__) \ + MAKE_NAMES(m, m_enumeration, __VA_ARGS__) \ + constexpr static std::string_view m_name{#enum_name##sv}; \ + m_enumeration m_value; \ + \ + public: \ + using value_container = decltype(m_values); \ + using value_iterator = typename value_container::iterator; \ + using value_type = typename value_container::value_type; \ + using name_container = decltype(m_names); \ + using name_iterator = typename name_container::iterator; \ + using name_type = typename name_container::value_type; \ + using underlying = type; \ + \ + /* NOLINTNEXTLINE(hicpp-explicit-conversions)*/ \ + constexpr enum_name(const m_enumeration& t_val) : m_value(t_val) {} \ + constexpr operator underlying() const { return m_value; } \ + \ + friend constexpr enum_name operator+(const m_enumeration& t_val); \ + \ + constexpr static value_container& values() { return m_values; } \ + constexpr static name_container& names() { return m_names; } \ + constexpr static size_t size() { return MM_COUNT_ARGS(__VA_ARGS__); } \ + constexpr static std::string_view name() { return m_name; } \ + constexpr static const value_type& from_string(std::string_view t_name \ + ) { \ + const auto* found_ptr = std::find_if( \ + m_names.begin(), \ + m_names.end(), \ + [t_name](auto t_iter_name) { return t_iter_name == t_name; } \ + ); \ + auto start_val = m_values.begin(); \ + std::advance( \ + start_val, std::distance(m_names.begin(), found_ptr) \ + ); \ + return *start_val; \ + } \ + constexpr name_type to_string() { \ + const auto* found_ptr = std::find_if( \ + m_values.begin(), \ + m_values.end(), \ + [this](auto t_iter_val) { return t_iter_val == m_value; } \ + ); \ + auto start_name = m_names.begin(); \ + std::advance( \ + start_name, std::distance(m_values.begin(), found_ptr) \ + ); \ + return *start_name; \ + } \ + }; \ + constexpr enum_name operator+(const enum_name::m_enumeration& t_val) { \ + return enum_name(t_val); \ + } \ + template <> struct std::hash< enum_name > { \ + size_t operator()(const enum_name& t_enum) const noexcept { \ + return std::hash< type >{}(t_enum); \ + } \ + }; +// NOLINTEND(cppcoreguidelines-macro-usage) +#endif diff --git a/src/EnumUtils/test/CMakeLists.txt b/src/EnumUtils/test/CMakeLists.txt new file mode 100644 index 0000000..a6cb5f9 --- /dev/null +++ b/src/EnumUtils/test/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(enumUtilsTest enumUtilsTest.cpp) +target_link_libraries(enumUtilsTest PUBLIC Catch2::Catch2WithMain EnumUtils + fmt::fmt) +target_link_libraries(enumUtilsTest PRIVATE project_options project_warnings) +set_target_properties(enumUtilsTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_TEST_OUTPUT_DIRECTORY}") + +include(Catch) +catch_discover_tests( + enumUtilsTest + TEST_PREFIX + "libEnumUtils." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "libEnumUtils." + OUTPUT_SUFFIX + .xml) diff --git a/src/EnumUtils/test/enumUtilsTest.cpp b/src/EnumUtils/test/enumUtilsTest.cpp new file mode 100644 index 0000000..6a3c15d --- /dev/null +++ b/src/EnumUtils/test/enumUtilsTest.cpp @@ -0,0 +1,28 @@ +#include "enumUtils.hpp" + +#include +#include + +#include + +TEST_CASE("ENUM MACROS") { + MAKE_ENUM(Color, int, RED, GREEN, BLUE = 3, ORANGE) + + MAKE_NAMES(Color, Color, RED, GREEN, BLUE = 3, ORANGE) + fmt::print("{}\n", Color_names); + CHECK(Color_names[0] == "RED"); + + MAKE_VALUES(Color, Color, RED, GREEN, BLUE = 3, ORANGE) + CHECK(Color_values[0] == RED); +} + +BETTER_ENUM(Color, int, RED, GREEN, ORANGE = 7, YELLOW) + +TEST_CASE("BETTER_ENUM") { + CHECK(Color::values()[0] == Color::RED); + fmt::print("{}\n", Color::name()); + fmt::print("{}\n", Color::names()); + fmt::print("{}\n", Color::values()); + fmt::print("{}\n", Color::from_string("ORANGE")); + fmt::print("{}\n", (+Color::RED).to_string()); +} diff --git a/src/cube.cpp b/src/cube.cpp new file mode 100644 index 0000000..5a22f4a --- /dev/null +++ b/src/cube.cpp @@ -0,0 +1,16 @@ +#include "cube.hpp" + +#include + +const std::unordered_map< CubeFace, CubeFace >& getAxisPairs() { + + const static std::unordered_map< CubeFace, CubeFace > face_opposites{ + { CubeFace::UP, CubeFace::DOWN}, + { CubeFace::DOWN, CubeFace::UP}, + {CubeFace::RIGHT, CubeFace::LEFT}, + { CubeFace::LEFT, CubeFace::RIGHT}, + {CubeFace::FRONT, CubeFace::BACK}, + { CubeFace::BACK, CubeFace::FRONT}, + }; + return face_opposites; +} diff --git a/src/cube.hpp b/src/cube.hpp index 02f9351..976a31c 100644 --- a/src/cube.hpp +++ b/src/cube.hpp @@ -8,92 +8,129 @@ */ #ifndef CUBE_HPP #define CUBE_HPP -#include // for format_parse_context, formatter -#include // for uint8_t -#include // for size_t +#include "enumUtils.hpp" -#include "enum.hpp" // for BiEnum +#include // for size_t +#include +#include +#include // for uint8_t +#include +#include + +#include // for format_parse_context, formatter +#include // for format_parse_context, formatter /*! * @brief number of faces on a cube. */ constexpr size_t num_cube_faces = 6; -/*! - * @brief CubeFace enum labels for faces of the cube. - */ -enum class CubeFace : uint8_t { UP, DOWN, RIGHT, LEFT, FRONT, BACK }; +BETTER_ENUM(CubeFace, uint8_t, UP, DOWN, RIGHT, LEFT, FRONT, BACK) /*! - * @brief BiEnum for mapping from CubeFace labels to quarter-turn metric - * notation. + * @brief Number of different turns that each face can make */ -constexpr BiEnum< CubeFace, char, num_cube_faces > cube_face_notation{ - {{{CubeFace::UP, 'U'}, - {CubeFace::DOWN, 'D'}, - {CubeFace::RIGHT, 'R'}, - {CubeFace::LEFT, 'L'}, - {CubeFace::FRONT, 'F'}, - {CubeFace::BACK, 'B'}}}}; +constexpr size_t num_turns = 3; -/*! - * @brief formatter specialization for CubeFace enum class - */ -template <> struct fmt::formatter< CubeFace > { - /*! - * @brief parse the format string - * - * @param ctx the format context. - * - * @return iterator to one past last parse char of the format string. - */ - static constexpr auto parse(format_parse_context& t_ctx) - -> decltype(t_ctx.begin()) { - return t_ctx.end(); +BETTER_ENUM(FaceTurn, uint8_t, ANTICLOCKWISE, CLOCKWISE, HALFTURN) + +constexpr auto makeFirstQuery(const auto& t_first_val) { + return [t_first_val](auto t_pair_val) { + return t_pair_val.first == t_first_val; + }; +} + +constexpr auto makeSecondQuery(const auto& t_second_val) { + return [t_second_val](auto t_pair_val) { + return t_pair_val.second == t_second_val; + }; +} + +struct CubeMove { + constexpr static std::array< std::pair< CubeFace, char >, CubeFace::size() > + face_notation{ + {{CubeFace::UP, 'U'}, + {CubeFace::DOWN, 'D'}, + {CubeFace::RIGHT, 'R'}, + {CubeFace::LEFT, 'L'}, + {CubeFace::FRONT, 'F'}, + {CubeFace::BACK, 'B'}} + }; + + constexpr static std::array< std::pair< FaceTurn, char >, FaceTurn::size() > + turn_notation{ + {{FaceTurn::ANTICLOCKWISE, '\''}, + {FaceTurn::CLOCKWISE, '\0'}, + {FaceTurn::HALFTURN, '2'}} + }; + + constexpr static CubeFace getFace(const char& t_c) { + const auto* face_ptr = std::find_if( + face_notation.begin(), face_notation.end(), makeSecondQuery(t_c) + ); + if (face_ptr == face_notation.end()) { + throw std::invalid_argument(fmt::format( + "{} is not recognized notation for a cube face\n", t_c + )); + } + return face_ptr->first; } - /*! - * @brief formats the CubeFace element into a printable character - * - * @tparam FormatContext - * @param face - * @param ctx - * - * @return Format ctx output iterator; - */ - template < typename FormatContext > - static auto format(const CubeFace& t_face, FormatContext& t_ctx) - -> decltype(t_ctx.out()) { - return fmt::format_to(t_ctx.out(), "{}", cube_face_notation[t_face]); + constexpr static FaceTurn getTurn(const char& t_c) { + const auto* turn_ptr = std::find_if( + turn_notation.begin(), turn_notation.end(), makeSecondQuery(t_c) + ); + if (turn_ptr == turn_notation.end()) { + throw std::invalid_argument(fmt::format( + "{} is not recognized notation for a face turn\n", t_c + )); + } + return turn_ptr->first; + } + + CubeFace face; + FaceTurn turn; + + explicit constexpr CubeMove(const CubeFace& t_face, const FaceTurn& t_turn) + : face(t_face), turn(t_turn) {} + + explicit constexpr CubeMove(const std::string_view t_notation) + : face(getFace(t_notation[0])), turn(getTurn(t_notation[1])) {} + + [[nodiscard]] CUBE_TIMER_CONSTEXPR std::string getNotation() const { + const auto* face_ptr = std::find_if( + face_notation.begin(), face_notation.end(), makeFirstQuery(face) + ); + const auto* turn_ptr = std::find_if( + turn_notation.begin(), turn_notation.end(), makeFirstQuery(turn) + ); + + return std::string(&(face_ptr->second), 1) + + std::string(&(turn_ptr->second), 1); + } + + static std::vector< CubeMove > generateMoveList() { + std::vector< CubeMove > move_list; + move_list.reserve(CubeFace::size() * FaceTurn::size()); + for (auto face : CubeFace::values()) { + for (auto turn : FaceTurn::values()) { + move_list.emplace_back(face, turn); + } + } + return move_list; } }; -/*! - * @brief Number of different turns that each face can make - */ -constexpr size_t num_turns = 3; -/*! - * @brief FaceTurn an enum class to label the different modes for each face - * move. - */ -enum class FaceTurn { ANTICLOCKWISE, CLOCKWISE, HALFTURN }; -/*! - * @brief BiEnum to map turn modes to their quarter-turn metric notation - */ -constexpr BiEnum< FaceTurn, char, num_turns > turn_notation{ - {{{FaceTurn::ANTICLOCKWISE, '\''}, - {FaceTurn::CLOCKWISE, '\0'}, - {FaceTurn::HALFTURN, '2'}}}}; +template <> struct fmt::formatter< CubeMove > { + template < typename ParseCtx > constexpr auto parse(ParseCtx& t_ctx) const { + return t_ctx.begin(); + } -/*! - * @brief Array to map faces to their opposite axis partners. - */ -constexpr BiEnum< CubeFace, CubeFace, num_cube_faces > cube_face_axis_pair{ - {{{CubeFace::UP, CubeFace::DOWN}, - {CubeFace::DOWN, CubeFace::UP}, - {CubeFace::RIGHT, CubeFace::LEFT}, - {CubeFace::LEFT, CubeFace::RIGHT}, - {CubeFace::FRONT, CubeFace::BACK}, - {CubeFace::BACK, CubeFace::FRONT}}}}; + template < typename FormatCtx > + constexpr auto format(const CubeMove& t_move, FormatCtx& t_ctx) const { + return format_to(t_ctx.out(), "{}", t_move.getNotation()); + } +}; +const std::unordered_map< CubeFace, CubeFace >& getAxisPairs(); #endif diff --git a/src/enum.hpp b/src/enum.hpp deleted file mode 100644 index 09ab87e..0000000 --- a/src/enum.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * @file enum.hpp - * @brief Helper functions and classes for working with enumerators. - * @author Brandon M. Whitchurch - * @version 0.0.1 - * @date 2023-01-16 - */ -#ifndef ENUM_HPP -#define ENUM_HPP - -#include // for find_if -#include // for array -#include // for size_t -#include // for pair - -/*! - * @brief BiEnumTraits traits class for BiEnum - * - * @tparam Enum enumerator type. Either plain enum or enum class is acceptable. - * @tparam Value - * @tparam N - */ -template < typename Enum, typename Value, size_t N > struct BiEnumTraits { - /*! - * @brief type of element stored in array. - */ - using element_type = std::pair< Enum, Value >; - /*! - * @brief typedef for the array type to be used as a map. - */ - using array_type = std::array< element_type, N >; -}; - -/*! - * @brief BiEnum is a bidirectional enum. I.e. one that allows converting - * between enum labels and string representations - * - * @tparam Enum - * @tparam Value - * @tparam N - */ -template < typename Enum, typename Value, size_t N > struct BiEnum { - - /*! - * @brief type traits for the BiEnum. - */ - using traits_type = BiEnumTraits< Enum, Value, N >; - /*! - * @brief array of pairs to function as contexpr map - */ - typename traits_type::array_type m_enum_map; - - /*! - * @brief Search the map by Value, the second member of the pair type. - * - * @param val - * - * @return Enum label that corresponds with val. - */ - constexpr Enum operator[](const Value& t_val) const { - const auto* found_ptr = std::find_if( - m_enum_map.begin(), - m_enum_map.end(), - [t_val](auto t_iter_val) { return t_iter_val.second == t_val; } - ); - return found_ptr->first; - } - - /*! - * @brief Search the map b Enum, the first member of the pair type. - * - * @param eVal - * - * @return Value correponding with the enum label. - */ - constexpr Value operator[](const Enum& t_e_val) const - requires(!std::is_same_v< Enum, Value >) - { - const auto* found_ptr = std::find_if( - m_enum_map.begin(), - m_enum_map.end(), - [t_e_val](auto t_iter_val) { return t_iter_val.first == t_e_val; } - ); - return found_ptr->second; - } -}; -#endif diff --git a/src/ppUtils/CMakeLists.txt b/src/ppUtils/CMakeLists.txt new file mode 100644 index 0000000..4e58a0e --- /dev/null +++ b/src/ppUtils/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(ppUtils INTERFACE) +target_include_directories(ppUtils INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(ppUtils INTERFACE project_options project_warnings) +set_target_properties(ppUtils PROPERTIES PUBLIC_HEADER ppUtils.hpp) +if(ENABLE_TESTING) + add_subdirectory(test) +endif() diff --git a/src/ppUtils/ppUtils.hpp b/src/ppUtils/ppUtils.hpp new file mode 100644 index 0000000..d0ac442 --- /dev/null +++ b/src/ppUtils/ppUtils.hpp @@ -0,0 +1,91 @@ +#ifndef PP_UTILS_HPP +#define PP_UTILS_HPP +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +/****************************************************************************** + * Object Literals * + ******************************************************************************/ +#define T_PARENS () +#define T_LPAREN ( +#define T_RPAREN ) +#define T_COMMA , + +/****************************************************************************** + * Basic Functions * + ******************************************************************************/ +#define MM_ID(arg) arg +#define MM_CONCAT(arg_a, arg_b) arg_a##arg_b +#define MM_STRINGIFY(arg) #arg +#define MM_STRINGIFY_M(arg) MM_STRINGIFY(arg) + +/*! + * Macros that will cause unexpanded macros in __VA_ARGS__ to be expanded. + * Current max depth is 2^8 + */ +#define MM_EXPAND(...) MM_EXPAND_8(__VA_ARGS__) +#define MM_EXPAND_1(...) __VA_ARGS__ +#define MM_EXPAND_2(...) MM_EXPAND_1(MM_EXPAND_1(__VA_ARGS__)) +#define MM_EXPAND_3(...) MM_EXPAND_2(MM_EXPAND_2(__VA_ARGS__)) +#define MM_EXPAND_4(...) MM_EXPAND_3(MM_EXPAND_3(__VA_ARGS__)) +#define MM_EXPAND_5(...) MM_EXPAND_4(MM_EXPAND_4(__VA_ARGS__)) +#define MM_EXPAND_6(...) MM_EXPAND_5(MM_EXPAND_5(__VA_ARGS__)) +#define MM_EXPAND_7(...) MM_EXPAND_6(MM_EXPAND_6(__VA_ARGS__)) +#define MM_EXPAND_8(...) MM_EXPAND_7(MM_EXPAND_7(__VA_ARGS__)) + +/****************************************************************************** + * High Order Macros * + ******************************************************************************/ +/*! + * Applies macro to each argument int the trailing argument list + */ +#define MM_FOR_EACH(macro, ...) \ + __VA_OPT__(MM_EXPAND(MM_FOR_EACH_HELPER(macro, __VA_ARGS__))) +#define MM_FOR_EACH_HELPER(macro, _1, ...) \ + macro(_1) __VA_OPT__(MM_FOR_EACH_AGAIN T_PARENS(macro, __VA_ARGS__)) +#define MM_FOR_EACH_AGAIN() MM_FOR_EACH_HELPER + +#define MM_TRANSFORM(macro, ...) \ + __VA_OPT__(MM_EXPAND(MM_TRANSFORM_HELPER(macro, __VA_ARGS__))) +#define MM_TRANSFORM_HELPER(macro, _1, ...) \ + macro(_1) __VA_OPT__( \ + T_COMMA MM_TRANSFORM_AGAIN T_PARENS T_LPAREN macro, \ + __VA_ARGS__ T_RPAREN \ + ) +#define MM_TRANSFORM_AGAIN() MM_TRANSFORM_HELPER + +#define MM_TRANSFORM_1(macro, ...) \ + __VA_OPT__(MM_EXPAND(MM_TRANSFORM_HELPER_1(macro, __VA_ARGS__))) +#define MM_TRANSFORM_HELPER_1(macro, _1, _2, ...) \ + macro(_1, _2) __VA_OPT__( \ + T_COMMA MM_TRANSFORM_AGAIN_1 T_PARENS T_LPAREN macro, \ + _1, \ + __VA_ARGS__ T_RPAREN \ + ) +#define MM_TRANSFORM_AGAIN_1() MM_TRANSFORM_HELPER_1 +/*! + * Calls macro(_1, arg) for each arg in the variadic argument list + */ +#define MM_FOR_EACH_1(macro, _1, ...) \ + __VA_OPT__(MM_EXPAND(MM_FOR_EACH_HELPER_1(macro, _1, __VA_ARGS__))) +#define MM_FOR_EACH_HELPER_1(macro, _1, _2, ...) \ + macro(_1, _2) \ + __VA_OPT__(MM_FOR_EACH_AGAIN_1 T_PARENS(macro, _1, __VA_ARGS__)) +#define MM_FOR_EACH_AGAIN_1() MM_FOR_EACH_HELPER_1 + +/*! + * Calls macro(a,b) on the first two arguments in the variadic list. The next + * call is generated as macro(macro(a,b), c) and so on until all arguments are + * consumed. + */ +#define MM_FOLD_LEFT(macro, ...) \ + __VA_OPT__(MM_EXPAND(MM_FOLD_LEFT_HELPER(macro, __VA_ARGS__))) +#define MM_FOLD_LEFT_HELPER(macro, _1, _2, ...) \ + __VA_OPT__(MM_FOLD_LEFT_AGAIN T_PARENS T_LPAREN macro, ) \ + macro(_1, _2) __VA_OPT__(, __VA_ARGS__ T_RPAREN) +#define MM_FOLD_LEFT_AGAIN() MM_FOLD_LEFT_HELPER + +#define MM_INCREMENT(arg) ((arg) + 1) +#define MM_COUNT_IF(sum, arg_b) MM_INCREMENT(sum) +#define MM_COUNT_ARGS(...) MM_FOLD_LEFT(MM_COUNT_IF, 0, __VA_ARGS__) + +// NOLINTEND(cppcoreguidelines-macro-usage) +#endif diff --git a/src/ppUtils/test/CMakeLists.txt b/src/ppUtils/test/CMakeLists.txt new file mode 100644 index 0000000..30491af --- /dev/null +++ b/src/ppUtils/test/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(ppUtilsTest ppUtilsTest.cpp) +target_link_libraries(ppUtilsTest PUBLIC Catch2::Catch2WithMain fmt::fmt + ppUtils) +target_link_libraries(ppUtilsTest PRIVATE project_options project_warnings) +set_target_properties(ppUtilsTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_TEST_OUTPUT_DIRECTORY}") + +include(Catch) +catch_discover_tests( + ppUtilsTest + TEST_PREFIX + "ppUtils." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "ppUtils." + OUTPUT_SUFFIX + .xml) diff --git a/src/ppUtils/test/ppUtilsTest.cpp b/src/ppUtils/test/ppUtilsTest.cpp new file mode 100644 index 0000000..d6e334b --- /dev/null +++ b/src/ppUtils/test/ppUtilsTest.cpp @@ -0,0 +1,44 @@ +#include "ppUtils.hpp" + +#include + +#include + +TEST_CASE("Basic Function Macros", "[macro][preprocessor][pp]") { + SECTION("ID") { + SECTION("char input") { REQUIRE(MM_ID('a') == 'a'); } + SECTION("string input") { + REQUIRE(MM_ID(std::string("ppUtils")) == std::string("ppUtils")); + } + SECTION("integer input") { REQUIRE(MM_ID(1) == 1); } + SECTION("floating input") { REQUIRE(MM_ID(1.0) == 1.0); } + } + + SECTION("CONCAT") { + const int a12 = 8; + REQUIRE(MM_CONCAT(a, 12) == 8); + } + SECTION("EXPAND") { + CHECK( + std::string(MM_STRINGIFY_M(MM_ID(MM_ID T_PARENS 2.0))) + == std::string("MM_ID () 2.0") + ); + + CHECK(MM_EXPAND(MM_ID T_PARENS 2.0) == 2.0); + } + + SECTION("FOR_EACH") { + const std::string res = MM_STRINGIFY_M(MM_FOR_EACH(MM_ID, 1, 2, 3, 4, 5)); + REQUIRE(res == "1 2 3 4 5"); + } + + SECTION("FOLD") { + REQUIRE( + std::string(MM_STRINGIFY_M(MM_FOLD_LEFT(MM_CONCAT, a, b, c, d))) + == "abcd" + ); + } + SECTION("NARG") { + CHECK(MM_COUNT_ARGS(a, b, c, d) == 4); + } +} diff --git a/src/scrambler.hpp b/src/scrambler.hpp index 35fa95e..9355bea 100644 --- a/src/scrambler.hpp +++ b/src/scrambler.hpp @@ -1,28 +1,28 @@ #ifndef SCRAMBLER_HPP #define SCRAMBLER_HPP -#include // for fill_n -#include // for array -#include // for size_t -#include // for operator<<, basic_ostream, stringstream, basic_... -#include // for mt19937, uniform_real_distribution, random_device -#include // for char_traits, allocator, string, getline -#include +#include "cube.hpp" // for num_cube_faces, CubeFace (ptr only), num_turns -#include "cube.hpp" // for num_cube_faces, CubeFace (ptr only), num_turns -#include "enum.hpp" // for BiEnum +#include // for fill_n +#include // for array +#include // for size_t +#include // for operator<<, basic_ostream, stringstream, basic_... +#include // for mt19937, uniform_real_distribution, random_device +#include +#include // for char_traits, allocator, string, getline +#include constexpr size_t default_scramble_length = 25; class Scrambler { - size_t m_length; - std::mt19937 m_gen; - std::uniform_real_distribution<> m_dis; + size_t m_length; + std::mt19937 m_gen; + std::uniform_real_distribution<> m_dis; std::array< CubeFace, 2 > m_state; std::array< double, num_cube_faces > m_computeTransitionProbs() { std::array< double, num_cube_faces > transition_probs{}; - if (cube_face_axis_pair[m_state[0]] != m_state[1]) { + if (getAxisPairs().at(m_state[0]) != m_state[1]) { transition_probs.fill(1.0 / (num_cube_faces - 1)); transition_probs.at(static_cast< size_t >(m_state[1])) = 0.0; } else { @@ -46,55 +46,53 @@ class Scrambler { : m_length(t_scramble_length), m_gen(std::random_device{}()), m_dis(0.0, 1.0), - m_state{} { - std::array< double, num_cube_faces > state_prob{}; + m_state({CubeFace::RIGHT, CubeFace::UP}) { + std::array< double, CubeFace::size() > state_prob{}; state_prob.fill(1.0 / num_cube_faces); double sum = 0.0; for (auto& prob : state_prob) { sum += prob; - prob = sum; + prob = sum; } double rand_val = m_dis(m_gen); - size_t idx = 0; + size_t idx = 0; while (rand_val >= state_prob.at(idx)) { ++idx; } - m_state[0] = CubeFace(idx); + m_state[0] = CubeFace::values()[idx]; state_prob.fill(1.0 / (num_cube_faces - 1)); state_prob.at(static_cast< size_t >(m_state[0])) = 0.0; - sum = 0.0; + sum = 0.0; for (auto& prob : state_prob) { sum += prob; - prob = sum; + prob = sum; } - idx = 0; + idx = 0; rand_val = m_dis(m_gen); while (rand_val > state_prob.at(idx)) { ++idx; } - m_state[1] = CubeFace(idx); + m_state[1] = CubeFace::values()[idx]; } std::string generateScramble() { - size_t moves_generated = 0; - std::stringstream the_scramble{""}; + size_t moves_generated = 0; + std::vector< CubeMove > the_scramble; + the_scramble.reserve(m_length); while (moves_generated < m_length) { - if (moves_generated > 0) { the_scramble << " "; } auto prob_table = m_computeTransitionProbs(); const double rand_face = m_dis(m_gen); - size_t idx = 0; + size_t idx = 0; while (rand_face > prob_table.at(idx)) { ++idx; } - const auto curr_face = CubeFace(idx); - m_state[0] = m_state[1]; - m_state[1] = curr_face; - the_scramble << cube_face_notation[curr_face]; + const auto curr_face = CubeFace::values()[idx]; + m_state[0] = m_state[1]; + m_state[1] = curr_face; const double rand_turn = m_dis(m_gen); - if (rand_turn < 1. / num_turns) { - the_scramble << turn_notation[FaceTurn::ANTICLOCKWISE]; - } else if (rand_turn < 2. / num_turns) { - the_scramble << turn_notation[FaceTurn::CLOCKWISE]; - } else { - the_scramble << turn_notation[FaceTurn::HALFTURN]; - } + idx = 0; + while (rand_turn > double(idx + 1) / 3) { ++idx; } + const auto curr_turn = + FaceTurn::values()[idx]; the_scramble.emplace_back( + curr_face, curr_turn + ); ++moves_generated; - } - return the_scramble.str(); + } + return fmt::format("{}", fmt::join(the_scramble, " ")); } static void scrambleStats( @@ -102,14 +100,10 @@ class Scrambler { std::array< size_t, num_cube_faces * num_turns >& t_stats ) { std::stringstream scramble_stream(t_scramble); - std::string move; - while (std::getline(scramble_stream, move, ' ')) { - const CubeFace face = cube_face_notation[move[0]]; - const FaceTurn turn = turn_notation[move[1]]; - ++t_stats.at( - num_turns * static_cast< size_t >(face) - + static_cast< size_t >(turn) - ); + std::string move_str; + while (std::getline(scramble_stream, move_str, ' ')) { + const CubeMove move(move_str); + ++t_stats.at(num_turns * move.face + move.turn); } } }; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 0ba6f1a..5c8d60c 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,11 +1,21 @@ -find_package(Catch2) - add_executable(scramblerTest scramblerTest.cpp) -target_link_libraries(scramblerTest PRIVATE scrambler Catch2::Catch2WithMain - project_options project_warnings) +target_link_libraries( + scramblerTest + PUBLIC scrambler Catch2::Catch2WithMain + PRIVATE project_options project_warnings) set_target_properties(scramblerTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_TEST_OUTPUT_DIRECTORY}") + +add_executable(cubeTest cubeTest.cpp) +target_link_libraries( + cubeTest + PUBLIC scrambler Catch2::Catch2WithMain + PRIVATE project_options project_warnings) + +set_target_properties(cubeTest PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_TEST_OUTPUT_DIRECTORY}") + include(Catch) catch_discover_tests( @@ -20,3 +30,15 @@ catch_discover_tests( "libscrambler." OUTPUT_SUFFIX .xml) +catch_discover_tests( + cubeTest + TEST_PREFIX + "cube." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "cube." + OUTPUT_SUFFIX + .xml) diff --git a/src/test/cubeTest.cpp b/src/test/cubeTest.cpp new file mode 100644 index 0000000..6a78dcc --- /dev/null +++ b/src/test/cubeTest.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include + +#include + +TEST_CASE("CubeMove") { + const auto my_face = CubeFace::UP; + const auto my_turn = FaceTurn::ANTICLOCKWISE; + + const CubeMove my_move(my_face, my_turn); + REQUIRE(my_move.getNotation() == "U'"); + fmt::print("{}\n", my_move.getNotation()); + + const CubeMove another_move("R"); + REQUIRE(another_move.face == CubeFace::RIGHT); + REQUIRE(another_move.turn == FaceTurn::CLOCKWISE); + + const CubeMove another_move2("B2"); + REQUIRE(another_move2.face == CubeFace::BACK); + REQUIRE(another_move2.turn == FaceTurn::HALFTURN); + + REQUIRE_THROWS(CubeMove("A'")); + REQUIRE_THROWS(CubeMove("U$")); + + std::vector< CubeMove > move_list = CubeMove::generateMoveList(); + fmt::print("{}\n", move_list); +} diff --git a/src/test/scramblerTest.cpp b/src/test/scramblerTest.cpp index bb146a3..0853db3 100644 --- a/src/test/scramblerTest.cpp +++ b/src/test/scramblerTest.cpp @@ -1,22 +1,7 @@ #include // for string_view, operator==, bas... #include "catch2/catch_test_macros.hpp" // for operator==, AssertionHandler -#include "enum.hpp" // for BiEnum -enum class TestEnum { RED = 42, BLUE = 2, YELLOW, GREEN, PURPLE }; -constexpr BiEnum< TestEnum, std::string_view, 5 > color_names{ - {{{TestEnum::RED, "red"}, - {TestEnum::BLUE, "blue"}, - {TestEnum::YELLOW, "yellow"}, - {TestEnum::GREEN, "green"}, - {TestEnum::PURPLE, "purple"}}}}; - -static_assert(color_names[color_names["red"]] == "red"); - -static_assert(color_names[color_names[TestEnum::YELLOW]] == TestEnum::YELLOW); - -TEST_CASE("BiEnums") { - REQUIRE(color_names[TestEnum::BLUE] == "blue"); - REQUIRE(color_names["purple"] == TestEnum::PURPLE); - REQUIRE(color_names[color_names["green"]] == "green"); +TEST_CASE("Hello World"){ + REQUIRE(true); }