poker-cli 0.11.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
poker_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Terminal Texas Hold'em."""
2
+
3
+ __version__ = "0.11.2"
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ from .ranges import RangeSource, RangeSpot, parse_pio_range
4
+
5
+
6
+ SOURCE = RangeSource(
7
+ name="PokerCoaching official chart",
8
+ url="https://pokercoaching.com/preflop-charts/",
9
+ note=(
10
+ "Official public study chart. 100BB cash, no ante; RFI 2.5BB "
11
+ "(SB 3BB), IP 3-bet 3.5x, OOP 3-bet 4x."
12
+ ),
13
+ )
14
+
15
+
16
+ # Transcribed from the official public chart above. The source deliberately
17
+ # simplifies mixed solver frequencies to one implementable action per combo.
18
+ RANGES = {
19
+ "RFI_UTG": {
20
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A8s,A7s,A6s,A5s,A4s,A3s,AKo,KK,KQs,KJs,KTs,K9s,K8s,AQo,KQo,QQ,QJs,QTs,Q9s,AJo,KJo,QJo,JJ,JTs,J9s,ATo,TT,T9s,99,88,77,66"
21
+ },
22
+ "RFI_HJ": {
23
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A8s,A7s,A6s,A5s,A4s,A3s,A2s,AKo,KK,KQs,KJs,KTs,K9s,K8s,K7s,K6s,AQo,KQo,QQ,QJs,QTs,Q9s,Q8s,AJo,KJo,QJo,JJ,JTs,J9s,ATo,KTo,QTo,TT,T9s,99,98s,88,87s,77,76s,66,55"
24
+ },
25
+ "RFI_CO": {
26
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A8s,A7s,A6s,A5s,A4s,A3s,A2s,AKo,KK,KQs,KJs,KTs,K9s,K8s,K7s,K6s,K5s,K4s,K3s,AQo,KQo,QQ,QJs,QTs,Q9s,Q8s,Q7s,Q6s,AJo,KJo,QJo,JJ,JTs,J9s,J8s,ATo,KTo,QTo,JTo,TT,T9s,T8s,T7s,A9o,99,98s,97s,A8o,88,87s,77,76s,66,55,44,33"
27
+ },
28
+ "RFI_BTN": {
29
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A8s,A7s,A6s,A5s,A4s,A3s,A2s,AKo,KK,KQs,KJs,KTs,K9s,K8s,K7s,K6s,K5s,K4s,K3s,K2s,AQo,KQo,QQ,QJs,QTs,Q9s,Q8s,Q7s,Q6s,Q5s,Q4s,Q3s,AJo,KJo,QJo,JJ,JTs,J9s,J8s,J7s,J6s,J5s,J4s,ATo,KTo,QTo,JTo,TT,T9s,T8s,T7s,T6s,A9o,K9o,Q9o,J9o,T9o,99,98s,97s,96s,A8o,K8o,T8o,98o,88,87s,86s,85s,A7o,77,76s,75s,A6o,66,65s,64s,A5o,55,54s,53s,A4o,44,33,22"
30
+ },
31
+ "RFI_SB": {
32
+ "raise": "AKs,ATs,A9s,A8s,A7s,A5s,KK,KJs,KTs,K8s,K5s,K3s,K2s,AQo,QQ,QJs,QTs,Q5s,Q4s,Q3s,Q2s,AJo,KJo,JJ,JTs,J7s,J6s,J5s,J4s,T9s,T6s,T5s,K9o,Q9o,J9o,96s,A8o,K8o,T8o,98o,A7o,K7o,A6o,65s,64s,54s,53s,A4o,33,22",
33
+ "call": "AA,AQs,AJs,A6s,A4s,A3s,A2s,AKo,KQs,K9s,K7s,K6s,K4s,KQo,Q9s,Q8s,Q7s,Q6s,QJo,J9s,J8s,J3s,J2s,ATo,KTo,QTo,JTo,TT,T8s,T7s,T4s,T3s,A9o,T9o,99,98s,97s,95s,94s,Q8o,J8o,88,87s,86s,85s,84s,Q7o,J7o,T7o,97o,87o,77,76s,75s,74s,K6o,Q6o,86o,76o,66,63s,A5o,K5o,Q5o,55,K4o,44,43s,A3o,A2o",
34
+ },
35
+ "HJ_VS_UTG": {
36
+ "raise": "AA,AKs,AQs,AJs,ATs,A5s,AKo,KK,KQs,KJs,KTs,AQo,KQo,QQ,QJs,JJ,TT,99"
37
+ },
38
+ "CO_VS_UTG": {
39
+ "raise": "AA,AKs,AQs,AJs,ATs,A5s,AKo,KK,KQs,KJs,KTs,AQo,KQo,QQ,QJs,JJ,TT,99,88"
40
+ },
41
+ "CO_VS_HJ": {
42
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A5s,A4s,AKo,KK,KQs,KJs,KTs,AQo,KQo,QQ,QJs,AJo,JJ,TT,99,88"
43
+ },
44
+ "BTN_VS_UTG": {
45
+ "raise": "AA,AKs,AQs,A9s,A8s,A4s,A3s,AKo,KK,K9s,KQo,QQ,QJs,AJo,JJ,T9s",
46
+ "call": "AJs,ATs,A5s,KQs,KJs,KTs,AQo,QTs,JTs,TT,99,88,77,76s,66,65s,55,54s",
47
+ },
48
+ "BTN_VS_HJ": {
49
+ "raise": "AA,AKs,AQs,A9s,A8s,A7s,A4s,A3s,AKo,KK,KTs,K9s,K8s,KQo,QQ,QTs,Q9s,AJo,JJ,T9s,66",
50
+ "call": "AJs,ATs,A5s,KQs,KJs,AQo,QJs,JTs,TT,99,98s,88,87s,77,55,44",
51
+ },
52
+ "BTN_VS_CO": {
53
+ "raise": "AA,AKs,AQs,A8s,A7s,A6s,A4s,A3s,AKo,KK,KQs,K9s,KQo,QQ,QJs,Q9s,AJo,KJo,QJo,JJ,JTs,J9s,ATo,TT,55",
54
+ "call": "AJs,ATs,A9s,A5s,KJs,KTs,AQo,QTs,T9s,99,98s,88,77,66",
55
+ },
56
+ "SB_VS_UTG": {
57
+ "raise": "AA,AKs,AQs,AJs,ATs,A5s,AKo,KK,KQs,KJs,KTs,AQo,QQ,QJs,JJ,TT,99"
58
+ },
59
+ "SB_VS_HJ": {
60
+ "raise": "AA,AKs,AQs,AJs,ATs,A5s,AKo,KK,KQs,KJs,KTs,AQo,QQ,QJs,QTs,JJ,JTs,TT,99,88,77"
61
+ },
62
+ "SB_VS_CO": {
63
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A5s,AKo,KK,KQs,KJs,KTs,AQo,KQo,QQ,QJs,QTs,JJ,JTs,J9s,TT,T9s,99,88,77,66"
64
+ },
65
+ "SB_VS_BTN": {
66
+ "raise": "AA,AKs,AQs,AJs,ATs,A9s,A8s,A7s,A5s,A4s,AKo,KK,KQs,KJs,KTs,K9s,AQo,KQo,QQ,QJs,QTs,Q9s,AJo,KJo,JJ,JTs,J9s,TT,T9s,T8s,99,88,77,66,55"
67
+ },
68
+ "BB_VS_UTG": {
69
+ "raise": "AA,AKs,AQs,A5s,A4s,AKo,KK,KQs,KJs,QQ,QJs,JJ,JTs,65s,54s",
70
+ "call": "AJs,ATs,A9s,A8s,A7s,A6s,A3s,A2s,KTs,K9s,K8s,K7s,K6s,K5s,K4s,K3s,K2s,AQo,KQo,QTs,Q9s,Q8s,Q7s,Q6s,Q5s,AJo,KJo,QJo,J9s,J8s,ATo,JTo,TT,T9s,T8s,T7s,99,98s,97s,96s,88,87s,86s,85s,77,76s,75s,74s,66,64s,63s,55,53s,44,43s,33,32s,22",
71
+ },
72
+ "BB_VS_HJ": {
73
+ "raise": "AA,AKs,AQs,A9s,A5s,A4s,AKo,KK,KQs,KJs,KTs,K5s,QQ,QJs,QTs,JJ,JTs,TT,65s,54s",
74
+ "call": "AJs,ATs,A8s,A7s,A6s,A3s,A2s,K9s,K8s,K7s,K6s,K4s,K3s,K2s,AQo,KQo,Q9s,Q8s,Q7s,Q6s,Q5s,AJo,KJo,QJo,J9s,J8s,J7s,ATo,KTo,QTo,JTo,T9s,T8s,T7s,A9o,99,98s,97s,96s,88,87s,86s,85s,77,76s,75s,74s,66,64s,63s,55,53s,44,43s,33,22",
75
+ },
76
+ "BB_VS_CO": {
77
+ "raise": "AA,AKs,AQs,AJs,A9s,A5s,A4s,AKo,KK,KQs,KJs,KTs,AQo,QQ,QJs,QTs,Q9s,JJ,JTs,J9s,TT,T9s,99,65s,54s",
78
+ "call": "ATs,A8s,A7s,A6s,A3s,A2s,K9s,K8s,K7s,K6s,K5s,K4s,K3s,K2s,KQo,Q8s,Q7s,Q6s,Q5s,Q4s,Q3s,AJo,KJo,QJo,J8s,J7s,J6s,ATo,KTo,QTo,JTo,T8s,T7s,A9o,T9o,98s,97s,96s,A8o,88,87s,86s,85s,77,76s,75s,74s,66,64s,63s,A5o,55,53s,52s,44,43s,33,22",
79
+ },
80
+ "BB_VS_BTN": {
81
+ "raise": "AA,AKs,AQs,AJs,ATs,A6s,A5s,A4s,AKo,KK,KQs,KJs,KTs,K9s,AQo,KQo,QQ,QJs,QTs,Q9s,JJ,JTs,J9s,J8s,TT,T9s,T8s,99,98s,97s,88,87s,76s,65s,54s",
82
+ "call": "A9s,A8s,A7s,A3s,A2s,K8s,K7s,K6s,K5s,K4s,K3s,K2s,Q8s,Q7s,Q6s,Q5s,Q4s,Q3s,Q2s,AJo,KJo,QJo,J7s,J6s,J5s,J4s,J3s,J2s,ATo,KTo,QTo,JTo,T7s,T6s,T5s,T4s,T3s,T2s,A9o,K9o,Q9o,J9o,T9o,96s,95s,94s,A8o,K8o,Q8o,J8o,T8o,98o,86s,85s,84s,A7o,K7o,87o,77,75s,74s,73s,A6o,K6o,76o,66,64s,63s,62s,A5o,65o,55,53s,52s,A4o,54o,44,43s,42s,A3o,33,32s,22",
83
+ },
84
+ }
85
+
86
+
87
+ def builtin_spots() -> list[RangeSpot]:
88
+ spots: list[RangeSpot] = []
89
+ for key, expressions in RANGES.items():
90
+ if key.startswith("RFI_"):
91
+ scenario = "RFI"
92
+ position = key.removeprefix("RFI_")
93
+ opener = None
94
+ open_size = 3.0 if position == "SB" else 2.5
95
+ else:
96
+ position, opener = key.split("_VS_")
97
+ scenario = "VS_OPEN"
98
+ open_size = 2.5
99
+ actions = {
100
+ action: parse_pio_range(expression)
101
+ for action, expression in expressions.items()
102
+ }
103
+ totals = {
104
+ combo: sum(values.get(combo, 0.0) for values in actions.values())
105
+ for combo in _all_combos()
106
+ }
107
+ actions["fold"] = {
108
+ combo: 1.0 - total for combo, total in totals.items() if total < 1.0
109
+ }
110
+ spots.append(
111
+ RangeSpot(
112
+ spot_id=f"pokercoaching-6max-100bb-{key.lower()}",
113
+ source=SOURCE,
114
+ format="6-max cash · 100BB · no ante",
115
+ players=6,
116
+ stack_bb=100,
117
+ ante_bb=0,
118
+ position=position,
119
+ scenario=scenario,
120
+ opener=opener,
121
+ open_size_bb=open_size,
122
+ actions=actions,
123
+ reference_only=True,
124
+ )
125
+ )
126
+ return spots
127
+
128
+
129
+ def _all_combos() -> tuple[str, ...]:
130
+ from .ranges import ALL_COMBOS
131
+
132
+ return ALL_COMBOS
poker_cli/cards.py ADDED
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import random
5
+
6
+ RANKS = "23456789TJQKA"
7
+ SUITS = "cdhs"
8
+ SUIT_SYMBOLS = {"c": "♣", "d": "♦", "h": "♥", "s": "♠"}
9
+
10
+
11
+ @dataclass(frozen=True, order=True)
12
+ class Card:
13
+ rank: int
14
+ suit: str
15
+
16
+ @classmethod
17
+ def parse(cls, value: str) -> "Card":
18
+ value = value.strip()
19
+ if len(value) != 2 or value[0].upper() not in RANKS or value[1].lower() not in SUITS:
20
+ raise ValueError(f"invalid card: {value}")
21
+ return cls(RANKS.index(value[0].upper()) + 2, value[1].lower())
22
+
23
+ def __str__(self) -> str:
24
+ return f"{RANKS[self.rank - 2]}{SUIT_SYMBOLS[self.suit]}"
25
+
26
+ def code(self) -> str:
27
+ return f"{RANKS[self.rank - 2]}{self.suit}"
28
+
29
+
30
+ class Deck:
31
+ def __init__(self, rng: random.Random | None = None) -> None:
32
+ self._rng = rng or random.Random()
33
+ self.cards = [Card(rank, suit) for rank in range(2, 15) for suit in SUITS]
34
+ self._rng.shuffle(self.cards)
35
+
36
+ def draw(self, count: int = 1) -> list[Card]:
37
+ if count > len(self.cards):
38
+ raise ValueError("not enough cards")
39
+ result = self.cards[-count:]
40
+ del self.cards[-count:]
41
+ return result
42
+