From b4efa027628a7ce3870e0c65e751d0f7eefbd33c Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Tue, 7 May 2024 11:27:32 +0000 Subject: [PATCH 01/10] Add tools directory + Outline handstrengths simulator tool --- CMakeLists.txt | 2 ++ tools/CMakeLists.txt | 7 +++++++ tools/hand_strengths/main.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/hand_strengths/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b107571..79fa2d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) set(SRC_DIR ${PROJECT_SOURCE_DIR}/src) set(TEST_DIR ${PROJECT_SOURCE_DIR}/tests) +set(TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools) set(PLAYER_DIR ${SRC_DIR}/players) set(CHECK_PLAYER ${PLAYER_DIR}/check_player/check_player.cpp) set(RAND_PLAYER ${PLAYER_DIR}/rand_player/rand_player.cpp) @@ -31,6 +32,7 @@ include_directories(${plog_SOURCE_DIR}/include) get_cmake_property(_variableNames VARIABLES) add_subdirectory(src) +add_subdirectory(tools) if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_subdirectory(tests) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..a93ae62 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,7 @@ +# Add the executable target +add_executable(hand_strengths hand_strengths/main.cpp) +# Include headers +target_include_directories(hand_strengths PUBLIC ${INCLUDE_DIR}) + +# Link with plog library +target_link_libraries(hand_strengths plog) \ No newline at end of file diff --git a/tools/hand_strengths/main.cpp b/tools/hand_strengths/main.cpp new file mode 100644 index 0000000..c553e20 --- /dev/null +++ b/tools/hand_strengths/main.cpp @@ -0,0 +1,26 @@ +#include "deck.h" + +int main(int argc, char** argv) { + srand(time(NULL)); // init random seed + // init logger + static plog::ColorConsoleAppender consoleAppender; + // add file logger + static plog::RollingFileAppender fileAppender("log_tool.txt", 1024 * 1024 * 10, 5); + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0) { + plog::init(plog::verbose, &consoleAppender).addAppender(&fileAppender); + break; + } + if (strcmp(argv[i], "-i") == 0) { + plog::init(plog::info, &consoleAppender).addAppender(&fileAppender); + break; + } + } + PLOG_INFO << "Starting Tool"; + + // TODO + + PLOG_INFO << "Finished Tool"; + + return 0; +} \ No newline at end of file From 05597dd3acaf5d5fe9e3da299f86a956f96cd616 Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Wed, 8 May 2024 21:21:59 +0000 Subject: [PATCH 02/10] Fix typo --- include/deck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/deck.h b/include/deck.h index 58acd15..512c13a 100644 --- a/include/deck.h +++ b/include/deck.h @@ -39,7 +39,7 @@ class Deck { /// @brief Generates a new deck of 52 cards /// @exception Guarantee No-throw constexpr Deck() noexcept { - // generate a 53 cards poker deck + // generate a 52 cards poker deck u_int8_t i = 0; for (u_int8_t suit = 0; suit < 4; suit++) { for (u_int8_t rank = 2; rank < 15; rank++) { From 32f68b7e3b929375721e4ff5eb772611aab6cd7e Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Wed, 8 May 2024 21:23:16 +0000 Subject: [PATCH 03/10] Add hand utils class to provide functionality for the tool --- tools/hand_strengths/hand_utils.cpp | 84 +++++++++++++++++++++ tools/hand_strengths/hand_utils.h | 111 ++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 tools/hand_strengths/hand_utils.cpp create mode 100644 tools/hand_strengths/hand_utils.h diff --git a/tools/hand_strengths/hand_utils.cpp b/tools/hand_strengths/hand_utils.cpp new file mode 100644 index 0000000..de4f08a --- /dev/null +++ b/tools/hand_strengths/hand_utils.cpp @@ -0,0 +1,84 @@ +#include + +#include "hand_utils.h" + +constexpr u_int8_t HandUtils::getHandIndex(const std::pair playerCards) noexcept { + const int16_t r1 = std::max(playerCards.first.rank, playerCards.second.rank); + const int16_t r2 = std::min(playerCards.first.rank, playerCards.second.rank); + return r1 + r2 - 4 + (r1 - 2) * (r1 - 3) / 2; +} + +constexpr unsigned char* HandUtils::getHandName(int8_t handIndex) noexcept { + // remove the possible ranks until the index is negative or 0. r1 is now the last removed rank + 1 + // r2 is r1 if the index is 0, otherwise the index + rank + u_int8_t r1 = 0; + u_int8_t r2 = 0; + for(u_int8_t i = 2; i < 14; i++){ + handIndex -= i; + if(handIndex <= 0){ + r1 = i+1; + r2 = handIndex == 0 ? i+1 : handIndex + i; + break; + } + } + // fill the name array with the ranks and the null terminator + HandUtils::name[0] = HandUtils::ranks[r1-2]; + HandUtils::name[1] = HandUtils::ranks[r2-2]; + HandUtils::name[2] = '\0'; + return HandUtils::name; +} + +void HandUtils::evaluateHands(const Card communityCards[], const std::pair playerCards[], const u_int8_t players) noexcept { + // set player 0 as the default winner and their hand as the strongest + u_int8_t winners[players] = {0}; + u_int8_t numWinners = 1; + HandStrengths strongestHand = HandStrengths::getHandStrength(playerCards[0], communityCards); + HandStrengths tmpHand; + // compare the hands of all players to find the strongest + for(u_int8_t p = 1; p < players; p++){ + tmpHand = HandStrengths::getHandStrength(playerCards[p], communityCards); + if(tmpHand > strongestHand){ + // new strongest hand, new winner + strongestHand = tmpHand; + numWinners = 1; + winners[0] = p; + } else if(tmpHand == strongestHand) + // same strength, add to winners + winners[numWinners++] = p; + } + // update the internal arrays with the winners and the total count for each hand + this->addWinners(playerCards, winners, numWinners, players); +} + +void HandUtils::writeResults(const std::string& filename, const u_int8_t players, const bool newFile) const noexcept { + // write hand + total + hand/total in csv file + std::ofstream file(filename, newFile ? std::ios::trunc : std::ios::app); + if(newFile) file << "Players, Hand, Suited, Wins, Total, Wins/Total, Wins/Total*Players\n"; + for(u_int8_t i = 0; i < 91; i++){ + //TODO: add hand names + file << +players << ", " << +i << ", true, " << this->handsSuited[i] << ", " << this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] * players << ",\n"; + file << +players << ", " << +i << ", false, " << this->handsUnsuited[i] << ", " << this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] * players << ",\n"; + } + file.close(); +} + +void HandUtils::addWinners(const std::pair playerCards[], const u_int8_t winners[], const u_int8_t numWinners, const u_int8_t players) noexcept { + // the amount that is added to the win stat if the hand wins + const u_int8_t add = (numWinners == 1 ? this->winnerAdd : this->splitAdd); + // iterate over all player hands + for(u_int8_t i = 0; i < players; i++){ + const u_int8_t ind = this->getHandIndex(playerCards[i]); + // add total to the total count and only if the hand wins, add to the win count + if(playerCards[i].first.suit == playerCards[i].second.suit){ + // suited + this->handsSuitedTotal[ind] += this->totalAdd; + if(std::find(winners, winners + numWinners, i) != winners + numWinners) + this->handsSuited[ind] += add; + } else { + // offsuited + this->handsUnsuitedTotal[ind] += this->totalAdd; + if(std::find(winners, winners + numWinners, i) != winners + numWinners) + this->handsUnsuited[ind] += add; + } + } +} diff --git a/tools/hand_strengths/hand_utils.h b/tools/hand_strengths/hand_utils.h new file mode 100644 index 0000000..c2825c2 --- /dev/null +++ b/tools/hand_strengths/hand_utils.h @@ -0,0 +1,111 @@ +#pragma once + +#include "hand_strengths.h" + +class HandUtils{ + public: + /// @brief Constructor clears all arrays + /// @param winnerAdd The amount to add to the win stat if the hand is the only winner + /// @param splitAdd The amount to add to the win stat if the hand is a split winner + /// @param totalAdd The amount to add to the total stat for each occurrence + /// @exception Guarantee No-throw + /// @note The totalAdd should be greater or equal than the maximum of winnerAdd and splitAdd to keep a meaningful total count + /// @note winnerAdd and splitAdd are used to balance the effects of winning and splitting + /// @note if splitAdd is 0, a split is treated as a loss, if winnerAdd is twice the value of splitAdd, a split is treated as a half win, etc. + HandUtils(const u_int8_t winnerAdd, const u_int8_t splitAdd, const u_int8_t totalAdd) noexcept : winnerAdd(winnerAdd), splitAdd(splitAdd), totalAdd(totalAdd) { + std::memset(this->handsSuited, 0, sizeof(this->handsSuited)); + std::memset(this->handsUnsuited, 0, sizeof(this->handsUnsuited)); + std::memset(this->handsSuitedTotal, 0, sizeof(this->handsSuitedTotal)); + std::memset(this->handsUnsuitedTotal, 0, sizeof(this->handsUnsuitedTotal)); + }; + + /// @brief Get the internal array index for a hand + /// @param playerCards The player's hand cards + /// @return The index of the hand in the internal arrays + /// @exception Guarantee No-throw + /// @note The index is calculated as r1+r2-4+(r1-2)*(r1-3)/2 with r1 >= r2 + /// @note The index is needed to map the player hands to the internal arrays (91 different options) (save memory and time) + /// @note There are hands that are "equal" like 2,3 and 3,2 + /// @note The suit is ignored for the index, the caller has to check if the hand is suited or not + /// @note With this in mind, there are 91 different hands (13+12+11+...+1) + /// @see getHandName() to get the name of a hand by its index + static constexpr u_int8_t getHandIndex(const std::pair playerCards) noexcept; + + /// @brief Get the name of a hand by its index + /// @param handIndex The (array) index of the hand + /// @return The name of the hand as a string in the format "XY" where X and Y are the ranks of the cards (X >= Y) + /// @exception Guarantee No-throw + /// @note The name is calculated by inverting r1+r2-4+(r1-2)*(r1-3)/2 with r1 >= r2 and r1, r2 in [2, 14] + /// @note The return array has 4 elements, the first two are the ranks + you can add a "s" or "o" to indicate suited or offsuited + null terminator + /// @note The caller has to know if the hand is suited or not (and add the suit to the name if needed) + /// @see getHandIndex() to get the index of a hand by its cards + static constexpr unsigned char* getHandName(int8_t handIndex) noexcept; + + /// @brief Evaluate the hands of the players and update the internal arrays + /// @param communityCards The community cards on the table (5 cards) + /// @param playerCards The players hand cards (number of players = array length with 2 cards each) + /// @param players The number of players + /// @exception Guarantee No-throw + /// @note The function calculates the hand strength of each player and compares them to find the winners and splits + /// @note The function then updates the internal arrays with the count for wins and split and the total count for each hand + /// @note The function does not check for impossible hands like multiple same cards + /// @note There are also a few impossible suited hands in the arrays like AAs but to remove them would be more complex (there are just 0 occurrences of them) + /// @see The hands are mapped to the internal arrays with the getHandIndex() function + /// @see The function addWinners() is used to update the internal arrays + void evaluateHands(const Card communityCards[], const std::pair playerCards[], const u_int8_t players) noexcept; + + /// @brief Write the results to a file in csv format + /// @param filename The name (path) of the file to write to + /// @param players The number of players + /// @param newFile If true, the file is created or overwritten, otherwise the results are appended + /// @exception Guarantee No-throw + /// @note The function writes the results for each hand in the internal arrays to the file + /// @note It writes Player number, Hand index, Suited (true, false), Wins and Split count, Total, Wins/Total, Wins/Total*Players + /// @note The last two are used to rank the hands by their strength + /// @note Wins/Total is the win rate of the hand and Wins/Total*Players is the normalized win rate (1 is average win rate, <1 is under average, >1 is above average) + void writeResults(const std::string& filename, const u_int8_t players, const bool newFile = true) const noexcept; + + private: + /// @brief Add the data to the internal arrays + /// @param playerCards The players hand cards + /// @param winners The indices of the winners + /// @param numWinners The number of winners + /// @param players The number of players + /// @exception Guarantee No-throw + /// @note The function adds the data to the internal arrays based on the winners and the players hands + /// @note The function uses the winnerAdd and splitAdd values to balance the effects of winning and splitting + /// @note The function uses the totalAdd value to add to the total count for each occurring hand + void addWinners(const std::pair playerCards[], const u_int8_t winners[], const u_int8_t numWinners, const u_int8_t players) noexcept; + + + /// @brief The amount to add to the win stat if the hand is the only winner + const u_int8_t winnerAdd; + + /// @brief The amount to add to the win stat if the hand is a split winner + const u_int8_t splitAdd; + + /// @brief The amount to add to the total stat for each occurrence + const u_int8_t totalAdd; + + /// @brief The array for win stats of each suited hand + /// @note The index is calculated with getHandIndex() + u_int32_t handsSuited[91]; + + /// @brief The array for win stats of each unsuited hand + /// @note The index is calculated with getHandIndex() + u_int32_t handsUnsuited[91]; + + /// @brief The array for total stats of each suited hand + /// @note The index is calculated with getHandIndex() + u_int32_t handsSuitedTotal[91]; + + /// @brief The array for total stats of each unsuited hand + /// @note The index is calculated with getHandIndex() + u_int32_t handsUnsuitedTotal[91]; + + /// @brief The array for the ranks of the cards (lookup array) + static constexpr unsigned char ranks[14] = "23456789TJQKA"; // + null terminator + + /// @brief Static member that is used to return the hand name + static unsigned char name[4]; +}; \ No newline at end of file From 7c510d69f54eb16a1d16fea7974ebe1c4fc1390e Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Wed, 8 May 2024 21:24:06 +0000 Subject: [PATCH 04/10] Update handstrengths main tool + Fix Cmake --- tools/CMakeLists.txt | 2 +- tools/hand_strengths/main.cpp | 46 +++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a93ae62..3fe2e47 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,5 +1,5 @@ # Add the executable target -add_executable(hand_strengths hand_strengths/main.cpp) +add_executable(hand_strengths ${SRC_DIR}/deck.cpp hand_strengths/main.cpp hand_strengths/hand_utils.cpp) # Include headers target_include_directories(hand_strengths PUBLIC ${INCLUDE_DIR}) diff --git a/tools/hand_strengths/main.cpp b/tools/hand_strengths/main.cpp index c553e20..9a0cbf7 100644 --- a/tools/hand_strengths/main.cpp +++ b/tools/hand_strengths/main.cpp @@ -1,4 +1,4 @@ -#include "deck.h" +#include "hand_utils.h" int main(int argc, char** argv) { srand(time(NULL)); // init random seed @@ -6,21 +6,53 @@ int main(int argc, char** argv) { static plog::ColorConsoleAppender consoleAppender; // add file logger static plog::RollingFileAppender fileAppender("log_tool.txt", 1024 * 1024 * 10, 5); + // options + u_int64_t iters = 10000000; + std::string filename = "hand_strengths.csv"; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-v") == 0) { plog::init(plog::verbose, &consoleAppender).addAppender(&fileAppender); - break; } - if (strcmp(argv[i], "-i") == 0) { + else if (strcmp(argv[i], "-i") == 0) { plog::init(plog::info, &consoleAppender).addAppender(&fileAppender); - break; + } + else if (strcmp(argv[i], "--iters") == 0) { + iters = std::stoull(argv[i + 1]); + } + else if (strcmp(argv[i], "-o") == 0) { + std::cout << "Output file: " << argv[i + 1] << std::endl; + filename = argv[i + 1]; } } - PLOG_INFO << "Starting Tool"; + PLOG_INFO << "Starting Handstrengths Tool"; - // TODO + Deck deck; + Card communityCards[5]; + std::pair playerCards[MAX_PLAYERS]; + // iterate over any meaningful number of players + for (u_int8_t players = 2; players <= MAX_PLAYERS; players++) { + // set up HandUtils // TODO: set custom (main-)options + HandUtils handUtils(1, 1, 1); + // simulate for an amount of iterations + for(u_int64_t i = 0; i < iters; i++){ + // shuffle deck and draw cards + deck.shuffle(); + for(u_int8_t j = 0; j < 5; j++) communityCards[j] = deck.draw(); + for(u_int8_t j = 0; j < players; j++) { + playerCards[j].first = deck.draw(); + playerCards[j].second = deck.draw(); + } + // simulate a showdown and remember any winners and splits as well as the total for each occurring hand + handUtils.evaluateHands(communityCards, playerCards, players); + // reset deck + deck.reset(); + } + // write the results for each player count to a file + handUtils.writeResults(filename, players, players == 2); + std::cout << "Wrote results for " << +players << " players\n"; + } - PLOG_INFO << "Finished Tool"; + PLOG_INFO << "Finished Handstrengths Tool"; return 0; } \ No newline at end of file From 4ca3e386b47a1448175d9a96ccd39f76a0cceb96 Mon Sep 17 00:00:00 2001 From: Moritz Richter <79808908+Duzzuti@users.noreply.github.com> Date: Wed, 8 May 2024 23:29:18 +0200 Subject: [PATCH 05/10] Update format_check.yml --- .github/workflows/format_check.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml index ec95d33..ef4905a 100644 --- a/.github/workflows/format_check.yml +++ b/.github/workflows/format_check.yml @@ -17,6 +17,8 @@ jobs: - 'src/players/*/' - 'include' - 'tests' + - 'tests/*/' + - 'tools/*/' steps: - uses: actions/checkout@v3 - name: Run clang-format style check for C/C++/Protobuf programs. @@ -24,4 +26,4 @@ jobs: with: clang-format-version: '13' check-path: ${{ matrix.path }} - fallback-style: 'Google' \ No newline at end of file + fallback-style: 'Google' From 1d617fe17b69aff0867b7f727a4ac1292703b3d8 Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Wed, 8 May 2024 21:30:17 +0000 Subject: [PATCH 06/10] Format --- format.sh | 2 +- tools/hand_strengths/hand_utils.cpp | 44 ++++++++++++++--------------- tools/hand_strengths/hand_utils.h | 15 +++++----- tools/hand_strengths/main.cpp | 15 ++++------ 4 files changed, 36 insertions(+), 40 deletions(-) diff --git a/format.sh b/format.sh index cc5bc25..741fdd6 100644 --- a/format.sh +++ b/format.sh @@ -1 +1 @@ -clang-format -i src/*.cpp include/*.h tests/*.cpp tests/*.h tests/*/*.cpp tests/*/*.h src/players/*/*.cpp src/players/*/*.h --style file:.clang-format \ No newline at end of file +clang-format -i src/*.cpp include/*.h tests/*.cpp tests/*.h tests/*/*.cpp tests/*/*.h src/players/*/*.cpp src/players/*/*.h tools/*/*.h tools/*/*.cpp --style file:.clang-format \ No newline at end of file diff --git a/tools/hand_strengths/hand_utils.cpp b/tools/hand_strengths/hand_utils.cpp index de4f08a..865da7d 100644 --- a/tools/hand_strengths/hand_utils.cpp +++ b/tools/hand_strengths/hand_utils.cpp @@ -1,7 +1,7 @@ -#include - #include "hand_utils.h" +#include + constexpr u_int8_t HandUtils::getHandIndex(const std::pair playerCards) noexcept { const int16_t r1 = std::max(playerCards.first.rank, playerCards.second.rank); const int16_t r2 = std::min(playerCards.first.rank, playerCards.second.rank); @@ -13,17 +13,17 @@ constexpr unsigned char* HandUtils::getHandName(int8_t handIndex) noexcept { // r2 is r1 if the index is 0, otherwise the index + rank u_int8_t r1 = 0; u_int8_t r2 = 0; - for(u_int8_t i = 2; i < 14; i++){ + for (u_int8_t i = 2; i < 14; i++) { handIndex -= i; - if(handIndex <= 0){ - r1 = i+1; - r2 = handIndex == 0 ? i+1 : handIndex + i; + if (handIndex <= 0) { + r1 = i + 1; + r2 = handIndex == 0 ? i + 1 : handIndex + i; break; } } // fill the name array with the ranks and the null terminator - HandUtils::name[0] = HandUtils::ranks[r1-2]; - HandUtils::name[1] = HandUtils::ranks[r2-2]; + HandUtils::name[0] = HandUtils::ranks[r1 - 2]; + HandUtils::name[1] = HandUtils::ranks[r2 - 2]; HandUtils::name[2] = '\0'; return HandUtils::name; } @@ -35,14 +35,14 @@ void HandUtils::evaluateHands(const Card communityCards[], const std::pair strongestHand){ + if (tmpHand > strongestHand) { // new strongest hand, new winner strongestHand = tmpHand; numWinners = 1; winners[0] = p; - } else if(tmpHand == strongestHand) + } else if (tmpHand == strongestHand) // same strength, add to winners winners[numWinners++] = p; } @@ -53,11 +53,13 @@ void HandUtils::evaluateHands(const Card communityCards[], const std::pairhandsSuited[i] << ", " << this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] * players << ",\n"; - file << +players << ", " << +i << ", false, " << this->handsUnsuited[i] << ", " << this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] * players << ",\n"; + if (newFile) file << "Players, Hand, Suited, Wins, Total, Wins/Total, Wins/Total*Players\n"; + for (u_int8_t i = 0; i < 91; i++) { + // TODO: add hand names + file << +players << ", " << +i << ", true, " << this->handsSuited[i] << ", " << this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] << ", " + << (double)this->handsSuited[i] / this->handsSuitedTotal[i] * players << ",\n"; + file << +players << ", " << +i << ", false, " << this->handsUnsuited[i] << ", " << this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] << ", " + << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] * players << ",\n"; } file.close(); } @@ -66,19 +68,17 @@ void HandUtils::addWinners(const std::pair playerCards[], const u_in // the amount that is added to the win stat if the hand wins const u_int8_t add = (numWinners == 1 ? this->winnerAdd : this->splitAdd); // iterate over all player hands - for(u_int8_t i = 0; i < players; i++){ + for (u_int8_t i = 0; i < players; i++) { const u_int8_t ind = this->getHandIndex(playerCards[i]); // add total to the total count and only if the hand wins, add to the win count - if(playerCards[i].first.suit == playerCards[i].second.suit){ + if (playerCards[i].first.suit == playerCards[i].second.suit) { // suited this->handsSuitedTotal[ind] += this->totalAdd; - if(std::find(winners, winners + numWinners, i) != winners + numWinners) - this->handsSuited[ind] += add; + if (std::find(winners, winners + numWinners, i) != winners + numWinners) this->handsSuited[ind] += add; } else { // offsuited this->handsUnsuitedTotal[ind] += this->totalAdd; - if(std::find(winners, winners + numWinners, i) != winners + numWinners) - this->handsUnsuited[ind] += add; + if (std::find(winners, winners + numWinners, i) != winners + numWinners) this->handsUnsuited[ind] += add; } } } diff --git a/tools/hand_strengths/hand_utils.h b/tools/hand_strengths/hand_utils.h index c2825c2..01811f1 100644 --- a/tools/hand_strengths/hand_utils.h +++ b/tools/hand_strengths/hand_utils.h @@ -2,7 +2,7 @@ #include "hand_strengths.h" -class HandUtils{ +class HandUtils { public: /// @brief Constructor clears all arrays /// @param winnerAdd The amount to add to the win stat if the hand is the only winner @@ -25,12 +25,12 @@ class HandUtils{ /// @exception Guarantee No-throw /// @note The index is calculated as r1+r2-4+(r1-2)*(r1-3)/2 with r1 >= r2 /// @note The index is needed to map the player hands to the internal arrays (91 different options) (save memory and time) - /// @note There are hands that are "equal" like 2,3 and 3,2 + /// @note There are hands that are "equal" like 2,3 and 3,2 /// @note The suit is ignored for the index, the caller has to check if the hand is suited or not /// @note With this in mind, there are 91 different hands (13+12+11+...+1) /// @see getHandName() to get the name of a hand by its index static constexpr u_int8_t getHandIndex(const std::pair playerCards) noexcept; - + /// @brief Get the name of a hand by its index /// @param handIndex The (array) index of the hand /// @return The name of the hand as a string in the format "XY" where X and Y are the ranks of the cards (X >= Y) @@ -40,7 +40,7 @@ class HandUtils{ /// @note The caller has to know if the hand is suited or not (and add the suit to the name if needed) /// @see getHandIndex() to get the index of a hand by its cards static constexpr unsigned char* getHandName(int8_t handIndex) noexcept; - + /// @brief Evaluate the hands of the players and update the internal arrays /// @param communityCards The community cards on the table (5 cards) /// @param playerCards The players hand cards (number of players = array length with 2 cards each) @@ -53,7 +53,7 @@ class HandUtils{ /// @see The hands are mapped to the internal arrays with the getHandIndex() function /// @see The function addWinners() is used to update the internal arrays void evaluateHands(const Card communityCards[], const std::pair playerCards[], const u_int8_t players) noexcept; - + /// @brief Write the results to a file in csv format /// @param filename The name (path) of the file to write to /// @param players The number of players @@ -77,7 +77,6 @@ class HandUtils{ /// @note The function uses the totalAdd value to add to the total count for each occurring hand void addWinners(const std::pair playerCards[], const u_int8_t winners[], const u_int8_t numWinners, const u_int8_t players) noexcept; - /// @brief The amount to add to the win stat if the hand is the only winner const u_int8_t winnerAdd; @@ -104,8 +103,8 @@ class HandUtils{ u_int32_t handsUnsuitedTotal[91]; /// @brief The array for the ranks of the cards (lookup array) - static constexpr unsigned char ranks[14] = "23456789TJQKA"; // + null terminator - + static constexpr unsigned char ranks[14] = "23456789TJQKA"; // + null terminator + /// @brief Static member that is used to return the hand name static unsigned char name[4]; }; \ No newline at end of file diff --git a/tools/hand_strengths/main.cpp b/tools/hand_strengths/main.cpp index 9a0cbf7..c4352d0 100644 --- a/tools/hand_strengths/main.cpp +++ b/tools/hand_strengths/main.cpp @@ -12,14 +12,11 @@ int main(int argc, char** argv) { for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-v") == 0) { plog::init(plog::verbose, &consoleAppender).addAppender(&fileAppender); - } - else if (strcmp(argv[i], "-i") == 0) { + } else if (strcmp(argv[i], "-i") == 0) { plog::init(plog::info, &consoleAppender).addAppender(&fileAppender); - } - else if (strcmp(argv[i], "--iters") == 0) { + } else if (strcmp(argv[i], "--iters") == 0) { iters = std::stoull(argv[i + 1]); - } - else if (strcmp(argv[i], "-o") == 0) { + } else if (strcmp(argv[i], "-o") == 0) { std::cout << "Output file: " << argv[i + 1] << std::endl; filename = argv[i + 1]; } @@ -34,11 +31,11 @@ int main(int argc, char** argv) { // set up HandUtils // TODO: set custom (main-)options HandUtils handUtils(1, 1, 1); // simulate for an amount of iterations - for(u_int64_t i = 0; i < iters; i++){ + for (u_int64_t i = 0; i < iters; i++) { // shuffle deck and draw cards deck.shuffle(); - for(u_int8_t j = 0; j < 5; j++) communityCards[j] = deck.draw(); - for(u_int8_t j = 0; j < players; j++) { + for (u_int8_t j = 0; j < 5; j++) communityCards[j] = deck.draw(); + for (u_int8_t j = 0; j < players; j++) { playerCards[j].first = deck.draw(); playerCards[j].second = deck.draw(); } From d61a48875e4ec4ca4dba6c721455d552c50fd164 Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Thu, 9 May 2024 18:54:10 +0000 Subject: [PATCH 07/10] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2601061..c60b1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ data model issues.txt log.*txt +log_tool.*txt +hand_strengths.csv # Prerequisites *.d From 00ec2c200bf127816d29cb0404a67c735be04c57 Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Thu, 9 May 2024 20:14:37 +0000 Subject: [PATCH 08/10] Fix bugs in getHandName() functionality --- tools/hand_strengths/hand_utils.cpp | 28 +++++++++++++--------------- tools/hand_strengths/hand_utils.h | 17 +++++++---------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/tools/hand_strengths/hand_utils.cpp b/tools/hand_strengths/hand_utils.cpp index 865da7d..f1e9afa 100644 --- a/tools/hand_strengths/hand_utils.cpp +++ b/tools/hand_strengths/hand_utils.cpp @@ -8,24 +8,23 @@ constexpr u_int8_t HandUtils::getHandIndex(const std::pair playerCar return r1 + r2 - 4 + (r1 - 2) * (r1 - 3) / 2; } -constexpr unsigned char* HandUtils::getHandName(int8_t handIndex) noexcept { +const std::string HandUtils::getHandName(int8_t handIndex) noexcept { // remove the possible ranks until the index is negative or 0. r1 is now the last removed rank + 1 // r2 is r1 if the index is 0, otherwise the index + rank u_int8_t r1 = 0; u_int8_t r2 = 0; - for (u_int8_t i = 2; i < 14; i++) { - handIndex -= i; + for (u_int8_t i = 1; i < 14; i++) { + if (i != 1) handIndex -= i; if (handIndex <= 0) { r1 = i + 1; - r2 = handIndex == 0 ? i + 1 : handIndex + i; + r2 = handIndex == 0 ? i + 1 : handIndex + i + 1; break; } } - // fill the name array with the ranks and the null terminator - HandUtils::name[0] = HandUtils::ranks[r1 - 2]; - HandUtils::name[1] = HandUtils::ranks[r2 - 2]; - HandUtils::name[2] = '\0'; - return HandUtils::name; + std::string name = ""; + name += HandUtils::ranks[r1 - 2]; + name += HandUtils::ranks[r2 - 2]; + return name; } void HandUtils::evaluateHands(const Card communityCards[], const std::pair playerCards[], const u_int8_t players) noexcept { @@ -53,13 +52,12 @@ void HandUtils::evaluateHands(const Card communityCards[], const std::pairhandsSuited[i] << ", " << this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] << ", " - << (double)this->handsSuited[i] / this->handsSuitedTotal[i] * players << ",\n"; - file << +players << ", " << +i << ", false, " << this->handsUnsuited[i] << ", " << this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] << ", " - << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] * players << ",\n"; + file << +players << ", " << +i << ", true, " << this->getHandName(i) << "s, " << this->handsSuited[i] << ", " << this->handsSuitedTotal[i] << ", " + << (double)this->handsSuited[i] / this->handsSuitedTotal[i] << ", " << (double)this->handsSuited[i] / this->handsSuitedTotal[i] * players << ",\n"; + file << +players << ", " << +i << ", false, " << this->getHandName(i) << "o, " << this->handsUnsuited[i] << ", " << this->handsUnsuitedTotal[i] << ", " + << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] << ", " << (double)this->handsUnsuited[i] / this->handsUnsuitedTotal[i] * players << ",\n"; } file.close(); } diff --git a/tools/hand_strengths/hand_utils.h b/tools/hand_strengths/hand_utils.h index 01811f1..4bdc76d 100644 --- a/tools/hand_strengths/hand_utils.h +++ b/tools/hand_strengths/hand_utils.h @@ -1,7 +1,7 @@ #pragma once #include "hand_strengths.h" - +//TODO: unittests class HandUtils { public: /// @brief Constructor clears all arrays @@ -36,10 +36,10 @@ class HandUtils { /// @return The name of the hand as a string in the format "XY" where X and Y are the ranks of the cards (X >= Y) /// @exception Guarantee No-throw /// @note The name is calculated by inverting r1+r2-4+(r1-2)*(r1-3)/2 with r1 >= r2 and r1, r2 in [2, 14] - /// @note The return array has 4 elements, the first two are the ranks + you can add a "s" or "o" to indicate suited or offsuited + null terminator + /// @note The return array does not include the suit. You can add a "s" or "o" to indicate suited or offsuited /// @note The caller has to know if the hand is suited or not (and add the suit to the name if needed) /// @see getHandIndex() to get the index of a hand by its cards - static constexpr unsigned char* getHandName(int8_t handIndex) noexcept; + static const std::string getHandName(int8_t handIndex) noexcept; /// @brief Evaluate the hands of the players and update the internal arrays /// @param communityCards The community cards on the table (5 cards) @@ -60,11 +60,14 @@ class HandUtils { /// @param newFile If true, the file is created or overwritten, otherwise the results are appended /// @exception Guarantee No-throw /// @note The function writes the results for each hand in the internal arrays to the file - /// @note It writes Player number, Hand index, Suited (true, false), Wins and Split count, Total, Wins/Total, Wins/Total*Players + /// @note It writes Player number, Hand index, Suited (true, false), Hand Name, Wins and Split count, Total, Wins/Total, Wins/Total*Players /// @note The last two are used to rank the hands by their strength /// @note Wins/Total is the win rate of the hand and Wins/Total*Players is the normalized win rate (1 is average win rate, <1 is under average, >1 is above average) void writeResults(const std::string& filename, const u_int8_t players, const bool newFile = true) const noexcept; + /// @brief The array for the ranks of the cards (lookup array) + static constexpr char ranks[14] = "23456789TJQKA"; // + null terminator + private: /// @brief Add the data to the internal arrays /// @param playerCards The players hand cards @@ -101,10 +104,4 @@ class HandUtils { /// @brief The array for total stats of each unsuited hand /// @note The index is calculated with getHandIndex() u_int32_t handsUnsuitedTotal[91]; - - /// @brief The array for the ranks of the cards (lookup array) - static constexpr unsigned char ranks[14] = "23456789TJQKA"; // + null terminator - - /// @brief Static member that is used to return the hand name - static unsigned char name[4]; }; \ No newline at end of file From 9d13cbc335041c19f0d18ef51660a4057684f0ec Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Thu, 9 May 2024 20:23:34 +0000 Subject: [PATCH 09/10] Add handutils options --- .gitignore | 2 +- tools/hand_strengths/hand_utils.h | 2 +- tools/hand_strengths/main.cpp | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c60b1eb..3076c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ model issues.txt log.*txt log_tool.*txt -hand_strengths.csv +hand_strengths*.csv # Prerequisites *.d diff --git a/tools/hand_strengths/hand_utils.h b/tools/hand_strengths/hand_utils.h index 4bdc76d..f026dea 100644 --- a/tools/hand_strengths/hand_utils.h +++ b/tools/hand_strengths/hand_utils.h @@ -1,7 +1,7 @@ #pragma once #include "hand_strengths.h" -//TODO: unittests +// TODO: unittests class HandUtils { public: /// @brief Constructor clears all arrays diff --git a/tools/hand_strengths/main.cpp b/tools/hand_strengths/main.cpp index c4352d0..23f58b0 100644 --- a/tools/hand_strengths/main.cpp +++ b/tools/hand_strengths/main.cpp @@ -7,8 +7,11 @@ int main(int argc, char** argv) { // add file logger static plog::RollingFileAppender fileAppender("log_tool.txt", 1024 * 1024 * 10, 5); // options + u_int8_t winnerAdd = 1; + u_int8_t splitAdd = 1; + u_int8_t totalAdd = 1; u_int64_t iters = 10000000; - std::string filename = "hand_strengths.csv"; + std::string filename = ""; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-v") == 0) { plog::init(plog::verbose, &consoleAppender).addAppender(&fileAppender); @@ -19,8 +22,16 @@ int main(int argc, char** argv) { } else if (strcmp(argv[i], "-o") == 0) { std::cout << "Output file: " << argv[i + 1] << std::endl; filename = argv[i + 1]; + } else if (strcmp(argv[i], "--options") == 0) { + winnerAdd = std::stoi(argv[i + 1]); + splitAdd = std::stoi(argv[i + 2]); + totalAdd = std::stoi(argv[i + 3]); } } + if (filename == "") { + filename = "hand_strengths" + std::to_string(+winnerAdd) + std::to_string(+splitAdd) + std::to_string(+totalAdd) + ".csv"; + } + PLOG_INFO << "Starting Handstrengths Tool"; Deck deck; @@ -28,8 +39,8 @@ int main(int argc, char** argv) { std::pair playerCards[MAX_PLAYERS]; // iterate over any meaningful number of players for (u_int8_t players = 2; players <= MAX_PLAYERS; players++) { - // set up HandUtils // TODO: set custom (main-)options - HandUtils handUtils(1, 1, 1); + // set up HandUtils + HandUtils handUtils(winnerAdd, splitAdd, totalAdd); // simulate for an amount of iterations for (u_int64_t i = 0; i < iters; i++) { // shuffle deck and draw cards From 52b39b815e29be70539829d779a5a487f6feab1c Mon Sep 17 00:00:00 2001 From: Duzzuti Date: Fri, 10 May 2024 16:25:55 +0000 Subject: [PATCH 10/10] Add hand strengths tool unittest --- CMakeLists.txt | 1 + tests/unittests/CMakeLists.txt | 5 ++++ tests/unittests/thandutils_unittest.cpp | 32 +++++++++++++++++++++++++ tools/CMakeLists.txt | 2 +- tools/hand_strengths/hand_utils.h | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/unittests/thandutils_unittest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 79fa2d8..616c3fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) set(SRC_DIR ${PROJECT_SOURCE_DIR}/src) set(TEST_DIR ${PROJECT_SOURCE_DIR}/tests) set(TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools) +set(THAND_STRENGTHS_DIR ${TOOLS_DIR}/hand_strengths) set(PLAYER_DIR ${SRC_DIR}/players) set(CHECK_PLAYER ${PLAYER_DIR}/check_player/check_player.cpp) set(RAND_PLAYER ${PLAYER_DIR}/rand_player/rand_player.cpp) diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 1861625..40ac1e8 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -33,6 +33,10 @@ add_executable(poker_test_gametest main_test.cpp gametest_unittest.cpp ${SRC_DIR target_link_libraries(poker_test_gametest gtest_main) target_include_directories(poker_test_gametest PUBLIC ${INCLUDE_DIR} ${PLAYER_DIR} ${TEST_DIR}) +add_executable(poker_test_thandstrengths main_test.cpp thandutils_unittest.cpp ${THAND_STRENGTHS_DIR}/hand_utils.cpp) +target_link_libraries(poker_test_thandstrengths gtest_main) +target_include_directories(poker_test_thandstrengths PUBLIC ${INCLUDE_DIR} ${THAND_STRENGTHS_DIR}) + add_executable(test main_test.cpp test_test.cpp) target_link_libraries(test gtest_main) target_include_directories(test PUBLIC ${INCLUDE_DIR}) @@ -43,4 +47,5 @@ add_test(POT_TEST poker_test_pot) add_test(UTILS_TEST poker_test_utils) add_test(CONST_TEST poker_test_const) add_test(GAME_TEST poker_test_gametest) +add_test(THANDSTRENGTHS_TEST poker_test_thandstrengths) add_test(TEST_TEST test) \ No newline at end of file diff --git a/tests/unittests/thandutils_unittest.cpp b/tests/unittests/thandutils_unittest.cpp new file mode 100644 index 0000000..741061b --- /dev/null +++ b/tests/unittests/thandutils_unittest.cpp @@ -0,0 +1,32 @@ +#include + +#include "hand_utils.h" + +TEST(THandUtils, handIndex) { + // iterates through all card combinations where r1 >= r2 and through all suit combinations + // checks if the index correct for all combinations and if the back-converted name is also correct + u_int8_t expectedInd = 0; + const std::string rankNames = "23456789TJQKA"; + for (u_int8_t r1 = 2; r1 < 15; r1++) { + for (u_int8_t r2 = 2; r2 <= r1; r2++) { + if (!(r1 == 2 && r2 == 2)) expectedInd++; + for (u_int8_t s1 = 0; s1 < 4; s1++) { + for (u_int8_t s2 = 0; s2 < 4; s2++) { + if (r1 == r2 && s1 == s2) continue; // skip same cards (impossible hand) + std::pair cards = {Card{r1, s1}, Card{r2, s2}}; + std::pair cards2 = {Card{r2, s2}, Card{r1, s1}}; + std::pair cards3 = {Card{r1, s2}, Card{r2, s1}}; + std::pair cards4 = {Card{r2, s1}, Card{r1, s2}}; + u_int8_t index = HandUtils::getHandIndex(cards); + EXPECT_EQ(index, HandUtils::getHandIndex(cards2)); + EXPECT_EQ(index, HandUtils::getHandIndex(cards3)); + EXPECT_EQ(index, HandUtils::getHandIndex(cards4)); + EXPECT_EQ(index, expectedInd); + std::string name = HandUtils::getHandName(index); + EXPECT_EQ(name, std::string() + rankNames[r1 - 2] + rankNames[r2 - 2]); + } + } + } + } + EXPECT_EQ(expectedInd, 90); +} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 3fe2e47..6d4ffa7 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,5 +1,5 @@ # Add the executable target -add_executable(hand_strengths ${SRC_DIR}/deck.cpp hand_strengths/main.cpp hand_strengths/hand_utils.cpp) +add_executable(hand_strengths ${SRC_DIR}/deck.cpp ${THAND_STRENGTHS_DIR}/main.cpp ${THAND_STRENGTHS_DIR}/hand_utils.cpp) # Include headers target_include_directories(hand_strengths PUBLIC ${INCLUDE_DIR}) diff --git a/tools/hand_strengths/hand_utils.h b/tools/hand_strengths/hand_utils.h index f026dea..489b8cb 100644 --- a/tools/hand_strengths/hand_utils.h +++ b/tools/hand_strengths/hand_utils.h @@ -1,7 +1,7 @@ #pragma once #include "hand_strengths.h" -// TODO: unittests + class HandUtils { public: /// @brief Constructor clears all arrays