def get_bucket_number(hole_cards, community_cards=None): """ Evaluate handstrength base on Chen's Formula if only hole cards are drawn, because it values both hand potential as well as relative value of card rank. Otherwise, evaluate handstrength using Deuces Monte-carlo Look-up table. Divide the bucket with equal probability based on the number of buckets. Using the handstrength value drawn earlier, find the right bucket. :param hole_cards: List(str) in format of 'CA' for hole cards belong to the current player :param community_cards: List(str) in format of 'S3' for community cards """ if not community_cards: points = starting_hand_evaluator(hole_cards) bucket_number = int( math.ceil((points + 1.5) / 21.5 * constants.BUCKET_NUM) - 1) else: hole_cards = list( map(lambda x: DeucesCard.new(x[1] + x[0].lower()), hole_cards)) community_cards = list( map(lambda x: DeucesCard.new(x[1] + x[0].lower()), community_cards)) evaluator = DeucesEvaluator() five_cards_ranking = evaluator.evaluate(hole_cards, community_cards) strength = 1.0 - evaluator.get_five_card_rank_percentage( five_cards_ranking) bucket_number = int(math.ceil(strength * constants.BUCKET_NUM) - 1) return 0 if bucket_number == -1 else bucket_number
def drawCardsAndDoRound(self, cardsToDraw): for player in self.playersInRound: self.playersBettedCurrentRound[player] = 0 print("\n\nPot total is", self.pot) print("\n Flop:") self.board += self.deck.draw(cardsToDraw) Card.print_pretty_cards(self.board) if (len(self.getPlayersInRoundNotInAllIn()) > 1): self.doRound()
async def evaluateHand(whichServer): global table global hands global board global isRound global firstGame global players global pot finalBoard = [] finalHands = [] handWinner = [] max = 100000 eval = Evaluator() for card in board[whichServer]: temp1 = card[1].replace('10', 'T') temp2 = card[0].lower() temp3 = Card.new(temp1[0] + temp2[0]) finalBoard.append(temp3) for p in table[whichServer]: for card in hands[whichServer][p]: temp1 = card[1].replace('10', 'T') temp2 = card[0].lower() temp3 = Card.new(temp1[0] + temp2[0]) finalHands.append(temp3) valueOfHand = eval.evaluate(finalBoard, finalHands) if valueOfHand < max: max = valueOfHand handWinner = [] handWinner.append(p) elif valueOfHand == max: handWinner.append(p) print(eval.evaluate(finalBoard, finalHands)) finalHands = [] pot[whichServer] /= len(handWinner) for winners in handWinner: players[whichServer][winners] += pot[whichServer] await bot.send_message( pokerChannel[whichServer], winners.display_name + " won the round! They won " + str(pot[whichServer]) + "$. Use command startPoker to start another one!") firstGame[whichServer] = 0 pot[whichServer] = 0 isRound[whichServer] = 0
def _get_deuces_card(self, card): card_str = "" val = card % 13 + 1 if val < 10 and val > 1: card_str = str(val) else: if val == 1: card_str = "A" if val == 10: card_str = "T" if val == 11: card_str = "J" if val == 12: card_str = "Q" if val == 13: card_str = "K" if card < 13: card_str += "h" elif card < 26: card_str += "d" elif card < 39: card_str += "s" else: card_str += "c" return Card.new(card_str)
def calculateHand(self, hole_card, round_state): """ Passes what's on table and in your hand to deuces, to calculate hand strength, if the flop hasnt been played yet, return -1 """ if (round_state["community_card"] == []): return -1 board = list( map(lambda x: Card.new(x[-1] + x[0].lower()), round_state["community_card"])) hand = list(map(lambda x: Card.new(x[-1] + x[0].lower()), hole_card)) evaluator = Evaluator() #Return as a fraction, lower is better return (evaluator.evaluate(board, hand) / 7462)
def create_cards(cards): # we want to return a list of Card objects, given the string label ret_cards = [] for card in cards: ret_cards.append(Card.new(card)) return ret_cards
def _five(self, cards): """ Performs an evalution given cards in integer form, mapping them to a rank in the range [1, 7462], with lower ranks being more powerful. Variant of Cactus Kev's 5 card evaluator, though I saved a lot of memory space using a hash table and condensing some of the calculations. """ # if flush if cards[0] & cards[1] & cards[2] & cards[3] & cards[4] & 0xF000: handOR = (cards[0] | cards[1] | cards[2] | cards[3] | cards[4]) >> 16 prime = Card.prime_product_from_rankbits(handOR) return self.table.flush_lookup[prime] # otherwise else: prime = Card.prime_product_from_hand(cards) return self.table.unsuited_lookup[prime]
def straight_and_highcards(self, straights, highcards): """ Unique five card sets. Straights and highcards. Reuses bit sequences from flush calculations. """ rank = LookupTable.MAX_FLUSH + 1 for s in straights: prime_product = Card.prime_product_from_rankbits(s) self.unsuited_lookup[prime_product] = rank rank += 1 rank = LookupTable.MAX_PAIR + 1 for h in highcards: prime_product = Card.prime_product_from_rankbits(h) self.unsuited_lookup[prime_product] = rank rank += 1
def GetFullDeck(): if Deck._FULL_DECK: return list(Deck._FULL_DECK) # create the standard 52 card deck for rank in Card.STR_RANKS: for suit, val in Card.CHAR_SUIT_TO_INT_SUIT.items(): Deck._FULL_DECK.append(Card.new(rank + suit)) return list(Deck._FULL_DECK)
def GetFullDeck(): if Deck._FULL_DECK: return list(Deck._FULL_DECK) # create the standard 52 card deck for rank in Card.STR_RANKS: for suit,val in Card.CHAR_SUIT_TO_INT_SUIT.iteritems(): Deck._FULL_DECK.append(Card.new(rank + suit)) return list(Deck._FULL_DECK)
def _two(self, cards): """ Pre-flop: 169 non-equivalent starting hands out of 1326 hands Ranks: Pairs, suited, unsuited. Plus if connectors. Ace always better Find index from self.startingHandRanks """ card1, card2 = cards[0], cards[1] rank1, rank2 = Card.get_rank_int(card1), Card.get_rank_int(card2) firstRank, secondRank = max(rank1, rank2), min(rank1, rank2) cardString = str(Card.STR_RANKS[firstRank]) + str( Card.STR_RANKS[secondRank]) if (firstRank != secondRank): #check if suited or not if (Card.get_suit_int(card1) == Card.get_suit_int(card2)): cardString += "s" else: cardString += "o" return self.startingHandRanks.index(cardString)
def evaluate_hand(board, hand): if board is None or len(board) < 3: raise ValueError("Missing board cards") if hand is None or len(hand) < 2: raise ValueError("Missing hole cards") deuces_board = [Card.new(board[0]), Card.new(board[1]), Card.new(board[2]), Card.new(board[3]), Card.new(board[4])] deuces_hand = [Card.new(hand[0]), Card.new(hand[1])] evaluator = Evaluator() score = evaluator.evaluate(deuces_board, deuces_hand) hand_rank = evaluator.get_rank_class(score) hand_rank_str = evaluator.class_to_string(hand_rank) return hand_rank_str, score
def flush_possible(full_board): """ Computes whether or not a flush is possible with the cards in the board. Args: full_board: list of int (deuces cards). Despite its name, does not actually have to be a 5-card board (can be 3, 4, or 5 cards) """ suits = [0, 0, 0, 0] suit_conversion = {'d': 0, 'c': 1, 'h': 2, 's': 3} num_conversion = {0: 'd', 1: 'c', 2: 'h', 3: 's'} for card in full_board: suits[suit_conversion[Card.int_to_str(card)[1]]] += 1 suit_amax = argmax(suits) max_suits = suits[suit_amax] if max_suits >= 3: return num_conversion[suit_amax], 5 - max_suits else: return 'n', -1
def read_lookup_table(hole_cards, lookup_table): """ Reads the preflop lookup table preflop_EHSs.txt. Args: hole_cards: list of int (deuces cards) lookup_table: read from preflop_EHSs.txt Return: tuple (float, float): EHS, EHS^2 """ sorted_hole = sorted(hole_cards) sorted_hole.reverse() card_strings = [Card.int_to_str(card) for card in sorted_hole] if card_strings[0][1] != card_strings[1][1]: suited = False else: suited = True card_strings[0] = card_strings[0][0] + 'd' if suited: card_strings[1] = card_strings[1][0] + 'd' else: card_strings[1] = card_strings[1][0] + 's' card_strings = tuple(card_strings) return lookup_table[card_strings]
def __str__(self): return Card.print_pretty_cards(self.cards)
def flushes(self): """ Straight flushes and flushes. Lookup is done on 13 bit integer (2^13 > 7462): xxxbbbbb bbbbbbbb => integer hand index """ # straight flushes in rank order straight_flushes = [ 7936, # int('0b1111100000000', 2), # royal flush 3968, # int('0b111110000000', 2), 1984, # int('0b11111000000', 2), 992, # int('0b1111100000', 2), 496, # int('0b111110000', 2), 248, # int('0b11111000', 2), 124, # int('0b1111100', 2), 62, # int('0b111110', 2), 31, # int('0b11111', 2), 4111 # int('0b1000000001111', 2) # 5 high ] # now we'll dynamically generate all the other # flushes (including straight flushes) flushes = [] gen = self.get_lexographically_next_bit_sequence(int('0b11111', 2)) # 1277 = number of high cards # 1277 + len(str_flushes) is number of hands with all cards unique rank for i in range(1277 + len(straight_flushes) - 1): # we also iterate over SFs # pull the next flush pattern from our generator f = next(gen) # if this flush matches perfectly any # straight flush, do not add it notSF = True for sf in straight_flushes: # if f XOR sf == 0, then bit pattern # is same, and we should not add if not f ^ sf: notSF = False if notSF: flushes.append(f) # we started from the lowest straight pattern, now we want to start ranking from # the most powerful hands, so we reverse flushes.reverse() # now add to the lookup map: # start with straight flushes and the rank of 1 # since theyit is the best hand in poker # rank 1 = Royal Flush! rank = 1 for sf in straight_flushes: prime_product = Card.prime_product_from_rankbits(sf) self.flush_lookup[prime_product] = rank rank += 1 # we start the counting for flushes on max full house, which # is the worst rank that a full house can have (2,2,2,3,3) rank = LookupTable.MAX_FULL_HOUSE + 1 for f in flushes: prime_product = Card.prime_product_from_rankbits(f) self.flush_lookup[prime_product] = rank rank += 1 # we can reuse these bit sequences for straights # and high cards since they are inherently related # and differ only by context self.straight_and_highcards(straight_flushes, flushes)
def to_deuces_intlist(L): return [Card.new(z) for z in pypoker_to_deuces_strlist(L)]
st = PokerState() st.playerJustMoved = self.playerJustMoved st.deck = self.deck st.board = self.board st.player1 = self.player1 st.player2 = self.player2 return st def DoMove(self, move): """ Update a state by carrying out the given move. Must update playerJustMoved. """ self.playerJustMoved = 3 - self.playerJustMoved def GetMoves(self): return ["aposta", "sai"] def GetResult(self, player1): p1 = self.evaluator.evaluate(self.player1, self.board) p2 = self.evaluator.evaluate(self.player2, self.board) if p1 < p2: return 1.0 board = [Card.new('Th'), Card.new('Kh'), Card.new('Qh'), Card.new('Jh')] Card.print_pretty_cards(board) p1 = [Card.new('8c'), Card.new('9c')] e = Evaluator() print e.evaluate(board, p1)
from deuces.card import Card from deuces.evaluator import Evaluator from deuces.deck import Deck # create a card card = Card.new('Qh') # create a board and hole cards board = [Card.new('2h'), Card.new('2s'), Card.new('Jc')] hand = [Card.new('Qs'), Card.new('Th')] # pretty print cards to console Card.print_pretty_cards(board + hand) # create an evaluator evaluator = Evaluator() # and rank your hand rank = evaluator.evaluate(board, hand) print("Rank for your hand is: %d" % rank) # or for random cards or games, create a deck print("Dealing a new hand...") deck = Deck() board = deck.draw(5) player1_hand = deck.draw(2) player2_hand = deck.draw(2) print("The board:") Card.print_pretty_cards(board)
def GenerateMultipliers(self, hands, hand_values, num_players=4, data_dir=None): """ Generates an array full of multipliers used to calculate the results of gameplay hands = Array of hands (rounds*all_hands_per_round, 3) with format: [[dealer_Cards], [player1_cards], [player2_cards], [player3_cards], ...] hand_values = Array of the associated hand ranks returned from evaluate_hands num_players = Number of players per round (excluding the player/dealer) Returns array of multipliers (num_rounds,num_players,4) specifying the following for each hand: multipliers[round,hand,0]: Play multiplier -- determines whether hand should be played and whether it wins, loses, or pushes (1 = win, -1 = loss, 0 = no play or push) multipliers[round,hand,1]: Ante multiplier -- 0 = ante pushes, 1 = ante wins, -1 = ante loses multipliers[round,hand,2]: Pair Plus multiplier -- -1 if Pair Plus loses, otherwise the bonus multiple multipliers[round,hand,3]: 6-Card Multiplier -- -1 if 6-Card bonus loses, otherwise the bonus multiple """ if data_dir is None: data_dir = self.tmp_dir all_hands = num_players + 1 num_rounds = len(hands) / all_hands multipliers = np.memmap(path.join(data_dir, 'multipliers'), dtype='int32', mode='w+', shape=(num_rounds, num_players, 4)) play_multipliers = multipliers[:, :, 0] ante_multipliers = multipliers[:, :, 1] pair_plus_multipliers = multipliers[:, :, 2] six_card_multipliers = multipliers[:, :, 3] #group hand values into sets of 1 dealer hand + related player hands #Split the dealer values into one group and player values into another splittable_values = hand_values.reshape(-1, all_hands) dealer_values = splittable_values.T[0].reshape(-1, 1) player_values = splittable_values.T[1:].T #Lower valued hands beat higher valued hands #Win_lose = -1 for player hands that lose, 1 for wins, and 0 for ties win_lose = np.memmap(path.join(data_dir, 'win_lose'), dtype='int32', mode='w+', shape=(num_rounds, num_players)) win_lose[:] = np.copysign(1, dealer_values - player_values) #Hand and card ranks needed to determine when play bet should be made for each hand #The suits are arbitrary as long as these are unsuited hands ACE_HIGH = self.evaluate_hand(Card.hand_to_binary(['Ac', '2d', '4d'])) KING_HIGH = self.evaluate_hand(Card.hand_to_binary(['Kc', '2d', '3d'])) QUEEN_HIGH = self.evaluate_hand(Card.hand_to_binary(['Qc', '2d', '3d'])) ACE = Card.get_rank_int(Card.new('Ac')) KING = Card.get_rank_int(Card.new('Kc')) QUEEN = Card.get_rank_int(Card.new('Qc')) #In each round, one dealer card is dealt face up #Here we build an array view containing the first dealer card for each round as our face up card #hands.reshape(1,-1)[0] is equivalent to hands.flatten() without copying into a new array dealer_face_up = hands.reshape(1, -1)[0][0::all_hands * 3].reshape(-1, 1) #Face up dealer cards are converted to int ranks #face_up_rank = (dealer_face_up>>8)&0xFF #Hand plays based on House Way: # Always play A high or better # Play K high if dealer face up card is K or worse # Play Q high if dealer face up card is Q or worse # Never play hands less than Q high #Play the hand = 1, don't play = 0 play_multipliers[:] = np.where( player_values <= ACE_HIGH, 1, np.where( player_values <= KING_HIGH, np.where((dealer_face_up >> 8) & 0xF < ACE, 1, 0), np.where(player_values <= QUEEN_HIGH, np.where((dealer_face_up >> 8) & 0xF < KING, 1, 0), 0))) #Pair Plus bonus loses (-1) when a player's hand is less than a pair #Otherwise it pays a multiple of the bet depending on the hand strength for pairs and better #The bet plays independently of the hand winning or losing, #but if the player does not make a play bet, the Pair Plus bonus is forfeit (-1) #Pay chart # Worse than one pair: -1 # Pair: 1x # Flush: 3x # Straight: 6x # Straight flush: 30x # Royal flush: 50x pair_plus_multipliers[:] = np.where( play_multipliers == 0, -1, np.where( player_values > self.tc_lookup.MAX_PAIR, -1, np.where( player_values > self.tc_lookup.MAX_FLUSH, 1, np.where( player_values > self.tc_lookup.MAX_STRAIGHT, 3, np.where( player_values > self.tc_lookup.MAX_TRIPS, 6, np.where( player_values > self.tc_lookup.MAX_STRAIGHT_FLUSH, 30, np.where(player_values > 1, 40, 50))))))) #Calculate Ante wins/losses #If a play bet is not made, ante loses (-1) automatically #If a play bet is made: # Hand wins: ante wins (+1) # Hand loses: ante loses (-1) if dealer has Q high or better, otherwise pushes (0) ante_multipliers[:] = np.where( play_multipliers == 0, -1, np.where(win_lose > -1, win_lose, np.where(dealer_values <= QUEEN_HIGH, -1, 0))) #Calculate Play wins/losses #We need to do this after calculating the Pair Plus bonus #so we don't confuse ties with forfeit hands #If the dealer hand is worse than Q high, the Play bet pushes (0) play_multipliers[:] = np.where(dealer_values <= QUEEN_HIGH, play_multipliers * win_lose, 0) del win_lose #Now we need to compute the 6-card bonuses #These are based on the best 5-card hand that can be made from the player's 3 card and the dealer's 3 #The first out of each group of hands is the dealer hand for that group #The rest are the player hands #we want groups like: [dcard1, dcard2, dcard3, pcard1, pcard2, pcard3] #to pass to deuces' hand evaluator #This requires some slicing arobatics to avoid building new arrays #Hands are arranged by round such as dealer_cards, player1_cards, player2_cards, etc. #Then stepped slices are taken and truncated to form slices for each combo of dealer and player cards #eg. If hands = [D01, D02, D03, Pa1, Pa2, Pa3, Pb1, Pb2, Pb3, # D11, D12, D13, Pc1, Pc2, Pc3, Pd1, Pd2, Pd3] # #Then eval_hands[0] = [[D01, D02, D03, Pa1, Pa2, Pa3], # [D11, D12, D13, Pc1, Pc2, Pc3]] # # eval_hands[1] = [[D01, D02, D03, Pb1, Pb2, Pb3], # [D11, D12, D13, Pd1, Pd2, Pd3]] eval_hands = [ hands.reshape(-1, all_hands, 3)[:, ::i + 1][:, :2].reshape(-1, 6) for i in xrange(num_players) ] #calculate 6card bonuses evaluator = Evaluator() #This is the main bottleneck in the simulator #The only real way to resolve it is to rebuild the deuces library's #hand evaluator to process arrays of hands rather than one at a time, #which is currently beyond the scope of this project for i in xrange(num_players): six_card_multipliers[:, i] = np.apply_along_axis( evaluator._six, 1, eval_hands[i]) #Map hand values to bonus payouts SC_MAX_STRAIGHT_FLUSH = evaluator.table.MAX_STRAIGHT_FLUSH SC_MAX_QUADS = evaluator.table.MAX_FOUR_OF_A_KIND SC_MAX_FULL_HOUSE = evaluator.table.MAX_FULL_HOUSE SC_MAX_FLUSH = evaluator.table.MAX_FLUSH SC_MAX_STRAIGHT = evaluator.table.MAX_STRAIGHT SC_MAX_TRIPS = evaluator.table.MAX_THREE_OF_A_KIND #6-Card Bonus Payout table #Worse than trips -1 #Trips 5 #Straight 10 #Flush 15 #Full House 25 #Quads 50 #Straight Flush 200 #Royal Flush 1000 six_card_multipliers[:] = np.where( six_card_multipliers > SC_MAX_TRIPS, -1, np.where( six_card_multipliers > SC_MAX_STRAIGHT, 5, np.where( six_card_multipliers > SC_MAX_FLUSH, 10, np.where( six_card_multipliers > SC_MAX_FULL_HOUSE, 15, np.where( six_card_multipliers > SC_MAX_QUADS, 25, np.where( six_card_multipliers > SC_MAX_STRAIGHT_FLUSH, 50, np.where(six_card_multipliers > 1, 200, 1000))))))) return multipliers
for card in hole_card + board: deck.remove_card(card) board_full = board + deck.draw(5 - len(board)) opponents_hole = [deck.draw(2) for i in range(nb_player - 1) ] #[unused_cards[2 * i:2 * i + 2] opponents_score = [ evaluator.evaluate(board_full, hole) for hole in opponents_hole ] #[HandEvaluator.eval_hand(hole, community_card) for hole in opponents_hole] my_score = evaluator.evaluate( board_full, hole_card) #HandEvaluator.eval_hand(hole_card, community_card) return 1 if my_score < min(opponents_score) else 0 evaluator = Evaluator() hole_card = [Card.new('9s'), Card.new('Ks')] board = [Card.new('8d'), Card.new('Ah'), Card.new('Kh')] nb_players = 6 nb_simulations = 10000 time_1 = time.time() hand_equity = deuces_estimate_win_rate(nb_simulations, nb_players, hole_card, board) print("The equity is: " + str(hand_equity * 100) + "%") time_2 = time.time() print("The total time taken is: " + str((time_2 - time_1) * 1000) + " [ms]") ### Getting evals/s evaluator = Evaluator() hole_card = [Card.new('9s'), Card.new('Ks')] board_full = [
def print(self): print(self.name + "(chips=" + str(self.stack) + "):") Card.print_pretty_card(self.card1) Card.print_pretty_card(self.card2)
def all_evaluation(full_board, evaluator): """ Efficient evaluation of all possible hands on a board. Args: full_board: list of int (deuces cards). Despite its name, does not actually have to be a 5-card board (can be 3, 4, or 5 cards) evaluator: deuces Evaluator object Returns: dict of deuces cards with evaluations """ all_hands = {} fp = flush_possible(full_board) ranks = ['A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2'] suits = ['s', 'c', 'd', 'h'] equivalent_lists = [] if fp[0] == 'n' or fp[1] == 2: for rank in ranks: this_rank = [] for suit in suits: card = Card.new(rank + suit) if card not in full_board: this_rank.append(card) equivalent_lists.append(this_rank) elif fp[1] <= 1: #where all cards with the same rank not of the flush suit are indistinct altered_suits = deepcopy(suits) altered_suits.remove(fp[0]) for rank in ranks: this_rank = [] for suit in altered_suits: card = Card.new(rank + suit) if card not in full_board: this_rank.append(card) equivalent_lists.append(this_rank) card_of_suit = Card.new(rank + fp[0]) if card_of_suit not in full_board: equivalent_lists.append([card_of_suit]) combos = combinations_with_replacement(equivalent_lists, 2) if fp[1] == 2: for combo in combos: for equivalent_x in combo[0]: for equivalent_y in combo[1]: if equivalent_x != equivalent_y and ( Card.int_to_str(equivalent_x)[1] != Card.int_to_str(equivalent_y)[1]): evaluation_most = evaluator.evaluate( full_board, [equivalent_x, equivalent_y]) break for equivalent_x in combo[0]: for equivalent_y in combo[1]: if equivalent_x != equivalent_y and ( Card.int_to_str(equivalent_x)[1] != Card.int_to_str(equivalent_y)[1]): all_hands[tuple(sorted([equivalent_x, equivalent_y ]))] = evaluation_most elif equivalent_x != equivalent_y and ( Card.int_to_str(equivalent_x)[1] == Card.int_to_str(equivalent_y)[1]): special_evaluation = evaluator.evaluate( full_board, [equivalent_x, equivalent_y]) all_hands[tuple(sorted([equivalent_x, equivalent_y ]))] = special_evaluation else: for combo in combos: for equivalent_x in combo[0]: for equivalent_y in combo[1]: if equivalent_x != equivalent_y: evaluation = evaluator.evaluate( full_board, [equivalent_x, equivalent_y]) break for equivalent_x in combo[0]: for equivalent_y in combo[1]: if equivalent_x != equivalent_y: all_hands[tuple(sorted([equivalent_x, equivalent_y]))] = evaluation return all_hands