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.
- poker_cli-0.11.2/LICENSE +21 -0
- poker_cli-0.11.2/PKG-INFO +201 -0
- poker_cli-0.11.2/README.md +175 -0
- poker_cli-0.11.2/pyproject.toml +37 -0
- poker_cli-0.11.2/setup.cfg +4 -0
- poker_cli-0.11.2/src/poker_cli/__init__.py +3 -0
- poker_cli-0.11.2/src/poker_cli/builtin_ranges.py +132 -0
- poker_cli-0.11.2/src/poker_cli/cards.py +42 -0
- poker_cli-0.11.2/src/poker_cli/cli.py +1250 -0
- poker_cli-0.11.2/src/poker_cli/coach.py +233 -0
- poker_cli-0.11.2/src/poker_cli/controllers.py +539 -0
- poker_cli-0.11.2/src/poker_cli/engine.py +281 -0
- poker_cli-0.11.2/src/poker_cli/evaluator.py +67 -0
- poker_cli-0.11.2/src/poker_cli/history.py +198 -0
- poker_cli-0.11.2/src/poker_cli/models.py +129 -0
- poker_cli-0.11.2/src/poker_cli/ranges.py +418 -0
- poker_cli-0.11.2/src/poker_cli/review.py +70 -0
- poker_cli-0.11.2/src/poker_cli/savegame.py +235 -0
- poker_cli-0.11.2/src/poker_cli/settings.py +178 -0
- poker_cli-0.11.2/src/poker_cli/strategy.py +80 -0
- poker_cli-0.11.2/src/poker_cli/tournament.py +427 -0
- poker_cli-0.11.2/src/poker_cli/tui.py +1325 -0
- poker_cli-0.11.2/src/poker_cli/usage.py +276 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/PKG-INFO +201 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/SOURCES.txt +28 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/dependency_links.txt +1 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/entry_points.txt +2 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/requires.txt +1 -0
- poker_cli-0.11.2/src/poker_cli.egg-info/top_level.txt +1 -0
- poker_cli-0.11.2/tests/test_poker.py +1028 -0
poker_cli-0.11.2/LICENSE
ADDED
|
@@ -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
|
+
[](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml)
|
|
30
|
+
[](https://www.python.org/)
|
|
31
|
+
[](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
|
+
[](https://github.com/redearly123/terminal-poker/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/)
|
|
5
|
+
[](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,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
|
+
|