├── .gitignore ├── LICENSE ├── PokerPy ├── __init__.pyi └── py.typed ├── README.md ├── pyproject.toml ├── setup.py ├── src ├── global.h └── main.cpp ├── test.py └── tests ├── __init__.py ├── test_cards.py ├── test_funcs.py └── test_hands.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.egg-info/ 3 | *.exe/ 4 | .vscode/ 5 | __pycache__ 6 | .devcontainer 7 | *.so 8 | *.out 9 | *.cpython* 10 | .pytest_cache 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PokerPy/__init__.pyi: -------------------------------------------------------------------------------- 1 | class Card: 2 | def __init__(self, value:int, suit:int) -> None: ... 3 | def __init__(self, cards_str:str) -> None: ... 4 | @property 5 | def value(self) -> str: ... 6 | @property 7 | def suit(self) -> str: ... 8 | 9 | 10 | class Hand: 11 | def __init__(self, hand_type:int, Cards:list[Card]) -> None: ... 12 | def __init__(self, hand_type:str, Cards:list[Card]) -> None: ... 13 | @property 14 | def hand_type(self) -> str: ... 15 | @property 16 | def Cards(self) -> list[Card]: ... 17 | def hand_heuristic(self) -> int: ... 18 | 19 | 20 | def get_best_hand(cards:list[Card]) -> Hand: ... 21 | def calculate_hand_frequency(cards:list[list[Card]]) -> list[dict[str,int]]: ... 22 | def nice_print_frequencies(frecs:list[dict[str,int]]) -> None: ... 23 | -------------------------------------------------------------------------------- /PokerPy/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glpcc/PokerPy/f20c5bbc03c55b6862f324327e74866f7223d5de/PokerPy/py.typed -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PokerPy 2 | 3 | A python module writen in C++ for caculating Texas Hold'em poker odds in under a second. The union between python and C++ is done with [pybind11](https://github.com/pybind/pybind11) 4 | 5 | # Installation 6 | To install this module use the following command. 7 | 8 | ```bash 9 | pip install git+https://github.com/glpcc/PokerPy 10 | ``` 11 | # Use Cases 12 | In the [test folder](tests/) are some use cases of possibilities with this module 13 | 14 | # Documentation 15 | 16 | ## Card Class 17 | ```python 18 | class Card: 19 | def __init__(self, card:str) -> None: ... 20 | @property 21 | def value(self) -> str: ... 22 | @property 23 | def color(self) -> str: ... 24 | ``` 25 | The Card class is used to represent a poker card, for the creation of a class you must pass a string with the form *KD* In which the first letter/s represent the value of the card i.e from 2 to A.Then the second letter represents the color of the card it can be passed as *D-H-C-S* or using the unicode characters *♣,♦,♥,♠*. 26 | 27 | Internally Card are stored as a struct with two int values, not accesible from the python interface. 28 | 29 | ### Warning 30 | At the time no checks for valid values of color and value are done so it is left to the user responsability. With undefined behaviour if done incorrectly. 31 | ## Hand Class 32 | ```python 33 | class Hand: 34 | def __init__(self, hand_type: str, Cards: list[Card]) -> None: ... 35 | @property 36 | def hand_type(self) -> str: ... 37 | @property 38 | def Cards(self) -> list[Card]: ... 39 | ``` 40 | This is a class to represent a poker Hand with a list of 5 cards representing the 5 chosen cards of the Hand and the Hand_type property, a string representing the type of the Hand. The value of the hand_type can be one of the following,in order of value. 41 | ```text 42 | Royal Flush 43 | Straight Flush 44 | Poker 45 | Full House 46 | Flush 47 | Straight 48 | Triples 49 | Double Pairs 50 | Pairs 51 | High Card 52 | ``` 53 | It is not recommended to create Hand Classes manually unless you want to calculate a specific Hand Heuristic. It should be mainly used as the return type of the *get_best_hand* function. 54 | ### Warning 55 | At the time no checks for valid values of hand_type or the number of hands are done so it is left to the user responsability. With undefined behaviour if done incorrectly. 56 | 57 | ## Utility Functions 58 | 59 | ### get_best_hand 60 | ```python 61 | def get_best_hand(cards: list[Card]) -> Hand: ... 62 | ``` 63 | It gets a list of 7 cards and returns the best possible Hand as a hand_type. 64 | 65 | Internally it sorts the cards and perform different checks for different types of hands on a single for loop for performance. It is internally used in the *calculate_hand_frequencies*. 66 | 67 | ### Warning 68 | At the time it isn't checked if the correct number of cards is supplied or if there is any repeated cards. Any thing outside 7 valid,non repeated cards is undefined behaviour. 69 | 70 | ### calculate_hand_frequency 71 | ```python 72 | def calculate_hand_frequency(cards: list[list[Card]]) -> list[dict[str,int]]: ... 73 | ``` 74 | Function that gets a list of players cards, and returns a list with all the hands frequencies for each player, taking into account the other players hands. 75 | It also calculates the win and draw frequency of each player, this is shown as the keys "Win" and "Draw" in each players dictionaries. 76 | 77 | It is done by iterating for all the possibe combinations of cards and calculating the best hand. 78 | 79 | ### Warning 80 | The number of cards per player supplied must be between 2 and 6.And all players should have the same cards. However different players can repeat cards, for example the fold should be shared upon all players. 81 | Anything outside these bounds is UNDEFINED BEHAVIOUR. 82 | 83 | ### nice_print_frequencies 84 | ```python 85 | def nice_print_frequencies(frecs: list[dict[str,int]]) -> None: ... 86 | ``` 87 | Function that gets a the frequencies of the *calculate_hand_frequency* function and prints them in a nice table format. 88 | 89 | ### calculate_hand_heuristic 90 | ```python 91 | def calculate_hand_heuristic(hand: Hand) -> int: ... 92 | ``` 93 | Function that gets a hand and uses bitshifting to calculate a hand value efficienly. It ensures that if a hand is better than another in texas hold'em, this function returns a larger int for the better hand. 94 | 95 | This is used internally in the *calculate_hand_frequency* function to calculate the win,draw chance of every possible combination of cards. 96 | 97 | # Limitations 98 | Supports up to 10 players, and might run slower than a second in old hardware. 99 | 100 | # Contributing 101 | 102 | Any performance increase or bugfix will be gladly welcomed. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel", "pybind11"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # Available at setup time due to pyproject.toml 3 | from pybind11 import get_cmake_dir 4 | from pybind11.setup_helpers import Pybind11Extension, build_ext,ParallelCompile 5 | from setuptools import setup 6 | 7 | __version__ = "1.0.1" 8 | 9 | 10 | ext_modules = [ 11 | Pybind11Extension("PokerPy", 12 | ["src/main.cpp"], 13 | cxx_std=20 14 | ), 15 | ] 16 | 17 | setup( 18 | name="PokerPy", 19 | version=__version__, 20 | author="Gonzalo Lope", 21 | author_email="gonzalolopecc@gmail.com", 22 | url="https://github.com/glpcc/Poker_probs", 23 | description="A fast poker probabilities calculator.", 24 | long_description="", 25 | ext_modules=ext_modules, 26 | install_requires=[ 27 | 'pybind11' 28 | ], 29 | package_data={ 30 | "PokerPy":["py.typed","__init__.pyi"], 31 | }, 32 | packages=["PokerPy"], 33 | # Currently, build_ext only provides an optional "highest supported C++ 34 | # level" feature, but in the future it may provide more features. 35 | # cmdclass={"build_ext": build_ext}, 36 | zip_safe=False, 37 | python_requires=">=3.7", 38 | ) 39 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | #ifndef _GLOBAL_H 2 | #define _GLOBAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | enum Suit {Hearts = 1, Diamonds, Clubs, Spades = 4}; 11 | enum HandType { 12 | HighCard = 1, 13 | Pairs, 14 | DoublePairs, 15 | Triples, 16 | Straight, 17 | Flush, 18 | FullHouse, 19 | Poker, 20 | StraightFlush, 21 | RoyalFlush = 10 22 | }; 23 | array suit_names = {"♥", "♦", "♣", "♠"}; 24 | map suit_values = {{"♥", Hearts}, {"♦", Diamonds}, {"♣", Clubs}, {"♠", Spades}, {"H", Hearts}, {"D", Diamonds}, {"C", Clubs}, {"S", Spades}}; 25 | array card_names = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}; 26 | map card_values = {{"2", 1}, {"3", 2}, {"4", 3}, {"5", 4}, {"6", 5}, {"7", 6}, {"8", 7}, {"9", 8}, {"10", 9}, {"J", 10}, {"Q", 11}, {"K", 12}, {"A", 13}}; 27 | array hand_names = { 28 | "High Card", 29 | "Pairs", 30 | "Double Pairs", 31 | "Triples", 32 | "Straight", 33 | "Flush", 34 | "Full House", 35 | "Poker", 36 | "Straight Flush", 37 | "Royal Flush" 38 | }; 39 | map hand_values = { 40 | {"High Card", HighCard}, 41 | {"Pairs", Pairs}, 42 | {"Double Pairs", DoublePairs}, 43 | {"Triples", Triples}, 44 | {"Straight", Straight}, 45 | {"Flush", Flush}, 46 | {"Full House", FullHouse}, 47 | {"Poker", Poker}, 48 | {"Straight Flush", StraightFlush}, 49 | {"Royal Flush", RoyalFlush} 50 | }; 51 | 52 | struct Card{ 53 | // 1 to 13 (2 to A) 54 | short value; 55 | // 0 to 3 (H to D) 56 | Suit suit; 57 | 58 | Card(): 59 | value(0), 60 | suit((Suit) 0) 61 | {} 62 | 63 | Card(short value, short suit): 64 | value(value), 65 | suit((Suit) suit) 66 | {} 67 | 68 | Card(string card) { 69 | this->value = card_values[card.substr(0,card.size()-1)]; 70 | this->suit = suit_values[card.substr(card.size()-1,1)]; 71 | } 72 | 73 | string to_string() const { 74 | return "Card: " + card_names[this->value - 1] + suit_names[this->suit - 1]; 75 | } 76 | 77 | bool operator==(const Card& rhs) const { 78 | return (this->value == rhs.value) && (this->suit == rhs.suit); 79 | } 80 | 81 | bool operator>=(const Card& rhs) const { 82 | if(this->value >= rhs.value){ 83 | return true; 84 | } else { 85 | return this->suit >= rhs.suit; 86 | } 87 | } 88 | }; 89 | 90 | struct Hand{ 91 | HandType hand_type; 92 | array Cards; 93 | 94 | Hand(): 95 | hand_type(HighCard), 96 | Cards() 97 | {} 98 | 99 | Hand(short hand_type, array cards): 100 | hand_type((HandType) hand_type), 101 | Cards(cards) 102 | {} 103 | 104 | Hand(string hand_type, array cards): 105 | hand_type(hand_values[hand_type]), 106 | Cards(cards) 107 | {} 108 | 109 | string to_string() const { 110 | string res = "Hand: " + hand_names[this->hand_type - 1] + ", Cards:"; 111 | for (Card card: this->Cards){ 112 | res += " " + card_names[card.value - 1] + suit_names[card.suit - 1]; 113 | } 114 | return res; 115 | } 116 | 117 | int hand_heuristic() const { 118 | // Uses bitshifting to ensure ranking of hands. It is shifted in pacs of 4bits allowing 16 options (13 needed) 119 | int64_t result = this->hand_type; 120 | for (auto card: this->Cards) 121 | { 122 | result = result << 4; 123 | result += card.value; 124 | } 125 | return result; 126 | } 127 | 128 | bool operator==(const Hand& rhs) const { 129 | return (this->hand_type == rhs.hand_type) && (this->Cards == rhs.Cards); 130 | } 131 | 132 | bool operator>=(const Hand& rhs) const { 133 | return this->hand_heuristic() >= rhs.hand_heuristic(); 134 | } 135 | }; 136 | 137 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "global.h" 12 | 13 | using namespace std; 14 | namespace py = pybind11; 15 | using namespace pybind11::literals; 16 | 17 | Hand get_best_hand(array cards){ 18 | // WARNING This function assumes the cards are sorted before hand 19 | // Divide the cards by suit 20 | array,4> color_cards; 21 | array num_color_cards = {0,0,0,0}; 22 | color_cards[cards[0].suit - 1][0] = cards[0]; 23 | num_color_cards[cards[0].suit - 1]++; 24 | // To get the straights 25 | array current_straight; 26 | current_straight[0] = cards[0]; 27 | int current_straight_size = 1; 28 | array straight; 29 | bool found_straight = false; 30 | // To get straight flushes 31 | array,4> straight_flushes; 32 | straight_flushes[cards[0].suit - 1][0] = cards[0]; 33 | array straight_flushes_sizes = {0,0,0,0}; 34 | straight_flushes_sizes[cards[0].suit - 1] += 1; 35 | array straight_flush; 36 | bool found_straight_flush = false; 37 | // To get rest of groupings 38 | array,3> pairs; 39 | int num_pairs = 0; 40 | array,2> triples; 41 | int num_triples = 0; 42 | array pokers; 43 | bool found_poker = false; 44 | array individuals; 45 | int num_individuals = 0; 46 | // Temporary variables; 47 | array group; 48 | int group_size = 1; 49 | group[0] = cards[0]; 50 | Card last_card = cards[0]; 51 | // Iterate through the sorted cards to extract the possible hands 52 | for(int i=1; i<7; i++) 53 | { 54 | // Search for pairs,triples,etc 55 | if (cards[i].value == last_card.value){ 56 | group[group_size] = cards[i]; 57 | group_size++; 58 | }else{ 59 | switch (group_size) 60 | { 61 | case 1: 62 | individuals[num_individuals] = group[0]; 63 | num_individuals++; 64 | break; 65 | case 2: 66 | pairs[num_pairs][0] = group[0]; 67 | pairs[num_pairs][1] = group[1]; 68 | num_pairs++; 69 | break; 70 | case 3: 71 | copy(group.begin(),group.begin()+3,triples[num_triples].begin()); 72 | num_triples++; 73 | break; 74 | case 4: 75 | pokers = group; 76 | found_poker = true; 77 | break; 78 | } 79 | group_size = 1; 80 | group[0] = cards[i]; 81 | } 82 | // Search for straights 83 | if (!found_straight){ 84 | if (last_card.value - cards[i].value == 1){ 85 | current_straight[current_straight_size] = cards[i]; 86 | current_straight_size++; 87 | }else if (cards[i].value != last_card.value){ 88 | current_straight_size = 1; 89 | current_straight[0] = cards[i]; 90 | } 91 | if (current_straight_size == 5){ 92 | straight = current_straight; 93 | //copy(current_straight,current_straight+5,straight); 94 | current_straight_size = 0; 95 | found_straight = true; 96 | } 97 | } 98 | // Search for flushes 99 | color_cards[cards[i].suit - 1][num_color_cards[cards[i].suit - 1]] = cards[i]; 100 | num_color_cards[cards[i].suit - 1]++; 101 | // Search for straight flushes 102 | if (straight_flushes_sizes[cards[i].suit - 1] != 0){ 103 | if (straight_flushes[cards[i].suit - 1][straight_flushes_sizes[cards[i].suit - 1]-1].value - cards[i].value == 1){ 104 | straight_flushes[cards[i].suit - 1][straight_flushes_sizes[cards[i].suit - 1]] = cards[i]; 105 | straight_flushes_sizes[cards[i].suit - 1]++; 106 | if (straight_flushes_sizes[cards[i].suit - 1] == 5){ 107 | straight_flush = straight_flushes[cards[i].suit - 1]; 108 | //copy(straight_flushes[cards[i].suit - 1],straight_flushes[cards[i].suit - 1]+5,straight_flush); 109 | found_straight_flush = true; 110 | straight_flushes_sizes[cards[i].suit - 1] = 0; 111 | } 112 | }else{ 113 | straight_flushes_sizes[cards[i].suit - 1] = 1; 114 | straight_flushes[cards[i].suit - 1][0] = cards[i]; 115 | } 116 | }else{ 117 | straight_flushes[cards[i].suit - 1][0] = cards[i]; 118 | straight_flushes_sizes[cards[i].suit - 1]++; 119 | } 120 | last_card = cards[i]; 121 | } 122 | // Add the last group 123 | switch (group_size) 124 | { 125 | case 1: 126 | individuals[num_individuals] = group[0]; 127 | num_individuals++; 128 | break; 129 | case 2: 130 | pairs[num_pairs][0] = group[0]; 131 | pairs[num_pairs][1] = group[1]; 132 | num_pairs++; 133 | break; 134 | case 3: 135 | copy(group.begin(),group.begin()+3,triples[num_triples].begin()); 136 | num_triples++; 137 | break; 138 | case 4: 139 | pokers = group; 140 | found_poker = true; 141 | break; 142 | } 143 | // Check for straight flushes 144 | if (!found_straight_flush){ 145 | // Iterate through the cards to see if ACES of some suit makes a straight flush 146 | for(int i=0; i<7; i++) 147 | { 148 | if (cards[i].value != 13){ 149 | break; 150 | }else if (straight_flushes_sizes[cards[i].suit - 1] == 4 && straight_flushes[cards[i].suit - 1][0].value == 4){ 151 | copy(straight_flushes[cards[i].suit - 1].begin(),straight_flushes[cards[i].suit - 1].begin()+4,straight_flush.begin()); 152 | straight_flush[4] = cards[i]; 153 | found_straight_flush = true; 154 | break; 155 | } 156 | 157 | } 158 | } 159 | // Create the result hand 160 | Hand result; 161 | if (found_straight_flush){ 162 | if (straight_flush[0].value == 13){ 163 | result.hand_type = RoyalFlush; 164 | }else{ 165 | result.hand_type = StraightFlush; 166 | } 167 | result.Cards = straight_flush; 168 | // copy(straight_flush,straight_flush+5,result.Cards); 169 | return result; 170 | } 171 | 172 | if (found_poker){ 173 | result.hand_type = Poker; 174 | copy(pokers.begin(),pokers.end(),result.Cards.begin()); 175 | for(int i=0; i<7; i++){ 176 | if (cards[i].value != pokers[0].value){ 177 | result.Cards[4] = cards[i]; 178 | break; 179 | } 180 | } 181 | return result; 182 | } 183 | 184 | if (num_triples > 0 && num_pairs > 0){ 185 | result.hand_type = FullHouse; 186 | copy(triples[0].begin(),triples[0].end(),result.Cards.begin()); 187 | result.Cards[3] = pairs[0][0]; 188 | result.Cards[4] = pairs[0][1]; 189 | return result; 190 | }else if(num_triples == 2){ 191 | result.hand_type = FullHouse; 192 | copy(triples[0].begin(),triples[0].end(),result.Cards.begin()); 193 | result.Cards[3] = triples[1][0]; 194 | result.Cards[4] = triples[1][1]; 195 | return result; 196 | } 197 | // Check flushes 198 | for (int i = 0; i < 4; i++) 199 | { 200 | if (num_color_cards[i] >= 5){ 201 | result.hand_type = Flush; 202 | copy(color_cards[i].begin(),color_cards[i].begin()+5,result.Cards.begin()); 203 | return result; 204 | } 205 | } 206 | 207 | // Check if the straight form with an ACE 208 | if (!found_straight && current_straight_size == 4 && current_straight[3].value == 1 && cards[0].value == 13){ 209 | copy(current_straight.begin(),current_straight.begin()+4,straight.begin()); 210 | found_straight = true; 211 | straight[4] = cards[0]; 212 | } 213 | 214 | if (found_straight){ 215 | result.hand_type = Straight; 216 | result.Cards = straight; 217 | return result; 218 | } 219 | 220 | if (num_triples == 1){ 221 | result.hand_type = Triples; 222 | copy(triples[0].begin(),triples[0].begin()+3,result.Cards.begin()); 223 | result.Cards[3] = individuals[0]; 224 | result.Cards[4] = individuals[1]; 225 | return result; 226 | } 227 | 228 | if (num_pairs >= 2){ 229 | result.hand_type = DoublePairs; 230 | result.Cards[0] = pairs[0][0]; 231 | result.Cards[1] = pairs[0][1]; 232 | result.Cards[2] = pairs[1][0]; 233 | result.Cards[3] = pairs[1][1]; 234 | result.Cards[4] = individuals[0]; 235 | return result; 236 | } 237 | 238 | if (num_pairs == 1){ 239 | result.hand_type = Pairs; 240 | result.Cards[0] = pairs[0][0]; 241 | result.Cards[1] = pairs[0][1]; 242 | result.Cards[2] = individuals[0]; 243 | result.Cards[3] = individuals[1]; 244 | result.Cards[4] = individuals[2]; 245 | return result; 246 | } 247 | result.hand_type = HighCard; 248 | copy(individuals.begin(),individuals.begin()+5,result.Cards.begin()); 249 | return result; 250 | } 251 | 252 | vector> calculate_hand_frequency(vector> cards){ 253 | 254 | int num_given_cards = cards[0].size(); 255 | vector> players_cards; 256 | // Sort the cards and plaace them in arrays of 7 cards 257 | for (int i = 0; i < cards.size(); i++) 258 | { 259 | array temp; 260 | sort(cards[i].begin(),cards[i].end(),[](Card &a,Card &b){return a.value > b.value;}); 261 | copy(cards[i].begin(),cards[i].end(),temp.begin()); 262 | players_cards.push_back(temp); 263 | } 264 | // Create the new hand array for passing it to the get_best_hand Function 265 | array new_hand; 266 | string possible_hand_types[10] = {"Royal Flush","Straight Flush","Poker","Full House","Flush","Straight","Triples","Double Pairs","Pairs","High Card"}; 267 | // Create the map with the hand_types and the number of hands of that type 268 | vector> players_hand_posibilities; 269 | map hand_posibilities; 270 | for (int i = 0; i < 10; i++) 271 | { 272 | hand_posibilities[possible_hand_types[i]] = 0; 273 | } 274 | hand_posibilities["Win"] = 0; 275 | hand_posibilities["Draw"] = 0; 276 | for (int l = 0; l < players_cards.size(); l++) 277 | { 278 | players_hand_posibilities.push_back(hand_posibilities); 279 | 280 | } 281 | Hand result; 282 | // Create all possible cards 283 | vector possible_cards; 284 | for (int j = 13; j > 0; j--) 285 | { 286 | for (int i = 4; i > 0; i--) 287 | { 288 | Card new_card; 289 | bool alredy_in_hand = false; 290 | for (int l = 0; l < players_cards.size(); l++) 291 | { 292 | for (int k = 0; k < num_given_cards; k++) 293 | { 294 | if (players_cards[l][k].value == j && players_cards[l][k].suit == i){ 295 | alredy_in_hand = true; 296 | break; 297 | } 298 | } 299 | } 300 | if (!alredy_in_hand){ 301 | new_card.value = j; 302 | new_card.suit = (Suit)i; 303 | possible_cards.push_back(new_card); 304 | } 305 | } 306 | 307 | } 308 | 309 | array indexes = {0,1,2,3,4}; 310 | int n = (7-num_given_cards); 311 | int N = possible_cards.size(); 312 | int num_possible_cases = 1; 313 | int intersected_cards = 0; 314 | int player_hand_euristic = 0; 315 | array drawed_players_indx = {0,0,0,0,0,0,0,0,0,0}; 316 | while (true){ 317 | int max_hand_heuristic = 0; 318 | int drawed_players = 0; 319 | for (int l = 0; l < players_cards.size(); l++) 320 | { 321 | // Sort efficiently the hand cards for efficiency 322 | intersected_cards = 0; 323 | for (int i = 0; i < 7; i++) 324 | { 325 | if (intersected_cards < num_given_cards){ 326 | if (i-intersected_cards >= n){ 327 | new_hand[i] = players_cards[l][intersected_cards]; 328 | intersected_cards++; 329 | continue; 330 | }else if (players_cards[l][intersected_cards].value >= possible_cards[indexes[i-intersected_cards]].value){ 331 | new_hand[i] = players_cards[l][intersected_cards]; 332 | intersected_cards++; 333 | continue; 334 | } 335 | } 336 | new_hand[i] = possible_cards[indexes[i-intersected_cards]]; 337 | } 338 | 339 | result = get_best_hand(new_hand); 340 | players_hand_posibilities[l][hand_names[result.hand_type - 1]]++; 341 | // Check if win or draw 342 | player_hand_euristic = result.hand_heuristic(); 343 | if (player_hand_euristic > max_hand_heuristic){ 344 | max_hand_heuristic = player_hand_euristic; 345 | drawed_players = 1; 346 | drawed_players_indx[0] = l; 347 | }else if (player_hand_euristic == max_hand_heuristic){ 348 | drawed_players_indx[drawed_players] = l; 349 | drawed_players++; 350 | } 351 | } 352 | if (drawed_players == 1){ 353 | players_hand_posibilities[drawed_players_indx[0]]["Win"]++; 354 | }else{ 355 | for (int i = 0; i < drawed_players; i++) 356 | { 357 | players_hand_posibilities[drawed_players_indx[i]]["Draw"]++; 358 | } 359 | } 360 | num_possible_cases++; 361 | if (indexes[0] == N-n){ 362 | break; 363 | } 364 | // Create a new combination of indexes 365 | // Iterate backwards through the indexes 366 | for (int i = 1; i <= n ; i++) { 367 | // Check if index can be aumented 368 | if (indexes[n-i] < N-i) { 369 | indexes[n-i]++; 370 | // Go through the following indexes to reduce them to the minimum possible value 371 | for (int j = n-i+1; j < n; j++) { 372 | indexes[j] = indexes[j-1] + 1; 373 | } 374 | break; 375 | } 376 | } 377 | } 378 | for (int l = 0; l < players_cards.size(); l++) 379 | { 380 | players_hand_posibilities[l]["Total Cases"] = num_possible_cases; 381 | 382 | } 383 | return players_hand_posibilities; 384 | } 385 | 386 | Hand get_best_hand_not_sorted(array cards){ 387 | sort(cards.begin(),cards.end(),[](Card &a,Card &b){return a.value > b.value;}); 388 | return get_best_hand(cards); 389 | } 390 | 391 | string round_float(float a,int num_decimals){ 392 | int temp = round(a*pow(10,num_decimals)); 393 | string total_number = to_string((float) temp/(pow(10,num_decimals))); 394 | return total_number.substr(0,total_number.find(".")+2); 395 | } 396 | 397 | void nice_print_frequencies(vector> frecs){ 398 | // Print win/draw probabilities 399 | for (int i = 0; i < frecs.size(); i++){ 400 | float win_chance = ((float) frecs[i]["Win"]/(float) frecs[i]["Total Cases"])*100; 401 | float draw_chance = ((float) frecs[i]["Draw"]/(float) frecs[i]["Total Cases"])*100; 402 | py::print("Player: ",i,", Win: ",round_float(win_chance,2),"%, Draw: ",round_float(draw_chance,2),"%","sep"_a=""); 403 | } 404 | py::print("\nHAND POSSIBILITIES\n"); 405 | // Print Table Headers 406 | py::print("\t\t","end"_a=""); 407 | for (int i = 0; i < frecs.size(); i++){ 408 | py::print("Player",i,"\t","end"_a="","sep"_a=""); 409 | } 410 | py::print(""); 411 | // Print Each hand posibilities 412 | for (int i = 0; i < 10; i++){ 413 | py::print(hand_names[i],"end"_a=""); 414 | if (hand_names[i].size() > 7){ 415 | py::print("\t","end"_a=""); 416 | }else{ 417 | py::print("\t\t","end"_a=""); 418 | } 419 | for (int j = 0; j < frecs.size(); j++){ 420 | float hand_pos = ((float) frecs[j][hand_names[i]]/(float) frecs[j]["Total Cases"])*100;; 421 | py::print(round_float(hand_pos,2),"%\t","end"_a="","sep"_a=""); 422 | } 423 | py::print(""); 424 | } 425 | } 426 | 427 | PYBIND11_MODULE(PokerPy, m) { 428 | m.doc() = "pybind11 plugin for calculating poker probabilities."; // optional module docstring 429 | py::class_(m, "Card") 430 | .def(py::init()) 431 | .def(py::init()) 432 | .def_property("value", [](Card& a){return card_names[a.value - 1];}, [](Card& a, string v){return a.value = card_values[v];}) 433 | .def_property("suit", [](Card& a){return suit_names[a.suit - 1];}, [](Card& a, string s){return a.suit = suit_values[s];}) 434 | .def("__repr__", &Card::to_string) 435 | .def("__eq__", &Card::operator==) 436 | .def("__ge__", &Card::operator>=); 437 | py::class_(m, "Hand") 438 | .def(py::init>()) 439 | .def(py::init>()) 440 | .def_property("hand_type", [](Hand& a){return hand_names[a.hand_type - 1];}, [](Hand& a, string ht){return a.hand_type = hand_values[ht];}) 441 | .def_readwrite("Cards", &Hand::Cards) 442 | .def("hand_heuristic", &Hand::hand_heuristic) 443 | .def("__repr__", &Hand::to_string) 444 | .def("__eq__", &Hand::operator==) 445 | .def("__ge__", &Hand::operator>=); 446 | m.def("get_best_hand", &get_best_hand_not_sorted, "A function that gets the best hands given 7 cards"); 447 | m.def("calculate_hand_frequency", &calculate_hand_frequency, "A function that gets the frequencies of the possible hands given any number of cards"); 448 | m.def("nice_print_frequencies", &nice_print_frequencies, "A function that gets the frequencies of the possible hands and prints them in nice format"); 449 | } 450 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from PokerPy import Card 2 | 3 | print(Card.from_string('KD')) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glpcc/PokerPy/f20c5bbc03c55b6862f324327e74866f7223d5de/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_cards.py: -------------------------------------------------------------------------------- 1 | from PokerPy import Card 2 | 3 | def test_card_init(): 4 | card = Card(12, 2) 5 | assert card.value == 'K' 6 | assert card.suit == '♦' 7 | 8 | def test_card_from_string(): 9 | card = Card('AD') 10 | assert card.value == 'A' 11 | assert card.suit == '♦' 12 | 13 | def test_card_repr(): 14 | card = Card(12, 2) 15 | assert card.__repr__() == "Card: K♦" 16 | 17 | def test_card_eq(): 18 | assert Card(12, 2) == Card(12, 2) 19 | assert Card(12, 2) != Card(13, 2) 20 | assert Card(12, 2) != Card(12, 1) 21 | assert Card(12, 2) != Card(13, 1) 22 | 23 | def test_card_ge(): 24 | assert Card(12, 2) >= Card(11, 2) 25 | assert Card(12, 2) >= Card(12, 1) -------------------------------------------------------------------------------- /tests/test_funcs.py: -------------------------------------------------------------------------------- 1 | from PokerPy import Card, Hand, calculate_hand_frequency, get_best_hand 2 | 3 | def test_calculate_frec(): 4 | test_cards = [[Card(12, 2),Card(8, 1)],[Card(12, 3),Card(13, 3)]] 5 | frecs = calculate_hand_frequency(test_cards) 6 | test_frecs = [ 7 | {'Double Pairs': 355878, 'Draw': 20234, 'Flush': 38643, 'Full House': 28846, 'High Card': 358523, 'Pairs': 784240, 'Poker': 1430, 'Royal Flush': 46, 'Straight': 77914, 'Straight Flush': 284, 'Total Cases': 1712305, 'Triples': 66500, 'Win': 380882}, 8 | {'Double Pairs': 347144, 'Draw': 20234, 'Flush': 124371, 'Full House': 28846, 'High Card': 337972, 'Pairs': 747226, 'Poker': 1430, 'Royal Flush': 992, 'Straight': 59303, 'Straight Flush': 70, 'Total Cases': 1712305, 'Triples': 64950, 'Win': 1311188} 9 | ] 10 | assert frecs == test_frecs 11 | 12 | def test_best_hand(): 13 | test_cards = [Card(12, 2),Card(9, 1),Card(12, 3),Card(13, 3),Card(3, 3),Card(2, 2),Card(1, 2)] 14 | best_hand = get_best_hand(test_cards) 15 | test_hand = Hand(2, [Card(12, 2),Card(12, 3),Card(13, 3),Card(9, 1),Card(3, 3)]) 16 | assert test_hand.hand_type == best_hand.hand_type 17 | assert test_hand.Cards == best_hand.Cards 18 | -------------------------------------------------------------------------------- /tests/test_hands.py: -------------------------------------------------------------------------------- 1 | from PokerPy import Card, Hand 2 | 3 | def test_hand_init(): 4 | all_cards = [Card(12, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)] 5 | hand = Hand(8, all_cards) 6 | assert hand.hand_type == "Poker" 7 | assert hand.Cards == all_cards 8 | 9 | def test_hand_init_from_string(): 10 | all_cards = [Card('KH'), Card('KD'), Card('KC'), Card('KS'), Card('QD')] 11 | hand = Hand("Poker", all_cards) 12 | assert hand.hand_type == "Poker" 13 | assert hand.Cards == all_cards 14 | 15 | def test_hand_repr(): 16 | hand = Hand(8, [Card(12, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)]) 17 | assert hand.__repr__() == "Hand: Poker, Cards: K♥ K♦ K♣ K♠ Q♦" 18 | 19 | def test_hand_eq(): 20 | poker_cards = [Card(12, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)] 21 | fh_cards = [Card(11, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)] 22 | assert Hand(8, poker_cards) == Hand(8, poker_cards) 23 | assert Hand(7, poker_cards) != Hand(8, poker_cards) 24 | assert Hand(8, poker_cards) != Hand(7, fh_cards) 25 | 26 | def test_hand_ge(): 27 | rf_cards = [Card(9, 1),Card(10, 1),Card(11, 1),Card(12, 1),Card(13, 1)] 28 | poker_cards = [Card(12, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)] 29 | fh_cards = [Card(11, 1), Card(12, 2), Card(12, 3), Card(12, 4), Card(11, 2)] 30 | assert Hand(8, poker_cards) >= Hand(8, poker_cards) 31 | assert Hand(7, poker_cards) >= Hand(7, fh_cards) 32 | assert Hand(10, rf_cards) >= Hand(8, poker_cards) 33 | 34 | def test_calculate_hand_heuristic(): 35 | test_hand = Hand(2, [Card(8, 1),Card(8, 3),Card(13, 3),Card(9, 1),Card(3, 3)]) 36 | test_heuristic = 2657683 37 | assert test_heuristic == test_hand.hand_heuristic() 38 | 39 | test_hand = Hand(10, [Card(9, 1),Card(10, 1),Card(11, 1),Card(12, 1),Card(13, 1)]) 40 | test_heuristic = 11119565 41 | assert test_heuristic == test_hand.hand_heuristic() --------------------------------------------------------------------------------