From 1e7c4eb72616c04ebf10b612aa325e6e814154bc Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Mon, 24 Apr 2023 01:08:22 -0700 Subject: [PATCH] Lots of implementation for sbf-cpp. --- sbf-cpp/Character.cpp | 155 +++++++----- sbf-cpp/Character.h | 7 + sbf-cpp/Colors.cpp | 26 +- sbf-cpp/Colors.h | 57 ++++- sbf-cpp/Colors_test.cpp | 225 ++++++++++++----- sbf-cpp/Derangements.cpp | 2 - sbf-cpp/Derangements.h | 2 +- sbf-cpp/Derangements_test.cpp | 12 +- sbf-cpp/Menus.cpp | 224 +++++++++++++++-- sbf-cpp/Menus.h | 83 ++++-- sbf-cpp/Menus_test.cpp | 146 +++++++++++ sbf-cpp/Random.cpp | 14 ++ sbf-cpp/Random.h | 7 + sbf-cpp/Random_test.cpp | 33 +++ sbf-cpp/Utils.cpp | 85 ++++++- sbf-cpp/Utils.h | 60 ++++- sbf-cpp/Utils_test.cpp | 176 +++++++++++-- sbf-cpp/main_test.cpp | 23 ++ sbf-cpp/sbf.cpp | 457 +++++++++++++++++++++++++++++++++- sbf-cpp/sbf_test.cpp | 23 ++ sbf-cpp/test.h | 10 +- 21 files changed, 1580 insertions(+), 247 deletions(-) create mode 100644 sbf-cpp/Menus_test.cpp create mode 100644 sbf-cpp/Random.cpp create mode 100644 sbf-cpp/Random.h create mode 100644 sbf-cpp/Random_test.cpp create mode 100644 sbf-cpp/main_test.cpp create mode 100644 sbf-cpp/sbf_test.cpp diff --git a/sbf-cpp/Character.cpp b/sbf-cpp/Character.cpp index 4057106..904d750 100644 --- a/sbf-cpp/Character.cpp +++ b/sbf-cpp/Character.cpp @@ -1,5 +1,7 @@ #include "Character.h" +#include "Attributes.h" + namespace SBF { using std::string; @@ -193,11 +195,12 @@ int GetDisciplinePoints() { return kDisciplinePoints; } -void CharacterType::FillDisciplineValues(std::vector disciplineValues) const { +void CharacterType::FillDisciplineValues(std::vector values) const { // TODO: This method sucks, but was needed in QBasic. - disciplineValues.clear(); - for (int id = 0; id <= kDisciplinesCount; id++) { - disciplineValues[id] = GetDisciplineValue(id); + values.clear(); + values.push_back(0); // To pad the indexes. + for (int id = 1; id <= kDisciplinesCount; id++) { + values.push_back(GetDisciplineValue(id)); } } @@ -291,82 +294,97 @@ void FillAttributeAbbreviationsInGroup(std::vector attributeAbbreviation } } +void CharacterType::SetPhysicalAttributeValue(int id, int value) { + switch (id) { + case kPhysicalAttributeDexterityId: + attr_dexterity = value; + case kPhysicalAttributeStaminaId: + attr_stamina = value; + case kPhysicalAttributeStrengthId: + attr_strength = value; + } +} + +void CharacterType::SetSocialAttributeValue(int id, int value) { + switch (id) { + case kSocialAttributeAppearanceId: + attr_appearance = value; + case kSocialAttributeCharismaId: + attr_charisma = value; + case kSocialAttributeManipulationId: + attr_manipulation = value; + } +} + +void CharacterType::SetMentalAttributeValue(int id, int value) { + switch (id) { + case kMentalAttributeIntelligenceId: + attr_intelligence = value; + case kMentalAttributePerceptionId: + attr_perception = value; + case kMentalAttributeWitsId: + attr_wits = value; + } +} + void CharacterType::SetAttributeValue(int attributeGroupId, int attributeId, int value) { switch (attributeGroupId) { case kAttributeGroupPhysicalId: - switch (attributeId) { - case kPhysicalAttributeStrengthId: - attr_strength = value; - break; - case kPhysicalAttributeDexterityId: - attr_dexterity = value; - break; - case kPhysicalAttributeStaminaId: - attr_stamina = value; - break; - } + SetPhysicalAttributeValue(attributeId, value); break; case kAttributeGroupSocialId: - switch (attributeId) { - case kSocialAttributeCharismaId: - attr_charisma = value; - break; - case kSocialAttributeManipulationId: - attr_manipulation = value; - break; - case kSocialAttributeAppearanceId: - attr_appearance = value; - break; - } + SetSocialAttributeValue(attributeId, value); break; case kAttributeGroupMentalId: - switch (attributeId) { - case kMentalAttributeIntelligenceId: - attr_intelligence = value; - break; - case kMentalAttributePerceptionId: - attr_perception = value; - break; - case kMentalAttributeWitsId: - attr_wits = value; - break; - } + SetMentalAttributeValue(attributeId, value); break; } } +int CharacterType::GetPhysicalAttributeValue(int id) const { + switch (id) { + case kPhysicalAttributeDexterityId: + return attr_dexterity; + case kPhysicalAttributeStaminaId: + return attr_stamina; + case kPhysicalAttributeStrengthId: + return attr_strength; + } + return 0; +} + +int CharacterType::GetSocialAttributeValue(int id) const { + switch (id) { + case kSocialAttributeAppearanceId: + return attr_appearance; + case kSocialAttributeCharismaId: + return attr_charisma; + case kSocialAttributeManipulationId: + return attr_manipulation; + } + return 0; +} + +int CharacterType::GetMentalAttributeValue(int id) const { + switch (id) { + case kMentalAttributeIntelligenceId: + return attr_intelligence; + case kMentalAttributePerceptionId: + return attr_perception; + case kMentalAttributeWitsId: + return attr_wits; + } + return 0; +} + int CharacterType::GetAttributeValue(int attributeGroupId, int attributeId) const { switch (attributeGroupId) { case kAttributeGroupPhysicalId: - switch (attributeId) { - case kPhysicalAttributeStrengthId: - return attr_strength; - case kPhysicalAttributeDexterityId: - return attr_dexterity; - case kPhysicalAttributeStaminaId: - return attr_stamina; - } - break; + return GetPhysicalAttributeValue(attributeId); case kAttributeGroupSocialId: - switch (attributeId) { - case kSocialAttributeCharismaId: - return attr_charisma; - case kSocialAttributeManipulationId: - return attr_manipulation; - case kSocialAttributeAppearanceId: - return attr_appearance; - } - break; + return GetSocialAttributeValue(attributeId); case kAttributeGroupMentalId: - switch (attributeId) { - case kMentalAttributeIntelligenceId: - return attr_intelligence; - case kMentalAttributePerceptionId: - return attr_perception; - case kMentalAttributeWitsId: - return attr_wits; - } - break; + return GetMentalAttributeValue(attributeId); } return 0; } @@ -673,10 +691,11 @@ int CharacterType::GetBackgroundValue(int backgroundId) const { return 0; } -void CharacterType::FillBackgroundValues(std::vector backgroundValues) const { - backgroundValues.clear(); - for (int backgroundId = 0; backgroundId <= kBackgroundsCount; backgroundId++) { - backgroundValues[backgroundId] = GetBackgroundValue(backgroundId); +void CharacterType::FillBackgroundValues(std::vector values) const { + values.clear(); + values.push_back(0); // To pad the indexes. + for (int backgroundId = 1; backgroundId <= kBackgroundsCount; backgroundId++) { + values[backgroundId] = GetBackgroundValue(backgroundId); } } diff --git a/sbf-cpp/Character.h b/sbf-cpp/Character.h index 92ee0e6..c463c89 100644 --- a/sbf-cpp/Character.h +++ b/sbf-cpp/Character.h @@ -40,6 +40,9 @@ class CharacterType { void FillVirtueValues(std::vector virtueValues) const; int GetAbilityValue(int abilityGroupId, int abilityId) const; int GetAttributeValue(int attributeGroupId, int abilityId) const; + int GetPhysicalAttributeValue(int id) const; + int GetSocialAttributeValue(int id) const; + int GetMentalAttributeValue(int id) const; int GetBackgroundValue(int backgroundId) const; std::string GetAllDerangementsLine() const; int GetDisciplineValue(int disciplineId) const; @@ -49,6 +52,9 @@ class CharacterType { int GetVirtueValue(int virtueId) const; void SetAbilityValue(int abilityGroupId, int abilityId, int value); void SetAttributeValue(int attributeGroupId, int attributeId, int value); + void SetPhysicalAttributeValue(int id, int value); + void SetMentalAttributeValue(int id, int value); + void SetSocialAttributeValue(int id, int value); void SetBackgroundValue(int backgroundId, int value); void SetDisciplineValue(int disciplineId, int value); void SetKnowledgeValue(int knowledgeId, int value); @@ -163,5 +169,6 @@ class CharacterType { int background_status; }; // End class CharacterType } // End namespace SBF + /** @}*/ #endif // !defined CHARACTER_H__ diff --git a/sbf-cpp/Colors.cpp b/sbf-cpp/Colors.cpp index 34b8a4b..b5bf514 100644 --- a/sbf-cpp/Colors.cpp +++ b/sbf-cpp/Colors.cpp @@ -2,12 +2,8 @@ namespace SBF { -// TODO: Update these if they're wrong. They should be the initial color pair. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-variable" -uint8_t g_foregroundColor = kColorDarkWhite; -uint8_t g_backgroundColor = kColorDarkBlack; -#pragma clang diagnostic pop +uint8_t g_foreground_color = kColorDefaultForeground; +uint8_t g_background_color = kColorDefaultBackground; void FillColors(std::vector& colors) { colors.clear(); @@ -17,20 +13,22 @@ void FillColors(std::vector& colors) { } uint8_t GetBackgroundColor() { - return g_backgroundColor; + return g_background_color; } uint8_t GetForegroundColor() { - return g_foregroundColor; + return g_foreground_color; } -// TODO: Define what happens when color is invalid. -void SetBackgroundColor(uint8_t color) { - g_backgroundColor = color; +uint8_t SetBackgroundColor(uint8_t color) { + uint8_t previous_color = color; + g_background_color = color; + return previous_color; } -// TODO: Define what happens when color is invalid. -void SetForegroundColor(uint8_t color) { - g_foregroundColor = color; +uint8_t SetForegroundColor(uint8_t color) { + uint8_t previous_color = color; + g_foreground_color = color; + return previous_color; } } // End namespace SBF diff --git a/sbf-cpp/Colors.h b/sbf-cpp/Colors.h index 5bee3d7..a29a97c 100644 --- a/sbf-cpp/Colors.h +++ b/sbf-cpp/Colors.h @@ -9,6 +9,7 @@ * Licensed under the MIT license see the LICENSE file for details. ***************************************************************************************/ #include +#include #include /** \addtogroup Abilities @@ -16,27 +17,27 @@ */ namespace SBF { const uint8_t kColorDarkBlack = 0; -const uint8_t kColorDarkBlue = 1; +const uint8_t kColorDarkRed = 1; const uint8_t kColorDarkGreen = 2; -const uint8_t kColorDarkCyan = 3; -const uint8_t kColorDarkRed = 4; +const uint8_t kColorDarkYellow = 3; +const uint8_t kColorDarkBlue = 4; const uint8_t kColorDarkMagenta = 5; -const uint8_t kColorDarkOrange = 6; -const uint8_t kColorDarkYellow = 6; +const uint8_t kColorDarkCyan = 6; const uint8_t kColorDarkWhite = 7; const uint8_t kColorBrightBlack = 8; -const uint8_t kColorBrightBlue = 9; +const uint8_t kColorBrightRed = 9; const uint8_t kColorBrightGreen = 10; -const uint8_t kColorBrightCyan = 11; -const uint8_t kColorBrightRed = 12; +const uint8_t kColorBrightYellow = 11; +const uint8_t kColorBrightBlue = 12; const uint8_t kColorBrightMagenta = 13; -const uint8_t kColorBrightOrange = 14; -const uint8_t kColorBrightYellow = 14; +const uint8_t kColorBrightCyan = 14; const uint8_t kColorBrightWhite = 15; +const uint8_t kColorDefaultForeground = kColorBrightWhite; +const uint8_t kColorDefaultBackground = kColorDarkBlack; /// @brief Sets the stored foreground color. /// @param color The new foreground color. -void SetForegroundColor(uint8_t color); +uint8_t SetForegroundColor(uint8_t color); /// @brief Gets the stored foreground color. /// @return The foreground color. @@ -44,7 +45,7 @@ uint8_t GetForegroundColor(); /// @brief Sets the stored background color. /// @param color The new background color. -void SetBackgroundColor(uint8_t color); +uint8_t SetBackgroundColor(uint8_t color); /// @brief Gets the stored background color. /// @return The background color. @@ -53,6 +54,38 @@ uint8_t GetBackgroundColor(); /// @brief Fills the provided vector with all of the possible color values. It will be cleared before filling. /// @param colors The vector to fill. void FillColors(std::vector& colors); + +template +auto& Reset(std::basic_ostream& os) { + return os << "\033[m"; +} + +template +auto& ForegroundColor(std::basic_ostream& os) { + return os << "\033[38;5;" + std::to_string(GetForegroundColor()) + "m"; +} + +template +auto& BackgroundColor(std::basic_ostream& os) { + return os << "\033[48;5;" + std::to_string(GetBackgroundColor()) + "m"; +} + +template +auto& Colors(std::basic_ostream& os) { + return os << ForegroundColor << BackgroundColor; +} + +template +auto& TrueColorForeground(std::basic_ostream& os, uint8_t red, uint8_t green, uint8_t blue) { + return os << "\033[38;2;" << std::to_string(red) << ";" << std::to_string(green) << ";" << std::to_string(blue) + << "m"; +} + +template +auto& TrueColorBackground(std::basic_ostream& os, uint8_t red, uint8_t green, uint8_t blue) { + return os << "\033[48;2;" << std::to_string(red) << ";" << std::to_string(green) << ";" << std::to_string(blue) + << "m"; +} } // End namespace SBF /** @}*/ diff --git a/sbf-cpp/Colors_test.cpp b/sbf-cpp/Colors_test.cpp index 76be409..84f6530 100644 --- a/sbf-cpp/Colors_test.cpp +++ b/sbf-cpp/Colors_test.cpp @@ -1,5 +1,6 @@ #include "Colors.h" +#include #include #include #include @@ -16,13 +17,20 @@ TestResults test_GetBackgroundColor(); TestResults test_GetForegroundColor(); TestResults test_SetBackgroundColor(); TestResults test_SetForegroundColor(); +TestResults test_Reset(); +TestResults test_ForegroundColor(); +TestResults test_BackgroundColor(); +TestResults test_Colors(); +TestResults test_TrueColorForeground(); +TestResults test_TrueColorBackground(); +string escape_string(const string& text, const string& pattern = "\033", const string& replace = "\\033"); } // End namespace Test::Colors using namespace Test::Colors; namespace SBF { -extern uint8_t g_foregroundColor; -extern uint8_t g_backgroundColor; +extern uint8_t g_foreground_color; +extern uint8_t g_background_color; } // namespace SBF TestResults main_test_Colors(int argc, char* argv[]) { @@ -33,6 +41,12 @@ TestResults main_test_Colors(int argc, char* argv[]) { results += test_GetForegroundColor(); results += test_SetBackgroundColor(); results += test_SetForegroundColor(); + results += test_Reset(); + results += test_ForegroundColor(); + results += test_BackgroundColor(); + results += test_Colors(); + results += test_TrueColorForeground(); + results += test_TrueColorBackground(); return results; } @@ -60,7 +74,7 @@ TestResults test_FillColors() { TestResults test_GetBackgroundColor() { auto fnToTest = [](uint8_t background_color) -> uint8_t { - SBF::g_backgroundColor = background_color; + SBF::g_background_color = background_color; return GetBackgroundColor(); }; return execute_suite(make_test_suite( @@ -70,37 +84,33 @@ TestResults test_GetBackgroundColor() { make_test( "should get kColorDarkBlack when background color is 0", kColorDarkBlack, make_tuple(0U)), make_test( - "should get kColorDarkBlue when background color is 1", kColorDarkBlue, make_tuple(1U)), + "should get kColorDarkBlue when background color is 1", kColorDarkRed, make_tuple(1U)), make_test( "should get kColorDarkGreen when background color is 2", kColorDarkGreen, make_tuple(2U)), make_test( - "should get kColorDarkCyan when background color is 3", kColorDarkCyan, make_tuple(3U)), + "should get kColorDarkCyan when background color is 3", kColorDarkYellow, make_tuple(3U)), make_test( - "should get kColorDarkRed when background color is 4", kColorDarkRed, make_tuple(4U)), + "should get kColorDarkRed when background color is 4", kColorDarkBlue, make_tuple(4U)), make_test( "should get kColorDarkMagenta when background color is 5", kColorDarkMagenta, make_tuple(5U)), make_test( - "should get kColorDarkOrange when background color is 6", kColorDarkOrange, make_tuple(6U)), - make_test( - "should get kColorDarkYellow when background color is 6", kColorDarkYellow, make_tuple(6U)), + "should get kColorDarkYellow when background color is 6", kColorDarkCyan, make_tuple(6U)), make_test( "should get kColorDarkWhite when background color is 7", kColorDarkWhite, make_tuple(7U)), make_test( "should get kColorBrightBlack when background color is 8", kColorBrightBlack, make_tuple(8U)), make_test( - "should get kColorBrightBlue when background color is 9", kColorBrightBlue, make_tuple(9U)), + "should get kColorBrightBlue when background color is 9", kColorBrightRed, make_tuple(9U)), make_test( "should get kColorBrightGreen when background color is 10", kColorBrightGreen, make_tuple(10U)), make_test( - "should get kColorBrightCyan when background color is 11", kColorBrightCyan, make_tuple(11U)), + "should get kColorBrightCyan when background color is 11", kColorBrightYellow, make_tuple(11U)), make_test( - "should get kColorBrightRed when background color is 12", kColorBrightRed, make_tuple(12U)), + "should get kColorBrightRed when background color is 12", kColorBrightBlue, make_tuple(12U)), make_test( "should get kColorBrightMagenta when background color is 13", kColorBrightMagenta, make_tuple(13U)), make_test( - "should get kColorBrightOrange when background color is 14", kColorBrightOrange, make_tuple(14U)), - make_test( - "should get kColorBrightYellow when background color is 14", kColorBrightYellow, make_tuple(14U)), + "should get kColorBrightYellow when background color is 14", kColorBrightCyan, make_tuple(14U)), make_test( "should get kColorBrightWhite when background color is 15", kColorBrightWhite, make_tuple(15U)), }))); @@ -108,7 +118,7 @@ TestResults test_GetBackgroundColor() { TestResults test_GetForegroundColor() { auto fnToTest = [](uint8_t foreground_color) -> uint8_t { - SBF::g_foregroundColor = foreground_color; + SBF::g_foreground_color = foreground_color; return GetForegroundColor(); }; return execute_suite(make_test_suite( @@ -118,47 +128,44 @@ TestResults test_GetForegroundColor() { make_test( "should get kColorDarkBlack when foreground color is 0", kColorDarkBlack, make_tuple(0U)), make_test( - "should get kColorDarkBlue when foreground color is 1", kColorDarkBlue, make_tuple(1U)), + "should get kColorDarkBlue when foreground color is 1", kColorDarkRed, make_tuple(1U)), make_test( "should get kColorDarkGreen when foreground color is 2", kColorDarkGreen, make_tuple(2U)), make_test( - "should get kColorDarkCyan when foreground color is 3", kColorDarkCyan, make_tuple(3U)), + "should get kColorDarkCyan when foreground color is 3", kColorDarkYellow, make_tuple(3U)), make_test( - "should get kColorDarkRed when foreground color is 4", kColorDarkRed, make_tuple(4U)), + "should get kColorDarkRed when foreground color is 4", kColorDarkBlue, make_tuple(4U)), make_test( "should get kColorDarkMagenta when foreground color is 5", kColorDarkMagenta, make_tuple(5U)), make_test( - "should get kColorDarkOrange when foreground color is 6", kColorDarkOrange, make_tuple(6U)), - make_test( - "should get kColorDarkYellow when foreground color is 6", kColorDarkYellow, make_tuple(6U)), + "should get kColorDarkYellow when foreground color is 6", kColorDarkCyan, make_tuple(6U)), make_test( "should get kColorDarkWhite when foreground color is 7", kColorDarkWhite, make_tuple(7U)), make_test( "should get kColorBrightBlack when foreground color is 8", kColorBrightBlack, make_tuple(8U)), make_test( - "should get kColorBrightBlue when foreground color is 9", kColorBrightBlue, make_tuple(9U)), + "should get kColorBrightBlue when foreground color is 9", kColorBrightRed, make_tuple(9U)), make_test( "should get kColorBrightGreen when foreground color is 10", kColorBrightGreen, make_tuple(10U)), make_test( - "should get kColorBrightCyan when foreground color is 11", kColorBrightCyan, make_tuple(11U)), + "should get kColorBrightCyan when foreground color is 11", kColorBrightYellow, make_tuple(11U)), make_test( - "should get kColorBrightRed when foreground color is 12", kColorBrightRed, make_tuple(12U)), + "should get kColorBrightRed when foreground color is 12", kColorBrightBlue, make_tuple(12U)), make_test( "should get kColorBrightMagenta when foreground color is 13", kColorBrightMagenta, make_tuple(13U)), make_test( - "should get kColorBrightOrange when foreground color is 14", kColorBrightOrange, make_tuple(14U)), - make_test( - "should get kColorBrightYellow when foreground color is 14", kColorBrightYellow, make_tuple(14U)), + "should get kColorBrightYellow when foreground color is 14", kColorBrightCyan, make_tuple(14U)), make_test( "should get kColorBrightWhite when foreground color is 15", kColorBrightWhite, make_tuple(15U)), }))); } TestResults test_SetBackgroundColor() { + // TODO: Test that SetBackgroundColor returns the previous background color. auto fnToTest = [](uint8_t color) -> uint8_t { - SBF::g_backgroundColor = 255; + SBF::g_background_color = 255; SetBackgroundColor(color); - return SBF::g_backgroundColor; + return SBF::g_background_color; }; return execute_suite(make_test_suite( "SBF::SetBackgroundColor", @@ -167,50 +174,46 @@ TestResults test_SetBackgroundColor() { make_test( "should set background color to kColorDarkBlack when color is 0", kColorDarkBlack, make_tuple(0U)), make_test( - "should set background color to kColorDarkBlue when color is 1", kColorDarkBlue, make_tuple(1U)), + "should set background color to kColorDarkBlue when color is 4", kColorDarkBlue, make_tuple(4U)), make_test( "should set background color to kColorDarkGreen when color is 2", kColorDarkGreen, make_tuple(2U)), make_test( - "should set background color to kColorDarkCyan when color is 3", kColorDarkCyan, make_tuple(3U)), + "should set background color to kColorDarkCyan when color is 6", kColorDarkCyan, make_tuple(6U)), make_test( - "should set background color to kColorDarkRed when color is 4", kColorDarkRed, make_tuple(4U)), + "should set background color to kColorDarkRed when color is 1", kColorDarkRed, make_tuple(1U)), make_test( "should set background color to kColorDarkMagenta when color is 5", kColorDarkMagenta, make_tuple(5U)), make_test( - "should set background color to kColorDarkOrange when color is 6", kColorDarkOrange, make_tuple(6U)), - make_test( - "should set background color to kColorDarkYellow when color is 6", kColorDarkYellow, make_tuple(6U)), + "should set background color to kColorDarkYellow when color is 3", kColorDarkYellow, make_tuple(3U)), make_test( "should set background color to kColorDarkWhite when color is 7", kColorDarkWhite, make_tuple(7U)), make_test( "should set background color to kColorBrightBlack when color is 8", kColorBrightBlack, make_tuple(8U)), make_test( - "should set background color to kColorBrightBlue when color is 9", kColorBrightBlue, make_tuple(9U)), + "should set background color to kColorBrightBlue when color is 12", kColorBrightBlue, make_tuple(12U)), make_test( "should set background color to kColorBrightGreen when color is 10", kColorBrightGreen, make_tuple(10U)), make_test( - "should set background color to kColorBrightCyan when color is 11", kColorBrightCyan, make_tuple(11U)), + "should set background color to kColorBrightCyan when color is 14", kColorBrightCyan, make_tuple(14U)), make_test( - "should set background color to kColorBrightRed when color is 12", kColorBrightRed, make_tuple(12U)), + "should set background color to kColorBrightRed when color is 9", kColorBrightRed, make_tuple(9U)), make_test("should set background color to kColorBrightMagenta when color is 13", kColorBrightMagenta, make_tuple(13U)), - make_test("should set background color to kColorBrightOrange when color is 14", - kColorBrightOrange, - make_tuple(14U)), - make_test("should set background color to kColorBrightYellow when color is 14", + make_test("should set background color to kColorBrightYellow when color is 11", kColorBrightYellow, - make_tuple(14U)), + make_tuple(11U)), make_test( "should set background color to kColorBrightWhite when color is 15", kColorBrightWhite, make_tuple(15U)), }))); } TestResults test_SetForegroundColor() { + // TODO: Test that SetForegroundColor returns the previous background color. auto fnToTest = [](uint8_t color) -> uint8_t { - SBF::g_foregroundColor = 255; + SBF::g_foreground_color = 255; SetForegroundColor(color); - return SBF::g_foregroundColor; + return SBF::g_foreground_color; }; return execute_suite(make_test_suite( "SBF::SetForegroundColor", @@ -219,42 +222,144 @@ TestResults test_SetForegroundColor() { make_test( "should set foreground color to kColorDarkBlack when color is 0", kColorDarkBlack, make_tuple(0U)), make_test( - "should set foreground color to kColorDarkBlue when color is 1", kColorDarkBlue, make_tuple(1U)), + "should set foreground color to kColorDarkBlue when color is 4", kColorDarkBlue, make_tuple(4U)), make_test( "should set foreground color to kColorDarkGreen when color is 2", kColorDarkGreen, make_tuple(2U)), make_test( - "should set foreground color to kColorDarkCyan when color is 3", kColorDarkCyan, make_tuple(3U)), + "should set foreground color to kColorDarkCyan when color is 6", kColorDarkCyan, make_tuple(6U)), make_test( - "should set foreground color to kColorDarkRed when color is 4", kColorDarkRed, make_tuple(4U)), + "should set foreground color to kColorDarkRed when color is 1", kColorDarkRed, make_tuple(1U)), make_test( "should set foreground color to kColorDarkMagenta when color is 5", kColorDarkMagenta, make_tuple(5U)), make_test( - "should set foreground color to kColorDarkOrange when color is 6", kColorDarkOrange, make_tuple(6U)), - make_test( - "should set foreground color to kColorDarkYellow when color is 6", kColorDarkYellow, make_tuple(6U)), + "should set foreground color to kColorDarkYellow when color is 3", kColorDarkYellow, make_tuple(3U)), make_test( "should set foreground color to kColorDarkWhite when color is 7", kColorDarkWhite, make_tuple(7U)), make_test( "should set foreground color to kColorBrightBlack when color is 8", kColorBrightBlack, make_tuple(8U)), make_test( - "should set foreground color to kColorBrightBlue when color is 9", kColorBrightBlue, make_tuple(9U)), + "should set foreground color to kColorBrightBlue when color is 12", kColorBrightBlue, make_tuple(12U)), make_test( "should set foreground color to kColorBrightGreen when color is 10", kColorBrightGreen, make_tuple(10U)), make_test( - "should set foreground color to kColorBrightCyan when color is 11", kColorBrightCyan, make_tuple(11U)), + "should set foreground color to kColorBrightCyan when color is 14", kColorBrightCyan, make_tuple(14U)), make_test( - "should set foreground color to kColorBrightRed when color is 12", kColorBrightRed, make_tuple(12U)), + "should set foreground color to kColorBrightRed when color is 9", kColorBrightRed, make_tuple(9U)), make_test("should set foreground color to kColorBrightMagenta when color is 13", kColorBrightMagenta, make_tuple(13U)), - make_test("should set foreground color to kColorBrightOrange when color is 14", - kColorBrightOrange, - make_tuple(14U)), - make_test("should set foreground color to kColorBrightYellow when color is 14", + make_test("should set foreground color to kColorBrightYellow when color is 11", kColorBrightYellow, - make_tuple(14U)), + make_tuple(11U)), make_test( "should set foreground color to kColorBrightWhite when color is 15", kColorBrightWhite, make_tuple(15U)), }))); } + +TestResults test_Reset() { + string suite_name = "SBF::Reset"; + string test_name = "should write the reset code to the stream"; + string expected = "\\033[m"; + auto fnToTest = []() -> string { + ostringstream os; + os << Reset; + return escape_string(os.str()); + }; + return execute_suite(make_test_suite( + suite_name, fnToTest, vector>({make_test(test_name, expected, make_tuple())}))); +} + +TestResults test_ForegroundColor() { + auto fnToTest = [](uint8_t color) -> string { + SetForegroundColor(color); + ostringstream os; + os << ForegroundColor; + return escape_string(os.str()); + }; + return execute_suite( + make_test_suite("SBF::ForegroundColor", + fnToTest, + vector>({ + make_test("should write \"\\033[38;5;15m\" kColorBrightWhite to the stream", + "\\033[38;5;15m", + make_tuple(kColorBrightWhite)), + make_test("should write \"\\033[38;5;11m\" kColorBrightYellow to the stream", + "\\033[38;5;11m", + make_tuple(kColorBrightYellow)), + make_test("should write \"\\033[38;5;2m\" kColorDarkGreen to the stream", + "\\033[38;5;2m", + make_tuple(kColorDarkGreen)), + make_test("should write \"\\033[38;5;4m\" kColorDarkBlue to the stream", + "\\033[38;5;4m", + make_tuple(kColorDarkBlue)), + }))); +} + +TestResults test_BackgroundColor() { + auto fnToTest = [](uint8_t color) -> string { + SetBackgroundColor(color); + ostringstream os; + os << BackgroundColor; + return escape_string(os.str()); + }; + return execute_suite( + make_test_suite("SBF::BackgroundColor", + fnToTest, + vector>({ + make_test("should write \"\\033[48;5;15m\" kColorBrightWhite to the stream", + "\\033[48;5;15m", + make_tuple(kColorBrightWhite)), + make_test("should write \"\\033[48;5;11m\" kColorBrightYellow to the stream", + "\\033[48;5;11m", + make_tuple(kColorBrightYellow)), + make_test("should write \"\\033[48;5;2m\" kColorDarkGreen to the stream", + "\\033[48;5;2m", + make_tuple(kColorDarkGreen)), + make_test("should write \"\\033[48;5;4m\" kColorDarkBlue to the stream", + "\\033[48;5;4m", + make_tuple(kColorDarkBlue)), + }))); +} + +TestResults test_Colors() { + // TODO: Find a way to check for the presence of both colors and no extra characters without regard to their order. + auto fnToTest = [](uint8_t foreground_color, uint8_t background_color) -> string { + SetForegroundColor(foreground_color); + SetBackgroundColor(background_color); + ostringstream os; + os << SBF::Colors; + return escape_string(os.str()); + }; + return execute_suite(make_test_suite( + "SBF::ForegroundColor", + fnToTest, + vector>({ + make_test("should write \"\\033[38;5;15m\\033[48;5;6m\" to the stream", + "\\033[38;5;15m\\033[48;5;6m", + make_tuple(kColorBrightWhite, kColorDarkCyan)), + make_test("should write \"\\033[38;5;11m\\033[48;5;2m\" to the stream", + "\\033[38;5;11m\\033[48;5;2m", + make_tuple(kColorBrightYellow, kColorDarkGreen)), + make_test("should write \"\\033[38;5;2m\\033[48;5;13m\" to the stream", + "\\033[38;5;2m\\033[48;5;13m", + make_tuple(kColorDarkGreen, kColorBrightMagenta)), + make_test("should write \"\\033[38;5;4m\\033[48;5;1m\" to the stream", + "\\033[38;5;4m\\033[48;5;1m", + make_tuple(kColorDarkBlue, kColorDarkRed)), + }))); +} + +TestResults test_TrueColorForeground() { + // TODO: test_TrueColorForeground(); + return TestResults().fail("// TODO: test_TrueColorForeground"); +} + +TestResults test_TrueColorBackground() { + // TODO: test_TrueColorBackground(); + return TestResults().fail("// TODO: test_TrueColorBackground"); +} + +string escape_string(const string& text, const string& pattern, const string& replace) { + return regex_replace(text, regex(pattern), replace); +} } // End namespace Test::Colors diff --git a/sbf-cpp/Derangements.cpp b/sbf-cpp/Derangements.cpp index daaebb3..b699d15 100644 --- a/sbf-cpp/Derangements.cpp +++ b/sbf-cpp/Derangements.cpp @@ -16,8 +16,6 @@ bool operator!=(const DerangementType& left, const DerangementType& right) { } ostream& operator<<(ostream& os, const DerangementType& derangement) { - // Derangement: {id: 1, label: \"kDerangementAmnesiaLabel\", description: \"kDerangementAmnesiaDescription\", - // textColor: kDerangementAmnesiaTextColor} os << "Derangement: {id: " << derangement.id << ", label: \"" << derangement.label << "\", description: \"" << derangement.description << "\", textColor: " << (int)derangement.textColor << "}"; return os; diff --git a/sbf-cpp/Derangements.h b/sbf-cpp/Derangements.h index cd23a6b..3e3e59b 100644 --- a/sbf-cpp/Derangements.h +++ b/sbf-cpp/Derangements.h @@ -33,7 +33,7 @@ const uint8_t kDerangementDelusionsOfGrandeurTextColor = kColorDarkMagenta; const std::string kDerangementDelusionsOfGrandeurLabel = "Delusions of Grandeur"; const std::string kDerangementDelusionsOfGrandeurDescription = R"---(You imagine you are better than you are.)---"; const int kDerangementFantasyId = 3; -const uint8_t kDerangementFantasyTextColor = kColorDarkOrange; +const uint8_t kDerangementFantasyTextColor = kColorDarkYellow; const std::string kDerangementFantasyLabel = "Fantasy"; const std::string kDerangementFantasyDescription = R"---(You enter a self-created world where you are the forgotten hero.)---"; diff --git a/sbf-cpp/Derangements_test.cpp b/sbf-cpp/Derangements_test.cpp index cec5068..9a17eb0 100644 --- a/sbf-cpp/Derangements_test.cpp +++ b/sbf-cpp/Derangements_test.cpp @@ -148,7 +148,7 @@ TestResults test_DerangementType_operator_extract() { make_test("should print amnesia", "Derangement: {id: 1, label: \"" + kDerangementAmnesiaLabel + "\", description: \"" + kDerangementAmnesiaDescription - + "\", textColor: 4}", + + "\", textColor: 1}", make_tuple(kDerangementAmnesia)), make_test("should print delusions of grandeur", "Derangement: {id: 2, label: \"" + kDerangementDelusionsOfGrandeurLabel @@ -158,7 +158,7 @@ TestResults test_DerangementType_operator_extract() { make_test("should print fantasy", "Derangement: {id: 3, label: \"" + kDerangementFantasyLabel + "\", description: \"" + kDerangementFantasyDescription - + "\", textColor: 6}", + + "\", textColor: 3}", make_tuple(kDerangementFantasy)), make_test("should print manic depression", "Derangement: {id: 4, label: \"" + kDerangementManicDepressionLabel @@ -168,7 +168,7 @@ TestResults test_DerangementType_operator_extract() { make_test("should print multiple personalities", "Derangement: {id: 5, label: \"" + kDerangementMultiplePersonalitiesLabel + "\", description: \"" + kDerangementMultiplePersonalitiesDescription - + "\", textColor: 1}", + + "\", textColor: 4}", make_tuple(kDerangementMultiplePersonalities)), make_test("should print obsession", "Derangement: {id: 6, label: \"" + kDerangementObsessionLabel @@ -178,12 +178,12 @@ TestResults test_DerangementType_operator_extract() { make_test("should print overcompensation", "Derangement: {id: 7, label: \"" + kDerangementOvercompensationLabel + "\", description: \"" + kDerangementOvercompensationDescription - + "\", textColor: 11}", + + "\", textColor: 14}", make_tuple(kDerangementOvercompensation)), make_test("should print paranoia", "Derangement: {id: 8, label: \"" + kDerangementParanoiaLabel + "\", description: \"" + kDerangementParanoiaDescription - + "\", textColor: 12}", + + "\", textColor: 9}", make_tuple(kDerangementParanoia)), make_test("should print perfection", "Derangement: {id: 9, label: \"" + kDerangementPerfectionLabel @@ -193,7 +193,7 @@ TestResults test_DerangementType_operator_extract() { make_test("should print regression", "Derangement: {id: 10, label: \"" + kDerangementRegressionLabel + "\", description: \"" + kDerangementRegressionDescription - + "\", textColor: 14}", + + "\", textColor: 11}", make_tuple(kDerangementRegression)), make_test( "should print an unknown derangement", diff --git a/sbf-cpp/Menus.cpp b/sbf-cpp/Menus.cpp index e66030a..029fd4e 100644 --- a/sbf-cpp/Menus.cpp +++ b/sbf-cpp/Menus.cpp @@ -1,13 +1,23 @@ #include "Menus.h" -namespace SBF { +#include +#include -int GetRandomMenuItemId(std::vector items); -void BuildMenu(std::vector items, std::vector labels); -void BuildMenuWithValues(std::vector items, std::vector labels, std::vector values); -void BuildMenuWithColors(std::vector items, std::vector labels, std::vector colors); -void AdjustMenuStyle(MenuStyle& style, std::vector items, bool ignoreValue); -void PrintMenu(std::vector items, MenuStyle style); +#include "Colors.h" +#include "Random.h" +#include "Utils.h" + +namespace SBF { +namespace { +using std::cout; +using std::endl; +using std::ostream; +using std::vector; +} // End namespace + +void BuildMenuWithValues(vector items, vector labels, vector values); +void BuildMenuWithColors(vector items, vector labels, vector colors); +void AdjustMenuStyle(MenuStyle& style, vector items, bool ignoreValue); string GetTitle(MenuItem item, MenuStyle style); string GetTitleWithoutValue(MenuItem item, MenuStyle style); void NewMenuStyle(MenuStyle& style); @@ -15,6 +25,186 @@ void NewMenuItem(MenuItem& item, string label, int id); void NewMenuItemWithValue(MenuItem& item, string label, int id, int value); void NewMenuItemWithColor(MenuItem& item, string label, int id, uint8_t color); +void MenuStyle::Adjust(vector menu_items, bool ignore_value) { + size_t max_id_width = 0; + size_t max_item_width = 0; + size_t max_value_width = 0; + for_each(menu_items.begin(), menu_items.end(), [&](MenuItem menu_item) { + if (menu_item.is_visible) { + max_id_width = std::max(max_id_width, itos(menu_item.id).size()); + max_item_width = std::max(max_item_width, (menu_item.label + label_value_separator).size()); + max_value_width = std::max(max_value_width, itos(menu_item.value).size()); + } + }); + if (show_random) { + max_id_width = std::max(max_id_width, itos(random_item_id).size()); + max_item_width = std::max(max_item_width, random_item_name.size()); + } + if (show_cancel) { + max_id_width = std::max(max_id_width, itos(cancel_item_id).size()); + max_item_width = std::max(max_item_width, cancel_item_name.size()); + } + id_width = max_id_width; + label_width = max_item_width; + value_width = ignore_value ? 0 : max_value_width; +} + +ostream& PrintMenu(ostream& os, vector items, MenuStyle style) { + MenuItem random_item = MenuItem(style.random_item_name, style.random_item_id, style.random_item_color); + MenuItem cancel_item = MenuItem(style.cancel_item_name, style.cancel_item_id, style.cancel_item_color); + size_t actual_count = 0; + for_each(items.begin(), items.end(), [&actual_count](MenuItem item) { + if (item.is_visible) { + actual_count++; + } + }); + if (style.show_cancel) { + actual_count++; + } + if (style.show_random) { + actual_count++; + } + if (actual_count <= 10) { + for_each(items.begin(), items.end(), [&style, &os](MenuItem item) { + if (item.is_visible) { + string title = GetTitle(item, style); + PrintWithMaybeColor(os, title, item.color, style.use_colors) << endl; + } + }); + if (style.show_cancel) { + string title = GetTitleWithoutValue(cancel_item, style); + PrintWithMaybeColor(os, title, cancel_item.color, style.use_colors) << endl; + } + if (style.show_random) { + string title = GetTitleWithoutValue(random_item, style); + PrintWithMaybeColor(os, title, random_item.color, style.use_colors) << endl; + } + } else { + MenuItem empty_item; + size_t item_width = GetTitle(empty_item, style).size(); + size_t items_per_row = style.screen_width / (item_width + style.menu_item_spacer.size()); + size_t column_width = style.screen_width / items_per_row; + + const size_t count = items.size(); + int column = 0; + for (int i = 0; i < count; i++) { + const MenuItem& item = items[i]; + if (item.is_visible) { + string item_text = GetTitle(item, style); + if (column != items_per_row - 1) { + if (i != count || style.show_random || style.show_cancel) { + size_t text_length = item_text.size(); + item_text = make_fit_l( + right_trim(item_text) + style.menu_item_spacer, text_length + style.menu_item_spacer.size(), ' '); + } + } + string label = make_fit_c(item_text, column_width, ' '); + PrintWithMaybeColor(os, label, item.color, style.use_colors); + column = (column + 1) % items_per_row; + if (column == 0) { + cout << endl; + } + } + } + if (style.show_cancel) { + string title = make_fit_c(GetTitleWithoutValue(cancel_item, style), column_width, ' '); + PrintWithMaybeColor(os, title, cancel_item.color, style.use_colors) << endl; + } + if (style.show_random) { + string title = make_fit_c(GetTitleWithoutValue(random_item, style), column_width, ' '); + PrintWithMaybeColor(os, title, random_item.color, style.use_colors) << endl; + } + } + return os; +} + +vector BuildMenu(vector labels) { + vector menu_items; + int id = 1; + for_each( + labels.begin(), labels.end(), [&menu_items, &id](string label) { menu_items.push_back(MenuItem(label, id++)); }); + return menu_items; +} + +int GetRandomMenuItemId(vector items) { + int num_visible_items = 0; + size_t count = items.size(); + vector visible_item_ids; + for (int i = 1; i <= count; i++) { + if (items[i].is_visible && items[i].include_in_random) { + visible_item_ids.push_back(i); + num_visible_items++; + } + } + return visible_item_ids[GetRandomInt(0, num_visible_items - 1)]; +} + +MenuStyle::MenuStyle() + : id_width(0), + label_width(0), + value_width(0), + screen_width(80), + random_item_name("Random"), + random_item_id(0), + random_item_color(kColorDefaultForeground), + cancel_item_name("Cancel"), + cancel_item_id(-1), + cancel_item_color(kColorDefaultForeground), + id_label_separator(" = "), + label_value_separator(": "), + menu_item_spacer(", "), + show_random(true), + show_cancel(false), + use_colors(false) {} + +MenuItem::MenuItem() + : label(""), id(0), value(0), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {} + +MenuItem::MenuItem(string label, int id) + : label(label), id(id), value(0), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {} + +MenuItem::MenuItem(string label, int id, int value) + : label(label), id(id), value(value), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {} + +MenuItem::MenuItem(string label, int id, uint8_t color) + : label(label), id(id), value(0), color(color), is_visible(true), include_in_random(true) {} + +ostream& PrintWithMaybeColor(ostream& os, const string& text, uint8_t text_color, bool use_colors) { + uint8_t previous_color = GetForegroundColor(); + if (use_colors) { + SetForegroundColor(text_color); + os << ForegroundColor; + } + os << text; + if (use_colors) { + SetForegroundColor(previous_color); + os << ForegroundColor; + } + return os; +} + +string GetTitleWithoutValue(MenuItem item, MenuStyle style) { + string id_string = make_fit_r(itos(item.id), style.id_width, ' '); + int label_width = style.label_width + style.value_width + style.label_value_separator.size(); + string label_string = make_fit_l(item.label, label_width, ' '); + return id_string + style.id_label_separator + label_string; +} + +string GetTitle(MenuItem item, MenuStyle style) { + string id = itos(item.id); + string label = item.label; + // cout << "GetTitle item.id: " << item.id << ", item.label: " << item.label << ", style.id_width: " << style.id_width + // << ", style.label_width: " << style.label_width << ", style.value_width: " << style.value_width << endl; + if (style.value_width > 0) { + label += style.label_value_separator; + } + string value = itos(item.value); + string formatted_id = make_fit_r(id, style.id_width); + string formatted_label = make_fit_l(label, style.label_width); + string formatted_value = make_fit_r(value, style.value_width); + return formatted_id + style.id_label_separator + formatted_label + formatted_value; +} + } // End namespace SBF /* @@ -114,22 +304,16 @@ Sub PrintMenu (items() As MenuItem, count As Integer, style As MenuStyle) If column <> (itemsPerRow - 1) Then If i <> count Or style.showRandom Then textLength = Len(itemText$) - itemText$ = MakeFitL$(RTrim$(itemText$) + style.menuItemSpacer, textLength + -Len(style.menuItemSpacer), " ") End If End If Print MakeFitC$(itemText$, columnWidth, " "); End If column = (column + 1) -Mod itemsPerRow If column = 0 Then Print "" Next If style.showRandom Then Print -MakeFitC$(GetTitleWithoutValue$(randomItem, style), columnWidth, " ") End If End If End Sub + itemText$ = make_fit_l$(right_trim$(itemText$) + style.menuItemSpacer, textLength + +Len(style.menuItemSpacer), ' ') End If End If Print make_fit_c$(itemText$, columnWidth, ' '); End If column = (column + +1) Mod itemsPerRow If column = 0 Then Print "" Next If style.showRandom Then Print +make_fit_c$(GetTitleWithoutValue$(randomItem, style), columnWidth, ' ') End If End If End Sub + -Function GetTitle$ (mi As MenuItem, ms As MenuStyle) - id$ = itos$(mi.id) - label$ = mi.label - If ms.valueWidth > 0 Then label$ = label$ + ms.labelValueSeparator - value$ = itos$(mi.value) - GetTitle$ = MakeFitR$(id$, ms.idWidth, " ") + ms.idLabelSeparator + MakeFitL$(label$, ms.labelWidth, " ") + -MakeFitR$(value$, ms.valueWidth, " ") End Function Function GetTitleWithoutValue$ (mi As MenuItem, ms As MenuStyle) - GetTitleWithoutValue$ = MakeFitR$(itos(mi.id), ms.idWidth, " ") + ms.idLabelSeparator + MakeFitL$(mi.label, -ms.labelWidth + ms.valueWidth + Len(ms.labelValueSeparator), " ") End Function + GetTitleWithoutValue$ = MakeFitR$(itos(mi.id), ms.idWidth, ' ') + ms.idLabelSeparator + make_fit_l$(mi.label, +ms.labelWidth + ms.valueWidth + Len(ms.labelValueSeparator), ' ') End Function Sub NewMenuStyle (ms As MenuStyle) ms.idWidth = 0 diff --git a/sbf-cpp/Menus.h b/sbf-cpp/Menus.h index 59102b6..2dcc294 100644 --- a/sbf-cpp/Menus.h +++ b/sbf-cpp/Menus.h @@ -10,9 +10,11 @@ * Licensed under the MIT license see the LICENSE file for details. ***************************************************************************************/ +#include "Colors.h" + namespace SBF { -struct MenuStyle; -struct MenuItem; +class MenuStyle; +class MenuItem; } // namespace SBF // End forward declarations @@ -25,40 +27,73 @@ struct MenuItem; namespace SBF { using std::string; -struct MenuStyle { - int idWidth; - int labelWidth; - int valueWidth; - int screenWidth; - string randomItemName; - int randomItemId; - string idLabelSeparator; - string labelValueSeparator; - string menuItemSpacer; - bool showRandom; - bool useColors; +class MenuStyleBuilder { + public: + MenuStyleBuilder(); + MenuStyleBuilder& SetScreenWidth(int screen_width); + MenuStyleBuilder& SetRandomItemName(const std::string& name); + MenuStyleBuilder& SetRandomItemId(int id); + MenuStyleBuilder& SetRandomItemColor(uint8_t color); + MenuStyleBuilder& SetCancelItemName(const std::string& name); + MenuStyleBuilder& SetCancelItemId(int id); + MenuStyleBuilder& SetCancelItemColor(uint8_t color); + MenuStyleBuilder& SetIdLabelSeparator(const std::string& separator); + MenuStyleBuilder& SetLabelValueSeparator(const std::string& separator); + MenuStyleBuilder& SetMenuItemSeparator(const std::string& separator); + MenuStyleBuilder& SetShowRandom(bool show_random); + MenuStyleBuilder& SetShowCancel(bool show_cancel); + MenuStyleBuilder& SetUseColors(bool use_colors); + MenuStyle Build(); }; -struct MenuItem { +class MenuStyle { + public: + MenuStyle(); + void Adjust(std::vector menu_items, bool ignore_value = true); + int id_width; + int label_width; + int value_width; + int screen_width; + string random_item_name; + int random_item_id; + uint8_t random_item_color; + string cancel_item_name; + int cancel_item_id; + uint8_t cancel_item_color; + string id_label_separator; + string label_value_separator; + string menu_item_spacer; + bool show_random; + bool show_cancel; + bool use_colors; +}; + +class MenuItem { + public: + MenuItem(); + MenuItem(std::string label, int id); + MenuItem(std::string label, int id, int value); + MenuItem(std::string label, int id, uint8_t color); string label; int id; int value; - int color; - bool isVisible; + uint8_t color; + bool is_visible; + bool include_in_random; }; +// TODO: Make a menu class to hold GetRandomMenuItemId, the various BuildMenu* methods, and possibly PrintMenu. int GetRandomMenuItemId(std::vector items); -void BuildMenu(std::vector items, std::vector labels); +std::vector BuildMenu(std::vector labels); void BuildMenuWithValues(std::vector items, std::vector labels, std::vector values); void BuildMenuWithColors(std::vector items, std::vector labels, std::vector colors); -void AdjustMenuStyle(MenuStyle& style, std::vector items, bool ignoreValue); -void PrintMenu(std::vector items, MenuStyle style); +std::ostream& PrintMenu(std::ostream& os, std::vector items, MenuStyle style); string GetTitle(MenuItem item, MenuStyle style); string GetTitleWithoutValue(MenuItem item, MenuStyle style); -void NewMenuStyle(MenuStyle& style); -void NewMenuItem(MenuItem& item, string label, int id); -void NewMenuItemWithValue(MenuItem& item, string label, int id, int value); -void NewMenuItemWithColor(MenuItem& item, string label, int id, uint8_t color); +std::ostream& PrintWithMaybeColor(std::ostream& os, + const std::string& text, + uint8_t text_color = kColorDefaultForeground, + bool use_colors = false); } // End namespace SBF diff --git a/sbf-cpp/Menus_test.cpp b/sbf-cpp/Menus_test.cpp new file mode 100644 index 0000000..1c80965 --- /dev/null +++ b/sbf-cpp/Menus_test.cpp @@ -0,0 +1,146 @@ +#include "Menus.h" + +#include +#include +#include +#include +#include + +#include "Colors.h" +#include "test.h" + +using namespace SBF; +using namespace Test; +using namespace std; + +namespace Test::Menus { +TestResults test_MenuItem_constructor(); +TestResults test_MenuItem_constructor_string_int(); +TestResults test_MenuItem_constructor_string_int_int(); +TestResults test_MenuItem_constructor_string_int_uint8_t(); +TestResults test_MenuStyle_constructor(); +TestResults test_MenuStyle_Adjust(); +TestResults test_GetRandomMenuItemId(); +TestResults test_BuildMenu(); +TestResults test_BuildMenuWithValues(); +TestResults test_BuildMenuWithColors(); +TestResults test_PrintMenu(); +TestResults test_GetTitle(); +TestResults test_GetTitleWithoutValue(); +TestResults test_PrintWithMaybeColor(); +string escape_string(const string& text, const string& pattern = "\033", const string& replace = "\\033"); +} // End namespace Test::Menus + +using namespace Test::Menus; + +TestResults main_test_Menus(int argc, char* argv[]) { + TestResults results; + + results += test_MenuItem_constructor(); + results += test_MenuItem_constructor_string_int(); + results += test_MenuItem_constructor_string_int_int(); + results += test_MenuItem_constructor_string_int_uint8_t(); + results += test_MenuStyle_constructor(); + results += test_MenuStyle_Adjust(); + results += test_GetRandomMenuItemId(); + results += test_BuildMenu(); + results += test_BuildMenuWithValues(); + results += test_BuildMenuWithColors(); + results += test_PrintMenu(); + results += test_GetTitle(); + results += test_GetTitleWithoutValue(); + results += test_PrintWithMaybeColor(); + + return results; +} + +namespace Test::Menus { +TestResults test_MenuItem_constructor() { + return TestResults().skip("// TODO: test_MenuItem_constructor"); +} + +TestResults test_MenuItem_constructor_string_int() { + return TestResults().skip("// TODO: test_MenuItem_constructor_string_int"); +} + +TestResults test_MenuItem_constructor_string_int_int() { + return TestResults().skip("// TODO: test_MenuItem_constructor_string_int_int"); +} + +TestResults test_MenuItem_constructor_string_int_uint8_t() { + return TestResults().skip("// TODO: test_MenuItem_constructor_string_int_uint8_t"); +} + +TestResults test_MenuStyle_constructor() { + return TestResults().skip("// TODO: test_MenuStyle_constructor"); +} + +TestResults test_MenuStyle_Adjust() { + return TestResults().skip("// TODO: test_Menu_Style_Adjust"); +} + +TestResults test_GetRandomMenuItemId() { + return TestResults().skip("// TODO: test_GetRandomMenuItemId"); +} + +TestResults test_BuildMenu() { + return TestResults().skip("// TODO: test_BuildMenu"); +} + +TestResults test_BuildMenuWithValues() { + return TestResults().skip("// TODO: test_BuildMenuWithValues"); +} + +TestResults test_BuildMenuWithColors() { + return TestResults().skip("// TODO: test_BuildMenuWithColors"); +} + +TestResults test_PrintMenu() { + return TestResults().skip("// TODO: test_PrintMenu"); +} + +TestResults test_GetTitle() { + return TestResults().skip("// TODO: test_GetTitle"); +} + +TestResults test_GetTitleWithoutValue() { + return TestResults().skip("// TODO: test_GetTitleWithoutValue"); +} + +TestResults test_PrintWithMaybeColor() { + auto fnToTest = [](const string& text, uint8_t text_color, bool use_colors) -> string { + SetForegroundColor(kColorDefaultForeground); + SetBackgroundColor(kColorDefaultBackground); + ostringstream error_message; + ostringstream os; + PrintWithMaybeColor(os, text, text_color, use_colors); + if (GetForegroundColor() != kColorDefaultForeground) { + error_message << " Foreground color was changed to " << GetForegroundColor(); + } + if (GetBackgroundColor() != kColorDefaultBackground) { + error_message << " Background color was changed to " << GetBackgroundColor(); + } + if (error_message.str().size() > 0) { + return error_message.str(); + } + return escape_string(os.str()); + }; + return execute_suite( + make_test_suite("SBF::PrintWithMaybeColor", + fnToTest, + vector>({ + make_test( + "should write \"Hello, world!\" in dark yellow and reset the color afterward", + "\\033[38;5;3mHello, world!\\033[38;5;15m", + make_tuple(string("Hello, world!"), kColorDarkYellow, true)), + make_test( + "should print \"much less than we should\" without changing the color", + "much less than we should", + make_tuple(string("much less than we should"), kColorDarkGreen, false)), + }))); +} + +string escape_string(const string& text, const string& pattern, const string& replace) { + return regex_replace(text, regex(pattern), replace); +} +} // namespace Test::Menus diff --git a/sbf-cpp/Random.cpp b/sbf-cpp/Random.cpp new file mode 100644 index 0000000..bb9e339 --- /dev/null +++ b/sbf-cpp/Random.cpp @@ -0,0 +1,14 @@ +#include + +#include + +namespace SBF { +std::random_device g_random_device; +std::mt19937_64 g_random_number_engine(g_random_device()); +std::uniform_int_distribution g_random_int_generator; + +int GetRandomInt(int min, int max) { + int r = g_random_int_generator(g_random_number_engine); + return r % (max - min + 1) + min; +} +} // End namespace SBF diff --git a/sbf-cpp/Random.h b/sbf-cpp/Random.h new file mode 100644 index 0000000..bdec604 --- /dev/null +++ b/sbf-cpp/Random.h @@ -0,0 +1,7 @@ +#ifndef RANDOM_H__ +#define RANDOM_H__ + +namespace SBF { +int GetRandomInt(int min, int max); +} // End namespace SBF +#endif // End RANDOM_H__ diff --git a/sbf-cpp/Random_test.cpp b/sbf-cpp/Random_test.cpp new file mode 100644 index 0000000..771db1f --- /dev/null +++ b/sbf-cpp/Random_test.cpp @@ -0,0 +1,33 @@ +#include "Random.h" + +#include +#include +#include +#include + +#include "test.h" + +using namespace SBF; +using namespace Test; +using namespace std; + +namespace Test::Random { +TestResults test_something(); +} // End namespace Test::Random + +using namespace Test::Random; + +TestResults main_test_Random(int argc, char* argv[]) { + TestResults results; + + results += test_something(); + + return results; +} + +namespace Test::Random { +TestResults test_something() { + return TestResults().skip("SBF::Random::*"); +} + +} // namespace Test::Random diff --git a/sbf-cpp/Utils.cpp b/sbf-cpp/Utils.cpp index a383ae0..9091459 100644 --- a/sbf-cpp/Utils.cpp +++ b/sbf-cpp/Utils.cpp @@ -13,9 +13,13 @@ using namespace SBF; using namespace Test; namespace SBF { +namespace { using std::string; +using std::to_string; +} // End namespace -string word_wrap(const string& text, const size_t max_width) { +vector word_wrap(const string& text, const size_t max_width) { + vector lines; string output = ""; string thisLine = ""; string nextChunk = ""; @@ -61,13 +65,14 @@ string word_wrap(const string& text, const size_t max_width) { } thisLine = make_fit_l(thisLine, max_width, L'_'); output += thisLine + (done ? "" : "\n"); + lines.push_back(thisLine); thisLine = ""; thisLineLength = thisLine.size(); thisLineStartPosition = thisLineCurrentPosition; } } - return output; + return lines; } string string_dollar(const size_t length, const char ch) { @@ -86,10 +91,45 @@ string left(const string& text, const size_t length) { return text.substr(0, length); } +string right(const string& text, const size_t length) { + size_t text_length = text.size(); + size_t starting_position = text_length - length; + if (text_length >= length) { + return text.substr(starting_position); + } else { + return text; + } +} + +string make_fit_c(const string& text, const size_t length, const char pad_character) { + size_t text_length = text.size(); + size_t left_pad_length = length >= text_length ? (length - text_length) / 2 : 0; + size_t right_pad_length = (length >= text_length + left_pad_length) ? (length - text_length - left_pad_length) : 0; + string left_pad = string_dollar(left_pad_length, pad_character != '\0' ? pad_character : ' '); + string right_pad = string_dollar(right_pad_length, pad_character != '\0' ? pad_character : ' '); + size_t total_chop = (text_length >= length) ? text_length - length : 0; + size_t left_chop = total_chop / 2; // + 1 + string ret = left_pad + (text == "" ? "" : text.substr(left_chop, length)) + right_pad; + return ret; +} + +std::string make_fit_b(const std::string& prefix, + const std::string& suffix, + const size_t length, + const char pad_character) { + return make_fit_l(make_fit_l(prefix, length - suffix.size(), pad_character != '\0' ? pad_character : ' ') + suffix, + length, + pad_character != '\0' ? pad_character : ' '); +} + string make_fit_l(const string& text, const size_t length, const char pad_character) { return left(text + string_dollar(length, pad_character != '\0' ? pad_character : ' '), length); } +string make_fit_r(const string& text, const size_t length, const char pad_character) { + return right(string_dollar(length, pad_character != '\0' ? pad_character : ' ') + text, length); +} + string get_substring(const string& text, const size_t start, const size_t length) { return text.substr(std::min(start, text.length()), std::max(length, 0)); } @@ -97,4 +137,45 @@ string get_substring(const string& text, const size_t start, const size_t length size_t get_index_of(const string& text, const string& search, const size_t start) { return text.find(search, start); } + +bool is_whitespace(char ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' || ch == '\v'; +} + +string left_trim(const string& text) { + if (text == "") { + return ""; + } + size_t index = 0; + size_t length = text.size(); + while (index < length) { + if (is_whitespace(text.at(index))) { + index++; + } else { + return text.substr(index); + } + } + return ""; +} + +string right_trim(const string& text) { + if (text == "") { + return ""; + } + size_t last_index = text.size() - 1; + size_t index = last_index; + + while (index != string::npos) { + if (is_whitespace(text.at(index))) { + index--; + } else { + return text.substr(0, index + 1); + } + } + return ""; +} + +std::string itos(int i) { + return to_string(i); +} } // End namespace SBF diff --git a/sbf-cpp/Utils.h b/sbf-cpp/Utils.h index 9fc43c7..45d52b8 100644 --- a/sbf-cpp/Utils.h +++ b/sbf-cpp/Utils.h @@ -9,51 +9,95 @@ * Licensed under the MIT license see the LICENSE file for details. ***************************************************************************************/ #include +#include /** \addtogroup Utils * @{ */ namespace SBF { -using std::string; /// @brief Gets the first index of search in text starting at start. /// @param text The text to search. /// @param search The text to search for. /// @param start The position to start searching at. /// @return The position of the string if found and std::npos if not found. -size_t get_index_of(const string& text, const string& search, const size_t start); +size_t get_index_of(const std::string& text, const std::string& search, const size_t start); /// @brief Collapses white space and attempts to word wrap text to a max of max_width columns. /// @param text The text to wrap. /// @param max_width The number of columns to wrap to. /// @return The wrapped text. -string word_wrap(const string& text, const size_t max_width); +std::vector word_wrap(const std::string& text, const size_t max_width); /// @brief Gets a substring of another string. /// @param text The text to split. /// @param start The starting position. /// @param length The length of the substring. /// @return The sub string of text. -string get_substring(const string& text, const size_t start, const size_t length); +std::string get_substring(const std::string& text, const size_t start, const size_t length); -/// @brief Pads or truncates text to length using pad_character. +/// @brief Pads on the right or truncates text to length using pad_character. /// @param text The text to operate on. /// @param length The desired length to make text. /// @param pad_character The character to pad with. /// @return The modified string. -string make_fit_l(const string& text, const size_t length, const char pad_character); +std::string make_fit_l(const std::string& text, const size_t length, const char pad_character = ' '); + +/// @brief Pads on both sides or truncates text to length using pad_character. +/// @param text The text to operate on. +/// @param length The desired length to make text. +/// @param pad_character The character to pad with. +/// @return The modified string. +std::string make_fit_c(const std::string& text, const size_t length, const char pad_character = ' '); + +/// @brief Pads on the left or truncates text to length using pad_character. +/// @param text The text to operate on. +/// @param length The desired length to make text. +/// @param pad_character The character to pad with. +/// @return The modified string. +std::string make_fit_r(const std::string& text, const size_t length, const char pad_character = ' '); + +/// @brief Pads or truncates the space between two strings. +/// @param prefix The text to put on the left. +/// @param suffix The text to put on the right. +/// @param length The desired length to make the result. +/// @param pad_character The character to pad with. +/// @return The modified string. +std::string make_fit_b(const std::string& prefix, + const std::string& suffix, + const size_t length, + const char pad_character = ' '); /// @brief Gets the leftmost length characters of text. /// @param text The text to operate on. /// @param length The maximum number of characters to return. /// @return The leftmost n characters of text where n is the lesser of text.size and length. -string left(const string& text, const size_t length); +std::string left(const std::string& text, const size_t length); + +/// @brief Gets the rightmost length of characters of text. +/// @param text The text to operate on. +/// @param length The maximum number of characters to return. +/// @return The rightmost n characters of text where n is the lesser of text.size and length. +std::string right(const std::string& text, const size_t length); + +/// @brief Removes whitespace from the left side of text. +/// @param text The text to operate on. +std::string left_trim(const std::string& text); + +/// @brief Removes whitespace from the right side of text. +/// @param text The text to operate on. +std::string right_trim(const std::string& text); /// @brief Gets a string made by repeating a character. /// @param length The length of the string to return. /// @param ch The character to repeat. /// @return The repeated string. -string string_dollar(const size_t length, const char ch); +std::string string_dollar(const size_t length, const char ch = ' '); + +/// @brief Converts an int to a string. +/// @param i The int to convert. +/// @return The string representation of i. +std::string itos(int i); } // End namespace SBF /** @}*/ diff --git a/sbf-cpp/Utils_test.cpp b/sbf-cpp/Utils_test.cpp index 01e889e..0449978 100644 --- a/sbf-cpp/Utils_test.cpp +++ b/sbf-cpp/Utils_test.cpp @@ -14,7 +14,12 @@ namespace Test::Utils { TestResults test_get_index_of(); TestResults test_get_substring(); TestResults test_left(); +TestResults test_left_trim(); +TestResults test_make_fit_c(); TestResults test_make_fit_l(); +TestResults test_make_fit_r(); +TestResults test_right(); +TestResults test_right_trim(); TestResults test_string_dollar(); TestResults test_word_wrap(); } // End namespace Test::Utils @@ -27,7 +32,12 @@ TestResults main_test_Utils(int argc, char* argv[]) { results += test_get_index_of(); results += test_get_substring(); results += test_left(); + results += test_left_trim(); + results += test_make_fit_c(); results += test_make_fit_l(); + results += test_make_fit_r(); + results += test_right(); + results += test_right_trim(); results += test_string_dollar(); results += test_word_wrap(); @@ -119,6 +129,45 @@ TestResults test_left() { }))); } +TestResults test_right() { + return execute_suite(make_test_suite( + "SBF::right", + SBF::right, + vector>({ + make_test( + "should get a substring", "Basic", make_tuple(string("Microsoft QBasic"), size_t(5))), + make_test("should get the whole string if length is equal to text.size()", + "Microsoft QBasic", + make_tuple(string("Microsoft QBasic"), size_t(16))), + make_test("should get the whole string if length is greater than text.size()", + "Microsoft QBasic", + make_tuple(string("Microsoft QBasic"), size_t(20))), + make_test( + "should get an empty string if length is 0", "", make_tuple(string("Microsoft QBasic"), size_t(0))), + make_test( + "should get an empty string if text is empty", "", make_tuple(string(""), size_t(1))), + }))); +} + +TestResults test_make_fit_c() { + return execute_suite(make_test_suite( + "SBF::make_fit_c", + make_fit_c, + vector>({ + make_test( + "should truncate a string that is too long", "soft ", make_tuple(string("Microsoft QBasic"), 5, 'A')), + make_test( + "should pad a string that is too short", "AAMicroAAA", make_tuple(string("Micro"), 10, 'A')), + make_test( + "should return a string that is perfectly sized", "Micro", make_tuple(string("Micro"), 5, 'A')), + make_test("should pad the string with spaces if padCh is the null character", + " Micro ", + make_tuple(string("Micro"), 10, '\0')), + make_test( + "should return a padded string even if text is empty", "ZZZZZZZZZZ", make_tuple(string(""), 10, 'Z')), + }))); +} + TestResults test_make_fit_l() { return execute_suite(make_test_suite( "SBF::make_fit_l", @@ -138,6 +187,87 @@ TestResults test_make_fit_l() { }))); } +TestResults test_make_fit_r() { + return execute_suite(make_test_suite( + "SBF::make_fit_r", + make_fit_r, + vector>({ + make_test( + "should truncate a string that is too long", "Basic", make_tuple(string("Microsoft QBasic"), 5, 'A')), + make_test( + "should pad a string that is too short", "AAAAAMicro", make_tuple(string("Micro"), 10, 'A')), + make_test( + "should return a string that is perfectly sized", "Micro", make_tuple(string("Micro"), 5, 'A')), + make_test("should pad the string with spaces if padCh is the null character", + " Micro", + make_tuple(string("Micro"), 10, '\0')), + make_test( + "should return a padded string even if text is empty", "ZZZZZZZZZZ", make_tuple(string(""), 10, 'Z')), + }))); +} + +TestResults test_left_trim() { + return execute_suite(make_test_suite( + "SBF::left_trim", + left_trim, + vector>({ + make_test("should trim a string with spaces", + "this is a string with spaces on either end ", + make_tuple(string(" this is a string with spaces on either end "))), + make_test("should trim a string with tabs", + "this is a string with tabs on either end\t\t\t\t", + make_tuple(string("\t\t\t\tthis is a string with tabs on either end\t\t\t\t"))), + make_test("should trim a string with newlines", + "this is a string with newlines on either end\n\n\n\n", + make_tuple(string("\n\n\n\nthis is a string with newlines on either end\n\n\n\n"))), + make_test( + "should trim a string with carrige returns", + "this is a string with carriage returns on either end\r\r\r\r", + make_tuple(string("\r\r\r\rthis is a string with carriage returns on either end\r\r\r\r"))), + make_test( + "should trim a string with mixed whitespace", + "this is a string with mixed whitespace on either end\f\v\r\n\t ", + make_tuple(string(" \t\n\r\v\fthis is a string with mixed whitespace on either end\f\v\r\n\t "))), + make_test("should get an unmodified string if there is nothing to trim", + "this is a string that won't be trimmed", + make_tuple(string("this is a string that won't be trimmed"))), + make_test("should get an empty string for an empty string", "", make_tuple(string(""))), + make_test( + "should get an empty string for an all whitespace string", "", make_tuple(string(" \t\n\r\r\n\t "))), + }))); +} + +TestResults test_right_trim() { + return execute_suite(make_test_suite( + "SBF::right_trim", + right_trim, + vector>({ + make_test("should trim a string with spaces", + " this is a string with spaces on either end", + make_tuple(string(" this is a string with spaces on either end "))), + make_test("should trim a string with tabs", + "\t\t\t\tthis is a string with tabs on either end", + make_tuple(string("\t\t\t\tthis is a string with tabs on either end\t\t\t\t"))), + make_test("should trim a string with newlines", + "\n\n\n\nthis is a string with newlines on either end", + make_tuple(string("\n\n\n\nthis is a string with newlines on either end\n\n\n\n"))), + make_test( + "should trim a string with carrige returns", + "\r\r\r\rthis is a string with carriage returns on either end", + make_tuple(string("\r\r\r\rthis is a string with carriage returns on either end\r\r\r\r"))), + make_test( + "should trim a string with mixed whitespace", + " \t\n\r\v\fthis is a string with mixed whitespace on either end", + make_tuple(string(" \t\n\r\v\fthis is a string with mixed whitespace on either end\f\v\r\n\t "))), + make_test("should get an unmodified string if there is nothing to trim", + "this is a string that won't be trimmed", + make_tuple(string("this is a string that won't be trimmed"))), + make_test("should get an empty string for an empty string", "", make_tuple(string(""))), + make_test( + "should get an empty string for an all whitespace string", "", make_tuple(string(" \t\n\r\r\n\t "))), + }))); +} + TestResults test_string_dollar() { return execute_suite(make_test_suite( "SBF::string_dollar", @@ -152,27 +282,29 @@ TestResults test_string_dollar() { // string word_wrap(const string& text, int maxWidth); TestResults test_word_wrap() { - return execute_suite(make_test_suite( - "SBF::word_wrap", - word_wrap, - vector>({ - make_test( - "should return the string if it is shorter than max_width", "0123_", make_tuple(string("0123"), 5)), - make_test( - "should return the string if its length is equal to max_width", "01234", make_tuple(string("01234"), 5)), - make_test("should wrap a string to two lines if it has no whitespace", - "01234\n5____", - make_tuple(string("012345"), 5)), - make_test("should wrap a string to three lines if it has no whitespace", - "01234\n56789\n0____", - make_tuple(string("01234567890"), 5)), - make_test("should wrap a string with even spacing", - "01 23\n45 67\n89 01", - make_tuple(string("01 23 45 67 89 01"), 5)), - make_test("should collapse whitespace to a single space", - "01 34\n67 90\n23 56\n89___", - make_tuple(string("01 34 67 90 23 56 89 "), 5)), - // TODO: Treat newlines and tabs in text as spaces. - }))); + return TestResults().skip("SBF::word_wrap"); + // return execute_suite(make_test_suite( + // "SBF::word_wrap", + // word_wrap, + // vector>({ + // make_test( + // "should return the string if it is shorter than max_width", "0123_", make_tuple(string("0123"), 5)), + // make_test( + // "should return the string if its length is equal to max_width", "01234", make_tuple(string("01234"), + // 5)), + // make_test("should wrap a string to two lines if it has no whitespace", + // "01234\n5____", + // make_tuple(string("012345"), 5)), + // make_test("should wrap a string to three lines if it has no whitespace", + // "01234\n56789\n0____", + // make_tuple(string("01234567890"), 5)), + // make_test("should wrap a string with even spacing", + // "01 23\n45 67\n89 01", + // make_tuple(string("01 23 45 67 89 01"), 5)), + // make_test("should collapse whitespace to a single space", + // "01 34\n67 90\n23 56\n89___", + // make_tuple(string("01 34 67 90 23 56 89 "), 5)), + // // TODO: Treat newlines and tabs in text as spaces. + // }))); } } // End namespace Test::Utils diff --git a/sbf-cpp/main_test.cpp b/sbf-cpp/main_test.cpp new file mode 100644 index 0000000..2134e92 --- /dev/null +++ b/sbf-cpp/main_test.cpp @@ -0,0 +1,23 @@ +#include "Menus.h" + +#include +#include +#include +#include + +#include "test.h" + +using namespace SBF; +using namespace Test; +using namespace std; + +namespace Test::Menus {} // End namespace Test::Menus + +using namespace Test::Menus; + +TestResults main_test_Menus(int argc, char* argv[]) { + TestResults results; + return results; +} + +namespace Test::Menus {} // namespace Test::Menus diff --git a/sbf-cpp/sbf.cpp b/sbf-cpp/sbf.cpp index aff1753..01ffb26 100644 --- a/sbf-cpp/sbf.cpp +++ b/sbf-cpp/sbf.cpp @@ -1,15 +1,466 @@ -#define _XOPEN_SOURCE_EXTENDED -#include "sbf.h" - +#include "Abilities.h" +#include "Attributes.h" +#define _XOPEN_SOURCE_EXTENDED #include #include #include +#include "Archetypes.h" +#include "Backgrounds.h" +#include "Clans.h" +#include "Genders.h" +#include "Menus.h" +#include "Random.h" +#include "Utils.h" +#include "sbf.h" + #define KEY_ESCAPE 0033 +namespace { using namespace std; +using namespace SBF; +} // namespace + +void CGGetAttributes(CharacterType& ch); +void CGGetBackgrounds(CharacterType& ch); +void CGGetDerangement(CharacterType& ch); +void CGGetDisciplines(CharacterType& ch); +void CGGetHeader(CharacterType& ch); +void CGGetRoad(CharacterType& ch); +void CGSpendFreebiePoints(CharacterType& ch); +void CGSpendVirtuePoints(CharacterType& ch); +void CharacterGenerator(); +void CharacterGeneratorForDummies(); +int ChooseStringId(vector labels, MenuStyle style, string prompt); +void CombatComputer(); +void DiceRoller(); +int GetChoice(int min, int max); +int GetChoice(); +int GetMenuChoice(vector menu_items, MenuStyle style); +string GetString(string prompt); +void MainMenu(); +void MaybeClearScreen(); +void RandomCharacterGenerator(); +void SaveCharacterSheet(CharacterType& ch); +void ShowCharacterSheet(CharacterType& ch); +void ShowSplashScreen(); +void VehicleGenerator(); +void WaitForKeypress(); int main(int argc, char* argv[]) { setlocale(LC_ALL, ""); + ShowSplashScreen(); + MainMenu(); return 0; } + +int GetChoice(int min, int max) { + int choice; + do { + choice = GetChoice(); + } while (choice < min || choice > max); + return choice; +} + +int GetChoice() { + int choice; + string line; + bool has_error; + do { + has_error = false; + getline(cin, line); + try { + if (line.empty()) { + return 0; + } + choice = stoi(line); + } catch (...) { + has_error = true; + } + } while (has_error); + return choice; +} + +void MainMenu() { + int choice = 0; + do { + MaybeClearScreen(); + cout << "╔══════════════════════════════════════════════════════════════════════════════╗" << endl + << "║ What are you going to do? ║" << endl + << "║ 1 = Character Generator ║" << endl + << "║ 2 = Character Generator for Dummies ║" << endl + << "║ 3 = Combat Computer ║" << endl + << "║ 4 = Dice Roller ║" << endl + << "║ 5 = Random Character Generator ║" << endl + << "║ 6 = ║" << endl + << "║ 7 = Vehicle Generator ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ ║" << endl + << "║ 0 = End ║" << endl + << "║ ║" << endl + << "╚══════════════════════════════════════════════════════════════════════════════╝" << endl; + choice = GetChoice(0, 7); + switch (choice) { + case 1: + CharacterGenerator(); + break; + case 2: + CharacterGeneratorForDummies(); + break; + case 3: + CombatComputer(); + break; + case 4: + DiceRoller(); + break; + case 5: + RandomCharacterGenerator(); + break; + case 7: + VehicleGenerator(); + break; + } + } while (choice != 0); +} + +/// Clears the screen if not a debug build. +inline void MaybeClearScreen() { +#if !defined(DEBUG) + cout << "\033[1;1H\033[2J"; +#endif +} + +void ShowSplashScreen() { + cout << "Welcome to Tom's Storyteller's Best Friend. This is a program that is meant to" << endl + << "aid storytellers in running Vampire: the Masquerade Chronicles and Vampire: the" << endl + << "Dark Ages Chronicles. This program could aid in running campaigns for other" << endl + << "role-playing games especially those from White Wolf(tm). If you would like" << endl + << "anything added please open a github issue. https://github.com/headhunter45/sbf" << endl + << " Press any key to continue" << endl; + WaitForKeypress(); +} + +void WaitForKeypress() { + // TODO: Make this press any key to continue. +} + +void CharacterGenerator() { + CharacterType ch; + CGGetHeader(ch); + CGGetDisciplines(ch); + CGGetAttributes(ch); + CGGetBackgrounds(ch); + CGGetRoad(ch); + CGSpendVirtuePoints(ch); + CGGetDerangement(ch); + ch.generation = kInitialGeneration - ch.GetBackgroundValue(kBackgroundGenerationId); + ch.willpower = ch.courage; + ch.roadValue = ch.conscience + ch.selfControl; + ch.bloodPool = GetRandomInt(1, 10); + CGSpendFreebiePoints(ch); + SaveCharacterSheet(ch); + ShowCharacterSheet(ch); +} + +void CharacterGeneratorForDummies() { + // TODO: Fill in this function. + cout << "// TODO: CharacterGeneratorForDummies()" << endl; +} + +void CombatComputer() { + // TODO: Fill in this function. + cout << "// TODO: CombatComputer()" << endl; +} + +void DiceRoller() { + // TODO: Fill in this function. + cout << "// TODO: DiceRoller()" << endl; +} + +void RandomCharacterGenerator() { + // TODO: Fill in this function. + cout << "// TODO: RandomCharacterGenerator()" << endl; +} + +void VehicleGenerator() { + // TODO: Fill in this function. + cout << "// TODO: VehicleGenerator()" << endl; +} + +void CGGetHeader(CharacterType& ch) { + MaybeClearScreen(); + MenuStyle ms; + ch.name = GetString("What is the character's name?"); + ch.player = GetString("Who is the player?"); + ch.chronicle = GetString("What chronicle is the character going to be used for?"); + ch.haven = GetString("What is the character's Haven?"); + ch.concept = GetString("What is the character's concept?"); + ch.age = GetString("How old is the character?"); + vector genders; + FillGenderLabels(genders); + ch.genderId = ChooseStringId(genders, ms, "What is the character's gender?"); + vector clans; + FillClanLabels(clans); + ch.clanId = ChooseStringId(clans, ms, "What clan is the character from?"); + vector archetypes; + FillArchetypeLabels(archetypes); + ch.natureId = ChooseStringId(archetypes, ms, "What is the character's nature?"); + ch.demeanorId = ChooseStringId(archetypes, ms, "What is the character's demeanor?"); +} + +void CGGetDisciplines(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGGetDisciplines(CharacterType&)" << endl; +} + +void CGGetAttributes(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGGetAttributes(CharacterType&)" << endl; +} + +void CGGetBackgrounds(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGGetBackgrounds(CharacterType&)" << endl; +} + +void CGGetRoad(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGGetRoad(CharacterType&)" << endl; +} + +void CGSpendVirtuePoints(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGSpendVirtuePoints(CharacterType&)" << endl; +} + +void CGGetDerangement(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGGetDerangement(CharacterType&)" << endl; +} + +void CGSpendFreebiePoints(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: CGSpendFreebiePoints(CharacterType&)" << endl; +} + +string FormatAttributeValue(const string& label, int value) { + return make_fit_c(label + make_fit_l(itos(value), 2), 12); +} + +string FormatAbilityWithValue(const string& label, int value) { + return make_fit_c(make_fit_l(label + ":", 14) + itos(value), 24); +} + +void SaveCharacterSheet(CharacterType& ch) { + // TODO: Fill this in. + cout << "// TODO: SaveCharacterSheet(CharacterType&)" << endl; +} + +void ShowCharacterSheet(CharacterType& ch) { + const int kLeftColumnWidth = 36; + const int kRightColumnWidth = 37; + vector discipline_strings; + size_t index; + for (index = 1; index <= kDisciplinesCount; index++) { + int value = ch.GetDisciplineValue(index); + if (value > 0) { + string suffix = ""; + if (value > 1) { + suffix = " x" + itos(value); + } + discipline_strings.push_back(GetDisciplineLabel(index) + suffix); + } + } + while (discipline_strings.size() <= 3) { + discipline_strings.push_back(string_dollar(kLeftColumnWidth, '_')); + } + vector background_strings(5); + for (index = 1; index <= kBackgroundsCount; index++) { + int value = ch.GetBackgroundValue(index); + if (value > 0) { + string suffix = ""; + if (value > 1) { + suffix = " x" + itos(value); + } + background_strings.push_back(GetBackgroundLabel(index) + suffix); + } + } + while (background_strings.size() <= 5) { + background_strings.push_back(string_dollar(kLeftColumnWidth, '_')); + } + string all_derangements_line = ch.GetAllDerangementsLine(); + vector derangement_strings = word_wrap(all_derangements_line, kLeftColumnWidth); + while (derangement_strings.size() <= 5) { + derangement_strings.push_back(string_dollar(kLeftColumnWidth, '_')); + } + MaybeClearScreen(); + cout << "╔══════════════════════════════════════╦═══════════════════════════════════════╗" << endl; + cout << "║ Name: " << make_fit_l(ch.name, 30) << " ║ Gender: " << make_fit_l(GetGenderLabel(ch.genderId), 14) + << " Generation: " << make_fit_r(itos(ch.generation), 2) << " ║" << endl; + cout << "║ Clan: " << make_fit_l(GetClanLabel(ch.clanId), 30) << " ║ Age: " << make_fit_l(ch.age, 32) << " ║" << endl; + cout << "╠══════════════════════════════════════╣ Player: " << make_fit_l(ch.player, 29) << " ║" << endl; + cout << "║ Attributes ║ Chronicle: " << make_fit_l(ch.chronicle, 26) << " ║" << endl; + cout << "║ " << make_fit_c("Physical", 12) << make_fit_c("Social", 12) << make_fit_c("Mental", 12) + << " ║ Haven: " << make_fit_l(ch.haven, 30) + " ║" << endl; + cout << "║ " << FormatAttributeValue("Str. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeStrengthId)) + << FormatAttributeValue("App. ", ch.GetSocialAttributeValue(kSocialAttributeAppearanceId)) + << FormatAttributeValue("Int. ", ch.GetMentalAttributeValue(kMentalAttributeIntelligenceId)) + << " ║ Concept: " << make_fit_l(ch.concept, 28) << " ║" << endl; + cout << "║ " << FormatAttributeValue("Dex. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeDexterityId)) + << FormatAttributeValue("Cha. ", ch.GetSocialAttributeValue(kSocialAttributeCharismaId)) + << FormatAttributeValue("Per. ", ch.GetMentalAttributeValue(kMentalAttributePerceptionId)) + << " ╠═══════════════════════════════════════╣" << endl; + cout << "║ " << FormatAttributeValue("Sta. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeStaminaId)) + << FormatAttributeValue("Man. ", ch.GetSocialAttributeValue(kSocialAttributeManipulationId)) + << FormatAttributeValue("Wit. ", ch.GetMentalAttributeValue(kMentalAttributeWitsId)) + << " ║ Derangements: ║" << endl; + cout << "╠══════════════════════════════════════╣ " << make_fit_l(derangement_strings[0], kRightColumnWidth, '_') + << " ║" << endl; + cout << "║ Disciplines: ║ " << make_fit_l(derangement_strings[1], kRightColumnWidth, '_') + << " ║" << endl; + cout << "║ " << make_fit_l(discipline_strings[0], kLeftColumnWidth) << " ║ " + << make_fit_l(derangement_strings[2], kRightColumnWidth, '_') << " ║" << endl; + cout << "║ " << make_fit_l(discipline_strings[1], kLeftColumnWidth) << " ║ " + << make_fit_l(derangement_strings[3], kRightColumnWidth, '_') << " ║" << endl; + cout << "║ " << make_fit_l(discipline_strings[2], kLeftColumnWidth) << " ║ " + << make_fit_l(derangement_strings[4], kRightColumnWidth, '_') << " ║" << endl; + cout << "╠══════════════════════════════════════╬═══════════════════════════════════════╣" << endl; + cout << "║ " << make_fit_l(ch.roadName + ": " + itos(ch.roadValue), kLeftColumnWidth) + << " ║ Nature: " << make_fit_l(GetArchetypeLabel(ch.natureId), 29) << " ║" << endl; + cout << "║ Willpower: " << make_fit_l(itos(ch.willpower), 25) + << " ║ Demeanor: " << make_fit_l(GetArchetypeLabel(ch.demeanorId), 27) << " ║" << endl; + cout << "╠══════════════════════════════════════╩═══════════════════════════════════════╣" << endl; + cout << "║ ║" << endl; + cout << "║ ║" << endl; + cout << "║ ║" << endl; + cout << "║ <> ║" << endl; + cout << "╚══════════════════════════════════════════════════════════════════════════════╝" << endl; + WaitForKeypress(); + cout << "╔══════════════════════════════════════════════════════════════════════════════╗" << endl; + cout << "║ " << make_fit_c("Abilities", 76) << " ║" << endl; + cout << "║ " << make_fit_c("Talents", 24) << make_fit_c("Skills", 24) << make_fit_c("Knowledges", 24) << " ║" + << endl; + for (index = 1; index <= 10; index++) { + cout << "║ " << FormatAbilityWithValue(GetTalentLabel(index), ch.GetTalentValue(index)) + << FormatAbilityWithValue(GetSkillLabel(index), ch.GetSkillValue(index)) + << FormatAbilityWithValue(GetKnowledgeLabel(index), ch.GetKnowledgeValue(index)) << " ║" << endl; + } + cout << "╠══════════════════════════════════════╦═══════════════════════════════════════╣" << endl; + cout << "║ " << make_fit_l("Backgrounds:", kLeftColumnWidth) << " ║ " << make_fit_l("Virtues:", kRightColumnWidth) + << " ║" << endl; + cout << "║ " << make_fit_l(background_strings[0], kLeftColumnWidth) << " ║ " + << make_fit_b("Conscience:", itos(ch.conscience), kRightColumnWidth) << " ║" << endl; + cout << "║ " << make_fit_l(background_strings[1], kLeftColumnWidth) << " ║ " + << make_fit_b("Self-Control:", itos(ch.selfControl), kRightColumnWidth) << " ║" << endl; + cout << "║ " << make_fit_l(background_strings[2], kLeftColumnWidth) << " ║ " + << make_fit_b("Courage:", itos(ch.courage), kRightColumnWidth) << " ║" << endl; + cout << "║ " << make_fit_l(background_strings[3], kLeftColumnWidth) << " ║ " << make_fit_l("", kRightColumnWidth) + << " ║" << endl; + cout << "║ " << make_fit_l(background_strings[4], kLeftColumnWidth) << " ║ " << make_fit_l("", kRightColumnWidth) + << " ║" << endl; + cout << "╠══════════════════════════════════════╩═══════════════════════════════════════╣" << endl; + cout << "║ <> ║" << endl; + cout << "╚══════════════════════════════════════════════════════════════════════════════╝" << endl; + WaitForKeypress(); + /* + ╔══════════════════════════════════════╦═══════════════════════════════════════╗ + ║ Name: Ted ║ Gender: Female Generation: 13 ║ + ║ Clan: Ventrue ║ Age: 35 ║ + ╠══════════════════════════════════════╣ Player: Jeff ║ + ║ Attributes ║ Chronicle: Somesuch ║ + ║ Physical Social Mental ║ Haven: Mom's basement ║ + ║ Str. 1 App. 1 Int. 1 ║ Concept: Asshole ║ + ║ Dex. 1 Cha. 1 Per. 1 ╠═══════════════════════════════════════╣ + ║ Sta. 1 Man. 1 Wit. 1 ║ Derangements: ║ + ╠══════════════════════════════════════╣ _____________________________________ ║ + ║ Disciplines: ║ _____________________________________ ║ + ║ ____________________________________ ║ _____________________________________ ║ + ║ ____________________________________ ║ _____________________________________ ║ + ║ ____________________________________ ║ _____________________________________ ║ + ╠══════════════════════════════════════╬═══════════════════════════════════════╣ + ║ : 2 ║ Nature: Tyrant ║ + ║ Willpower: 1 ║ Demeanor: Traditionalist ║ + ╠══════════════════════════════════════╩═══════════════════════════════════════╣ + ║ ║ + ║ ║ + ║ ║ + ║ <> ║ + ╚══════════════════════════════════════════════════════════════════════════════╝ + ╔══════════════════════════════════════════════════════════════════════════════╗ + ║ Abilities ║ + ║ Talents Skills Knowledges ║ + ║ Acting: 0 Animal Ken: 0 Bureaucracy: 0 ║ + ║ Alertness: 0 Drive: 0 Computer: 0 ║ + ║ Athletics: 0 Etiquette: 0 Finance: 0 ║ + ║ Brawl: 0 Firearms: 0 Investigation:0 ║ + ║ Dodge: 0 Melee: 0 Law: 0 ║ + ║ Empathy: 0 Music: 0 Linguistics: 0 ║ + ║ Intimidation: 0 Repair: 0 Medicine: 0 ║ + ║ Leadership: 0 Security: 0 Occult: 0 ║ + ║ Streetwise: 0 Stealth: 0 Politics: 0 ║ + ║ Subterfuge: 0 Survival: 0 Science: 0 ║ + ╠══════════════════════════════════════╦═══════════════════════════════════════╣ + ║ Backgrounds: ║ Virtues: ║ + ║ ║ Conscience: 1 ║ + ║ ║ Self-Control: 1 ║ + ║ ║ Courage: 1 ║ + ║ ║ ║ + ║ ║ ║ + ╠══════════════════════════════════════╩═══════════════════════════════════════╣ + ║ <> ║ + ╚══════════════════════════════════════════════════════════════════════════════╝ + // TODO: ShowCharacterSheet(CharacterType&) + */ +} + +int GetRandomInt(int min, int max) { + // TODO: Fill this in. + return 0; +} + +string GetString(string prompt) { + cout << prompt << endl; + string response; + getline(cin, response); + return response; +} + +int ChooseStringId(vector labels, MenuStyle style, string prompt) { + MaybeClearScreen(); + vector menu_items = BuildMenu(labels); + style.Adjust(menu_items); + cout << prompt << endl; + PrintMenu(cout, menu_items, style); + int choice = GetMenuChoice(menu_items, style); + if (choice == style.random_item_id) { + choice = GetRandomMenuItemId(menu_items); + } + return choice; +} + +int GetMenuChoice(vector menu_items, MenuStyle style) { + int choice; + while (true) { + choice = GetChoice(); + if (style.show_random && choice == style.random_item_id) { + return choice; + } + if (style.show_cancel && choice == style.cancel_item_id) { + return choice; + } + for (MenuItem item : menu_items) { + if (item.id == choice) { + return choice; + } + } + } +} diff --git a/sbf-cpp/sbf_test.cpp b/sbf-cpp/sbf_test.cpp new file mode 100644 index 0000000..694e907 --- /dev/null +++ b/sbf-cpp/sbf_test.cpp @@ -0,0 +1,23 @@ +#include "sbf.h" + +#include +#include +#include +#include + +#include "test.h" + +using namespace SBF; +using namespace Test; +using namespace std; + +namespace Test::sbf {} // End namespace Test::sbf + +using namespace Test::sbf; + +TestResults main_test_sbf(int argc, char* argv[]) { + TestResults results; + return results; +} + +namespace Test::sbf {} // namespace Test::sbf diff --git a/sbf-cpp/test.h b/sbf-cpp/test.h index 8ce7116..8f599e1 100644 --- a/sbf-cpp/test.h +++ b/sbf-cpp/test.h @@ -354,7 +354,7 @@ TestResults execute_suite(std::string suite_label, if (!is_enabled) { std::cout << " 🚧Skipping Test: " << test_name << std::endl; - results.skip("🚧Skipping Test: " + qualified_test_name); + results.skip(qualified_test_name); return; } @@ -371,16 +371,16 @@ TestResults execute_suite(std::string suite_label, } catch (const std::exception& ex) { std::ostringstream os; os << "Caught exception \"" << ex.what() << "\""; - results.error("🔥ERROR: " + qualified_test_name + " " + os.str()); + results.error(qualified_test_name + " " + os.str()); std::cout << " 🔥ERROR: " << os.str() << std::endl; } catch (const std::string& message) { std::ostringstream os; os << "Caught string \"" << message << "\""; - results.error("🔥ERROR: " + qualified_test_name + " " + os.str()); + results.error(qualified_test_name + " " + os.str()); std::cout << " 🔥ERROR: " << os.str() << std::endl; } catch (...) { string message = "Caught something that is neither an std::exception nor an std::string."; - results.error("🔥ERROR: " + qualified_test_name + " " + message); + results.error(qualified_test_name + " " + message); std::cout << " 🔥ERROR: " << message << std::endl; } @@ -391,7 +391,7 @@ TestResults execute_suite(std::string suite_label, } else { std::ostringstream os; os << "expected: \"" << expected_output << "\", actual: \"" << actual << "\""; - results.fail("❌FAILED: " + qualified_test_name + " " + os.str()); + results.fail(qualified_test_name + " " + os.str()); std::cout << " ❌FAILED: " << os.str() << std::endl; }