dillonlaird commited on
Commit
b578b56
1 Parent(s): b6952bb

initial commit

Browse files
Files changed (5) hide show
  1. app.py +137 -0
  2. holdem.py +100 -0
  3. poker_functions.py +367 -0
  4. requirements.txt +4 -0
  5. simulation.py +267 -0
app.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from typing import List
4
+
5
+ import streamlit as st
6
+ from landingai.common import Prediction
7
+ from landingai.predict import Predictor
8
+ from landingai.st_utils import render_svg, setup_page
9
+ from landingai.visualize import overlay_predictions
10
+ from PIL import Image
11
+
12
+ import simulation as s
13
+ from holdem import run_simulation
14
+
15
+ setup_page(page_title="LandingLens Holdem Odds Calculator")
16
+
17
+ Image.MAX_IMAGE_PIXELS = None
18
+
19
+
20
+ _HAND = "hand"
21
+ _FLOP = "flop"
22
+
23
+
24
+ def main():
25
+ st.markdown("""
26
+ <h3 align="center">Holdem Odds Calculator</h3>
27
+ <p align="center">
28
+ <img width="100" height="100" src="https://github.com/landing-ai/landingai-python/raw/main/assets/avi-logo.png">
29
+ </p>
30
+ """, unsafe_allow_html=True)
31
+ tab1, tab2 = st.tabs(["Your hand", "Flop"])
32
+ with tab1:
33
+ image_file_hand = st.file_uploader("Your hand")
34
+ # image_file_hand = image_file = st.camera_input("Your hand")
35
+ if image_file_hand is not None:
36
+ preds = inference(image_file_hand)
37
+ if len(preds) != 2:
38
+ _show_error_message(2, preds, image_file_hand, "hand")
39
+ image_file_hand = None
40
+ return
41
+ st.session_state[_HAND] = preds, image_file_hand
42
+ with tab2:
43
+ image_file_flop = st.file_uploader("Flop")
44
+ # image_file_flop = image_file = st.camera_input(label="Flop")
45
+ if image_file_flop is not None:
46
+ preds = inference(image_file_flop)
47
+ if len(preds) != 3:
48
+ _show_error_message(3, preds, image_file_flop, "flop")
49
+ image_file_flop = None
50
+ return
51
+ st.session_state[_FLOP] = preds, image_file_flop
52
+
53
+ if _HAND not in st.session_state:
54
+ st.info("Please take a photo of your hand.")
55
+ return
56
+ if _FLOP not in st.session_state:
57
+ _show_predictions(*st.session_state[_HAND], "Your hand")
58
+ hand = [_convert_name(det.label_name) for det in st.session_state[_HAND][0]]
59
+ run_simulation(hand=hand)
60
+ st.info("Please take a photo of the flop to continue.")
61
+ return
62
+ col1, col2 = st.columns(2)
63
+ with col1:
64
+ _show_predictions(*st.session_state[_HAND], "Your hand")
65
+ with col2:
66
+ _show_predictions(*st.session_state[_FLOP], "Flop")
67
+
68
+ hand = [_convert_name(det.label_name) for det in st.session_state[_HAND][0]]
69
+ flop = [_convert_name(det.label_name) for det in st.session_state[_FLOP][0]]
70
+ if not _validate_cards(hand, flop):
71
+ return
72
+ run_simulation(hand=hand, flop=flop)
73
+ st.write("Interested in building more Computer Vision applications? Check out our [LandingLens](https://landing.ai/) platform and our [open source Python SDK](https://github.com/landing-ai/landingai-python)!")
74
+
75
+
76
+ def _validate_cards(hand, flop) -> bool:
77
+ check = hand + flop
78
+ if s.dedup(check):
79
+ st.error(
80
+ "There is a duplicate card. Please check the board and your hand and try again.",
81
+ icon="🚨",
82
+ )
83
+ return False
84
+ if not s.validate_card(check):
85
+ st.error(
86
+ "At least one of your cards is not valid. Please try again.", icon="🚨"
87
+ )
88
+ return False
89
+ return True
90
+
91
+
92
+ def _convert_name(name: str) -> str:
93
+ if name.startswith("10"):
94
+ return f"T{name[2:].lower()}"
95
+ else:
96
+ return f"{name[0].upper()}{name[1:].lower()}"
97
+
98
+
99
+ # TODO Rename this here and in `main`
100
+ def _show_error_message(expected_len, preds, img_file, pred_name):
101
+ msg = f"Detected {len(preds)}, expects {expected_len} cards in your {pred_name}. Please try again with a new photo."
102
+ st.error(msg)
103
+ _show_predictions(preds, img_file, pred_name)
104
+
105
+
106
+ @st.cache_data
107
+ def inference(image_file) -> List[Prediction]:
108
+ image = Image.open(image_file).convert("RGB")
109
+ predictor = Predictor(endpoint_id="2549edc1-35ad-45aa-b27e-f49e79f5922e", api_key=os.environ.get("LANDINGAI_API_KEY"))
110
+ logging.info("Running Poker prediction")
111
+ preds = predictor.predict(image)
112
+ preds = _dedup_preds(preds)
113
+ logging.info(
114
+ f"Poker prediction done successfully against {image_file} with {len(preds)} predictions."
115
+ )
116
+ return preds
117
+
118
+
119
+ def _show_predictions(preds, image_file, caption: str) -> None:
120
+ image = Image.open(image_file).convert("RGB")
121
+ display_image = overlay_predictions(preds, image)
122
+ st.image(
123
+ display_image,
124
+ channels="RGB",
125
+ caption=caption,
126
+ )
127
+
128
+
129
+ def _dedup_preds(preds: List[Prediction]) -> List[Prediction]:
130
+ """Deduplicate predictions by the prediction value."""
131
+ result = {p.label_name: p for p in preds}
132
+ return list(result.values())
133
+
134
+
135
+ # Run the app
136
+ if __name__ == "__main__":
137
+ main()
holdem.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from prettytable import PrettyTable
3
+
4
+ import simulation as s
5
+ from poker_functions import Card
6
+
7
+
8
+ def run_simulation(
9
+ hand: list[str],
10
+ flop: list[str] = None,
11
+ turn: list[str] = None,
12
+ river: list[str] = None,
13
+ sims=10000,
14
+ ) -> None:
15
+ sim = s.simulation_one_player(hand, flop, turn, river, sims=sims)
16
+ hc_pct = s.percent(sim[1], sim[0])
17
+ hc_ratio = s.ratio(sim[1], sim[0])
18
+ pair_pct = s.percent(sim[2], sim[0])
19
+ pair_ratio = s.ratio(sim[2], sim[0])
20
+ two_pair_pct = s.percent(sim[3], sim[0])
21
+ two_pair_ratio = s.ratio(sim[3], sim[0])
22
+ three_ok_pct = s.percent(sim[4], sim[0])
23
+ three_ok_ratio = s.ratio(sim[4], sim[0])
24
+ straight_pct = s.percent(sim[5], sim[0])
25
+ straight_ratio = s.ratio(sim[5], sim[0])
26
+ flush_pct = s.percent(sim[6], sim[0])
27
+ flush_ratio = s.ratio(sim[6], sim[0])
28
+ boat_pct = s.percent(sim[7], sim[0])
29
+ boat_ratio = s.ratio(sim[7], sim[0])
30
+ quads_pct = s.percent(sim[8], sim[0])
31
+ quads_ratio = s.ratio(sim[8], sim[0])
32
+ strt_flush_pct = s.percent(sim[9], sim[0])
33
+ strt_flush_ratio = s.ratio(sim[9], sim[0])
34
+
35
+ hole_card_str = f"{Card(hand[0]).pretty_name} {Card(hand[1]).pretty_name}"
36
+
37
+ table = PrettyTable()
38
+ table.field_names = ["Your Hand", "Board"]
39
+ if flop is None:
40
+ flop = []
41
+ if turn is None:
42
+ turn = []
43
+ if river is None:
44
+ river = []
45
+ board_str = "".join(f"{Card(card).pretty_name} " for card in (flop + turn + river))
46
+ table.add_row([hole_card_str, board_str])
47
+
48
+ odds = PrettyTable()
49
+ odds.add_column(
50
+ "Best Final Hand",
51
+ [
52
+ "High Card",
53
+ "Pair",
54
+ "Two Pair",
55
+ "Three of a Kind",
56
+ "Straight",
57
+ "Flush",
58
+ "Full House",
59
+ "Four of a Kind",
60
+ "Straight Flush",
61
+ ],
62
+ )
63
+ odds.add_column(
64
+ "% Prob",
65
+ [
66
+ hc_pct,
67
+ pair_pct,
68
+ two_pair_pct,
69
+ three_ok_pct,
70
+ straight_pct,
71
+ flush_pct,
72
+ boat_pct,
73
+ quads_pct,
74
+ strt_flush_pct,
75
+ ],
76
+ )
77
+ odds.add_column(
78
+ "Odds",
79
+ [
80
+ hc_ratio,
81
+ pair_ratio,
82
+ two_pair_ratio,
83
+ three_ok_ratio,
84
+ straight_ratio,
85
+ flush_ratio,
86
+ boat_ratio,
87
+ quads_ratio,
88
+ strt_flush_ratio,
89
+ ],
90
+ )
91
+ st.divider()
92
+ st.subheader("Odds")
93
+ st.write(table, "\n")
94
+ st.write(f"We ran your hand {sims} times. Here's the odds:\n")
95
+ st.write(odds, "\n")
96
+ st.divider()
97
+
98
+
99
+ if __name__ == "__main__":
100
+ run_simulation(["As", "Ah"], ["2s", "3s", "4s"], sims=4000)
poker_functions.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from collections import Counter
3
+ from dataclasses import dataclass
4
+
5
+ card_values = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
6
+ ranks = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']
7
+ rank_value = dict(zip(ranks, card_values))
8
+ value_rank = dict(zip(card_values, ranks))
9
+ suits = ['c', 'd', 'h', 's']
10
+ hand_values = {'hc': 1,
11
+ 'pair': 2,
12
+ '2pair': 3,
13
+ '3ok': 4,
14
+ 'straight': 5,
15
+ 'flush': 6,
16
+ 'boat': 7,
17
+ '4ok': 8,
18
+ 'straight_flush': 9
19
+ }
20
+
21
+ HAND_REGISTRY = []
22
+
23
+ ##### CLASSES #####
24
+
25
+ @dataclass
26
+ class Card:
27
+ def __init__(self, card_str):
28
+ self.rank = str(card_str[0])
29
+ self.suit = card_str[1]
30
+ self.name = self.rank + self.suit
31
+ self.value = rank_value[self.rank]
32
+
33
+ def __str__(self):
34
+ return self.name
35
+
36
+ @property
37
+ def pretty_name(self):
38
+ rank = '10' if self.rank == 'T' else self.rank
39
+ suit_map = {
40
+ "c": "♣",
41
+ "d": "♦",
42
+ "h": "♥",
43
+ "s": "♠",
44
+ }
45
+ return f'{rank}{suit_map[self.suit]}'
46
+
47
+
48
+ def __getitem__(self, item):
49
+ if item == 'rank':
50
+ return self.rank
51
+ elif item == 'suit':
52
+ return self.suit
53
+ elif item == 'name':
54
+ return self.name
55
+ elif item == 'value':
56
+ return self.value
57
+
58
+
59
+ @dataclass()
60
+ class Hand:
61
+ def __init__(self, type, high_value, low_value = 0, kicker=0):
62
+ """Type = name of hand (e.g. Pair)
63
+ value = value of the hand (i.e. Straight Flush is the most valuable)
64
+ high_value = value. either the high card in straight or flush, the set in full house, the top pair in 2pair, etc
65
+ low_value = the lower pair in 2 pair or the pair in a full house
66
+ kicker = value of the kicker in the hand. Can be null
67
+ """
68
+ kicker_rank = value_rank[kicker] if kicker in card_values else 0
69
+ low_rank = value_rank[low_value] if low_value in card_values else 0
70
+ self.type = type
71
+ self.hand_value = hand_values[type]
72
+ self.kicker = kicker
73
+ self.kicker_rank = kicker_rank
74
+ self.high_value = high_value
75
+ self.high_rank = value_rank[self.high_value]
76
+ self.low_value = low_value
77
+ self.low_rank = low_rank
78
+
79
+ def __str__(self):
80
+ return f'{self.type}-{self.high_rank}'
81
+
82
+ def __getitem__(self, item):
83
+ if item in ['hand_value', 'high_value']:
84
+ return self.high_value
85
+ elif item == 'high_rank':
86
+ return self.high_rank
87
+ elif item == 'kicker':
88
+ return self.kicker
89
+ elif item == 'kicker_rank':
90
+ return self.kicker_rank
91
+ elif item == 'low_rank':
92
+ return self.low_rank
93
+ elif item == 'low_value':
94
+ return self.low_value
95
+ elif item == 'type':
96
+ return self.type
97
+
98
+
99
+ class Deck(list):
100
+ def __init__(self, deck):
101
+ self.deck = deck
102
+
103
+ def __getitem__(self, item):
104
+ return self.deck[item]
105
+
106
+ def __iter__(self):
107
+ yield from self.deck
108
+
109
+ def __len__(self):
110
+ return len(self.deck)
111
+
112
+ def deal_card(self):
113
+ """Select a random card from the deck. Return the card and the deck with the card removed"""
114
+ i = random.randint(0, len(self)-1)
115
+ card = self[i]
116
+ self.deck.pop(i)
117
+ return card, self
118
+
119
+ def update_deck(self, card):
120
+ """Remove card from deck"""
121
+ deck_names = [card.name for card in self.deck]
122
+ card_name = card.name if isinstance(card, Card) else card
123
+ deck_idx = deck_names.index(card_name)
124
+ self.deck.pop(deck_idx)
125
+
126
+
127
+ ##### USEFUL FUNCTIONS #####
128
+
129
+ def register(func):
130
+ """Add a function to the hand register"""
131
+ HAND_REGISTRY.append(func)
132
+ return func
133
+
134
+ def make_card(input_list):
135
+ """Input_list is either a list of Card objects or string Objects. If Cards, return the cards.
136
+ If string, convert to Card and return"""
137
+ if len(input_list) == 0:
138
+ return input_list
139
+ elif isinstance(input_list[0], Card):
140
+ return input_list
141
+ else:
142
+ card_list = [Card(card) for card in input_list]
143
+ return card_list
144
+
145
+
146
+ def generate_deck():
147
+ deck = []
148
+ for rank in ranks:
149
+ for suit in suits:
150
+ card_str = rank + suit
151
+ _card = Card(card_str)
152
+ deck.append(_card)
153
+ deck = Deck(deck)
154
+ return deck
155
+
156
+
157
+ ##### POKER #####
158
+ def find_multiple(hand, board, n=2):
159
+ """Is there a pair, three of a kind, four of a kind/?"""
160
+ hand = make_card(hand)
161
+ board = make_card(board)
162
+ multiple = False
163
+ multiple_hand = None
164
+ total_hand = hand + board
165
+ values = [card.value for card in total_hand]
166
+ c = Counter(values)
167
+ for value in set(values):
168
+ if c[value] == 2 and n == 2:
169
+ multiple = True
170
+ hand_type = 'pair'
171
+ high_value = value
172
+ low_value = max([value for value in values if value != high_value])
173
+ kicker = max([value for value in values if value not in [high_value, low_value]])
174
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker)
175
+ return multiple_hand
176
+ elif c[value] == 3 and n == 3:
177
+ multiple = True
178
+ hand_type = '3ok'
179
+ high_value = value
180
+ low_value = max([foo for foo in values if foo != high_value])
181
+ kicker = max([bar for bar in values if bar not in [high_value, low_value]])
182
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker)
183
+ return multiple_hand
184
+ elif c[value] == 4 and n == 4:
185
+ multiple = True
186
+ hand_type = '4ok'
187
+ high_value = value
188
+ low_value = max([value for value in values if value != high_value])
189
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value)
190
+ return multiple_hand
191
+ return multiple
192
+
193
+
194
+ def evaluate_straight(values):
195
+ """Evaluates a list of card values to determine whether there are 5 consecutive values"""
196
+ straight = False
197
+ count = 0
198
+ straight_hand_values = []
199
+ sranks = [bit for bit in reversed(range(2, 15))]
200
+ sranks.append(14)
201
+ for rank in sranks:
202
+ if rank in values:
203
+ count += 1
204
+ straight_hand_values.append(rank)
205
+ if count == 5:
206
+ straight = True
207
+ return straight, straight_hand_values
208
+ else:
209
+ count = 0
210
+ straight_hand_values = []
211
+ return straight, straight_hand_values
212
+
213
+
214
+ @register
215
+ def find_straight_flush(hand, board):
216
+ """Find a straight flush in a given hand/board combination"""
217
+ hand = make_card(hand)
218
+ board = make_card(board)
219
+ straight_flush = False
220
+ flush = find_flush(hand, board)
221
+ if flush:
222
+ total_hand = hand + board
223
+ total_hand = [card for card in total_hand]
224
+ hand_suits = [card.suit for card in total_hand]
225
+ c = Counter(hand_suits)
226
+ flush_suit = c.most_common(1)[0][0]
227
+ flush_hand = [card.value for card in total_hand if card.suit == flush_suit]
228
+ straight_flush, straight_hand = evaluate_straight(flush_hand)
229
+ if straight_flush:
230
+ high_value = max(straight_hand)
231
+ hand_type = 'straight_flush'
232
+ straight_flush_hand = Hand(hand_type,high_value)
233
+ return straight_flush_hand
234
+ else:
235
+ return straight_flush
236
+ else:
237
+ return straight_flush
238
+
239
+
240
+ @register
241
+ def find_quads(hand, board):
242
+ quads = find_multiple(hand, board, n=4)
243
+ return quads
244
+
245
+
246
+ @register
247
+ def find_full_house(hand, board):
248
+ """Is there a full house?"""
249
+ hand = make_card(hand)
250
+ board = make_card(board)
251
+ boat = False
252
+ boat_hand = None
253
+ total_hand = hand + board
254
+ values = [card.value for card in total_hand]
255
+ c = Counter(values)
256
+ for value in set(values):
257
+ if c[value] == 3:
258
+ high_value = value
259
+ c.pop(value)
260
+ for value in set(values):
261
+ if c[value] > 1:
262
+ low_value = value
263
+ kicker = max([value for value in values if value != high_value and value != low_value])
264
+ boat_hand = Hand('boat', high_value, low_value=low_value, kicker=kicker)
265
+ boat = True
266
+ return boat_hand
267
+ return boat
268
+
269
+
270
+ @register
271
+ def find_flush(hand, board):
272
+ """Does any combination of 5 cards in hand or on board amount to 5 of the same suit"""
273
+ hand = make_card(hand)
274
+ board = make_card(board)
275
+ total_hand = hand + board
276
+ total_hand_suits = [card.suit for card in total_hand]
277
+ flush = False
278
+ c = Counter(total_hand_suits)
279
+ for suit in total_hand_suits:
280
+ if c[suit] >= 5:
281
+ flush = True
282
+ if flush:
283
+ flush_cards = [card for card in total_hand if card.suit == c.most_common(1)[0][0]]
284
+ high_value = max([card.value for card in flush_cards])
285
+ flush_hand = Hand('flush', high_value)
286
+ return flush_hand
287
+ else:
288
+ return flush
289
+
290
+
291
+ @register
292
+ def find_straight(hand, board):
293
+ """Find a straight in a given hand/board combination"""
294
+ hand = make_card(hand)
295
+ board = make_card(board)
296
+ straight = False
297
+ straight_hand = None
298
+ high_value = 2
299
+ reqd_hand_size = 5 # required hand size gives us some flexibility at the cost of more lines. could be more efficient if we say 'if len(values)<5'
300
+ total_hand = hand + board
301
+ values = [*set(card.value for card in total_hand)]
302
+ slices = len(values) - reqd_hand_size
303
+ if slices < 0:
304
+ return straight
305
+ else:
306
+ straight, straight_hand_values = evaluate_straight(values)
307
+ if straight:
308
+ hand_type = 'straight'
309
+ if 14 in straight_hand_values: # all([5,14]) does not work here so using nested ifs.
310
+ if 5 in straight_hand_values:
311
+ high_value = 5
312
+ else:
313
+ high_value = max(straight_hand_values)
314
+ straight_hand = Hand(hand_type, high_value)
315
+ return straight_hand
316
+ else:
317
+ return straight
318
+
319
+
320
+ @register
321
+ def find_trips(hand, board):
322
+ trips = find_multiple(hand, board, n=3)
323
+ return trips
324
+
325
+
326
+ @register
327
+ def find_two_pair(hand, board):
328
+ """Is there two-pair?"""
329
+ hand = make_card(hand)
330
+ board = make_card(board)
331
+ two_pair = False
332
+ # two_pair_hand = None
333
+ total_hand = hand + board
334
+ values = [card.value for card in total_hand]
335
+ c = Counter(values)
336
+ for value in values:
337
+ if c[value] > 1:
338
+ pair1 = Hand('pair', value)
339
+ c.pop(value)
340
+ for value in values:
341
+ if c[value] > 1:
342
+ pair2 = Hand('pair', value)
343
+ kicker = max([value for value in values if value != pair1.high_value and value != pair2.high_value])
344
+ two_pair_hand = Hand('2pair', max(pair1.high_value, pair2.high_value), low_value=min(pair1.high_value, pair2.high_value), kicker=kicker)
345
+ two_pair = True
346
+ return two_pair_hand
347
+ return two_pair
348
+
349
+
350
+ @register
351
+ def find_pair(hand, board):
352
+ pair = find_multiple(hand, board, n=2)
353
+ return pair
354
+
355
+
356
+ @register
357
+ def find_high_card(hand, board):
358
+ hand = make_card(hand)
359
+ board = make_card(board)
360
+ total_hand = hand + board
361
+ total_hand_values = [card.value for card in total_hand]
362
+ total_hand_values.sort()
363
+ high_value = total_hand_values[-1]
364
+ low_value = total_hand_values[-2]
365
+ kicker = total_hand_values[-3]
366
+ high_card_hand = Hand('hc', high_value,low_value=low_value, kicker=kicker)
367
+ return high_card_hand
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit
2
+ extra-streamlit-components
3
+ landingai
4
+ prettytable
simulation.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import poker_functions as p
2
+ from fractions import Fraction
3
+ from collections import Counter
4
+
5
+
6
+ class Player:
7
+ def __init__(self, number, cards=[]):
8
+ if len(cards) > 0:
9
+ cards = p.make_card(cards)
10
+ else:
11
+ cards = []
12
+ self.number = number
13
+ self.cards = cards
14
+ self.hand = None
15
+ self.starting_cards = None
16
+ self.wins = 0
17
+
18
+ def __str__(self):
19
+ return "player_" + str(self.number)
20
+
21
+ def dedup(board):
22
+ duplicate = False
23
+ c = Counter(board)
24
+ for card in board:
25
+ if c[card] > 1:
26
+ duplicate = True
27
+ return duplicate
28
+ return duplicate
29
+
30
+
31
+ def validate_card(check):
32
+ """Detect invalid cards in a passed collection"""
33
+ valid = True
34
+ deck = p.generate_deck()
35
+ valid_cards = [card.name for card in deck]
36
+ for card in check:
37
+ if card not in valid_cards:
38
+ valid = False
39
+ return valid
40
+ return valid
41
+
42
+
43
+ def convert_and_update(deck, cards):
44
+ if len(cards) == 0:
45
+ return deck, cards
46
+ else:
47
+ cards = p.make_card(cards)
48
+ for card in cards:
49
+ deck.update_deck(card)
50
+ return deck, cards
51
+
52
+ ##### SIMULATIONS #####
53
+ def evaluate_hand(hole_cards, flop=[], turn=[], river=[]):
54
+ board = flop + turn + river
55
+ hand = None
56
+ if len(hole_cards + board) < 5:
57
+ return hand
58
+ else:
59
+ for func in p.HAND_REGISTRY:
60
+ func = func(hole_cards, board)
61
+ if not func:
62
+ continue
63
+ else:
64
+ return func
65
+
66
+
67
+ def score_game(contestants):
68
+ # TODO make this more elegant by functionizing repeated code.
69
+ """Application will determine the highest hand, including low and kicker for each player in player_list"""
70
+ high = ['flush', 'straight', 'straight_flush']
71
+ kick = ['4ok']
72
+ hi_lo = ['boat']
73
+ hi_lo_kick = ['2pair', 'hc', '3ok', 'pair']
74
+ high_hand = max(contestants, key=lambda x: x.hand.hand_value) # contestant with highest hand
75
+ same_high_hand = [player for player in contestants if player.hand.hand_value == high_hand.hand.hand_value]
76
+ if len(same_high_hand) == 1:
77
+ same_high_hand[0].wins += 1
78
+ return contestants
79
+ elif high_hand.hand.type in high:
80
+ high_card = max(same_high_hand, key=lambda x: x.hand.high_value)
81
+ same_high_card = [player for player in same_high_hand if player.hand.high_value == high_card.hand.high_value]
82
+ if len(same_high_card) == 1:
83
+ high_card.wins += 1
84
+ return contestants
85
+ else:
86
+ return contestants
87
+ elif high_hand.hand.type in hi_lo:
88
+ over = max(same_high_hand, key=lambda x: x.hand.high_value) # Highest pair in hand
89
+ same_over = [player for player in same_high_hand if player.hand.high_value == over.hand.high_value]
90
+ if len(same_over) == 1:
91
+ over.wins += 1
92
+ return contestants
93
+ else:
94
+ under = max(same_over, key=lambda x: x.hand.low_value) # lowest pair in hand
95
+ same_under = [player for player in same_over if player.hand.low_value == under.hand.low_value]
96
+ if len(same_under) == 1:
97
+ under.wins += 1
98
+ return contestants
99
+ else:
100
+ return contestants
101
+ elif high_hand.hand.type in hi_lo_kick:
102
+ over = max(same_high_hand, key=lambda x: x.hand.high_value) # Highest pair in hand
103
+ same_over = [player for player in same_high_hand if player.hand.high_value == over.hand.high_value]
104
+ if len(same_over) == 1:
105
+ over.wins += 1
106
+ return contestants
107
+ else:
108
+ under = max(same_over, key=lambda x: x.hand.low_value) # lowest pair in hand
109
+ same_under = [player for player in same_over if player.hand.low_value == under.hand.low_value]
110
+ if len(same_under) == 1:
111
+ under.wins += 1
112
+ return contestants
113
+ else:
114
+ kicker = max(same_under, key=lambda x: x.hand.kicker)
115
+ same_kicker = [player for player in same_under if player.hand.kicker == kicker.hand.kicker]
116
+ if len(same_kicker) == 1:
117
+ kicker.wins += 1
118
+ return contestants
119
+ else:
120
+ return contestants
121
+ elif high_hand.hand.type in kick:
122
+ low_val = max(same_high_hand, key=lambda x: x.hand.low_value)
123
+ same_low_val = [player for player in same_high_hand if player.hand.low_value == low_val.hand.low_value]
124
+ if len(same_low_val) == 1:
125
+ low_val.wins += 1
126
+ return contestants
127
+ else:
128
+ return contestants
129
+
130
+
131
+ def simulation_one_player(hole, flop=None, turn=None, river=None, sims=100000):
132
+ if flop is None:
133
+ flop = []
134
+ if turn is None:
135
+ turn = []
136
+ if river is None:
137
+ river = []
138
+ full_board = 7 # number of cards required to run sim
139
+ passed_cards = len(hole) + len(flop) + len(turn) + len(river)
140
+ passed_flop = list(flop)
141
+ high_cards = 0
142
+ pairs = 0
143
+ two_pairs = 0
144
+ trips = 0
145
+ straights = 0
146
+ flushes = 0
147
+ boats = 0
148
+ quads = 0
149
+ straight_flushes = 0
150
+ invalid = 0
151
+ for i in range(sims):
152
+ deck = p.generate_deck()
153
+ deck, hole = convert_and_update(deck, hole)
154
+ deck, flop = convert_and_update(deck, flop)
155
+ deck, turn = convert_and_update(deck, turn)
156
+ deck, river = convert_and_update(deck, river)
157
+ j = full_board - passed_cards
158
+ for _ in range(j):
159
+ deal, deck = deck.deal_card()
160
+ flop.append(deal) # Adding to flop because it shouldn't matter, will revert flop back at end of loop
161
+ hand = evaluate_hand(hole, flop, turn, river)
162
+ if hand.type == '2pair':
163
+ two_pairs += 1
164
+ elif hand.type == '3ok':
165
+ trips += 1
166
+ elif hand.type == '4ok':
167
+ quads += 1
168
+ elif hand.type == 'boat':
169
+ boats += 1
170
+ elif hand.type == 'flush':
171
+ flushes += 1
172
+ elif hand.type == 'hc':
173
+ high_cards += 1
174
+ elif hand.type == 'pair':
175
+ pairs += 1
176
+ elif hand.type == 'straight':
177
+ straights += 1
178
+ elif hand.type == 'straight_flush':
179
+ straight_flushes += 1
180
+ else:
181
+ invalid += 1
182
+ i += 1
183
+ flop = list(passed_flop)
184
+ return sims, high_cards, pairs, two_pairs, trips, straights, flushes, boats, quads, straight_flushes
185
+
186
+
187
+ def simulation_multiplayer(hole_one, hole_two=[], hole_three=[], hole_four=[], hole_five=[], hole_six=[],
188
+ flop = [], turn = [], river = [], opponents=2, sims=10000):
189
+ contestant_hands = [hole_one, hole_two, hole_three, hole_four, hole_five, hole_six]
190
+ contestants = []
191
+ flop = p.make_card(flop)
192
+ turn = p.make_card(turn)
193
+ river = p.make_card(river)
194
+ passed_flop_stable = [card for card in flop]
195
+ for n in range(opponents):
196
+ player_name = 'opponent' + str(n+1)
197
+ player_name = Player(n, contestant_hands[n])
198
+ contestants.append(player_name)
199
+ i = 0
200
+ passed_board = len(flop) + len(turn) + len(river)
201
+ full_board = 5
202
+ k = full_board - passed_board
203
+ for i in range(sims):
204
+ deck = p.generate_deck()
205
+ for contestant in contestants: # TODO move assigning Player.starting_cards to init
206
+ if len(contestant.cards) == 2:
207
+ contestant.starting_cards = True
208
+ for card in contestant.cards:
209
+ deck.update_deck(card) # remove known hole cards from deck
210
+ else:
211
+ contestant.starting_cards = False
212
+ hole_cards = []
213
+ for j in range(2):
214
+ deal, deck = deck.deal_card()
215
+ hole_cards.append(deal)
216
+ contestant.cards = hole_cards # assign new hole cards if not passed
217
+ for l in range(k): # complete the board as needed
218
+ deal, deck = deck.deal_card()
219
+ flop.append(deal)
220
+ for contestant in contestants:
221
+ hand = evaluate_hand(contestant.cards, flop, turn, river)
222
+ contestant.hand = hand
223
+ # Compare hand values in contestants
224
+ contestants = score_game(contestants)
225
+ i += 1
226
+ # Revert to starting state
227
+ flop = [card for card in passed_flop_stable]
228
+ for contestant in contestants:
229
+ if contestant.starting_cards is False:
230
+ contestant.cards = []
231
+ hole_cards = []
232
+ return contestants
233
+
234
+
235
+
236
+ # TODO for single and mult: find and return most likely hand. Return number of outs and odds.
237
+
238
+ ##### MATH #####
239
+ def percent(hits, sims):
240
+ percent = round((hits / sims) * 100,0)
241
+ return percent
242
+
243
+ def ratio(hits, sims):
244
+ """Return a ratio (e.g. 3:5) for two input numbers"""
245
+ percent = round((hits / sims),2)
246
+ fraction = str(Fraction(percent).limit_denominator())
247
+ fraction = fraction.replace('/', ':')
248
+ return fraction
249
+
250
+
251
+ ##### REFERENCE #####
252
+ outs = {'1':('46:1','45:1',"22:1"),
253
+ '2':('22:1','22:1','11:1'),
254
+ '3':('15:1', '14:1', '7:1'),
255
+ '4':('11:1','10:1','5:1'),
256
+ '5':('8.5:1', '8:1','4:1'),
257
+ '6':('7:1','7:1','3:1'),
258
+ '7':('6:1','6:1','2.5:1'),
259
+ '8':('5:1','5:1','2.5:1'),
260
+ '9':('4:1','4:1','2:1'),
261
+ '10':('3.5:1','3.5:1','1.5:1'),
262
+ '11':('3.3:1','3.2:1','1.5:1'),
263
+ '12':('3:1','3:1','1.2:1'),
264
+ }
265
+
266
+
267
+ rank_value = p.rank_value