Files
holdem_poker/CLAUDE.md
T
thejayman77 479be1f6b9 Initial commit: Hold'em engine, bots, and simulation harness
Kotlin Multiplatform engine (JVM target only for now; androidTarget and
iosArm64 slot in without touching commonMain).

Core:
- HandEvaluator: single-pass 5-7 card evaluation, ~24M evals/sec. Verified
  exhaustively against published frequencies for all 2,598,960 five-card hands.
- Equity: Monte Carlo with ties split. PreflopChart ranks the 169 starting
  hands using all-in equity plus an explicit playability adjustment, so
  looseness means "plays the top N%".
- Table: no-limit betting rounds, side pots, odd-chip splits, uncalled-bet
  refunds, and incomplete (short all-in) raises that correctly do not reopen
  betting.

Bots:
- SkillLevel and PlayStyle are orthogonal axes. Skill drives decision quality
  (rollout accuracy, pot-odds discipline, position awareness, error rate);
  style drives bluffing, sandbagging, aggression, tightness.
- BotMood gives tilt that persists between hands and decays.
- OpponentModel lets Advanced/Expert exploit habitual bettors.
- MathBot emits a DecisionTrace of the numbers behind each decision, which the
  coach will later hand to an LLM to narrate. The LLM never does poker maths.

Simulator:
- 2,200-3,400 hands/sec. Deck RNG is separate from bot RNGs so rollout counts
  cannot shift the deal.
- Controlled skill-ladder test asserts the difficulty gradient is monotonic:
  73.9 / 53.9 / 27.6 / -155.4 bb/100 over 50k hands.

Assets: 52 CC0 English-pattern card faces plus generated backs.

Tests: 30 passing (evaluator, table rules, pre-flop chart).

Known open: win-rate magnitudes ~10x realistic and several profiles looser
than their labels. Tuning, not correctness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 04:36:03 -04:00

69 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Poker — Texas Hold'em with teachable AI opponents
Kotlin Multiplatform. Ships iOS + Android; Android first (only Android hardware
for physical testing).
## Build
No `java`/`gradle` on PATH — use Android Studio's bundled JDK:
```bash
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
./gradlew :engine:jvmTest # evaluator + engine tests
./gradlew :sim:run --args="50000" # simulate 50k hands, print bot stats
```
## Layout
| Path | What |
|---|---|
| `engine/src/commonMain/.../core/` | Cards, evaluator, equity, pre-flop chart |
| `engine/src/commonMain/.../bot/` | Skill/style profiles, `MathBot` |
| `engine/src/commonMain/.../game/` | `Table` — betting rounds, side pots, showdown |
| `sim/` | JVM-only headless simulator used to **tune** bot profiles |
| `assets/cards/` | 52 CC0 card faces + generated backs |
`engine` is pure Kotlin with no platform APIs, so `androidTarget()` /
`iosArm64()` slot in without touching `commonMain`.
## Design rules
1. **Poker maths never goes near the LLM.** Difficulty and style are engine-side
EV/frequency calculations — instant, deterministic, testable, offline. The LLM
only narrates numbers the engine already computed (`DecisionTrace`), and adds
persona/table talk.
2. **Skill and style are orthogonal.** `SkillLevel` = how correct decisions are;
`PlayStyle` = bluffing, sandbagging, aggression, tightness. Build the strongest
bot, then inject *controlled error* for lower tiers.
3. **Pre-flop is range-based, not equity-based.** All-in equity overvalues trash
(7-2o has ~35% vs one random hand but is unplayable). `PreflopChart` ranks the
169 starting hands so `looseness` means "plays the top N%".
4. **The simulator is how bots get tuned.** Run it after any bot change; it prints
a controlled skill-ladder test that must stay monotonic.
## Testing notes
- `Table` takes a `CardSource`, so `StackedDeck.of(holes, board)` gives fully
deterministic hands. Use it for any rule test.
- The simulator gives the **deck its own RNG**, separate from each bot's. Never
share one: bots consume RNG proportional to their `equityIterations`, so a
shared stream means changing a profile silently changes the cards dealt.
- `./gradlew :sim:run --args="chart"` dumps the starting-hand ranking.
- Small samples lie. 1,000 hands is not enough to rank profiles — use 50,000+
before believing a gradient.
## Status
- Evaluator: verified exhaustively against published frequencies for all
2,598,960 five-card hands. ~24M evals/sec.
- Engine: chip-conserving; side pots, odd-chip splits, uncalled-bet refunds, and
incomplete (short all-in) raises all covered by tests.
- Bots: skill gradient **passes** monotonically (73.9 / 53.9 / 27.6 / 155.4
bb/100 at 50k hands).
- Known-imperfect: win-rate magnitudes are still ~10x realistic, and several
profiles are looser than their labels (the Rock plays ~38% VPIP, should be
~12%). Tuning is the open work.
- Gradle emits an `archives` deprecation from the Kotlin Multiplatform plugin's
own `jvm()` target registration — upstream in Kotlin 2.2.10, not our build.
- Not built yet: LLM persona layer, opt-in coach, Compose UI.