diff --git a/src/components/Analysis/Highlight.tsx b/src/components/Analysis/Highlight.tsx
index 2edddae1..0751f69e 100644
--- a/src/components/Analysis/Highlight.tsx
+++ b/src/components/Analysis/Highlight.tsx
@@ -1,3 +1,4 @@
+import { Chess } from 'chess.ts'
import { cpToWinrate } from 'src/lib'
import { MoveTooltip } from './MoveTooltip'
import { InteractiveDescription } from './InteractiveDescription'
@@ -54,6 +55,35 @@ export const Highlight: React.FC
- {moveEvaluation?.stockfish - ? `${moveEvaluation.stockfish.model_optimal_cp > 0 ? '+' : ''}${moveEvaluation.stockfish.model_optimal_cp / 100}` - : '...'} + {isCurrentPositionCheckmate + ? 'Checkmate' + : moveEvaluation?.stockfish + ? formatEvaluation( + moveEvaluation.stockfish.model_optimal_cp, + moveEvaluation.stockfish.mate_in, + moveEvaluation.stockfish.is_checkmate, + ) + : '...'}
@@ -386,8 +442,9 @@ export const Highlight: React.FC- {cp > 0 ? '+' : null} - {`${(cp / 100).toFixed(2)}`} + {Math.abs(cp) >= 10000 + ? `${cp > 0 ? '+' : '-'}M${Math.max(1, Math.floor(Math.abs(10000 - Math.abs(cp)) / 100) + 1)}` + : `${cp > 0 ? '+' : ''}${(cp / 100).toFixed(2)}`}
) diff --git a/src/components/Settings/MaiaModelSettings.tsx b/src/components/Settings/MaiaModelSettings.tsx index b2c92ff8..d717ad24 100644 --- a/src/components/Settings/MaiaModelSettings.tsx +++ b/src/components/Settings/MaiaModelSettings.tsx @@ -119,9 +119,12 @@ export const MaiaModelSettings: React.FC = () => { return (- Manage your locally stored Maia chess engine model. The model is downloaded once and stored in your browser for offline use. + Manage your locally stored Maia chess engine model. The model is + downloaded once and stored in your browser for offline use.
- warning - IndexedDB storage is not supported in your browser. Model management features are unavailable. + + warning + + IndexedDB storage is not supported in your browser. Model + management features are unavailable.
Model Status
-{statusDisplay.text}
++ {statusDisplay.text} +
Play sounds when chess pieces are moved or captured
diff --git a/src/lib/analysis.ts b/src/lib/analysis.ts index 208d9a5f..4d445469 100644 --- a/src/lib/analysis.ts +++ b/src/lib/analysis.ts @@ -17,13 +17,29 @@ export function convertBackendEvalToStockfishEval( const cp_relative_vec: { [key: string]: number } = {} let model_optimal_cp = -Infinity let model_move = '' + let bestMateIn: number | undefined = undefined + // Detect mate values and convert them for (const move in possibleMoves) { const cp = possibleMoves[move] + + // Detect mate patterns (±10000 indicates mate) + let mateIn: number | undefined = undefined + if (Math.abs(cp) >= 10000) { + // Estimate mate in moves based on how close to 10000 it is + // The closer to 10000, the faster the mate + const mateDistance = Math.abs(10000 - Math.abs(cp)) + mateIn = + cp > 0 + ? Math.max(1, Math.floor(mateDistance / 100) + 1) + : -Math.max(1, Math.floor(mateDistance / 100) + 1) + } + cp_vec[move] = cp if (cp > model_optimal_cp) { model_optimal_cp = cp model_move = move + bestMateIn = mateIn } } @@ -45,7 +61,9 @@ export function convertBackendEvalToStockfishEval( for (const move in cp_vec_sorted) { const cp = cp_vec_sorted[move] - const winrate = cpToWinrate(cp, false) + // For mate positions, set winrate to 1.0 or 0.0 + const isMate = Math.abs(cp) >= 10000 + const winrate = isMate ? (cp > 0 ? 1.0 : 0.0) : cpToWinrate(cp, false) winrate_vec[move] = winrate if (winrate_vec[move] > max_winrate) { @@ -71,8 +89,13 @@ export function convertBackendEvalToStockfishEval( for (const move in cp_vec_sorted) { cp_vec_sorted[move] *= -1 } + if (bestMateIn !== undefined) { + bestMateIn *= -1 + } } + // We can't easily detect checkmate from backend data without FEN, + // so we'll leave is_checkmate as undefined for backend evaluations return { sent: true, depth: 20, @@ -82,6 +105,8 @@ export function convertBackendEvalToStockfishEval( cp_relative_vec: cp_relative_vec_sorted, winrate_vec: winrate_vec_sorted, winrate_loss_vec: winrate_loss_vec_sorted, + mate_in: bestMateIn, + is_checkmate: undefined, } } diff --git a/src/lib/engine/stockfish.ts b/src/lib/engine/stockfish.ts index 9d544652..f9158761 100644 --- a/src/lib/engine/stockfish.ts +++ b/src/lib/engine/stockfish.ts @@ -173,7 +173,11 @@ class Engine { return } + // Handle mate values properly instead of converting to ±10000 + let mateIn: number | undefined = undefined if (!isNaN(mate) && isNaN(cp)) { + // For mate scores, use a very high cp value for sorting but keep mate info + mateIn = mate cp = mate > 0 ? 10000 : -10000 } @@ -189,11 +193,16 @@ class Engine { For example: - If Stockfish reports CP = 100 (White's advantage) and it's White's turn, we keep CP = 100. - If Stockfish reports CP = 100 (White's advantage) and it's Black's turn, we change CP to -100, indicating that Black is at a disadvantage. + + The same logic applies to mate values - they need to be adjusted for the current player's perspective. */ const board = new Chess(this.fen) const isBlackTurn = board.turn() === 'b' if (isBlackTurn) { cp *= -1 + if (mateIn !== undefined) { + mateIn *= -1 + } } if (this.store[depth]) { @@ -215,7 +224,11 @@ class Engine { ? this.store[depth].model_optimal_cp - cp : cp - this.store[depth].model_optimal_cp - const winrate = cpToWinrate(cp * (isBlackTurn ? -1 : 1), false) + const winrate = mateIn + ? mateIn > 0 + ? 1.0 + : 0.0 + : cpToWinrate(cp * (isBlackTurn ? -1 : 1), false) if (!this.store[depth].winrate_vec) { this.store[depth].winrate_vec = {} @@ -229,7 +242,11 @@ class Engine { winrateVec[move] = winrate } } else { - const winrate = cpToWinrate(cp * (isBlackTurn ? -1 : 1), false) + const winrate = mateIn + ? mateIn > 0 + ? 1.0 + : 0.0 + : cpToWinrate(cp * (isBlackTurn ? -1 : 1), false) this.store[depth] = { depth: depth, @@ -239,6 +256,7 @@ class Engine { cp_relative_vec: { [move]: 0 }, winrate_vec: { [move]: winrate }, winrate_loss_vec: { [move]: 0 }, + mate_in: mateIn, sent: false, } } @@ -279,6 +297,10 @@ class Engine { ) } + // Check if position is checkmate (no legal moves and king in check) + const board = new Chess(this.fen) + this.store[depth].is_checkmate = board.inCheckmate() + this.store[depth].sent = true if (this.evaluationResolver) { this.evaluationResolver(this.store[depth]) diff --git a/src/types/analysis.ts b/src/types/analysis.ts index 14897832..cc40250e 100644 --- a/src/types/analysis.ts +++ b/src/types/analysis.ts @@ -29,6 +29,8 @@ export interface StockfishEvaluation { cp_relative_vec: { [key: string]: number } winrate_vec?: { [key: string]: number } winrate_loss_vec?: { [key: string]: number } + mate_in?: number + is_checkmate?: boolean } export interface CachedEngineAnalysisEntry {