def estimate_proba(hand, board, n_player, n_simul=1000): hand = Card.str_to_int(hand) board = Card.str_to_int(board) evaluator = Evaluator() to_draw = 5 - len(board) deck = set_deck(hand, board) winnings = [] for _ in range(n_simul): deck2 = deepcopy(deck) shuffle(deck2.cards) if to_draw == 1: board2 = board + [deck2.draw(to_draw)] else: board2 = board + deck2.draw(to_draw) if n_player > 2: other_hands = list( zip(deck2.draw(n_player - 1), deck2.draw(n_player - 1))) score_others = min([ evaluator.evaluate(list(hand2), board2) for hand2 in other_hands ]) elif n_player == 2: other_hand = deck2.draw(2) score_others = evaluator.evaluate(other_hand, board2) score_player = evaluator.evaluate(hand, board2) winnings += [score_player < score_others] return mean(winnings)
def __update_community(self): # card increment based on which round we are in if self.turn_nb == 0: pass elif self.turn_nb == 1: self.community_cards += Card.int_to_str(self.deck.draw(3)) elif self.turn_nb == 2: self.community_cards += [Card.int_to_str(self.deck.draw(1))] elif self.turn_nb == 3: self.community_cards += [Card.int_to_str(self.deck.draw(1))] elif self.turn_nb > 3: return
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 __rank_players(self): # we first compute the strength of each player's hand # warning: the evaluator gives value 0 to the best possible hand evaluator = Evaluator() hand_strength = np.array([ evaluator.evaluate(Card.str_to_int(player.hand), Card.str_to_int(self.community_cards)) if player.round_status != 'out' else np.inf for player in self.players ]) # we then rank them, making sure equal players have equal ranks player_ranking = np.zeros(self.n_players) for rank, hand_value in enumerate(np.unique(np.sort(hand_strength))): player_ranking[hand_strength == hand_value] = rank return player_ranking
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 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.str_to_int(rank + suit)) return list(Deck._FULL_DECK)
def __next_round(self): # reinitializing/updating round data self.players_info = [ player.get_player_data() for player in self.players ] self.round_logger = [] self.dealer += 1 self.turn_nb = 0 self.dealer %= self.n_players # distributing cards to players still in self.deck = Deck() self.community_cards = [] print('Starting round %d with players %s' % (self.round_nb, str([ i for i, player in enumerate(self.players) if player.game_status == 'in' ]))) for player in self.players: if player.game_status == 'in': player.new_round(hand=Card.int_to_str(self.deck.draw(2)), blind=self.blind) # running the 4 betting turns for _ in range(4): self.__next_turn() self.display() # attributing the gains pots = self.__create_pots() ranking = self.__rank_players() self.__distribute_pots(pots, ranking) for player in self.players: if player.stack <= 0 and player.game_status != 'out': player.game_status = 'out' player.round_status = 'out' # logging self.game_logger += [self.__get_round_info()] self.game_logger[-1]['round_history'] = self.round_logger self.game_logger[-1]["winner"] = np.where(ranking == 0)[0].tolist() self.display()
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 __str__(self): return Card.print_pretty_cards(self.cards)