diff --git a/CMakeLists.txt b/CMakeLists.txt index 3644740..f397df1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,133 +1,87 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.21) project(combat_cpp VERSION 0.1.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - - -#---------------------------------------------------------------- -# Packages -#---------------------------------------------------------------- -set(CMAKE_INCLUDE_CURRENT_DIR ON) -add_definitions(-DCMAKE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") - -#---------------------------------------------------------------- -# GTest External Project (from gtest documentation) -# https://bit.ly/3g8iXeM -#---------------------------------------------------------------- - -# Download and unpack googletest at configure time -configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) -execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) -if(result) - message(FATAL_ERROR "CMake step for googletest failed: ${result}") -endif() -execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) -if(result) - message(FATAL_ERROR "Build step for googletest failed: ${result}") -endif() - -# Prevent overriding the parent project's compiler/linker -# settings on Windows -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - -# Add googletest directly to our build. This defines -# the gtest and gtest_main targets. -add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src - ${CMAKE_CURRENT_BINARY_DIR}/googletest-build - EXCLUDE_FROM_ALL) - -# The gtest/gtest_main targets carry header search path -# dependencies automatically when using CMake 2.8.11 or -# later. Otherwise we have to add them here ourselves. -if (CMAKE_VERSION VERSION_LESS 2.8.11) - include_directories("${gtest_SOURCE_DIR}/include") -endif() +enable_testing() #---------------------------------------------------------------- -# Protobuf Messages +# Set compiler flags +# (modified from fmtlib: https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt) #---------------------------------------------------------------- -add_subdirectory(messages) +include(CheckCXXCompilerFlag) + +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic + -Wold-style-cast -Wundef + -Wredundant-decls -Wwrite-strings -Wpointer-arith + -Wcast-qual -Wformat=2 -Wmissing-include-dirs + -Wcast-align + -Wctor-dtor-privacy -Wdisabled-optimization + -Winvalid-pch -Woverloaded-virtual + -Wconversion -Wundef + -Wno-ctor-dtor-privacy -Wno-format-nonliteral) + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} + -Wno-dangling-else -Wno-unused-local-typedefs) + endif () + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion + -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast + -Wvector-operation-performance -Wsized-deallocation -Wshadow) + endif () + if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 + -Wnull-dereference -Wduplicated-cond) + endif () +endif () + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef + -Wdeprecated -Wweak-vtables -Wshadow + -Wno-gnu-zero-variadic-macro-arguments) + check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING) + if (HAS_NULLPTR_WARNING) + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} + -Wzero-as-null-pointer-constant) + endif () +endif () + +if (MSVC) + set(PEDANTIC_COMPILE_FLAGS /W3) +endif () #---------------------------------------------------------------- -# Main Sources +# Add messages library #---------------------------------------------------------------- -set(ser_includes - include/Die.hpp - include/Combatant.hpp - include/Utility.hpp - include/Item.hpp - include/Weapon.hpp - include/Armour.hpp - include/States.hpp - include/GameLogic.hpp -) - -set(ser_sources - src/Die.cpp - src/Combatant.cpp - src/Utility.cpp - src/Weapon.cpp - src/Armour.cpp - src/GameLogic.cpp -) - -set(all_sources - ${ser_includes} - ${ser_sources} -) - +add_subdirectory(proto) #---------------------------------------------------------------- -# Executables +# Add main library #---------------------------------------------------------------- -add_executable(exampleMain - src/main.cpp - ${all_sources} -) +file(GLOB COMBAT_CPP_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") +file(GLOB COMBAT_CPP_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/combat_cpp/*.hpp") -target_compile_features(exampleMain - PRIVATE - cxx_std_17 -) +add_library(${PROJECT_NAME} ${COMBAT_CPP_SOURCES} ${COMBAT_CPP_HEADERS}) -target_include_directories(exampleMain - PUBLIC +target_include_directories(${PROJECT_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -target_link_libraries(exampleMain - PUBLIC - messages ) -add_executable(runAllTests - src/all_tests.cpp - ${all_sources} -) - -target_compile_features(runAllTests +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17 ) -target_include_directories(runAllTests - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include +target_compile_options(${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src + ${PEDANTIC_COMPILE_FLAGS} ) -target_link_libraries(runAllTests - PUBLIC - gtest_main - messages -) +#---------------------------------------------------------------- +# Add Apps +#---------------------------------------------------------------- +add_subdirectory(app) -enable_testing() -add_test(GTest runAllTests) +#---------------------------------------------------------------- +# Add Tests +#---------------------------------------------------------------- +add_subdirectory(test) \ No newline at end of file diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in deleted file mode 100644 index 226cf8c..0000000 --- a/CMakeLists.txt.in +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 2.8.2) - -project(googletest-download NONE) - -include(ExternalProject) -ExternalProject_Add(googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG main - SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" - BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) \ No newline at end of file diff --git a/README.md b/README.md index 508e025..cc8321b 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,20 @@ Possible actions are: Fight, Check Health, Rest, Look Around, and Exit. While fi The game ends when all enemies are defeated, or the user chooses to exit. I hope you enjoy playing this little game! -Want to try it out? All you need to do is clone, configure + build with CMake, and run exampleMain :D -It has passed build actions for windows-latest, ubuntu-latest, and macos-latest, however I have only personally tested on windows and ubuntu (in WSL for now). - -Last but not least, please report any bugs you find! Any and all feedback is always welcome as I plan to continue casual improvements of this. - -Note: Enemy names and any references are meant simply as a tribute to stories I have enjoyed in the past. I will gladly modify as required should anyone have any issues with it - -Note: The majority of rules are based on this source: https://roll20.net/compendium/dnd5e/CategoryIndex%3ARules#content +## Pre-requisites and Setup + +1. Make sure your c++ dev environment is setup + a. On windows, Visual Studio makes set up very simple (2022 was used in this project) + b. On Linux/MacOS, be sure to install both `build-essential` and `ninja-build` + c. Make sure to install CMake version 3.21 or newer +2. Populate submodules with `git submodule update --init` +3. Build with your favorite CMake methods (either GUI or command line) +4. Run tests if you feel like it / are developing +5. Try the game by running `main_game` + +## Notices + +* Enemy names and any references are meant simply as a tribute to stories I have enjoyed in the past. I will gladly modify as required should anyone have any issues with it +* The majority of rules are based on this source: https://roll20.net/compendium/dnd5e/CategoryIndex%3ARules#content +* It has passed build actions for windows-latest, ubuntu-latest, and macos-latest, however I have only personally tested on windows and ubuntu. +* Please report any bugs you find! Any and all feedback is always welcome as I plan to continue casual improvements of this. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..e7f6a1d --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,21 @@ +message("-- Configuring applications") +add_executable(main_game + main.cpp +) + +target_compile_features(main_game + PRIVATE + cxx_std_17 +) + +target_link_libraries(main_game + PRIVATE + combat_cpp +) + +target_compile_options(main_game + PRIVATE + ${PEDANTIC_COMPILE_FLAGS} +) + +message("-- DONE configuring application") diff --git a/src/main.cpp b/app/main.cpp similarity index 96% rename from src/main.cpp rename to app/main.cpp index 380e914..4d2a4c4 100644 --- a/src/main.cpp +++ b/app/main.cpp @@ -1,6 +1,6 @@ #include -#include "GameLogic.hpp" +#include "combat_cpp/GameLogic.hpp" int main() { diff --git a/include/Armour.hpp b/include/combat_cpp/Armour.hpp similarity index 97% rename from include/Armour.hpp rename to include/combat_cpp/Armour.hpp index f5604f7..7240bc0 100644 --- a/include/Armour.hpp +++ b/include/combat_cpp/Armour.hpp @@ -4,7 +4,7 @@ #include #include -#include "Item.hpp" +#include "combat_cpp/Item.hpp" enum class ArmourType { diff --git a/include/Combatant.hpp b/include/combat_cpp/Combatant.hpp similarity index 93% rename from include/Combatant.hpp rename to include/combat_cpp/Combatant.hpp index 0382267..62faab2 100644 --- a/include/Combatant.hpp +++ b/include/combat_cpp/Combatant.hpp @@ -8,10 +8,10 @@ #include -#include "Utility.hpp" -#include "Die.hpp" -#include "Weapon.hpp" -#include "Armour.hpp" +#include "combat_cpp/Utility.hpp" +#include "combat_cpp/Die.hpp" +#include "combat_cpp/Weapon.hpp" +#include "combat_cpp/Armour.hpp" struct AttackResult { diff --git a/include/Die.hpp b/include/combat_cpp/Die.hpp similarity index 100% rename from include/Die.hpp rename to include/combat_cpp/Die.hpp diff --git a/include/GameLogic.hpp b/include/combat_cpp/GameLogic.hpp similarity index 88% rename from include/GameLogic.hpp rename to include/combat_cpp/GameLogic.hpp index a34f06c..64102f0 100644 --- a/include/GameLogic.hpp +++ b/include/combat_cpp/GameLogic.hpp @@ -5,9 +5,9 @@ #include #include -#include "Combatant.hpp" -#include "Die.hpp" -#include "States.hpp" +#include "combat_cpp/Combatant.hpp" +#include "combat_cpp/Die.hpp" +#include "combat_cpp/States.hpp" class GameLogic { diff --git a/include/Item.hpp b/include/combat_cpp/Item.hpp similarity index 100% rename from include/Item.hpp rename to include/combat_cpp/Item.hpp diff --git a/include/States.hpp b/include/combat_cpp/States.hpp similarity index 96% rename from include/States.hpp rename to include/combat_cpp/States.hpp index 1cb9cfc..de0c067 100644 --- a/include/States.hpp +++ b/include/combat_cpp/States.hpp @@ -5,7 +5,7 @@ #include #include -#include "Combatant.hpp" +#include "combat_cpp/Combatant.hpp" struct GameState { @@ -14,8 +14,8 @@ struct GameState struct Combat { Combat() : target(nullptr), target_key(), player_initiative(0) {}; - Combat(Combatant* const target, const uint8_t initiative) - : target(target) + Combat(Combatant* const target_, const uint8_t initiative) + : target(target_) , target_key() , player_initiative(initiative) { @@ -52,6 +52,7 @@ struct GameState } if (new_state.has_value()) { state_ = new_state.value(); }; }; + void FinishFight() { std::optional new_state; @@ -79,6 +80,7 @@ struct GameState } if (new_state.has_value()) { state_ = new_state.value(); }; }; + void FinishRest() { std::optional new_state; @@ -106,6 +108,7 @@ struct GameState } if (new_state.has_value()) { state_ = new_state.value(); }; }; + void FinishLook() { std::optional new_state; @@ -133,6 +136,7 @@ struct GameState } if (new_state.has_value()) { state_ = new_state.value(); }; }; + void FinishSelfCheck() { std::optional new_state; diff --git a/include/Utility.hpp b/include/combat_cpp/Utility.hpp similarity index 99% rename from include/Utility.hpp rename to include/combat_cpp/Utility.hpp index a5c2281..72c32cc 100644 --- a/include/Utility.hpp +++ b/include/combat_cpp/Utility.hpp @@ -60,6 +60,6 @@ namespace Utility void ConditionStringInPlace(std::string& str, const bool remove_whitespace, const bool to_lower); std::string ConditionString(std::string str, const bool remove_whitespace, const bool to_lower); -}; +} #endif // !COMBAT_CPP_UTILITY_HPP_ diff --git a/include/Weapon.hpp b/include/combat_cpp/Weapon.hpp similarity index 90% rename from include/Weapon.hpp rename to include/combat_cpp/Weapon.hpp index 8579d43..31a7943 100644 --- a/include/Weapon.hpp +++ b/include/combat_cpp/Weapon.hpp @@ -5,12 +5,12 @@ #include #include -#include "Item.hpp" -#include "Die.hpp" +#include "combat_cpp/Item.hpp" +#include "combat_cpp/Die.hpp" struct WeaponInfo { - WeaponInfo(const std::string& description, const int die_n, const int num_dice) + WeaponInfo(const std::string& description, const uint8_t die_n, const uint8_t num_dice) { Description = description; DieN = die_n; diff --git a/messages/CMakeLists.txt b/messages/CMakeLists.txt deleted file mode 100644 index fa36ec4..0000000 --- a/messages/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -message("-- Beginning Protofile Generation") -find_package(protobuf CONFIG REQUIRED) - -file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/*.proto") -message("Generating proto files for: ${ProtoFiles}") -PROTOBUF_GENERATE_CPP(ProtoSources ProtoHeaders ${ProtoFiles}) - -add_library(messages STATIC ${ProtoSources} ${ProtoHeaders}) -target_link_libraries(messages - PUBLIC - protobuf::libprotoc - protobuf::libprotobuf -) -target_include_directories(messages - PUBLIC - INTERFACE ${CMAKE_CURRENT_BINARY_DIR} -) - -message("-- Protofile Generation done") diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt new file mode 100644 index 0000000..5818340 --- /dev/null +++ b/proto/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(messages) + +target_include_directories(messages + PUBLIC + INTERFACE ${CMAKE_CURRENT_BINARY_DIR} +) \ No newline at end of file diff --git a/proto/messages/CMakeLists.txt b/proto/messages/CMakeLists.txt new file mode 100644 index 0000000..36e7e8c --- /dev/null +++ b/proto/messages/CMakeLists.txt @@ -0,0 +1,23 @@ +message("-- Beginning Protofile Generation") +find_package(protobuf CONFIG REQUIRED) + +file(GLOB PROTO_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.proto") +message("Generating proto files for: ${PROTO_FILES}") +PROTOBUF_GENERATE_CPP(PROTO_SOURCES PROTO_HEADERS ${PROTO_FILES}) + +add_library(messages ${PROTO_SOURCES} ${PROTO_HEADERS}) +target_link_libraries(messages + PUBLIC + protobuf::libprotoc + protobuf::libprotobuf +) +target_compile_features(messages + PRIVATE + cxx_std_17 +) +target_compile_options(messages + PRIVATE + ${PEDANTIC_COMPILE_FLAGS} +) + +message("-- Protofile Generation done") diff --git a/messages/compile_test.proto b/proto/messages/compile_test.proto similarity index 100% rename from messages/compile_test.proto rename to proto/messages/compile_test.proto diff --git a/src/Armour.cpp b/src/Armour.cpp index 66307a1..0221b1e 100644 --- a/src/Armour.cpp +++ b/src/Armour.cpp @@ -1,4 +1,4 @@ -#include "Armour.hpp" +#include "combat_cpp/Armour.hpp" Armour::Armour() : Item() diff --git a/src/Combatant.cpp b/src/Combatant.cpp index 3f77480..45d0b06 100644 --- a/src/Combatant.cpp +++ b/src/Combatant.cpp @@ -1,4 +1,4 @@ -#include "Combatant.hpp" +#include "combat_cpp/Combatant.hpp" Combatant::Combatant(const std::string& name, const Utility::Classes class_type) : Combatant() diff --git a/src/Die.cpp b/src/Die.cpp index c2204c7..82e4187 100644 --- a/src/Die.cpp +++ b/src/Die.cpp @@ -1,4 +1,4 @@ -#include "Die.hpp" +#include "combat_cpp/Die.hpp" Die::Die(const uint8_t num_sides) : sides_(num_sides) diff --git a/src/GameLogic.cpp b/src/GameLogic.cpp index 70e0f49..d38c590 100644 --- a/src/GameLogic.cpp +++ b/src/GameLogic.cpp @@ -1,8 +1,8 @@ -#include "GameLogic.hpp" +#include "combat_cpp/GameLogic.hpp" #include -#include "Utility.hpp" +#include "combat_cpp/Utility.hpp" GameLogic::GameLogic(const std::string&& player_name, const std::string&& player_class) : state_() @@ -26,7 +26,7 @@ GameLogic::GameLogic(const std::string&& player_name, const std::string&& player { std::cout << i->second->ToString() << "\n\n"; } -}; +} bool GameLogic::Execute() { @@ -106,7 +106,6 @@ bool GameLogic::IdleLoop() { Combatant* target_ptr = combatants_.at(target).get(); std::cout << "You step towards " << target << " and prepare for Combat!\n\n"; - uint_fast64_t fight_turn = 0; // NOTE: This will be reafactored in future multiple opponent combat uint8_t player_initiative = (player->StatCheck(Utility::Stats::DEX) > target_ptr->StatCheck(Utility::Stats::DEX)) ? 1 : 0; diff --git a/src/Utility.cpp b/src/Utility.cpp index cb83e6c..73dcc89 100644 --- a/src/Utility.cpp +++ b/src/Utility.cpp @@ -1,4 +1,4 @@ -#include "Utility.hpp" +#include "combat_cpp/Utility.hpp" #include #include @@ -57,14 +57,14 @@ namespace Utility } else { - return Utility::Classes::Rogue; + return Classes::Rogue; } } uint8_t SumDice(const std::vector& dice) { return std::reduce(dice.cbegin(), dice.cend()); - }; + } std::string StatString(const std::unordered_map& stats) { @@ -77,7 +77,7 @@ namespace Utility str += ", CHR: " + std::to_string(stats.at(Stats::CHR)); str += " }"; return str; - }; + } std::string StatString(const std::unordered_map& stats) { @@ -91,6 +91,7 @@ namespace Utility str += " }"; return str; } + int8_t DetermineModifier(uint8_t stat) { if (2 <= stat && stat <= 4) { @@ -136,6 +137,7 @@ namespace Utility std::transform(str.begin(), str.end(), str.begin(), [](auto& c) {return std::tolower(c); }); } } + std::string ConditionString(std::string str, const bool remove_whitespace, const bool to_lower) { ConditionStringInPlace(str, remove_whitespace, to_lower); diff --git a/src/Weapon.cpp b/src/Weapon.cpp index aedc6e2..0e306c2 100644 --- a/src/Weapon.cpp +++ b/src/Weapon.cpp @@ -1,4 +1,4 @@ -#include "Weapon.hpp" +#include "combat_cpp/Weapon.hpp" Weapon::Weapon() : Item() diff --git a/src/all_tests.cpp b/src/all_tests.cpp deleted file mode 100644 index 23bf4b7..0000000 --- a/src/all_tests.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include - -#include "Combatant.hpp" -#include "GameLogic.hpp" - -#include "messages/compile_test.pb.h" - -#pragma region CombatantTests - -class CombatantTest : public testing::Test -{ -protected: - Combatant player{ "player", Utility::Classes::Rogue }; - Combatant enemy{ "enemy", Utility::Classes::Fighter }; -}; - -TEST_F(CombatantTest, Constructor) -{ - ASSERT_STREQ(player.GetName().c_str(), "player"); - ASSERT_STREQ(player.GetClass().c_str(), "Rogue"); - ASSERT_STREQ(enemy.GetName().c_str(), "enemy"); - ASSERT_STREQ(enemy.GetClass().c_str(), "Fighter"); -} - -TEST_F(CombatantTest, SustainDamageDecreasesHealth) -{ - auto max_health = player.GetHealth(); - player.SustainDamage(max_health - 1); - EXPECT_LT(player.GetHealth(), max_health); -} - -TEST_F(CombatantTest, SustainDamageDecreasesCorrectAmount) -{ - auto max_health = player.GetHealth(); - player.SustainDamage(max_health - 1); - EXPECT_EQ(player.GetHealth(), 1); -} - -TEST_F(CombatantTest, SustainDamageDoesNotUnderflow) -{ - player.SustainDamage(100); - EXPECT_EQ(player.GetHealth(), 0); -} - -TEST_F(CombatantTest, HealIncreasesHealthIfDamaged) -{ - auto max_health = player.GetHealth(); - player.SustainDamage(max_health - 1); - auto damaged = player.GetHealth(); - player.Heal(-1); - EXPECT_GT(player.GetHealth(), damaged); -} - -TEST_F(CombatantTest, HealDoesNotGoAboveMaxHealth) -{ - auto max_health = player.GetHealth(); - player.Heal(-1); - EXPECT_EQ(player.GetHealth(), max_health); -} - -TEST_F(CombatantTest, DetermineModifierReturnsProperValuesForValidRange) -{ - std::vector expected_modifiers = - { - -4, // 2 - -4, // 3 - -4, // 4 - -3, // 5 - -3, // 6 - -2, // 7 - -2, // 8 - -1, // 9 - -1, // 10 - 0, // 11 - 0, // 12 - 1, // 13 - 1, // 14 - 2, // 15 - 2, // 16 - 3, // 17 - 3, // 18 - 4, // 19 - 4 // 20 - }; - - std::vector calulated_modifiers; - for (uint8_t i = 2; i <= 20; ++i) - { - calulated_modifiers.push_back(Utility::DetermineModifier(i)); - ASSERT_EQ(calulated_modifiers[i-2], expected_modifiers[i-2]); - } -} - -TEST_F(CombatantTest, DetermineModifiersReturnsINT8_MINOutOfRange) -{ - ASSERT_EQ(Utility::DetermineModifier(100), INT8_MIN); - ASSERT_EQ(Utility::DetermineModifier(0), INT8_MIN); -} - -// NOTE: this test is a little weird as attack is a random action -TEST_F(CombatantTest, AttackDoesDamage) -{ - auto max_health = enemy.GetHealth(); - while (player.Attack(&enemy).first.damage == 0); - EXPECT_LT(enemy.GetHealth(), max_health); -} - -#pragma endregion - -#pragma region StateTests - -class StateTest : public testing::Test -{ -protected: - GameState game_state; -}; - -TEST_F(StateTest, GameInitialisesToIdleState) -{ - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, IdleCanTransitionToCombat) -{ - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, IdleCanTransitionToResting) -{ - game_state.TakeRest(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, IdleCanTransitionToSelfChecking) -{ - game_state.SelfCheck(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, IdleCanTransitionToLookingAround) -{ - game_state.LookAround(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, CombatCanTransitionToRest) -{ - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - game_state.TakeRest(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, CombatCanTransitionToIdle) -{ - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - game_state.FinishFight(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, RestCanTransitionToIdle) -{ - game_state.TakeRest(); - game_state.FinishRest(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, SelfCheckingCanTransitionToIdle) -{ - game_state.SelfCheck(); - game_state.FinishSelfCheck(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, LookingAroundCanTransitionToIdle) -{ - game_state.LookAround(); - game_state.FinishLook(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, InvalidTransitionsFromCombatDontChangeState) -{ - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - - game_state.LookAround(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - game_state.SelfCheck(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, InvalidTransitionsFromRestDontChangeState) -{ - game_state.TakeRest(); - - game_state.LookAround(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - game_state.SelfCheck(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, InvalidTransitionsFromSelfCheckingDontChangeState) -{ - game_state.SelfCheck(); - - game_state.LookAround(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - game_state.TakeRest(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -TEST_F(StateTest, InvalidTransitionsFromLookingAroundDontChangeState) -{ - game_state.LookAround(); - - game_state.TakeRest(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - game_state.SelfCheck(); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); - - Combatant dummy{ "dummy", Utility::Classes::Rogue }; - game_state.StartFight(&dummy, 0); - EXPECT_TRUE(std::holds_alternative(game_state.GetState())); -} - -#pragma endregion - -#pragma region ProtoTests - -TEST(ProtoTests, CanBuildCompileTestMessage) -{ - constexpr auto kName = "test"; - constexpr auto kType = combatcpp::CompileTest_Type::CompileTest_Type_TYPE_MSG; - - combatcpp::CompileTest msg; - msg.set_name(kName); - msg.set_type(kType); - - EXPECT_STREQ(kName, msg.name().c_str()); - EXPECT_EQ(kType, msg.type()); -} - -#pragma endregion - -#pragma region LegacyTests -/* -TEST_F(CombatantTest, SetStatsChangesStatsAndModifiers) -{ - auto default_stats = player.GetStats(); - auto default_modifiers = player.GetModifiers(); - auto default_max_health = player.GetHealth(); - - // Test max modifier stats - std::vector mega_stats(6, 20); - player.SetStats(mega_stats); - - auto new_max_health = player.GetHealth(); - ASSERT_GT(new_max_health, default_max_health); - - auto new_stats = player.GetStats(); - auto new_modifiers = player.GetModifiers(); - for (uint8_t i = 0; i < 6; ++i) - { - ASSERT_GT(new_stats[i], default_stats[i]); - ASSERT_GT(new_modifiers[i], default_modifiers[i]); - } - - // Test min modifier stats - std::vector weak_stats(6, 2); - player.SetStats(weak_stats); - - new_max_health = player.GetHealth(); - ASSERT_LT(new_max_health, default_max_health); - - new_stats = player.GetStats(); - new_modifiers = player.GetModifiers(); - for (uint8_t i = 0; i < 6; ++i) - { - ASSERT_LT(new_stats[i], default_stats[i]); - ASSERT_LT(new_modifiers[i], default_modifiers[i]); - } -} -*/ -#pragma endregion diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..9a57508 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,26 @@ +find_package(GTest CONFIG REQUIRED) + +macro(make_test_exe name) + set(TEST_NAME "test_${name}") + add_executable(${TEST_NAME} + ${TEST_NAME}.cpp + ) + + target_compile_features(${TEST_NAME} + PRIVATE + cxx_std_17 + ) + + target_link_libraries(${TEST_NAME} + PRIVATE + GTest::gtest_main + messages + combat_cpp + ) + + add_test(${TEST_NAME} ${TEST_NAME}) +endmacro(make_test_exe) + +make_test_exe(combatant) +make_test_exe(proto) +make_test_exe(state) diff --git a/test/test_combatant.cpp b/test/test_combatant.cpp new file mode 100644 index 0000000..be838de --- /dev/null +++ b/test/test_combatant.cpp @@ -0,0 +1,102 @@ +#include + +#include "combat_cpp/Combatant.hpp" + + +class CombatantTest : public testing::Test +{ +protected: + Combatant player{ "player", Utility::Classes::Rogue }; + Combatant enemy{ "enemy", Utility::Classes::Fighter }; +}; + +TEST_F(CombatantTest, Constructor) +{ + ASSERT_STREQ(player.GetName().c_str(), "player"); + ASSERT_STREQ(player.GetClass().c_str(), "Rogue"); + ASSERT_STREQ(enemy.GetName().c_str(), "enemy"); + ASSERT_STREQ(enemy.GetClass().c_str(), "Fighter"); +} + +TEST_F(CombatantTest, SustainDamageDecreasesHealth) +{ + auto max_health = player.GetHealth(); + player.SustainDamage(max_health - 1); + EXPECT_LT(player.GetHealth(), max_health); +} + +TEST_F(CombatantTest, SustainDamageDecreasesCorrectAmount) +{ + auto max_health = player.GetHealth(); + player.SustainDamage(max_health - 1); + EXPECT_EQ(player.GetHealth(), 1); +} + +TEST_F(CombatantTest, SustainDamageDoesNotUnderflow) +{ + player.SustainDamage(100); + EXPECT_EQ(player.GetHealth(), 0); +} + +TEST_F(CombatantTest, HealIncreasesHealthIfDamaged) +{ + auto max_health = player.GetHealth(); + player.SustainDamage(max_health - 1); + auto damaged = player.GetHealth(); + player.Heal(-1); + EXPECT_GT(player.GetHealth(), damaged); +} + +TEST_F(CombatantTest, HealDoesNotGoAboveMaxHealth) +{ + auto max_health = player.GetHealth(); + player.Heal(-1); + EXPECT_EQ(player.GetHealth(), max_health); +} + +TEST_F(CombatantTest, DetermineModifierReturnsProperValuesForValidRange) +{ + std::vector expected_modifiers = + { + -4, // 2 + -4, // 3 + -4, // 4 + -3, // 5 + -3, // 6 + -2, // 7 + -2, // 8 + -1, // 9 + -1, // 10 + 0, // 11 + 0, // 12 + 1, // 13 + 1, // 14 + 2, // 15 + 2, // 16 + 3, // 17 + 3, // 18 + 4, // 19 + 4 // 20 + }; + + std::vector calulated_modifiers; + for (uint8_t i = 2; i <= 20; ++i) + { + calulated_modifiers.push_back(Utility::DetermineModifier(i)); + ASSERT_EQ(calulated_modifiers[i-2], expected_modifiers[i-2]); + } +} + +TEST_F(CombatantTest, DetermineModifiersReturnsINT8_MINOutOfRange) +{ + ASSERT_EQ(Utility::DetermineModifier(100), INT8_MIN); + ASSERT_EQ(Utility::DetermineModifier(0), INT8_MIN); +} + +// NOTE: this test is a little weird as attack is a random action +TEST_F(CombatantTest, AttackDoesDamage) +{ + auto max_health = enemy.GetHealth(); + while (player.Attack(&enemy).first.damage == 0); + EXPECT_LT(enemy.GetHealth(), max_health); +} diff --git a/test/test_proto.cpp b/test/test_proto.cpp new file mode 100644 index 0000000..8c5b1e3 --- /dev/null +++ b/test/test_proto.cpp @@ -0,0 +1,16 @@ +#include + +#include "messages/compile_test.pb.h" + +TEST(ProtoTests, CanBuildCompileTestMessage) +{ + constexpr auto kName = "test"; + constexpr auto kType = combatcpp::CompileTest_Type::CompileTest_Type_TYPE_MSG; + + combatcpp::CompileTest msg; + msg.set_name(kName); + msg.set_type(kType); + + EXPECT_STREQ(kName, msg.name().c_str()); + EXPECT_EQ(kType, msg.type()); +} diff --git a/test/test_state.cpp b/test/test_state.cpp new file mode 100644 index 0000000..4f301fe --- /dev/null +++ b/test/test_state.cpp @@ -0,0 +1,135 @@ +#include + +#include "combat_cpp/Combatant.hpp" +#include "combat_cpp/GameLogic.hpp" + + +class StateTest : public testing::Test +{ +protected: + GameState game_state; +}; + +TEST_F(StateTest, GameInitialisesToIdleState) +{ + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, IdleCanTransitionToCombat) +{ + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, IdleCanTransitionToResting) +{ + game_state.TakeRest(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, IdleCanTransitionToSelfChecking) +{ + game_state.SelfCheck(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, IdleCanTransitionToLookingAround) +{ + game_state.LookAround(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, CombatCanTransitionToRest) +{ + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + game_state.TakeRest(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, CombatCanTransitionToIdle) +{ + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + game_state.FinishFight(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, RestCanTransitionToIdle) +{ + game_state.TakeRest(); + game_state.FinishRest(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, SelfCheckingCanTransitionToIdle) +{ + game_state.SelfCheck(); + game_state.FinishSelfCheck(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, LookingAroundCanTransitionToIdle) +{ + game_state.LookAround(); + game_state.FinishLook(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, InvalidTransitionsFromCombatDontChangeState) +{ + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + + game_state.LookAround(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + game_state.SelfCheck(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, InvalidTransitionsFromRestDontChangeState) +{ + game_state.TakeRest(); + + game_state.LookAround(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + game_state.SelfCheck(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, InvalidTransitionsFromSelfCheckingDontChangeState) +{ + game_state.SelfCheck(); + + game_state.LookAround(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + game_state.TakeRest(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} + +TEST_F(StateTest, InvalidTransitionsFromLookingAroundDontChangeState) +{ + game_state.LookAround(); + + game_state.TakeRest(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + game_state.SelfCheck(); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); + + Combatant dummy{ "dummy", Utility::Classes::Rogue }; + game_state.StartFight(&dummy, 0); + EXPECT_TRUE(std::holds_alternative(game_state.GetState())); +} diff --git a/vcpkg.json b/vcpkg.json index e5a8ae8..318be50 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "name": "combat-cpp", "version-string": "0.0.0", "dependencies": [ - "protobuf" + "protobuf", + "gtest" ] } \ No newline at end of file