diff --git a/.gitignore b/.gitignore index 0d20b64..03c2ce7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.pyc +.idea +.venv +build/ +dist/ +*.egg-info/ \ No newline at end of file diff --git a/holdem_calc/__init__.py b/holdem_calc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/holdem_argparser.py b/holdem_calc/holdem_argparser.py similarity index 75% rename from holdem_argparser.py rename to holdem_calc/holdem_argparser.py index c708e30..057a8f2 100644 --- a/holdem_argparser.py +++ b/holdem_calc/holdem_argparser.py @@ -1,6 +1,6 @@ import argparse import re -import holdem_functions +from holdem_calc import holdem_functions # Wrapper class which holds the arguments for library calls @@ -13,6 +13,7 @@ def __init__(self, board, exact, num, input_file, hole_cards): self.input = input_file self.exact = exact + # Parses arguments passed to holdem_calc as a library call def parse_lib_args(args): error_check_arguments(args) @@ -22,25 +23,26 @@ def parse_lib_args(args): hole_cards, board = parse_cards(args.cards, args.board) return hole_cards, args.n, args.exact, board, args.input + # Parses command line arguments to holdem_calc def parse_args(): # Define possible command line arguments parser = argparse.ArgumentParser( description="Find the odds that a Texas Hold'em hand will win. Note " - "that cards must be given in the following format: As, Jc, Td, 3h.") + "that cards must be given in the following format: As, Jc, Td, 3h.") parser.add_argument("cards", nargs="*", type=str, metavar="hole card", help="Hole cards you want to find the odds for.") parser.add_argument("-b", "--board", nargs="*", type=str, metavar="card", help="Add board cards") parser.add_argument("-e", "--exact", action="store_true", help="Find exact odds by enumerating every possible " - "board") + "board") parser.add_argument("-n", type=int, default=100000, help="Run N Monte Carlo simulations") parser.add_argument("-i", "--input", type=str, help="Read hole cards and boards from an input file. " - "Commandline arguments for hole cards and board will " - "be ignored") + "Commandline arguments for hole cards and board will " + "be ignored") # Parse command line arguments and check for errors args = parser.parse_args() error_check_arguments(args) @@ -50,16 +52,17 @@ def parse_args(): hole_cards, board = parse_cards(args.cards, args.board) return hole_cards, args.n, args.exact, board, args.input + # Parses a line taken from the input file and returns the hole cards and board def parse_file_args(line): if line is None or len(line) == 0: - print line - print "Invalid format" + print(line) + print("Invalid format") exit() values = line.split("|") if len(values) > 2 or len(values) < 1: - print line - print "Invalid format" + print(line) + print("Invalid format") exit() hole_cards = values[0].split() all_cards = list(hole_cards) @@ -70,6 +73,7 @@ def parse_file_args(line): error_check_cards(all_cards) return parse_cards(hole_cards, board) + # Parses hole cards and board def parse_cards(cards, board): hole_cards = create_hole_cards(cards) @@ -77,32 +81,12 @@ def parse_cards(cards, board): board = parse_board(board) return hole_cards, board -# Error check the command line arguments -def error_check_arguments(args): - # Check that the number of Monte Carlo simulations is a positive number - if args.n <= 0: - print "Number of Monte Carlo simulations must be positive." - exit() - # Check that we can open the specified input file - if args.input: - file_name = args.input - try: - input_file = open(file_name, 'r') - input_file.close() - except IOError: - print "Error opening file " + file_name - exit() - # Check to make sure all cards are of a valid format - all_cards = list(args.cards) - if args.board: - all_cards.extend(args.board) - error_check_cards(all_cards) # Error check the command line arguments def error_check_arguments(args): # Check that the number of Monte Carlo simulations is a positive number if args.n <= 0: - print "Number of Monte Carlo simulations must be positive." + print("Number of Monte Carlo simulations must be positive.") exit() # Check that we can open the specified input file if args.input: @@ -111,7 +95,7 @@ def error_check_arguments(args): input_file = open(file_name, 'r') input_file.close() except IOError: - print "Error opening file " + file_name + print("Error opening file " + file_name) exit() # Check to make sure all cards are of a valid format all_cards = list(args.cards) @@ -119,24 +103,26 @@ def error_check_arguments(args): all_cards.extend(args.board) error_check_cards(all_cards) + # Checking that the hole cards + board are formatted properly and unique def error_check_cards(all_cards): card_re = re.compile('[AKQJT98765432][scdh]') for card in all_cards: if card != "?" and not card_re.match(card): - print "Invalid card given." + print("Invalid card given.") exit() else: if all_cards.count(card) != 1 and card != "?": - print "The cards given must be unique." + print("The cards given must be unique.") exit() + # Returns tuple of two-tuple hole_cards: e.g. ((As, Ks), (Ad, Kd), (Jh, Th)) def create_hole_cards(raw_hole_cards): # Checking that there are an even number of hole cards if (raw_hole_cards is None or len(raw_hole_cards) < 2 or len(raw_hole_cards) % 2): - print "You must provide a non-zero even number of hole cards" + print("You must provide a non-zero even number of hole cards") exit() # Create two-tuples out of hole cards hole_cards, current_hole_cards = [], [] @@ -150,24 +136,26 @@ def create_hole_cards(raw_hole_cards): if None in current_hole_cards: if (current_hole_cards[0] is not None or current_hole_cards[1] is not None): - print "Unknown hole cards must come in pairs" + print("Unknown hole cards must come in pairs") exit() hole_cards.append((current_hole_cards[0], current_hole_cards[1])) current_hole_cards = [] if hole_cards.count((None, None)) > 1: - print "Can only have one set of unknown hole cards" + print("Can only have one set of unknown hole cards") return tuple(hole_cards) + # Returns list of board cards: e.g. [As Ks Ad Kd] def parse_board(board): if len(board) > 5 or len(board) < 3: - print "Board must have a length of 3, 4, or 5." + print("Board must have a length of 3, 4, or 5.") exit() if "?" in board: - print "Board cannot have unknown cards" + print("Board cannot have unknown cards") exit() return create_cards(board) + # Instantiates new cards from the arguments and returns them in a tuple def create_cards(card_strings): return [holdem_functions.Card(arg) for arg in card_strings] diff --git a/holdem_calc.py b/holdem_calc/holdem_calc.py similarity index 94% rename from holdem_calc.py rename to holdem_calc/holdem_calc.py index 1dd8b0e..6b15d4d 100644 --- a/holdem_calc.py +++ b/holdem_calc/holdem_calc.py @@ -1,17 +1,18 @@ import time -import holdem_functions -import holdem_argparser +from holdem_calc import holdem_functions, holdem_argparser def main(): hole_cards, num, exact, board, file_name = holdem_argparser.parse_args() run(hole_cards, num, exact, board, file_name, True) + def calculate(board, exact, num, input_file, hole_cards, verbose): args = holdem_argparser.LibArgs(board, exact, num, input_file, hole_cards) hole_cards, n, e, board, filename = holdem_argparser.parse_lib_args(args) return run(hole_cards, n, e, board, filename, verbose) + def run(hole_cards, num, exact, board, file_name, verbose): if file_name: input_file = open(file_name, 'r') @@ -21,12 +22,13 @@ def run(hole_cards, num, exact, board, file_name, verbose): hole_cards, board = holdem_argparser.parse_file_args(line) deck = holdem_functions.generate_deck(hole_cards, board) run_simulation(hole_cards, num, exact, board, deck, verbose) - print "-----------------------------------" + print("-----------------------------------") input_file.close() else: deck = holdem_functions.generate_deck(hole_cards, board) return run_simulation(hole_cards, num, exact, board, deck, verbose) + def run_simulation(hole_cards, num, exact, given_board, deck, verbose): num_players = len(hole_cards) # Create results data structures which track results of comparisons @@ -36,7 +38,7 @@ def run_simulation(hole_cards, num, exact, given_board, deck, verbose): # 3) result_list: list of the best possible poker hand for each pair of # hole cards for a given board result_histograms, winner_list = [], [0] * (num_players + 1) - for _ in xrange(num_players): + for _ in range(num_players): result_histograms.append([0] * len(holdem_functions.hand_rankings)) # Choose whether we're running a Monte Carlo or exhaustive simulation board_length = 0 if given_board is None else len(given_board) @@ -67,7 +69,8 @@ def run_simulation(hole_cards, num, exact, given_board, deck, verbose): result_histograms) return holdem_functions.find_winning_percentage(winner_list) + if __name__ == '__main__': start = time.time() main() - print "\nTime elapsed(seconds): ", time.time() - start + print("\nTime elapsed(seconds): ", time.time() - start) diff --git a/holdem_functions.py b/holdem_calc/holdem_functions.py similarity index 96% rename from holdem_functions.py rename to holdem_calc/holdem_functions.py index 31d42da..8ed9928 100644 --- a/holdem_functions.py +++ b/holdem_calc/holdem_functions.py @@ -6,9 +6,10 @@ "Straight", "Flush", "Full House", "Four of a Kind", "Straight Flush", "Royal Flush") suit_value_dict = {"T": 10, "J": 11, "Q": 12, "K": 13, "A": 14} -for num in xrange(2, 10): +for num in range(2, 10): suit_value_dict[str(num)] = num + class Card: # Takes in strings of the format: "As", "Tc", "6d" def __init__(self, card_string): @@ -29,6 +30,7 @@ def __eq__(self, other): return False return self.value == other.value and self.suit == other.suit + # Returns deck of cards with all hole cards and board cards removed def generate_deck(hole_cards, board): deck = [] @@ -46,24 +48,28 @@ def generate_deck(hole_cards, board): deck.remove(taken_card) return tuple(deck) + # Generate all possible hole card combinations def generate_hole_cards(deck): import itertools return itertools.combinations(deck, 2) + # Generate num_iterations random boards def generate_random_boards(deck, num_iterations, board_length): import random import time random.seed(time.time()) - for _ in xrange(num_iterations): + for _ in range(num_iterations): yield random.sample(deck, 5 - board_length) + # Generate all possible boards def generate_exhaustive_boards(deck, num_iterations, board_length): import itertools return itertools.combinations(deck, 5 - board_length) + # Returns a board of cards all with suit = flush_index def generate_suit_board(flat_board, flush_index): histogram = [card.value for card in flat_board @@ -71,6 +77,7 @@ def generate_suit_board(flat_board, flush_index): histogram.sort(reverse=True) return histogram + # Returns a list of two tuples of the form: (value of card, frequency of card) def preprocess(histogram): return [(14 - index, frequency) for index, frequency in @@ -89,6 +96,7 @@ def preprocess_board(flat_board): suit_histogram[card.suit_index] += 1 return suit_histogram, histogram, max(suit_histogram) + # Returns tuple: (Is there a straight flush?, high card) def detect_straight_flush(suit_board): contiguous_length, fail_index = 1, len(suit_board) - 5 @@ -109,12 +117,14 @@ def detect_straight_flush(suit_board): contiguous_length = 1 return False, + # Returns the highest kicker available def detect_highest_quad_kicker(histogram_board): for elem in histogram_board: if elem[1] < 4: return elem[0] + # Returns tuple: (Is there a straight?, high card) def detect_straight(histogram_board): contiguous_length, fail_index = 1, len(histogram_board) - 5 @@ -135,6 +145,7 @@ def detect_straight(histogram_board): contiguous_length = 1 return False, + # Returns tuple of the two highest kickers that result from the three of a kind def detect_three_of_a_kind_kickers(histogram_board): kicker1 = -1 @@ -145,12 +156,14 @@ def detect_three_of_a_kind_kickers(histogram_board): else: return kicker1, elem[0] + # Returns the highest kicker available def detect_highest_kicker(histogram_board): for elem in histogram_board: if elem[1] == 1: return elem[0] + # Returns tuple: (kicker1, kicker2, kicker3) def detect_pair_kickers(histogram_board): kicker1, kicker2 = -1, -1 @@ -163,11 +176,13 @@ def detect_pair_kickers(histogram_board): else: return kicker1, kicker2, elem[0] + # Returns a list of the five highest cards in the given board # Note: Requires a sorted board to be given as an argument def get_high_cards(histogram_board): return histogram_board[:5] + # Return Values: # Royal Flush: (9,) # Straight Flush: (8, high card) @@ -238,6 +253,7 @@ def detect_hand(hole_cards, given_board, suit_histogram, # Check for high cards return 0, get_high_cards(histogram_board) + # Returns the index of the player with the winning hand def compare_hands(result_list): best_hand = max(result_list) @@ -247,22 +263,23 @@ def compare_hands(result_list): return 0 return winning_player_index + # Print results def print_results(hole_cards, winner_list, result_histograms): float_iterations = float(sum(winner_list)) - print "Winning Percentages:" + print("Winning Percentages:") for index, hole_card in enumerate(hole_cards): winning_percentage = float(winner_list[index + 1]) / float_iterations if hole_card == (None, None): - print "(?, ?) : ", winning_percentage + print("(?, ?) : ", winning_percentage) else: - print hole_card, ": ", winning_percentage - print "Ties: ", float(winner_list[0]) / float_iterations, "\n" + print(hole_card, ": ", winning_percentage) + print("Ties: ", float(winner_list[0]) / float_iterations, "\n") for player_index, histogram in enumerate(result_histograms): - print "Player" + str(player_index + 1) + " Histogram: " + print("Player" + str(player_index + 1) + " Histogram: ") for index, elem in enumerate(histogram): - print hand_rankings[index], ": ", float(elem) / float_iterations - print + print(hand_rankings[index], ": ", float(elem) / float_iterations) + # Returns the winning percentages def find_winning_percentage(winner_list): @@ -273,6 +290,7 @@ def find_winning_percentage(winner_list): percentages.append(winning_percentage) return percentages + # Populate provided data structures with results from simulation def find_winner(generate_boards, deck, hole_cards, num, board_length, given_board, winner_list, result_histograms): diff --git a/parallel_holdem_calc.py b/holdem_calc/parallel_holdem_calc.py similarity index 96% rename from parallel_holdem_calc.py rename to holdem_calc/parallel_holdem_calc.py index c75eede..a57f97d 100644 --- a/parallel_holdem_calc.py +++ b/holdem_calc/parallel_holdem_calc.py @@ -1,18 +1,19 @@ import multiprocessing import time -import holdem_argparser -import holdem_functions +from holdem_calc import holdem_functions, holdem_argparser def main(): hole_cards, num, exact, board, file_name = holdem_argparser.parse_args() run(hole_cards, num, exact, board, file_name, True) + def calculate(board, exact, num, input_file, hole_cards, verbose): args = holdem_argparser.LibArgs(board, exact, num, input_file, hole_cards) hole_cards, n, e, board, filename = holdem_argparser.parse_lib_args(args) return run(hole_cards, n, e, board, filename, verbose) + def run(hole_cards, num, exact, board, file_name, verbose): if file_name: input_file = open(file_name, 'r') @@ -22,12 +23,13 @@ def run(hole_cards, num, exact, board, file_name, verbose): hole_cards, board = holdem_argparser.parse_file_args(line) deck = holdem_functions.generate_deck(hole_cards, board) run_simulation(hole_cards, num, exact, board, deck, verbose) - print "-----------------------------------" + print("-----------------------------------") input_file.close() else: deck = holdem_functions.generate_deck(hole_cards, board) return run_simulation(hole_cards, num, exact, board, deck, verbose) + def run_simulation(hole_cards, num, exact, given_board, deck, verbose): num_players = len(hole_cards) # Choose whether we're running a Monte Carlo or exhaustive simulation @@ -63,7 +65,7 @@ def run_simulation(hole_cards, num, exact, given_board, deck, verbose): given_board, winner_list, result_histograms) # Go through each parallel data structure and aggregate results combined_winner_list, combined_histograms = [0] * (num_players + 1), [] - for _ in xrange(num_players): + for _ in range(num_players): combined_histograms.append([0] * len(holdem_functions.hand_rankings)) for index, element in enumerate(winner_list): combined_winner_list[index % (num_players + 1)] += element @@ -75,6 +77,7 @@ def run_simulation(hole_cards, num, exact, given_board, deck, verbose): combined_histograms) return holdem_functions.find_winning_percentage(combined_winner_list) + def unknown_simulation_init(hole_cards_list, unknown_index, deck_list, generate_boards, num, board_length, given_board, combined_winner_list, combined_result_histograms): @@ -88,6 +91,7 @@ def unknown_simulation_init(hole_cards_list, unknown_index, deck_list, unknown_simulation.combined_winner_list = combined_winner_list unknown_simulation.combined_result_histograms = combined_result_histograms + def unknown_simulation(new_hole_cards): # Extract parameters hole_cards_list = unknown_simulation.hole_cards_list @@ -102,7 +106,7 @@ def unknown_simulation(new_hole_cards): # Set simulation variables num_players = len(hole_cards_list) result_histograms, winner_list = [], [0] * (num_players + 1) - for _ in xrange(num_players): + for _ in range(num_players): result_histograms.append([0] * len(holdem_functions.hand_rankings)) hole_cards_list[unknown_index] = new_hole_cards deck.remove(new_hole_cards[0]) @@ -122,6 +126,7 @@ def unknown_simulation(new_hole_cards): (proc_id * num_players + histogram_index) + index] += result + def find_winner(generate_boards, deck, hole_cards, num, board_length, given_board, winner_list, result_histograms): num_processes = multiprocessing.cpu_count() @@ -132,6 +137,7 @@ def find_winner(generate_boards, deck, hole_cards, num, board_length, result_histograms)) pool.map(simulation, generate_boards(deck, num, board_length)) + # Initialize shared variables for simulation def simulation_init(given_board, hole_cards, winner_list, result_histograms): simulation.given_board = given_board @@ -139,6 +145,7 @@ def simulation_init(given_board, hole_cards, winner_list, result_histograms): simulation.winner_list = winner_list simulation.result_histograms = result_histograms + # Separated function for each thread to execute while running def simulation(remaining_board): # Extract variables shared through inheritance @@ -158,7 +165,7 @@ def simulation(remaining_board): proc_id = int(proc_name.split("-")[-1]) % multiprocessing.cpu_count() # Create results data structure which tracks results of comparisons result_list = [] - for _ in xrange(num_players): + for _ in range(num_players): result_list.append([]) # Find the best possible poker hand given the created board and the # hole cards and save them in the results data structures @@ -176,7 +183,8 @@ def simulation(remaining_board): result_histograms[len(holdem_functions.hand_rankings) * (proc_id * num_players + index) + result[0]] += 1 + if __name__ == '__main__': start = time.time() main() - print "\nTime elapsed(seconds): ", time.time() - start + print("\nTime elapsed(seconds): ", time.time() - start) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..86b275f --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name='holdem_calc', + version='1.0.0', + packages=setuptools.find_packages(), + url='https://github.com/RoelandMatthijssens/holdem_calc', + license='MIT', + author='Enermis', + author_email='roeland.matthijssens@gmail.com', + description='''Holdem Calculator library''', + long_description=long_description, + long_description_content_type="text/markdown", + python_requires='>=3.6', +)