diff --git a/src/Core/Enum.hpp b/src/Core/Enum.hpp new file mode 100644 index 0000000..e1e6905 --- /dev/null +++ b/src/Core/Enum.hpp @@ -0,0 +1,52 @@ +#include +#include +#include "Exceptions/NotReachableException.hpp" +#include "MacroUtils.hpp" + +#define ENUM_CASE(x, name) \ + case name::x: \ + return #x; +# +#define ENUM_PARSE_CASE(x, name) \ + case ConstHash(#x): \ + return name::x; +# +#define ENUM_PARSE_CASE_INSENSITIVE(x, name) \ + case ConstHashCI(#x): \ + return name::x; +# +#define ARRAY_NAME(x, name) name::x, + +#define ENUM(name, type, values...) \ + enum class name : type { values }; \ + class name##Helper { \ + inline static uint32_t constexpr ConstHash(char const* input) { \ + return *input ? static_cast(*input) + 33 * ConstHash(input + 1) : 5381; \ + } \ + inline static constexpr char charToLower(const char c) { \ + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; \ + } \ + inline static uint32_t constexpr ConstHashCI(char const* input) { \ + return charToLower(*input) ? static_cast(charToLower(*input)) + 33 * ConstHashCI(input + 1) \ + : 5381; \ + } \ + \ + public: \ + static std::string ToString(name value) { \ + switch (value) { FOR_EACH(ENUM_CASE, name, values) } \ + throw NotReachableException(); \ + } \ + static name Parse(const std::string& input, bool caseInsensitive = false) { \ + if (caseInsensitive) \ + return ParseCaseInsensitive(input); \ + switch (ConstHash(input.c_str())) { FOR_EACH(ENUM_PARSE_CASE, name, values) } \ + throw CreatureException("String '" + input + "' could not be parsed as " #name); \ + } \ + static std::vector GetValues() { return {FOR_EACH(ARRAY_NAME, name, values)}; } \ + \ + private: \ + static name ParseCaseInsensitive(const std::string& input) { \ + switch (ConstHashCI(input.c_str())) { FOR_EACH(ENUM_PARSE_CASE_INSENSITIVE, name, values) } \ + throw CreatureException("String '" + input + "' could not be parsed as " #name); \ + } \ + }; diff --git a/src/Core/MacroUtils.hpp b/src/Core/MacroUtils.hpp new file mode 100644 index 0000000..72623fb --- /dev/null +++ b/src/Core/MacroUtils.hpp @@ -0,0 +1,43 @@ +#define FE_0(FUNC, arg) +#define FE_1(FUNC, arg, X) FUNC(X, arg) +#define FE_2(FUNC, arg, X, ...) FUNC(X, arg) FE_1(FUNC, arg, __VA_ARGS__) +#define FE_3(FUNC, arg, X, ...) FUNC(X, arg) FE_2(FUNC, arg, __VA_ARGS__) +#define FE_4(FUNC, arg, X, ...) FUNC(X, arg) FE_3(FUNC, arg, __VA_ARGS__) +#define FE_5(FUNC, arg, X, ...) FUNC(X, arg) FE_4(FUNC, arg, __VA_ARGS__) +#define FE_6(FUNC, arg, X, ...) FUNC(X, arg) FE_5(FUNC, arg, __VA_ARGS__) +#define FE_7(FUNC, arg, X, ...) FUNC(X, arg) FE_6(FUNC, arg, __VA_ARGS__) +#define FE_8(FUNC, arg, X, ...) FUNC(X, arg) FE_7(FUNC, arg, __VA_ARGS__) +#define FE_9(FUNC, arg, X, ...) FUNC(X, arg) FE_8(FUNC, arg, __VA_ARGS__) +#define FE_10(FUNC, arg, X, ...) FUNC(X, arg) FE_9(FUNC, arg, __VA_ARGS__) +#define FE_11(FUNC, arg, X, ...) FUNC(X, arg) FE_10(FUNC, arg, __VA_ARGS__) +#define FE_12(FUNC, arg, X, ...) FUNC(X, arg) FE_11(FUNC, arg, __VA_ARGS__) +#define FE_13(FUNC, arg, X, ...) FUNC(X, arg) FE_12(FUNC, arg, __VA_ARGS__) +#define FE_14(FUNC, arg, X, ...) FUNC(X, arg) FE_13(FUNC, arg, __VA_ARGS__) +#define FE_15(FUNC, arg, X, ...) FUNC(X, arg) FE_14(FUNC, arg, __VA_ARGS__) +#define FE_16(FUNC, arg, X, ...) FUNC(X, arg) FE_15(FUNC, arg, __VA_ARGS__) +#define FE_17(FUNC, arg, X, ...) FUNC(X, arg) FE_16(FUNC, arg, __VA_ARGS__) +#define FE_18(FUNC, arg, X, ...) FUNC(X, arg) FE_17(FUNC, arg, __VA_ARGS__) +#define FE_19(FUNC, arg, X, ...) FUNC(X, arg) FE_18(FUNC, arg, __VA_ARGS__) +#define FE_20(FUNC, arg, X, ...) FUNC(X, arg) FE_19(FUNC, arg, __VA_ARGS__) +#define FE_21(FUNC, arg, X, ...) FUNC(X, arg) FE_20(FUNC, arg, __VA_ARGS__) +#define FE_22(FUNC, arg, X, ...) FUNC(X, arg) FE_21(FUNC, arg, __VA_ARGS__) +#define FE_23(FUNC, arg, X, ...) FUNC(X, arg) FE_22(FUNC, arg, __VA_ARGS__) +#define FE_24(FUNC, arg, X, ...) FUNC(X, arg) FE_23(FUNC, arg, __VA_ARGS__) +#define FE_25(FUNC, arg, X, ...) FUNC(X, arg) FE_24(FUNC, arg, __VA_ARGS__) +#define FE_26(FUNC, arg, X, ...) FUNC(X, arg) FE_25(FUNC, arg, __VA_ARGS__) +#define FE_27(FUNC, arg, X, ...) FUNC(X, arg) FE_26(FUNC, arg, __VA_ARGS__) +#define FE_28(FUNC, arg, X, ...) FUNC(X, arg) FE_27(FUNC, arg, __VA_ARGS__) +#define FE_29(FUNC, arg, X, ...) FUNC(X, arg) FE_28(FUNC, arg, __VA_ARGS__) +#define FE_30(FUNC, arg, X, ...) FUNC(X, arg) FE_29(FUNC, arg, __VA_ARGS__) +#define FE_31(FUNC, arg, X, ...) FUNC(X, arg) FE_30(FUNC, arg, __VA_ARGS__) +#define FE_32(FUNC, arg, X, ...) FUNC(X, arg) FE_31(FUNC, arg, __VA_ARGS__) +#define FE_33(FUNC, arg, X, ...) THE_FOREACH_MACRO_CURRENTLY_ONLY_SUPPORTS_UP_TO_32_VALUES FE_32(FUNC, arg, __VA_ARGS__) + +#define GET_MACRO(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \ + _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, NAME, ...) \ + NAME +#define FOR_EACH(action, arg, ...) \ + GET_MACRO(_0, __VA_ARGS__, FE_33, FE_32, FE_31, FE_30, FE_29, FE_28, FE_27, FE_26, FE_25, FE_24, FE_23, FE_22, \ + FE_21, FE_20, FE_19, FE_18, FE_17, FE_16, FE_15, FE_14, FE_13, FE_12, FE_11, FE_10, FE_9, FE_8, FE_7, \ + FE_6, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0) \ + (action, arg, __VA_ARGS__) \ No newline at end of file diff --git a/src/Core/Statistic.hpp b/src/Core/Statistic.hpp index 8cf9eb8..0f56733 100644 --- a/src/Core/Statistic.hpp +++ b/src/Core/Statistic.hpp @@ -2,16 +2,10 @@ #define CREATURELIB_STATISTIC_HPP #include +#include "Enum.hpp" namespace CreatureLib::Core { - enum Statistic : uint8_t { - Health, - PhysicalAttack, - PhysicalDefense, - MagicalAttack, - MagicalDefense, - Speed - }; + ENUM(Statistic, uint8_t, Health, PhysicalAttack, PhysicalDefense, MagicalAttack, MagicalDefense, Speed) } #endif // CREATURELIB_STATISTIC_HPP diff --git a/src/Core/StatisticSet.hpp b/src/Core/StatisticSet.hpp index 92a8795..39e9747 100644 --- a/src/Core/StatisticSet.hpp +++ b/src/Core/StatisticSet.hpp @@ -31,47 +31,47 @@ namespace CreatureLib::Core { [[nodiscard]] inline T GetStat(Statistic stat) const { switch (stat) { - case Health: return _health; - case PhysicalAttack: return _physicalAttack; - case PhysicalDefense: return _physicalDefense; - case MagicalAttack: return _magicalAttack; - case MagicalDefense: return _magicalDefense; - case Speed: return _speed; + case CreatureLib::Core::Statistic::Health: return _health; + case CreatureLib::Core::Statistic::PhysicalAttack: return _physicalAttack; + case CreatureLib::Core::Statistic::PhysicalDefense: return _physicalDefense; + case CreatureLib::Core::Statistic::MagicalAttack: return _magicalAttack; + case CreatureLib::Core::Statistic::MagicalDefense: return _magicalDefense; + case CreatureLib::Core::Statistic::Speed: return _speed; default: throw NotReachableException(); } } inline void SetStat(Statistic stat, T value) { switch (stat) { - case Health: _health = value; break; - case PhysicalAttack: _physicalAttack = value; break; - case PhysicalDefense: _physicalDefense = value; break; - case MagicalAttack: _magicalAttack = value; break; - case MagicalDefense: _magicalDefense = value; break; - case Speed: _speed = value; break; + case CreatureLib::Core::Statistic::Health: _health = value; break; + case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack = value; break; + case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense = value; break; + case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack = value; break; + case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense = value; break; + case CreatureLib::Core::Statistic::Speed: _speed = value; break; default: throw NotReachableException(); } } inline void IncreaseStatBy(Statistic stat, T amount) { switch (stat) { - case Health: _health += amount; break; - case PhysicalAttack: _physicalAttack += amount; break; - case PhysicalDefense: _physicalDefense += amount; break; - case MagicalAttack: _magicalAttack += amount; break; - case MagicalDefense: _magicalDefense += amount; break; - case Speed: _speed += amount; break; + case CreatureLib::Core::Statistic::Health: _health += amount; break; + case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack += amount; break; + case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense += amount; break; + case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack += amount; break; + case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense += amount; break; + case CreatureLib::Core::Statistic::Speed: _speed += amount; break; default: throw NotReachableException(); } } inline void DecreaseStatBy(Statistic stat, T amount) { switch (stat) { - case Health: _health -= amount; break; - case PhysicalAttack: _physicalAttack -= amount; break; - case PhysicalDefense: _physicalDefense -= amount; break; - case MagicalAttack: _magicalAttack -= amount; break; - case MagicalDefense: _magicalDefense -= amount; break; - case Speed: _speed -= amount; break; + case CreatureLib::Core::Statistic::Health: _health -= amount; break; + case CreatureLib::Core::Statistic::PhysicalAttack: _physicalAttack -= amount; break; + case CreatureLib::Core::Statistic::PhysicalDefense: _physicalDefense -= amount; break; + case CreatureLib::Core::Statistic::MagicalAttack: _magicalAttack -= amount; break; + case CreatureLib::Core::Statistic::MagicalDefense: _magicalDefense -= amount; break; + case CreatureLib::Core::Statistic::Speed: _speed -= amount; break; default: throw NotReachableException(); } } diff --git a/src/Library/Attacks/AttackCategory.hpp b/src/Library/Attacks/AttackCategory.hpp index a5d59b6..022b7ad 100644 --- a/src/Library/Attacks/AttackCategory.hpp +++ b/src/Library/Attacks/AttackCategory.hpp @@ -2,9 +2,10 @@ #define CREATURELIB_ATTACKCATEGORY_HPP #include +#include "../../Core/Enum.hpp" namespace CreatureLib::Library { - enum class AttackCategory : uint8_t { Physical, Magical, Status }; + ENUM(AttackCategory, uint8_t, Physical, Magical, Status) } #endif // CREATURELIB_ATTACKCATEGORY_HPP diff --git a/src/Library/Attacks/AttackTarget.hpp b/src/Library/Attacks/AttackTarget.hpp index d9313da..d9abae6 100644 --- a/src/Library/Attacks/AttackTarget.hpp +++ b/src/Library/Attacks/AttackTarget.hpp @@ -2,25 +2,16 @@ #define CREATURELIB_ATTACKTARGET_HPP #include +#include "../../Core/Enum.hpp" namespace CreatureLib::Library { - enum class AttackTarget : uint8_t { - Adjacent, - AdjacentAlly, - AdjacentAllySelf, - AdjacentOpponent, + ENUM(AttackTarget, uint8_t, Adjacent, AdjacentAlly, AdjacentAllySelf, AdjacentOpponent, - All, - AllAdjacent, - AllAdjacentOpponent, - AllAlly, - AllOpponent, + All, AllAdjacent, AllAdjacentOpponent, AllAlly, AllOpponent, - Any, + Any, - RandomOpponent, - Self, - }; + RandomOpponent, Self) } #endif // CREATURELIB_ATTACKTARGET_HPP diff --git a/src/Library/Gender.hpp b/src/Library/Gender.hpp index b64a783..ab1bbf3 100644 --- a/src/Library/Gender.hpp +++ b/src/Library/Gender.hpp @@ -2,13 +2,14 @@ #define CREATURELIB_GENDER_HPP #include +#include "../Core/Enum.hpp" namespace CreatureLib::Library { /*! \brief Might be somewhat controversial nowadays, but as many creature battling games only have two genders, we'll hardcode those. */ - enum class Gender : uint8_t { Male, Female, Genderless }; + ENUM(Gender, uint8_t, Male, Female, Genderless) } #endif // CREATURELIB_GENDER_HPP diff --git a/tests/EnumTests.cpp b/tests/EnumTests.cpp new file mode 100644 index 0000000..f94fea8 --- /dev/null +++ b/tests/EnumTests.cpp @@ -0,0 +1,40 @@ +#ifdef TESTS_BUILD +#include +#include "../extern/catch.hpp" +#include "../src/Core/Enum.hpp" + +ENUM(TestEnum, uint8_t, Val1, Val2, Val3) + +TEST_CASE("Parse Enum case sensitive", "[Utilities]") { + CHECK(TestEnumHelper::Parse("Val1") == TestEnum::Val1); + CHECK(TestEnumHelper::Parse("Val2") == TestEnum::Val2); + CHECK(TestEnumHelper::Parse("Val3") == TestEnum::Val3); + CHECK_THROWS(TestEnumHelper::Parse("Val4")); + CHECK_THROWS(TestEnumHelper::Parse("val1")); +} + +TEST_CASE("Parse Enum case insensitive", "[Utilities]") { + CHECK(TestEnumHelper::Parse("Val1", true) == TestEnum::Val1); + CHECK(TestEnumHelper::Parse("Val2", true) == TestEnum::Val2); + CHECK(TestEnumHelper::Parse("Val3", true) == TestEnum::Val3); + CHECK(TestEnumHelper::Parse("val1", true) == TestEnum::Val1); + CHECK(TestEnumHelper::Parse("vAL2", true) == TestEnum::Val2); + CHECK(TestEnumHelper::Parse("VaL3", true) == TestEnum::Val3); + CHECK_THROWS(TestEnumHelper::Parse("Val4", true)); +} + +TEST_CASE("Enum To String", "[Utilities]") { + CHECK(TestEnumHelper::ToString(TestEnum::Val1) == "Val1"); + CHECK(TestEnumHelper::ToString(TestEnum::Val2) == "Val2"); + CHECK(TestEnumHelper::ToString(TestEnum::Val3) == "Val3"); +} + +TEST_CASE("Enum Get Values", "[Utilities]") { + auto vec = TestEnumHelper::GetValues(); + REQUIRE(vec.size() == 3); + CHECK(vec[0] == TestEnum::Val1); + CHECK(vec[1] == TestEnum::Val2); + CHECK(vec[2] == TestEnum::Val3); +} + +#endif \ No newline at end of file