Compose Multiplatform chess app with full support for all standard chess rules and a 3D view mode, targeting:
- Android
- Desktop (JVM): Linux and macOS
- Web (Wasm)
- iOS
For the desktop target on Linux and macOS, JDK 26 is recommended. The desktop 3D renderer uses a native C++ Filament bridge; after a clean checkout run tools/fetch_filament_desktop.sh to fetch the gitignored Filament desktop payload before desktop builds.
For the desktop target on Linux, install stockfish first:
sudo apt install stockfish # For Ubuntu/Debian
sudo pacman -S stockfish # For Arch
sudo dnf install stockfish # For FedoraFor the desktop target on macOS:
brew install stockfishmacOS, Xcode 16+, and a working project JDK are required.
tools/fetch_filament_ios.shopen iosApp/iosApp.xcodeproj- Run the
iosAppscheme
The Stockfish engine is bundled automatically. The Filament iOS xcframeworks are fetched separately because they are large generated dependencies and are intentionally gitignored.
The project is split into three Gradle modules:
:chess-core— the Compose-free, platform-agnostic chess engine core (all game rules, FEN/UCI/SAN/PGN converters,GameViewModel, and the pure-Kotlin 3D-board math/scene mapping). It has no Compose, no russhwolf/Settings, nojava.lang.Process. Targets: Android, JVM (desktop), iOS (arm64/simulator), JS (IR), and Wasm. Published to GitHub Packages asio.github.ber4444:chess-core(tag-driven: pushchess-core-v*→publish-chess-core.ymlpublishes that version). Consumed by:app(below) and by the React Native port, so there is no duplicated Kotlin across the two repos — bumpchessCoreVersionto pick up core changes.:app— the Compose Multiplatform app: all UI screens, platform glue, Stockfish bridges, and the 3D renderers. Depends on:chess-coreviaapi(project(":chess-core")).:androidApp— thin Android application wrapper (manifest, launcher icons) that depends on:app.
The core↔app boundary is enforced by three seams (don't re-couple them):
Piecehas noassetfield;:appresolves drawables viaPieceAssets.asset().- Core types have no
@Immutable(Compose stability hints);:app's compiler re-infers stability. GameViewModeltakes aGameSnapshotSink(core interface), not the russhwolfCurrentGameStore;:appadapts viaCurrentGameStore.asSnapshotSink().
graph TD
subgraph commonMain ["commonMain (Shared)"]
GS["Game State (GameUiState)"] --> GameScreen["GameScreen (UI)"]
GS --> FC["FenConverter"]
FC --> FEN["FEN String"]
FEN --> BM["Board3DSceneMapper"]
BM --> B3S["Board3DScene (Renderer-Agnostic)"]
GameScreen -. "Toggles 3D view" .-> Renderer
Assets["Assets (chess.glb, IBL .ktx)"] --> Renderer
B3S --> Renderer
Renderer[["Chess3DBoardRenderer Contract"]]
end
subgraph Platforms ["Platform-Specific Renderers"]
Renderer --> Android["Android<br>AndroidSceneViewChessRenderer<br>(Filament / SceneView)"]
Renderer --> iOS["iOS<br>FilamentIosChessRenderer<br>(Filament / Metal)"]
Renderer --> Desktop["Desktop<br>DesktopFilamentChessRenderer<br>(Filament / native C++)"]
Renderer --> Web["Web (Wasm)<br>FilamentWasmChessRenderer<br>(Filament / WebGL)"]
end
%% Styling
classDef common fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px,color:#000;
classDef state fill:#ffffff,stroke:#1e88e5,stroke-width:1px,color:#000;
classDef contract fill:#fff8e1,stroke:#ffb300,stroke-width:2px,color:#000;
classDef platform fill:#fafafa,stroke:#9e9e9e,stroke-width:2px,color:#000;
class commonMain common;
class GS,GameScreen,FC,FEN,BM,B3S,Assets state;
class Renderer contract;
class Android,iOS,Desktop,Web platform;
- Full Chess Rules: The application covers all standard chess rules and includes an explicit draw-by-agreement flow where the Stockfish engine evaluates whether to accept or decline draw offers.
- Game Lifecycle & Persistence: The in-progress game is auto-saved on every move and restored on next launch (board, turn, move list). On game end, the user can Save game (to a persisted Game History) and Share PGN (platform share sheet / file dialog / download). PGN export is full Standard Algebraic Notation with the Seven Tag Roster; paste a saved PGN into lichess.org "Import game" to validate. A History screen lists saved games with a detail view and delete.
- Engine Difficulty: A persisted Easy / Medium / Hard / Max setting (in Settings) weakens or strengthens Stockfish play via the UCI
Skill Leveloption and a per-move think-time budget. Applies to the Stockfish engine on every platform. - Settings & Navigation: A minimal multiplatform navigation host (
AppRoot) switches between the game, History, and Settings screens, with a persisted 3D-board toggle (default on) and the engine-difficulty selector in Settings. - 3D Board View: The app features a playable 3D board with shared camera, tap-to-move, ray picking, and move animation logic. Desktop, iOS, and web share
FilamentEncodedChessRendererfor FEN-to-scene, camera, selection, and transition state; their platform peers only own the Filament surface. Android uses Filament through SceneView (the visual reference); iOS uses Metal-native Filament through a Swift/Obj-C++CAMetalLayerbridge; desktop uses native C++ Filament with a headless swap chain and RGBA readback into Compose; web uses Filament (Wasm) loading the samechess.glbAndroid uses. Seedocs/plans/web-graphics-spike-result.mdanddocs/plans/ios-filament-spike-result.mdfor the spike verdicts. - Stockfish Engine Integrations:
- Android: Pinned to Stockfish 17, as the Stockfish 18 binary exceeds GitHub's 100 MB file limit.
- Desktop: Relies on system-installed binaries (e.g., via
aptorbrew). - Web (Wasm): Uses a lightweight
stockfish-18-lite-single.jsrunning in a Web Worker. - iOS: Wraps
ChessKitEngineusing an async-sync bridge and utilizes NNUE viaEvalFileSmall.
chess-core/src/commonMainthe Compose-free chess engine core (rules, FEN/UCI/SAN/PGN,GameViewModel, board3d math) — published asio.github.ber4444:chess-coreapp/src/commonMainthe Compose UI, persistence, share, and 3D renderer glue (depends on:chess-core)app/src/androidMainAndroid-specific shared implementation and Stockfish integrationandroidApp/src/mainAndroid application manifest that depends on the shared KMP moduleapp/src/desktopMaindesktop launcherapp/src/wasmJsMainweb launcherapp/src/iosMainshared iOS implementationiosApp/Xcode project and Swift adapter
Third-party asset and dependency notices live in THIRD_PARTY_NOTICES.md.
:chess-core is the platform-agnostic chess engine core. It is consumed by :app for the UI/platform glue, and published as io.github.ber4444:chess-core for the React Native repository.
- Dependency split (
apivsimplementation):kotlinx-coroutines-coreis anapidependency becauseStateFlowis exposed onGameViewModel.kermitandkotlinx-serialization-jsonareimplementationdetails. - Public API surface: The core intentionally exposes engine entry points (
GameViewModel,ChessEngine), state types (GameUiState,WinState), the piece model, FEN/PGN converters, the persistence seam (GameSnapshotSink), andboard3dscene types. - Internal implementation: Raw move-generation rules, draw-condition internals, and 3D math helpers are deliberately marked
internaland are completely unreachable by consumers. If a symbol isn't listed above, assume it is internal.
./gradlew :chess-core:checkruns the chess-core test suite across all targets (desktop + iOS sim + JS)./gradlew :chess-core:jsNodeTestruns just the Kotlin/JS tests (the target the React Native port consumes)./gradlew testruns shared unit tests./gradlew :androidApp:assembleDebug :androidApp:installDebugbuilds and installs the Android app./gradlew :app:runlaunches the desktop app./gradlew :app:wasmJsBrowserDevelopmentRunstarts the web target./gradlew :app:wasmJsBrowserDevelopmentWebpackbuilds the web development bundle without starting the dev server./gradlew :app:connectedAndroidDeviceTestruns Android UI tests./gradlew :app:iosSimulatorArm64Testruns iOS Compose UI tests./gradlew :app:desktopTest --tests "*board3d*"runs the 3D desktop tests (DesktopRendererSmokeTest writesbuild/chess3d-*.pngto eyeball the render)./gradlew :chess-core:publishToMavenLocalpublishesio.github.ber4444:chess-coreto the local Maven cache (for local cross-repo iteration)tools/ios_3d_screenshot.shcaptures the real iOS 3D board from a booted simulator
The core is published to GitHub Packages from CI (.github/workflows/publish-chess-core.yml), driven
by a tag. To publish a new version:
git tag -a chess-core-v0.2.0 -m "Publish io.github.ber4444:chess-core:0.2.0"
git push origin chess-core-v0.2.0The version is the tag with the chess-core-v prefix stripped. Consumers (the RN port, or any other
Kotlin project) add the GitHub Packages Maven repo (auth: PAT with read:packages) and depend on
io.github.ber4444:chess-core:<version>.