Update channels - ci.yml now publishes each green push to dev as a prerelease tagged v1.0.<n>-dev, so the dev channel has something to install. Workflow artifacts could not serve this: downloading one needs authentication, which the app does not have. - Prereleases specifically, because GitHub's "latest release" endpoint skips them — a production install can never be handed a dev build. Pull requests build and check but never publish. - Settings gains a channel picker. The two channels are numbered by separate workflow run counters, so versions are only compared within a channel; the app reads the -dev suffix in its own versionName to know where it stands, and treats a channel change as an explicit switch. The screen warns that such a switch can be a downgrade Android will refuse. - PLG_RELEASE becomes PLG_CHANNEL, naming the channel rather than a flag. Euchre - Cards in a trick now sit on the side of the table belonging to whoever played them, rotated to face them, labelled You / Left / Partner / Right. Previously they were a flat row tagged with seat numbers, which made it hard to tell who had played what. - Bidding says what it will actually do. As dealer the button reads "Pick it up" rather than "Order up", since the turn card comes to your hand, and a line of explanatory text spells out the consequence either way. Sequence - Playable squares get a much heavier outline, scaled off cell size and in a fixed amber that reads against a white card, a green corner and a chip alike. The old 2dp theme-coloured border was hard to see. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
5.5 KiB
Pro Libertate Games
A collection of card and board games for Android, playable against the computer or against other people over Wi-Fi (LAN) or Bluetooth.
What works today
The app shell is complete and two games are playable end to end:
| Playable | Euchre, Sequence |
| Listed, not yet implemented | Kaiser, President, Golf, Wizard, Crazy 8s, Mastermind, Backgammon, Chess, Checkers |
Unimplemented games appear in the menu marked Coming soon. Adding one means
writing a GameRules implementation, a GameAi, and a screen, then flipping
available in GameCatalog — the menu, lobby, networking, settings and OTA
layers are game-agnostic and need no changes.
Playing with other people
Pick a game, choose Host a game for others to join, and the device advertises itself on every radio it has. Other devices choose Join a game nearby and pick the host from the list. Seats start out filled by the computer and are handed to people as they join, so any mix of humans and AI works — whatever is still marked Computer when the host starts stays AI.
- Wi-Fi (LAN) — both devices must be on the same network. Discovery uses mDNS/NSD; play runs over a TCP socket.
- Bluetooth — devices must already be paired in Android's Bluetooth settings. Play runs over Classic RFCOMM. Scanning for unpaired devices is not implemented.
Both transports can be live at once, so some players can be on Wi-Fi and others on Bluetooth at the same table.
How a match is kept in step
The host is authoritative. Clients send intents ("I would like to play this card") and the host re-validates every one against the rules before it takes effect — a client cannot make an illegal move by sending a malformed message. Each client is then sent a state redacted for its own seat, so opponents' hands and the deck order are never transmitted to devices that should not see them.
Settings
- Sound on/off.
- Animation speed, 0.5×–2×. One multiplier drives card movement and how long the computer appears to think.
- Updates — check on launch, or check on demand, from either of two
channels:
- Production — stable builds, published from
main. - Dev — preview builds, published from
devon every green CI run.
- Production — stable builds, published from
Screen sizes
There are no fixed orientations or hard-coded card sizes. Layouts measure the space they are given: the menu grid reflows by width, the Sequence board is always square and sized to fit whatever is left after the hand, and card widths are a fraction of the smaller screen dimension. Rotation is handled inside the activity, so a hand is not lost when the device turns.
Building
./gradlew assembleDebug # APK at app/build/outputs/apk/debug/
./gradlew testDebugUnitTest # rules engines and AI
Requires JDK 17 and the Android SDK (compileSdk 34).
CI
.github/workflows/ci.yml— builds, tests and lints ondevand on pull requests intodevormain. Its APK artifact is a dev build, versioned1.0.0-dev..github/workflows/release.yml— on push tomain, builds the APK and publishes it as a GitHub Release taggedv1.0.<run-number>. The in-app updater reads that tag, which is why the APK'sversionCodeis the same run number.
Update channels
Both pipelines publish, to two separate channels:
| Channel | Published by | Tag | Marked |
|---|---|---|---|
| Production | release.yml, push to main |
v1.0.<n> |
release |
| Dev | ci.yml, push to dev |
v1.0.<n>-dev |
prerelease |
Dev builds are published as prereleases deliberately: GitHub's "latest release" endpoint skips prereleases, so a production-channel install can never be handed a dev build even by accident. Pull requests build and check but never publish, so a PR from a fork cannot ship anything.
release.yml additionally refuses to run off any branch but main, so a manual
dispatch aimed elsewhere does nothing rather than shipping unreviewed code.
The two channels have independent version sequences — a build's versionCode
is the run number of the workflow that produced it, and the two workflows count
separately. Versions are therefore only ever compared within a channel; the app
reads the -dev suffix in its own versionName to know which channel it is on,
and treats a change of channel as an explicit switch rather than an upgrade.
One consequence: switching channels can be a downgrade as far as Android is
concerned, in which case the install is refused until the current copy is
uninstalled. The settings screen says so when it applies.
The debug keystore is committed on purpose: every build is signed with the same key, so an OTA update installs over the previous one instead of being rejected for a signature mismatch.
Rules
The exact rules implemented, and the options that vary them, are documented per game:
Layout
game/ rules engines and AI — pure Kotlin, no Android, unit-tested
cards/ deck, suits, ranks
engine/ GameRules / GameAi contracts, seats, table config
euchre/ Euchre model, rules, AI
sequence/ Sequence model, board, rules, AI
net/ wire protocol, LAN and Bluetooth transports, lobby, match driver
settings/ DataStore-backed preferences
update/ GitHub Releases OTA updater
ui/ Compose screens
Keeping game/ free of Android types is deliberate: the same code drives the
AI, the host's authoritative state and the JVM unit tests.