No description
  • Kotlin 99.1%
  • Shell 0.9%
Find a file
Rune Jensen 49d7c6ce28
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
Add Sudoku and Danish translation
Sudoku is a third game on the menu: Easy to Expert, generated with a
unique solution, with notes, undo, erase, hints and a 3-mistake limit.

All UI text moved to string resources, with a Danish translation in
values-da that is used when the device language is Danish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:43:09 +00:00
.woodpecker numbers game 2026-09-23 14:25:00 +00:00
app Add Sudoku and Danish translation 2026-09-23 14:43:09 +00:00
gradle/wrapper numbers game 2026-09-23 14:25:00 +00:00
.gitignore numbers game 2026-09-23 14:25:00 +00:00
build.gradle.kts numbers game 2026-09-23 14:25:00 +00:00
build.sh numbers game 2026-09-23 14:25:00 +00:00
gradle.properties numbers game 2026-09-23 14:25:00 +00:00
gradlew numbers game 2026-09-23 14:25:00 +00:00
gradlew.bat numbers game 2026-09-23 14:25:00 +00:00
README.md Add Sudoku and Danish translation 2026-09-23 14:43:09 +00:00
settings.gradle.kts numbers game 2026-09-23 14:25:00 +00:00

Number Games

An Android app (Kotlin + Jetpack Compose) with three number puzzles, picked from a start menu:

  • Number Sums: erase numbers so each row and column adds up to its target. From Medium up the grid is split into colored regions, and each region has its own target sum. Choose Erase or Keep at the bottom, then tap cells. A wrong move costs one of 3 lives. Once a row, column or region is settled, the rest of it fills in automatically. You get 3 hints per puzzle. Every generated puzzle is checked with a solver to make sure it has exactly one solution.
  • Number Match: tap two numbers that are equal or add up to 10. They must be next to each other across, down or diagonally, or have only cleared cells between them. The end of a row also connects to the start of the next. Empty rows are removed. Add numbers copies all remaining numbers to the end of the board (5 per stage). Clearing the board starts the next stage, and your score carries over. Choose a Random start or the Classic 1–19 layout.
  • Sudoku: classic 9×9. Tap a cell, then a digit. Wrong digits stay on the board in red and count as a mistake, and 3 mistakes end the game. Notes switches the pad to pencil marks, which are removed automatically when you place that digit in the same row, column or box. You also get Undo, Erase and 3 hints. Easy to Expert leave 40, 33, 28 or 24 clues, removed in symmetric pairs, and every puzzle has exactly one solution.

The app is in English, or in Danish when the device language is Danish (res/values-da/).

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 26 (Android 8.0+), 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 numbers-<tag>.apk from 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

You don't need a local JDK. keytool ships in the same container image ./build.sh uses. This writes numbers-release.jks to the current directory:

docker run --rm -it \
  -u "$(id -u):$(id -g)" \
  -v "$PWD":/out -w /out \
  ghcr.io/cirruslabs/android-sdk:36 \
  keytool -genkeypair -v \
    -keystore numbers-release.jks \
    -alias numbers \
    -keyalg RSA -keysize 4096 -validity 10000

-it is needed because keytool prompts for the passwords and the certificate's name fields. -u makes the file belong to you instead of root. If you run this from the repo root, .gitignore keeps *.jks from being committed. Even so, move the file out of the repo once the secret is set.

To get the value for the numbers_keystore secret:

base64 -w0 numbers-release.jks

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
numbers_keystore base64 -w0 numbers-release.jks output
numbers_keystore_password keystore password
numbers_key_alias key alias (numbers above)
numbers_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 NUMBERS_KEYSTORE, NUMBERS_KEYSTORE_PASSWORD, NUMBERS_KEY_ALIAS and NUMBERS_KEY_PASSWORD set. It then renames the output to numbers-${CI_COMMIT_TAG}.apk and attaches it to the matching Forgejo release.

Layout

app/src/main/java/dk/jaduer/numbers/
  MainActivity.kt          game selector and navigation
  ui/                      theme, menu, shared header
  sums/                    SumsPuzzle, SumsSolver, SumsGenerator, SumsGame (rules), ViewModel, screen
  match/                   MatchGame (rules), ViewModel, screen
  sudoku/                  Sudoku (solver, generator), SudokuGame (rules), ViewModel, screen
app/src/main/res/values/strings.xml       English strings
app/src/main/res/values-da/strings.xml    Danish strings

The rules (SumsGame.kt, MatchGame.kt, SudokuGame.kt, and the generators and solvers) are plain Kotlin with no Android dependencies, and are covered by the tests in app/src/test.