Phases 1-7 complete. The game is playable:
- 8x8 grid with wooden theme, 27 shape templates
- Drag and drop with floating shape above finger + ghost preview on grid
- Score tracking, line clearing with multiplier bonuses
- Game over detection and restart
- ViewModel architecture with StateFlow
- When lines are cleared, don't remove them instantly
- Add a
clearingCells: Set<Pair<Int, Int>>field to GameState (or a separate UI state) - Animation sequence:
- Flash cleared cells white (150ms)
- Fade cleared cells to transparent (200ms)
- Remove cells from grid
- Use Compose
AnimatableorLaunchedEffectwith delay - During animation, block new shape placements
- When points are scored, show floating "+N" text
- Position it near the center of the cleared area
- Animate: rise upward ~40dp while fading out over 800ms
- Use
AnimatedVisibilityor manualAnimatablefor offset + alpha - Gold color for normal clears, brighter/larger for multi-line bonuses
- Short vibration on successful shape placement (
HapticFeedbackType.LongPress) - Stronger double-tap vibration on line clear
- Use
LocalHapticFeedback.current.performHapticFeedback() - No vibration on invalid drop (shape snaps back silently)
- After each placement, check which remaining tray shapes can still fit
- Pass
dimmed = truetoShapePreviewfor shapes that can't fit anywhere - Already supported —
ShapePreviewhas adimmedparameter that reduces alpha to 0.3 - Add
canFitAnywhere(grid, shape): Booleanhelper or reuseisGameOverlogic per-shape
- When drag starts, animate the tray shape scaling up slightly before it disappears
- Add a subtle drop shadow to the floating shape (
graphicsLayer { shadowElevation }) - Consider a slight scale-up (1.1x) on the floating shape to make it feel "lifted"
- Fade-in the overlay (animate background alpha from 0 to 0.7)
- Scale-in the dialog card (0.8 → 1.0 with overshoot)
- Show final score with counting animation (0 → final over 1 second)
- If new high score, add a brief celebration effect (maybe pulsing gold text)
- Add to
libs.versions.toml:datastorePreferences = "1.1.4"androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" } - Add to
app/build.gradle.kts:implementation(libs.androidx.datastore.preferences)
- Create
data/package - Use
DataStore<Preferences>with a singleintPreferencesKey("high_score") - Functions:
suspend fun getHighScore(): Intsuspend fun saveHighScore(score: Int)val highScoreFlow: Flow<Int>
- Create the DataStore instance as a top-level singleton:
val Context.dataStore by preferencesDataStore(name = "block_puzzle_prefs")
- Inject
DataStoreintoGameViewModel(viaAndroidViewModelor manual injection) - On init: load high score from DataStore into GameState
- On score update: if new high score, save to DataStore
- High score survives app restarts
- Serialize
GameStateto JSON usingkotlinx.serializationor manual JSON - Save on
onPause/ app backgrounding - Restore on
onCreateif saved state exists - Clear saved state on game over (so restarting gives a fresh game)
- Fields to persist:
grid(8x8 array of color enum values)currentShapes(3 nullable shape definitions)scoreisGameOver
- If complexity isn't worth it for a family game, skip 9.4
- Just persist high score (9.2-9.3)
- Starting a fresh game on app reopen is acceptable
- Use Android Studio's Image Asset Studio (right-click
res→ New → Image Asset) - Design: simple colored block arrangement on a dark wood background
- Generates all mipmap densities automatically
- Adaptive icon with foreground + background layers
- Verify
strings.xmlhasapp_name = "Block Puzzle" - Update
versionNameto "1.0.0" inbuild.gradle.kts - Verify
AndroidManifest.xmlhas no unnecessary permissions (should have none)
- Android 12+ has built-in splash screen API
- Set
android:windowSplashScreenBackgroundto dark wood color inthemes.xml - Set
android:windowSplashScreenAnimatedIconto the app icon - Minimal code — just theme configuration
- Generate signing key:
- Android Studio → Build → Generate Signed Bundle / APK
- Create new keystore (
.jksfile) - Store securely — needed for all future updates
- Build release APK:
- Select APK (not Bundle, since we're not using Play Store)
- Sign with the new key
- Enable minification (
isMinifyEnabled = true) + R8 for smaller APK - Add ProGuard rules if needed (Compose usually needs none)
- Test the release APK:
- Install on a real device via
adb install - Verify everything works (no debug-only behavior)
- Install on a real device via
- Option A — Direct APK sharing (simplest):
- Email or chat the
.apkfile to family - Recipients enable "Install from unknown sources" in Settings
- One-click install
- Email or chat the
- Option B — Google Play internal testing (more polished):
- Create a Google Play Developer account ($25 one-time fee)
- Upload as internal test track (up to 100 testers)
- Testers get a link to install via Play Store
- No public listing, no review process
- Easier updates — just upload a new version
- All unit tests pass
- Game plays correctly on emulator
- Game plays correctly on a real device (if available)
- High score persists across restarts
- No crashes on rotation / backgrounding / foregrounding
- App icon looks good in launcher
- APK size is reasonable (should be under 10MB)
- No leftover debug logging or test data