- Kotlin 97.8%
- Shell 2.2%
|
|
||
|---|---|---|
| .woodpecker | ||
| app | ||
| gradle | ||
| .gitignore | ||
| build.gradle.kts | ||
| build.sh | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
Blocks
A Blockudoku-style block puzzle for Android, in Kotlin + Jetpack Compose.
How to play
- The board is a 9×9 grid split into nine 3×3 boxes.
- You get three pieces at a time. Drag them onto the board (they float above your finger so you can see where they'll land). Pieces never rotate.
- A full row, column or 3×3 box is cleared. While you drag, the regions a drop would clear light up.
- When all three pieces are used, three new ones are dealt.
- The game ends when none of the remaining pieces fits anywhere. Pieces that can't fit are greyed out in the tray.
Scoring
| Event | Points |
|---|---|
| Placing a piece | 1 per block |
| Clearing n regions in one move | 18 × n² (18, 72, 162, 288, …) |
| Streak (clears on consecutive moves) | +10 × (streak − 1) |
A move that clears nothing resets the streak. The best score and the game in progress are saved, so closing the app never loses a game.
Pieces
18 shape families: lines of 1–5, 2- and 3-block diagonals, small and large corners, the 2×2 square, L/J, T, S/Z, plus, U and the long T. Every distinct rotation is its own piece. The weights are set per family, so shapes with four orientations don't crowd out the ones with one. The deal is random, but it re-rolls a few times so you don't get a set where nothing fits while a playable set is easy to find.
Code
game/: the rules, in pure Kotlin with no Android dependencies and covered by unit tests (app/src/test).Boardis an immutable grid,Gamehandles placing, clearing, scoring and dealing, andGameCodecsaves and loads a game.ui/: the Compose screen. The board is drawn on aCanvas, and a single pointer handler on the root layout handles dragging from the tray, snapping and dropping.
Build
Builds run inside the ghcr.io/cirruslabs/android-sdk:36 container, so you
don't need a local JDK or Android SDK:
./build.sh # test assembleDebug
./build.sh assembleRelease # R8-minified release APK -> app/build/outputs/apk/release/
adb install app/build/outputs/apk/debug/app-debug.apk
minSdk 33 (Android 13+), targetSdk 36.
assembleRelease works locally without any signing secrets. It produces an
unsigned app-release-unsigned.apk, which is useful for checking that R8
shrinking still builds. CI builds the signed release APKs on tags; see
"Release/signing" below.
Installing
Every version tag gets a signed APK attached to its Forgejo release. There are two ways to install it:
- Direct: download
blocks-<tag>.apkfrom the latest release and open it on the device. You'll be asked once to allow installing apps from your browser or file manager. - Obtainium: add this repo as an app source. Obtainium detects Forgejo releases and picks up each new tag. There is no Play Store listing.
Release/signing (for maintainers)
Release builds are R8-minified and signed with a dedicated app-signing
keystore, never the debug key. The signing config in app/build.gradle.kts
reads the keystore path and passwords from environment variables and is
only applied when they're set. That's why a local ./build.sh assembleRelease succeeds unsigned, and why CI only signs on tag builds, where
the secrets are injected.
One-time: generate the keystore
keytool -genkeypair -v \
-keystore blocks-release.jks \
-alias blocks \
-keyalg RSA -keysize 4096 -validity 10000
Keep the .jks file and its passwords somewhere durable and private, such as
a password manager, never this repo. If you lose it, future releases can't
update installs that were signed with it.
Woodpecker secrets
.woodpecker/build.yaml builds and signs the release APK only on tag events,
using these repo secrets:
| Secret | Value |
|---|---|
blocks_keystore |
base64 -w0 blocks-release.jks output |
blocks_keystore_password |
keystore password |
blocks_key_alias |
key alias (blocks above) |
blocks_key_password |
key password |
gitea_token |
Forgejo API token with write access to releases |
Set them in the Woodpecker UI under the repo's Secrets settings. On a tag push,
CI decodes the keystore to a temp file and runs assembleRelease with
BLOCKS_KEYSTORE, BLOCKS_KEYSTORE_PASSWORD, BLOCKS_KEY_ALIAS and
BLOCKS_KEY_PASSWORD set. It then renames the output to
blocks-${CI_COMMIT_TAG}.apk and attaches it to the matching Forgejo release.