You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
9.0 KiB
Python

#!/usr/bin/env python
from itertools import combinations, product
from random import sample
import matplotlib.pyplot as plt
import numpy as np
SAMPLE_SIZE = 10000
RANKS = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
LOOPED_RANKS = [RANKS[-1]] + RANKS
SUITS = ["", "", "", ""]
DECK = list(product(RANKS, SUITS))
def count_rank(table, rank):
return len(list(filter(lambda card: card[0] == rank, table)))
def count_suit(table, suit):
return len(list(filter(lambda card: card[1] == suit, table)))
def find_high_card(table):
output = {}
for rank in RANKS:
output[rank] = count_rank(table, rank) >= 1
return output
def find_one_pair(table):
output = {}
for rank in RANKS:
output[rank] = count_rank(table, rank) >= 2
return output
def find_two_pair(table):
output = {}
for (rank1, rank2) in combinations(RANKS, 2):
output[rank1 + '+' + rank2] = count_rank(table, rank1) >= 2 and count_rank(table, rank2) >= 2
return output
def find_three_of_a_kind(table):
output = {}
for rank in RANKS:
output[rank] = count_rank(table, rank) >= 3
return output
def find_straight(table):
output = {}
for i in range(4, len(LOOPED_RANKS)):
output[LOOPED_RANKS[i]] = (count_rank(table, LOOPED_RANKS[i-4]) >= 1 and
count_rank(table, LOOPED_RANKS[i-3]) >= 1 and
count_rank(table, LOOPED_RANKS[i-2]) >= 1 and
count_rank(table, LOOPED_RANKS[i-1]) >= 1 and
count_rank(table, LOOPED_RANKS[i]) >= 1)
return output
def find_flush(table):
output = {}
for suit in SUITS:
output[suit] = count_suit(table, suit) >= 5
return output
def find_full_house(table):
output = {}
for (rank1, rank2) in product(RANKS, RANKS):
output[rank1 + ' over ' + rank2] = count_rank(table, rank1) >= 3 and count_rank(table, rank2) >= 2
return output
def find_four_of_a_kind(table):
output = {}
for rank in RANKS:
output[rank] = count_rank(table, rank) >= 4
return output
def find_straight_flush(table):
output = {}
for i in range(4, len(LOOPED_RANKS)):
found = False
for s in SUITS:
if ((LOOPED_RANKS[i-4], s) in table and
(LOOPED_RANKS[i-3], s) in table and
(LOOPED_RANKS[i-2], s) in table and
(LOOPED_RANKS[i-1], s) in table and
(LOOPED_RANKS[i], s) in table):
found = True
output[LOOPED_RANKS[i]] = found
return output
def find_hands(table):
return {
"high card": find_high_card(table),
"one pair": find_one_pair(table),
"two pair": find_two_pair(table),
"three of a kind": find_three_of_a_kind(table),
"straight": find_straight(table),
"flush": find_flush(table),
"full house": find_full_house(table),
"four of a kind": find_four_of_a_kind(table),
"straight flush": find_straight_flush(table),
}
def sanity_check(table, result):
for hand_type in result["one pair"]:
if result["one pair"][hand_type] and not result["high card"][hand_type]:
print(f"Invalid result at one pair of {hand_type} but not high card")
print(table)
print(result)
for hand_type in result["two pair"]:
[rank1, rank2] = hand_type.split("+")
if result["two pair"][hand_type] and not (result["one pair"][rank1] and result["one pair"][rank2]):
print(f"Invalid result at two pair of {hand_type} but not respective one pairs")
print(table)
print(result)
for hand_type in result["three of a kind"]:
if result["three of a kind"][hand_type] and not result["one pair"][hand_type]:
print(f"Invalid result at three of a kind of {hand_type} but not one pair")
print(table)
print(result)
for hand_type in result["straight"]:
i = LOOPED_RANKS.index(hand_type)
if result["straight"][hand_type] and not (result["high card"][LOOPED_RANKS[i-4]] and
result["high card"][LOOPED_RANKS[i-3]] and
result["high card"][LOOPED_RANKS[i-2]] and
result["high card"][LOOPED_RANKS[i-1]] and
result["high card"][LOOPED_RANKS[i]]):
print(f"Invalid result at straight of {hand_type} but not high cards")
print(table)
print(result)
for hand_type in result["full house"]:
[rank1, rank2] = hand_type.split(" over ")
if result["full house"][hand_type] and not (result["three of a kind"][rank1] and result["one pair"][rank2]):
print(f"Invalid result at full house of {hand_type} but not respective three of a kind and one pair")
print(table)
print(result)
for hand_type in result["four of a kind"]:
if result["four of a kind"][hand_type] and not result["three of a kind"][hand_type]:
print(f"Invalid result at four of a kind of {hand_type} but not three of a kind")
print(table)
print(result)
for hand_type in result["straight flush"]:
if result["straight flush"][hand_type] and not result["straight"][hand_type]:
print(f"Invalid result at straight flush of {hand_type} but not straight")
print(table)
print(result)
# def find_hand_averages(result):
# output = {}
# for hand in result:
# output[hand] = 0
# for hand_type in result[hand]:
# output[hand] += result[hand][hand_type]
# output[hand] /= len(result[hand])
# return output
def find_probabilities(size):
results = []
for _ in range(SAMPLE_SIZE):
table = sample(DECK, size)
result = find_hands(table)
sanity_check(table, result)
results += [result]
# The idea was to find the probability of each hand this way:
# for each hand
# for each hand_type of this hand
# find the probability of this hand_type with occurrences / SAMPLE_SIZE
# find the probability of the hand by averaging the probability of each hand_type, that is
# sum everything and divide by the number of types (= len(results[0][hand]))
# We don't use this approach becuase finding the intermediate probabilities for each hand_type introduces
# division, which comes with rounding errors.
# To avoid this we postpone division as much as possible, working with integers as much as possible
probabilities = {}
for hand in results[0]:
occurrences = 0
for hand_type in results[0][hand]:
occurrences_per_type = 0
for i in range(SAMPLE_SIZE):
if results[i][hand][hand_type]:
occurrences_per_type += 1
# probability_per_type should be occurrences_per_type / len(results[0][hand]) but
# it's better to postpone divisions as much as possible to prevent rounding errors
occurrences += occurrences_per_type
probabilities[hand] = occurrences / (SAMPLE_SIZE * len(results[0][hand]))
return probabilities
# output = {}
# for hand in results[0]:
# output[hand] = {}
# for hand_type in results[0][hand]:
# output[hand][hand_type] = 0
# for i in range(SAMPLE_SIZE):
# if results[i][hand][hand_type]:
# output[hand][hand_type] += 1
# output[hand][hand_type] /= SAMPLE_SIZE
# return find_hand_averages(output)
groups = tuple(range(5, 52, 5))
global_results = {
"high card": (),
"flush": (),
"one pair": (),
"straight": (),
"two pair": (),
"three of a kind": (),
"full house": (),
"straight flush": (),
"four of a kind": (),
}
for size in groups:
results = find_probabilities(size)
for hand in global_results:
global_results[hand] += (results[hand],)
results = sorted(results.items(), key=lambda hand: -hand[1])
print("===========================")
print(" cards on table:", str(size).rjust(9))
print("---------------------------")
for (hand, p) in results:
str_p = f"{p:.4%}"
# str_p = f"{p}"
print(" " + hand.ljust(15) + str_p.rjust(10))
print("===========================")
print("")
print("")
x = np.arange(len(groups)) # the label locations
width = 0.08 # the width of the bars
multiplier = 0
fig, ax = plt.subplots(layout='constrained')
for attribute, measurement in global_results.items():
offset = width * multiplier
rects = ax.bar(x + offset, measurement, width, label=attribute)
# ax.bar_label(rects, padding=3)
multiplier += 1
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Probabilità')
ax.set_title('Probabilità mani poker polacco in funzione del numero di carte in gioco')
ax.set_xticks(x + width*4, groups)
ax.legend(loc='upper left', ncols=3)
ax.set_ylim(0, 1.05)
plt.show()