8e2afb93f3
- NCWF-5: scaffold /analysis route with ChessBoard viewer and navigation - NCWF-6: FEN / PGN / Game-ID input form with depth selector - NCWF-7: extend GameApiService with analyzePosition(); add AnalysisService with game-wide annotation pipeline; proxy /api/analysis -> :8087 - NCWF-8: EvalTimelineComponent — SVG win-chance chart per ply - NCWF-9: AnnotatedMoveListComponent — quality labels (!! ! ?! ? ??) derived from win-chance delta Also fix pre-existing app.spec.ts failure (missing provideHttpClient). Apply project-wide prettier formatting pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
import { Injectable, DestroyRef, inject } from '@angular/core';
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
import { GameApiService } from './game-api.service';
|
|
import { getPieceAtSquare, isPieceColor } from '../core/chess/fen.utils';
|
|
import { GameState, LegalMove } from '../models/game.models';
|
|
|
|
export interface BoardSelection {
|
|
selectedSquare: string | null;
|
|
highlightedSquares: string[];
|
|
selectedSquareMoves: LegalMove[];
|
|
}
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class BoardSelectionService {
|
|
private readonly gameApi = inject(GameApiService);
|
|
private readonly destroyRef = inject(DestroyRef);
|
|
|
|
handleSquareSelection(
|
|
square: string,
|
|
gameId: string,
|
|
state: GameState | null,
|
|
currentSelection: BoardSelection,
|
|
onMovesLoaded: (moves: LegalMove[]) => void,
|
|
onError: (error: string) => void,
|
|
): BoardSelection {
|
|
if (!state) {
|
|
return currentSelection;
|
|
}
|
|
|
|
// If clicking on a highlighted square, it's a move
|
|
if (currentSelection.selectedSquare && currentSelection.highlightedSquares.includes(square)) {
|
|
const selectedMove = currentSelection.selectedSquareMoves.find((move) => move.to === square);
|
|
if (selectedMove) {
|
|
return { selectedSquare: null, highlightedSquares: [], selectedSquareMoves: [] };
|
|
}
|
|
return currentSelection;
|
|
}
|
|
|
|
// Check if square has a piece of the correct color
|
|
const piece = getPieceAtSquare(state.fen, square);
|
|
if (!piece || !isPieceColor(piece, state.turn)) {
|
|
return { selectedSquare: null, highlightedSquares: [], selectedSquareMoves: [] };
|
|
}
|
|
|
|
// Load legal moves for this square
|
|
this.gameApi
|
|
.getLegalMoves(gameId, square)
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe({
|
|
next: (response) => {
|
|
onMovesLoaded(response.moves);
|
|
},
|
|
error: () => {
|
|
onError('Could not load legal moves for selected square.');
|
|
},
|
|
});
|
|
|
|
return { selectedSquare: square, highlightedSquares: [], selectedSquareMoves: [] };
|
|
}
|
|
|
|
clearSelection(): BoardSelection {
|
|
return { selectedSquare: null, highlightedSquares: [], selectedSquareMoves: [] };
|
|
}
|
|
}
|