Files
amethyst/docs/live-chess-implementation-status.md
T
2026-02-10 11:58:00 +02:00

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 API
    • ChessEngine.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:

  1. 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)
    }
    
  2. Navigation Routes (ui/navigation/routes/)

    • Add LiveChessGame(gameId: String) route
    • Wire up from challenge card click
    • Pass LiveChessGameState to screen
  3. ChessGameScreen.kt (amethyst/ui/screen/loggedIn/chess/)

    • Wrapper around LiveChessGameScreen composable
    • Connects ViewModel to UI
    • Handles move publishing callback

Priority 2: Event Publishing & Subscriptions

Files to modify:

  1. 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)
    }
    
  2. 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:

  1. 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:

  1. 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
        }
    }
    
  2. 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:

  1. Entry Point: FAB on Chess feed (pending UI implementation)
  2. Display: Visual cards in one feed with borders/icons
  3. Discovery: Open challenges shown in Chess feed (green border)
  4. Tap Action: Navigate directly to full screen (pending nav)
  5. Notifications: In-app badge for counts (pending implementation)
  6. Filter Scope: Meaningful events only (completed games, challenges, endings)
  7. Storage: Local cache + relay sync (pending Room/ViewModel)
  8. 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:

  1. Open Amethyst
  2. Select "Chess" from home feed dropdown
  3. See completed games (Kind 64)
  4. See challenges with colored borders (Kind 30064)
  5. 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:

  1. ChessViewModel - Game state management & event publishing
  2. Navigation - Route to LiveChessGameScreen
  3. Event Subscriptions - Listen for opponent moves
  4. FAB - Add "New Game" button to chess feed
  5. 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.