From 3f1985f6e2fd3294cd8a88430ef44546dd3fdc17 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 03:54:43 -0500 Subject: [PATCH 01/28] python3 fixes for perf_deuces, move to top level and rename to perf, remove other performance modules --- performance/perf_deuces.py => perf.py | 18 ++++----- performance/perf_handeval.py | 43 -------------------- performance/perf_specialk.py | 56 --------------------------- 3 files changed, 9 insertions(+), 108 deletions(-) rename performance/perf_deuces.py => perf.py (67%) delete mode 100644 performance/perf_handeval.py delete mode 100644 performance/perf_specialk.py diff --git a/performance/perf_deuces.py b/perf.py similarity index 67% rename from performance/perf_deuces.py rename to perf.py index f7a0059..2b3d86c 100644 --- a/performance/perf_deuces.py +++ b/perf.py @@ -27,9 +27,9 @@ def setup(n, m): cumtime += (time.time() - start) avg = float(cumtime / n) -print "7 card evaluation:" -print "[*] Deuces: Average time per evaluation: %f" % avg -print "[*] Decues: Evaluations per second = %f" % (1.0 / avg) +print("7 card evaluation:") +print("[*] Deuces: Average time per evaluation: %f" % avg) +print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) ### @@ -41,9 +41,9 @@ def setup(n, m): cumtime += (time.time() - start) avg = float(cumtime / n) -print "6 card evaluation:" -print "[*] Deuces: Average time per evaluation: %f" % avg -print "[*] Decues: Evaluations per second = %f" % (1.0 / avg) +print("6 card evaluation:") +print("[*] Deuces: Average time per evaluation: %f" % avg) +print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) ### @@ -55,6 +55,6 @@ def setup(n, m): cumtime += (time.time() - start) avg = float(cumtime / n) -print "5 card evaluation:" -print "[*] Deuces: Average time per evaluation: %f" % avg -print "[*] Decues: Evaluations per second = %f" % (1.0 / avg) +print("5 card evaluation:") +print("[*] Deuces: Average time per evaluation: %f" % avg) +print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) diff --git a/performance/perf_handeval.py b/performance/perf_handeval.py deleted file mode 100644 index c0912a7..0000000 --- a/performance/perf_handeval.py +++ /dev/null @@ -1,43 +0,0 @@ -from card import Card -from hand_evaluator import HandEvaluator -import time -import random - -def setup(n): - - hands = [] - boards = [] - - full_deck = [] - for i in range(2, 14 + 1): - for j in range(1, 4 + 1): - full_deck.append(Card(i, j)) - - - for i in range(n): - - deck = list(full_deck) - random.shuffle(deck) - hand = [] - board = [] - for j in range(2): - hand.append(deck.pop(0)) - for j in range(5): - board.append(deck.pop(0)) - - hands.append(hand) - boards.append(board) - - return boards, hands - -N = 10000 -cumtime = 0.0 -boards, hands = setup(N) -for i in range(len(boards)): - start = time.time() - HandEvaluator.evaluate_hand(hands[i], boards[i]) - cumtime += (time.time() - start) - -avg = float(cumtime / N) -print "[*] Pokerhand-eval: Average time per evaluation: %f" % avg -print "[*] Pokerhand-eval: Evaluations per second = %f" % (1.0 / avg) \ No newline at end of file diff --git a/performance/perf_specialk.py b/performance/perf_specialk.py deleted file mode 100644 index 7568eb1..0000000 --- a/performance/perf_specialk.py +++ /dev/null @@ -1,56 +0,0 @@ -import time -from SevenEval import SevenEval -from FiveEval import FiveEval -import random - -def setup(n, m): - - hands = [] - boards = [] - - for i in range(n): - - deck = range(52) - random.shuffle(deck) - hand = [] - board = [] - for j in range(2): - hand.append(deck.pop(0)) - for j in range(m): - board.append(deck.pop(0)) - - hands.append(hand) - boards.append(board) - - return boards, hands - -s = SevenEval() - -N = 10000 -cumtime = 0.0 -boards, hands = setup(N, 5) -for i in range(len(boards)): - start = time.time() - s.getRankOfSeven(*(boards[i] + hands[i])) - cumtime += (time.time() - start) - -avg = float(cumtime / N) -print "7 card evaluation:" -print "[*] SpecialK: Average time per evaluation: %f" % avg -print "[*] SpecialK: Evaluations per second = %f" % (1.0 / avg) - -#### - -f = FiveEval() - -cumtime = 0.0 -boards, hands = setup(N, 3) -for i in range(len(boards)): - start = time.time() - f.getRankOfFive(*(boards[i] + hands[i])) - cumtime += (time.time() - start) - -avg = float(cumtime / N) -print "5 card evaluation:" -print "[*] SpecialK: Average time per evaluation: %f" % avg -print "[*] SpecialK: Evaluations per second = %f" % (1.0 / avg) \ No newline at end of file From 5679e36d111597c2ee1d9ddc7ea8ebb4d5435b7f Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:09:16 -0500 Subject: [PATCH 02/28] update README.md --- README.md | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ba6f80f..2c1acfb 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,11 @@ $ pip install deuces ## Implementation notes -Deuces, originally written for the MIT Pokerbots Competition, is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). +Deuces was originally written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). Deuces handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices, but later releases may have dedicated and faster algorithms for these. -I also have lookup tables for 2 card rollouts, which is particularly handy in evaluating Texas Hold'em preflop pot equity, but they are forthcoming as well. - -See my blog for an explanation of how the library works and how the lookup table generation is done: -http://willdrevo.com/ (haven't posted yet) +I also have lookup tables for 2 card rollouts, which is particularly handy in evaluating Texas Hold'em preflop pot equity, but they are forthcoming as well. ## Usage @@ -118,37 +115,6 @@ or, coolest of all, get a blow-by-blow analysis of the stages of the game with r ========== HAND OVER ========== Player 2 is the winner with a Straight -And that's Deuces, yo. - -## Performance - -Just how fast is Deuces? Check out `performance` folder for a couple of tests comparing Deuces to other pure Python hand evaluators. - -Here are the results evaluating 10,000 random 5, 6, and 7 card boards: - - 5 card evaluation: - [*] Pokerhand-eval: Evaluations per second = 83.577580 - [*] Deuces: Evaluations per second = 235722.458889 - [*] SpecialK: Evaluations per second = 376833.177604 - - 6 card evaluation: - [*] Pokerhand-eval: Evaluations per second = 55.519042 - [*] Deuces: Evaluations per second = 45677.395466 - [*] SpecialK: N/A - - 7 card evaluation: - [*] Pokerhand-eval: Evaluations per second = 51.529784 - [*] Deuces: Evaluations per second = 15220.969303 - [*] SpecialK: Evaluations per second = 142698.833384 - -Compared to [`pokerhand-eval`](https://github.com/aliang/pokerhand-eval), Deuces is 2400x faster on 5 card evaluation, and drops to 300x faster on 7 card evaluation. - -However, [`SpecialKEval`](https://github.com/SpecialK/SpecialKEval/) reigns supreme, with an impressive nearly 400k evals / sec (a factor of ~1.7 improvement over Deuces) for 5 cards, and an impressive 140k /sec on 7 cards (factor of 10). - -For poker hand evaluation in Python, if you desire a cleaner user interface and more readable and adaptable code, I recommend Deuces, because if you *really* need speed, you should be using C anyway. The extra 10x on 7 cards with SpecialK won't get you much more in terms of Monte Carlo simulations, and SpecialK's 5 card evals are within a factor of 2 of Deuces's evals/s. - -For C/C++, I'd recommand [`pokerstove`](https://github.com/andrewprock/pokerstove), as its hyperoptimized C++ Boost routines can do 10+ million evals/s. - ## License Copyright (c) 2013 Will Drevo From a03fea53e4fdd5240f7a291e2a67cda9561b1757 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:10:23 -0500 Subject: [PATCH 03/28] update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c1acfb..b320dc8 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,7 @@ $ pip install deuces Deuces was originally written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). -Deuces handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices, but later releases may have dedicated and faster algorithms for these. - -I also have lookup tables for 2 card rollouts, which is particularly handy in evaluating Texas Hold'em preflop pot equity, but they are forthcoming as well. +Deuces handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices. ## Usage From b7eab6502ee0d27fe5302a71752618b7402c6108 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:14:24 -0500 Subject: [PATCH 04/28] fix installation instructions and describe fork --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b320dc8..ae7a890 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ A pure Python poker hand evaluation library ## Installation ``` -$ pip install deuces +$ pip install git+https://github.com/ihendley/deuces ``` ## Implementation notes -Deuces was originally written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). +This is a fork of https://github.com/worldveil/deuces that supports Python 3. Deuces was originally written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). Deuces handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices. From 8c2aa94365ea07108fdcf5f2202d0999e329c217 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:46:39 -0500 Subject: [PATCH 05/28] reflect rename in README.md --- README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ae7a890..4d2ebb3 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,34 @@ -Deuces +Treys ======== A pure Python poker hand evaluation library - [ 2 ❤ ] , [ 2 ♠ ] + [ 3 ❤ ] , [ 3 ♠ ] ## Installation ``` -$ pip install git+https://github.com/ihendley/deuces +$ pip install git+https://github.com/ihendley/treys ``` ## Implementation notes -This is a fork of https://github.com/worldveil/deuces that supports Python 3. Deuces was originally written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Deuces won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). +Treys is a fork of https://github.com/worldveil/deuces that supports Python 3. -Deuces handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices. +Treys (originally Deuces) was written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Treys won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). + +Treys handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices. ## Usage -Deuces is easy to set up and use. +Treys is easy to set up and use. ```python ->>> from deuces import Card +>>> from treys import Card >>> card = Card.new('Qh') ``` -Card objects are represented as integers to keep Deuces performant and lightweight. +Card objects are represented as integers to keep Treys performant and lightweight. Now let's create the board and an example Texas Hold'em hand: @@ -51,7 +53,7 @@ If you have [`termacolor`](http://pypi.python.org/pypi/termcolor) installed, the Otherwise move straight to evaluating your hand strength: ```python ->>> from deuces import Evaluator +>>> from treys import Evaluator >>> evaluator = Evaluator() >>> print evaluator.evaluate(board, hand) 1600 @@ -59,9 +61,9 @@ Otherwise move straight to evaluating your hand strength: Hand strength is valued on a scale of 1 to 7462, where 1 is a Royal Flush and 7462 is unsuited 7-5-4-3-2, as there are only 7642 distinctly ranked hands in poker. Once again, refer to my blog post for a more mathematically complete explanation of why this is so. -If you want to deal out cards randomly from a deck, you can also do that with Deuces: +If you want to deal out cards randomly from a deck, you can also do that with Treys: ```python ->>> from deuces import Deck +>>> from treys import Deck >>> deck = Deck() >>> board = deck.draw(5) >>> player1_hand = deck.draw(2) From 4aaad5625589d792bc33ac328f10def9dd32813a Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:50:39 -0500 Subject: [PATCH 06/28] refactor for repo rename --- go.py | 2 +- perf.py | 14 +++++++------- setup.py | 6 +++--- {deuces => treys}/__init__.py | 0 {deuces => treys}/card.py | 0 {deuces => treys}/deck.py | 0 {deuces => treys}/evaluator.py | 0 {deuces => treys}/lookup.py | 0 8 files changed, 11 insertions(+), 11 deletions(-) rename {deuces => treys}/__init__.py (100%) rename {deuces => treys}/card.py (100%) rename {deuces => treys}/deck.py (100%) rename {deuces => treys}/evaluator.py (100%) rename {deuces => treys}/lookup.py (100%) diff --git a/go.py b/go.py index 2b085cd..9223ded 100644 --- a/go.py +++ b/go.py @@ -1,4 +1,4 @@ -from deuces import Card, Evaluator, Deck +from treys import Card, Evaluator, Deck # create a card card = Card.new('Qh') diff --git a/perf.py b/perf.py index 2b3d86c..6d2056d 100644 --- a/perf.py +++ b/perf.py @@ -1,6 +1,6 @@ import time import random -from deuces import Card, Deck, Evaluator +from treys import Card, Deck, Evaluator def setup(n, m): @@ -28,8 +28,8 @@ def setup(n, m): avg = float(cumtime / n) print("7 card evaluation:") -print("[*] Deuces: Average time per evaluation: %f" % avg) -print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) ### @@ -42,8 +42,8 @@ def setup(n, m): avg = float(cumtime / n) print("6 card evaluation:") -print("[*] Deuces: Average time per evaluation: %f" % avg) -print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) ### @@ -56,5 +56,5 @@ def setup(n, m): avg = float(cumtime / n) print("5 card evaluation:") -print("[*] Deuces: Average time per evaluation: %f" % avg) -print("[*] Decues: Evaluations per second = %f" % (1.0 / avg)) +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) diff --git a/setup.py b/setup.py index c4d454e..b7eaa07 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,14 @@ from setuptools import setup setup( - name='deuces', + name='treys', version='0.1', description=__doc__, long_description=open('README.md').read(), author='Will Drevo', - url='https://github.com/worldveil/deuces', + url='https://github.com/ihendley/treys', license='MIT', - packages=['deuces'], + packages=['treys'], classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', diff --git a/deuces/__init__.py b/treys/__init__.py similarity index 100% rename from deuces/__init__.py rename to treys/__init__.py diff --git a/deuces/card.py b/treys/card.py similarity index 100% rename from deuces/card.py rename to treys/card.py diff --git a/deuces/deck.py b/treys/deck.py similarity index 100% rename from deuces/deck.py rename to treys/deck.py diff --git a/deuces/evaluator.py b/treys/evaluator.py similarity index 100% rename from deuces/evaluator.py rename to treys/evaluator.py diff --git a/deuces/lookup.py b/treys/lookup.py similarity index 100% rename from deuces/lookup.py rename to treys/lookup.py From 1080c121763d5bdfe302d627265ffe5247a8350b Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 04:54:04 -0500 Subject: [PATCH 07/28] indicate Python 3 support in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b7eaa07..a7f156f 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ 'Intended Audience :: Education', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', 'Topic :: Games/Entertainment' ] ) From 1ba0c11a751b78b4898d4db0a1a230b24946f6fc Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 05:10:41 -0500 Subject: [PATCH 08/28] fixed setup.py description --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a7f156f..de43a8b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -Deuces: A pure Python poker hand evaluation library +Treys: A pure Python poker hand evaluation library """ from setuptools import setup @@ -7,7 +7,7 @@ setup( name='treys', version='0.1', - description=__doc__, + description='treys is a pure Python poker hand evaluation library', long_description=open('README.md').read(), author='Will Drevo', url='https://github.com/ihendley/treys', From 544b940e4badcbabdd7351cff80b9702d08595f5 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 11 Dec 2017 05:20:00 -0500 Subject: [PATCH 09/28] update installation instructions for pypi --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d2ebb3..ce4c44b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A pure Python poker hand evaluation library ## Installation ``` -$ pip install git+https://github.com/ihendley/treys +$ pip install treys ``` ## Implementation notes From ab83b0baa41f5cbf8b2b3515b48c9b76bafb9968 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Wed, 13 Dec 2017 03:35:27 -0500 Subject: [PATCH 10/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce4c44b..2a78b06 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ $ pip install treys ## Implementation notes -Treys is a fork of https://github.com/worldveil/deuces that supports Python 3. +Treys is a Python 3 port of [Deuces](https://github.com/worldveil/deuces). Most of work is taken from [msaindon'](https://github.com/msaindon/deuces) fork. Treys (originally Deuces) was written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Treys won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). From 262a4fb3cfe815ca40dbf0d6d805934f9b3030c6 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Wed, 13 Dec 2017 03:35:43 -0500 Subject: [PATCH 11/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a78b06..0c2d455 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ $ pip install treys ## Implementation notes -Treys is a Python 3 port of [Deuces](https://github.com/worldveil/deuces). Most of work is taken from [msaindon'](https://github.com/msaindon/deuces) fork. +Treys is a Python 3 port of [Deuces](https://github.com/worldveil/deuces). Most of work is taken from [msaindon's](https://github.com/msaindon/deuces) fork. Treys (originally Deuces) was written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Treys won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). From dcd30986923d92622d425c214665d1ab424f7102 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Wed, 13 Dec 2017 04:47:45 -0500 Subject: [PATCH 12/28] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c2d455..3c9aea3 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Otherwise move straight to evaluating your hand strength: ```python >>> from treys import Evaluator >>> evaluator = Evaluator() ->>> print evaluator.evaluate(board, hand) +>>> print(evaluator.evaluate(board, hand)) 1600 ``` @@ -87,10 +87,10 @@ Let's evaluate both hands strength, and then bin them into classes, one for each ``` or get a human-friendly string to describe the score, - >>> print "Player 1 hand rank = %d (%s)\n" % (p1_score, evaluator.class_to_string(p1_class)) + >>> print("Player 1 hand rank = %d (%s)\n" % (p1_score, evaluator.class_to_string(p1_class))) Player 1 hand rank = 6330 (High Card) - >>> print "Player 2 hand rank = %d (%s)\n" % (p2_score, evaluator.class_to_string(p2_class)) + >>> print("Player 2 hand rank = %d (%s)\n" % (p2_score, evaluator.class_to_string(p2_class))) Player 2 hand rank = 1609 (Straight) or, coolest of all, get a blow-by-blow analysis of the stages of the game with relation to hand strength: From 4e4e02353d1f917c4fdd620984daf4b6d49dd096 Mon Sep 17 00:00:00 2001 From: chunhao Date: Thu, 22 Feb 2018 18:14:01 +0800 Subject: [PATCH 13/28] f string isn't supported by python 3.5 --- treys/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/treys/card.py b/treys/card.py index 753aea2..0c03bd7 100644 --- a/treys/card.py +++ b/treys/card.py @@ -189,7 +189,7 @@ def int_to_pretty_str(card_int): r = Card.STR_RANKS[rank_int] - return f"[{r}{s}]" + return "[{}{}]".format(r,s) @staticmethod def print_pretty_card(card_int): From 1f4c1f4bf2a87a2e93f80055519f4984bc5ebf4f Mon Sep 17 00:00:00 2001 From: chunhao Date: Thu, 22 Feb 2018 18:18:50 +0800 Subject: [PATCH 14/28] f string isn't supported by python 3.5 --- treys/evaluator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/treys/evaluator.py b/treys/evaluator.py index 2fa8e6d..f5354ed 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -142,7 +142,7 @@ def hand_summary(self, board, hands): for i in range(len(stages)): line = "=" * line_length - print(f"{line} {stages[i]} {line}") + print("{} {} {}".format(line,stages[i],line)) best_rank = 7463 # rank one worse than worst hand winners = [] @@ -153,7 +153,7 @@ def hand_summary(self, board, hands): rank_class = self.get_rank_class(rank) class_string = self.class_to_string(rank_class) percentage = 1.0 - self.get_five_card_rank_percentage(rank) # higher better here - print(f"Player {player + 1} hand = {class_string}, percentage rank among all hands = {percentage}") + print("Player {} hand = {}, percentage rank among all hands = {}".format(player + 1, class_string, percentage)) # detect winner if rank == best_rank: @@ -166,16 +166,16 @@ def hand_summary(self, board, hands): # if we're not on the river if i != stages.index("RIVER"): if len(winners) == 1: - print(f"Player {winners[0] + 1} hand is currently winning.\n") + print("Player {} hand is currently winning.\n".format(winners[0] + 1)) else: - print(f"Players {[x + 1 for x in winners]} are tied for the lead.\n") + print("Players {} are tied for the lead.\n".format([x + 1 for x in winners])) # otherwise on all other streets else: hand_result = self.class_to_string(self.get_rank_class(self.evaluate(hands[winners[0]], board))) print() - print(f"{line} HAND OVER {line}") + print("{} HAND OVER {}".format(line, line)) if len(winners) == 1: - print(f"Player {winners[0] + 1} is the winner with a {hand_result}\n") + print("Player {} is the winner with a {}\n".format(winners[0] + 1, hand_result)) else: - print(f"Players {winners} tied for the win with a {hand_result}\n") + print("Players {} tied for the win with a {}\n".format(winners,hand_result)) From d4eb101e8b0e3f13c74cd58c34eb2cd162ad54a6 Mon Sep 17 00:00:00 2001 From: chunhao Date: Thu, 22 Feb 2018 18:20:02 +0800 Subject: [PATCH 15/28] f string isn't supported by python 3.5 --- go.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.py b/go.py index 9223ded..f3e1abc 100644 --- a/go.py +++ b/go.py @@ -47,8 +47,8 @@ p2_class = evaluator.get_rank_class(p2_score) # or get a human-friendly string to describe the score -print(f"Player 1 hand rank = {p1_score} {evaluator.class_to_string(p1_class)}") -print(f"Player 2 hand rank = {p2_score} {evaluator.class_to_string(p2_class)}") +print("Player 1 hand rank = {} {evaluator.class_to_string(p1_class)}".format(p1_score)) +print("Player 2 hand rank = {} {evaluator.class_to_string(p2_class)}".format(p2_score)) # or just a summary of the entire hand hands = [player1_hand, player2_hand] From f7505e684d1de257f3d4e67feccf35896b8bc7d0 Mon Sep 17 00:00:00 2001 From: "J.Bowman" Date: Wed, 30 May 2018 21:23:54 -0400 Subject: [PATCH 16/28] pretty print methods should return the string, not just print --- treys/card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/treys/card.py b/treys/card.py index 0c03bd7..fc21d3c 100644 --- a/treys/card.py +++ b/treys/card.py @@ -196,7 +196,7 @@ def print_pretty_card(card_int): """ Expects a single integer as input """ - print(Card.int_to_pretty_str(card_int)) + return Card.int_to_pretty_str(card_int) @staticmethod def print_pretty_cards(card_ints): @@ -211,4 +211,4 @@ def print_pretty_cards(card_ints): else: output += str(Card.int_to_pretty_str(c)) + " " - print(output) + return output From 3f24fe088788c58b325f72028d96cabf71270a4f Mon Sep 17 00:00:00 2001 From: Andrew Yao Date: Wed, 15 Aug 2018 10:36:33 +0800 Subject: [PATCH 17/28] Added 1 to all winners' index --- treys/evaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/treys/evaluator.py b/treys/evaluator.py index f5354ed..4ff34c3 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -178,4 +178,4 @@ def hand_summary(self, board, hands): if len(winners) == 1: print("Player {} is the winner with a {}\n".format(winners[0] + 1, hand_result)) else: - print("Players {} tied for the win with a {}\n".format(winners,hand_result)) + print("Players {} tied for the win with a {}\n".format([x + 1 for x in winners],hand_result)) From 172d8d313ecef6cde75c2ee89eb8ed724e0d2a05 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 20 Aug 2018 22:48:07 -0400 Subject: [PATCH 18/28] cleanup and version bump --- README.md | 138 ---------------------------------------------------- __init__.py | 1 + setup.py | 4 +- 3 files changed, 3 insertions(+), 140 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 3c9aea3..0000000 --- a/README.md +++ /dev/null @@ -1,138 +0,0 @@ -Treys -======== - -A pure Python poker hand evaluation library - - [ 3 ❤ ] , [ 3 ♠ ] - -## Installation - -``` -$ pip install treys -``` - -## Implementation notes - -Treys is a Python 3 port of [Deuces](https://github.com/worldveil/deuces). Most of work is taken from [msaindon's](https://github.com/msaindon/deuces) fork. - -Treys (originally Deuces) was written by [Will Drevo](http://willdrevo.com/) for the MIT Pokerbots Competition. It is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Treys won't beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). - -Treys handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups are done by combinatorially evaluating the 5 card choices. - -## Usage - -Treys is easy to set up and use. - -```python ->>> from treys import Card ->>> card = Card.new('Qh') -``` - -Card objects are represented as integers to keep Treys performant and lightweight. - -Now let's create the board and an example Texas Hold'em hand: - -```python ->>> board = [ ->>> Card.new('Ah'), ->>> Card.new('Kd'), ->>> Card.new('Jc') ->>> ] ->>> hand = [ ->>> Card.new('Qs'), ->>> Card.new('Th') ->>> ] -``` - -Pretty print card integers to the terminal: - - >>> Card.print_pretty_cards(board + hand) - [ A ❤ ] , [ K ♦ ] , [ J ♣ ] , [ Q ♠ ] , [ T ❤ ] - -If you have [`termacolor`](http://pypi.python.org/pypi/termcolor) installed, they will be colored as well. - -Otherwise move straight to evaluating your hand strength: -```python ->>> from treys import Evaluator ->>> evaluator = Evaluator() ->>> print(evaluator.evaluate(board, hand)) -1600 -``` - -Hand strength is valued on a scale of 1 to 7462, where 1 is a Royal Flush and 7462 is unsuited 7-5-4-3-2, as there are only 7642 distinctly ranked hands in poker. Once again, refer to my blog post for a more mathematically complete explanation of why this is so. - -If you want to deal out cards randomly from a deck, you can also do that with Treys: -```python ->>> from treys import Deck ->>> deck = Deck() ->>> board = deck.draw(5) ->>> player1_hand = deck.draw(2) ->>> player2_hand = deck.draw(2) -``` -and print them: - - >>> Card.print_pretty_cards(board) - [ 4 ♣ ] , [ A ♠ ] , [ 5 ♦ ] , [ K ♣ ] , [ 2 ♠ ] - >>> Card.print_pretty_cards(player1_hand) - [ 6 ♣ ] , [ 7 ❤ ] - >>> Card.print_pretty_cards(player2_hand) - [ A ♣ ] , [ 3 ❤ ] - -Let's evaluate both hands strength, and then bin them into classes, one for each hand type (High Card, Pair, etc) -```python ->>> p1_score = evaluator.evaluate(board, player1_hand) ->>> p2_score = evaluator.evaluate(board, player2_hand) ->>> p1_class = evaluator.get_rank_class(p1_score) ->>> p2_class = evaluator.get_rank_class(p2_score) -``` -or get a human-friendly string to describe the score, - - >>> print("Player 1 hand rank = %d (%s)\n" % (p1_score, evaluator.class_to_string(p1_class))) - Player 1 hand rank = 6330 (High Card) - - >>> print("Player 2 hand rank = %d (%s)\n" % (p2_score, evaluator.class_to_string(p2_class))) - Player 2 hand rank = 1609 (Straight) - -or, coolest of all, get a blow-by-blow analysis of the stages of the game with relation to hand strength: - - >>> hands = [player1_hand, player2_hand] - >>> evaluator.hand_summary(board, hands) - - ========== FLOP ========== - Player 1 hand = High Card, percentage rank among all hands = 0.893192 - Player 2 hand = Pair, percentage rank among all hands = 0.474672 - Player 2 hand is currently winning. - - ========== TURN ========== - Player 1 hand = High Card, percentage rank among all hands = 0.848298 - Player 2 hand = Pair, percentage rank among all hands = 0.452292 - Player 2 hand is currently winning. - - ========== RIVER ========== - Player 1 hand = High Card, percentage rank among all hands = 0.848298 - Player 2 hand = Straight, percentage rank among all hands = 0.215626 - - ========== HAND OVER ========== - Player 2 is the winner with a Straight - -## License - -Copyright (c) 2013 Will Drevo - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/__init__.py b/__init__.py index e69de29..6921ea0 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1 @@ +name = "treys" diff --git a/setup.py b/setup.py index de43a8b..dd0e56d 100644 --- a/setup.py +++ b/setup.py @@ -6,9 +6,9 @@ setup( name='treys', - version='0.1', + version='0.1.1', description='treys is a pure Python poker hand evaluation library', - long_description=open('README.md').read(), + long_description=open('README.rst').read(), author='Will Drevo', url='https://github.com/ihendley/treys', license='MIT', From 9263efff42a51ec3b3527f402a6064ba41b4efff Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 20 Aug 2018 22:49:13 -0400 Subject: [PATCH 19/28] move license to own file and convert readme to rst --- LICENSE | 19 +++++++ README.rst | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 LICENSE create mode 100644 README.rst diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..efecdb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Will Drevo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e529289 --- /dev/null +++ b/README.rst @@ -0,0 +1,150 @@ +Treys +===== + +A pure Python poker hand evaluation library + +:: + + [ 3 ❤ ] , [ 3 ♠ ] + +Installation +------------ + +:: + + $ pip install treys + +Implementation notes +-------------------- + +Treys is a Python 3 port of +`Deuces `__. Most of work is taken +from `msaindon’s `__ fork. + +Treys (originally Deuces) was written by `Will +Drevo `__ for the MIT Pokerbots Competition. It +is lightweight and fast. All lookups are done with bit arithmetic and +dictionary lookups. That said, Treys won’t beat a C implemenation (~250k +eval/s) but it is useful for situations where Python is required or +where bots are allocated reasonable thinking time (human time scale). + +Treys handles 5, 6, and 7 card hand lookups. The 6 and 7 card lookups +are done by combinatorially evaluating the 5 card choices. + +Usage +----- + +Treys is easy to set up and use. + +.. code:: python + + >>> from treys import Card + >>> card = Card.new('Qh') + +Card objects are represented as integers to keep Treys performant and +lightweight. + +Now let’s create the board and an example Texas Hold’em hand: + +.. code:: python + + >>> board = [ + >>> Card.new('Ah'), + >>> Card.new('Kd'), + >>> Card.new('Jc') + >>> ] + >>> hand = [ + >>> Card.new('Qs'), + >>> Card.new('Th') + >>> ] + +Pretty print card integers to the terminal: + +:: + + >>> Card.print_pretty_cards(board + hand) + [ A ❤ ] , [ K ♦ ] , [ J ♣ ] , [ Q ♠ ] , [ T ❤ ] + +If you have `termcolor `__ +installed, they will be colored as well. + +Otherwise move straight to evaluating your hand strength: + +.. code:: python + + >>> from treys import Evaluator + >>> evaluator = Evaluator() + >>> print(evaluator.evaluate(board, hand)) + 1600 + +Hand strength is valued on a scale of 1 to 7462, where 1 is a Royal +Flush and 7462 is unsuited 7-5-4-3-2, as there are only 7642 distinctly +ranked hands in poker. Once again, refer to my blog post for a more +mathematically complete explanation of why this is so. + +If you want to deal out cards randomly from a deck, you can also do that +with Treys: + +.. code:: python + + >>> from treys import Deck + >>> deck = Deck() + >>> board = deck.draw(5) + >>> player1_hand = deck.draw(2) + >>> player2_hand = deck.draw(2) + +and print them: + +:: + + >>> Card.print_pretty_cards(board) + [ 4 ♣ ] , [ A ♠ ] , [ 5 ♦ ] , [ K ♣ ] , [ 2 ♠ ] + >>> Card.print_pretty_cards(player1_hand) + [ 6 ♣ ] , [ 7 ❤ ] + >>> Card.print_pretty_cards(player2_hand) + [ A ♣ ] , [ 3 ❤ ] + +Let’s evaluate both hands strength, and then bin them into classes, one +for each hand type (High Card, Pair, etc) + +.. code:: python + + >>> p1_score = evaluator.evaluate(board, player1_hand) + >>> p2_score = evaluator.evaluate(board, player2_hand) + >>> p1_class = evaluator.get_rank_class(p1_score) + >>> p2_class = evaluator.get_rank_class(p2_score) + +or get a human-friendly string to describe the score, + +:: + + >>> print("Player 1 hand rank = %d (%s)\n" % (p1_score, evaluator.class_to_string(p1_class))) + Player 1 hand rank = 6330 (High Card) + + >>> print("Player 2 hand rank = %d (%s)\n" % (p2_score, evaluator.class_to_string(p2_class))) + Player 2 hand rank = 1609 (Straight) + +or, coolest of all, get a blow-by-blow analysis of the stages of the +game with relation to hand strength: + +:: + + >>> hands = [player1_hand, player2_hand] + >>> evaluator.hand_summary(board, hands) + + ========== FLOP ========== + Player 1 hand = High Card, percentage rank among all hands = 0.893192 + Player 2 hand = Pair, percentage rank among all hands = 0.474672 + Player 2 hand is currently winning. + + ========== TURN ========== + Player 1 hand = High Card, percentage rank among all hands = 0.848298 + Player 2 hand = Pair, percentage rank among all hands = 0.452292 + Player 2 hand is currently winning. + + ========== RIVER ========== + Player 1 hand = High Card, percentage rank among all hands = 0.848298 + Player 2 hand = Straight, percentage rank among all hands = 0.215626 + + ========== HAND OVER ========== + Player 2 is the winner with a Straight From 8d05d4f2636103cc968002c4d8ce4ad68191ccee Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Mon, 20 Aug 2018 22:53:36 -0400 Subject: [PATCH 20/28] version bump for pypi --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dd0e56d..570ed14 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='treys', - version='0.1.1', + version='0.1.3', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), author='Will Drevo', From f018bf7c87d5ae463dd9002cb0111f8aa9e1add8 Mon Sep 17 00:00:00 2001 From: Oscar Blazejewski Date: Wed, 19 Jun 2019 16:26:07 +0200 Subject: [PATCH 21/28] Fix typo --- treys/evaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/treys/evaluator.py b/treys/evaluator.py index 4ff34c3..3daa7ae 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -135,7 +135,7 @@ def hand_summary(self, board, hands): assert len(board) == 5, "Invalid board length" for hand in hands: - assert len(hand) == 2, "Inavlid hand length" + assert len(hand) == 2, "Invalid hand length" line_length = 10 stages = ["FLOP", "TURN", "RIVER"] From 71c0c55ed3a59bede1a87e237886e6ec70e4d586 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Wed, 4 May 2022 03:07:08 -0400 Subject: [PATCH 22/28] fix card and rank printing, add royal flush rank, add four-color deck --- go.py | 7 +++++-- setup.py | 2 +- treys/card.py | 24 +++++++++++++++++------- treys/evaluator.py | 7 ++++--- treys/lookup.py | 3 +++ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/go.py b/go.py index f3e1abc..943244c 100644 --- a/go.py +++ b/go.py @@ -22,7 +22,10 @@ # and rank your hand rank = evaluator.evaluate(board, hand) +class_ = evaluator.get_rank_class(rank) +print("{} {}".format(rank, evaluator.class_to_string(class_))) print() + # or for random cards or games, create a deck print("Dealing a new hand...") deck = Deck() @@ -47,8 +50,8 @@ p2_class = evaluator.get_rank_class(p2_score) # or get a human-friendly string to describe the score -print("Player 1 hand rank = {} {evaluator.class_to_string(p1_class)}".format(p1_score)) -print("Player 2 hand rank = {} {evaluator.class_to_string(p2_class)}".format(p2_score)) +print("Player 1 hand rank = {} {}".format(p1_score, evaluator.class_to_string(p1_class))) +print("Player 2 hand rank = {} {}".format(p2_score, evaluator.class_to_string(p2_class))) # or just a summary of the entire hand hands = [player1_hand, player2_hand] diff --git a/setup.py b/setup.py index 570ed14..6485631 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='treys', - version='0.1.3', + version='0.1.4', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), author='Will Drevo', diff --git a/treys/card.py b/treys/card.py index fc21d3c..8b39094 100644 --- a/treys/card.py +++ b/treys/card.py @@ -48,8 +48,11 @@ class Card: 8: chr(9827) # clubs } - # hearts and diamonds - PRETTY_REDS = [2, 4] + SUIT_COLORS = { + 2: "red", + 4: "blue", + 8: "green" + } @staticmethod def new(string): @@ -182,10 +185,10 @@ def int_to_pretty_str(card_int): suit_int = Card.get_suit_int(card_int) rank_int = Card.get_rank_int(card_int) - # if we need to color red + # color s = Card.PRETTY_SUITS[suit_int] - if color and suit_int in Card.PRETTY_REDS: - s = colored(s, "red") + if color and suit_int in Card.SUIT_COLORS: + s = colored(s, Card.SUIT_COLORS[suit_int]) r = Card.STR_RANKS[rank_int] @@ -196,10 +199,10 @@ def print_pretty_card(card_int): """ Expects a single integer as input """ - return Card.int_to_pretty_str(card_int) + print(Card.int_to_pretty_str(card_int)) @staticmethod - def print_pretty_cards(card_ints): + def ints_to_pretty_str(card_ints): """ Expects a list of cards in integer form. """ @@ -212,3 +215,10 @@ def print_pretty_cards(card_ints): output += str(Card.int_to_pretty_str(c)) + " " return output + + @staticmethod + def print_pretty_cards(card_ints): + """ + Expects a list of cards in integer form. + """ + print(Card.ints_to_pretty_str(card_ints)) diff --git a/treys/evaluator.py b/treys/evaluator.py index 3daa7ae..d262113 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -28,8 +28,7 @@ def evaluate(self, cards, board): """ This is the function that the user calls to get a hand rank. - Supports empty board, etc very flexible. No input validation - because that's cycles! + No input validation because that's cycles! """ all_cards = cards + board return self.hand_size_map[len(all_cards)](all_cards) @@ -92,7 +91,9 @@ def get_rank_class(self, hr): Returns the class of hand given the hand hand_rank returned from evaluate. """ - if hr >= 0 and hr <= LookupTable.MAX_STRAIGHT_FLUSH: + if hr >= 0 and hr <= LookupTable.MAX_ROYAL_FLUSH: + return LookupTable.MAX_TO_RANK_CLASS[LookupTable.MAX_ROYAL_FLUSH] + elif hr <= LookupTable.MAX_STRAIGHT_FLUSH: return LookupTable.MAX_TO_RANK_CLASS[LookupTable.MAX_STRAIGHT_FLUSH] elif hr <= LookupTable.MAX_FOUR_OF_A_KIND: return LookupTable.MAX_TO_RANK_CLASS[LookupTable.MAX_FOUR_OF_A_KIND] diff --git a/treys/lookup.py b/treys/lookup.py index 2a30136..609cc33 100644 --- a/treys/lookup.py +++ b/treys/lookup.py @@ -25,6 +25,7 @@ class LookupTable(object): * Royal flush (best hand possible) => 1 * 7-5-4-3-2 unsuited (worst hand possible) => 7462 """ + MAX_ROYAL_FLUSH = 1 MAX_STRAIGHT_FLUSH = 10 MAX_FOUR_OF_A_KIND = 166 MAX_FULL_HOUSE = 322 @@ -36,6 +37,7 @@ class LookupTable(object): MAX_HIGH_CARD = 7462 MAX_TO_RANK_CLASS = { + MAX_ROYAL_FLUSH: 0, MAX_STRAIGHT_FLUSH: 1, MAX_FOUR_OF_A_KIND: 2, MAX_FULL_HOUSE: 3, @@ -48,6 +50,7 @@ class LookupTable(object): } RANK_CLASS_TO_STRING = { + 0: "Royal Flush", 1: "Straight Flush", 2: "Four of a Kind", 3: "Full House", From 9a9df7d0ac869ea97d7193b5af3fdc8fb4a1afb4 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Thu, 5 May 2022 10:32:09 -0400 Subject: [PATCH 23/28] add type hints, various bug fixes and cleanup --- README.rst | 12 ++++----- go.py | 5 +++- perf.py | 10 +++++--- setup.py | 5 ++-- treys/__init__.py | 4 +-- treys/card.py | 46 +++++++++++++++++---------------- treys/deck.py | 24 ++++++++---------- treys/evaluator.py | 24 ++++++++++-------- treys/lookup.py | 63 ++++++++++++++++++++++++---------------------- 9 files changed, 100 insertions(+), 93 deletions(-) diff --git a/README.rst b/README.rst index e529289..4ea8c3f 100644 --- a/README.rst +++ b/README.rst @@ -18,12 +18,11 @@ Implementation notes -------------------- Treys is a Python 3 port of -`Deuces `__. Most of work is taken -from `msaindon’s `__ fork. +`Deuces `__ based on the initial work in +`msaindon’s `__ fork. Deuces was written +by `Will Drevo `__ for the MIT Pokerbots Competition. -Treys (originally Deuces) was written by `Will -Drevo `__ for the MIT Pokerbots Competition. It -is lightweight and fast. All lookups are done with bit arithmetic and +Treys is lightweight and fast. All lookups are done with bit arithmetic and dictionary lookups. That said, Treys won’t beat a C implemenation (~250k eval/s) but it is useful for situations where Python is required or where bots are allocated reasonable thinking time (human time scale). @@ -79,8 +78,7 @@ Otherwise move straight to evaluating your hand strength: Hand strength is valued on a scale of 1 to 7462, where 1 is a Royal Flush and 7462 is unsuited 7-5-4-3-2, as there are only 7642 distinctly -ranked hands in poker. Once again, refer to my blog post for a more -mathematically complete explanation of why this is so. +ranked hands in poker. If you want to deal out cards randomly from a deck, you can also do that with Treys: diff --git a/go.py b/go.py index 943244c..bccf863 100644 --- a/go.py +++ b/go.py @@ -1,4 +1,7 @@ -from treys import Card, Evaluator, Deck +from treys.card import Card +from treys.evaluator import Evaluator +from treys.deck import Deck + # create a card card = Card.new('Qh') diff --git a/perf.py b/perf.py index 6d2056d..74c08de 100644 --- a/perf.py +++ b/perf.py @@ -1,15 +1,17 @@ import time -import random -from treys import Card, Deck, Evaluator -def setup(n, m): +from treys.deck import Deck +from treys.evaluator import Evaluator + + +def setup(n: int, m: int) -> tuple[list[list[int]], list[list[int]]]: deck = Deck() boards = [] hands = [] - for i in range(n): + for _ in range(n): boards.append(deck.draw(m)) hands.append(deck.draw(2)) deck.shuffle() diff --git a/setup.py b/setup.py index 6485631..11fc1f6 100644 --- a/setup.py +++ b/setup.py @@ -4,12 +4,13 @@ from setuptools import setup + setup( name='treys', - version='0.1.4', + version='0.1.5', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), - author='Will Drevo', + author='Will Drevo, Mark Saindon, Imran Hendley', url='https://github.com/ihendley/treys', license='MIT', packages=['treys'], diff --git a/treys/__init__.py b/treys/__init__.py index 59e51fd..8b13789 100644 --- a/treys/__init__.py +++ b/treys/__init__.py @@ -1,3 +1 @@ -from .card import Card -from .deck import Deck -from .evaluator import Evaluator + diff --git a/treys/card.py b/treys/card.py index 8b39094..ce25440 100644 --- a/treys/card.py +++ b/treys/card.py @@ -1,3 +1,5 @@ +from typing import Sequence + class Card: """ Static class that handles cards. We represent cards as 32-bit integers, so @@ -26,36 +28,36 @@ class Card: """ # the basics - STR_RANKS = '23456789TJQKA' - INT_RANKS = range(13) - PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] + STR_RANKS: str = '23456789TJQKA' + INT_RANKS: range = range(13) + PRIMES: list[int] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] # conversion from string => int - CHAR_RANK_TO_INT_RANK = dict(zip(list(STR_RANKS), INT_RANKS)) - CHAR_SUIT_TO_INT_SUIT = { + CHAR_RANK_TO_INT_RANK: dict[str, int] = dict(zip(list(STR_RANKS), INT_RANKS)) + CHAR_SUIT_TO_INT_SUIT: dict[str, int] = { 's': 1, # spades 'h': 2, # hearts 'd': 4, # diamonds 'c': 8, # clubs } - INT_SUIT_TO_CHAR_SUIT = 'xshxdxxxc' + INT_SUIT_TO_CHAR_SUIT: str = 'xshxdxxxc' # for pretty printing - PRETTY_SUITS = { + PRETTY_SUITS: dict[int, str] = { 1: chr(9824), # spades 2: chr(9829), # hearts 4: chr(9830), # diamonds 8: chr(9827) # clubs } - SUIT_COLORS = { + SUIT_COLORS: dict[int, str] = { 2: "red", 4: "blue", 8: "green" } @staticmethod - def new(string): + def new(string: str) -> int: """ Converts Card string to binary integer representation of card, inspired by: @@ -75,29 +77,29 @@ def new(string): return bitrank | suit | rank | rank_prime @staticmethod - def int_to_str(card_int): + def int_to_str(card_int: int) -> str: rank_int = Card.get_rank_int(card_int) suit_int = Card.get_suit_int(card_int) return Card.STR_RANKS[rank_int] + Card.INT_SUIT_TO_CHAR_SUIT[suit_int] @staticmethod - def get_rank_int(card_int): + def get_rank_int(card_int: int) -> int: return (card_int >> 8) & 0xF @staticmethod - def get_suit_int(card_int): + def get_suit_int(card_int: int) -> int: return (card_int >> 12) & 0xF @staticmethod - def get_bitrank_int(card_int): + def get_bitrank_int(card_int: int) -> int: return (card_int >> 16) & 0x1FFF @staticmethod - def get_prime(card_int): + def get_prime(card_int: int) -> int: return card_int & 0x3F @staticmethod - def hand_to_binary(card_strs): + def hand_to_binary(card_strs: Sequence[str]) -> list[int]: """ Expects a list of cards as strings and returns a list of integers of same length corresponding to those strings. @@ -108,7 +110,7 @@ def hand_to_binary(card_strs): return bhand @staticmethod - def prime_product_from_hand(card_ints): + def prime_product_from_hand(card_ints: Sequence[int]) -> int: """ Expects a list of cards in integer form. """ @@ -120,7 +122,7 @@ def prime_product_from_hand(card_ints): return product @staticmethod - def prime_product_from_rankbits(rankbits): + def prime_product_from_rankbits(rankbits: int) -> int: """ Returns the prime product using the bitrank (b) bits of the hand. Each 1 in the sequence is converted @@ -151,7 +153,7 @@ def prime_product_from_rankbits(rankbits): return product @staticmethod - def int_to_binary(card_int): + def int_to_binary(card_int: int) -> str: """ For debugging purposes. Displays the binary number as a human readable string in groups of four digits. @@ -167,7 +169,7 @@ def int_to_binary(card_int): return "".join(output) @staticmethod - def int_to_pretty_str(card_int): + def int_to_pretty_str(card_int: int) -> str: """ Prints a single card """ @@ -195,14 +197,14 @@ def int_to_pretty_str(card_int): return "[{}{}]".format(r,s) @staticmethod - def print_pretty_card(card_int): + def print_pretty_card(card_int: int) -> None: """ Expects a single integer as input """ print(Card.int_to_pretty_str(card_int)) @staticmethod - def ints_to_pretty_str(card_ints): + def ints_to_pretty_str(card_ints: Sequence[int]) -> str: """ Expects a list of cards in integer form. """ @@ -217,7 +219,7 @@ def ints_to_pretty_str(card_ints): return output @staticmethod - def print_pretty_cards(card_ints): + def print_pretty_cards(card_ints: Sequence[int]) -> None: """ Expects a list of cards in integer form. """ diff --git a/treys/deck.py b/treys/deck.py index 59c24dd..79226c7 100644 --- a/treys/deck.py +++ b/treys/deck.py @@ -1,4 +1,5 @@ from random import shuffle as rshuffle + from .card import Card @@ -8,36 +9,33 @@ class Deck: deck with the list of unique card integers. Each object instantiated simply makes a copy of this object and shuffles it. """ - _FULL_DECK = [] + _FULL_DECK: list[int] = [] - def __init__(self): + def __init__(self) -> None: self.shuffle() - def shuffle(self): + def shuffle(self) -> None: # and then shuffle self.cards = Deck.GetFullDeck() rshuffle(self.cards) - def draw(self, n=1): - if n == 1: - return self.cards.pop(0) - + def draw(self, n: int = 1) -> list[int]: cards = [] - for i in range(n): - cards.append(self.draw()) + for _ in range(n): + cards.append(self.cards.pop()) return cards - def __str__(self): - return Card.print_pretty_cards(self.cards) + def __str__(self) -> str: + return Card.ints_to_pretty_str(self.cards) @staticmethod - def GetFullDeck(): + def GetFullDeck() -> list[int]: 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(): + for suit, _ in Card.CHAR_SUIT_TO_INT_SUIT.items(): Deck._FULL_DECK.append(Card.new(rank + suit)) return list(Deck._FULL_DECK) \ No newline at end of file diff --git a/treys/evaluator.py b/treys/evaluator.py index d262113..eb163fc 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -1,9 +1,11 @@ import itertools +from typing import Sequence + from .card import Card -from .deck import Deck from .lookup import LookupTable -class Evaluator(object): + +class Evaluator: """ Evaluates hand strengths using a variant of Cactus Kev's algorithm: http://suffe.cool/poker/evaluator.html @@ -14,7 +16,7 @@ class Evaluator(object): all calculations are done with bit arithmetic and table lookups. """ - def __init__(self): + def __init__(self) -> None: self.table = LookupTable() @@ -24,7 +26,7 @@ def __init__(self): 7: self._seven } - def evaluate(self, cards, board): + def evaluate(self, cards: list[int], board: list[int]) -> int: """ This is the function that the user calls to get a hand rank. @@ -33,7 +35,7 @@ def evaluate(self, cards, board): all_cards = cards + board return self.hand_size_map[len(all_cards)](all_cards) - def _five(self, cards): + def _five(self, cards: Sequence[int]) -> int: """ Performs an evalution given cards in integer form, mapping them to a rank in the range [1, 7462], with lower ranks being more powerful. @@ -52,7 +54,7 @@ def _five(self, cards): prime = Card.prime_product_from_hand(cards) return self.table.unsuited_lookup[prime] - def _six(self, cards): + def _six(self, cards: Sequence[int]) -> int: """ Performs five_card_eval() on all (6 choose 5) = 6 subsets of 5 cards in the set of 6 to determine the best ranking, @@ -69,7 +71,7 @@ def _six(self, cards): return minimum - def _seven(self, cards): + def _seven(self, cards: Sequence[int]) -> int: """ Performs five_card_eval() on all (7 choose 5) = 21 subsets of 5 cards in the set of 7 to determine the best ranking, @@ -86,7 +88,7 @@ def _seven(self, cards): return minimum - def get_rank_class(self, hr): + def get_rank_class(self, hr: int) -> int: """ Returns the class of hand given the hand hand_rank returned from evaluate. @@ -114,19 +116,19 @@ def get_rank_class(self, hr): else: raise Exception("Inavlid hand rank, cannot return rank class") - def class_to_string(self, class_int): + def class_to_string(self, class_int: int) -> str: """ Converts the integer class hand score into a human-readable string. """ return LookupTable.RANK_CLASS_TO_STRING[class_int] - def get_five_card_rank_percentage(self, hand_rank): + def get_five_card_rank_percentage(self, hand_rank: int) -> float: """ Scales the hand rank score to the [0.0, 1.0] range. """ return float(hand_rank) / float(LookupTable.MAX_HIGH_CARD) - def hand_summary(self, board, hands): + def hand_summary(self, board: list[int], hands: list[list[int]]) -> None: """ Gives a sumamry of the hand with ranks as time proceeds. diff --git a/treys/lookup.py b/treys/lookup.py index 609cc33..dacd332 100644 --- a/treys/lookup.py +++ b/treys/lookup.py @@ -1,8 +1,11 @@ +from collections.abc import Iterator import itertools +from typing import Sequence + from .card import Card -class LookupTable(object): +class LookupTable: """ Number of Distinct Hand Values: @@ -25,18 +28,18 @@ class LookupTable(object): * Royal flush (best hand possible) => 1 * 7-5-4-3-2 unsuited (worst hand possible) => 7462 """ - MAX_ROYAL_FLUSH = 1 - MAX_STRAIGHT_FLUSH = 10 - MAX_FOUR_OF_A_KIND = 166 - MAX_FULL_HOUSE = 322 - MAX_FLUSH = 1599 - MAX_STRAIGHT = 1609 - MAX_THREE_OF_A_KIND = 2467 - MAX_TWO_PAIR = 3325 - MAX_PAIR = 6185 - MAX_HIGH_CARD = 7462 - - MAX_TO_RANK_CLASS = { + MAX_ROYAL_FLUSH: int = 1 + MAX_STRAIGHT_FLUSH: int = 10 + MAX_FOUR_OF_A_KIND: int = 166 + MAX_FULL_HOUSE: int = 322 + MAX_FLUSH: int = 1599 + MAX_STRAIGHT: int = 1609 + MAX_THREE_OF_A_KIND: int = 2467 + MAX_TWO_PAIR: int = 3325 + MAX_PAIR: int = 6185 + MAX_HIGH_CARD: int = 7462 + + MAX_TO_RANK_CLASS: dict[int, int] = { MAX_ROYAL_FLUSH: 0, MAX_STRAIGHT_FLUSH: 1, MAX_FOUR_OF_A_KIND: 2, @@ -49,7 +52,7 @@ class LookupTable(object): MAX_HIGH_CARD: 9 } - RANK_CLASS_TO_STRING = { + RANK_CLASS_TO_STRING: dict[int, str] = { 0: "Royal Flush", 1: "Straight Flush", 2: "Four of a Kind", @@ -62,13 +65,13 @@ class LookupTable(object): 9: "High Card" } - def __init__(self): + def __init__(self) -> None: """ Calculates lookup tables """ # create dictionaries - self.flush_lookup = {} - self.unsuited_lookup = {} + self.flush_lookup: dict[int, int] = {} + self.unsuited_lookup: dict[int, int] = {} # create the lookup table in piecewise fashion # this will call straights and high cards method, @@ -76,7 +79,7 @@ def __init__(self): self.flushes() self.multiples() - def flushes(self): + def flushes(self) -> None: """ Straight flushes and flushes. @@ -148,7 +151,7 @@ def flushes(self): # and differ only by context self.straight_and_highcards(straight_flushes, flushes) - def straight_and_highcards(self, straights, highcards): + def straight_and_highcards(self, straights: Sequence[int], highcards: Sequence[int]) -> None: """ Unique five card sets. Straights and highcards. @@ -167,7 +170,7 @@ def straight_and_highcards(self, straights, highcards): self.unsuited_lookup[prime_product] = rank rank += 1 - def multiples(self): + def multiples(self) -> None: """ Pair, Two Pair, Three of a Kind, Full House, and 4 of a Kind. """ @@ -211,9 +214,9 @@ def multiples(self): kickers.remove(r) gen = itertools.combinations(kickers, 2) - for kickers in gen: + for kickers_2combo in gen: - c1, c2 = kickers + c1, c2 = kickers_2combo product = Card.PRIMES[r]**3 * Card.PRIMES[c1] * Card.PRIMES[c2] self.unsuited_lookup[product] = rank rank += 1 @@ -221,7 +224,7 @@ def multiples(self): # 4) Two Pair rank = LookupTable.MAX_THREE_OF_A_KIND + 1 - tpgen = itertools.combinations(backwards_ranks, 2) + tpgen = itertools.combinations(tuple(backwards_ranks), 2) for tp in tpgen: pair1, pair2 = tp @@ -242,25 +245,25 @@ def multiples(self): kickers = backwards_ranks[:] kickers.remove(pairrank) - kgen = itertools.combinations(kickers, 3) + kgen = itertools.combinations(tuple(kickers), 3) - for kickers in kgen: + for kickers_3combo in kgen: - k1, k2, k3 = kickers + k1, k2, k3 = kickers_3combo product = Card.PRIMES[pairrank]**2 * Card.PRIMES[k1] \ * Card.PRIMES[k2] * Card.PRIMES[k3] self.unsuited_lookup[product] = rank rank += 1 - def write_table_to_disk(self, table, filepath): + def write_table_to_disk(self, table: dict[int, int], filepath: str) -> None: """ Writes lookup table to disk """ with open(filepath, 'w') as f: - for prime_prod, rank in table.iteritems(): + for prime_prod, rank in table.items(): f.write(str(prime_prod) + "," + str(rank) + '\n') - def get_lexographically_next_bit_sequence(self, bits): + def get_lexographically_next_bit_sequence(self, bits: int) -> Iterator[int]: """ Bit hack from here: http://www-graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation @@ -274,4 +277,4 @@ def get_lexographically_next_bit_sequence(self, bits): while True: t = (next | (next - 1)) + 1 next = t | ((((t & -t) // (next & -next)) >> 1) - 1) - yield next \ No newline at end of file + yield next From 1836be844be3c072abd861e65ef53ad783ff47a6 Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Tue, 21 Jun 2022 00:47:26 -0400 Subject: [PATCH 24/28] add plo evaluation and deck seeding --- go.py | 13 +++++---- perf.py | 6 ++--- plo_go.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++ plo_perf.py | 62 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- treys/deck.py | 7 ++--- treys/evaluator.py | 33 +++++++++++++++++------ 7 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 plo_go.py create mode 100644 plo_perf.py diff --git a/go.py b/go.py index bccf863..e47e35e 100644 --- a/go.py +++ b/go.py @@ -10,7 +10,9 @@ board = [ Card.new('2h'), Card.new('2s'), - Card.new('Jc') + Card.new('Jc'), + Card.new('As'), + Card.new('Kc') ] hand = [ Card.new('Qs'), @@ -18,13 +20,14 @@ ] # pretty print cards to console -Card.print_pretty_cards(board + hand) +Card.print_pretty_cards(board) +Card.print_pretty_cards(hand) # create an evaluator evaluator = Evaluator() # and rank your hand -rank = evaluator.evaluate(board, hand) +rank = evaluator.evaluate(hand, board) class_ = evaluator.get_rank_class(rank) print("{} {}".format(rank, evaluator.class_to_string(class_))) print() @@ -45,8 +48,8 @@ print("Player 2's cards:") Card.print_pretty_cards(player2_hand) -p1_score = evaluator.evaluate(board, player1_hand) -p2_score = evaluator.evaluate(board, player2_hand) +p1_score = evaluator.evaluate(player1_hand, board) +p2_score = evaluator.evaluate(player2_hand, board) # bin the scores into classes p1_class = evaluator.get_rank_class(p1_score) diff --git a/perf.py b/perf.py index 74c08de..f0f11af 100644 --- a/perf.py +++ b/perf.py @@ -25,7 +25,7 @@ def setup(n: int, m: int) -> tuple[list[list[int]], list[list[int]]]: boards, hands = setup(n, 5) for i in range(len(boards)): start = time.time() - evaluator.evaluate(boards[i], hands[i]) + evaluator.evaluate(hands[i], boards[i]) cumtime += (time.time() - start) avg = float(cumtime / n) @@ -39,7 +39,7 @@ def setup(n: int, m: int) -> tuple[list[list[int]], list[list[int]]]: boards, hands = setup(n, 4) for i in range(len(boards)): start = time.time() - evaluator.evaluate(boards[i], hands[i]) + evaluator.evaluate(hands[i], boards[i]) cumtime += (time.time() - start) avg = float(cumtime / n) @@ -53,7 +53,7 @@ def setup(n: int, m: int) -> tuple[list[list[int]], list[list[int]]]: boards, hands = setup(n, 3) for i in range(len(boards)): start = time.time() - evaluator.evaluate(boards[i], hands[i]) + evaluator.evaluate(hands[i], boards[i]) cumtime += (time.time() - start) avg = float(cumtime / n) diff --git a/plo_go.py b/plo_go.py new file mode 100644 index 0000000..d16e192 --- /dev/null +++ b/plo_go.py @@ -0,0 +1,66 @@ +from treys.card import Card +from treys.evaluator import PLOEvaluator +from treys.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'), + Card.new('Ah'), + Card.new('Ks') +] +hand = [ + Card.new('Qs'), + Card.new('Th'), + Card.new('9c'), + Card.new('8s') +] + +# pretty print cards to console +Card.print_pretty_cards(board) +Card.print_pretty_cards(hand) + +# create an evaluator +evaluator = PLOEvaluator() + +# and rank your hand +rank = evaluator.evaluate(hand, board) +class_ = evaluator.get_rank_class(rank) +print("{} {}".format(rank, evaluator.class_to_string(class_))) +print() + +# or for random cards or games, create a deck +print("Dealing a new hand...") +deck = Deck() +board = deck.draw(5) +player1_hand = deck.draw(4) +player2_hand = deck.draw(4) + +print("The board:") +Card.print_pretty_cards(board) + +print("Player 1's cards:") +Card.print_pretty_cards(player1_hand) + +print("Player 2's cards:") +Card.print_pretty_cards(player2_hand) + +p1_score = evaluator.evaluate(player1_hand, board) +p2_score = evaluator.evaluate(player2_hand, board) + +# bin the scores into classes +p1_class = evaluator.get_rank_class(p1_score) +p2_class = evaluator.get_rank_class(p2_score) + +# or get a human-friendly string to describe the score +print("Player 1 hand rank = {} {}".format(p1_score, evaluator.class_to_string(p1_class))) +print("Player 2 hand rank = {} {}".format(p2_score, evaluator.class_to_string(p2_class))) + +# or just a summary of the entire hand +hands = [player1_hand, player2_hand] +evaluator.hand_summary(board, hands) diff --git a/plo_perf.py b/plo_perf.py new file mode 100644 index 0000000..8ff9bb2 --- /dev/null +++ b/plo_perf.py @@ -0,0 +1,62 @@ +import time + +from treys.deck import Deck +from treys.evaluator import PLOEvaluator + + +def setup(n: int, m: int) -> tuple[list[list[int]], list[list[int]]]: + + deck = Deck() + + boards = [] + hands = [] + + for _ in range(n): + boards.append(deck.draw(m)) + hands.append(deck.draw(4)) + deck.shuffle() + + return boards, hands + + +n = 10000 +cumtime = 0.0 +evaluator = PLOEvaluator() +boards, hands = setup(n, 5) +for i in range(len(boards)): + start = time.time() + evaluator.evaluate(hands[i], boards[i]) + cumtime += (time.time() - start) + +avg = float(cumtime / n) +print("9 card evaluation:") +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) + +### + +cumtime = 0.0 +boards, hands = setup(n, 4) +for i in range(len(boards)): + start = time.time() + evaluator.evaluate(hands[i], boards[i]) + cumtime += (time.time() - start) + +avg = float(cumtime / n) +print("8 card evaluation:") +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) + +### + +cumtime = 0.0 +boards, hands = setup(n, 3) +for i in range(len(boards)): + start = time.time() + evaluator.evaluate(hands[i], boards[i]) + cumtime += (time.time() - start) + +avg = float(cumtime / n) +print("7 card evaluation:") +print("[*] Treys: Average time per evaluation: %f" % avg) +print("[*] Treys: Evaluations per second = %f" % (1.0 / avg)) diff --git a/setup.py b/setup.py index 11fc1f6..207bc4d 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='treys', - version='0.1.5', + version='0.1.6', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), author='Will Drevo, Mark Saindon, Imran Hendley', diff --git a/treys/deck.py b/treys/deck.py index 79226c7..278b75e 100644 --- a/treys/deck.py +++ b/treys/deck.py @@ -1,4 +1,4 @@ -from random import shuffle as rshuffle +from random import Random from .card import Card @@ -11,13 +11,14 @@ class Deck: """ _FULL_DECK: list[int] = [] - def __init__(self) -> None: + def __init__(self, seed: int = None) -> None: + self._random = Random(seed) self.shuffle() def shuffle(self) -> None: # and then shuffle self.cards = Deck.GetFullDeck() - rshuffle(self.cards) + self._random.shuffle(self.cards) def draw(self, n: int = 1) -> list[int]: cards = [] diff --git a/treys/evaluator.py b/treys/evaluator.py index eb163fc..df8fc2f 100644 --- a/treys/evaluator.py +++ b/treys/evaluator.py @@ -16,6 +16,9 @@ class Evaluator: all calculations are done with bit arithmetic and table lookups. """ + HAND_LENGTH = 2 + BOARD_LENGTH = 5 + def __init__(self) -> None: self.table = LookupTable() @@ -26,13 +29,13 @@ def __init__(self) -> None: 7: self._seven } - def evaluate(self, cards: list[int], board: list[int]) -> int: + def evaluate(self, hand: list[int], board: list[int]) -> int: """ This is the function that the user calls to get a hand rank. No input validation because that's cycles! """ - all_cards = cards + board + all_cards = hand + board return self.hand_size_map[len(all_cards)](all_cards) def _five(self, cards: Sequence[int]) -> int: @@ -62,8 +65,7 @@ def _six(self, cards: Sequence[int]) -> int: """ minimum = LookupTable.MAX_HIGH_CARD - all5cardcombobs = itertools.combinations(cards, 5) - for combo in all5cardcombobs: + for combo in itertools.combinations(cards, 5): score = self._five(combo) if score < minimum: @@ -79,8 +81,7 @@ def _seven(self, cards: Sequence[int]) -> int: """ minimum = LookupTable.MAX_HIGH_CARD - all5cardcombobs = itertools.combinations(cards, 5) - for combo in all5cardcombobs: + for combo in itertools.combinations(cards, 5): score = self._five(combo) if score < minimum: @@ -136,9 +137,9 @@ def hand_summary(self, board: list[int], hands: list[list[int]]) -> None: analysis to make sense. """ - assert len(board) == 5, "Invalid board length" + assert len(board) == self.BOARD_LENGTH, "Invalid board length" for hand in hands: - assert len(hand) == 2, "Invalid hand length" + assert len(hand) == self.HAND_LENGTH, "Invalid hand length" line_length = 10 stages = ["FLOP", "TURN", "RIVER"] @@ -182,3 +183,19 @@ def hand_summary(self, board: list[int], hands: list[list[int]]) -> None: print("Player {} is the winner with a {}\n".format(winners[0] + 1, hand_result)) else: print("Players {} tied for the win with a {}\n".format([x + 1 for x in winners],hand_result)) + + +class PLOEvaluator(Evaluator): + + HAND_LENGTH = 4 + + def evaluate(self, hand: list[int], board: list[int]) -> int: + minimum = LookupTable.MAX_HIGH_CARD + + for hand_combo in itertools.combinations(hand, 2): + for board_combo in itertools.combinations(board, 3): + score = Evaluator._five(self, list(board_combo) + list(hand_combo)) + if score < minimum: + minimum = score + + return minimum From ef1e8b281109e1e47f78560ba383d32cbc2fa0ee Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Tue, 21 Jun 2022 01:27:16 -0400 Subject: [PATCH 25/28] fix imports --- setup.py | 2 +- treys/__init__.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 207bc4d..1c09823 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='treys', - version='0.1.6', + version='0.1.7', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), author='Will Drevo, Mark Saindon, Imran Hendley', diff --git a/treys/__init__.py b/treys/__init__.py index 8b13789..74f4337 100644 --- a/treys/__init__.py +++ b/treys/__init__.py @@ -1 +1,3 @@ - +from .card import Card +from .deck import Deck +from .evaluator import Evaluator, PLOEvaluator From 01cb5f2ad105fa889ca20c8bf84b0320ba7049ec Mon Sep 17 00:00:00 2001 From: Imran Hendley Date: Tue, 21 Jun 2022 12:02:23 -0400 Subject: [PATCH 26/28] fix imports --- __init__.py | 1 - setup.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 6921ea0..0000000 --- a/__init__.py +++ /dev/null @@ -1 +0,0 @@ -name = "treys" diff --git a/setup.py b/setup.py index 1c09823..cac08a3 100644 --- a/setup.py +++ b/setup.py @@ -2,18 +2,18 @@ Treys: A pure Python poker hand evaluation library """ -from setuptools import setup +from setuptools import setup, find_packages setup( name='treys', - version='0.1.7', + version='0.1.8', description='treys is a pure Python poker hand evaluation library', long_description=open('README.rst').read(), author='Will Drevo, Mark Saindon, Imran Hendley', url='https://github.com/ihendley/treys', license='MIT', - packages=['treys'], + packages=find_packages(include=['treys', 'treys.*']), classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', From 2d5ce1f18fa97d0df800f21d54bf4695a1749826 Mon Sep 17 00:00:00 2001 From: Chazzz <270857+Chazzz@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:20:21 -0800 Subject: [PATCH 27/28] Add unicode support to Card.new --- treys/card.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/treys/card.py b/treys/card.py index ce25440..b66e79c 100644 --- a/treys/card.py +++ b/treys/card.py @@ -39,6 +39,10 @@ class Card: 'h': 2, # hearts 'd': 4, # diamonds 'c': 8, # clubs + '\u2660': 1, # spades (unicode) + '\u2764': 2, # hearts (unicode) + '\u2666': 4, # diamonds (unicode) + '\u2663': 8, # clubs (unicode) } INT_SUIT_TO_CHAR_SUIT: str = 'xshxdxxxc' From c856e89eab936a6e0e9f82da3ea4d675d63a43ca Mon Sep 17 00:00:00 2001 From: Kimi Heinonen Date: Wed, 8 Mar 2023 22:31:50 +0200 Subject: [PATCH 28/28] Fix 104 card deck --- treys/card.py | 1 + treys/deck.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/treys/card.py b/treys/card.py index b66e79c..7956fbf 100644 --- a/treys/card.py +++ b/treys/card.py @@ -29,6 +29,7 @@ class Card: # the basics STR_RANKS: str = '23456789TJQKA' + STR_SUITS: str = 'shdc' INT_RANKS: range = range(13) PRIMES: list[int] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] diff --git a/treys/deck.py b/treys/deck.py index 278b75e..7743fca 100644 --- a/treys/deck.py +++ b/treys/deck.py @@ -36,7 +36,7 @@ def GetFullDeck() -> list[int]: # create the standard 52 card deck for rank in Card.STR_RANKS: - for suit, _ in Card.CHAR_SUIT_TO_INT_SUIT.items(): + for suit in Card.STR_SUITS: Deck._FULL_DECK.append(Card.new(rank + suit)) return list(Deck._FULL_DECK) \ No newline at end of file