Playable Android table

The game runs on device: verified on a Pixel 10 Pro emulator (Android 17) by
installing, tapping through a hand, and confirming it advanced pre-flop to flop
with correct pot, folds, and re-offered action.

App:
- :app module on AGP 9.2.1. Note AGP 9 has built-in Kotlin support, so applying
  org.jetbrains.kotlin.android conflicts with it ("extension with name 'kotlin'
  already registered"); only android.application + kotlin.compose are applied,
  matching recipeze.
- PokerViewModel runs a continuous cash game and publishes to Compose.
- Compose table: opponents, board, pot, hero, action bar with a raise slider.

Frames are queued, not conflated. An all-in runout emits flop, turn and river
microseconds apart; pushing those into a StateFlow would collapse them and the
board would jump from empty to complete. The engine's suspending observer sends
into a Channel, a consumer paces each frame, and only then is StateFlow updated
— so backpressure paces the engine rather than the UI dropping frames. Three
tests cover this, including a characterisation test showing a conflating
StateFlow does lose the intermediate frames.

Assets:
- tools/generate_card_assets.sh rasterises the SVGs into four density buckets
  using sips, which renders SVG directly — no librsvg or ImageMagick.
- Resource names are prefixed card_ because Android resource names may not start
  with a digit (10_of_clubs would be rejected).
- CardArt.kt maps deck index to drawable via static R references, so R8 resource
  shrinking cannot strip the artwork the way getIdentifier lookups would risk.

Layout fixes found by actually looking at the running app: five opponents did
not fit a fixed-width scrolling row (Enzo was off-screen), the header collided
with the status bar clock, and the board floated against a large dead space.

Tests: 52 -> 55, green on jvmTest and testAndroidHostTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jay
2026-07-25 17:23:34 -04:00
parent 679b25e2c7
commit 950c7ceb57
234 changed files with 726 additions and 5 deletions
+7 -3
View File
@@ -9,8 +9,10 @@ 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
./gradlew :engine:jvmTest # engine tests (JVM)
./gradlew :engine:testAndroidHostTest # same suite, Android variant
./gradlew :sim:run --args="50000" # simulate 50k hands, print bot stats
./gradlew :app:assembleDebug # build the APK
```
## Layout
@@ -21,7 +23,9 @@ export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
| `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 |
| `app/` | Android app: Compose table, `PokerViewModel` |
| `assets/cards/` | 52 CC0 card faces + generated backs (**source of truth**) |
| `tools/generate_card_assets.sh` | Rasterises those SVGs into `app/.../drawable-*` |
`engine` is pure Kotlin with no platform APIs, so `androidTarget()` /
`iosArm64()` slot in without touching `commonMain`.