-
Notifications
You must be signed in to change notification settings - Fork 1
/
lookup.py
281 lines (232 loc) · 9.13 KB
/
lookup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# from collections.abc import Iterator
import itertools
from typing import Sequence
from card import Card
from typing import List, Dict, Iterator
class LookupTable:
"""
Number of Distinct Hand Values:
Straight Flush 10
Four of a Kind 156 [(13 choose 2) * (2 choose 1)]
Full Houses 156 [(13 choose 2) * (2 choose 1)]
Flush 1277 [(13 choose 5) - 10 straight flushes]
Straight 10
Three of a Kind 858 [(13 choose 3) * (3 choose 1)]
Two Pair 858 [(13 choose 3) * (3 choose 2)]
One Pair 2860 [(13 choose 4) * (4 choose 1)]
High Card + 1277 [(13 choose 5) - 10 straights]
-------------------------
TOTAL 7462
Here we create a lookup table which maps:
5 card hand's unique prime product => rank in range [1, 7462]
Examples:
* Royal flush (best hand possible) => 1
* 7-5-4-3-2 unsuited (worst hand possible) => 7462
"""
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,
MAX_FULL_HOUSE: 3,
MAX_FLUSH: 4,
MAX_STRAIGHT: 5,
MAX_THREE_OF_A_KIND: 6,
MAX_TWO_PAIR: 7,
MAX_PAIR: 8,
MAX_HIGH_CARD: 9
}
RANK_CLASS_TO_STRING: Dict[int, str] = {
0: "Royal Flush",
1: "Straight Flush",
2: "Four of a Kind",
3: "Full House",
4: "Flush",
5: "Straight",
6: "Three of a Kind",
7: "Two Pair",
8: "Pair",
9: "High Card"
}
def __init__(self) -> None:
"""
Calculates lookup tables
"""
# create dictionaries
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,
# we reuse some of the bit sequences
self.flushes()
self.multiples()
def flushes(self) -> None:
"""
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 it 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 straight_and_highcards(self, straights: Sequence[int], highcards: Sequence[int]) -> None:
"""
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 multiples(self) -> None:
"""
Pair, Two Pair, Three of a Kind, Full House, and 4 of a Kind.
"""
backwards_ranks = list(range(len(Card.INT_RANKS) - 1, -1, -1))
# 1) Four of a Kind
rank = LookupTable.MAX_STRAIGHT_FLUSH + 1
# for each choice of a set of four rank
for i in backwards_ranks:
# and for each possible kicker rank
kickers = backwards_ranks[:]
kickers.remove(i)
for k in kickers:
product = Card.PRIMES[i]**4 * Card.PRIMES[k]
self.unsuited_lookup[product] = rank
rank += 1
# 2) Full House
rank = LookupTable.MAX_FOUR_OF_A_KIND + 1
# for each three of a kind
for i in backwards_ranks:
# and for each choice of pair rank
pairranks = backwards_ranks[:]
pairranks.remove(i)
for pr in pairranks:
product = Card.PRIMES[i]**3 * Card.PRIMES[pr]**2
self.unsuited_lookup[product] = rank
rank += 1
# 3) Three of a Kind
rank = LookupTable.MAX_STRAIGHT + 1
# pick three of one rank
for r in backwards_ranks:
kickers = backwards_ranks[:]
kickers.remove(r)
gen = itertools.combinations(kickers, 2)
for kickers_2combo in gen:
c1, c2 = kickers_2combo
product = Card.PRIMES[r]**3 * Card.PRIMES[c1] * Card.PRIMES[c2]
self.unsuited_lookup[product] = rank
rank += 1
# 4) Two Pair
rank = LookupTable.MAX_THREE_OF_A_KIND + 1
tpgen = itertools.combinations(tuple(backwards_ranks), 2)
for tp in tpgen:
pair1, pair2 = tp
kickers = backwards_ranks[:]
kickers.remove(pair1)
kickers.remove(pair2)
for kicker in kickers:
product = Card.PRIMES[pair1]**2 * Card.PRIMES[pair2]**2 * Card.PRIMES[kicker]
self.unsuited_lookup[product] = rank
rank += 1
# 5) Pair
rank = LookupTable.MAX_TWO_PAIR + 1
# choose a pair
for pairrank in backwards_ranks:
kickers = backwards_ranks[:]
kickers.remove(pairrank)
kgen = itertools.combinations(tuple(kickers), 3)
for kickers_3combo in kgen:
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: Dict[int, int], filepath: str) -> None:
"""
Writes lookup table to disk
"""
with open(filepath, 'w') as f:
for prime_prod, rank in table.items():
f.write(str(prime_prod) + "," + str(rank) + '\n')
def get_lexographically_next_bit_sequence(self, bits: int) -> Iterator[int]:
"""
Bit hack from here:
http://www-graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
Generator even does this in poker order rank
so no need to sort when done! Perfect.
"""
t = int((bits | (bits - 1))) + 1
next = t | ((int(((t & -t) / (bits & -bits))) >> 1) - 1)
yield next
while True:
t = (next | (next - 1)) + 1
next = t | ((((t & -t) // (next & -next)) >> 1) - 1)
yield next