10 KiB
Live Chess Implementation Status
✅ Completed Implementation
1. Chess Engine & Move Validation
- kchesslib dependency added to
quartz/build.gradle.kts:150 - ChessEngine interface with expect/actual pattern:
ChessEngine.kt(commonMain) - Platform-agnostic APIChessEngine.jvmAndroid.kt(jvmAndroid) - kchesslib implementation
- Full move validation: Legal moves, check/checkmate, castling, en passant, pawn promotion
- FEN import/export: Board state serialization
- Move history tracking: SAN notation
2. Interactive UI Components (commons/)
-
InteractiveChessBoard.kt: Click-to-move with visual feedback
- Selected piece highlighting (green)
- Legal move indicators (circles/rings)
- Automatic move validation
- Callback system for move events
-
LiveChessGame.kt: Complete game UI
- NewChessGameDialog (challenge creation)
- LiveChessGameScreen (full game interface)
- GameInfoHeader (turn indicator, game status)
- MoveHistoryDisplay (SAN move list)
- GameControls (Resign, Offer Draw)
3. Nostr Event Protocol (quartz/nip64Chess/)
New Event Kinds:
| Kind | Event | Purpose |
|---|---|---|
| 30064 | LiveChessGameChallengeEvent | Create/accept challenges |
| 30065 | LiveChessGameAcceptEvent | Accept challenge |
| 30066 | LiveChessMoveEvent | Individual moves (SAN + FEN) |
| 30067 | LiveChessGameEndEvent | Game result & termination |
Event Factory Integration (EventFactory.kt:190-193):
- All live chess events registered for automatic parsing
4. Game State Management
- LiveChessGameState.kt: Coordinates engine, Nostr, and UI
- Turn validation
- Move synchronization
- Automatic end detection (checkmate/stalemate)
- PGN generation
- Position mismatch recovery
5. Feed Integration
Chess Feed Filter (TopNavFilterState.kt:130-142):
- Shows Kind 64 (completed games)
- Shows Kind 30064 (challenges - open & directed)
- Shows Kind 30067 (game endings)
- Excludes Kind 30066 (individual moves - too noisy)
Event Rendering (Chess.kt, NoteCompose.kt):
RenderChessGame()- Completed PGN games (Kind 64)RenderLiveChessChallenge()- Challenge cards with visual states:- 🔓 Open challenges (green border)
- 💌 Incoming challenges (orange border)
- ⏳ Sent challenges (gray border)
RenderLiveChessGameEnd()- Game results with PGN
🚧 Remaining Work
Priority 1: Navigation & Game Flow
Files to create:
-
ChessViewModel.kt (amethyst/ui/screen/loggedIn/chess/)
class ChessViewModel(account: Account) { val activeGames: StateFlow<List<LiveChessGameState>> val challenges: StateFlow<List<LiveChessGameChallengeEvent>> val badgeCount: StateFlow<Int> // For in-app notifications fun createChallenge(opponentPubkey: String?, color: Color) fun acceptChallenge(challengeEventId: String) fun publishMove(gameId: String, move: ChessMoveEvent) } -
Navigation Routes (ui/navigation/routes/)
- Add
LiveChessGame(gameId: String)route - Wire up from challenge card click
- Pass LiveChessGameState to screen
- Add
-
ChessGameScreen.kt (amethyst/ui/screen/loggedIn/chess/)
- Wrapper around
LiveChessGameScreencomposable - Connects ViewModel to UI
- Handles move publishing callback
- Wrapper around
Priority 2: Event Publishing & Subscriptions
Files to modify:
-
ChessViewModel.kt - Add relay operations:
// Subscribe to game events fun subscribeToGame(gameId: String) { // Listen for Kind 30066 (moves) with d tag = gameId // Listen for Kind 30067 (game end) with d tag = gameId } // Publish events suspend fun publishChallenge(challenge: ChessGameChallenge) { val event = LiveChessGameChallengeEvent.build(...) account.sendEvent(event) } -
Event Handlers - Process incoming events:
fun handleOpponentMove(moveEvent: LiveChessMoveEvent) { val game = activeGames.find { it.gameId == moveEvent.gameId() } game?.applyOpponentMove(moveEvent.san(), moveEvent.fen()) }
Priority 3: FAB & Challenge Creation
Files to modify:
- HomeScreen.kt or chess-specific screen
var showNewGameDialog by remember { mutableStateOf(false) } Scaffold( floatingActionButton = { if (isChessFeed) { FloatingActionButton( onClick = { showNewGameDialog = true } ) { Icon(Icons.Default.Add, "New Game") } } } ) if (showNewGameDialog) { NewChessGameDialog( onDismiss = { showNewGameDialog = false }, onCreateGame = { opponentPubkey, color -> viewModel.createChallenge(opponentPubkey, color) showNewGameDialog = false } ) }
Priority 4: In-App Badges (Optional)
Files to create:
-
BadgeProvider.kt (commons/)
object ChessBadgeProvider { fun getBadgeCount( challenges: List<LiveChessGameChallengeEvent>, activeGames: List<LiveChessGameState>, userPubkey: String ): Int { val incomingChallenges = challenges.count { it.opponentPubkey() == userPubkey } val yourTurnGames = activeGames.count { it.isPlayerTurn() } return incomingChallenges + yourTurnGames } } -
Badge Display - Add to chess filter icon:
BadgedBox(badge = { Badge { Text("$count") } }) { Icon(/* chess icon */) }
📋 Implementation Checklist
Phase 1: Basic Playability
- Create ChessViewModel
- Add navigation route for LiveChessGameScreen
- Create ChessGameScreen wrapper
- Wire up NewGameDialog → ViewModel.createChallenge()
- Implement event publishing (challenges, moves, game end)
- Test: Create challenge, see it in feed
Phase 2: Two-Player Functionality
- Implement relay subscriptions for game events
- Wire up move synchronization (publish/receive)
- Handle challenge acceptance flow
- Test: Play complete game between two accounts
Phase 3: Polish
- Add FAB to chess feed
- Implement badge counts
- Add "Accept Challenge" button to incoming challenge cards
- Add "View Game" navigation from challenge/game-end cards
- Handle edge cases (network errors, position desync, abandoned games)
Phase 4: Persistence (Optional)
- Create Room entities for active games
- Store games locally for offline viewing
- Sync on app start
- Background service for "your turn" notifications
🎯 UX Decisions Implemented
Based on user choices:
- ✅ Entry Point: FAB on Chess feed (pending UI implementation)
- ✅ Display: Visual cards in one feed with borders/icons
- ✅ Discovery: Open challenges shown in Chess feed (green border)
- ✅ Tap Action: Navigate directly to full screen (pending nav)
- ⏳ Notifications: In-app badge for counts (pending implementation)
- ✅ Filter Scope: Meaningful events only (completed games, challenges, endings)
- ⏳ Storage: Local cache + relay sync (pending Room/ViewModel)
- ✅ Input: Click-to-move (already implemented)
🔧 Quick Start for Development
Test the Chess Engine:
val engine = ChessEngine()
engine.reset()
val result = engine.makeMove("e2", "e4")
println("Move success: ${result.success}, SAN: ${result.san}")
Test the Interactive Board:
@Composable
fun TestScreen() {
val engine = remember { ChessEngine().apply { reset() } }
InteractiveChessBoard(
engine = engine,
onMoveMade = { san -> println("Move made: $san") }
)
}
View Chess Feed:
- Open Amethyst
- Select "Chess" from home feed dropdown
- See completed games (Kind 64)
- See challenges with colored borders (Kind 30064)
- See game endings with results (Kind 30067)
📁 File Structure
AmethystMultiplatform/
├── quartz/ # Protocol layer (KMP)
│ ├── src/commonMain/kotlin/.../nip64Chess/
│ │ ├── ChessEngine.kt ✅ Engine interface
│ │ ├── ChessPosition.kt ✅ Board state
│ │ ├── ChessMove.kt ✅ Move representation
│ │ ├── ChessGame.kt ✅ Game model
│ │ ├── PGNParser.kt ✅ PGN parsing (31 tests)
│ │ ├── ChessGameEvent.kt ✅ Kind 64
│ │ ├── LiveChessEvents.kt ✅ Data classes
│ │ ├── LiveChessGameEvents.kt ✅ Kind 30064-30067
│ │ └── LiveChessGameState.kt ✅ Game coordinator
│ └── src/jvmAndroid/kotlin/.../nip64Chess/
│ └── ChessEngine.jvmAndroid.kt ✅ kchesslib impl
│
├── commons/ # Shared UI (Android/Desktop)
│ └── src/main/java/.../commons/chess/
│ ├── ChessBoard.kt ✅ Static board
│ ├── InteractiveChessBoard.kt ✅ Click-to-move
│ ├── ChessGameViewer.kt ✅ PGN playback
│ ├── MoveNavigator.kt ✅ Move controls
│ ├── PGNMetadata.kt ✅ Game info
│ └── LiveChessGame.kt ✅ Dialog + screen
│
├── amethyst/ # Android app
│ └── src/main/java/.../amethyst/
│ ├── ui/note/types/
│ │ └── Chess.kt ✅ Event renderers (3 types)
│ ├── ui/note/
│ │ └── NoteCompose.kt ✅ Event dispatching
│ ├── ui/screen/
│ │ ├── TopNavFilterState.kt ✅ Chess filter (3 kinds)
│ │ └── loggedIn/chess/ ⏳ TODO: ViewModels, screens
│ └── ui/navigation/routes/ ⏳ TODO: Navigation
│
└── docs/
└── live-chess-implementation-status.md ✅ This file
🚀 Next Steps
To make chess fully playable, implement in this order:
- ChessViewModel - Game state management & event publishing
- Navigation - Route to LiveChessGameScreen
- Event Subscriptions - Listen for opponent moves
- FAB - Add "New Game" button to chess feed
- Test - Play a complete game between two test accounts
The foundation is complete and tested. The remaining work is "wiring" - connecting the chess engine and UI to Amethyst's existing relay/ViewModel infrastructure.
Status: Core chess functionality fully implemented. Integration layer partially complete. Estimated 4-6 hours of focused work to reach playable state.