poker-cli 0.11.2__tar.gz

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.
Files changed (30) hide show
  1. poker_cli-0.11.2/LICENSE +21 -0
  2. poker_cli-0.11.2/PKG-INFO +201 -0
  3. poker_cli-0.11.2/README.md +175 -0
  4. poker_cli-0.11.2/pyproject.toml +37 -0
  5. poker_cli-0.11.2/setup.cfg +4 -0
  6. poker_cli-0.11.2/src/poker_cli/__init__.py +3 -0
  7. poker_cli-0.11.2/src/poker_cli/builtin_ranges.py +132 -0
  8. poker_cli-0.11.2/src/poker_cli/cards.py +42 -0
  9. poker_cli-0.11.2/src/poker_cli/cli.py +1250 -0
  10. poker_cli-0.11.2/src/poker_cli/coach.py +233 -0
  11. poker_cli-0.11.2/src/poker_cli/controllers.py +539 -0
  12. poker_cli-0.11.2/src/poker_cli/engine.py +281 -0
  13. poker_cli-0.11.2/src/poker_cli/evaluator.py +67 -0
  14. poker_cli-0.11.2/src/poker_cli/history.py +198 -0
  15. poker_cli-0.11.2/src/poker_cli/models.py +129 -0
  16. poker_cli-0.11.2/src/poker_cli/ranges.py +418 -0
  17. poker_cli-0.11.2/src/poker_cli/review.py +70 -0
  18. poker_cli-0.11.2/src/poker_cli/savegame.py +235 -0
  19. poker_cli-0.11.2/src/poker_cli/settings.py +178 -0
  20. poker_cli-0.11.2/src/poker_cli/strategy.py +80 -0
  21. poker_cli-0.11.2/src/poker_cli/tournament.py +427 -0
  22. poker_cli-0.11.2/src/poker_cli/tui.py +1325 -0
  23. poker_cli-0.11.2/src/poker_cli/usage.py +276 -0
  24. poker_cli-0.11.2/src/poker_cli.egg-info/PKG-INFO +201 -0
  25. poker_cli-0.11.2/src/poker_cli.egg-info/SOURCES.txt +28 -0
  26. poker_cli-0.11.2/src/poker_cli.egg-info/dependency_links.txt +1 -0
  27. poker_cli-0.11.2/src/poker_cli.egg-info/entry_points.txt +2 -0
  28. poker_cli-0.11.2/src/poker_cli.egg-info/requires.txt +1 -0
  29. poker_cli-0.11.2/src/poker_cli.egg-info/top_level.txt +1 -0
  30. poker_cli-0.11.2/tests/test_poker.py +1028 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 redearly123
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: poker-cli
3
+ Version: 0.11.2
4
+ Summary: A privacy-safe, terminal Texas Hold'em game with optional LLM opponents and hand review.
5
+ Author: redearly123
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/redearly123/terminal-poker
8
+ Project-URL: Repository, https://github.com/redearly123/terminal-poker
9
+ Project-URL: Issues, https://github.com/redearly123/terminal-poker/issues
10
+ Keywords: poker,texas-holdem,terminal,tui,codex,claude
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Games/Entertainment :: Board Games
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: rich<15,>=14.1
25
+ Dynamic: license-file
26
+
27
+ # Terminal Poker
28
+
29
+ [![CI](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml/badge.svg)](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml)
30
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://www.python.org/)
31
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
32
+
33
+ 在终端里玩一届完整的德州扑克锦标赛。默认 6-max Sit & Go,由本机
34
+ Codex / Claude Code 登录态驱动 AI 对手,并在比赛结束后复盘你的决策。
35
+
36
+ ## 核心能力
37
+
38
+ - 让 AI 当你的德扑搭子,也可以全程使用离线对手
39
+ - TUI 可视化牌桌:手牌、公共牌、筹码、动作、翻牌动画和摊牌
40
+ - 自动保存锦标赛进度,随时退出、继续
41
+ - 长期保存每手牌、公开行动、筹码变化和玩家决策
42
+ - 记录整届比赛的玩家决策,结束后由 AI 集中复盘
43
+ - 内置有明确来源的翻前范围表,支持导入自己的求解器数据
44
+ - 模型只会收到自己的底牌与公开信息,不会看到其他玩家手牌
45
+
46
+ ## 安装
47
+
48
+ 需要 Python 3.11+。推荐使用 `pipx` 安装成全局命令:
49
+
50
+ ```bash
51
+ pipx install git+https://github.com/redearly123/terminal-poker.git
52
+ poker
53
+ ```
54
+
55
+ 本地开发安装:
56
+
57
+ ```bash
58
+ git clone https://github.com/redearly123/terminal-poker.git
59
+ cd terminal-poker
60
+ python3 -m venv .venv
61
+ .venv/bin/python -m pip install -e .
62
+ .venv/bin/poker
63
+ ```
64
+
65
+ Codex / Claude Code 只在在线对手和 AI 复盘时需要。没有安装或登录,也能在
66
+ 开局菜单选择“离线混合”正常游玩。
67
+
68
+ ## 开始游戏
69
+
70
+ ```bash
71
+ poker
72
+ ```
73
+
74
+ 一个页面完成全部设置:
75
+
76
+ - `↑/↓` 或 `Tab`:移动
77
+ - `←/→`:切换选项
78
+ - `Enter`:开始
79
+ - `Esc/Q`:退出
80
+
81
+ 牌桌操作:
82
+
83
+ ```text
84
+ 1 Fold
85
+ 2 Check / Call
86
+ 3 Raise
87
+ 4 翻前范围表
88
+ 5 无法继续行动时快进本手
89
+ ```
90
+
91
+ 加注金额表示“本轮总下注到多少”。每手结算后按 `1` 继续,按 `0` 结束。
92
+
93
+ 继续最近存档、查看长期记录和模型额度:
94
+
95
+ ```bash
96
+ poker resume
97
+ poker history
98
+ poker history --limit 20
99
+ poker history path
100
+ poker usage
101
+ ```
102
+
103
+ ## AI 对手与复盘
104
+
105
+ 开局菜单可选择 Codex、Claude Code、混合在线或纯离线对手。程序复用本机
106
+ CLI 的现有登录态,不读取或复制凭据;Claude 默认使用较省额度的 Haiku。
107
+ 在线对手默认每个行动都调用模型。开局页面会记住上一次成功确认的全部设置,
108
+ 下次运行 `poker` 时自动恢复。需要更快的混合模式时,可改为只在大额跟注、
109
+ 较大底池和转河关键节点调用模型;该模式全桌每手最多调用 3 次:
110
+
111
+ ```bash
112
+ poker --online-policy key
113
+ ```
114
+
115
+ 默认在整届锦标赛结束后集中复盘,也可以改成逐手复盘或完全关闭:
116
+
117
+ ```bash
118
+ poker --review tournament
119
+ poker --review hand
120
+ poker --review none
121
+ ```
122
+
123
+ 每次模型调用只包含该对手自己的底牌、公共牌、公开行动、筹码和合法动作。
124
+ 完整引擎、牌堆及其他玩家底牌不会进入模型输入;调用失败时会自动切换到离线
125
+ 策略,不中断牌局。
126
+
127
+ ## 长期牌局记录
128
+
129
+ 默认把每一手追加到本机 JSONL 历史中,包括牌面、完整公开行动、盲注、前后
130
+ 筹码、结果和你的决策分析。对手底牌只在实际摊牌公开后记录;未亮出的牌不会
131
+ 写入文件。开局菜单可关闭,命令行也可使用 `--no-history`。
132
+
133
+ ## 翻前范围表
134
+
135
+ 牌桌内按 `4` 打开。颜色分别表示 jam、raise、call、混合频率和 fold,并
136
+ 标出当前手牌。
137
+
138
+ 内置表来自
139
+ [PokerCoaching 公开的 6-max 图表](https://pokercoaching.com/preflop-charts/)。
140
+ 完全匹配显示 `EXACT`;人数、筹码或行动树不完全匹配时显示 `REFERENCE`
141
+ 并说明偏差,不会把 100BB 现金桌参考冒充成精确短码或 ICM 解。
142
+
143
+ 导入自己的 FreeBetRange / 求解器范围:
144
+
145
+ ```bash
146
+ poker ranges example
147
+ poker ranges import ~/Downloads/my-ranges.json
148
+ poker ranges list
149
+ ```
150
+
151
+ ## 常用参数
152
+
153
+ ```bash
154
+ poker --opponent mixed # 纯离线
155
+ poker --coach codex # Codex 复盘
156
+ poker --codex-model gpt-5.4-mini
157
+ poker --claude-model sonnet # 更换 Claude 模型
158
+ poker --online-policy key # 仅关键局面在线思考
159
+ poker --blind-speed turbo # 加快升盲
160
+ poker --size compact # 调整牌桌尺寸
161
+ poker --theme classic # 更换皮肤
162
+ poker --plain # 纯文本模式
163
+ ```
164
+
165
+ 完整参数:
166
+
167
+ ```bash
168
+ poker --help
169
+ ```
170
+
171
+ ## 验证
172
+
173
+ ```bash
174
+ PYTHONPATH=src python -m unittest discover -s tests -v
175
+ ```
176
+
177
+ ## 发布
178
+
179
+ `main` 用于持续开发,不会自动覆盖 PyPI。发布新版本只需打开 GitHub
180
+ Actions 的 `Release`,点击 `Run workflow`,选择 `patch`、`minor` 或
181
+ `major`。工作流会自动修改版本、测试、提交、打 tag、创建 Release,并通过
182
+ Trusted Publishing 上传 PyPI。
183
+
184
+ 首次发布前,仓库所有者只需在 PyPI 添加一次 Trusted Publisher:
185
+
186
+ ```text
187
+ Owner: redearly123
188
+ Repository: terminal-poker
189
+ Workflow: publish.yml
190
+ Environment: pypi
191
+ ```
192
+
193
+ 已安装用户仍需主动更新:
194
+
195
+ ```bash
196
+ pipx upgrade poker-cli
197
+ # 或
198
+ python -m pip install --upgrade poker-cli
199
+ ```
200
+
201
+ MIT Licensed.
@@ -0,0 +1,175 @@
1
+ # Terminal Poker
2
+
3
+ [![CI](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml/badge.svg)](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://www.python.org/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+
7
+ 在终端里玩一届完整的德州扑克锦标赛。默认 6-max Sit & Go,由本机
8
+ Codex / Claude Code 登录态驱动 AI 对手,并在比赛结束后复盘你的决策。
9
+
10
+ ## 核心能力
11
+
12
+ - 让 AI 当你的德扑搭子,也可以全程使用离线对手
13
+ - TUI 可视化牌桌:手牌、公共牌、筹码、动作、翻牌动画和摊牌
14
+ - 自动保存锦标赛进度,随时退出、继续
15
+ - 长期保存每手牌、公开行动、筹码变化和玩家决策
16
+ - 记录整届比赛的玩家决策,结束后由 AI 集中复盘
17
+ - 内置有明确来源的翻前范围表,支持导入自己的求解器数据
18
+ - 模型只会收到自己的底牌与公开信息,不会看到其他玩家手牌
19
+
20
+ ## 安装
21
+
22
+ 需要 Python 3.11+。推荐使用 `pipx` 安装成全局命令:
23
+
24
+ ```bash
25
+ pipx install git+https://github.com/redearly123/terminal-poker.git
26
+ poker
27
+ ```
28
+
29
+ 本地开发安装:
30
+
31
+ ```bash
32
+ git clone https://github.com/redearly123/terminal-poker.git
33
+ cd terminal-poker
34
+ python3 -m venv .venv
35
+ .venv/bin/python -m pip install -e .
36
+ .venv/bin/poker
37
+ ```
38
+
39
+ Codex / Claude Code 只在在线对手和 AI 复盘时需要。没有安装或登录,也能在
40
+ 开局菜单选择“离线混合”正常游玩。
41
+
42
+ ## 开始游戏
43
+
44
+ ```bash
45
+ poker
46
+ ```
47
+
48
+ 一个页面完成全部设置:
49
+
50
+ - `↑/↓` 或 `Tab`:移动
51
+ - `←/→`:切换选项
52
+ - `Enter`:开始
53
+ - `Esc/Q`:退出
54
+
55
+ 牌桌操作:
56
+
57
+ ```text
58
+ 1 Fold
59
+ 2 Check / Call
60
+ 3 Raise
61
+ 4 翻前范围表
62
+ 5 无法继续行动时快进本手
63
+ ```
64
+
65
+ 加注金额表示“本轮总下注到多少”。每手结算后按 `1` 继续,按 `0` 结束。
66
+
67
+ 继续最近存档、查看长期记录和模型额度:
68
+
69
+ ```bash
70
+ poker resume
71
+ poker history
72
+ poker history --limit 20
73
+ poker history path
74
+ poker usage
75
+ ```
76
+
77
+ ## AI 对手与复盘
78
+
79
+ 开局菜单可选择 Codex、Claude Code、混合在线或纯离线对手。程序复用本机
80
+ CLI 的现有登录态,不读取或复制凭据;Claude 默认使用较省额度的 Haiku。
81
+ 在线对手默认每个行动都调用模型。开局页面会记住上一次成功确认的全部设置,
82
+ 下次运行 `poker` 时自动恢复。需要更快的混合模式时,可改为只在大额跟注、
83
+ 较大底池和转河关键节点调用模型;该模式全桌每手最多调用 3 次:
84
+
85
+ ```bash
86
+ poker --online-policy key
87
+ ```
88
+
89
+ 默认在整届锦标赛结束后集中复盘,也可以改成逐手复盘或完全关闭:
90
+
91
+ ```bash
92
+ poker --review tournament
93
+ poker --review hand
94
+ poker --review none
95
+ ```
96
+
97
+ 每次模型调用只包含该对手自己的底牌、公共牌、公开行动、筹码和合法动作。
98
+ 完整引擎、牌堆及其他玩家底牌不会进入模型输入;调用失败时会自动切换到离线
99
+ 策略,不中断牌局。
100
+
101
+ ## 长期牌局记录
102
+
103
+ 默认把每一手追加到本机 JSONL 历史中,包括牌面、完整公开行动、盲注、前后
104
+ 筹码、结果和你的决策分析。对手底牌只在实际摊牌公开后记录;未亮出的牌不会
105
+ 写入文件。开局菜单可关闭,命令行也可使用 `--no-history`。
106
+
107
+ ## 翻前范围表
108
+
109
+ 牌桌内按 `4` 打开。颜色分别表示 jam、raise、call、混合频率和 fold,并
110
+ 标出当前手牌。
111
+
112
+ 内置表来自
113
+ [PokerCoaching 公开的 6-max 图表](https://pokercoaching.com/preflop-charts/)。
114
+ 完全匹配显示 `EXACT`;人数、筹码或行动树不完全匹配时显示 `REFERENCE`
115
+ 并说明偏差,不会把 100BB 现金桌参考冒充成精确短码或 ICM 解。
116
+
117
+ 导入自己的 FreeBetRange / 求解器范围:
118
+
119
+ ```bash
120
+ poker ranges example
121
+ poker ranges import ~/Downloads/my-ranges.json
122
+ poker ranges list
123
+ ```
124
+
125
+ ## 常用参数
126
+
127
+ ```bash
128
+ poker --opponent mixed # 纯离线
129
+ poker --coach codex # Codex 复盘
130
+ poker --codex-model gpt-5.4-mini
131
+ poker --claude-model sonnet # 更换 Claude 模型
132
+ poker --online-policy key # 仅关键局面在线思考
133
+ poker --blind-speed turbo # 加快升盲
134
+ poker --size compact # 调整牌桌尺寸
135
+ poker --theme classic # 更换皮肤
136
+ poker --plain # 纯文本模式
137
+ ```
138
+
139
+ 完整参数:
140
+
141
+ ```bash
142
+ poker --help
143
+ ```
144
+
145
+ ## 验证
146
+
147
+ ```bash
148
+ PYTHONPATH=src python -m unittest discover -s tests -v
149
+ ```
150
+
151
+ ## 发布
152
+
153
+ `main` 用于持续开发,不会自动覆盖 PyPI。发布新版本只需打开 GitHub
154
+ Actions 的 `Release`,点击 `Run workflow`,选择 `patch`、`minor` 或
155
+ `major`。工作流会自动修改版本、测试、提交、打 tag、创建 Release,并通过
156
+ Trusted Publishing 上传 PyPI。
157
+
158
+ 首次发布前,仓库所有者只需在 PyPI 添加一次 Trusted Publisher:
159
+
160
+ ```text
161
+ Owner: redearly123
162
+ Repository: terminal-poker
163
+ Workflow: publish.yml
164
+ Environment: pypi
165
+ ```
166
+
167
+ 已安装用户仍需主动更新:
168
+
169
+ ```bash
170
+ pipx upgrade poker-cli
171
+ # 或
172
+ python -m pip install --upgrade poker-cli
173
+ ```
174
+
175
+ MIT Licensed.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "poker-cli"
7
+ version = "0.11.2"
8
+ description = "A privacy-safe, terminal Texas Hold'em game with optional LLM opponents and hand review."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "redearly123" }]
13
+ keywords = ["poker", "texas-holdem", "terminal", "tui", "codex", "claude"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Operating System :: MacOS",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Games/Entertainment :: Board Games",
25
+ ]
26
+ dependencies = ["rich>=14.1,<15"]
27
+
28
+ [project.scripts]
29
+ poker = "poker_cli.cli:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/redearly123/terminal-poker"
33
+ Repository = "https://github.com/redearly123/terminal-poker"
34
+ Issues = "https://github.com/redearly123/terminal-poker/issues"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
@@ -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
+