Add easy to use macro to generate enum helper functions for parsing, stringifying and iteration.
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Deukhoofd 2020-02-12 19:48:56 +01:00
parent a8944e2026
commit 2732a904c4
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
8 changed files with 170 additions and 48 deletions

52
src/Core/Enum.hpp Normal file
View File

@ -0,0 +1,52 @@
#include <algorithm>
#include <string>
#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<uint32_t>(*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<uint32_t>(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<name> 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); \
} \
};

43
src/Core/MacroUtils.hpp Normal file
View File

@ -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__)

View File

@ -2,16 +2,10 @@
#define CREATURELIB_STATISTIC_HPP
#include <cstdint>
#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

View File

@ -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();
}
}

View File

@ -2,9 +2,10 @@
#define CREATURELIB_ATTACKCATEGORY_HPP
#include <cstdint>
#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

View File

@ -2,25 +2,16 @@
#define CREATURELIB_ATTACKTARGET_HPP
#include <cstdint>
#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

View File

@ -2,13 +2,14 @@
#define CREATURELIB_GENDER_HPP
#include <cstdint>
#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

40
tests/EnumTests.cpp Normal file
View File

@ -0,0 +1,40 @@
#ifdef TESTS_BUILD
#include <cstring>
#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