{
  "markdown": "# pktui\n\nA [ratatui](https://ratatui.rs) terminal client for the\n[pkcore](https://github.com/ImperialBower/pkcore) poker engine.\n\n`pktui` is the terminal sibling of\n[pkarena0-web](https://github.com/ImperialBower/pkarena0-web): same engine,\nsame three modes (Play / Arena / Replay), same bot roster, swap the SVG\ntable for a ratatui one.\n\n## Install / run\n\n```sh\n# from the workspace root, with ../pkcore checked out alongside ../pktui\ncargo run --release                                # default: Play / NLHE (you vs 8 bots)\ncargo run --release -- play                        # same as above, explicit\ncargo run --release -- play --variant plo          # Pot-Limit Omaha (you vs 8 bots)\ncargo run --release -- play --variant stud-hi      # 7-card Stud Hi (you vs 7 bots)\ncargo run --release -- play --variant razz         # 7-card Razz lowball (you vs 7 bots)\ncargo run --release -- arena --speed-ms 400        # all-bot NLHE\ncargo run --release -- arena --variant plo         # all-bot PLO (9 bots)\ncargo run --release -- arena --variant stud-hi     # all-bot Stud Hi (8 bots)\ncargo run --release -- arena --variant razz        # all-bot Razz (8 bots)\ncargo run --release -- replay path/to/session.yaml # step through saved hand history\ncargo run --release -- spectate                          # watch a live pkdealer table (http://localhost:50051)\ncargo run --release -- spectate --endpoint http://host:50051\n```\n\n### Variants and seat caps\n\n| Variant   | `--variant` flag | Max seats | Play table | Arena table |\n|-----------|------------------|-----------|------------|-------------|\n| NLHE      | `nlhe` (default) | 9         | 1 hero + 8 bots | 9 bots |\n| PLO       | `plo`            | 9         | 1 hero + 8 bots | 9 bots |\n| Stud Hi   | `stud-hi`        | 8         | 1 hero + 7 bots | 8 bots |\n| Razz      | `razz`           | 8         | 1 hero + 7 bots | 8 bots |\n\nStud-family variants are capped at 8 seats — pkcore's `Table::MAX_STUD_SEATS`.\nEight players need 56 cards across 7 streets and the deck holds 52, so when the\nstub runs short on 7th street the dealer turns a single face-up community card\nthat every remaining player counts as their seventh. Nine runs dry two streets\nearlier, which no community card can rescue, so the engine rejects it.\n\n### Forced-bet flags by variant\n\nHold'em-family (NLHE, PLO) uses blinds; stud-family (Stud Hi, Razz) uses\nante + bring-in + small-bet / big-bet. Pass the flags applicable to the\nvariant you picked — others are ignored.\n\n```sh\n# NLHE / PLO — blinds + chips\ncargo run --release -- play --small-blind 50 --big-blind 100 --chips 10000\ncargo run --release -- play --variant plo --small-blind 50 --big-blind 100\n\n# Stud Hi / Razz — ante / bring-in / small-bet / big-bet\ncargo run --release -- play --variant stud-hi \\\n    --ante 10 --bring-in 25 --small-bet 50 --big-bet 100 --chips 10000\ncargo run --release -- play --variant razz \\\n    --ante 10 --bring-in 25 --small-bet 50 --big-bet 100\n```\n\nIf `--small-bet` / `--big-bet` are omitted for stud-family, they fall back\nto `--small-blind` / `--big-blind` so the existing NLHE defaults still\nproduce a playable Stud / Razz table.\n\n`pktui` uses Rust edition 2024 and pins `rust-version = 1.94.1`. The\n[`Cargo.toml`](Cargo.toml) declares `pkcore` as a `crates.io` dependency but\noverrides it with `[patch.crates-io] pkcore = { path = \"../pkcore\" }` so\nlocal engine work is picked up without publishing. Comment out the patch\nsection to build against the published crate exclusively.\n\n## Modes\n\n| Mode     | Subcommand                      | Description                                          |\n|----------|---------------------------------|------------------------------------------------------|\n| Play     | `pktui play`                    | One human at seat 0; bots at the remaining seats. NLHE seats 8 bots, stud-family seats 7. |\n| Arena    | `pktui arena`                   | Bots only, watch-only. NLHE seats 9, stud-family seats 8. Use `+` / `-` to adjust pace. |\n| Replay   | `pktui replay <FILE>`           | Step through a saved `HandCollection` YAML file.     |\n| Spectate | `pktui spectate [--endpoint …]` | Read-only live viewer of a running `pkdealer` table. |\n\nAll live modes accept `--variant {nlhe,plo,stud-hi,razz}`, `--seed N`, and\n`--chips N`. Hold'em-family (NLHE, PLO) adds `--small-blind` / `--big-blind`;\nstud-family adds `--ante` / `--bring-in` / `--small-bet` / `--big-bet`.\nArena additionally accepts `--speed-ms N` (default 800).\n\n### Spectate mode\n\n`spectate` is a read-only viewer of a live\n[`pkdealer`](https://github.com/ImperialBower/pkdealer) table. It connects to\nthe dealer's gRPC `StreamEvents` endpoint and renders the table, a per-seat\nprofit/loss column, and a rolling event log — the terminal counterpart to the\nweb `pkspectator`. It needs the `pkdealer` repo checked out as a sibling\n(`../pkdealer`) so the shared protobuf crate is available. Press `space` to\nfreeze/unfreeze the display, `q` to quit. The viewer auto-reconnects if the\ndealer restarts.\n\n## Keyboard\n\n| Mode       | Key            | Action                                                          |\n|------------|----------------|-----------------------------------------------------------------|\n| Global     | `?`            | Toggle help overlay                                             |\n| Global     | `q` / `Ctrl+C` | Quit                                                            |\n| Global     | `D`            | Dump Play state to `./pktui-dump-<seed>-<phase>-<unix>.yaml`    |\n| Play       | `f`            | Fold                                                            |\n| Play       | `k`            | Check                                                           |\n| Play       | `c`            | Call                                                            |\n| Play       | `a`            | All-in                                                          |\n| Play       | `b` / `r`      | Confirm bet/raise using the current bet amount                  |\n| Play       | `Enter`        | Confirm bet/raise — or deal next hand between hands             |\n| Play       | `1` / `2` / `3`| Set bet to min / ½-pot / pot                                    |\n| Play       | digits         | Type bet amount digit-by-digit                                  |\n| Play       | `+` / `-`      | Bump bet amount by 50                                           |\n| Play       | `Backspace`    | Delete last digit of bet amount                                 |\n| Arena      | `+` / `-`      | Faster / slower bots (100 ms steps)                             |\n| Replay     | `n` / `→`      | Next street                                                     |\n| Replay     | `p` / `←`      | Previous street                                                 |\n| Replay     | `N` / `Enter`  | Next hand                                                       |\n| Replay     | `P`            | Previous hand                                                   |\n| Spectate   | `space`        | Freeze / unfreeze the display                                   |\n\n## Config file\n\nOn first save, `pktui` writes\n`$XDG_CONFIG_HOME/pktui/config.toml` (typically\n`~/.config/pktui/config.toml` on Linux/macOS,\n`%APPDATA%\\pktui\\config.toml` on Windows):\n\n```toml\nsmall_blind = 50\nbig_blind = 100\nchips = 10000\narena_speed_ms = 800\nplay_speed_ms = 600\n```\n\nAnything on the command line overrides the config.\n\n## Architecture\n\n`pktui` follows an Elm-style Model / Message / Update loop:\n\n```text\ncrossterm event ──► Event ──► event_to_msg ──► Msg ──► update(app, msg) ──► App\n                                                                            │\n                                                                            ▼\n                                                                       ui::view ──► ratatui Frame\n```\n\n* [`src/app.rs`](src/app.rs) — the `App` model, plus the `AppMode` enum that\n  dispatches to per-mode state.\n* [`src/modes/`](src/modes/) — `PlayState`, `ArenaState`, `ReplayState`.\n* [`src/update.rs`](src/update.rs) — `Msg` enum and the `update` reducer.\n* [`src/event.rs`](src/event.rs) — crossterm polling with tick timer for bot\n  pacing.\n* [`src/ui/`](src/ui/) — render functions (`table`, `action_bar`, `log_view`,\n  `help`, `replay_view`).\n* [`src/main.rs`](src/main.rs) — thin entry point: parse CLI, init terminal,\n  run loop, restore terminal on exit.\n\nThe engine boundary is small: pktui reads `session.table` for rendering,\ncalls `session.next_step()` to advance, and calls `session.apply_action(seat,\nPlayerAction)` for both bot and human decisions.\n\n## Development\n\n```sh\ncargo build                  # build the binary + library\ncargo test                   # unit + integration tests\ncargo test --doc             # doc tests (CLAUDE.md requires one per public fn)\nmake ayce                    # full pipeline: fmt + test + clippy + deny + docs\n```\n\nCLAUDE.md in the repo root captures the coding conventions (every public\nfunction has a unit test and a doc test, `unwrap`/`expect`/`panic` are\nforbidden outside tests, etc.). New code should follow them.\n\n## Licence\n\nDual-licensed under MIT OR Apache-2.0 (your choice), matching the upstream\n`pkcore` engine.\n",
  "bytes": 9217,
  "sha": "8c3b6e1ffee85aa7c26fe2fd719215869b7330140c2c3e493efe39c445dffad5",
  "repo_slug": "imperialbower/pktui",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_imperialbower_pktui_okf_index_md_853f6495/readme"
}