An Android chess app built with Kotlin and Jetpack Compose. Play a local match against a tunable minimax AI, or drop into famous historical games a few plies from the critical position.
- Three AI levels — Casual, Standard, Expert (minimax search depth 2/3/4 with alpha-beta pruning and quiescence extension).
- Blitz clocks — toggleable per-side timers with quick presets (1 / 3 / 5 / 10 / 15 / 30 min) and automatic timeout detection.
- Historical battles — a bundled dataset of famous games (Fischer, Tal, …) replayed to a fork point so you can try to find the winning continuation. Pick a specific battle or leave the selection on Random.
- Import your own PGN — paste a chess.com or Lichess export into the
importer (
{[%clk ...]}clock annotations are handled) and the game is saved locally and appears alongside the bundled battles. - Local match history — every finished or abandoned match is recorded to a private JSON file and shown on a dedicated History screen with date, time, puzzle name (if any), and result badge (Win / Loss / Draw / Abandoned).
- Six board themes — Wood, Marble, Green, Midnight, Coral, Mono.
- Move + haptic feedback — every move fires a sound sample and a matching vibration, chosen so the tactile weight tracks the event's importance (see the table below). The Sound switch in settings mutes the audio channel while keeping haptics on for silent play.
- Turn-decider coin toss — randomised starting colour with a spinning king animation for non-puzzle games.
- Abandon-match guard — back gesture and the settings button both prompt before discarding an in-progress match (the abandoned game is still recorded in history).
- Edge-to-edge dark UI with status- and nav-bar insets respected.
| Setup | Historical battle | Abandon confirmation |
|---|---|---|
![]() |
![]() |
![]() |
| Midnight theme | Match history | Battle picker |
|---|---|---|
![]() |
![]() |
![]() |
| Import PGN | Save confirmation | Playing an import |
|---|---|---|
![]() |
![]() |
![]() |
ChessBoardState.executeMove classifies every applied move into a
MoveSound
and publishes it on a hot SharedFlow. The game screen collects the flow and
hands each event to
ChessFeedback,
which plays a sample through a pre-loaded SoundPool and triggers a
VibrationEffect on the device's default vibrator.
The classifier picks exactly one category per move (terminal state wins, then check, then castle, then promote, then capture, then quiet move):
| Event | Sample (res/raw/) |
Haptic pattern |
|---|---|---|
MOVE — quiet move |
move_self.mp3 |
One-shot 18 ms, amplitude 80 |
CAPTURE — taking a piece |
capture.mp3 |
One-shot 30 ms, amplitude 160 |
CASTLE — king-side or queen-side |
castle.mp3 |
One-shot 40 ms, amplitude 140 |
PROMOTE — pawn reaches last rank |
promote.mp3 |
EFFECT_CLICK (platform predefined) |
CHECK — opponent's king is attacked |
move_check.mp3 |
Waveform — two 30 ms pulses at max amplitude |
GAME_OVER — checkmate / stalemate / timeout |
notify.mp3 |
EFFECT_DOUBLE_CLICK (platform predefined) |
The SoundPool is allocated per-composition via rememberChessFeedback() and
released through a DisposableEffect so no resources leak when navigating
back to setup. Vibration requires the VIBRATE permission (no runtime
prompt), declared in the manifest; devices without a vibrator silently skip
the haptic half.
The chess feature lives under app/src/main/java/com/umain/uchess/chess/ and
is split into focused, single-responsibility files:
chess/
├── model/ Domain types — PieceColor, PieceType, GameStatus, Piece,
│ Position (+ POSITIONS / getPos helper), Move, ImmutableBoard,
│ ChessPuzzle, GameSettings, ChessScreen,
│ MatchResult, MatchRecord
├── theme/ Design tokens (ChessPalette) and board colour themes (BoardTheme)
├── pgn/ PgnToFen replay engine (SAN + castling + promotion) and
│ PgnImporter that parses chess.com / Lichess PGN exports
├── data/ HistoricalBattleLoader (res/raw/historical_battles.json),
│ MatchHistoryRepository (filesDir/match_history.json),
│ ImportedGamesRepository (filesDir/imported_games.json)
├── ai/ ChessAI — minimax, piece-square tables, quiescence search
├── state/ ChessBoardState — Compose-observable game state holder
├── feedback/ MoveSound events, ChessSoundPlayer (SoundPool),
│ ChessHaptics (Vibrator), rememberChessFeedback()
└── ui/ Composables
├── ChessApp.kt Navigation root (Settings ↔ Game ↔ History
│ ↔ Battles ↔ Import PGN)
├── board/ ChessBoard, ChessSquare
├── game/ ChessGameScreen, PlayerTimer, GameOverDialog,
│ TurnDeciderAnimation, AbandonMatchDialog
├── history/ MatchHistoryScreen
├── battles/ HistoricalBattlesScreen picker, PgnImportScreen
└── settings/ SettingsScreen
Every public type carries KDoc and every composable has at least one
@Preview for inspection in Android Studio.
- Android Studio Narwhal (or newer)
- JDK 17
- A device or emulator running Android 13 (API 33) or later.
./gradlew assembleDebug # build
./gradlew installDebug # install on a connected deviceIn Android Studio, just press Run ▶ after opening the project.
The core chess implementation — board rules, minimax AI, PGN replay, and the original single-file Compose UI — is derived from Kyriakos Georgiopoulos's public gist:
https://gist.github.com/Kyriakos-Georgiopoulos/8e7ac7eda4c43f719d9dcc2fdb4176fa
Licensed under the Apache License, Version 2.0. The gist has since been reorganised into the clean-architecture layout above, and extended with edge-to-edge handling, sound/haptic feedback, a local match history, an abandon-match guard, a battle picker, and chess.com / Lichess PGN import.
Full attribution and license text: THIRD_PARTY_NOTICES.md.
Apache License 2.0 for the derived chess module. See THIRD_PARTY_NOTICES.md for the upstream copyright notice.








