Rivers laid out from the centre of an 18x18 board on 1x3 tiles, each with three mouths that must line up with whatever they touch. One side runs water north and south, the other east and west, and a side's score is one edge multiplied by the other — so reaching only one of your two edges is worth nothing, which is the whole tension of the game. Two things fell out of the geometry rather than needing to be tabulated. The tile set is generated: three mouths chosen from the eight places one can sit on a 1x3 tile gives 56 arrangements, turning a tile end for end pairs them off with none fixed, so there are exactly 28 distinct tiles and the box's 84 is three of each. And because every tile joins the network by a mouth while all three of its own mouths interconnect, the board is always one connected river system — so scoring never traces connectivity, and a mouth at the edge is an exit by construction. Both properties are asserted over whole games. The placement check is one comparison doing two jobs: where a new tile abuts an old one, both sides must agree. That refuses a river dead-ending against stone and, read the other way, refuses a blank flank that would wall off a river already down. 18x18 comes out under 20dp a cell on a phone, too fine to tap, so laying a tile is a tap to line it up and a button to commit. Tapping the same cell again cycles the other legal ways the tile could cover it, which is also how it gets turned — a rotate control would have landed on illegal positions. A tap on a cell the tile cannot cover is ignored rather than treated as a deselection, so a fingertip one cell wide of the mark does not throw away a placement. Two AI settings, not three. A blocking-heavy variant was written and dropped because it played the full setting dead even over 30 games; shipping it as a step up would have been a claim that could not be backed. What ships is held to measured numbers: full strength won 20 of 20 against random placement from either axis, gentle beats random about three games in four and loses to full strength about four in five, and random against random comes out even, which is what makes the rest of those numbers mean anything. The reconstruction's uncertainties stay called out in the rules doc rather than being smoothed over now that the code exists: the 18x18 board reading, the marked exits placed at 3, 8 and 13 by choice, and the three-player flood variant left out because nothing found says how the flood scores. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015pyhC1KyFkQk6hUnnX2Tof
9.2 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 nine games are playable end to end:
| Playable | Euchre, Kaiser, President, Golf, Wizard, Crazy 8s, Sequence, Chess, Ta Yü |
| Listed, not yet implemented | Mastermind, Backgammon, Checkers |
Unimplemented games appear in the menu marked Coming soon on dev builds
only — a production release lists just the games that can actually be played,
so nobody installs it and taps into a dead end. The menu decides this from the
installed APK's own version name, so it follows the build rather than the
channel selected for future updates. 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
Branding
The launcher icon is a single square of the Wallace tartan set on the bias, and the app opens on the same cloth carrying the name in a Celtic uncial hand for two seconds before fading into the menu.
The tartan is drawn, not an image: ui/theme/Tartan.kt lays the sett down as
warp and then as weft at half opacity, which is what produces the blended
squares instead of a flat grid. The icon is the same construction as a vector
drawable, so it stays sharp at every density and needs no PNGs.
Two honest caveats:
- The sett is the published threadcount:
K/4 R32 K32 Y/4. The slashes are pivots, soWallaceSettholds the half-sett and reflects it out toK4 R32 K32 Y4 K32 R32— 136 threads, about half black, half red, with a yellow line worth 3%. Two details define the pattern and are easy to get backwards: the yellow overstripe runs down the centre of a wide black band, and the narrow black guard sits between two red blocks. The icon is generated from the same numbers, so the icon and the splash cannot drift. Weave scale is derived from the sett rather than fixed, so changing the threadcount rescales both instead of silently zooming in. - The typeface is bundled under a licence. Uncial Antiqua, © 2011 Brian J. Bonislawsky DBA Astigmatic, Reserved Font Name "Uncial Antiqua", used under the SIL Open Font License 1.1. The full licence is at docs/licenses/UncialAntiqua-OFL.txt and must stay with the font — the OFL requires it to be distributed alongside.
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.
The Ta Yü board is the tightest fit of the lot — 18 × 18 cells comes out under 20dp a cell on a phone — which is why laying a tile there is a tap to line the placement up and a button to commit it, rather than a single tap on a target smaller than a fingertip.
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.
Installing without the app
The newest production build is always at this link, which never changes:
https://github.com/adrianoftyriel/ProLibertateGames/releases/latest/download/ProLibertateGames.apk
Every release carries the same APK twice — once named for its version, and once
as plain ProLibertateGames.apk, because GitHub's /releases/latest/download/
redirect needs a file name that stays put. The in-app updater deliberately picks
the versioned copy, so an installed build can always be traced back to its tag.
There is no equivalent link for the dev channel: /releases/latest skips
prereleases by design, and that is exactly what keeps a production install from
being handed a dev build. Dev builds are reached through the in-app updater with
the channel set to dev, or from the releases page.
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:
- Euchre
- Sequence — note the caveat about the board layout
- Kaiser
- President
- Golf
- Wizard
- Crazy 8s
- Chess
- Ta Yü — note the caveats about the reconstruction: the game is out of print and the rulebook could not be obtained, so the document says which parts are attested and which were derived
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
kaiser/ Kaiser model, bidding, rules, AI
sequence/ Sequence model, board, rules, AI
president/ President model, rules, AI
golf/ Golf model, scoring, rules, AI
wizard/ Wizard model, rules, AI
crazyeights/ Crazy 8s model, rules, AI
chess/ Chess model, FEN, move generation, search
tayu/ Ta Yü tile geometry, river rules, scoring, 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.