pkcore-py 0.9.0__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 (33) hide show
  1. pkcore_py-0.9.0/.github/workflows/ci.yml +43 -0
  2. pkcore_py-0.9.0/.github/workflows/publish.yml +93 -0
  3. pkcore_py-0.9.0/.gitignore +11 -0
  4. pkcore_py-0.9.0/CHANGELOG.md +225 -0
  5. pkcore_py-0.9.0/CLAUDE.md +7 -0
  6. pkcore_py-0.9.0/Cargo.lock +1688 -0
  7. pkcore_py-0.9.0/Cargo.toml +20 -0
  8. pkcore_py-0.9.0/LICENSE-APACHE +201 -0
  9. pkcore_py-0.9.0/LICENSE-MIT +21 -0
  10. pkcore_py-0.9.0/Makefile +94 -0
  11. pkcore_py-0.9.0/PKG-INFO +907 -0
  12. pkcore_py-0.9.0/README.md +886 -0
  13. pkcore_py-0.9.0/demo.py +218 -0
  14. pkcore_py-0.9.0/docs/STACK.md +35 -0
  15. pkcore_py-0.9.0/docs/pypi-publishing-plan.md +228 -0
  16. pkcore_py-0.9.0/docs/superpowers/plans/2026-04-28-pkpy-0.0.52-upgrade.md +1742 -0
  17. pkcore_py-0.9.0/docs/superpowers/plans/2026-04-29-pkpy-pokersession-tablenocell.md +1215 -0
  18. pkcore_py-0.9.0/docs/superpowers/specs/2026-04-28-pkpy-0.0.52-upgrade-design.md +137 -0
  19. pkcore_py-0.9.0/docs/superpowers/specs/2026-04-29-pkpy-pokersession-tablenocell-design.md +319 -0
  20. pkcore_py-0.9.0/examples/calc.py +150 -0
  21. pkcore_py-0.9.0/examples/gto.py +90 -0
  22. pkcore_py-0.9.0/examples/the_hand.py +125 -0
  23. pkcore_py-0.9.0/pyproject.toml +35 -0
  24. pkcore_py-0.9.0/python/pkcore/__init__.py +143 -0
  25. pkcore_py-0.9.0/src/bot.rs +7 -0
  26. pkcore_py-0.9.0/src/hand_history.rs +7 -0
  27. pkcore_py-0.9.0/src/lib.rs +4089 -0
  28. pkcore_py-0.9.0/src/session.rs +236 -0
  29. pkcore_py-0.9.0/src/stats.rs +7 -0
  30. pkcore_py-0.9.0/src/table_no_cell.rs +231 -0
  31. pkcore_py-0.9.0/tests/test_pkcore.py +1142 -0
  32. pkcore_py-0.9.0/tests/test_session.py +174 -0
  33. pkcore_py-0.9.0/tests/test_table_no_cell.py +140 -0
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.13"
19
+
20
+ - name: Set up Rust
21
+ uses: dtolnay/rust-toolchain@stable
22
+
23
+ - name: Cache Rust build
24
+ uses: actions/cache@v4
25
+ with:
26
+ path: |
27
+ ~/.cargo/registry
28
+ ~/.cargo/git
29
+ target
30
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
31
+ restore-keys: ${{ runner.os }}-cargo-
32
+
33
+ - name: Create virtualenv
34
+ run: python -m venv .venv
35
+
36
+ - name: Install maturin and pytest
37
+ run: .venv/bin/pip install maturin pytest
38
+
39
+ - name: Build and install wheel
40
+ run: .venv/bin/maturin develop
41
+
42
+ - name: Run tests
43
+ run: .venv/bin/pytest
@@ -0,0 +1,93 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write # required for PyPI trusted publishing
10
+
11
+ jobs:
12
+ linux:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ target: [x86_64, aarch64]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: PyO3/maturin-action@v1
20
+ with:
21
+ target: ${{ matrix.target }}
22
+ args: --release --out dist --find-interpreter
23
+ sccache: "true"
24
+ manylinux: auto
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: wheels-linux-${{ matrix.target }}
28
+ path: dist
29
+
30
+ macos:
31
+ runs-on: macos-latest
32
+ strategy:
33
+ matrix:
34
+ target: [x86_64, aarch64]
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: PyO3/maturin-action@v1
38
+ with:
39
+ target: ${{ matrix.target }}
40
+ args: --release --out dist --find-interpreter
41
+ sccache: "true"
42
+ - uses: actions/upload-artifact@v4
43
+ with:
44
+ name: wheels-macos-${{ matrix.target }}
45
+ path: dist
46
+
47
+ windows:
48
+ runs-on: windows-latest
49
+ strategy:
50
+ matrix:
51
+ target: [x86_64]
52
+ steps:
53
+ - uses: actions/checkout@v4
54
+ - uses: PyO3/maturin-action@v1
55
+ with:
56
+ target: ${{ matrix.target }}
57
+ args: --release --out dist --find-interpreter
58
+ sccache: "true"
59
+ - uses: actions/upload-artifact@v4
60
+ with:
61
+ name: wheels-windows-${{ matrix.target }}
62
+ path: dist
63
+
64
+ sdist:
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: PyO3/maturin-action@v1
69
+ with:
70
+ command: sdist
71
+ args: --out dist
72
+ - uses: actions/upload-artifact@v4
73
+ with:
74
+ name: wheels-sdist
75
+ path: dist
76
+
77
+ publish:
78
+ name: Publish to PyPI
79
+ runs-on: ubuntu-latest
80
+ needs: [linux, macos, windows, sdist]
81
+ environment:
82
+ name: pypi
83
+ url: https://pypi.org/project/pkcore.py/
84
+ steps:
85
+ - uses: actions/download-artifact@v4
86
+ with:
87
+ pattern: wheels-*
88
+ merge-multiple: true
89
+ path: dist
90
+ - uses: PyO3/maturin-action@v1
91
+ with:
92
+ command: upload
93
+ args: --non-interactive --skip-existing dist/*
@@ -0,0 +1,11 @@
1
+ /target/
2
+ .venv/
3
+ *.so
4
+ __pycache__/
5
+ *.egg-info/
6
+ dist/
7
+ *.pyc
8
+ *.dylib
9
+ *.dylib.dSYM/
10
+ Cargo.lock
11
+ .claude/
@@ -0,0 +1,225 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project tracks [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ The crate version is kept in lockstep with the underlying `pkcore` dependency.
8
+
9
+ ## [Unreleased]
10
+
11
+ ### Breaking (package rename)
12
+
13
+ - Renamed the project from `pkpy` to `pkcore.py`. The PyPI distribution is now
14
+ `pkcore.py` (was `pkpython`) and the Python import is `pkcore` (was `pkpy`):
15
+
16
+ ```bash
17
+ pip install pkcore.py
18
+ ```
19
+
20
+ ```python
21
+ from pkcore import Card # was: from pkpy import Card
22
+ ```
23
+
24
+ The Rust crate is `pkcore-py` (Cargo names cannot contain a dot; the crate is
25
+ not published to crates.io). The compiled extension module is
26
+ `pkcore._pkcore` (was `pkpy._pkpy`). The repository moved to
27
+ `ImperialBower/pkcore.py`; the old GitHub URL redirects. `pkpython` on PyPI
28
+ receives no further releases.
29
+
30
+ ### Changed
31
+
32
+ - Bumped `pkcore` dependency from `0.8.0` to `0.9.0`. No code changes were
33
+ needed; the crate compiles cleanly against the new version.
34
+ - Bumped `pkpy` crate version to `0.9.0` to stay in lockstep with `pkcore`.
35
+
36
+ - Relicensed from `GPL-3.0-or-later` to `MIT OR Apache-2.0`, matching `pkcore`.
37
+ Replaced `LICENSE` with `LICENSE-MIT` and `LICENSE-APACHE`; updated
38
+ `Cargo.toml`, `pyproject.toml` classifiers, and the README badges.
39
+
40
+ ## [0.7.0] - 2026-08-22
41
+
42
+ ### Changed
43
+
44
+ - Bumped `pkcore` dependency from `0.2.1` to `0.7.0`, skipping the `0.3`, `0.4`,
45
+ `0.5` and `0.6` lines.
46
+ - Bumped `pkpy` crate version to `0.7.0` to stay in lockstep with `pkcore`.
47
+
48
+ ### Breaking (Python API)
49
+
50
+ - **`Deck.get(index)` returns `Card | None` instead of `Card`.** `pkcore` 0.7.0
51
+ made `Deck::get` return `Option<Card>` rather than indexing out of bounds, so
52
+ an index outside `0..=51` is now `None` in Python instead of an abort. Callers
53
+ that trusted the index are unaffected; callers that pass computed indices
54
+ should check for `None`.
55
+ - **`PokerSession.next_actor()` raises instead of returning `None` on a failed
56
+ street advance.** `pkcore` 0.7.0 changed `next_actor` to
57
+ `Result<Option<u8>, PKError>` (`DEFECT_019`). Previously a dry deck collapsed
58
+ to `None`, which every `while (actor := session.next_actor()) is not None`
59
+ loop reads as "hand over" — `end_hand()` then raised `ActionIsntFinished` and
60
+ the pot was stranded. Only "no streets remain" is still `None`; a real failure
61
+ now raises `ValueError`. Recover with `abort_hand()`.
62
+ - **`KuhnCfr.train(iterations)` can raise.** `pkcore` 0.7.0 changed
63
+ `KuhnCfr::train` to return `Result<(), PKError>`. pkpy previously discarded
64
+ that result, so a training error produced a silently half-trained strategy.
65
+ - **`SevenFiveBCM.from_cards` and `IndexCardMap.from_cards` raise `ValueError` on
66
+ an unsupported card count.** `pkcore` 0.6.0 replaced `Ok(Self::default())` with
67
+ `Err(PKError::InvalidCardCount)`. Code that relied on a rank-0 default for a
68
+ three-card input must catch the error instead.
69
+
70
+ ### Added
71
+
72
+ - **`PokerSession.abort_hand()` → `int`.** Abandons a hand that cannot continue,
73
+ returning each player's committed chips and resetting the table. This is the
74
+ documented escape hatch for a `Failed` step and for a raising `next_actor()`;
75
+ `end_hand()` cannot settle such a hand because there was no showdown.
76
+ - **`SessionStep` gained the `"Failed"` kind and a `SessionStep.error()`
77
+ accessor.** `pkcore` 0.7.0 added `SessionStep::Failed(PKError)` for a hand that
78
+ cannot continue (dealing or chip collection failed mid-hand). `kind()` now
79
+ returns `"Failed"` for it and `error()` returns the message as a `str`; it
80
+ returns `None` for every other kind.
81
+
82
+ ### Migration notes
83
+
84
+ Drive-a-hand loops should handle the new failure kind:
85
+
86
+ ```python
87
+ session.start_hand()
88
+ while True:
89
+ step = session.next_step()
90
+ if step.kind() == "Failed":
91
+ session.abort_hand() # NOT end_hand() — no showdown to resolve
92
+ break
93
+ if step.kind() == "HandComplete":
94
+ session.end_hand()
95
+ break
96
+ if step.kind() == "PlayerToAct":
97
+ session.apply_action(step.seat(), decide(step.seat()))
98
+ ```
99
+
100
+ Verified with `make ayce`: 228 pytest tests pass, `demo.py` and all three
101
+ examples (`the_hand.py`, `calc.py`, `gto.py`) run clean.
102
+
103
+ ---
104
+
105
+ ## [0.2.1] - 2026-07-10
106
+
107
+ ### Changed
108
+
109
+ - Bumped `pkcore` dependency from `0.2.0` to `0.2.1`.
110
+ - Bumped `pkpy` crate version to `0.2.1` to stay in lockstep with `pkcore`.
111
+
112
+ ### Security (inherited from `pkcore` 0.2.1)
113
+
114
+ `pkcore` 0.2.1 is a dependency-hygiene patch with **no public API, behavior, or
115
+ wire-format changes** — the postcard binary encoding is byte-identical, so solver
116
+ caches and hand-history data are unaffected. It carries two supply-chain fixes that
117
+ flow through to pkpy's dependency tree:
118
+
119
+ - **`crossbeam-epoch` 0.9.18 → 0.9.20 (RUSTSEC-2026-0204).** Fixes an invalid pointer
120
+ dereference in `crossbeam-epoch`'s `fmt::Pointer`/`Display` impl. Pulled in
121
+ transitively via `rayon`; a lockfile-only change.
122
+ - **`atomic-polyfill` (RUSTSEC-2023-0089) removed from the tree.** `pkcore` now builds
123
+ `postcard` with `default-features = false`, dropping the default `heapless-cas`
124
+ feature that pulled the unmaintained `atomic-polyfill` crate. It is gone from
125
+ pkpy's dependency graph after this bump.
126
+
127
+ ### Migration notes
128
+
129
+ - Public Rust API of `pkcore` is **unchanged**. No method signatures, types, or
130
+ imports moved. pkpy compiles clean against `pkcore 0.2.1` with no source changes
131
+ (verified via `cargo check` against local `pkcore 0.2.1`).
132
+ - No Python-facing behavior changes: the same bindings, method names, and return
133
+ values as under `pkcore 0.2.0`.
134
+
135
+ ---
136
+
137
+ ## [0.2.0] - 2026-07-08
138
+
139
+ > **Never published to PyPI.** This version was tagged in-tree but not released; its
140
+ > changes reach users for the first time bundled into 0.2.1. It is the first pkpy build
141
+ > on the `pkcore` 0.2.x line — pkpy migrated **directly from `pkcore` 0.0.54**, skipping
142
+ > the 0.1.x series.
143
+
144
+ ### Changed
145
+
146
+ - Bumped `pkcore` dependency from `0.0.54` to `0.2.0`, and enabled pkcore's **`store`
147
+ feature** explicitly. `pkcore` 0.2.0 moved storage/BCM and solver persistence behind
148
+ cargo features (previously always compiled in); pkpy now opts into `store` so BCM
149
+ loading and `SolverResult` save/load continue to work.
150
+ - Bumped `pkpy` crate version to `0.2.0` to stay in lockstep with `pkcore`.
151
+ - **Internal migration to pkcore 0.2.0's reorganized module tree** (the `casino` package
152
+ reorg and the `TableNoCell → Table` type rename). Import paths in the Rust binding layer
153
+ were updated (`casino::table::event` → `casino::action` / `casino::table_celled::event`,
154
+ `casino::table::seats::*` → `casino::equity::*`, `casino::table::winnings` →
155
+ `casino::winnings`, `casino::table_no_cell::TableNoCell` → `casino::table::Table`, etc.).
156
+
157
+ **No Python-facing class or method names changed.** pkpy deliberately preserves its
158
+ existing Python names — `TableNoCell`, `PlayerNoCell`, `SeatNoCell`, `SeatsNoCell`,
159
+ `TableAction`, `TableLog`, `SeatEquity`, `Seatbit`, `Winnings`, `PotWin`, and the rest —
160
+ so existing Python code imports and calls exactly the same symbols. The pkcore rename is
161
+ invisible from Python.
162
+
163
+ ### Behavior change (inherited from `pkcore` 0.2.0)
164
+
165
+ - **`DealEval(hole_cards)` is now fallible.** The constructor previously always succeeded;
166
+ it now raises a Python exception when the hole cards are invalid. This follows pkcore
167
+ 0.2.0 changing `DealEval::new` to return a `Result` as part of the panic-boundary /
168
+ error de-leak audit work. Wrap `DealEval(...)` in `try/except` if you pass unvalidated
169
+ input; the happy path is unchanged.
170
+ - Errors surfaced from the dealer/eval paths are now pkcore's own error enums (the 0.2.0
171
+ "no format-crate leak" change). pkpy still maps them to Python exceptions via the same
172
+ `to_py_err` path, so raised exception messages may differ slightly from 0.0.54.
173
+
174
+ ### Migration notes
175
+
176
+ - **No Python source changes required** for typical usage: same class names, same methods,
177
+ same imports.
178
+ - The one behavioral gotcha is `DealEval(...)` now raising on invalid hole cards instead of
179
+ being infallible.
180
+ - Card `Display` / `FromStr` string forms (e.g. `"6♠ 6♥"`) and serialized representations
181
+ are unchanged, so data produced under 0.0.54 remains readable.
182
+
183
+ ---
184
+
185
+ ## [0.0.54] - 2026-04-30
186
+
187
+ ### Changed
188
+
189
+ - Bumped `pkcore` dependency from `0.0.53` to `0.0.54`.
190
+ - Bumped `pkpy` crate version to `0.0.54` to stay in lockstep with `pkcore`.
191
+
192
+ ### Fixed (inherited from `pkcore` 0.0.54)
193
+
194
+ No pkpy code was modified for this release, but the upstream fix changes
195
+ observable behavior on one Python-exposed method, `TableNoCell.to_call()`.
196
+
197
+ - **Short-stacked big blind — call target now anchored to the configured BB.**
198
+ When the BB is all-in for less than the configured big blind (e.g. BB=100
199
+ but stack=30), `TableNoCell.to_call()` now returns the full configured BB
200
+ (`100`) instead of the amount the BB physically posted (`30`). Other
201
+ players must call the full configured amount; chip conservation is
202
+ preserved at showdown via side-pot stratification (multiway) or
203
+ uncalled-bet returns (heads-up / no second contestant at that tier).
204
+ This matches standard cardroom rules (TDA, WSOP).
205
+ - **`act_call` now degrades gracefully when the caller is short.** When a
206
+ caller cannot cover the call target, the action is converted to an
207
+ all-in for the caller's remaining stack rather than erroring on
208
+ insufficient chips. Surfaced through pkpy via `PokerSession.apply_action`.
209
+ - **`min_raise` stays anchored to the configured BB** even when the BB is
210
+ all-in for less. Prior behavior could allow under-sized raises in the
211
+ short-BB scenario.
212
+
213
+ ### Migration notes
214
+
215
+ - Public Rust API of `pkcore` is **unchanged**. No method signatures, types,
216
+ or imports moved. pkpy compiles clean against `pkcore 0.0.54` with no
217
+ source changes.
218
+ - If you have Python code that asserts specific chip math against a
219
+ short-stacked-BB scenario built on pkcore 0.0.53 semantics, those
220
+ assertions will need to be updated. The pkpy test suite does not
221
+ currently exercise this scenario, so the in-tree tests remain green.
222
+
223
+ ---
224
+
225
+ Earlier releases pre-date this changelog. See `git log` for prior history.
@@ -0,0 +1,7 @@
1
+ # pkcore.py project instructions
2
+
3
+ ## Version rule
4
+
5
+ `pkcore.py`'s version in `Cargo.toml` must always match the `pkcore` dependency
6
+ version. When `pkcore` is bumped, bump `pkcore.py`'s own `version` field to the
7
+ same value in the same change.