This code finds the probabilities of the various scores in Poker. It uses a Monte Carlo approach, in which, two million (\$2 \times 10^{6}\$) hands are simulated and evaluated, not an exhaustive search.
According to Wikipedia, my results are accurate.
I strived to avoid over-engineering, where the only tricky part is poker_value
as it uses a bit of metaprogramming, but it was necessary to avoid duplication.
I am interested in performance improvements only if they do not hurt readability and straightforwardness.
An example output is:
Poker probabilities:
('nothing', 50.1837)
('pair', 42.1968)
('two_pair', 4.77195)
('three_of_a_kind', 2.18145)
('straight', 0.3568)
('flush', 0.198)
('full_house', 0.08485)
('four_of_a_kind', 0.02505)
('straight_flush', 0.0014)
Time taken: 107.4311831
The code:
from __future__ import division
from collections import namedtuple, Counter
import doctest
import random
import time
CARDS_PER_HAND = 5
VALUES = 13
KINDS = 4
ACCURACY = 2 * 10 ** 6
Card = namedtuple("Card", ["value", "kind"])
def all_equal(lst):
"""
>>> all_equal([1,1,1,1])
True
>>> all_equal([1,2,3])
False
"""
return len(set(lst)) == 1
def couples(lst):
"""
>>> couples([1,5,7])
[[1, 5], [5, 7]]
"""
return [ [curr, lst[index + 1]]
for index, curr in enumerate(lst[:-1])]
def one_by_one_increasing(lst):
"""
>>> one_by_one_increasing([5, 6, 7, 8])
True
"""
return all(nexxt == previous + 1
for previous, nexxt in couples(lst))
def most_common(lst):
"""
>>> most_common([1,2,2,2,3])
2
"""
return Counter(lst).most_common()[0][0]
def most_common_count(lst):
"""
>>> most_common_count([4,4,4,1,1])
3
>>> most_common_count([7,3,1,7,8])
2
"""
return lst.count(most_common(lst))
def first_true(arg, funcs):
"""
>>> def false(n): return False
>>> def even(n): return n % 2 == 0
>>> first_true(14, [false, even]).__name__
'even'
"""
for f in funcs:
if f(arg):
return f
def values(cards):
"""
>>> values([ Card(12, 3), Card(5, 2), Card(8, 3)])
[12, 5, 8]
"""
return [c.value for c in cards]
def kinds(cards):
"""
>>> kinds([ Card(12, 2), Card(5, 3)])
[2, 3]
"""
return [c.kind for c in cards]
def is_straight_flush(hand):
"""
>>> is_straight_flush(Card(x, 2) for x in range(4, 9))
True
"""
return is_flush(hand) and is_straight(hand)
def is_flush(hand):
"""
>>> is_flush(Card(x, 2) for x in [3,6,1,10])
True
"""
return all_equal(kinds(hand))
def is_four_of_a_kind(hand):
"""
>>> is_four_of_a_kind([ Card(5, 2), Card(5, 3), Card(5, 1), Card(5, 4), Card(8, 3) ])
True
"""
return most_common_count(values(hand)) == 4
def is_three_of_a_kind(hand):
"""
>>> is_three_of_a_kind( [Card(12, 2)]*3 + [Card(2, 3), Card(9, 2)])
True
>>> is_three_of_a_kind( [Card(2, 1), Card(3, 2), Card(3, 1), Card(5, 1), Card(9, 4)] )
False
"""
return most_common_count(values(hand)) == 3
def is_pair(hand):
"""
>>> is_pair( [Card(2, 1), Card(3, 2), Card(3, 1), Card(5, 1), Card(9, 4)] )
True
"""
return most_common_count(values(hand)) == 2
def is_straight(hand):
"""
>>> is_straight( [Card(value, random.randint(0,4)) for value in range(0,5)] )
True
"""
return one_by_one_increasing(sorted(values(hand)))
def is_two_pair(hand):
"""
>>> is_two_pair([Card(1, 1), Card(3, 2), Card(3, 1), Card(9, 1), Card(9, 4)])
True
>>> is_two_pair( [Card(2, 1), Card(3, 2), Card(3, 1), Card(5, 1), Card(9, 4)] )
False
"""
return is_pair(hand) and is_pair([c for c in hand if c.value != most_common(values(hand))])
def is_full_house(hand):
"""
>>> is_full_house([Card(3, 1), Card(3, 2), Card(3, 1), Card(9, 1), Card(9, 4)])
True
"""
return is_three_of_a_kind(hand) and is_pair([c for c in hand if c.value != most_common(hand).value])
def is_nothing(hand):
"""
A hand is always at least nothing
>>> is_nothing(Card(random.randint(0,12), random.randint(0,3)) for _ in range(CARDS_PER_HAND))
True
"""
return True
def poker_value(hand, possible_scores=[is_straight_flush, is_four_of_a_kind, is_full_house,
is_flush, is_straight, is_three_of_a_kind, is_two_pair, is_pair, is_nothing]):
"""
>>> poker_value([ Card(5, 1), Card(7, 2), Card(9, 3), Card(10, 1), Card(10, 1) ])
'pair'
>>> poker_value([ Card(val, 3) for val in range(2, 7)])
'straight_flush'
"""
return first_true(hand, possible_scores).__name__[3:]
def poker_deck(max_value=VALUES, number_of_kinds=KINDS):
"""
>>> len(poker_deck())
52
"""
return [ Card(value, kind)
for value in range(max_value)
for kind in range(number_of_kinds)]
def poker_percentages(accuracy):
deck = poker_deck()
occurencies = Counter( poker_value(random.sample(deck, CARDS_PER_HAND))
for _ in range(accuracy))
return list(sorted(((name, round((occurencies[name] / accuracy * 100), CARDS_PER_HAND) )
for name in occurencies), key=lambda x: x[1], reverse=True))
def main():
start = time.time()
print("Poker probabilities:")
print("\n".join(map(str, poker_percentages(ACCURACY))))
print("Time taken: {}".format(time.time() - start))
if __name__ == "__main__":
doctest.testmod()
main()