Scoundrel Solitaire (Native Android - Kotlin + Jetpack Compose)
  • Kotlin 98.7%
  • Just 1.3%
Find a file
PatillaCode dfc938b300
All checks were successful
Release / build (push) Successful in 2m45s
Add Tolkien, Steampunk, Americana collections + theme stepper (#5)
Co-authored-by: PatillaCode <patillacode@gmail.com>
Co-committed-by: PatillaCode <patillacode@gmail.com>
2026-09-04 16:14:36 +02:00
.forgejo/workflows Fix CI: use absolute path for the decoded keystore 2026-08-30 16:42:36 +02:00
.idea/codeStyles Fix package mismatch: MainActivity was unreachable at runtime 2026-08-30 10:58:23 +02:00
app Add Tolkien, Steampunk, Americana collections + theme stepper (#5) 2026-09-04 16:14:36 +02:00
docs Replace launcher icon with the Scoundrel Solitaire crest art 2026-08-30 17:10:45 +02:00
engine full-collections-revamp (#1) 2026-08-31 23:14:01 +02:00
gradle Fix language switching, trim HP display, cap rules overlay height 2026-08-30 15:43:25 +02:00
.gitignore gitignore 2026-08-30 16:27:51 +02:00
build.gradle.kts Scaffold Gradle skeleton: :engine port + :app placeholder 2026-08-30 10:32:32 +02:00
CLAUDE.md Add Tolkien, Steampunk, Americana collections + theme stepper (#5) 2026-09-04 16:14:36 +02:00
gradle.properties Add Toy Box, Wild West, and Greek Myth collections (#2) 2026-09-01 13:06:55 +02:00
gradlew Scaffold Gradle skeleton: :engine port + :app placeholder 2026-08-30 10:32:32 +02:00
gradlew.bat Scaffold Gradle skeleton: :engine port + :app placeholder 2026-08-30 10:32:32 +02:00
justfile tweaks: sounds, rules, tutorial (#4) 2026-09-02 17:28:58 +02:00
README.md Clarify Obtainium override-source step is required, not optional 2026-08-30 17:01:44 +02:00
settings.gradle.kts Scaffold Gradle skeleton: :engine port + :app placeholder 2026-08-30 10:32:32 +02:00

scoundrel-android

Native Android port of Scoundrel (Kotlin + Jetpack Compose). Same rules and art, reimagined for touch: drag a monster left to fight bare-handed, right to strike with your weapon, watch the damage preview live under your thumb.

Self-hosted distribution only — no Play Store, no Google Play Services.

Status

Feature-complete: engine, gestures/haptics, collections/themes, i18n (en/es), stats, sound effects, launcher icon, and signed CI releases. Daily challenge is deliberately deferred. See the implementation plan for phase-by-phase detail.

Commands

just test       # ./gradlew :engine:test — pure Kotlin/JVM, no Android SDK needed
just check      # test + lint
just dev        # install debug build on a connected device
just sync-art   # copy PNGs from ../scoundrel/src/collections (sibling checkout required)
just build      # assemble debug APK
just release X.Y.Z

Structure

  • engine/ — pure Kotlin/JVM game rules (Deck.kt, Rng.kt, Engine.kt), zero Android imports. EngineTest.kt is a direct port of the web repo's engine.test.ts, plus seed-determinism tests (newGame(seed) makes a run reproducible; avoidRoom reshuffles using the same Rng carried in GameState, not a global).
  • app/ — Jetpack Compose UI. Single Activity, one GameViewModel, no DI, no navigation library.

Requirements

Android Studio (Otter 3 Feature Drop or later — AGP 9's built-in Kotlin support needs it), SDK Platform 37 (compileSdk; Compose 1.12 requires it — targetSdk stays 36, see app/build.gradle.kts), an x86_64 emulator image or a physical device. See the implementation plan for full environment setup.

Releasing

Distribution is a signed APK attached to a Forgejo release — no Play Store, no Google Play Services.

just release 0.1.0   # tags v0.1.0 and pushes; release.yml does the rest

That push triggers .forgejo/workflows/release.yml, which runs the tests, builds a signed app-release.apk, and attaches it to a new Forgejo release under the repo's Releases tab. Download the APK from there and share that link with anyone.

Signing. Every release build must be cryptographically signed with the same key, so Android can prove all updates to the app came from the same publisher. The signing key lives in ~/.android-keystores/scoundrel.jks — outside the repo, gitignored, and not recoverable if lost: back it up somewhere durable, since losing it means the app can never be upgraded in place again (a "new" version would be a different app to Android). CI is handed the same key through 5 repo secrets (Settings → Actions → Secrets):

Secret What it is
RELEASE_KEYSTORE_B64 the keystore file itself, base64-encoded (base64 -i ~/.android-keystores/scoundrel.jks)
RELEASE_STORE_PASSWORD password to open the keystore file
RELEASE_KEY_PASSWORD password for the signing key inside it (same value — PKCS12 keystores don't support separate ones)
RELEASE_KEY_ALIAS the key's name inside the keystore (scoundrel)
FORGEJO_TOKEN a Forgejo access token (repo write scope) so CI can create the release and upload the APK via the API

Before every new release, bump versionCode (+1) and versionName in app/build.gradle.kts — Android uses versionCode to decide whether an installed APK counts as an upgrade.

Installing (sideloading)

There's no app store step. On the phone:

  1. Download the .apk from the release's Releases page.
  2. Tap it. Android will ask to allow installs from that source (browser/file manager) the first time — allow it once.
  3. It installs like any other app.

This is the same mechanism F-Droid and most non-Play-Store apps use.

Obtainium setup

For automatic update checks instead of downloading each release by hand, add this repo to Obtainium. Tap the button below from your phone:

That opens Obtainium's Add App screen with the URL pre-filled, but self-hosted Forgejo instances aren't auto-detected (only codeberg.org is) — you still need to manually set Override Source to Forgejo (Codeberg) before confirming, or it fails with "could not find a suitable release". This applies whether you got here via the button or typed the URL in yourself: https://forgejo.patilla.es/patillacode/scoundrel-android.

Once added, Obtainium polls the Releases page and prompts you to install when a new version is published.