Yazhou Cao commited on
Commit
346f4c8
·
1 Parent(s): b372c40

Initial commit

Browse files
Files changed (7) hide show
  1. .gitignore +7 -0
  2. README.md +24 -0
  3. app.py +132 -0
  4. holdem.py +97 -0
  5. poker_functions.py +356 -0
  6. requirements.txt +4 -0
  7. simulation.py +267 -0
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /venv
2
+ /.pytest_cache/*
3
+ /.idea/*
4
+ .env
5
+ holdem_pck
6
+ holdem_calc
7
+ __pycache__/
README.md CHANGED
@@ -10,3 +10,27 @@ pinned: false
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
13
+
14
+ ## Introduction
15
+
16
+ This example is a Streamlit app that supports running OCR inference.
17
+
18
+ In particular, this app allows you to run inference with uploaded images or images from your camera.
19
+
20
+ ### Target Audience
21
+ We recommend that users have:
22
+
23
+ - Basic Python programming skills.
24
+ - A basic understanding of the Streamlit library. For more information, go [here](https://docs.streamlit.io/library/get-started/main-concepts).
25
+
26
+ ### Installation
27
+
28
+ ```
29
+ pip install -r examples/apps/ocr/requirements.txt
30
+ ```
31
+
32
+ ### Launch the App
33
+
34
+ ```
35
+ streamlit run examples/apps/ocr/app.py
36
+ ```
app.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List
3
+ import extra_streamlit_components as stx
4
+
5
+ import numpy as np
6
+ import streamlit as st
7
+ from landingai.common import Prediction
8
+ from landingai.predict import Predictor
9
+ from landingai.st_utils import render_svg, setup_page
10
+ from landingai.visualize import overlay_predictions
11
+ from PIL import Image
12
+
13
+ from holdem import run_simulation
14
+ import simulation as s
15
+
16
+ setup_page(page_title="LandingLens")
17
+
18
+ Image.MAX_IMAGE_PIXELS = None
19
+
20
+ _NUM_PLAYERS = 2
21
+ _HAND = "hand"
22
+ _FLOP = "flop"
23
+
24
+
25
+ def main():
26
+ # render_svg(Path("./examples/apps/ocr/static/LandingLens_OCR_logo.svg").read_text())
27
+ # image_file = st.camera_input("Camera View")
28
+ tab1, tab2 = st.tabs(["Your hand", "Flop"])
29
+ with tab1:
30
+ # image_file_hand = st.file_uploader("Your hand")
31
+ image_file_hand = image_file = st.camera_input("Your hand")
32
+ if image_file_hand is not None:
33
+ preds = _run_inference(image_file_hand)
34
+ if len(preds) != 2:
35
+ _show_error_message(2, preds, image_file_hand, "Your hand")
36
+ image_file_hand = None
37
+ return
38
+ st.session_state[_HAND] = preds, image_file_hand
39
+ with tab2:
40
+ # image_file_flop = st.file_uploader("Flop")
41
+ image_file_flop = image_file = st.camera_input(label="Flop")
42
+ if image_file_flop is not None:
43
+ preds = _run_inference(image_file_flop)
44
+ if len(preds) != 3:
45
+ _show_error_message(3, preds, image_file_flop, "Flop")
46
+ image_file_flop = None
47
+ return
48
+ st.session_state[_FLOP] = preds, image_file_flop
49
+
50
+ if _HAND not in st.session_state:
51
+ st.info("Please take a photo for your hand.")
52
+ return
53
+ if _FLOP not in st.session_state:
54
+ _show_predictions(*st.session_state[_HAND], "Your hand")
55
+ hand = [_convert_name(det.label_name) for det in st.session_state[_HAND][0]]
56
+ run_simulation(hand=hand)
57
+ st.info("Please take a photo for flop to continue.")
58
+ return
59
+ col1, col2 = st.columns(2)
60
+ with col1:
61
+ _show_predictions(*st.session_state[_HAND], "Your hand")
62
+ with col2:
63
+ _show_predictions(*st.session_state[_FLOP], "Flop")
64
+
65
+ hand = [_convert_name(det.label_name) for det in st.session_state[_HAND][0]]
66
+ flop = [_convert_name(det.label_name) for det in st.session_state[_FLOP][0]]
67
+ if not _validate_cards(hand, flop):
68
+ return
69
+ run_simulation(hand=hand, flop=flop)
70
+
71
+
72
+ def _validate_cards(hand, flop) -> bool:
73
+ check = hand + flop
74
+ if s.dedup(check):
75
+ st.error("There is a duplicate card. Please check the board and your hand and try again.", icon="🚨")
76
+ return False
77
+ if not s.validate_card(check):
78
+ st.error("At least one of your cards is not valid. Please try again.", icon="🚨")
79
+ return False
80
+ return True
81
+
82
+
83
+ def _convert_name(name: str) -> str:
84
+ if name.startswith("10"):
85
+ return f"T{name[2:].lower()}"
86
+ else:
87
+ return f"{name[0].upper()}{name[1:].lower()}"
88
+
89
+ # TODO Rename this here and in `main`
90
+ def _show_error_message(arg0, preds, arg2, arg3):
91
+ msg = (
92
+ f"{arg0 - len(preds)} card in your hand is not detected. Please try again with a new image."
93
+ if len(preds) < arg0
94
+ else f"More than {len(preds) - arg0} card in your hand is detected. Please try again with a new image."
95
+ )
96
+ st.error(msg)
97
+ _show_predictions(preds, arg2, arg3)
98
+
99
+
100
+ @st.cache_data
101
+ def _run_inference(image_file) -> List[Prediction]:
102
+ image = Image.open(image_file).convert("RGB")
103
+ predictor = Predictor(endpoint_id="ecab49f5-9047-4d5a-8c92-b7ad8e908324")
104
+ logging.info("Running Poker prediction")
105
+ preds = predictor.predict(image)
106
+ preds = _dedup_preds(preds)
107
+ logging.info(
108
+ f"Poker prediction done successfully against {image_file} with {len(preds)} predictions."
109
+ )
110
+ # st.write(preds)
111
+ return preds
112
+
113
+
114
+ def _show_predictions(preds, image_file, caption: str) -> None:
115
+ image = Image.open(image_file).convert("RGB")
116
+ display_image = overlay_predictions(preds, image)
117
+ st.image(
118
+ display_image,
119
+ channels="RGB",
120
+ caption=caption,
121
+ )
122
+
123
+
124
+ def _dedup_preds(preds: List[Prediction]) -> List[Prediction]:
125
+ """Deduplicate predictions by the prediction value."""
126
+ result = {p.label_name: p for p in preds}
127
+ return list(result.values())
128
+
129
+
130
+ # Run the app
131
+ if __name__ == "__main__":
132
+ main()
holdem.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from prettytable import PrettyTable
2
+ import simulation as s
3
+ import streamlit as st
4
+
5
+
6
+ def run_simulation(
7
+ hand: list[str],
8
+ flop: list[str] = None,
9
+ turn: list[str] = None,
10
+ river: list[str] = None,
11
+ sims=100000,
12
+ ) -> None:
13
+ sim = s.simulation_one_player(hand, flop, turn, river, sims)
14
+ hc_pct = s.percent(sim[1], sim[0])
15
+ hc_ratio = s.ratio(sim[1], sim[0])
16
+ pair_pct = s.percent(sim[2], sim[0])
17
+ pair_ratio = s.ratio(sim[2], sim[0])
18
+ two_pair_pct = s.percent(sim[3], sim[0])
19
+ two_pair_ratio = s.ratio(sim[3], sim[0])
20
+ three_ok_pct = s.percent(sim[4], sim[0])
21
+ three_ok_ratio = s.ratio(sim[4], sim[0])
22
+ straight_pct = s.percent(sim[5], sim[0])
23
+ straight_ratio = s.ratio(sim[5], sim[0])
24
+ flush_pct = s.percent(sim[6], sim[0])
25
+ flush_ratio = s.ratio(sim[6], sim[0])
26
+ boat_pct = s.percent(sim[7], sim[0])
27
+ boat_ratio = s.ratio(sim[7], sim[0])
28
+ quads_pct = s.percent(sim[8], sim[0])
29
+ quads_ratio = s.ratio(sim[8], sim[0])
30
+ strt_flush_pct = s.percent(sim[9], sim[0])
31
+ strt_flush_ratio = s.ratio(sim[9], sim[0])
32
+
33
+ hole_card_str = f"{hand[0]} {hand[1]}"
34
+
35
+ table = PrettyTable()
36
+ table.field_names = ["Your Hand", "Board"]
37
+ if flop is None:
38
+ flop = []
39
+ if turn is None:
40
+ turn = []
41
+ if river is None:
42
+ river = []
43
+ board_str = "".join(f"{card} " for card in (flop + turn + river))
44
+ table.add_row([hole_card_str, board_str])
45
+
46
+ odds = PrettyTable()
47
+ odds.add_column(
48
+ "Best Final Hand",
49
+ [
50
+ "High Card",
51
+ "Pair",
52
+ "Two Pair",
53
+ "Three of a Kind",
54
+ "Straight",
55
+ "Flush",
56
+ "Full House",
57
+ "Four of a Kind",
58
+ "Straight Flush",
59
+ ],
60
+ )
61
+ odds.add_column(
62
+ "% Prob",
63
+ [
64
+ hc_pct,
65
+ pair_pct,
66
+ two_pair_pct,
67
+ three_ok_pct,
68
+ straight_pct,
69
+ flush_pct,
70
+ boat_pct,
71
+ quads_pct,
72
+ strt_flush_pct,
73
+ ],
74
+ )
75
+ odds.add_column(
76
+ "Odds",
77
+ [
78
+ hc_ratio,
79
+ pair_ratio,
80
+ two_pair_ratio,
81
+ three_ok_ratio,
82
+ straight_ratio,
83
+ flush_ratio,
84
+ boat_ratio,
85
+ quads_ratio,
86
+ strt_flush_ratio,
87
+ ],
88
+ )
89
+ st.divider()
90
+ st.subheader("Odds")
91
+ st.write(table, "\n")
92
+ st.write(f"We ran your hand {sims} times. Here's the odds:\n")
93
+ st.write(odds, "\n")
94
+ st.divider()
95
+
96
+ if __name__ == "__main__":
97
+ run_simulation(["As", "Ah"], ["2s", "3s", "4s"], sims=10000)
poker_functions.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
37
+ def __getitem__(self, item):
38
+ if item == 'rank':
39
+ return self.rank
40
+ elif item == 'suit':
41
+ return self.suit
42
+ elif item == 'name':
43
+ return self.name
44
+ elif item == 'value':
45
+ return self.value
46
+
47
+
48
+ @dataclass()
49
+ class Hand:
50
+ def __init__(self, type, high_value, low_value = 0, kicker=0):
51
+ """Type = name of hand (e.g. Pair)
52
+ value = value of the hand (i.e. Straight Flush is the most valuable)
53
+ high_value = value. either the high card in straight or flush, the set in full house, the top pair in 2pair, etc
54
+ low_value = the lower pair in 2 pair or the pair in a full house
55
+ kicker = value of the kicker in the hand. Can be null
56
+ """
57
+ kicker_rank = value_rank[kicker] if kicker in card_values else 0
58
+ low_rank = value_rank[low_value] if low_value in card_values else 0
59
+ self.type = type
60
+ self.hand_value = hand_values[type]
61
+ self.kicker = kicker
62
+ self.kicker_rank = kicker_rank
63
+ self.high_value = high_value
64
+ self.high_rank = value_rank[self.high_value]
65
+ self.low_value = low_value
66
+ self.low_rank = low_rank
67
+
68
+ def __str__(self):
69
+ return f'{self.type}-{self.high_rank}'
70
+
71
+ def __getitem__(self, item):
72
+ if item in ['hand_value', 'high_value']:
73
+ return self.high_value
74
+ elif item == 'high_rank':
75
+ return self.high_rank
76
+ elif item == 'kicker':
77
+ return self.kicker
78
+ elif item == 'kicker_rank':
79
+ return self.kicker_rank
80
+ elif item == 'low_rank':
81
+ return self.low_rank
82
+ elif item == 'low_value':
83
+ return self.low_value
84
+ elif item == 'type':
85
+ return self.type
86
+
87
+
88
+ class Deck(list):
89
+ def __init__(self, deck):
90
+ self.deck = deck
91
+
92
+ def __getitem__(self, item):
93
+ return self.deck[item]
94
+
95
+ def __iter__(self):
96
+ yield from self.deck
97
+
98
+ def __len__(self):
99
+ return len(self.deck)
100
+
101
+ def deal_card(self):
102
+ """Select a random card from the deck. Return the card and the deck with the card removed"""
103
+ i = random.randint(0, len(self)-1)
104
+ card = self[i]
105
+ self.deck.pop(i)
106
+ return card, self
107
+
108
+ def update_deck(self, card):
109
+ """Remove card from deck"""
110
+ deck_names = [card.name for card in self.deck]
111
+ card_name = card.name if isinstance(card, Card) else card
112
+ deck_idx = deck_names.index(card_name)
113
+ self.deck.pop(deck_idx)
114
+
115
+
116
+ ##### USEFUL FUNCTIONS #####
117
+
118
+ def register(func):
119
+ """Add a function to the hand register"""
120
+ HAND_REGISTRY.append(func)
121
+ return func
122
+
123
+ def make_card(input_list):
124
+ """Input_list is either a list of Card objects or string Objects. If Cards, return the cards.
125
+ If string, convert to Card and return"""
126
+ if len(input_list) == 0:
127
+ return input_list
128
+ elif isinstance(input_list[0], Card):
129
+ return input_list
130
+ else:
131
+ card_list = [Card(card) for card in input_list]
132
+ return card_list
133
+
134
+
135
+ def generate_deck():
136
+ deck = []
137
+ for rank in ranks:
138
+ for suit in suits:
139
+ card_str = rank + suit
140
+ _card = Card(card_str)
141
+ deck.append(_card)
142
+ deck = Deck(deck)
143
+ return deck
144
+
145
+
146
+ ##### POKER #####
147
+ def find_multiple(hand, board, n=2):
148
+ """Is there a pair, three of a kind, four of a kind/?"""
149
+ hand = make_card(hand)
150
+ board = make_card(board)
151
+ multiple = False
152
+ multiple_hand = None
153
+ total_hand = hand + board
154
+ values = [card.value for card in total_hand]
155
+ c = Counter(values)
156
+ for value in set(values):
157
+ if c[value] == 2 and n == 2:
158
+ multiple = True
159
+ hand_type = 'pair'
160
+ high_value = value
161
+ low_value = max([value for value in values if value != high_value])
162
+ kicker = max([value for value in values if value not in [high_value, low_value]])
163
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker)
164
+ return multiple_hand
165
+ elif c[value] == 3 and n == 3:
166
+ multiple = True
167
+ hand_type = '3ok'
168
+ high_value = value
169
+ low_value = max([foo for foo in values if foo != high_value])
170
+ kicker = max([bar for bar in values if bar not in [high_value, low_value]])
171
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker)
172
+ return multiple_hand
173
+ elif c[value] == 4 and n == 4:
174
+ multiple = True
175
+ hand_type = '4ok'
176
+ high_value = value
177
+ low_value = max([value for value in values if value != high_value])
178
+ multiple_hand = Hand(hand_type, high_value, low_value=low_value)
179
+ return multiple_hand
180
+ return multiple
181
+
182
+
183
+ def evaluate_straight(values):
184
+ """Evaluates a list of card values to determine whether there are 5 consecutive values"""
185
+ straight = False
186
+ count = 0
187
+ straight_hand_values = []
188
+ sranks = [bit for bit in reversed(range(2, 15))]
189
+ sranks.append(14)
190
+ for rank in sranks:
191
+ if rank in values:
192
+ count += 1
193
+ straight_hand_values.append(rank)
194
+ if count == 5:
195
+ straight = True
196
+ return straight, straight_hand_values
197
+ else:
198
+ count = 0
199
+ straight_hand_values = []
200
+ return straight, straight_hand_values
201
+
202
+
203
+ @register
204
+ def find_straight_flush(hand, board):
205
+ """Find a straight flush in a given hand/board combination"""
206
+ hand = make_card(hand)
207
+ board = make_card(board)
208
+ straight_flush = False
209
+ flush = find_flush(hand, board)
210
+ if flush:
211
+ total_hand = hand + board
212
+ total_hand = [card for card in total_hand]
213
+ hand_suits = [card.suit for card in total_hand]
214
+ c = Counter(hand_suits)
215
+ flush_suit = c.most_common(1)[0][0]
216
+ flush_hand = [card.value for card in total_hand if card.suit == flush_suit]
217
+ straight_flush, straight_hand = evaluate_straight(flush_hand)
218
+ if straight_flush:
219
+ high_value = max(straight_hand)
220
+ hand_type = 'straight_flush'
221
+ straight_flush_hand = Hand(hand_type,high_value)
222
+ return straight_flush_hand
223
+ else:
224
+ return straight_flush
225
+ else:
226
+ return straight_flush
227
+
228
+
229
+ @register
230
+ def find_quads(hand, board):
231
+ quads = find_multiple(hand, board, n=4)
232
+ return quads
233
+
234
+
235
+ @register
236
+ def find_full_house(hand, board):
237
+ """Is there a full house?"""
238
+ hand = make_card(hand)
239
+ board = make_card(board)
240
+ boat = False
241
+ boat_hand = None
242
+ total_hand = hand + board
243
+ values = [card.value for card in total_hand]
244
+ c = Counter(values)
245
+ for value in set(values):
246
+ if c[value] == 3:
247
+ high_value = value
248
+ c.pop(value)
249
+ for value in set(values):
250
+ if c[value] > 1:
251
+ low_value = value
252
+ kicker = max([value for value in values if value != high_value and value != low_value])
253
+ boat_hand = Hand('boat', high_value, low_value=low_value, kicker=kicker)
254
+ boat = True
255
+ return boat_hand
256
+ return boat
257
+
258
+
259
+ @register
260
+ def find_flush(hand, board):
261
+ """Does any combination of 5 cards in hand or on board amount to 5 of the same suit"""
262
+ hand = make_card(hand)
263
+ board = make_card(board)
264
+ total_hand = hand + board
265
+ total_hand_suits = [card.suit for card in total_hand]
266
+ flush = False
267
+ c = Counter(total_hand_suits)
268
+ for suit in total_hand_suits:
269
+ if c[suit] >= 5:
270
+ flush = True
271
+ if flush:
272
+ flush_cards = [card for card in total_hand if card.suit == c.most_common(1)[0][0]]
273
+ high_value = max([card.value for card in flush_cards])
274
+ flush_hand = Hand('flush', high_value)
275
+ return flush_hand
276
+ else:
277
+ return flush
278
+
279
+
280
+ @register
281
+ def find_straight(hand, board):
282
+ """Find a straight in a given hand/board combination"""
283
+ hand = make_card(hand)
284
+ board = make_card(board)
285
+ straight = False
286
+ straight_hand = None
287
+ high_value = 2
288
+ 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'
289
+ total_hand = hand + board
290
+ values = [*set(card.value for card in total_hand)]
291
+ slices = len(values) - reqd_hand_size
292
+ if slices < 0:
293
+ return straight
294
+ else:
295
+ straight, straight_hand_values = evaluate_straight(values)
296
+ if straight:
297
+ hand_type = 'straight'
298
+ if 14 in straight_hand_values: # all([5,14]) does not work here so using nested ifs.
299
+ if 5 in straight_hand_values:
300
+ high_value = 5
301
+ else:
302
+ high_value = max(straight_hand_values)
303
+ straight_hand = Hand(hand_type, high_value)
304
+ return straight_hand
305
+ else:
306
+ return straight
307
+
308
+
309
+ @register
310
+ def find_trips(hand, board):
311
+ trips = find_multiple(hand, board, n=3)
312
+ return trips
313
+
314
+
315
+ @register
316
+ def find_two_pair(hand, board):
317
+ """Is there two-pair?"""
318
+ hand = make_card(hand)
319
+ board = make_card(board)
320
+ two_pair = False
321
+ # two_pair_hand = None
322
+ total_hand = hand + board
323
+ values = [card.value for card in total_hand]
324
+ c = Counter(values)
325
+ for value in values:
326
+ if c[value] > 1:
327
+ pair1 = Hand('pair', value)
328
+ c.pop(value)
329
+ for value in values:
330
+ if c[value] > 1:
331
+ pair2 = Hand('pair', value)
332
+ kicker = max([value for value in values if value != pair1.high_value and value != pair2.high_value])
333
+ two_pair_hand = Hand('2pair', max(pair1.high_value, pair2.high_value), low_value=min(pair1.high_value, pair2.high_value), kicker=kicker)
334
+ two_pair = True
335
+ return two_pair_hand
336
+ return two_pair
337
+
338
+
339
+ @register
340
+ def find_pair(hand, board):
341
+ pair = find_multiple(hand, board, n=2)
342
+ return pair
343
+
344
+
345
+ @register
346
+ def find_high_card(hand, board):
347
+ hand = make_card(hand)
348
+ board = make_card(board)
349
+ total_hand = hand + board
350
+ total_hand_values = [card.value for card in total_hand]
351
+ total_hand_values.sort()
352
+ high_value = total_hand_values[-1]
353
+ low_value = total_hand_values[-2]
354
+ kicker = total_hand_values[-3]
355
+ high_card_hand = Hand('hc', high_value,low_value=low_value, kicker=kicker)
356
+ 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